feat(youtube): add mediaCache mode and safer stream media extraction (#130)

This commit is contained in:
2026-06-27 19:20:18 -07:00
committed by GitHub
parent d199376364
commit 5326ad32f5
52 changed files with 4065 additions and 165 deletions
+52
View File
@@ -80,6 +80,8 @@ test('loads defaults when config is missing', () => {
assert.equal('remoteControlDeviceName' in config.jellyfin, false);
assert.equal('deviceId' in config.jellyfin, false);
assert.equal('clientVersion' in config.jellyfin, false);
assert.equal(config.youtube.mediaCache.mode, 'direct');
assert.equal(config.youtube.mediaCache.maxHeight, 720);
assert.equal(config.ai.enabled, false);
assert.equal(config.ai.apiKeyCommand, '');
assert.equal(config.texthooker.openBrowser, false);
@@ -1750,6 +1752,56 @@ test('parses global shortcuts and startup settings', () => {
assert.equal(config.youtubeSubgen.fixWithAi, true);
});
test('parses YouTube media cache config and warns on invalid values', () => {
const validDir = makeTempDir();
fs.writeFileSync(
path.join(validDir, 'config.jsonc'),
`{
"youtube": {
"mediaCache": {
"mode": "background",
"maxHeight": 480
}
}
}`,
'utf-8',
);
const validService = new ConfigService(validDir);
assert.equal(validService.getConfig().youtube.mediaCache.mode, 'background');
assert.equal(validService.getConfig().youtube.mediaCache.maxHeight, 480);
const invalidDir = makeTempDir();
fs.writeFileSync(
path.join(invalidDir, 'config.jsonc'),
`{
"youtube": {
"mediaCache": {
"mode": "always",
"maxHeight": -1
}
}
}`,
'utf-8',
);
const invalidService = new ConfigService(invalidDir);
assert.equal(
invalidService.getConfig().youtube.mediaCache.mode,
DEFAULT_CONFIG.youtube.mediaCache.mode,
);
assert.equal(
invalidService.getConfig().youtube.mediaCache.maxHeight,
DEFAULT_CONFIG.youtube.mediaCache.maxHeight,
);
assert.ok(
invalidService.getWarnings().some((warning) => warning.path === 'youtube.mediaCache.mode'),
);
assert.ok(
invalidService.getWarnings().some((warning) => warning.path === 'youtube.mediaCache.maxHeight'),
);
});
test('parses controller settings with logical bindings and tuning knobs', () => {
const dir = makeTempDir();
fs.writeFileSync(
+4
View File
@@ -111,6 +111,10 @@ export const CORE_DEFAULT_CONFIG: Pick<
},
youtube: {
primarySubLanguages: ['ja', 'jpn'],
mediaCache: {
mode: 'direct',
maxHeight: 720,
},
},
subsync: {
alass_path: '',
+18
View File
@@ -119,6 +119,24 @@ export function buildCoreConfigOptionRegistry(
description:
'Comma-separated primary subtitle language priority for managed subtitle auto-selection.',
},
{
path: 'youtube.mediaCache.mode',
kind: 'enum',
enumValues: ['direct', 'background'],
enumLabels: {
direct: 'Direct stream extraction',
background: 'Background media cache',
},
defaultValue: defaultConfig.youtube.mediaCache.mode,
description: 'How YouTube card audio/images are extracted.',
},
{
path: 'youtube.mediaCache.maxHeight',
kind: 'number',
defaultValue: defaultConfig.youtube.mediaCache.maxHeight,
description:
'Maximum video height downloaded for the YouTube background media cache. Set to 0 for unlimited.',
},
{
path: 'controller.enabled',
kind: 'boolean',
+1
View File
@@ -32,6 +32,7 @@ export interface ConfigOptionRegistryEntry {
* `osd` and `osd-system` in NOTIFICATION_TYPE_VALUES.
*/
enumValues?: readonly string[];
enumLabels?: Record<string, string>;
/**
* Optional settings UI subset when legacy/runtime-valid enum options should remain
* editable in config files but hidden from new UI choices, for example
+33
View File
@@ -297,6 +297,39 @@ export function applyCoreDomainConfig(context: ResolveContext): void {
'Expected string array.',
);
}
if (isObject(src.youtube.mediaCache)) {
const mode = src.youtube.mediaCache.mode;
if (mode === 'direct' || mode === 'background') {
resolved.youtube.mediaCache.mode = mode;
} else if (mode !== undefined) {
warn(
'youtube.mediaCache.mode',
mode,
resolved.youtube.mediaCache.mode,
"Expected 'direct' or 'background'.",
);
}
const maxHeight = asNumber(src.youtube.mediaCache.maxHeight);
if (maxHeight !== undefined && Number.isInteger(maxHeight) && maxHeight >= 0) {
resolved.youtube.mediaCache.maxHeight = maxHeight;
} else if (src.youtube.mediaCache.maxHeight !== undefined) {
warn(
'youtube.mediaCache.maxHeight',
src.youtube.mediaCache.maxHeight,
resolved.youtube.mediaCache.maxHeight,
'Expected a whole number at least 0.',
);
}
} else if (src.youtube.mediaCache !== undefined) {
warn(
'youtube.mediaCache',
src.youtube.mediaCache,
resolved.youtube.mediaCache,
'Expected object.',
);
}
}
if (isObject(src.subsync)) {
+14
View File
@@ -165,6 +165,20 @@ test('settings registry exposes specialized controls for config-assisted inputs'
assert.equal(field('discordPresence.presenceStyle').control, 'select');
});
test('settings registry exposes YouTube media cache mode as a labeled select', () => {
const mediaCacheMode = field('youtube.mediaCache.mode');
const mediaCacheMaxHeight = field('youtube.mediaCache.maxHeight');
assert.equal(mediaCacheMode.control, 'select');
assert.deepEqual(mediaCacheMode.enumValues, ['direct', 'background']);
assert.deepEqual(mediaCacheMode.enumLabels, {
direct: 'Direct stream extraction',
background: 'Background media cache',
});
assert.equal(mediaCacheMaxHeight.control, 'number');
assert.equal(mediaCacheMaxHeight.defaultValue, 720);
});
test('settings registry exposes css declaration editor for primary and secondary subtitle appearance', () => {
const primaryVisible = fields
.filter(
+1
View File
@@ -720,6 +720,7 @@ function fieldForLeaf(leaf: Leaf): ConfigSettingsField {
...(option?.settingsEnumValues || option?.enumValues
? { enumValues: option.settingsEnumValues ?? option.enumValues }
: {}),
...(option?.enumLabels ? { enumLabels: option.enumLabels } : {}),
restartBehavior: restartBehaviorForPath(leaf.path),
advanced:
leaf.path.startsWith('controller.') ||