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
// Defaults for SubMiner YouTube subtitle loading and languages.
// ==========================================
"youtubeSubgen": {
"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.
"youtube": {
"primarySubLanguages": [
"ja",
"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.
// ==========================================

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -83,6 +83,12 @@ export function buildCoreConfigOptionRegistry(
defaultValue: defaultConfig.logging.level,
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',
kind: 'boolean',

View File

@@ -411,11 +411,5 @@ export function buildIntegrationConfigOptionRegistry(
description:
'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',
description: ['Defaults for SubMiner YouTube subtitle loading and languages.'],
key: 'youtubeSubgen',
key: 'youtube',
},
{
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)) {
const mode = src.subsync.defaultMode;
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.');
}
if (Array.isArray(src.youtubeSubgen.primarySubLanguages)) {
resolved.youtubeSubgen.primarySubLanguages = src.youtubeSubgen.primarySubLanguages.filter(
(item): item is string => typeof item === 'string',
);
} else if (src.youtubeSubgen.primarySubLanguages !== undefined) {
if (src.youtubeSubgen.primarySubLanguages !== undefined) {
warn(
'youtubeSubgen.primarySubLanguages',
src.youtubeSubgen.primarySubLanguages,
resolved.youtubeSubgen.primarySubLanguages,
'Expected string array.',
undefined,
'Removed. Use youtube.primarySubLanguages instead.',
);
}
}

View File

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

View File

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