mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-30 18:09:05 -04:00
* feat(backend): add moss-tts-cpp text-to-speech backend Add a Go + purego backend wrapping the moss-tts.cpp ggml port of the OpenMOSS MOSS-TTS-Local v1.5 text-to-speech model (GPT-J local transformer decoded through MOSS-Audio-Tokenizer-v2), producing 48 kHz stereo audio with optional reference-audio voice cloning. Mirrors the qwen3-tts-cpp backend: dlopen the static-ggml shared library, bind the moss-tts.cpp C-API via purego, and serve the gRPC TTS method. A thin C shim holds the pipeline handle and copies engine PCM into a Go-freeable buffer. Wires the CI registration: backend-matrix.yml (CPU, CUDA 12/13, Intel SYCL f16/f32, Vulkan, ROCm, NVIDIA L4T, plus Darwin metal), backend/index.yaml metas and image entries pointing at mudler/MOSS-TTS-Local-Transformer-v1.5-GGUF, the root Makefile build targets, and the changed-backends.js path mapping. Assisted-by: Claude:claude-opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * docs: list the moss-tts-cpp backend among the LocalAI-maintained engines Add moss-tts.cpp to the README "Backends built by us" table, the Text-to-Speech compatibility table, and the reference-audio voice-cloning backend list, so the new backend is documented alongside its peers. Assisted-by: Claude:claude-opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * backend(moss-tts-cpp): pin moss-tts.cpp to the squashed single-commit release moss-tts.cpp history was collapsed to a single commit; repoint MOSSTTS_CPP_VERSION to ee722b8e9205ee9b1b1c398a4e87e4e393e9be41. Assisted-by: Claude:claude-opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * backend(moss-tts-cpp): add the moss-tts-cpp-development gallery meta The gallery had the -development image entries but no matching -development meta anchor (as locate-anything-cpp and depth-anything-cpp have), so the master build was not installable as a gallery backend. Add moss-tts-cpp-development mirroring the production meta with the -development capability image names. Assisted-by: Claude:claude-opus-4-8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
79 lines
1.8 KiB
Go
79 lines
1.8 KiB
Go
package main
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// loadOptions holds the parsed model-level options. MOSS-TTS-Local needs three
|
|
// GGUFs (local transformer, audio codec, text tokenizer); the model path is the
|
|
// local transformer and codec/tokenizer are resolved as siblings or via these
|
|
// options.
|
|
type loadOptions struct {
|
|
codecPath string
|
|
tokenizerPath string
|
|
seed int
|
|
}
|
|
|
|
func splitOption(o string) (key, value string, ok bool) {
|
|
i := strings.Index(o, ":")
|
|
if i < 0 {
|
|
return "", "", false
|
|
}
|
|
return strings.TrimSpace(o[:i]), strings.TrimSpace(o[i+1:]), true
|
|
}
|
|
|
|
// parseOptions reads the backend "key:value" option slice. Unknown keys are
|
|
// ignored. Default seed is -1 (engine random).
|
|
func parseOptions(opts []string) loadOptions {
|
|
o := loadOptions{seed: -1}
|
|
for _, oo := range opts {
|
|
key, value, ok := splitOption(oo)
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch key {
|
|
case "codec", "audio_tokenizer":
|
|
o.codecPath = value
|
|
case "tokenizer", "text_tokenizer":
|
|
o.tokenizerPath = value
|
|
case "seed":
|
|
if n, err := strconv.Atoi(value); err == nil {
|
|
o.seed = n
|
|
}
|
|
}
|
|
}
|
|
return o
|
|
}
|
|
|
|
var refAudioExts = []string{".wav", ".flac", ".mp3", ".ogg", ".m4a"}
|
|
|
|
// resolveVoice interprets the request Voice field. MOSS-TTS-Local has no named
|
|
// speakers, only reference-audio cloning, so a value ending in a known audio
|
|
// extension is treated as a clone-reference path and anything else is ignored
|
|
// (the caller then falls back to the config audio_path).
|
|
func resolveVoice(voice string) (refPath string) {
|
|
v := strings.TrimSpace(voice)
|
|
if v == "" {
|
|
return ""
|
|
}
|
|
lower := strings.ToLower(v)
|
|
for _, ext := range refAudioExts {
|
|
if strings.HasSuffix(lower, ext) {
|
|
return v
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func parseInt(v string, def int) int {
|
|
if v == "" {
|
|
return def
|
|
}
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil {
|
|
return def
|
|
}
|
|
return n
|
|
}
|