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;
|
||||
}
|
||||
public getWhat(): string[] | null {
|
||||
return null;
|
||||
return this._browseMedia._metadata?.what ?? null;
|
||||
}
|
||||
public getScore(): number | null {
|
||||
return null;
|
||||
|
||||
@@ -2,4 +2,5 @@ export interface BrowseMediaMetadata {
|
||||
cameraID: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
what?: string[];
|
||||
}
|
||||
|
||||
@@ -68,20 +68,22 @@ export class ReolinkCameraManagerEngine extends BrowseMediaCameraManagerEngine {
|
||||
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(/ +/);
|
||||
if (parts.length !== 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const startDate = parse(parts[0], 'HH:mm:ss', parent._metadata.startDate);
|
||||
if (!isValidDate(startDate)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const durationMatch = parts[1].match(
|
||||
/(?<hours>\d+):(?<minutes>\d+):(?<seconds>\d+)/,
|
||||
);
|
||||
const durationMatch =
|
||||
parts.length > 1
|
||||
? parts[1].match(/(?<hours>\d+):(?<minutes>\d+):(?<seconds>\d+)/)
|
||||
: null;
|
||||
|
||||
const duration = durationMatch?.groups
|
||||
? {
|
||||
hours: Number(durationMatch.groups.hours),
|
||||
@@ -90,10 +92,19 @@ export class ReolinkCameraManagerEngine extends BrowseMediaCameraManagerEngine {
|
||||
}
|
||||
: null;
|
||||
|
||||
const what =
|
||||
parts.length > 2
|
||||
? parts
|
||||
.splice(2)
|
||||
.map((l) => l.toLowerCase())
|
||||
.sort()
|
||||
: null;
|
||||
|
||||
return {
|
||||
cameraID: cameraID,
|
||||
startDate: 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_REMOTE_CONTROL = 'remote_control' as const;
|
||||
const CONF_REMOTE_CONTROL = 'remote_control' as const;
|
||||
export const CONF_REMOTE_CONTROL_ENTITIES_CAMERA =
|
||||
`${CONF_REMOTE_CONTROL}.entities.camera` as const;
|
||||
|
||||
|
||||
@@ -143,6 +143,28 @@ const TEST_FILES: BrowseMedia = {
|
||||
can_expand: false,
|
||||
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',
|
||||
media_class: 'video',
|
||||
@@ -363,6 +385,39 @@ describe('ReolinkCameraManagerEngine', () => {
|
||||
},
|
||||
{
|
||||
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: {
|
||||
cameraID: 'office',
|
||||
@@ -720,8 +775,8 @@ describe('ReolinkCameraManagerEngine', () => {
|
||||
thumbnail: null,
|
||||
children: [
|
||||
{
|
||||
// Title not matching format.
|
||||
title: '21:23:53 0:00:34 _____MALFORMED_____',
|
||||
// Title has invalid title (should be timestamp).
|
||||
title: 'AA:BB:CC',
|
||||
media_class: 'video',
|
||||
media_content_type: 'video',
|
||||
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 { CardController } from '../../../../src/card-controller/controller';
|
||||
import { WebkitFullScreenProvider } from '../../../../src/card-controller/fullscreen/webkit';
|
||||
import { ConditionStateManager } from '../../../../src/conditions/state-manager';
|
||||
import {
|
||||
@@ -14,21 +13,19 @@ const createWebkitVideoElement = (): HTMLVideoElement &
|
||||
return document.createElement('video');
|
||||
};
|
||||
|
||||
const setElement = (api: CardController, element: HTMLElement): void => {
|
||||
const createPlayer = (element: HTMLElement): AdvancedCameraCardMediaPlayer => {
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
player.getFullscreenElement.mockReturnValue(element);
|
||||
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
}),
|
||||
);
|
||||
return player;
|
||||
};
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('WebkitFullScreenProvider', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
beforeAll(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
@@ -60,7 +57,12 @@ describe('WebkitFullScreenProvider', () => {
|
||||
const element = createWebkitVideoElement();
|
||||
element.webkitDisplayingFullscreen = fullscreen;
|
||||
|
||||
setElement(api, element);
|
||||
const player = createPlayer(element);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(provider.isInFullscreen()).toBe(fullscreen);
|
||||
});
|
||||
@@ -74,7 +76,12 @@ describe('WebkitFullScreenProvider', () => {
|
||||
const element = createWebkitVideoElement();
|
||||
element.webkitSupportsFullscreen = supported;
|
||||
|
||||
setElement(api, element);
|
||||
const player = createPlayer(element);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(provider.isSupported()).toBe(supported);
|
||||
});
|
||||
@@ -89,7 +96,12 @@ describe('WebkitFullScreenProvider', () => {
|
||||
element.webkitEnterFullscreen = vi.fn();
|
||||
element.webkitSupportsFullscreen = true;
|
||||
|
||||
setElement(api, element);
|
||||
const player = createPlayer(element);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
}),
|
||||
);
|
||||
|
||||
provider.setFullscreen(true);
|
||||
|
||||
@@ -104,7 +116,12 @@ describe('WebkitFullScreenProvider', () => {
|
||||
element.webkitExitFullscreen = vi.fn();
|
||||
element.webkitSupportsFullscreen = true;
|
||||
|
||||
setElement(api, element);
|
||||
const player = createPlayer(element);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
}),
|
||||
);
|
||||
|
||||
provider.setFullscreen(false);
|
||||
|
||||
@@ -120,7 +137,12 @@ describe('WebkitFullScreenProvider', () => {
|
||||
element.webkitExitFullscreen = vi.fn();
|
||||
element.webkitSupportsFullscreen = false;
|
||||
|
||||
setElement(api, element);
|
||||
const player = createPlayer(element);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
}),
|
||||
);
|
||||
|
||||
provider.setFullscreen(true);
|
||||
provider.setFullscreen(false);
|
||||
@@ -138,7 +160,12 @@ describe('WebkitFullScreenProvider', () => {
|
||||
element.webkitEnterFullscreen = 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(false);
|
||||
@@ -204,8 +231,6 @@ describe('WebkitFullScreenProvider', () => {
|
||||
});
|
||||
|
||||
it('should play the video after fullscreen ends', () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const handler = vi.fn();
|
||||
const api = createCardAPI();
|
||||
const stateManager = new ConditionStateManager();
|
||||
@@ -218,10 +243,13 @@ describe('WebkitFullScreenProvider', () => {
|
||||
const element = createWebkitVideoElement();
|
||||
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 });
|
||||
|
||||
stateManager.setState({ mediaLoadedInfo });
|
||||
|
||||
Reference in New Issue
Block a user