refactor: move youtube primary subtitle config to youtube

This commit is contained in:
2026-03-25 23:53:56 -07:00
parent 61d15f9431
commit 242402b253
16 changed files with 66 additions and 44 deletions

View File

@@ -0,0 +1,5 @@
type: changed
area: launcher
Moved YouTube primary subtitle language defaults to `youtube.primarySubLanguages`.
Removed the old `youtubeSubgen.primarySubLanguages` config path from the generated config and docs.

View File

@@ -417,20 +417,11 @@
// YouTube Playback Settings // YouTube Playback Settings
// Defaults for SubMiner YouTube subtitle loading and languages. // Defaults for SubMiner YouTube subtitle loading and languages.
// ========================================== // ==========================================
"youtubeSubgen": { "youtube": {
"whisperBin": "", // Legacy compatibility path kept for external subtitle fallback tools; not used by default.
"whisperModel": "", // Legacy compatibility model path kept for external subtitle fallback tooling; not used by default.
"whisperVadModel": "", // Legacy compatibility VAD path kept for external subtitle fallback tooling; not used by default.
"whisperThreads": 4, // Legacy thread tuning for subtitle fallback tooling; not used by default.
"fixWithAi": false, // Legacy subtitle fallback post-processing switch kept for compatibility; use is currently disabled by default. Values: true | false
"ai": {
"model": "", // Optional model override for legacy subtitle fallback post-processing; not used by default.
"systemPrompt": "" // Optional system prompt override for legacy subtitle fallback post-processing; not used by default.
}, // Ai setting.
"primarySubLanguages": [ "primarySubLanguages": [
"ja", "ja",
"jpn" "jpn"
] // Comma-separated primary subtitle language priority used by the launcher. ] // Comma-separated primary subtitle language priority for YouTube auto-loading.
}, // Defaults for SubMiner YouTube subtitle loading and languages. }, // Defaults for SubMiner YouTube subtitle loading and languages.
// ========================================== // ==========================================

View File

@@ -29,6 +29,8 @@ test('parseLauncherYoutubeSubgenConfig keeps only valid typed values', () => {
model: 'openrouter/subgen-model', model: 'openrouter/subgen-model',
systemPrompt: 'Fix subtitles only.', systemPrompt: 'Fix subtitles only.',
}, },
},
youtube: {
primarySubLanguages: ['ja', 42, 'en'], primarySubLanguages: ['ja', 42, 'en'],
}, },
secondarySub: { secondarySub: {

View File

@@ -14,6 +14,9 @@ export function parseLauncherYoutubeSubgenConfig(
youtubeSubgenRaw && typeof youtubeSubgenRaw === 'object' youtubeSubgenRaw && typeof youtubeSubgenRaw === 'object'
? (youtubeSubgenRaw as Record<string, unknown>) ? (youtubeSubgenRaw as Record<string, unknown>)
: null; : null;
const youtubeRaw = root.youtube;
const youtube =
youtubeRaw && typeof youtubeRaw === 'object' ? (youtubeRaw as Record<string, unknown>) : null;
const secondarySubRaw = root.secondarySub; const secondarySubRaw = root.secondarySub;
const secondarySub = const secondarySub =
secondarySubRaw && typeof secondarySubRaw === 'object' secondarySubRaw && typeof secondarySubRaw === 'object'
@@ -74,7 +77,7 @@ export function parseLauncherYoutubeSubgenConfig(
} }
: undefined, : undefined,
), ),
primarySubLanguages: asStringArray(youtubeSubgen?.primarySubLanguages), primarySubLanguages: asStringArray(youtube?.primarySubLanguages),
secondarySubLanguages: asStringArray(secondarySub?.secondarySubLanguages), secondarySubLanguages: asStringArray(secondarySub?.secondarySubLanguages),
jimakuApiKey: typeof jimaku?.apiKey === 'string' ? jimaku.apiKey : undefined, jimakuApiKey: typeof jimaku?.apiKey === 'string' ? jimaku.apiKey : undefined,
jimakuApiKeyCommand: jimakuApiKeyCommand:

View File

