Skip to content

Commit

Permalink
Improve Movie indexer and make indexing more generic
Browse files Browse the repository at this point in the history
  • Loading branch information
undyingwraith committed Dec 14, 2024
1 parent a9e772e commit 4af69fe
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 29 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/Regexes.ts
Original file line number Diff line number Diff line change
@@ -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)$/,
};
12 changes: 8 additions & 4 deletions packages/core/src/Services/IndexManager.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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<ILibrary>(lib));
const indexSignal = new Signal<ILibraryIndex<any> | undefined>(this.objectStore.get(this.getIndexStorageKey(lib.name)));
Expand Down Expand Up @@ -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}]`);
Expand All @@ -86,6 +88,8 @@ export class IndexManager implements IIndexManager {
}
}

private indexers: IIndexFetcher<any>[] = [];

private libraries = new Map<string, Signal<ILibrary>>();

private updates = new Map<string, Promise<void>>();
Expand Down
16 changes: 16 additions & 0 deletions packages/core/src/Services/Indexer/IIndexFetcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
import { ILibrary } from 'ipmc-interfaces';

export interface IIndexFetcher<TIndex> {
/**
* Fetches the index of the specified CID.
* @param cid cid to index.
*/
fetchIndex(cid: string): Promise<TIndex>;

/**
* 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;
}
13 changes: 10 additions & 3 deletions packages/core/src/Services/Indexer/MovieIndexFetcher.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -24,16 +24,23 @@ export class MovieIndexFetcher implements IIndexFetcher<IMovieMetaData[]> {

public async extractMovieMetaData(node: IIpfsService, entry: IFileInfo, skeleton?: any): Promise<IMovieMetaData> {
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';
}
}
6 changes: 5 additions & 1 deletion packages/core/src/Services/Indexer/SeriesIndexFetcher.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -72,4 +72,8 @@ export class SeriesIndexFetcher implements IIndexFetcher<ISeriesMetaData[]> {

return episode;
}

public canIndex(library: ILibrary): boolean {
return library.type === 'series';
}
}
18 changes: 18 additions & 0 deletions packages/core/tests/Regexes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
7 changes: 7 additions & 0 deletions packages/interfaces/src/MetaData/Features/HasYear.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface HasYear {
year: number;
}

export function isYearFeature(item: any): item is HasYear {
return item !== undefined && typeof item.year === 'number';
}
7 changes: 3 additions & 4 deletions packages/interfaces/src/MetaData/Features/index.ts
Original file line number Diff line number Diff line change
@@ -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';
12 changes: 0 additions & 12 deletions packages/interfaces/src/MetaData/Library/ILibrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,8 @@ import { ISeriesMetaData } from './ISeriesMetaData';

export type IMovieLibrary = IGenericLibrary<IMovieMetaData, 'movie'>;

export function isMovieLibrary(item: any): item is IMovieLibrary {
return isGenericLibrary<IMovieMetaData, 'movie'>(item) && item.type === 'movie';
}

export type ISeriesLibrary = IGenericLibrary<ISeriesMetaData, 'series'>;

export function isSeriesLibrary(item: any): item is ISeriesLibrary {
return isGenericLibrary<any, 'series'>(item) && item.type === 'series';
}

export type IMusicLibrary = IGenericLibrary<any, 'music'>;

export function isMusicLibrary(item: any): item is IMusicLibrary {
return isGenericLibrary<any, 'music'>(item) && item.type === 'music';
}

export type ILibrary = IMovieLibrary | ISeriesLibrary | IMusicLibrary;
1 change: 0 additions & 1 deletion packages/interfaces/src/MetaData/Library/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -32,7 +32,7 @@ export function FileGridItem(props: { file: IFileInfo; onOpen: () => void; displ
return useComputed(() => (
<Card sx={{ width: width.value }}>
<CardMedia image={url.value} sx={{ height: height.value, width: width.value }} />
<CardContent>{isTitleFeature(file) ? file.title : file.name}</CardContent>
<CardHeader title={isTitleFeature(file) ? file.title : file.name} subheader={isYearFeature(file) ? file.year : undefined}></CardHeader>
<CardActions>
<PinButton cid={file.cid} />
<Button onClick={onOpen}>{_t('Open')}</Button>
Expand Down

0 comments on commit 4af69fe

Please sign in to comment.