Restore multi-copy digit capture and add AniList selection (#56)

This commit is contained in:
2026-04-25 21:44:55 -07:00
committed by GitHub
parent 7ac51cd5e9
commit d8934647a9
140 changed files with 4097 additions and 326 deletions
@@ -76,6 +76,32 @@ test('guessAnilistMediaInfo joins multi-part guessit titles', async () => {
});
});
test('guessAnilistMediaInfo preserves useful guessit alternative title for ambiguous Re ZERO filenames', async () => {
const result = await guessAnilistMediaInfo(
'/tmp/Re - ZERO, Starting Life in Another World (2016) - S01E01 - - The End of the Beginning and the Beginning of the End [v2 Bluray-1080p Proper][10bit][x265][FLAC 2.0][EN+JA]-SCY.mkv',
null,
{
runGuessit: async () =>
JSON.stringify({
title: 'Re',
alternative_title: 'ZERO, Starting Life in Another World',
year: 2016,
season: 1,
episode: 1,
}),
},
);
assert.deepEqual(result, {
title: 'Re ZERO, Starting Life in Another World',
alternativeTitle: 'ZERO, Starting Life in Another World',
year: 2016,
season: 1,
episode: 1,
source: 'guessit',
});
});
test('updateAnilistPostWatchProgress updates progress when behind', async () => {
const originalFetch = globalThis.fetch;
let call = 0;
+26 -1
View File
@@ -7,6 +7,8 @@ const ANILIST_GRAPHQL_URL = 'https://graphql.anilist.co';
export interface AnilistMediaGuess {
title: string;
alternativeTitle?: string;
year?: number;
season: number | null;
episode: number | null;
source: 'guessit' | 'fallback';
@@ -131,6 +133,20 @@ function firstPositiveInteger(value: unknown): number | null {
return null;
}
function firstYear(value: unknown): number | undefined {
const candidate = firstPositiveInteger(value);
if (candidate === null) return undefined;
return candidate >= 1900 && candidate <= 2200 ? candidate : undefined;
}
function buildGuessitTitle(title: string, alternativeTitle: string | null): string {
if (!alternativeTitle) return title;
if (title.length <= 3) {
return `${title} ${alternativeTitle}`.replace(/\s+/g, ' ').trim();
}
return title;
}
function normalizeTitle(text: string): string {
return text.trim().toLowerCase().replace(/\s+/g, ' ');
}
@@ -215,10 +231,19 @@ export async function guessAnilistMediaInfo(
const stdout = await deps.runGuessit(guessitTarget);
const parsed = JSON.parse(stdout) as Record<string, unknown>;
const title = readGuessitTitle(parsed.title);
const alternativeTitle = readGuessitTitle(parsed.alternative_title);
const episode = firstPositiveInteger(parsed.episode);
const season = firstPositiveInteger(parsed.season);
const year = firstYear(parsed.year);
if (title) {
return { title, season, episode, source: 'guessit' };
return {
title: buildGuessitTitle(title, alternativeTitle),
...(alternativeTitle ? { alternativeTitle } : {}),
...(year ? { year } : {}),
season,
episode,
source: 'guessit',
};
}
} catch {
// Ignore guessit failures and fall back to internal parser.