mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-07 13:08:54 -07:00
feat(youtube): add mediaCache mode and safer stream media extraction (#130)
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -111,6 +111,10 @@ export const CORE_DEFAULT_CONFIG: Pick<
|
||||
},
|
||||
youtube: {
|
||||
primarySubLanguages: ['ja', 'jpn'],
|
||||
mediaCache: {
|
||||
mode: 'direct',
|
||||
maxHeight: 720,
|
||||
},
|
||||
},
|
||||
subsync: {
|
||||
alass_path: '',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.') ||
|
||||
|
||||
Reference in New Issue
Block a user