mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-21 17:16:20 -07:00
fix(jellyfin): fix jellyfin media metadata (#250)
This commit is contained in:
@@ -257,6 +257,30 @@ function repairLeakedJellyfinVideoParseMetadata(
|
||||
return updated.changes;
|
||||
}
|
||||
|
||||
function repairLeakedJellyfinAnimeParseMetadata(
|
||||
db: DatabaseSync,
|
||||
currentTimestamp: string,
|
||||
): number {
|
||||
const updated = db
|
||||
.prepare(
|
||||
`
|
||||
UPDATE imm_anime
|
||||
SET metadata_json = NULL, LAST_UPDATE_DATE = ?
|
||||
WHERE (
|
||||
metadata_json LIKE '%api_key=%'
|
||||
OR lower(metadata_json) LIKE '%api key%'
|
||||
) AND (
|
||||
lower(metadata_json) LIKE '%stream?%'
|
||||
OR lower(metadata_json) LIKE '%/stream?%'
|
||||
OR lower(metadata_json) LIKE '%/videos/%'
|
||||
OR lower(metadata_json) LIKE '%mediasourceid%'
|
||||
)
|
||||
`,
|
||||
)
|
||||
.run(currentTimestamp);
|
||||
return updated.changes;
|
||||
}
|
||||
|
||||
export function repairJellyfinStreamVideoLinks(db: DatabaseSync): JellyfinLinkRepairSummary {
|
||||
const candidates = db
|
||||
.prepare(
|
||||
@@ -290,7 +314,8 @@ export function repairJellyfinStreamVideoLinks(db: DatabaseSync): JellyfinLinkRe
|
||||
const currentTimestamp = toDbTimestamp(nowMs());
|
||||
const repaired =
|
||||
repairLeakedJellyfinAnimeTitles(db, currentTimestamp) +
|
||||
repairLeakedJellyfinVideoParseMetadata(db, currentTimestamp);
|
||||
repairLeakedJellyfinVideoParseMetadata(db, currentTimestamp) +
|
||||
repairLeakedJellyfinAnimeParseMetadata(db, currentTimestamp);
|
||||
summary.repaired += repaired;
|
||||
return summary;
|
||||
}
|
||||
@@ -422,6 +447,7 @@ export function repairJellyfinStreamVideoLinks(db: DatabaseSync): JellyfinLinkRe
|
||||
}
|
||||
summary.repaired += repairLeakedJellyfinAnimeTitles(db, currentTimestamp);
|
||||
summary.repaired += repairLeakedJellyfinVideoParseMetadata(db, currentTimestamp);
|
||||
summary.repaired += repairLeakedJellyfinAnimeParseMetadata(db, currentTimestamp);
|
||||
db.exec('COMMIT');
|
||||
} catch (error) {
|
||||
db.exec('ROLLBACK');
|
||||
|
||||
@@ -147,6 +147,25 @@ test('getLocalVideoMetadata derives title and falls back to null hash on read er
|
||||
assert.equal(hashFallbackMetadata.hashSha256, null);
|
||||
});
|
||||
|
||||
test('stream stats parsing preserves display titles and never persists transport credentials', async () => {
|
||||
const targets: string[] = [];
|
||||
const parsed = await guessAnimeVideoMetadata(
|
||||
'https://jellyfin.example/Videos/item/stream?api_key=test-secret',
|
||||
'Fate/stay night S01E02',
|
||||
{
|
||||
runGuessit: async (target) => {
|
||||
targets.push(target);
|
||||
return JSON.stringify({ title: 'Fate/stay night', season: 1, episode: 2 });
|
||||
},
|
||||
},
|
||||
);
|
||||
assert.deepEqual(targets, ['Fate/stay night S01E02']);
|
||||
assert.equal(parsed?.parsedBasename, 'Fate/stay night S01E02');
|
||||
assert.equal(parsed?.parsedTitle, 'Fate/stay night');
|
||||
assert.equal(JSON.stringify(parsed).includes('test-secret'), false);
|
||||
assert.equal(JSON.stringify(parsed).includes('/stream'), false);
|
||||
});
|
||||
|
||||
test('guessAnimeVideoMetadata uses guessit basename output first when available', async () => {
|
||||
const seenTargets: string[] = [];
|
||||
const parsed = await guessAnimeVideoMetadata(
|
||||
|
||||
@@ -3,6 +3,7 @@ import { spawn as nodeSpawn } from 'node:child_process';
|
||||
import * as fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { parseMediaInfo } from '../../../jimaku/utils';
|
||||
import { resolveMediaLookupTarget, sanitizeMediaTitle } from '../../../shared/media-identity';
|
||||
import {
|
||||
guessAnilistMediaInfo,
|
||||
runGuessit,
|
||||
@@ -184,6 +185,7 @@ export async function guessAnimeVideoMetadata(
|
||||
mediaTitle: string | null,
|
||||
deps: GuessAnimeVideoMetadataDeps = {},
|
||||
): Promise<ParsedAnimeVideoGuess | null> {
|
||||
const lookupTarget = resolveMediaLookupTarget(mediaPath, mediaTitle);
|
||||
const parsed = await guessAnilistMediaInfo(mediaPath, mediaTitle, {
|
||||
runGuessit: deps.runGuessit ?? runGuessit,
|
||||
});
|
||||
@@ -191,7 +193,12 @@ export async function guessAnimeVideoMetadata(
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsedBasename = mediaPath ? path.basename(mediaPath) : null;
|
||||
const parsedBasename =
|
||||
lookupTarget === sanitizeMediaTitle(mediaTitle)
|
||||
? lookupTarget
|
||||
: lookupTarget
|
||||
? path.basename(lookupTarget)
|
||||
: null;
|
||||
if (parsed.source === 'guessit') {
|
||||
return {
|
||||
parsedBasename,
|
||||
@@ -207,7 +214,7 @@ export async function guessAnimeVideoMetadata(
|
||||
};
|
||||
}
|
||||
|
||||
const fallbackInfo = parseMediaInfo(mediaPath ?? mediaTitle);
|
||||
const fallbackInfo = parseMediaInfo(lookupTarget);
|
||||
return {
|
||||
parsedBasename: parsedBasename ?? fallbackInfo.filename ?? null,
|
||||
parsedTitle: parsed.title,
|
||||
|
||||
Reference in New Issue
Block a user