Compare commits

...
Author SHA1 Message Date
localai-org-maint-bot 67db320ee6 fix(audio-cpp): forward voice reference transcripts
Saved voices send ref_text, but Fish Audio requires reference_text.
Derive the canonical parameter while preserving explicit overrides.
Both TTS modes use the shared builder.

Add regression cases and document the parameter alias.

Assisted-by: Codex:gpt-6
2026-09-12 16:06:11 +00:00
3 changed files with 48 additions and 0 deletions

No files matched your search

@@ -135,6 +135,13 @@ build_tts_request(const backend::TTSRequest &request,
task.options["language"] = request.language();
}
// Saved voice profiles send ref_text; Fish Audio reads reference_text.
// Derive the alias before copying params so an explicit canonical key wins.
const auto reference_text = request.params().find("ref_text");
if (reference_text != request.params().end()) {
task.options["reference_text"] = reference_text->second;
}
// LAST, so an explicit params entry wins over anything derived above. That
// matters for "caption": a caller who sets params[caption] has named the
// exact string they want, and it must not be overwritten by `instructions`.
@@ -375,6 +375,40 @@ static void test_tts_language_and_params() {
"tts params: an explicit param overrides the derived caption");
}
static void test_tts_reference_transcript() {
backend::TTSRequest request;
request.set_text("New speech to generate.");
request.set_voice("reference.wav");
(*request.mutable_params())["ref_text"] = "The saved voice transcript.";
const auto task = build_tts_request(request, clip(24000, 1));
check(option_or(task.options, "reference_text", "") ==
"The saved voice transcript.",
"tts reference: saved transcript reaches Fish Audio's option");
check(option_or(task.options, "ref_text", "") ==
"The saved voice transcript.",
"tts reference: original transcript parameter is preserved");
check(task.text_input->text == "New speech to generate.",
"tts reference: transcript does not replace synthesis text");
(*request.mutable_params())["reference_text"] = "Explicit transcript.";
const auto explicit_task = build_tts_request(request, clip(24000, 1));
check(option_or(explicit_task.options, "reference_text", "") ==
"Explicit transcript.",
"tts reference: explicit canonical parameter wins over alias");
(*request.mutable_params())["reference_text"] = "";
const auto empty_task = build_tts_request(request, clip(24000, 1));
check(has_key(empty_task.options, "reference_text") &&
empty_task.options.at("reference_text").empty(),
"tts reference: explicit empty canonical parameter is preserved");
request.mutable_params()->clear();
const auto missing_task = build_tts_request(request, clip(24000, 1));
check(!has_key(missing_task.options, "reference_text"),
"tts reference: no transcript is invented when none was supplied");
}
static void test_sound_generation_minimal() {
backend::SoundGenerationRequest request;
request.set_text("a distant thunderstorm");
@@ -562,6 +596,7 @@ int main() {
test_tts_empty_language_is_not_a_language();
test_tts_clip_and_instructions();
test_tts_language_and_params();
test_tts_reference_transcript();
test_sound_generation_minimal();
test_sound_generation_full();
test_transform_text_absent();
+6
View File
@@ -217,6 +217,12 @@ voice conversion from the same weights.
## Family notes
- **Fish Audio voice cloning**: save a reference clip with its transcript in the
Voice Library, then select **Use in Text to Speech**. The backend accepts
`params.ref_text` as an alias for `params.reference_text` in both ordinary and
streaming speech requests. If you supply both parameters, `reference_text`
takes precedence. For direct requests with a reference file in `voice`, supply
its transcript in one of these parameters.
- **Supertonic**: use the `orig` GGUF package, whose weights are f32. The f16 package was
observed to reach `ggml_concat` with mismatched operand types and take the backend
process down with `SIGABRT` on the first request, rather than returning an error.