feat(subtitles): generate Japanese subtitles during Jellyfin playback (#294)

* feat(subtitles): generate Japanese subtitles during Jellyfin playback

- Read the item's original file via toJellyfinOriginalStreamUrl and write output to a temp dir
- Transcoded playback uses Jellyfin's audio index and only external references
- Show Jellyfin media by title and redact URL tokens in subprocess errors
- Update overlay copy, docs, and changelog fragment

* fix(subtitles): redact full stream tokens and reuse one Jellyfin output folder

* test(subtitles): make Jellyfin output path assertions platform-neutral

- Compare dirname/basename via node:path instead of POSIX-only regexes
- Assert the full sub-add command uses the generated output path
This commit is contained in:
sudacode authored and GitHub committed 2026-10-13 21:55:28 -07:00
1 parent 7909fcd2cf
commit bf4e5bb609
14 files changed
+295 -25

No files matched your search

+23
View File
@@ -254,6 +254,29 @@ function createDirectPlayUrl(
return `${session.serverUrl}/Videos/${itemId}/stream?${query.toString()}`;
}
/**
* Maps a playing stream URL of `itemId`, direct or transcoded, to its original file. Returns null
* when the URL is not that item's stream or carries no access token.
*/
export function toJellyfinOriginalStreamUrl(playbackUrl: string, itemId: string): string | null {
let url: URL;
try {
url = new URL(playbackUrl);
} catch {
return null;
}
const at = url.pathname.toLowerCase().indexOf(`/videos/${itemId.toLowerCase()}/`);
if (!/^https?:$/.test(url.protocol) || !itemId || at < 0) return null;
const param = (pattern: RegExp) =>
[...url.searchParams].find(([key, value]) => pattern.test(key) && value)?.[1];
const accessToken = param(/^api_?key$/i);
if (!accessToken) return null;
const query = new URLSearchParams({ static: 'true', ApiKey: accessToken });
const mediaSourceId = param(/^mediasourceid$/i);
if (mediaSourceId) query.set('MediaSourceId', mediaSourceId);
return `${url.origin}${url.pathname.slice(0, at)}/Videos/${itemId}/stream?${query.toString()}`;
}
function createTranscodeUrl(
session: JellyfinAuthSession,
itemId: string,