From 4af69fe2da79c36e14befd64124800077dcd02c0 Mon Sep 17 00:00:00 2001 From: Undyingwraith Date: Sat, 14 Dec 2024 05:57:15 +0100 Subject: [PATCH] Improve Movie indexer and make indexing more generic --- packages/core/src/Regexes.ts | 2 +- packages/core/src/Services/IndexManager.ts | 12 ++++++++---- .../core/src/Services/Indexer/IIndexFetcher.ts | 16 ++++++++++++++++ .../src/Services/Indexer/MovieIndexFetcher.ts | 13 ++++++++++--- .../src/Services/Indexer/SeriesIndexFetcher.ts | 6 +++++- packages/core/tests/Regexes.test.ts | 18 ++++++++++++++++++ .../src/MetaData/Features/HasYear.ts | 7 +++++++ .../interfaces/src/MetaData/Features/index.ts | 7 +++---- .../src/MetaData/Library/ILibrary.ts | 12 ------------ .../interfaces/src/MetaData/Library/index.ts | 1 - .../molecules/FileGridItem/FileGridItem.tsx | 6 +++--- 11 files changed, 71 insertions(+), 29 deletions(-) create mode 100644 packages/interfaces/src/MetaData/Features/HasYear.ts diff --git a/packages/core/src/Regexes.ts b/packages/core/src/Regexes.ts index 77f6804..de96be4 100644 --- a/packages/core/src/Regexes.ts +++ b/packages/core/src/Regexes.ts @@ -1,5 +1,5 @@ export const Regexes = { - VideoFile: /([\w\s]+)(?: \((\d{4})\))?.mpd$/, + VideoFile: /([\w\s':.-]+)(?: \((\d{4})\))?.mpd$/, Thumbnail: /thumb\d*\.(jpg|jpeg|png)$/, Poster: /poster\d*\.(jpg|jpeg|png)$/, }; diff --git a/packages/core/src/Services/IndexManager.ts b/packages/core/src/Services/IndexManager.ts index b238628..98d40ae 100644 --- a/packages/core/src/Services/IndexManager.ts +++ b/packages/core/src/Services/IndexManager.ts @@ -1,8 +1,7 @@ import { Signal } from '@preact/signals-core'; import { inject, injectable, postConstruct, preDestroy } from 'inversify'; -import { IIndexManager, IIpfsService, IIpfsServiceSymbol, ILibrary, ILibraryIndex, IObjectStore, IObjectStoreSymbol, IProfile, IProfileSymbol, ITask, ITranslationService, ITranslationServiceSymbol, isMovieLibrary, isSeriesLibrary } from 'ipmc-interfaces'; -import { MovieIndexFetcher, SeriesIndexFetcher } from './Indexer'; -import { ITaskManager, ITaskManagerSymbol } from 'ipmc-interfaces'; +import { IIndexManager, IIpfsService, IIpfsServiceSymbol, ILibrary, ILibraryIndex, IObjectStore, IObjectStoreSymbol, IProfile, IProfileSymbol, ITask, ITaskManager, ITaskManagerSymbol, ITranslationService, ITranslationServiceSymbol } from 'ipmc-interfaces'; +import { IIndexFetcher, MovieIndexFetcher, SeriesIndexFetcher } from './Indexer'; @injectable() export class IndexManager implements IIndexManager { @@ -13,6 +12,9 @@ export class IndexManager implements IIndexManager { @inject(ITaskManagerSymbol) private readonly taskManager: ITaskManager, @inject(ITranslationServiceSymbol) private readonly translationService: ITranslationService, ) { + this.indexers.push(new MovieIndexFetcher(this.ipfs)); + this.indexers.push(new SeriesIndexFetcher(this.ipfs)); + for (const lib of this.profile.libraries) { this.libraries.set(lib.name, new Signal(lib)); const indexSignal = new Signal | undefined>(this.objectStore.get(this.getIndexStorageKey(lib.name))); @@ -66,7 +68,7 @@ export class IndexManager implements IIndexManager { if (library.upstream != undefined && index != undefined) { try { const cid = await this.ipfs.resolve(library.upstream); - const indexer = isMovieLibrary(library) ? new MovieIndexFetcher(this.ipfs) : isSeriesLibrary(library) ? new SeriesIndexFetcher(this.ipfs) : undefined; + const indexer = this.indexers.find(i => i.canIndex(library)); if (index.value?.cid != cid || indexer?.version !== index.value.indexer) { if (indexer == undefined) { throw new Error(`Unknown library type [${library.type}]`); @@ -86,6 +88,8 @@ export class IndexManager implements IIndexManager { } } + private indexers: IIndexFetcher[] = []; + private libraries = new Map>(); private updates = new Map>(); diff --git a/packages/core/src/Services/Indexer/IIndexFetcher.ts b/packages/core/src/Services/Indexer/IIndexFetcher.ts index 65a5ae0..03c9912 100644 --- a/packages/core/src/Services/Indexer/IIndexFetcher.ts +++ b/packages/core/src/Services/Indexer/IIndexFetcher.ts @@ -1,4 +1,20 @@ +import { ILibrary } from 'ipmc-interfaces'; + export interface IIndexFetcher { + /** + * Fetches the index of the specified CID. + * @param cid cid to index. + */ fetchIndex(cid: string): Promise; + + /** + * Checks wheter the indexer can handler specified {@link ILibrary}. + * @param library library to check compatibility for. + */ + canIndex(library: ILibrary): boolean; + + /** + * Version of the indexer. + */ version: string; } diff --git a/packages/core/src/Services/Indexer/MovieIndexFetcher.ts b/packages/core/src/Services/Indexer/MovieIndexFetcher.ts index 9d930d4..6d7f629 100644 --- a/packages/core/src/Services/Indexer/MovieIndexFetcher.ts +++ b/packages/core/src/Services/Indexer/MovieIndexFetcher.ts @@ -1,4 +1,4 @@ -import { IFileInfo, IIpfsService, IMovieMetaData } from 'ipmc-interfaces'; +import { IFileInfo, IIpfsService, ILibrary, IMovieMetaData } from 'ipmc-interfaces'; import { Regexes } from '../../Regexes'; import { IIndexFetcher } from './IIndexFetcher'; @@ -24,16 +24,23 @@ export class MovieIndexFetcher implements IIndexFetcher { public async extractMovieMetaData(node: IIpfsService, entry: IFileInfo, skeleton?: any): Promise { const files = (await this.node.ls(entry.cid)).filter(f => f.type == 'file'); - const videoFile = files.find(f => f.name.endsWith('.mpd')); + const videoFile = files.find(f => Regexes.VideoFile.exec(f.name) != null); if (!videoFile) throw new Error('Failed to find video file in ' + entry.name + '|' + entry.cid); + const videoData = Regexes.VideoFile.exec(videoFile.name)!; + return { ...entry, - title: videoFile.name.substring(0, videoFile.name.lastIndexOf('.')), + title: videoData[1], + year: videoData[2] != null ? parseInt(videoData[2]) : undefined, video: videoFile, thumbnails: files.filter(f => Regexes.Thumbnail.exec(f.name) != null), posters: files.filter(f => Regexes.Poster.exec(f.name) != null), }; } + + public canIndex(library: ILibrary): boolean { + return library.type === 'movie'; + } } diff --git a/packages/core/src/Services/Indexer/SeriesIndexFetcher.ts b/packages/core/src/Services/Indexer/SeriesIndexFetcher.ts index 47bf536..315918c 100644 --- a/packages/core/src/Services/Indexer/SeriesIndexFetcher.ts +++ b/packages/core/src/Services/Indexer/SeriesIndexFetcher.ts @@ -1,4 +1,4 @@ -import { IEpisodeMetaData, IFileInfo, IIpfsService, ISeasonMetaData, ISeriesMetaData } from 'ipmc-interfaces'; +import { IEpisodeMetaData, IFileInfo, IIpfsService, ILibrary, ISeasonMetaData, ISeriesMetaData } from 'ipmc-interfaces'; import { Regexes } from '../../Regexes'; import { IIndexFetcher } from './IIndexFetcher'; @@ -72,4 +72,8 @@ export class SeriesIndexFetcher implements IIndexFetcher { return episode; } + + public canIndex(library: ILibrary): boolean { + return library.type === 'series'; + } } diff --git a/packages/core/tests/Regexes.test.ts b/packages/core/tests/Regexes.test.ts index 736761f..db0a48b 100644 --- a/packages/core/tests/Regexes.test.ts +++ b/packages/core/tests/Regexes.test.ts @@ -14,6 +14,24 @@ describe('Regexes', () => { expect(res2[0]).toEqual('Sample Movie.mpd'); expect(res2[1]).toEqual('Sample Movie'); expect(res2[2]).toBe(undefined); + + const res3 = Regexes.VideoFile.exec('Sample Movie: Subtitle (2015).mpd'); + expect(res3).not.toBeNull(); + expect(res3[0]).toEqual('Sample Movie: Subtitle (2015).mpd'); + expect(res3[1]).toEqual('Sample Movie: Subtitle'); + expect(res3[2]).toBe('2015'); + + const res4 = Regexes.VideoFile.exec('Sample\'s Movie Vol.2 (2015).mpd'); + expect(res4).not.toBeNull(); + expect(res4[0]).toEqual('Sample\'s Movie Vol.2 (2015).mpd'); + expect(res4[1]).toEqual('Sample\'s Movie Vol.2'); + expect(res4[2]).toBe('2015'); + + const res5 = Regexes.VideoFile.exec('Sample Movie - Subtitle (2015).mpd'); + expect(res5).not.toBeNull(); + expect(res5[0]).toEqual('Sample Movie - Subtitle (2015).mpd'); + expect(res5[1]).toEqual('Sample Movie - Subtitle'); + expect(res5[2]).toBe('2015'); }); test('Thumbnail file gets matched', () => { diff --git a/packages/interfaces/src/MetaData/Features/HasYear.ts b/packages/interfaces/src/MetaData/Features/HasYear.ts new file mode 100644 index 0000000..b22d4e9 --- /dev/null +++ b/packages/interfaces/src/MetaData/Features/HasYear.ts @@ -0,0 +1,7 @@ +export interface HasYear { + year: number; +} + +export function isYearFeature(item: any): item is HasYear { + return item !== undefined && typeof item.year === 'number'; +} diff --git a/packages/interfaces/src/MetaData/Features/index.ts b/packages/interfaces/src/MetaData/Features/index.ts index 741444b..b8626de 100644 --- a/packages/interfaces/src/MetaData/Features/index.ts +++ b/packages/interfaces/src/MetaData/Features/index.ts @@ -1,4 +1,3 @@ -export type { HasPoster } from './HasPoster'; -export { isPosterFeature } from './HasPoster'; -export type { HasTitle } from './HasTitle'; -export { isTitleFeature } from './HasTitle'; +export { type HasPoster, isPosterFeature } from './HasPoster'; +export { type HasTitle, isTitleFeature } from './HasTitle'; +export { type HasYear, isYearFeature } from './HasYear'; diff --git a/packages/interfaces/src/MetaData/Library/ILibrary.ts b/packages/interfaces/src/MetaData/Library/ILibrary.ts index a407912..bd7de17 100644 --- a/packages/interfaces/src/MetaData/Library/ILibrary.ts +++ b/packages/interfaces/src/MetaData/Library/ILibrary.ts @@ -4,20 +4,8 @@ import { ISeriesMetaData } from './ISeriesMetaData'; export type IMovieLibrary = IGenericLibrary; -export function isMovieLibrary(item: any): item is IMovieLibrary { - return isGenericLibrary(item) && item.type === 'movie'; -} - export type ISeriesLibrary = IGenericLibrary; -export function isSeriesLibrary(item: any): item is ISeriesLibrary { - return isGenericLibrary(item) && item.type === 'series'; -} - export type IMusicLibrary = IGenericLibrary; -export function isMusicLibrary(item: any): item is IMusicLibrary { - return isGenericLibrary(item) && item.type === 'music'; -} - export type ILibrary = IMovieLibrary | ISeriesLibrary | IMusicLibrary; diff --git a/packages/interfaces/src/MetaData/Library/index.ts b/packages/interfaces/src/MetaData/Library/index.ts index fd54bc5..6ed82a8 100644 --- a/packages/interfaces/src/MetaData/Library/index.ts +++ b/packages/interfaces/src/MetaData/Library/index.ts @@ -1,6 +1,5 @@ export type { IGenericLibrary } from './IGenericLibrary'; export type { ILibrary, IMovieLibrary } from './ILibrary'; -export { isMovieLibrary, isMusicLibrary, isSeriesLibrary } from './ILibrary'; export type { IMovieMetaData } from './IMovieMetaData'; export type { IEpisodeMetaData, ISeriesMetaData, ISeasonMetaData } from './ISeriesMetaData'; export { LibraryType } from './LibraryType'; diff --git a/packages/ui/src/components/molecules/FileGridItem/FileGridItem.tsx b/packages/ui/src/components/molecules/FileGridItem/FileGridItem.tsx index 9b4fcd4..8c1d0b7 100644 --- a/packages/ui/src/components/molecules/FileGridItem/FileGridItem.tsx +++ b/packages/ui/src/components/molecules/FileGridItem/FileGridItem.tsx @@ -1,6 +1,6 @@ -import { Button, Card, CardActions, CardContent, CardMedia } from '@mui/material'; +import { Button, Card, CardActions, CardContent, CardHeader, CardMedia } from '@mui/material'; import { ReadonlySignal, useComputed } from '@preact/signals-react'; -import { IFileInfo, isIVideoFile, isPosterFeature, isTitleFeature } from 'ipmc-interfaces'; +import { IFileInfo, isIVideoFile, isPosterFeature, isTitleFeature, isYearFeature } from 'ipmc-interfaces'; import React from 'react'; import { useFileUrl } from '../../../hooks'; import { useTranslation } from '../../../hooks/useTranslation'; @@ -32,7 +32,7 @@ export function FileGridItem(props: { file: IFileInfo; onOpen: () => void; displ return useComputed(() => ( - {isTitleFeature(file) ? file.title : file.name} +