fix(subtitles): allow Whisper generation during Windows YouTube stream playback (#282)

This commit is contained in:
sudacode authored and GitHub committed 2026-10-02 22:13:15 -07:00
1 parent 0aba17d95b
commit 138bfe9759
6 files changed
+137 -9

No files matched your search

@@ -51,6 +51,11 @@ export interface SubtitleGenerationRuntimeDeps {
signal: AbortSignal;
onProgress: (progress: SubtitleGenerationProgress) => void;
}) => Promise<string>;
/**
* Maps a playing stream URL back to its YouTube page URL. Windows YouTube playback opens a
* resolved direct stream in mpv, so mpv's path alone does not identify the video.
*/
resolveYoutubeSourceUrl?: (mediaPath: string) => string | null;
}
async function currentLocalMedia(client: GenerationMpvClient | null): Promise<string | null> {
@@ -62,12 +67,6 @@ async function currentLocalMedia(client: GenerationMpvClient | null): Promise<st
return typeof directory === 'string' ? path.resolve(directory, media) : null;
}
async function currentYoutubeMedia(client: GenerationMpvClient | null): Promise<string | null> {
if (!client?.connected) return null;
const media = await client.requestProperty('path');
return typeof media === 'string' ? toYoutubeWatchUrl(media) : null;
}
async function loadGeneratedSubtitles(client: GenerationMpvClient, outputPath: string) {
const loaded = await client.request([
'sub-add',
@@ -136,6 +135,15 @@ export function createSubtitleGenerationRuntime(deps: SubtitleGenerationRuntimeD
: '',
};
}
// The YouTube page behind a resolved stream URL, or the media path itself.
function youtubeSource(mediaPath: string | null | undefined): string | null | undefined {
return (mediaPath && deps.resolveYoutubeSourceUrl?.(mediaPath)) || mediaPath;
}
async function currentYoutubeMedia(client: GenerationMpvClient | null): Promise<string | null> {
if (!client?.connected) return null;
const media = await client.requestProperty('path');
return typeof media === 'string' ? toYoutubeWatchUrl(youtubeSource(media)) : null;
}
const report = (update: SubtitleGenerationProgress) => {
progress = update;
deps.onProgress(update);
@@ -246,7 +254,7 @@ export function createSubtitleGenerationRuntime(deps: SubtitleGenerationRuntimeD
/** Stops a YouTube job once its video is no longer playing, which also deletes its audio. */
handleMediaPathChange(mediaPath: string | null | undefined): void {
if (!youtubeJobUrl) return;
if (extractYoutubeVideoId(mediaPath) !== extractYoutubeVideoId(youtubeJobUrl))
if (extractYoutubeVideoId(youtubeSource(mediaPath)) !== extractYoutubeVideoId(youtubeJobUrl))
controller?.abort();
},
async setVadEnabled(enabled: boolean): Promise<SubtitleGenerationStatus> {