mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-12 17:16:20 -07:00
feat(subtitles): add local Japanese subtitle generation
- Generate Japanese SRTs through the overlay modal or launcher - Support managed whisper.cpp models and optional dialogue detection
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { resolveConfig } from '../resolve';
|
||||
import { buildConfigSettingsRegistry } from '../settings/registry';
|
||||
import { SUBTITLE_GENERATION_MODELS } from '../../shared/subtitle-generation-model-catalog';
|
||||
import { resolveSubtitleGenerationConfig } from '../../shared/subtitle-generation';
|
||||
|
||||
test('every downloadable multilingual model is accepted by config and offered in settings', () => {
|
||||
const settings = buildConfigSettingsRegistry(resolveConfig({}).resolved).find(
|
||||
(entry) => entry.configPath === 'subtitleGeneration.managedModel',
|
||||
);
|
||||
assert.deepEqual(
|
||||
settings?.enumValues,
|
||||
SUBTITLE_GENERATION_MODELS.map((model) => model.id),
|
||||
);
|
||||
for (const { id } of SUBTITLE_GENERATION_MODELS) {
|
||||
const { resolved, warnings } = resolveConfig({ subtitleGeneration: { managedModel: id } });
|
||||
assert.equal(resolved.subtitleGeneration.managedModel, id);
|
||||
assert.equal(warnings.length, 0);
|
||||
}
|
||||
});
|
||||
|
||||
test('generation config rejects English-only, unknown, and prototype model names', () => {
|
||||
for (const managedModel of ['tiny.en', 'small.en-q5_1', 'unknown', 'toString', '__proto__']) {
|
||||
const warnings: string[] = [];
|
||||
const resolved = resolveSubtitleGenerationConfig({ managedModel }, (key) => warnings.push(key));
|
||||
assert.equal(resolved.managedModel, 'small');
|
||||
assert.equal(warnings.length, 1);
|
||||
}
|
||||
});
|
||||
|
||||
test('generation config is resolved and its external model path is editable without restart', () => {
|
||||
const { resolved, warnings } = resolveConfig({
|
||||
subtitleGeneration: { modelPath: '/models/japanese.bin', managedModel: 'medium', threads: 8 },
|
||||
});
|
||||
assert.equal(resolved.subtitleGeneration.modelPath, '/models/japanese.bin');
|
||||
assert.equal(resolved.subtitleGeneration.managedModel, 'medium');
|
||||
assert.equal(warnings.length, 0);
|
||||
const field = buildConfigSettingsRegistry(resolved).find(
|
||||
(entry) => entry.configPath === 'subtitleGeneration.modelPath',
|
||||
);
|
||||
assert.equal(field?.category, 'integrations');
|
||||
assert.equal(field?.restartBehavior, 'hot-reload');
|
||||
});
|
||||
|
||||
test('generation executable overrides default to empty and accept blank values without warnings', () => {
|
||||
for (const subtitleGeneration of [
|
||||
{},
|
||||
{ whisperPath: '', ffmpegPath: '', ffprobePath: '' },
|
||||
{ whisperPath: ' ', ffmpegPath: ' ', ffprobePath: ' ' },
|
||||
]) {
|
||||
const { resolved, warnings } = resolveConfig({ subtitleGeneration });
|
||||
assert.equal(resolved.subtitleGeneration.whisperPath, '');
|
||||
assert.equal(resolved.subtitleGeneration.ffmpegPath, '');
|
||||
assert.equal(resolved.subtitleGeneration.ffprobePath, '');
|
||||
assert.equal(warnings.length, 0);
|
||||
}
|
||||
});
|
||||
|
||||
test('subtitle generation shortcut can be customized or disabled', () => {
|
||||
assert.equal(resolveConfig({}).resolved.shortcuts.openSubtitleGeneration, 'Ctrl+Shift+G');
|
||||
assert.equal(
|
||||
resolveConfig({ shortcuts: { openSubtitleGeneration: 'Ctrl+Alt+G' } }).resolved.shortcuts
|
||||
.openSubtitleGeneration,
|
||||
'Ctrl+Alt+G',
|
||||
);
|
||||
assert.equal(
|
||||
resolveConfig({ shortcuts: { openSubtitleGeneration: null } }).resolved.shortcuts
|
||||
.openSubtitleGeneration,
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
test('dialogue detection paths are optional, validated, and editable in Settings', () => {
|
||||
const { resolved, warnings } = resolveConfig({
|
||||
subtitleGeneration: { vadModelPath: ' /models/silero.bin ', vadPath: ' /bin/vad ' },
|
||||
});
|
||||
assert.equal(resolved.subtitleGeneration.vadModelPath, '/models/silero.bin');
|
||||
assert.equal(resolved.subtitleGeneration.vadPath, '/bin/vad');
|
||||
assert.equal(warnings.length, 0);
|
||||
for (const key of ['vadModelPath', 'vadPath'] as const) {
|
||||
const field = buildConfigSettingsRegistry(resolved).find(
|
||||
(entry) => entry.configPath === `subtitleGeneration.${key}`,
|
||||
);
|
||||
assert.equal(field?.category, 'integrations');
|
||||
assert.equal(field?.restartBehavior, 'hot-reload');
|
||||
assert.equal(resolveConfig({}).resolved.subtitleGeneration[key], '');
|
||||
assert.equal(resolveConfig({ subtitleGeneration: { [key]: false } }).warnings.length, 1);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user