feat(youtube): add YouTube browser window and Whisper subtitle source (#273)

This commit is contained in:
2026-09-28 20:35:47 -07:00
committed by GitHub
parent c68b73c8c1
commit 07b32626a6
101 changed files with 2269 additions and 726 deletions
+14
View File
@@ -4,6 +4,7 @@ import {
launchTexthookerOnly,
runAppCommandWithInherit,
} from '../mpv.js';
import { shouldForwardLogLevel } from '../types.js';
import type { LauncherCommandContext } from './context.js';
type AppCommandDeps = {
@@ -12,6 +13,10 @@ type AppCommandDeps = {
appPath: string,
logLevel: LauncherCommandContext['args']['logLevel'],
) => void;
launchYoutubeBrowserDetached: (
appPath: string,
logLevel: LauncherCommandContext['args']['logLevel'],
) => void;
launchAppBackgroundDetached: (
appPath: string,
logLevel: LauncherCommandContext['args']['logLevel'],
@@ -22,6 +27,11 @@ const defaultAppCommandDeps: AppCommandDeps = {
runAppCommandWithInherit,
launchSyncUiDetached: (appPath, logLevel) =>
launchAppCommandDetached(appPath, ['--sync-window'], logLevel, 'sync-ui'),
launchYoutubeBrowserDetached: (appPath, logLevel) => {
const appArgs = ['--youtube-browser'];
if (shouldForwardLogLevel(logLevel)) appArgs.push('--log-level', logLevel);
launchAppCommandDetached(appPath, appArgs, logLevel, 'youtube-browser');
},
launchAppBackgroundDetached,
};
@@ -41,6 +51,10 @@ export function runAppPassthroughCommand(
deps.launchSyncUiDetached(appPath, args.logLevel);
return true;
}
if (args.youtubeBrowser) {
deps.launchYoutubeBrowserDetached(appPath, args.logLevel);
return true;
}
if (!args.appPassthrough) {
return false;
}
+21
View File
@@ -207,6 +207,7 @@ test('app command starts default macOS background app detached from launcher', (
calls.push('attached');
},
launchSyncUiDetached: () => calls.push('sync-ui'),
launchYoutubeBrowserDetached: () => calls.push('youtube'),
launchAppBackgroundDetached: (appPath, logLevel) => {
calls.push(`detached:${appPath}:${logLevel}`);
},
@@ -227,6 +228,7 @@ test('app command starts default Linux background app detached from launcher', (
calls.push('attached');
},
launchSyncUiDetached: () => calls.push('sync-ui'),
launchYoutubeBrowserDetached: () => calls.push('youtube'),
launchAppBackgroundDetached: (appPath, logLevel) => {
calls.push(`detached:${appPath}:${logLevel}`);
},
@@ -248,6 +250,7 @@ test('app command keeps explicit passthrough args attached', () => {
forwarded.push(appArgs);
},
launchSyncUiDetached: () => detached.push('sync-ui'),
launchYoutubeBrowserDetached: () => detached.push('youtube'),
launchAppBackgroundDetached: () => {
detached.push('detached');
},
@@ -266,6 +269,7 @@ test('sync UI command launches the app detached from the terminal', () => {
const handled = runAppPassthroughCommand(context, {
runAppCommandWithInherit: () => calls.push('piped'),
launchSyncUiDetached: (appPath, logLevel) => calls.push(`sync-ui:${appPath}:${logLevel}`),
launchYoutubeBrowserDetached: () => calls.push('youtube'),
launchAppBackgroundDetached: () => calls.push('detached'),
});
@@ -273,6 +277,23 @@ test('sync UI command launches the app detached from the terminal', () => {
assert.deepEqual(calls, ['sync-ui:/tmp/subminer.app:warn']);
});
test('youtube command launches the YouTube browser detached from the terminal', () => {
const context = createContext();
context.args.youtubeBrowser = true;
const calls: string[] = [];
const handled = runAppPassthroughCommand(context, {
runAppCommandWithInherit: () => calls.push('piped'),
launchSyncUiDetached: () => calls.push('sync-ui'),
launchYoutubeBrowserDetached: (appPath, logLevel) =>
calls.push(`youtube:${appPath}:${logLevel}`),
launchAppBackgroundDetached: () => calls.push('detached'),
});
assert.equal(handled, true);
assert.deepEqual(calls, ['youtube:/tmp/subminer.app:warn']);
});
test('mpv pre-app command exits non-zero when socket is not ready', async () => {
const context = createContext();
context.args.mpvStatus = true;
+2 -12
View File
@@ -16,20 +16,9 @@ function createContext(): LauncherCommandContext {
recursive: false,
profile: '',
startOverlay: false,
youtubeMode: 'download',
whisperBin: '',
whisperModel: '',
whisperVadModel: '',
whisperThreads: 0,
youtubeSubgenOutDir: '',
youtubeSubgenAudioFormat: '',
youtubeSubgenKeepTemp: false,
youtubeFixWithAi: false,
youtubePrimarySubLangs: [],
youtubeSecondarySubLangs: [],
youtubeAudioLangs: [],
youtubeWhisperSourceLanguage: '',
aiConfig: {},
useTexthooker: false,
autoStartOverlay: false,
texthookerOnly: false,
@@ -63,6 +52,7 @@ function createContext(): LauncherCommandContext {
logsExport: false,
version: false,
settings: false,
youtubeBrowser: false,
configPath: false,
configShow: false,
mpvIdle: false,
@@ -162,7 +152,7 @@ test('youtube playback launches overlay with app-owned youtube flow args', async
assert.deepEqual(calls, [
'startMpv',
'startOverlay:--youtube-play https://www.youtube.com/watch?v=65Ovd7t8sNw --youtube-mode download',
'startOverlay:--youtube-play https://www.youtube.com/watch?v=65Ovd7t8sNw',
]);
assert.equal(receivedStartMpvOptions[0]?.startPaused, true);
assert.equal(receivedStartMpvOptions[0]?.disableYoutubeSubtitleAutoLoad, true);
+1 -2
View File
@@ -240,7 +240,6 @@ export async function runPlaybackCommandWithDeps(
const isYoutubeUrl = selectedTarget.kind === 'url' && isYoutubeTarget(selectedTarget.target);
const isAppOwnedYoutubeFlow = isYoutubeUrl;
const youtubeMode = args.youtubeMode ?? 'download';
const configDir = getLauncherConfigDir();
if (isYoutubeUrl) {
@@ -325,7 +324,7 @@ export async function runPlaybackCommandWithDeps(
);
}
const extraAppArgs = isAppOwnedYoutubeFlow
? ['--youtube-play', selectedTarget.target, '--youtube-mode', youtubeMode]
? ['--youtube-play', selectedTarget.target]
: shouldLauncherAttachRunningApp
? [
pluginRuntimeConfig.autoStartVisibleOverlay