fix: Reolink metadata should not reject media with labels (#1900)

- Related to: #1870
This commit is contained in:
Dermot Duffy
2025-02-17 20:13:21 -08:00
committed by GitHub
parent c66c3ec919
commit bb147a45fd
6 changed files with 128 additions and 33 deletions
+1 -1
View File
@@ -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;
+1
View File
@@ -2,4 +2,5 @@ export interface BrowseMediaMetadata {
cameraID: string;
startDate: Date;
endDate: Date;
what?: string[];
}
+18 -7
View File
@@ -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
View File
@@ -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 });