From 67db320ee673510cea5ac9e3eafe95a35a33ac65 Mon Sep 17 00:00:00 2001 From: localai-org-maint-bot <306269227+localai-org-maint-bot@users.noreply.github.com> Date: Sat, 12 Sep 2026 16:06:11 +0000 Subject: [PATCH] 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 --- backend/cpp/audio-cpp/generation_request.cpp | 7 ++++ .../audio-cpp/generation_request_ctest.cpp | 35 +++++++++++++++++++ docs/content/features/audio-cpp.md | 6 ++++ 3 files changed, 48 insertions(+) diff --git a/backend/cpp/audio-cpp/generation_request.cpp b/backend/cpp/audio-cpp/generation_request.cpp index 805bf35d8..c0aa2b95b 100644 --- a/backend/cpp/audio-cpp/generation_request.cpp +++ b/backend/cpp/audio-cpp/generation_request.cpp @@ -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`. diff --git a/backend/cpp/audio-cpp/generation_request_ctest.cpp b/backend/cpp/audio-cpp/generation_request_ctest.cpp index e9b5d5106..a2fcce0e3 100644 --- a/backend/cpp/audio-cpp/generation_request_ctest.cpp +++ b/backend/cpp/audio-cpp/generation_request_ctest.cpp @@ -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(); diff --git a/docs/content/features/audio-cpp.md b/docs/content/features/audio-cpp.md index f3a7ce4be..f1adca749 100644 --- a/docs/content/features/audio-cpp.md +++ b/docs/content/features/audio-cpp.md @@ -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.