fix: Reolink metadata should not reject media with labels (#1900)
- Related to: #1870
This commit is contained in:
@@ -57,7 +57,7 @@ class BrowseMediaEventViewMedia extends ViewMedia implements EventViewMedia {
|
|||||||
return this._browseMedia.thumbnail;
|
return this._browseMedia.thumbnail;
|
||||||
}
|
}
|
||||||
public getWhat(): string[] | null {
|
public getWhat(): string[] | null {
|
||||||
return null;
|
return this._browseMedia._metadata?.what ?? null;
|
||||||
}
|
}
|
||||||
public getScore(): number | null {
|
public getScore(): number | null {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ export interface BrowseMediaMetadata {
|
|||||||
cameraID: string;
|
cameraID: string;
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
|
what?: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,20 +68,22 @@ export class ReolinkCameraManagerEngine extends BrowseMediaCameraManagerEngine {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title of the form "21:47:03 0:00:44"
|
// Titles may be of the form:
|
||||||
|
// - "21:47:03 0:00:44"
|
||||||
|
// - "21:47:03 0:00:44 Person" (https://github.com/dermotduffy/advanced-camera-card/issues/1870)
|
||||||
|
// - "21:47:03 0:00:44 Vehicle Person" (https://github.com/dermotduffy/advanced-camera-card/issues/1870)
|
||||||
const parts = media.title.split(/ +/);
|
const parts = media.title.split(/ +/);
|
||||||
if (parts.length !== 2) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const startDate = parse(parts[0], 'HH:mm:ss', parent._metadata.startDate);
|
const startDate = parse(parts[0], 'HH:mm:ss', parent._metadata.startDate);
|
||||||
if (!isValidDate(startDate)) {
|
if (!isValidDate(startDate)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const durationMatch = parts[1].match(
|
const durationMatch =
|
||||||
/(?<hours>\d+):(?<minutes>\d+):(?<seconds>\d+)/,
|
parts.length > 1
|
||||||
);
|
? parts[1].match(/(?<hours>\d+):(?<minutes>\d+):(?<seconds>\d+)/)
|
||||||
|
: null;
|
||||||
|
|
||||||
const duration = durationMatch?.groups
|
const duration = durationMatch?.groups
|
||||||
? {
|
? {
|
||||||
hours: Number(durationMatch.groups.hours),
|
hours: Number(durationMatch.groups.hours),
|
||||||
@@ -90,10 +92,19 @@ export class ReolinkCameraManagerEngine extends BrowseMediaCameraManagerEngine {
|
|||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const what =
|
||||||
|
parts.length > 2
|
||||||
|
? parts
|
||||||
|
.splice(2)
|
||||||
|
.map((l) => l.toLowerCase())
|
||||||
|
.sort()
|
||||||
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cameraID: cameraID,
|
cameraID: cameraID,
|
||||||
startDate: startDate,
|
startDate: startDate,
|
||||||
endDate: duration ? add(startDate, duration) : startDate,
|
endDate: duration ? add(startDate, duration) : startDate,
|
||||||
|
...(what && { what: what }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -382,7 +382,7 @@ export const CONF_PERFORMANCE_STYLE_BORDER_RADIUS = `${CONF_PERFORMANCE}.style.b
|
|||||||
|
|
||||||
export const CONF_PROFILES = 'profiles' as const;
|
export const CONF_PROFILES = 'profiles' as const;
|
||||||
|
|
||||||
export const CONF_REMOTE_CONTROL = 'remote_control' as const;
|
const CONF_REMOTE_CONTROL = 'remote_control' as const;
|
||||||
export const CONF_REMOTE_CONTROL_ENTITIES_CAMERA =
|
export const CONF_REMOTE_CONTROL_ENTITIES_CAMERA =
|
||||||
`${CONF_REMOTE_CONTROL}.entities.camera` as const;
|
`${CONF_REMOTE_CONTROL}.entities.camera` as const;
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,28 @@ const TEST_FILES: BrowseMedia = {
|
|||||||
can_expand: false,
|
can_expand: false,
|
||||||
thumbnail: null,
|
thumbnail: null,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '21:35:05 0:00:41 Zebra Person Vehicle',
|
||||||
|
media_class: 'video',
|
||||||
|
media_content_type: 'video',
|
||||||
|
media_content_id:
|
||||||
|
'media-source://reolink/FILE|01J8XHYTNH77WE3C654K03KX1F|0|sub|Rec_20241105_052905_211_S.mp4',
|
||||||
|
children_media_class: null,
|
||||||
|
can_play: true,
|
||||||
|
can_expand: false,
|
||||||
|
thumbnail: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '21:37:00',
|
||||||
|
media_class: 'video',
|
||||||
|
media_content_type: 'video',
|
||||||
|
media_content_id:
|
||||||
|
'media-source://reolink/FILE|01J8XHYTNH77WE3C654K03KX1F|0|sub|Rec_20241105_052905_211_S.mp4',
|
||||||
|
children_media_class: null,
|
||||||
|
can_play: true,
|
||||||
|
can_expand: false,
|
||||||
|
thumbnail: null,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '22:04:49 0:00:35',
|
title: '22:04:49 0:00:35',
|
||||||
media_class: 'video',
|
media_class: 'video',
|
||||||
@@ -363,6 +385,39 @@ describe('ReolinkCameraManagerEngine', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
browseMedia: [
|
browseMedia: [
|
||||||
|
{
|
||||||
|
_metadata: {
|
||||||
|
cameraID: 'office',
|
||||||
|
startDate: new Date('2024-11-04T21:37:00.000Z'),
|
||||||
|
endDate: new Date('2024-11-04T21:37:00.000Z'),
|
||||||
|
},
|
||||||
|
can_expand: false,
|
||||||
|
can_play: true,
|
||||||
|
children_media_class: null,
|
||||||
|
media_class: 'video',
|
||||||
|
media_content_id:
|
||||||
|
'media-source://reolink/FILE|01J8XHYTNH77WE3C654K03KX1F|0|sub|Rec_20241105_052905_211_S.mp4',
|
||||||
|
media_content_type: 'video',
|
||||||
|
thumbnail: null,
|
||||||
|
title: '21:37:00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_metadata: {
|
||||||
|
cameraID: 'office',
|
||||||
|
endDate: new Date('2024-11-04T21:35:46.000Z'),
|
||||||
|
startDate: new Date('2024-11-04T21:35:05.000Z'),
|
||||||
|
what: ['person', 'vehicle', 'zebra'],
|
||||||
|
},
|
||||||
|
can_expand: false,
|
||||||
|
can_play: true,
|
||||||
|
children_media_class: null,
|
||||||
|
media_class: 'video',
|
||||||
|
media_content_id:
|
||||||
|
'media-source://reolink/FILE|01J8XHYTNH77WE3C654K03KX1F|0|sub|Rec_20241105_052905_211_S.mp4',
|
||||||
|
media_content_type: 'video',
|
||||||
|
thumbnail: null,
|
||||||
|
title: '21:35:05 0:00:41 Zebra Person Vehicle',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
_metadata: {
|
_metadata: {
|
||||||
cameraID: 'office',
|
cameraID: 'office',
|
||||||
@@ -720,8 +775,8 @@ describe('ReolinkCameraManagerEngine', () => {
|
|||||||
thumbnail: null,
|
thumbnail: null,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
// Title not matching format.
|
// Title has invalid title (should be timestamp).
|
||||||
title: '21:23:53 0:00:34 _____MALFORMED_____',
|
title: 'AA:BB:CC',
|
||||||
media_class: 'video',
|
media_class: 'video',
|
||||||
media_content_type: 'video',
|
media_content_type: 'video',
|
||||||
media_content_id:
|
media_content_id:
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||||
import { mock } from 'vitest-mock-extended';
|
import { mock } from 'vitest-mock-extended';
|
||||||
import { CardController } from '../../../../src/card-controller/controller';
|
|
||||||
import { WebkitFullScreenProvider } from '../../../../src/card-controller/fullscreen/webkit';
|
import { WebkitFullScreenProvider } from '../../../../src/card-controller/fullscreen/webkit';
|
||||||
import { ConditionStateManager } from '../../../../src/conditions/state-manager';
|
import { ConditionStateManager } from '../../../../src/conditions/state-manager';
|
||||||
import {
|
import {
|
||||||
@@ -14,21 +13,19 @@ const createWebkitVideoElement = (): HTMLVideoElement &
|
|||||||
return document.createElement('video');
|
return document.createElement('video');
|
||||||
};
|
};
|
||||||
|
|
||||||
const setElement = (api: CardController, element: HTMLElement): void => {
|
const createPlayer = (element: HTMLElement): AdvancedCameraCardMediaPlayer => {
|
||||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||||
player.getFullscreenElement.mockReturnValue(element);
|
player.getFullscreenElement.mockReturnValue(element);
|
||||||
|
return player;
|
||||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
|
||||||
createMediaLoadedInfo({
|
|
||||||
player: player,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// @vitest-environment jsdom
|
// @vitest-environment jsdom
|
||||||
describe('WebkitFullScreenProvider', () => {
|
describe('WebkitFullScreenProvider', () => {
|
||||||
afterEach(() => {
|
beforeAll(() => {
|
||||||
vi.restoreAllMocks();
|
vi.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -60,7 +57,12 @@ describe('WebkitFullScreenProvider', () => {
|
|||||||
const element = createWebkitVideoElement();
|
const element = createWebkitVideoElement();
|
||||||
element.webkitDisplayingFullscreen = fullscreen;
|
element.webkitDisplayingFullscreen = fullscreen;
|
||||||
|
|
||||||
setElement(api, element);
|
const player = createPlayer(element);
|
||||||
|
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||||
|
createMediaLoadedInfo({
|
||||||
|
player: player,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
expect(provider.isInFullscreen()).toBe(fullscreen);
|
expect(provider.isInFullscreen()).toBe(fullscreen);
|
||||||
});
|
});
|
||||||
@@ -74,7 +76,12 @@ describe('WebkitFullScreenProvider', () => {
|
|||||||
const element = createWebkitVideoElement();
|
const element = createWebkitVideoElement();
|
||||||
element.webkitSupportsFullscreen = supported;
|
element.webkitSupportsFullscreen = supported;
|
||||||
|
|
||||||
setElement(api, element);
|
const player = createPlayer(element);
|
||||||
|
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||||
|
createMediaLoadedInfo({
|
||||||
|
player: player,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
expect(provider.isSupported()).toBe(supported);
|
expect(provider.isSupported()).toBe(supported);
|
||||||
});
|
});
|
||||||
@@ -89,7 +96,12 @@ describe('WebkitFullScreenProvider', () => {
|
|||||||
element.webkitEnterFullscreen = vi.fn();
|
element.webkitEnterFullscreen = vi.fn();
|
||||||
element.webkitSupportsFullscreen = true;
|
element.webkitSupportsFullscreen = true;
|
||||||
|
|
||||||
setElement(api, element);
|
const player = createPlayer(element);
|
||||||
|
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||||
|
createMediaLoadedInfo({
|
||||||
|
player: player,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
provider.setFullscreen(true);
|
provider.setFullscreen(true);
|
||||||
|
|
||||||
@@ -104,7 +116,12 @@ describe('WebkitFullScreenProvider', () => {
|
|||||||
element.webkitExitFullscreen = vi.fn();
|
element.webkitExitFullscreen = vi.fn();
|
||||||
element.webkitSupportsFullscreen = true;
|
element.webkitSupportsFullscreen = true;
|
||||||
|
|
||||||
setElement(api, element);
|
const player = createPlayer(element);
|
||||||
|
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||||
|
createMediaLoadedInfo({
|
||||||
|
player: player,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
provider.setFullscreen(false);
|
provider.setFullscreen(false);
|
||||||
|
|
||||||
@@ -120,7 +137,12 @@ describe('WebkitFullScreenProvider', () => {
|
|||||||
element.webkitExitFullscreen = vi.fn();
|
element.webkitExitFullscreen = vi.fn();
|
||||||
element.webkitSupportsFullscreen = false;
|
element.webkitSupportsFullscreen = false;
|
||||||
|
|
||||||
setElement(api, element);
|
const player = createPlayer(element);
|
||||||
|
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||||
|
createMediaLoadedInfo({
|
||||||
|
player: player,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
provider.setFullscreen(true);
|
provider.setFullscreen(true);
|
||||||
provider.setFullscreen(false);
|
provider.setFullscreen(false);
|
||||||
@@ -138,7 +160,12 @@ describe('WebkitFullScreenProvider', () => {
|
|||||||
element.webkitEnterFullscreen = vi.fn();
|
element.webkitEnterFullscreen = vi.fn();
|
||||||
element.webkitExitFullscreen = vi.fn();
|
element.webkitExitFullscreen = vi.fn();
|
||||||
|
|
||||||
setElement(api, element);
|
const player = createPlayer(element);
|
||||||
|
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||||
|
createMediaLoadedInfo({
|
||||||
|
player: player,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
provider.setFullscreen(true);
|
provider.setFullscreen(true);
|
||||||
provider.setFullscreen(false);
|
provider.setFullscreen(false);
|
||||||
@@ -204,8 +231,6 @@ describe('WebkitFullScreenProvider', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should play the video after fullscreen ends', () => {
|
it('should play the video after fullscreen ends', () => {
|
||||||
vi.useFakeTimers();
|
|
||||||
|
|
||||||
const handler = vi.fn();
|
const handler = vi.fn();
|
||||||
const api = createCardAPI();
|
const api = createCardAPI();
|
||||||
const stateManager = new ConditionStateManager();
|
const stateManager = new ConditionStateManager();
|
||||||
@@ -218,10 +243,13 @@ describe('WebkitFullScreenProvider', () => {
|
|||||||
const element = createWebkitVideoElement();
|
const element = createWebkitVideoElement();
|
||||||
element.play = vi.fn();
|
element.play = vi.fn();
|
||||||
|
|
||||||
setElement(api, element);
|
const player = createPlayer(element);
|
||||||
|
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||||
|
createMediaLoadedInfo({
|
||||||
|
player: player,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
|
||||||
player.getFullscreenElement.mockReturnValue(element);
|
|
||||||
const mediaLoadedInfo = createMediaLoadedInfo({ player });
|
const mediaLoadedInfo = createMediaLoadedInfo({ player });
|
||||||
|
|
||||||
stateManager.setState({ mediaLoadedInfo });
|
stateManager.setState({ mediaLoadedInfo });
|
||||||
|
|||||||
Reference in New Issue
Block a user