{"record":{"id":"a5bb82f724e106fe","repo":"SubtitleEdit/subtitleedit","slug":"voice-is-not-a-chatterboxvoice","errorCode":null,"errorMessage":"Voice is not a ChatterboxVoice","messagePattern":"Voice is not a ChatterboxVoice","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/ui/Features/Video/TextToSpeech/Engines/ChatterboxTtsCpp.cs","lineNumber":347,"sourceCode":"            : ChatterboxLanguages.All);\n\n    public Task<Voice[]> RefreshVoices(string language, CancellationToken cancellationToken)\n    {\n        return GetVoices(language);\n    }\n\n    public async Task<TtsResult> Speak(\n        string text,\n        string outputFolder,\n        Voice voice,\n        TtsLanguage? language,\n        string? region,\n        string? model,\n        CancellationToken cancellationToken)\n    {\n        if (voice.EngineVoice is not ChatterboxVoice chatterboxVoice)\n        {\n            throw new ArgumentException(\"Voice is not a ChatterboxVoice\");\n        }\n\n        var modelKey = ResolveModelKey(model);\n        await EnsureServerRunningAsync(modelKey, cancellationToken);\n\n        // Off the calling thread because the repair shells out to ffmpeg. Deliberately not a hard\n        // failure either: the check is stricter than the backend's (which still has a partial\n        // 16 kHz path), so a reference that could not be re-encoded - no ffmpeg, say - is still\n        // worth sending. If the backend does reject it, the 500 handler below turns \"backend\n        // returned empty audio\" into an explanation.\n        await Task.Run(() => EnsureCloneReferenceIsUsable(chatterboxVoice.FilePath), cancellationToken);\n\n        var outputFileName = Path.Combine(TtsOutputFolder.Resolve(outputFolder, GetSetFolder), Guid.NewGuid() + \".wav\");\n        var inputText = text;\n\n        // Multilingual language selection (#13273-adjacent): a per-request field the server\n        // turns into the [xx] prompt token. Only the Base model is multilingual — Turbo is an\n        // English-only distillation, so no field is sent for it regardless of the pick.","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Features/Video/TextToSpeech/Engines/ChatterboxTtsCpp.cs#L329-L365","documentation":"ChatterboxTtsCpp.Speak pattern-matches voice.EngineVoice to ChatterboxVoice before resolving the model key and ensuring the server is running. Any other engine's voice triggers ArgumentException — a programmer/config error before any work is done.","triggerScenarios":"A Voice whose EngineVoice is not ChatterboxVoice is passed to ChatterboxTtsCpp.Speak.","commonSituations":"Stale mapping after switching to/from Chatterbox; cast dialog desync; voice picked from a different engine's list.","solutions":["Re-select the voice from the Chatterbox voice list after switching engines.","Clear non-Chatterbox voice mappings before invoking Speak.","Filter the voice picker to the active engine."],"exampleFix":"// before\nawait _chatterbox.Speak(text, out, voice, lang, region, model, ct);\n// after\nif (voice.EngineVoice is not ChatterboxVoice)\n    throw new InvalidOperationException($\"Voice {voice.Name} is not a Chatterbox voice.\");\nawait _chatterbox.Speak(text, out, voice, lang, region, model, ct);","handlingStrategy":"type-guard","validationCode":"if (voice.EngineVoice is not ChatterboxVoice) return Invalid($\"Voice {voice.Name} is not a Chatterbox voice\");","typeGuard":"static bool IsChatterboxVoice(Voice v) => v.EngineVoice is ChatterboxVoice;","tryCatchPattern":"try { await chatterbox.Speak(...); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"ChatterboxVoice\"))\n{ /* re-select the voice from the Chatterbox list */ }","preventionTips":["Filter the voice picker to Chatterbox voices when Chatterbox is active.","Clear non-Chatterbox mappings on engine switch.","Guard the type at the call site before resolving the model key."],"tags":["tts","voice","type-mismatch","chatterbox","text-to-speech"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}