Files
SubMiner/src/shared/subtitle-generation.ts
T
sudacode 2d623838d5 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
2026-09-07 21:43:53 -07:00

87 lines
2.5 KiB
TypeScript

import {
isSubtitleGenerationModelId,
RECOMMENDED_SUBTITLE_GENERATION_MODEL,
type SubtitleGenerationModelId,
} from './subtitle-generation-model-catalog';
export interface SubtitleGenerationConfig {
whisperPath: string;
modelPath: string;
managedModel: SubtitleGenerationModelId;
threads: number;
ffmpegPath: string;
ffprobePath: string;
vadModelPath: string;
vadPath: string;
}
export const DEFAULT_SUBTITLE_GENERATION_CONFIG: SubtitleGenerationConfig = {
whisperPath: '',
modelPath: '',
managedModel: RECOMMENDED_SUBTITLE_GENERATION_MODEL,
threads: 4,
ffmpegPath: '',
ffprobePath: '',
vadModelPath: '',
vadPath: '',
};
export interface SubtitleGenerationProgress {
stage: 'download' | 'extract' | 'transcribe' | 'write';
percent?: number;
message: string;
}
export type SubtitleGenerationModelStatus =
| { kind: 'external' | 'managed'; path: string }
| { kind: 'missing'; path: string }
| { kind: 'invalid'; path: string; message: string };
export function resolveSubtitleGenerationConfig(
value: unknown,
onWarning?: (key: string, value: unknown, message: string) => void,
): SubtitleGenerationConfig {
const result = { ...DEFAULT_SUBTITLE_GENERATION_CONFIG };
if (value === undefined) return result;
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
onWarning?.('subtitleGeneration', value, 'Expected an object.');
return result;
}
for (const [key, rawValue] of Object.entries(value)) {
if (
key !== 'whisperPath' &&
key !== 'modelPath' &&
key !== 'ffmpegPath' &&
key !== 'ffprobePath' &&
key !== 'vadModelPath' &&
key !== 'vadPath'
)
continue;
const candidate: unknown = rawValue;
if (typeof candidate === 'string') {
result[key] = candidate.trim();
} else onWarning?.(key, candidate, 'Expected a string.');
}
if ('managedModel' in value) {
if (isSubtitleGenerationModelId(value.managedModel)) {
result.managedModel = value.managedModel;
} else
onWarning?.(
'managedModel',
value.managedModel,
'Expected a supported multilingual Whisper model.',
);
}
if ('threads' in value) {
if (
typeof value.threads === 'number' &&
Number.isInteger(value.threads) &&
value.threads >= 1 &&
value.threads <= 256
) {
result.threads = value.threads;
} else onWarning?.('threads', value.threads, 'Expected an integer between 1 and 256.');
}
return result;
}