SubtitleEdit/subtitleedit · error · ArgumentException
Voice is not an F5TtsVoice
Error message
Voice is not an F5TtsVoice
What it means
ArgumentException from F5TtsCrispAsr.Speak: the passed Voice's EngineVoice is not an F5TtsVoice. F5-TTS requires its concrete voice type because it reads f5Voice.FilePath (the reference WAV) to clone from. Any other engine's voice is rejected before the file checks run.
Source
Thrown at src/ui/Features/Video/TextToSpeech/Engines/F5TtsCrispAsr.cs:363
public Task<string[]> GetModels() => Task.FromResult(new[] { ModelKeyF16 });
public Task<TtsLanguage[]> GetLanguages(Voice voice, string? model) => Task.FromResult(Array.Empty<TtsLanguage>());
public Task<Voice[]> RefreshVoices(string language, CancellationToken cancellationToken) =>
GetVoices(language);
public async Task<TtsResult> Speak(
string text,
string outputFolder,
Voice voice,
TtsLanguage? language,
string? region,
string? model,
CancellationToken cancellationToken)
{
if (voice.EngineVoice is not F5TtsVoice f5Voice)
{
throw new ArgumentException("Voice is not an F5TtsVoice");
}
if (string.IsNullOrEmpty(f5Voice.FilePath))
{
throw new InvalidOperationException(
"F5-TTS (CrispASR) requires a reference voice WAV. "
+ "Import one via the voice settings, then pick it in the voice combo. "
+ "Reference WAV should be 24 kHz mono (3-10 s of clean speech) with an "
+ "adjacent .txt file holding the spoken transcription.");
}
var refText = TryReadRefText(f5Voice.FilePath);
var modelKey = ResolveModelKey(model);
await EnsureServerRunningAsync(modelKey, f5Voice.FilePath, refText, cancellationToken);
var outputFileName = Path.Combine(TtsOutputFolder.Resolve(outputFolder, GetSetFolder), Guid.NewGuid() + ".wav");
var speed = Math.Clamp(Se.Settings.Video.TextToSpeech.F5TtsCrispAsrSpeed, 0.25, 4.0);View on GitHub (pinned to 17a9f07487)
Solutions
- Pass only voices produced by F5TtsCrispAsr.GetVoices/RefreshVoices into F5TtsCrispAsr.Speak.
- Dispatch each Voice to its owning engine by EngineVoice type.
- Add a test asserting each cast voice maps to its producing engine.
Example fix
// before
await f5TtsEngine.Speak(text, out, edgeVoice, lang, region, model, ct);
// after
var engine = voice.EngineVoice switch
{
F5TtsVoice _ => f5TtsEngine,
EdgeTtsVoice _ => edgeTtsEngine,
_ => throw new InvalidOperationException($"No engine for {voice.EngineVoice.GetType().Name}"),
};
await engine.Speak(text, out, voice, lang, region, model, ct); Defensive patterns
Strategy: type-guard
Validate before calling
if (voice.EngineVoice is not F5TtsVoice) {
Se.WriteToolsLog($"Skipping F5-TTS synthesis: voice is {voice.EngineVoice?.GetType().Name}", true);
return;
} Type guard
static bool IsF5TtsVoice(Voice voice) => voice?.EngineVoice is F5TtsVoice;
Try / catch
try { await f5TtsEngine.Speak(text, out, voice, lang, region, model, ct); }
catch (ArgumentException ex) when (ex.Message == "Voice is not an F5TtsVoice")
{ Se.WriteToolsLog($"Voice/engine mismatch for F5-TTS: {voice.EngineVoice?.GetType().Name}", true); } Prevention
- Route each Voice to the engine that produced it.
- Unit-test cast/review voices resolve to their producing engine.
- Never wrap another engine's EngineVoice in a Voice for F5-TTS.
When it happens
Trigger: Calling f5TtsEngine.Speak(...) with a Voice whose EngineVoice is not an F5TtsVoice; a dispatch bug; deserialised voice rehydrated to the wrong subtype.
Common situations: Cast row passing a global voice from another engine into F5-TTS; refactor that changed EngineVoice typing; voice JSON rehydration choosing the wrong concrete type.
Related errors
- Voice is not a CosyVoice3Voice
- Voice is not an EdgeTtsVoice
- Voice is not an ElevenLabVoice
- F5-TTS (CrispASR) requires a reference voice WAV. Import one
- F5-TTS (CrispASR) — the crispasr server crashed during synth
AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13).
Data as JSON: /api/errors/b29822d60826c25c.
Report an issue: GitHub.