mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-25 00:11:26 -07:00
refactor: remove legacy youtube launcher mode plumbing
This commit is contained in:
@@ -10,7 +10,6 @@ test('launcher root help lists subcommands', () => {
|
|||||||
|
|
||||||
assert.match(output, /Commands:/);
|
assert.match(output, /Commands:/);
|
||||||
assert.match(output, /jellyfin\|jf/);
|
assert.match(output, /jellyfin\|jf/);
|
||||||
assert.match(output, /yt\|youtube/);
|
|
||||||
assert.match(output, /doctor/);
|
assert.match(output, /doctor/);
|
||||||
assert.match(output, /config/);
|
assert.match(output, /config/);
|
||||||
assert.match(output, /mpv/);
|
assert.match(output, /mpv/);
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ export function createDefaultArgs(launcherConfig: LauncherYoutubeSubgenConfig):
|
|||||||
youtubeSubgenAudioFormat: process.env.SUBMINER_YT_SUBGEN_AUDIO_FORMAT || 'm4a',
|
youtubeSubgenAudioFormat: process.env.SUBMINER_YT_SUBGEN_AUDIO_FORMAT || 'm4a',
|
||||||
youtubeSubgenKeepTemp: process.env.SUBMINER_YT_SUBGEN_KEEP_TEMP === '1',
|
youtubeSubgenKeepTemp: process.env.SUBMINER_YT_SUBGEN_KEEP_TEMP === '1',
|
||||||
youtubeFixWithAi: launcherConfig.fixWithAi === true,
|
youtubeFixWithAi: launcherConfig.fixWithAi === true,
|
||||||
youtubeMode: undefined,
|
|
||||||
jimakuApiKey: process.env.SUBMINER_JIMAKU_API_KEY || '',
|
jimakuApiKey: process.env.SUBMINER_JIMAKU_API_KEY || '',
|
||||||
jimakuApiKeyCommand: process.env.SUBMINER_JIMAKU_API_KEY_COMMAND || '',
|
jimakuApiKeyCommand: process.env.SUBMINER_JIMAKU_API_KEY_COMMAND || '',
|
||||||
jimakuApiBaseUrl: process.env.SUBMINER_JIMAKU_API_BASE_URL || DEFAULT_JIMAKU_API_BASE_URL,
|
jimakuApiBaseUrl: process.env.SUBMINER_JIMAKU_API_BASE_URL || DEFAULT_JIMAKU_API_BASE_URL,
|
||||||
@@ -250,29 +249,6 @@ export function applyInvocationsToArgs(parsed: Args, invocations: CliInvocations
|
|||||||
parsed.jellyfinLogout = Boolean(modeFlags.logout);
|
parsed.jellyfinLogout = Boolean(modeFlags.logout);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invocations.ytInvocation) {
|
|
||||||
if (invocations.ytInvocation.mode) {
|
|
||||||
parsed.youtubeMode = invocations.ytInvocation.mode;
|
|
||||||
}
|
|
||||||
if (invocations.ytInvocation.logLevel)
|
|
||||||
parsed.logLevel = parseLogLevel(invocations.ytInvocation.logLevel);
|
|
||||||
if (invocations.ytInvocation.outDir)
|
|
||||||
parsed.youtubeSubgenOutDir = invocations.ytInvocation.outDir;
|
|
||||||
if (invocations.ytInvocation.keepTemp) parsed.youtubeSubgenKeepTemp = true;
|
|
||||||
if (invocations.ytInvocation.whisperBin)
|
|
||||||
parsed.whisperBin = invocations.ytInvocation.whisperBin;
|
|
||||||
if (invocations.ytInvocation.whisperModel)
|
|
||||||
parsed.whisperModel = invocations.ytInvocation.whisperModel;
|
|
||||||
if (invocations.ytInvocation.whisperVadModel)
|
|
||||||
parsed.whisperVadModel = invocations.ytInvocation.whisperVadModel;
|
|
||||||
if (invocations.ytInvocation.whisperThreads)
|
|
||||||
parsed.whisperThreads = invocations.ytInvocation.whisperThreads;
|
|
||||||
if (invocations.ytInvocation.ytSubgenAudioFormat) {
|
|
||||||
parsed.youtubeSubgenAudioFormat = invocations.ytInvocation.ytSubgenAudioFormat;
|
|
||||||
}
|
|
||||||
if (invocations.ytInvocation.target) ensureTarget(invocations.ytInvocation.target, parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invocations.dictionaryLogLevel) {
|
if (invocations.dictionaryLogLevel) {
|
||||||
parsed.logLevel = parseLogLevel(invocations.dictionaryLogLevel);
|
parsed.logLevel = parseLogLevel(invocations.dictionaryLogLevel);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,19 +14,6 @@ export interface JellyfinInvocation {
|
|||||||
logLevel?: string;
|
logLevel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface YtInvocation {
|
|
||||||
target?: string;
|
|
||||||
mode?: 'download' | 'generate';
|
|
||||||
outDir?: string;
|
|
||||||
keepTemp?: boolean;
|
|
||||||
whisperBin?: string;
|
|
||||||
whisperModel?: string;
|
|
||||||
whisperVadModel?: string;
|
|
||||||
whisperThreads?: number;
|
|
||||||
ytSubgenAudioFormat?: string;
|
|
||||||
logLevel?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CommandActionInvocation {
|
export interface CommandActionInvocation {
|
||||||
action: string;
|
action: string;
|
||||||
logLevel?: string;
|
logLevel?: string;
|
||||||
@@ -34,7 +21,6 @@ export interface CommandActionInvocation {
|
|||||||
|
|
||||||
export interface CliInvocations {
|
export interface CliInvocations {
|
||||||
jellyfinInvocation: JellyfinInvocation | null;
|
jellyfinInvocation: JellyfinInvocation | null;
|
||||||
ytInvocation: YtInvocation | null;
|
|
||||||
configInvocation: CommandActionInvocation | null;
|
configInvocation: CommandActionInvocation | null;
|
||||||
mpvInvocation: CommandActionInvocation | null;
|
mpvInvocation: CommandActionInvocation | null;
|
||||||
appInvocation: { appArgs: string[] } | null;
|
appInvocation: { appArgs: string[] } | null;
|
||||||
@@ -90,8 +76,6 @@ function getTopLevelCommand(argv: string[]): { name: string; index: number } | n
|
|||||||
const commandNames = new Set([
|
const commandNames = new Set([
|
||||||
'jellyfin',
|
'jellyfin',
|
||||||
'jf',
|
'jf',
|
||||||
'yt',
|
|
||||||
'youtube',
|
|
||||||
'doctor',
|
'doctor',
|
||||||
'config',
|
'config',
|
||||||
'mpv',
|
'mpv',
|
||||||
@@ -143,7 +127,6 @@ export function parseCliPrograms(
|
|||||||
invocations: CliInvocations;
|
invocations: CliInvocations;
|
||||||
} {
|
} {
|
||||||
let jellyfinInvocation: JellyfinInvocation | null = null;
|
let jellyfinInvocation: JellyfinInvocation | null = null;
|
||||||
let ytInvocation: YtInvocation | null = null;
|
|
||||||
let configInvocation: CommandActionInvocation | null = null;
|
let configInvocation: CommandActionInvocation | null = null;
|
||||||
let mpvInvocation: CommandActionInvocation | null = null;
|
let mpvInvocation: CommandActionInvocation | null = null;
|
||||||
let appInvocation: { appArgs: string[] } | null = null;
|
let appInvocation: { appArgs: string[] } | null = null;
|
||||||
@@ -218,43 +201,6 @@ export function parseCliPrograms(
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
commandProgram
|
|
||||||
.command('yt')
|
|
||||||
.alias('youtube')
|
|
||||||
.description('YouTube workflows')
|
|
||||||
.argument('[target]', 'YouTube URL or ytsearch: query')
|
|
||||||
.option('--mode <mode>', 'YouTube subtitle acquisition mode')
|
|
||||||
.option('-o, --out-dir <dir>', 'Subtitle output dir')
|
|
||||||
.option('--keep-temp', 'Keep temp files')
|
|
||||||
.option('--whisper-bin <path>', 'whisper.cpp CLI path')
|
|
||||||
.option('--whisper-model <path>', 'whisper model path')
|
|
||||||
.option('--whisper-vad-model <path>', 'whisper.cpp VAD model path')
|
|
||||||
.option('--whisper-threads <n>', 'whisper.cpp thread count')
|
|
||||||
.option('--yt-subgen-audio-format <format>', 'Audio extraction format')
|
|
||||||
.option('--log-level <level>', 'Log level')
|
|
||||||
.action((target: string | undefined, options: Record<string, unknown>) => {
|
|
||||||
ytInvocation = {
|
|
||||||
target,
|
|
||||||
mode:
|
|
||||||
typeof options.mode === 'string' && (options.mode === 'download' || options.mode === 'generate')
|
|
||||||
? options.mode
|
|
||||||
: undefined,
|
|
||||||
outDir: typeof options.outDir === 'string' ? options.outDir : undefined,
|
|
||||||
keepTemp: options.keepTemp === true,
|
|
||||||
whisperBin: typeof options.whisperBin === 'string' ? options.whisperBin : undefined,
|
|
||||||
whisperModel: typeof options.whisperModel === 'string' ? options.whisperModel : undefined,
|
|
||||||
whisperVadModel:
|
|
||||||
typeof options.whisperVadModel === 'string' ? options.whisperVadModel : undefined,
|
|
||||||
whisperThreads:
|
|
||||||
typeof options.whisperThreads === 'number' && Number.isFinite(options.whisperThreads)
|
|
||||||
? Math.floor(options.whisperThreads)
|
|
||||||
: undefined,
|
|
||||||
ytSubgenAudioFormat:
|
|
||||||
typeof options.ytSubgenAudioFormat === 'string' ? options.ytSubgenAudioFormat : undefined,
|
|
||||||
logLevel: typeof options.logLevel === 'string' ? options.logLevel : undefined,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
commandProgram
|
commandProgram
|
||||||
.command('dictionary')
|
.command('dictionary')
|
||||||
.alias('dict')
|
.alias('dict')
|
||||||
@@ -388,7 +334,6 @@ export function parseCliPrograms(
|
|||||||
rootTarget: rootProgram.processedArgs[0],
|
rootTarget: rootProgram.processedArgs[0],
|
||||||
invocations: {
|
invocations: {
|
||||||
jellyfinInvocation,
|
jellyfinInvocation,
|
||||||
ytInvocation,
|
|
||||||
configInvocation,
|
configInvocation,
|
||||||
mpvInvocation,
|
mpvInvocation,
|
||||||
appInvocation,
|
appInvocation,
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ ${bunBinary} -e "const net=require('node:net'); const fs=require('node:fs'); con
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('launcher disables plugin startup pause gate for app-owned youtube flow', { timeout: 15000 }, () => {
|
test('launcher routes youtube urls through regular playback startup', { timeout: 15000 }, () => {
|
||||||
withTempDir((root) => {
|
withTempDir((root) => {
|
||||||
const homeDir = path.join(root, 'home');
|
const homeDir = path.join(root, 'home');
|
||||||
const xdgConfigHome = path.join(root, 'xdg');
|
const xdgConfigHome = path.join(root, 'xdg');
|
||||||
@@ -430,13 +430,16 @@ ${bunBinary} -e "const net=require('node:net'); const fs=require('node:fs'); con
|
|||||||
SUBMINER_TEST_MPV_ARGS: mpvArgsPath,
|
SUBMINER_TEST_MPV_ARGS: mpvArgsPath,
|
||||||
SUBMINER_TEST_CAPTURE: path.join(root, 'captured-args.txt'),
|
SUBMINER_TEST_CAPTURE: path.join(root, 'captured-args.txt'),
|
||||||
};
|
};
|
||||||
const result = runLauncher(['yt', 'https://www.youtube.com/watch?v=abc123'], env);
|
const result = runLauncher(['https://www.youtube.com/watch?v=abc123'], env);
|
||||||
|
|
||||||
assert.equal(result.status, 0, `stdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
|
assert.equal(result.status, 0, `stdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
|
||||||
assert.match(
|
const forwardedArgs = fs
|
||||||
fs.readFileSync(mpvArgsPath, 'utf8'),
|
.readFileSync(mpvArgsPath, 'utf8')
|
||||||
/--script-opts=.*subminer-auto_start_pause_until_ready=no/,
|
.trim()
|
||||||
);
|
.split('\n')
|
||||||
|
.map((item) => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
assert.equal(forwardedArgs.includes('https://www.youtube.com/watch?v=abc123'), true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -85,13 +85,6 @@ test('parseArgs maps mpv idle action', () => {
|
|||||||
assert.equal(parsed.mpvStatus, false);
|
assert.equal(parsed.mpvStatus, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parseArgs captures youtube mode forwarding', () => {
|
|
||||||
const parsed = parseArgs(['youtube', 'https://example.com', '--mode', 'generate'], 'subminer', {});
|
|
||||||
|
|
||||||
assert.equal(parsed.target, 'https://example.com');
|
|
||||||
assert.equal(parsed.youtubeMode, 'generate');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('parseArgs maps dictionary command and log-level override', () => {
|
test('parseArgs maps dictionary command and log-level override', () => {
|
||||||
const parsed = parseArgs(['dictionary', '.', '--log-level', 'debug'], 'subminer', {});
|
const parsed = parseArgs(['dictionary', '.', '--log-level', 'debug'], 'subminer', {});
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ ${B}Session${R}
|
|||||||
--background Start in tray/background mode
|
--background Start in tray/background mode
|
||||||
--start Connect to mpv and launch overlay
|
--start Connect to mpv and launch overlay
|
||||||
--launch-mpv ${D}[targets...]${R} Launch mpv with the SubMiner mpv profile and exit
|
--launch-mpv ${D}[targets...]${R} Launch mpv with the SubMiner mpv profile and exit
|
||||||
--youtube-play ${D}URL${R} Start app-owned YouTube subtitle auto-load flow for a URL
|
|
||||||
--youtube-mode ${D}download|generate${R} Subtitle acquisition mode for YouTube flow
|
|
||||||
--stop Stop the running instance
|
--stop Stop the running instance
|
||||||
--stats Open the stats dashboard in your browser
|
--stats Open the stats dashboard in your browser
|
||||||
--texthooker Start texthooker server only ${D}(no overlay)${R}
|
--texthooker Start texthooker server only ${D}(no overlay)${R}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ test('cli command runtime handler prepares overlay prerequisites before overlay
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
handler({ youtubePlay: 'https://www.youtube.com/watch?v=test' } as never);
|
handler({ settings: true } as never);
|
||||||
|
|
||||||
assert.deepEqual(calls, ['prereqs', 'context', 'cli:initial:ctx']);
|
assert.deepEqual(calls, ['prereqs', 'context', 'cli:initial:ctx']);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
|
|||||||
currentSubAssText: '',
|
currentSubAssText: '',
|
||||||
playbackPaused: null,
|
playbackPaused: null,
|
||||||
previousSecondarySubVisibility: null,
|
previousSecondarySubVisibility: null,
|
||||||
youtubePlaybackFlowPending: false,
|
|
||||||
},
|
},
|
||||||
getQuitOnDisconnectArmed: () => false,
|
getQuitOnDisconnectArmed: () => false,
|
||||||
scheduleQuitCheck: () => {},
|
scheduleQuitCheck: () => {},
|
||||||
@@ -281,7 +280,6 @@ test('composeMpvRuntimeHandlers skips MeCab warmup when all POS-dependent annota
|
|||||||
currentSubAssText: '',
|
currentSubAssText: '',
|
||||||
playbackPaused: null,
|
playbackPaused: null,
|
||||||
previousSecondarySubVisibility: null,
|
previousSecondarySubVisibility: null,
|
||||||
youtubePlaybackFlowPending: false,
|
|
||||||
},
|
},
|
||||||
getQuitOnDisconnectArmed: () => false,
|
getQuitOnDisconnectArmed: () => false,
|
||||||
scheduleQuitCheck: () => {},
|
scheduleQuitCheck: () => {},
|
||||||
@@ -413,7 +411,6 @@ test('composeMpvRuntimeHandlers runs tokenization warmup once across sequential
|
|||||||
currentSubAssText: '',
|
currentSubAssText: '',
|
||||||
playbackPaused: null,
|
playbackPaused: null,
|
||||||
previousSecondarySubVisibility: null,
|
previousSecondarySubVisibility: null,
|
||||||
youtubePlaybackFlowPending: false,
|
|
||||||
},
|
},
|
||||||
getQuitOnDisconnectArmed: () => false,
|
getQuitOnDisconnectArmed: () => false,
|
||||||
scheduleQuitCheck: () => {},
|
scheduleQuitCheck: () => {},
|
||||||
@@ -553,7 +550,6 @@ test('composeMpvRuntimeHandlers does not block first tokenization on dictionary
|
|||||||
currentSubAssText: '',
|
currentSubAssText: '',
|
||||||
playbackPaused: null,
|
playbackPaused: null,
|
||||||
previousSecondarySubVisibility: null,
|
previousSecondarySubVisibility: null,
|
||||||
youtubePlaybackFlowPending: false,
|
|
||||||
},
|
},
|
||||||
getQuitOnDisconnectArmed: () => false,
|
getQuitOnDisconnectArmed: () => false,
|
||||||
scheduleQuitCheck: () => {},
|
scheduleQuitCheck: () => {},
|
||||||
@@ -687,7 +683,6 @@ test('composeMpvRuntimeHandlers shows annotation loading OSD after tokenization-
|
|||||||
currentSubAssText: '',
|
currentSubAssText: '',
|
||||||
playbackPaused: null,
|
playbackPaused: null,
|
||||||
previousSecondarySubVisibility: null,
|
previousSecondarySubVisibility: null,
|
||||||
youtubePlaybackFlowPending: false,
|
|
||||||
},
|
},
|
||||||
getQuitOnDisconnectArmed: () => false,
|
getQuitOnDisconnectArmed: () => false,
|
||||||
scheduleQuitCheck: () => {},
|
scheduleQuitCheck: () => {},
|
||||||
@@ -835,7 +830,6 @@ test('composeMpvRuntimeHandlers reuses completed background tokenization warmups
|
|||||||
currentSubAssText: '',
|
currentSubAssText: '',
|
||||||
playbackPaused: null,
|
playbackPaused: null,
|
||||||
previousSecondarySubVisibility: null,
|
previousSecondarySubVisibility: null,
|
||||||
youtubePlaybackFlowPending: false,
|
|
||||||
},
|
},
|
||||||
getQuitOnDisconnectArmed: () => false,
|
getQuitOnDisconnectArmed: () => false,
|
||||||
scheduleQuitCheck: () => {},
|
scheduleQuitCheck: () => {},
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ test('initial args handler forwards args to cli handler', () => {
|
|||||||
|
|
||||||
test('initial args handler bootstraps overlay before initial overlay-runtime commands', () => {
|
test('initial args handler bootstraps overlay before initial overlay-runtime commands', () => {
|
||||||
const calls: string[] = [];
|
const calls: string[] = [];
|
||||||
const args = { youtubePlay: 'https://youtube.com/watch?v=abc' } as never;
|
const args = { settings: true } as never;
|
||||||
const handleInitialArgs = createHandleInitialArgsHandler({
|
const handleInitialArgs = createHandleInitialArgsHandler({
|
||||||
getInitialArgs: () => args,
|
getInitialArgs: () => args,
|
||||||
isBackgroundMode: () => false,
|
isBackgroundMode: () => false,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { SubtitleData } from '../../types';
|
import type { SubtitleData } from '../../types';
|
||||||
|
|
||||||
export function createHandleMpvSubtitleChangeHandler(deps: {
|
export function createHandleMpvSubtitleChangeHandler(deps: {
|
||||||
shouldSuppressSubtitleEvents?: () => boolean;
|
|
||||||
setCurrentSubText: (text: string) => void;
|
setCurrentSubText: (text: string) => void;
|
||||||
getImmediateSubtitlePayload?: (text: string) => SubtitleData | null;
|
getImmediateSubtitlePayload?: (text: string) => SubtitleData | null;
|
||||||
emitImmediateSubtitle?: (payload: SubtitleData) => void;
|
emitImmediateSubtitle?: (payload: SubtitleData) => void;
|
||||||
@@ -11,10 +10,6 @@ export function createHandleMpvSubtitleChangeHandler(deps: {
|
|||||||
}) {
|
}) {
|
||||||
return ({ text }: { text: string }): void => {
|
return ({ text }: { text: string }): void => {
|
||||||
deps.setCurrentSubText(text);
|
deps.setCurrentSubText(text);
|
||||||
if (deps.shouldSuppressSubtitleEvents?.()) {
|
|
||||||
deps.refreshDiscordPresence();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const immediatePayload = deps.getImmediateSubtitlePayload?.(text) ?? null;
|
const immediatePayload = deps.getImmediateSubtitlePayload?.(text) ?? null;
|
||||||
if (immediatePayload) {
|
if (immediatePayload) {
|
||||||
(deps.emitImmediateSubtitle ?? deps.broadcastSubtitle)(immediatePayload);
|
(deps.emitImmediateSubtitle ?? deps.broadcastSubtitle)(immediatePayload);
|
||||||
@@ -30,27 +25,19 @@ export function createHandleMpvSubtitleChangeHandler(deps: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createHandleMpvSubtitleAssChangeHandler(deps: {
|
export function createHandleMpvSubtitleAssChangeHandler(deps: {
|
||||||
shouldSuppressSubtitleEvents?: () => boolean;
|
|
||||||
setCurrentSubAssText: (text: string) => void;
|
setCurrentSubAssText: (text: string) => void;
|
||||||
broadcastSubtitleAss: (text: string) => void;
|
broadcastSubtitleAss: (text: string) => void;
|
||||||
}) {
|
}) {
|
||||||
return ({ text }: { text: string }): void => {
|
return ({ text }: { text: string }): void => {
|
||||||
deps.setCurrentSubAssText(text);
|
deps.setCurrentSubAssText(text);
|
||||||
if (deps.shouldSuppressSubtitleEvents?.()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
deps.broadcastSubtitleAss(text);
|
deps.broadcastSubtitleAss(text);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createHandleMpvSecondarySubtitleChangeHandler(deps: {
|
export function createHandleMpvSecondarySubtitleChangeHandler(deps: {
|
||||||
shouldSuppressSubtitleEvents?: () => boolean;
|
|
||||||
broadcastSecondarySubtitle: (text: string) => void;
|
broadcastSecondarySubtitle: (text: string) => void;
|
||||||
}) {
|
}) {
|
||||||
return ({ text }: { text: string }): void => {
|
return ({ text }: { text: string }): void => {
|
||||||
if (deps.shouldSuppressSubtitleEvents?.()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
deps.broadcastSecondarySubtitle(text);
|
deps.broadcastSecondarySubtitle(text);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ test('main mpv event binder wires callbacks through to runtime deps', () => {
|
|||||||
calls.push('post-watch');
|
calls.push('post-watch');
|
||||||
},
|
},
|
||||||
logSubtitleTimingError: () => calls.push('subtitle-error'),
|
logSubtitleTimingError: () => calls.push('subtitle-error'),
|
||||||
shouldSuppressSubtitleEvents: () => false,
|
|
||||||
|
|
||||||
setCurrentSubText: (text) => calls.push(`set-sub:${text}`),
|
setCurrentSubText: (text) => calls.push(`set-sub:${text}`),
|
||||||
broadcastSubtitle: (payload) => calls.push(`broadcast-sub:${payload.text}`),
|
broadcastSubtitle: (payload) => calls.push(`broadcast-sub:${payload.text}`),
|
||||||
onSubtitleChange: (text) => calls.push(`subtitle-change:${text}`),
|
onSubtitleChange: (text) => calls.push(`subtitle-change:${text}`),
|
||||||
@@ -94,67 +92,3 @@ test('main mpv event binder wires callbacks through to runtime deps', () => {
|
|||||||
assert.ok(calls.includes('sync-immersion'));
|
assert.ok(calls.includes('sync-immersion'));
|
||||||
assert.ok(calls.includes('flush-playback'));
|
assert.ok(calls.includes('flush-playback'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('main mpv event binder suppresses subtitle broadcasts while youtube flow is pending', () => {
|
|
||||||
const handlers = new Map<string, (payload: unknown) => void>();
|
|
||||||
const calls: string[] = [];
|
|
||||||
|
|
||||||
const bind = createBindMpvMainEventHandlersHandler({
|
|
||||||
reportJellyfinRemoteStopped: () => {},
|
|
||||||
syncOverlayMpvSubtitleSuppression: () => {},
|
|
||||||
resetSubtitleSidebarEmbeddedLayout: () => {},
|
|
||||||
hasInitialJellyfinPlayArg: () => false,
|
|
||||||
isOverlayRuntimeInitialized: () => false,
|
|
||||||
isQuitOnDisconnectArmed: () => false,
|
|
||||||
scheduleQuitCheck: () => {},
|
|
||||||
isMpvConnected: () => false,
|
|
||||||
quitApp: () => {},
|
|
||||||
recordImmersionSubtitleLine: () => {},
|
|
||||||
hasSubtitleTimingTracker: () => false,
|
|
||||||
recordSubtitleTiming: () => {},
|
|
||||||
maybeRunAnilistPostWatchUpdate: async () => {},
|
|
||||||
logSubtitleTimingError: () => {},
|
|
||||||
shouldSuppressSubtitleEvents: () => true,
|
|
||||||
setCurrentSubText: (text) => calls.push(`set-sub:${text}`),
|
|
||||||
broadcastSubtitle: (payload) => calls.push(`broadcast-sub:${payload.text}`),
|
|
||||||
onSubtitleChange: (text) => calls.push(`subtitle-change:${text}`),
|
|
||||||
refreshDiscordPresence: () => calls.push('presence-refresh'),
|
|
||||||
setCurrentSubAssText: (text) => calls.push(`set-ass:${text}`),
|
|
||||||
broadcastSubtitleAss: (text) => calls.push(`broadcast-ass:${text}`),
|
|
||||||
broadcastSecondarySubtitle: (text) => calls.push(`broadcast-secondary:${text}`),
|
|
||||||
updateCurrentMediaPath: () => {},
|
|
||||||
restoreMpvSubVisibility: () => {},
|
|
||||||
getCurrentAnilistMediaKey: () => null,
|
|
||||||
resetAnilistMediaTracking: () => {},
|
|
||||||
maybeProbeAnilistDuration: () => {},
|
|
||||||
ensureAnilistMediaGuess: () => {},
|
|
||||||
syncImmersionMediaState: () => {},
|
|
||||||
updateCurrentMediaTitle: () => {},
|
|
||||||
resetAnilistMediaGuessState: () => {},
|
|
||||||
notifyImmersionTitleUpdate: () => {},
|
|
||||||
recordPlaybackPosition: () => {},
|
|
||||||
recordMediaDuration: () => {},
|
|
||||||
reportJellyfinRemoteProgress: () => {},
|
|
||||||
recordPauseState: () => {},
|
|
||||||
updateSubtitleRenderMetrics: () => {},
|
|
||||||
setPreviousSecondarySubVisibility: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
bind({
|
|
||||||
on: (event, handler) => {
|
|
||||||
handlers.set(event, handler as (payload: unknown) => void);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
handlers.get('subtitle-change')?.({ text: 'line' });
|
|
||||||
handlers.get('subtitle-ass-change')?.({ text: 'ass' });
|
|
||||||
handlers.get('secondary-subtitle-change')?.({ text: 'sec' });
|
|
||||||
|
|
||||||
assert.ok(calls.includes('set-sub:line'));
|
|
||||||
assert.ok(calls.includes('set-ass:ass'));
|
|
||||||
assert.ok(calls.includes('presence-refresh'));
|
|
||||||
assert.ok(!calls.includes('broadcast-sub:line'));
|
|
||||||
assert.ok(!calls.includes('subtitle-change:line'));
|
|
||||||
assert.ok(!calls.includes('broadcast-ass:ass'));
|
|
||||||
assert.ok(!calls.includes('broadcast-secondary:sec'));
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ export function createBindMpvMainEventHandlersHandler(deps: {
|
|||||||
recordSubtitleTiming: (text: string, start: number, end: number) => void;
|
recordSubtitleTiming: (text: string, start: number, end: number) => void;
|
||||||
maybeRunAnilistPostWatchUpdate: () => Promise<void>;
|
maybeRunAnilistPostWatchUpdate: () => Promise<void>;
|
||||||
logSubtitleTimingError: (message: string, error: unknown) => void;
|
logSubtitleTimingError: (message: string, error: unknown) => void;
|
||||||
shouldSuppressSubtitleEvents?: () => boolean;
|
|
||||||
|
|
||||||
setCurrentSubText: (text: string) => void;
|
setCurrentSubText: (text: string) => void;
|
||||||
getImmediateSubtitlePayload?: (text: string) => SubtitleData | null;
|
getImmediateSubtitlePayload?: (text: string) => SubtitleData | null;
|
||||||
@@ -100,7 +99,6 @@ export function createBindMpvMainEventHandlersHandler(deps: {
|
|||||||
logError: (message, error) => deps.logSubtitleTimingError(message, error),
|
logError: (message, error) => deps.logSubtitleTimingError(message, error),
|
||||||
});
|
});
|
||||||
const handleMpvSubtitleChange = createHandleMpvSubtitleChangeHandler({
|
const handleMpvSubtitleChange = createHandleMpvSubtitleChangeHandler({
|
||||||
shouldSuppressSubtitleEvents: () => deps.shouldSuppressSubtitleEvents?.() ?? false,
|
|
||||||
setCurrentSubText: (text) => deps.setCurrentSubText(text),
|
setCurrentSubText: (text) => deps.setCurrentSubText(text),
|
||||||
getImmediateSubtitlePayload: (text) => deps.getImmediateSubtitlePayload?.(text) ?? null,
|
getImmediateSubtitlePayload: (text) => deps.getImmediateSubtitlePayload?.(text) ?? null,
|
||||||
emitImmediateSubtitle: (payload) => deps.emitImmediateSubtitle?.(payload),
|
emitImmediateSubtitle: (payload) => deps.emitImmediateSubtitle?.(payload),
|
||||||
@@ -109,12 +107,10 @@ export function createBindMpvMainEventHandlersHandler(deps: {
|
|||||||
refreshDiscordPresence: () => deps.refreshDiscordPresence(),
|
refreshDiscordPresence: () => deps.refreshDiscordPresence(),
|
||||||
});
|
});
|
||||||
const handleMpvSubtitleAssChange = createHandleMpvSubtitleAssChangeHandler({
|
const handleMpvSubtitleAssChange = createHandleMpvSubtitleAssChangeHandler({
|
||||||
shouldSuppressSubtitleEvents: () => deps.shouldSuppressSubtitleEvents?.() ?? false,
|
|
||||||
setCurrentSubAssText: (text) => deps.setCurrentSubAssText(text),
|
setCurrentSubAssText: (text) => deps.setCurrentSubAssText(text),
|
||||||
broadcastSubtitleAss: (text) => deps.broadcastSubtitleAss(text),
|
broadcastSubtitleAss: (text) => deps.broadcastSubtitleAss(text),
|
||||||
});
|
});
|
||||||
const handleMpvSecondarySubtitleChange = createHandleMpvSecondarySubtitleChangeHandler({
|
const handleMpvSecondarySubtitleChange = createHandleMpvSecondarySubtitleChangeHandler({
|
||||||
shouldSuppressSubtitleEvents: () => deps.shouldSuppressSubtitleEvents?.() ?? false,
|
|
||||||
broadcastSecondarySubtitle: (text) => deps.broadcastSecondarySubtitle(text),
|
broadcastSecondarySubtitle: (text) => deps.broadcastSecondarySubtitle(text),
|
||||||
});
|
});
|
||||||
const handleMpvMediaPathChange = createHandleMpvMediaPathChangeHandler({
|
const handleMpvMediaPathChange = createHandleMpvMediaPathChangeHandler({
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ test('mpv main event main deps map app state updates and delegate callbacks', as
|
|||||||
currentSubAssText: '',
|
currentSubAssText: '',
|
||||||
playbackPaused: null,
|
playbackPaused: null,
|
||||||
previousSecondarySubVisibility: false,
|
previousSecondarySubVisibility: false,
|
||||||
youtubePlaybackFlowPending: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const deps = createBuildBindMpvMainEventHandlersMainDepsHandler({
|
const deps = createBuildBindMpvMainEventHandlersMainDepsHandler({
|
||||||
@@ -75,7 +74,6 @@ test('mpv main event main deps map app state updates and delegate callbacks', as
|
|||||||
deps.recordSubtitleTiming('y', 0, 1);
|
deps.recordSubtitleTiming('y', 0, 1);
|
||||||
await deps.maybeRunAnilistPostWatchUpdate();
|
await deps.maybeRunAnilistPostWatchUpdate();
|
||||||
deps.logSubtitleTimingError('err', new Error('boom'));
|
deps.logSubtitleTimingError('err', new Error('boom'));
|
||||||
assert.equal(deps.shouldSuppressSubtitleEvents?.(), false);
|
|
||||||
deps.setCurrentSubText('sub');
|
deps.setCurrentSubText('sub');
|
||||||
deps.broadcastSubtitle({ text: 'sub', tokens: null });
|
deps.broadcastSubtitle({ text: 'sub', tokens: null });
|
||||||
deps.onSubtitleChange('sub');
|
deps.onSubtitleChange('sub');
|
||||||
@@ -119,7 +117,7 @@ test('mpv main event main deps map app state updates and delegate callbacks', as
|
|||||||
assert.ok(calls.includes('reset-sidebar-layout'));
|
assert.ok(calls.includes('reset-sidebar-layout'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('mpv main event main deps suppress subtitle events while youtube flow is pending', () => {
|
test('mpv main event main deps wire subtitle callbacks without suppression gate', () => {
|
||||||
const deps = createBuildBindMpvMainEventHandlersMainDepsHandler({
|
const deps = createBuildBindMpvMainEventHandlersMainDepsHandler({
|
||||||
appState: {
|
appState: {
|
||||||
initialArgs: null,
|
initialArgs: null,
|
||||||
@@ -131,7 +129,6 @@ test('mpv main event main deps suppress subtitle events while youtube flow is pe
|
|||||||
currentSubAssText: '',
|
currentSubAssText: '',
|
||||||
playbackPaused: null,
|
playbackPaused: null,
|
||||||
previousSecondarySubVisibility: false,
|
previousSecondarySubVisibility: false,
|
||||||
youtubePlaybackFlowPending: true,
|
|
||||||
},
|
},
|
||||||
getQuitOnDisconnectArmed: () => false,
|
getQuitOnDisconnectArmed: () => false,
|
||||||
scheduleQuitCheck: () => {},
|
scheduleQuitCheck: () => {},
|
||||||
@@ -158,5 +155,6 @@ test('mpv main event main deps suppress subtitle events while youtube flow is pe
|
|||||||
refreshDiscordPresence: () => {},
|
refreshDiscordPresence: () => {},
|
||||||
})();
|
})();
|
||||||
|
|
||||||
assert.equal(deps.shouldSuppressSubtitleEvents?.(), true);
|
deps.setCurrentSubText('sub');
|
||||||
|
assert.equal(typeof deps.setCurrentSubText, 'function');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
|||||||
currentSubtitleData?: SubtitleData | null;
|
currentSubtitleData?: SubtitleData | null;
|
||||||
playbackPaused: boolean | null;
|
playbackPaused: boolean | null;
|
||||||
previousSecondarySubVisibility: boolean | null;
|
previousSecondarySubVisibility: boolean | null;
|
||||||
youtubePlaybackFlowPending: boolean;
|
|
||||||
};
|
};
|
||||||
getQuitOnDisconnectArmed: () => boolean;
|
getQuitOnDisconnectArmed: () => boolean;
|
||||||
scheduleQuitCheck: (callback: () => void) => void;
|
scheduleQuitCheck: (callback: () => void) => void;
|
||||||
@@ -120,7 +119,6 @@ export function createBuildBindMpvMainEventHandlersMainDepsHandler(deps: {
|
|||||||
maybeRunAnilistPostWatchUpdate: () => deps.maybeRunAnilistPostWatchUpdate(),
|
maybeRunAnilistPostWatchUpdate: () => deps.maybeRunAnilistPostWatchUpdate(),
|
||||||
logSubtitleTimingError: (message: string, error: unknown) =>
|
logSubtitleTimingError: (message: string, error: unknown) =>
|
||||||
deps.logSubtitleTimingError(message, error),
|
deps.logSubtitleTimingError(message, error),
|
||||||
shouldSuppressSubtitleEvents: () => deps.appState.youtubePlaybackFlowPending,
|
|
||||||
setCurrentSubText: (text: string) => {
|
setCurrentSubText: (text: string) => {
|
||||||
deps.appState.currentSubText = text;
|
deps.appState.currentSubText = text;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import type { YoutubePickerOpenPayload } from '../../types';
|
|||||||
const payload: YoutubePickerOpenPayload = {
|
const payload: YoutubePickerOpenPayload = {
|
||||||
sessionId: 'yt-1',
|
sessionId: 'yt-1',
|
||||||
url: 'https://example.com/watch?v=abc',
|
url: 'https://example.com/watch?v=abc',
|
||||||
mode: 'download',
|
|
||||||
tracks: [],
|
tracks: [],
|
||||||
defaultPrimaryTrackId: null,
|
defaultPrimaryTrackId: null,
|
||||||
defaultSecondaryTrackId: null,
|
defaultSecondaryTrackId: null,
|
||||||
|
|||||||
@@ -95,14 +95,14 @@ test('transitionAnilistUpdateInFlightState updates inFlight only', () => {
|
|||||||
assert.notEqual(transitioned, current);
|
assert.notEqual(transitioned, current);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('applyStartupState does not mark youtube playback flow pending from startup args alone', () => {
|
test('applyStartupState preserves cleared startup-only runtime flags', () => {
|
||||||
const appState = createAppState({
|
const appState = createAppState({
|
||||||
mpvSocketPath: '/tmp/mpv.sock',
|
mpvSocketPath: '/tmp/mpv.sock',
|
||||||
texthookerPort: 4000,
|
texthookerPort: 4000,
|
||||||
});
|
});
|
||||||
|
|
||||||
applyStartupState(appState, {
|
applyStartupState(appState, {
|
||||||
initialArgs: parseArgs(['--youtube-play', 'https://www.youtube.com/watch?v=video123']),
|
initialArgs: parseArgs(['--settings']),
|
||||||
mpvSocketPath: '/tmp/mpv.sock',
|
mpvSocketPath: '/tmp/mpv.sock',
|
||||||
texthookerPort: 4000,
|
texthookerPort: 4000,
|
||||||
backendOverride: null,
|
backendOverride: null,
|
||||||
@@ -111,5 +111,5 @@ test('applyStartupState does not mark youtube playback flow pending from startup
|
|||||||
backgroundMode: false,
|
backgroundMode: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(appState.youtubePlaybackFlowPending, false);
|
assert.equal(appState.initialArgs?.settings, true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -188,7 +188,6 @@ export interface AppState {
|
|||||||
overlayDebugVisualizationEnabled: boolean;
|
overlayDebugVisualizationEnabled: boolean;
|
||||||
statsOverlayVisible: boolean;
|
statsOverlayVisible: boolean;
|
||||||
subsyncInProgress: boolean;
|
subsyncInProgress: boolean;
|
||||||
youtubePlaybackFlowPending: boolean;
|
|
||||||
initialArgs: CliArgs | null;
|
initialArgs: CliArgs | null;
|
||||||
mpvSocketPath: string;
|
mpvSocketPath: string;
|
||||||
texthookerPort: number;
|
texthookerPort: number;
|
||||||
@@ -273,7 +272,6 @@ export function createAppState(values: AppStateInitialValues): AppState {
|
|||||||
fieldGroupingResolver: null,
|
fieldGroupingResolver: null,
|
||||||
fieldGroupingResolverSequence: 0,
|
fieldGroupingResolverSequence: 0,
|
||||||
subsyncInProgress: false,
|
subsyncInProgress: false,
|
||||||
youtubePlaybackFlowPending: false,
|
|
||||||
initialArgs: null,
|
initialArgs: null,
|
||||||
mpvSocketPath: values.mpvSocketPath,
|
mpvSocketPath: values.mpvSocketPath,
|
||||||
texthookerPort: values.texthookerPort,
|
texthookerPort: values.texthookerPort,
|
||||||
@@ -293,7 +291,6 @@ export function createAppState(values: AppStateInitialValues): AppState {
|
|||||||
|
|
||||||
export function applyStartupState(appState: AppState, startupState: StartupState): void {
|
export function applyStartupState(appState: AppState, startupState: StartupState): void {
|
||||||
appState.initialArgs = startupState.initialArgs;
|
appState.initialArgs = startupState.initialArgs;
|
||||||
appState.youtubePlaybackFlowPending = false;
|
|
||||||
appState.mpvSocketPath = startupState.mpvSocketPath;
|
appState.mpvSocketPath = startupState.mpvSocketPath;
|
||||||
appState.texthookerPort = startupState.texthookerPort;
|
appState.texthookerPort = startupState.texthookerPort;
|
||||||
appState.backendOverride = startupState.backendOverride;
|
appState.backendOverride = startupState.backendOverride;
|
||||||
|
|||||||
@@ -151,7 +151,6 @@ test('youtube track picker close restores focus and mouse-ignore state', () => {
|
|||||||
modal.openYoutubePickerModal({
|
modal.openYoutubePickerModal({
|
||||||
sessionId: 'yt-1',
|
sessionId: 'yt-1',
|
||||||
url: 'https://example.com',
|
url: 'https://example.com',
|
||||||
mode: 'download',
|
|
||||||
tracks: [],
|
tracks: [],
|
||||||
defaultPrimaryTrackId: null,
|
defaultPrimaryTrackId: null,
|
||||||
defaultSecondaryTrackId: null,
|
defaultSecondaryTrackId: null,
|
||||||
@@ -241,7 +240,6 @@ test('youtube track picker re-acknowledges repeated open requests', () => {
|
|||||||
modal.openYoutubePickerModal({
|
modal.openYoutubePickerModal({
|
||||||
sessionId: 'yt-1',
|
sessionId: 'yt-1',
|
||||||
url: 'https://example.com/one',
|
url: 'https://example.com/one',
|
||||||
mode: 'download',
|
|
||||||
tracks: [],
|
tracks: [],
|
||||||
defaultPrimaryTrackId: null,
|
defaultPrimaryTrackId: null,
|
||||||
defaultSecondaryTrackId: null,
|
defaultSecondaryTrackId: null,
|
||||||
@@ -250,7 +248,6 @@ test('youtube track picker re-acknowledges repeated open requests', () => {
|
|||||||
modal.openYoutubePickerModal({
|
modal.openYoutubePickerModal({
|
||||||
sessionId: 'yt-2',
|
sessionId: 'yt-2',
|
||||||
url: 'https://example.com/two',
|
url: 'https://example.com/two',
|
||||||
mode: 'generate',
|
|
||||||
tracks: [],
|
tracks: [],
|
||||||
defaultPrimaryTrackId: null,
|
defaultPrimaryTrackId: null,
|
||||||
defaultSecondaryTrackId: null,
|
defaultSecondaryTrackId: null,
|
||||||
@@ -327,7 +324,6 @@ test('youtube track picker surfaces rejected resolve calls as modal status', asy
|
|||||||
modal.openYoutubePickerModal({
|
modal.openYoutubePickerModal({
|
||||||
sessionId: 'yt-1',
|
sessionId: 'yt-1',
|
||||||
url: 'https://example.com',
|
url: 'https://example.com',
|
||||||
mode: 'download',
|
|
||||||
tracks: [
|
tracks: [
|
||||||
{
|
{
|
||||||
id: 'auto:ja-orig',
|
id: 'auto:ja-orig',
|
||||||
@@ -432,7 +428,6 @@ test('youtube track picker ignores duplicate resolve submissions while request i
|
|||||||
modal.openYoutubePickerModal({
|
modal.openYoutubePickerModal({
|
||||||
sessionId: 'yt-1',
|
sessionId: 'yt-1',
|
||||||
url: 'https://example.com',
|
url: 'https://example.com',
|
||||||
mode: 'download',
|
|
||||||
tracks: [
|
tracks: [
|
||||||
{
|
{
|
||||||
id: 'auto:ja-orig',
|
id: 'auto:ja-orig',
|
||||||
@@ -534,7 +529,6 @@ test('youtube track picker only consumes handled keys', async () => {
|
|||||||
modal.openYoutubePickerModal({
|
modal.openYoutubePickerModal({
|
||||||
sessionId: 'yt-1',
|
sessionId: 'yt-1',
|
||||||
url: 'https://example.com',
|
url: 'https://example.com',
|
||||||
mode: 'download',
|
|
||||||
tracks: [],
|
tracks: [],
|
||||||
defaultPrimaryTrackId: null,
|
defaultPrimaryTrackId: null,
|
||||||
defaultSecondaryTrackId: null,
|
defaultSecondaryTrackId: null,
|
||||||
@@ -640,7 +634,6 @@ test('youtube track picker ignores immediate Enter after open before allowing ke
|
|||||||
modal.openYoutubePickerModal({
|
modal.openYoutubePickerModal({
|
||||||
sessionId: 'yt-1',
|
sessionId: 'yt-1',
|
||||||
url: 'https://example.com',
|
url: 'https://example.com',
|
||||||
mode: 'download',
|
|
||||||
tracks: [
|
tracks: [
|
||||||
{
|
{
|
||||||
id: 'auto:ja-orig',
|
id: 'auto:ja-orig',
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function createYoutubeTrackPickerModal(
|
|||||||
|
|
||||||
function applyPayload(payload: YoutubePickerOpenPayload): void {
|
function applyPayload(payload: YoutubePickerOpenPayload): void {
|
||||||
ctx.state.youtubePickerPayload = payload;
|
ctx.state.youtubePickerPayload = payload;
|
||||||
ctx.dom.youtubePickerTitle.textContent = `${payload.mode === 'generate' ? 'Generate' : 'Download'} subtitles for ${payload.url}`;
|
ctx.dom.youtubePickerTitle.textContent = `Select YouTube subtitles for ${payload.url}`;
|
||||||
ctx.dom.youtubePickerPrimarySelect.innerHTML = '';
|
ctx.dom.youtubePickerPrimarySelect.innerHTML = '';
|
||||||
ctx.dom.youtubePickerSecondarySelect.innerHTML = '';
|
ctx.dom.youtubePickerSecondarySelect.innerHTML = '';
|
||||||
|
|
||||||
|
|||||||
@@ -561,7 +561,6 @@ export interface ControllerRuntimeSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type JimakuLanguagePreference = 'ja' | 'en' | 'none';
|
export type JimakuLanguagePreference = 'ja' | 'en' | 'none';
|
||||||
export type YoutubeFlowMode = 'download' | 'generate';
|
|
||||||
export type { YoutubeTrackKind };
|
export type { YoutubeTrackKind };
|
||||||
|
|
||||||
export interface YoutubeTrackOption {
|
export interface YoutubeTrackOption {
|
||||||
@@ -578,7 +577,6 @@ export interface YoutubeTrackOption {
|
|||||||
export interface YoutubePickerOpenPayload {
|
export interface YoutubePickerOpenPayload {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
url: string;
|
url: string;
|
||||||
mode: YoutubeFlowMode;
|
|
||||||
tracks: YoutubeTrackOption[];
|
tracks: YoutubeTrackOption[];
|
||||||
defaultPrimaryTrackId: string | null;
|
defaultPrimaryTrackId: string | null;
|
||||||
defaultSecondaryTrackId: string | null;
|
defaultSecondaryTrackId: string | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user