@@ -919,7 +919,7 @@ test('accepts trailing commas in jsonc', () => {
"enabled": "auto", "enabled": "auto",
"port": 7788, "port": 7788,
}, },
"youtubeSubgen": { "youtube": {
"primarySubLanguages": ["ja", "en",], "primarySubLanguages": ["ja", "en",],
}, },
}`, }`,
@@ -929,7 +929,7 @@ test('accepts trailing commas in jsonc', () => {
const service = new ConfigService(dir); const service = new ConfigService(dir);
const config = service.getConfig(); const config = service.getConfig();
assert.equal(config.websocket.port, 7788); assert.equal(config.websocket.port, 7788);
assert.deepEqual(config.youtubeSubgen.primarySubLanguages, ['ja', 'en']); assert.deepEqual(config.youtube.primarySubLanguages, ['ja', 'en']);
}); });
test('reloadConfigStrict rejects invalid jsonc and preserves previous config', () => { test('reloadConfigStrict rejects invalid jsonc and preserves previous config', () => {
@@ -1149,8 +1149,10 @@ test('parses global shortcuts and startup settings', () => {
"toggleVisibleOverlayGlobal": "Alt+Shift+U", "toggleVisibleOverlayGlobal": "Alt+Shift+U",
"openJimaku": "Ctrl+Alt+J" "openJimaku": "Ctrl+Alt+J"
}, },
"youtube": {
"primarySubLanguages": ["ja", "jpn", "jp"]
},
"youtubeSubgen": { "youtubeSubgen": {
"primarySubLanguages": ["ja", "jpn", "jp"],
"whisperVadModel": "/models/vad.bin", "whisperVadModel": "/models/vad.bin",
"whisperThreads": 12, "whisperThreads": 12,
"fixWithAi": true "fixWithAi": true
@@ -1165,7 +1167,7 @@ test('parses global shortcuts and startup settings', () => {
assert.equal(config.ai.apiKeyCommand, 'pass show subminer/ai'); assert.equal(config.ai.apiKeyCommand, 'pass show subminer/ai');
assert.equal(config.shortcuts.toggleVisibleOverlayGlobal, 'Alt+Shift+U'); assert.equal(config.shortcuts.toggleVisibleOverlayGlobal, 'Alt+Shift+U');
assert.equal(config.shortcuts.openJimaku, 'Ctrl+Alt+J'); assert.equal(config.shortcuts.openJimaku, 'Ctrl+Alt+J');
assert.deepEqual(config.youtubeSubgen.primarySubLanguages, ['ja', 'jpn', 'jp']); assert.deepEqual(config.youtube.primarySubLanguages, ['ja', 'jpn', 'jp']);
assert.equal(config.youtubeSubgen.whisperVadModel, '/models/vad.bin'); assert.equal(config.youtubeSubgen.whisperVadModel, '/models/vad.bin');
assert.equal(config.youtubeSubgen.whisperThreads, 12); assert.equal(config.youtubeSubgen.whisperThreads, 12);
assert.equal(config.youtubeSubgen.fixWithAi, true); assert.equal(config.youtubeSubgen.fixWithAi, true);
@@ -2008,7 +2010,8 @@ test('template generator includes known keys', () => {
assert.match(output, /"websocket":/); assert.match(output, /"websocket":/);
assert.match(output, /"discordPresence":/); assert.match(output, /"discordPresence":/);
assert.match(output, /"startupWarmups":/); assert.match(output, /"startupWarmups":/);
assert.match(output, /"youtubeSubgen":/); assert.match(output, /"youtube":/);
assert.doesNotMatch(output, /"youtubeSubgen":/);
assert.match(output, /"characterDictionary":\s*\{/); assert.match(output, /"characterDictionary":\s*\{/);
assert.match(output, /"preserveLineBreaks": false/); assert.match(output, /"preserveLineBreaks": false/);
assert.match(output, /"knownWords"\s*:\s*\{/); assert.match(output, /"knownWords"\s*:\s*\{/);
@@ -2074,17 +2077,11 @@ test('template generator includes known keys', () => {
); );
assert.match( assert.match(
output, output,
/"fixWithAi": false,? \/\/ Legacy subtitle fallback post-processing switch kept for compatibility; use is currently disabled by default\. Values: true \| false/, /"primarySubLanguages": \[\s*"ja",\s*"jpn"\s*\],? \/\/ Comma-separated primary subtitle language priority for YouTube auto-loading\./,
);
assert.match(
output,
/"systemPrompt": "",? \/\/ Optional system prompt override for legacy subtitle fallback post-processing; not used by default\./,
); );
assert.doesNotMatch(output, /"mode": "automatic"/); assert.doesNotMatch(output, /"mode": "automatic"/);
assert.match( assert.doesNotMatch(output, /"fixWithAi": false/);
output, assert.doesNotMatch(output, /"whisperThreads": 4/);
/"whisperThreads": 4,? \/\/ Legacy thread tuning for subtitle fallback tooling; not used by default\./,
);
assert.match( assert.match(
output, output,
/"launchAtStartup": true,? \/\/ Launch texthooker server automatically when SubMiner starts\. Values: true \| false/, /"launchAtStartup": true,? \/\/ Launch texthooker server automatically when SubMiner starts\. Values: true \| false/,

View File

@@ -30,6 +30,7 @@ const {
controller, controller,
shortcuts, shortcuts,
secondarySub, secondarySub,
youtube,
subsync, subsync,
startupWarmups, startupWarmups,
auto_start_overlay, auto_start_overlay,
@@ -51,6 +52,7 @@ export const DEFAULT_CONFIG: ResolvedConfig = {
ankiConnect, ankiConnect,
shortcuts, shortcuts,
secondarySub, secondarySub,
youtube,
subsync, subsync,
startupWarmups, startupWarmups,
subtitleStyle, subtitleStyle,

View File

@@ -11,6 +11,7 @@ export const CORE_DEFAULT_CONFIG: Pick<
| 'controller' | 'controller'
| 'shortcuts' | 'shortcuts'
| 'secondarySub' | 'secondarySub'
| 'youtube'
| 'subsync' | 'subsync'
| 'startupWarmups' | 'startupWarmups'
| 'auto_start_overlay' | 'auto_start_overlay'
@@ -93,6 +94,9 @@ export const CORE_DEFAULT_CONFIG: Pick<
autoLoadSecondarySub: false, autoLoadSecondarySub: false,
defaultMode: 'hover', defaultMode: 'hover',
}, },
youtube: {
primarySubLanguages: ['ja', 'jpn'],
},
subsync: { subsync: {
defaultMode: 'auto', defaultMode: 'auto',
alass_path: '', alass_path: '',

View File

@@ -152,6 +152,5 @@ export const INTEGRATIONS_DEFAULT_CONFIG: Pick<
model: '', model: '',
systemPrompt: '', systemPrompt: '',
}, },
primarySubLanguages: ['ja', 'jpn'],
}, },
}; };

View File

@@ -22,6 +22,7 @@ test('config option registry includes critical paths and has unique entries', ()
'controller.enabled', 'controller.enabled',
'controller.scrollPixelsPerSecond', 'controller.scrollPixelsPerSecond',
'startupWarmups.lowPowerMode', 'startupWarmups.lowPowerMode',
'youtube.primarySubLanguages',
'subtitleStyle.enableJlpt', 'subtitleStyle.enableJlpt',
'subtitleStyle.autoPauseVideoOnYomitanPopup', 'subtitleStyle.autoPauseVideoOnYomitanPopup',
'ankiConnect.enabled', 'ankiConnect.enabled',
@@ -43,6 +44,7 @@ test('config template sections include expected domains and unique keys', () =>
'annotationWebsocket', 'annotationWebsocket',
'controller', 'controller',
'startupWarmups', 'startupWarmups',
'youtube',
'subtitleStyle', 'subtitleStyle',
'ankiConnect', 'ankiConnect',
'yomitan', 'yomitan',

View File

@@ -83,6 +83,12 @@ export function buildCoreConfigOptionRegistry(
defaultValue: defaultConfig.logging.level, defaultValue: defaultConfig.logging.level,
description: 'Minimum log level for runtime logging.', description: 'Minimum log level for runtime logging.',
}, },
{
path: 'youtube.primarySubLanguages',
kind: 'string',
defaultValue: defaultConfig.youtube.primarySubLanguages.join(','),
description: 'Comma-separated primary subtitle language priority for YouTube auto-loading.',
},
{ {
path: 'controller.enabled', path: 'controller.enabled',
kind: 'boolean', kind: 'boolean',

View File

@@ -411,11 +411,5 @@ export function buildIntegrationConfigOptionRegistry(
description: description:
'Optional system prompt override for legacy subtitle fallback post-processing; not used by default.', 'Optional system prompt override for legacy subtitle fallback post-processing; not used by default.',
}, },
{
path: 'youtubeSubgen.primarySubLanguages',
kind: 'string',
defaultValue: defaultConfig.youtubeSubgen.primarySubLanguages.join(','),
description: 'Comma-separated primary subtitle language priority used by the launcher.',
},
]; ];
} }

View File

@@ -132,7 +132,7 @@ const INTEGRATION_TEMPLATE_SECTIONS: ConfigTemplateSection[] = [
{ {
title: 'YouTube Playback Settings', title: 'YouTube Playback Settings',
description: ['Defaults for SubMiner YouTube subtitle loading and languages.'], description: ['Defaults for SubMiner YouTube subtitle loading and languages.'],
key: 'youtubeSubgen', key: 'youtube',
}, },
{ {
title: 'Anilist', title: 'Anilist',

View File

@@ -524,6 +524,21 @@ export function applyCoreDomainConfig(context: ResolveContext): void {
} }
} }
if (isObject(src.youtube)) {
if (Array.isArray(src.youtube.primarySubLanguages)) {
resolved.youtube.primarySubLanguages = src.youtube.primarySubLanguages.filter(
(item): item is string => typeof item === 'string',
);
} else if (src.youtube.primarySubLanguages !== undefined) {
warn(
'youtube.primarySubLanguages',
src.youtube.primarySubLanguages,
resolved.youtube.primarySubLanguages,
'Expected string array.',
);
}
}
if (isObject(src.subsync)) { if (isObject(src.subsync)) {
const mode = src.subsync.defaultMode; const mode = src.subsync.defaultMode;
if (mode === 'auto' || mode === 'manual') { if (mode === 'auto' || mode === 'manual') {

View File

@@ -135,16 +135,12 @@ export function applySubtitleDomainConfig(context: ResolveContext): void {
warn('youtubeSubgen.ai', src.youtubeSubgen.ai, resolved.youtubeSubgen.ai, 'Expected object.'); warn('youtubeSubgen.ai', src.youtubeSubgen.ai, resolved.youtubeSubgen.ai, 'Expected object.');
} }
if (Array.isArray(src.youtubeSubgen.primarySubLanguages)) { if (src.youtubeSubgen.primarySubLanguages !== undefined) {
resolved.youtubeSubgen.primarySubLanguages = src.youtubeSubgen.primarySubLanguages.filter(
(item): item is string => typeof item === 'string',
);
} else if (src.youtubeSubgen.primarySubLanguages !== undefined) {
warn( warn(
'youtubeSubgen.primarySubLanguages', 'youtubeSubgen.primarySubLanguages',
src.youtubeSubgen.primarySubLanguages, src.youtubeSubgen.primarySubLanguages,
resolved.youtubeSubgen.primarySubLanguages, undefined,
'Expected string array.', 'Removed. Use youtube.primarySubLanguages instead.',
); );
} }
} }

View File

@@ -1337,7 +1337,7 @@ const startupOsdSequencer = createStartupOsdSequencer({
showOsd: (message) => showMpvOsd(message), showOsd: (message) => showMpvOsd(message),
}); });
const youtubePrimarySubtitleNotificationRuntime = createYoutubePrimarySubtitleNotificationRuntime({ const youtubePrimarySubtitleNotificationRuntime = createYoutubePrimarySubtitleNotificationRuntime({
getPrimarySubtitleLanguages: () => getResolvedConfig().youtubeSubgen.primarySubLanguages, getPrimarySubtitleLanguages: () => getResolvedConfig().youtube.primarySubLanguages,
notifyFailure: (message) => reportYoutubeSubtitleFailure(message), notifyFailure: (message) => reportYoutubeSubtitleFailure(message),
schedule: (fn, delayMs) => setTimeout(fn, delayMs), schedule: (fn, delayMs) => setTimeout(fn, delayMs),
clearSchedule: clearYoutubePrimarySubtitleNotificationTimer, clearSchedule: clearYoutubePrimarySubtitleNotificationTimer,

View File

@@ -683,6 +683,10 @@ export interface AiConfig {
requestTimeoutMs?: number; requestTimeoutMs?: number;
} }
export interface YoutubeConfig {
primarySubLanguages?: string[];
}
export interface YoutubeSubgenConfig { export interface YoutubeSubgenConfig {
whisperBin?: string; whisperBin?: string;
whisperModel?: string; whisperModel?: string;
@@ -690,7 +694,6 @@ export interface YoutubeSubgenConfig {
whisperThreads?: number; whisperThreads?: number;
fixWithAi?: boolean; fixWithAi?: boolean;
ai?: AiFeatureConfig; ai?: AiFeatureConfig;
primarySubLanguages?: string[];
} }
export interface StatsConfig { export interface StatsConfig {
@@ -750,6 +753,7 @@ export interface Config {
jellyfin?: JellyfinConfig; jellyfin?: JellyfinConfig;
discordPresence?: DiscordPresenceConfig; discordPresence?: DiscordPresenceConfig;
ai?: AiConfig; ai?: AiConfig;
youtube?: YoutubeConfig;
youtubeSubgen?: YoutubeSubgenConfig; youtubeSubgen?: YoutubeSubgenConfig;
immersionTracking?: ImmersionTrackingConfig; immersionTracking?: ImmersionTrackingConfig;
stats?: StatsConfig; stats?: StatsConfig;
@@ -929,6 +933,9 @@ export interface ResolvedConfig {
systemPrompt: string; systemPrompt: string;
requestTimeoutMs: number; requestTimeoutMs: number;
}; };
youtube: YoutubeConfig & {
primarySubLanguages: string[];
};
youtubeSubgen: YoutubeSubgenConfig & { youtubeSubgen: YoutubeSubgenConfig & {
whisperBin: string; whisperBin: string;
whisperModel: string; whisperModel: string;
@@ -936,7 +943,6 @@ export interface ResolvedConfig {
whisperThreads: number; whisperThreads: number;
fixWithAi: boolean; fixWithAi: boolean;
ai: AiFeatureConfig; ai: AiFeatureConfig;
primarySubLanguages: string[];
}; };
immersionTracking: { immersionTracking: {
enabled: boolean; enabled: boolean;