mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-23 05:16:23 -07:00
feat(overlay): add optional subtitle selection modal (#265)
This commit is contained in:
@@ -56,6 +56,7 @@ const { immersionTracking } = IMMERSION_DEFAULT_CONFIG;
|
||||
const { stats } = STATS_DEFAULT_CONFIG;
|
||||
|
||||
export const DEFAULT_CONFIG: ResolvedConfig = {
|
||||
subtitleSelection: { enabled: false },
|
||||
subtitleGeneration: { ...DEFAULT_SUBTITLE_GENERATION_CONFIG },
|
||||
subtitlePosition,
|
||||
keybindings,
|
||||
|
||||
@@ -99,6 +99,7 @@ export const CORE_DEFAULT_CONFIG: Pick<
|
||||
openRuntimeOptions: 'CommandOrControl+Shift+O',
|
||||
openJimaku: 'Ctrl+Shift+J',
|
||||
openTsukihime: 'Ctrl+Shift+T',
|
||||
openSubtitleSelection: 'g-s',
|
||||
openSubtitleGeneration: 'Ctrl+Shift+G',
|
||||
openSessionHelp: 'CommandOrControl+Slash',
|
||||
openControllerSelect: 'Alt+C',
|
||||
|
||||
@@ -628,6 +628,13 @@ export function buildCoreConfigOptionRegistry(
|
||||
defaultValue: defaultConfig.shortcuts.openSessionHelp,
|
||||
description: 'Accelerator that opens the session help / keybinding cheatsheet.',
|
||||
},
|
||||
{
|
||||
path: 'shortcuts.openSubtitleSelection',
|
||||
kind: 'string',
|
||||
defaultValue: defaultConfig.shortcuts.openSubtitleSelection,
|
||||
description:
|
||||
'Open subtitle selection when enabled. Use g-s to press g then s. Set null to unbind.',
|
||||
},
|
||||
{
|
||||
path: 'shortcuts.openSubtitleGeneration',
|
||||
kind: 'string',
|
||||
|
||||
@@ -6,6 +6,13 @@ export function buildSubtitleConfigOptionRegistry(
|
||||
defaultConfig: ResolvedConfig,
|
||||
): ConfigOptionRegistryEntry[] {
|
||||
return [
|
||||
{
|
||||
path: 'subtitleSelection.enabled',
|
||||
kind: 'boolean',
|
||||
defaultValue: defaultConfig.subtitleSelection.enabled,
|
||||
description:
|
||||
'Use the SubMiner modal to select primary and secondary subtitle tracks. When enabled, its shortcut overrides mpv subtitle selection.',
|
||||
},
|
||||
...(
|
||||
['whisperPath', 'modelPath', 'ffmpegPath', 'ffprobePath', 'vadModelPath', 'vadPath'] as const
|
||||
).map((key) => ({
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { ConfigTemplateSection } from './shared';
|
||||
|
||||
const CORE_TEMPLATE_SECTIONS: ConfigTemplateSection[] = [
|
||||
{
|
||||
title: 'Subtitle Selection',
|
||||
description: ['Select primary and secondary mpv subtitle tracks from the overlay.'],
|
||||
notes: ['Hot-reload: enabling or disabling updates the session shortcut immediately.'],
|
||||
key: 'subtitleSelection',
|
||||
},
|
||||
{
|
||||
title: 'Japanese Subtitle Generation',
|
||||
description: [
|
||||
|
||||
@@ -2,7 +2,13 @@ function pathStartsWith(path: string, prefix: string): boolean {
|
||||
return path === prefix || path.startsWith(`${prefix}.`);
|
||||
}
|
||||
|
||||
const HOT_RELOAD_ROOTS = ['subtitleStyle', 'keybindings', 'shortcuts', 'subtitleSidebar'] as const;
|
||||
const HOT_RELOAD_ROOTS = [
|
||||
'subtitleStyle',
|
||||
'keybindings',
|
||||
'shortcuts',
|
||||
'subtitleSidebar',
|
||||
'subtitleSelection',
|
||||
] as const;
|
||||
|
||||
const HOT_RELOAD_EXACT_OR_PREFIX_PATHS = [
|
||||
'secondarySub.defaultMode',
|
||||
|
||||
@@ -6,6 +6,25 @@ import { asBoolean, asNumber, asString, isObject } from './shared';
|
||||
export function applyCoreDomainConfig(context: ResolveContext): void {
|
||||
const { src, resolved, warn } = context;
|
||||
|
||||
if (isObject(src.subtitleSelection)) {
|
||||
const enabled = asBoolean(src.subtitleSelection.enabled);
|
||||
if (enabled !== undefined) resolved.subtitleSelection.enabled = enabled;
|
||||
else if (src.subtitleSelection.enabled !== undefined)
|
||||
warn(
|
||||
'subtitleSelection.enabled',
|
||||
src.subtitleSelection.enabled,
|
||||
resolved.subtitleSelection.enabled,
|
||||
'Expected boolean.',
|
||||
);
|
||||
} else if (src.subtitleSelection !== undefined) {
|
||||
warn(
|
||||
'subtitleSelection',
|
||||
src.subtitleSelection,
|
||||
resolved.subtitleSelection,
|
||||
'Expected object.',
|
||||
);
|
||||
}
|
||||
|
||||
if (isObject(src.texthooker)) {
|
||||
const launchAtStartup = asBoolean(src.texthooker.launchAtStartup);
|
||||
if (launchAtStartup !== undefined) {
|
||||
@@ -237,6 +256,7 @@ export function applyCoreDomainConfig(context: ResolveContext): void {
|
||||
'openRuntimeOptions',
|
||||
'openJimaku',
|
||||
'openTsukihime',
|
||||
'openSubtitleSelection',
|
||||
'openSubtitleGeneration',
|
||||
'openSessionHelp',
|
||||
'openControllerSelect',
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { resolveConfig } from '../resolve';
|
||||
import { DEFAULT_CONFIG } from '../definitions';
|
||||
import { buildConfigSettingsRegistry } from '../settings/registry';
|
||||
import { resolveConfiguredShortcuts } from '../../core/utils/shortcut-config';
|
||||
import {
|
||||
compileSessionBindings,
|
||||
buildPluginSessionBindingsArtifact,
|
||||
} from '../../core/services/session-bindings';
|
||||
|
||||
function bindings(config: ReturnType<typeof resolveConfig>['resolved']) {
|
||||
return compileSessionBindings({
|
||||
shortcuts: resolveConfiguredShortcuts(config, DEFAULT_CONFIG),
|
||||
keybindings: config.keybindings,
|
||||
platform: 'linux',
|
||||
});
|
||||
}
|
||||
|
||||
test('subtitle selection is opt-in and enabling it compiles g-s for mpv and the overlay', () => {
|
||||
const defaults = resolveConfig({}).resolved;
|
||||
assert.equal(defaults.subtitleSelection.enabled, false);
|
||||
assert.equal(defaults.shortcuts.openSubtitleSelection, 'g-s');
|
||||
const find = (config: typeof defaults) =>
|
||||
bindings(config).bindings.find(
|
||||
(binding) =>
|
||||
binding.actionType === 'session-action' && binding.actionId === 'openSubtitleSelection',
|
||||
);
|
||||
assert.equal(find(defaults), undefined);
|
||||
const enabled = resolveConfig({ subtitleSelection: { enabled: true } }).resolved;
|
||||
const binding = find(enabled);
|
||||
assert.ok(binding);
|
||||
assert.deepEqual(binding.key, { code: 'KeyG-KeyS', modifiers: [] });
|
||||
assert.equal(bindings(enabled).warnings.length, 0);
|
||||
const artifact = buildPluginSessionBindingsArtifact({
|
||||
bindings: [binding],
|
||||
warnings: [],
|
||||
numericSelectionTimeoutMs: 1000,
|
||||
});
|
||||
assert.deepEqual(artifact.bindings[0], {
|
||||
...binding,
|
||||
cliArgs: ['--session-action', '{"actionId":"openSubtitleSelection"}'],
|
||||
});
|
||||
enabled.subtitleSelection.enabled = false;
|
||||
assert.equal(find(enabled), undefined);
|
||||
});
|
||||
|
||||
test('subtitle selection settings are validated, hot reloadable, and the shortcut can be cleared', () => {
|
||||
// @ts-expect-error Config files can contain invalid values at runtime.
|
||||
const { resolved, warnings } = resolveConfig({ subtitleSelection: { enabled: 'yes' } });
|
||||
assert.equal(resolved.subtitleSelection.enabled, false);
|
||||
assert.equal(warnings.length, 1);
|
||||
const field = buildConfigSettingsRegistry(resolved).find(
|
||||
(entry) => entry.configPath === 'subtitleSelection.enabled',
|
||||
);
|
||||
assert.equal(field?.category, 'behavior');
|
||||
assert.equal(field?.restartBehavior, 'hot-reload');
|
||||
const cleared = resolveConfig({
|
||||
subtitleSelection: { enabled: true },
|
||||
shortcuts: { openSubtitleSelection: null },
|
||||
}).resolved;
|
||||
assert.equal(resolveConfiguredShortcuts(cleared, DEFAULT_CONFIG).openSubtitleSelection, null);
|
||||
});
|
||||
@@ -455,6 +455,9 @@ function categoryAndSection(path: string): { category: ConfigSettingsCategory; s
|
||||
if (path.startsWith('subsync.')) {
|
||||
return { category: 'integrations', section: topSection(path) };
|
||||
}
|
||||
if (path.startsWith('subtitleSelection.')) {
|
||||
return { category: 'behavior', section: 'Subtitle Selection' };
|
||||
}
|
||||
if (path.startsWith('subtitleGeneration.')) {
|
||||
return { category: 'integrations', section: 'Japanese Subtitle Generation' };
|
||||
}
|
||||
@@ -631,6 +634,7 @@ function subsectionForPath(path: string): string | undefined {
|
||||
leaf === 'openRuntimeOptions' ||
|
||||
leaf === 'openJimaku' ||
|
||||
leaf === 'openTsukihime' ||
|
||||
leaf === 'openSubtitleSelection' ||
|
||||
leaf === 'openSubtitleGeneration' ||
|
||||
leaf === 'openSessionHelp' ||
|
||||
leaf === 'openControllerSelect' ||
|
||||
|
||||
Reference in New Issue
Block a user