internal/autogen/audio_test.go
Rama main · réplica de lectura del repositorio original.
package autogen
import (
"strings"
"testing"
)
func TestIsTTSModel(t *testing.T) {
cases := []struct {
arch string
file string
want bool
}{
{"qwen3-tts", "qwen-talker-1.7b-base-Q8_0.gguf", true}, // arch hit
{"qwen3", "qwen-talker-0.6b-base-Q4_K_M.gguf", true}, // bare LM arch, name fallback
{"qwentts-codec", "qwen-tokenizer-12hz-Q8_0.gguf", true},
{"kokoro", "Kokoro_no_espeak_Q8_0.gguf", true}, // TTS.cpp, arch hit
{"", "Kokoro_espeak_f16.gguf", true}, // TTS.cpp, name fallback
{"", "Parler_TTS_Mini_Q5_0.gguf", true},
{"qwen3", "Qwen3.6-35B-A3B-Q4_K_S.gguf", false}, // ordinary chat LM
{"llama", "some-llm.gguf", false},
{"llama", "Diamond-Coder-7B-Q4_K_M.gguf", false}, // "dia" must not misroute
}
for _, c := range cases {
if got := IsTTSModel(Metadata{Architecture: c.arch}, c.file); got != c.want {
t.Errorf("IsTTSModel(%q, %q) = %v, want %v", c.arch, c.file, got, c.want)
}
}
}
func TestEmitTTSModel(t *testing.T) {
s := Settings{TtsServerExe: "tts-server", TtlSec: 600}
row := GgufRow{
FullPath: "/models/tts/qwen-talker-1.7b-base-Q8_0.gguf",
FileName: "qwen-talker-1.7b-base-Q8_0.gguf",
SizeGB: 1.0,
CodecPath: "/models/tts/qwen-tokenizer-12hz-Q8_0.gguf",
CodecSizeGB: 0.5,
}
meta := Metadata{Architecture: "qwen3-tts"}
var b strings.Builder
var emitted []string
emitTTSModel(&b, s, row, &Override{}, "qwen-talker-1.7b-base-q8_0", meta, &emitted)
out := b.String()
for _, want := range []string{
"tts-server",
"--model /models/tts/qwen-talker-1.7b-base-Q8_0.gguf",
"--codec /models/tts/qwen-tokenizer-12hz-Q8_0.gguf",
"--port ${PORT}",
"checkEndpoint: /health",
"out: [audio]",
} {
if !strings.Contains(out, want) {
t.Errorf("emitted YAML missing %q:\n%s", want, out)
}
}
if len(emitted) != 1 {
t.Fatalf("emitted = %v, want 1 entry", emitted)
}
// qwentts registers voices from a clip; the playground's clone UI is gated on
// this capability, not on whether the model reported named speakers.
if !strings.Contains(out, "voiceClone: true") {
t.Errorf("qwentts emit should declare voiceClone:\n%s", out)
}
// qwentts accepts whatever model name the request carries, so the rewrite is
// TTS.cpp-only — emitting it here would just hide the real id from the logs.
if strings.Contains(out, "useModelName") {
t.Errorf("qwentts emit should not rewrite the model name:\n%s", out)
}
// Missing codec surfaces a WARNING and no --codec flag.
row.CodecPath, row.CodecSizeGB = "", 0
var b2 strings.Builder
var em2 []string
emitTTSModel(&b2, s, row, &Override{}, "talker-nocodec", meta, &em2)
if !strings.Contains(b2.String(), "WARNING") || strings.Contains(b2.String(), "--codec") {
t.Errorf("missing-codec emit should warn and omit --codec:\n%s", b2.String())
}
}
// A TTS.cpp model must render TTS.cpp's own flags (--model-path, no codec, no
// voices dir) and pick the ttscpp registry entry even though a qwentts entry is
// present and marked as the class default.
func TestEmitTTSModel_TTSCpp(t *testing.T) {
s := Settings{
TtsServerExe: "C:/qwentts/tts-server.exe",
TtlSec: 600,
Backends: []BackendEntry{
{ID: "qwentts", Kind: "tts", Name: "tts-server", Path: "C:/qwentts/tts-server.exe", Default: true},
{ID: "ttscpp", Kind: "ttscpp", Name: "TTS.cpp", Path: "C:/ttscpp/tts-server.exe"},
},
}
row := GgufRow{
FullPath: "D:/models/tts/Kokoro_no_espeak_Q8_0.gguf",
FileName: "Kokoro_no_espeak_Q8_0.gguf",
SizeGB: 0.1,
}
meta := Metadata{Architecture: "kokoro"}
var b strings.Builder
var emitted []string
emitTTSModel(&b, s, row, &Override{}, "kokoro-q8_0", meta, &emitted)
out := b.String()
for _, want := range []string{
"C:/ttscpp/tts-server.exe",
"--model-path D:/models/tts/Kokoro_no_espeak_Q8_0.gguf",
"--port ${PORT}",
"checkEndpoint: /health",
"out: [audio]",
// TTS.cpp 400s "Invalid Model: kokoro-q8_0" without this rewrite: its model
// map is keyed by gguf file stem, not by our model id.
`useModelName: "Kokoro_no_espeak_Q8_0"`,
} {
if !strings.Contains(out, want) {
t.Errorf("emitted YAML missing %q:\n%s", want, out)
}
}
// Kokoro's speakers are a baked-in pack and TTS.cpp has no clone route, so the
// capability must be absent or the Speech tab offers a button that 404s.
for _, unwanted := range []string{"--codec", "--voices-dir", "WARNING", "C:/qwentts", "voiceClone"} {
if strings.Contains(out, unwanted) {
t.Errorf("TTS.cpp emit should not contain %q:\n%s", unwanted, out)
}
}
// A Qwen talker in the same fleet still resolves to qwentts, even though the
// registry also holds a TTS.cpp entry.
talker := GgufRow{
FullPath: "D:/models/tts/qwen-talker-1.7b-base-Q8_0.gguf",
FileName: "qwen-talker-1.7b-base-Q8_0.gguf",
CodecPath: "D:/models/tts/qwen-tokenizer-12hz-Q8_0.gguf",
}
got := strings.Join(ttsCmdLines(s, talker, &Override{}, Metadata{Architecture: "qwen3-tts"}), " ")
if !strings.Contains(got, "C:/qwentts/tts-server.exe") || !strings.Contains(got, "--codec") {
t.Errorf("talker should keep the qwentts backend and codec flag: %s", got)
}
// An explicit per-model backend override outranks the format preference.
ov := &Override{Backend: "qwentts"}
got = strings.Join(ttsCmdLines(s, row, ov, meta), " ")
if !strings.Contains(got, "C:/qwentts/tts-server.exe") {
t.Errorf("explicit Override.Backend should win: %s", got)
}
}
// With no TTS.cpp entry registered, the emit must warn instead of quietly
// launching the qwentts binary against weights it cannot parse.
func TestEmitTTSModel_TTSCppNoBackendWarns(t *testing.T) {
s := Settings{TtsServerExe: "C:/qwentts/tts-server.exe", TtlSec: 600}
row := GgufRow{FullPath: "D:/models/tts/Kokoro_espeak_f16.gguf", FileName: "Kokoro_espeak_f16.gguf"}
var b strings.Builder
var emitted []string
emitTTSModel(&b, s, row, &Override{}, "kokoro-f16", Metadata{Architecture: "kokoro"}, &emitted)
if !strings.Contains(b.String(), "WARNING") {
t.Errorf("unregistered TTS.cpp backend should warn:\n%s", b.String())
}
}