mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-12 05:16:19 -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,86 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user