mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 22:17:36 +00:00
Baseline groundwork for TTS expansion plan. Introduces a capabilities system that the follow-up plan (phase-01..04) builds on. Backend: - `audio.ParamSchema` / `ProviderCapabilities` types + per-provider capabilities.go for edge, elevenlabs, minimax, openai, gemini. - Gemini TTS provider (client, models, voices, wav encoder, multi-speaker via SpeakerVoice, audio-tag aware prompts). - TTSOptions gains `Params map[string]any` (read-only) + `Speakers`. - `VoiceListProvider` interface decouples HTTP voice handler from provider-specific impls. - `nested_keys.go` resolves dot-separated param paths for nested provider bodies (voice_settings.stability etc.). - Characterization + defaults-invariant tests per provider lock nil-params byte-equivalence before new params land. - `/v1/tts/capabilities` HTTP endpoint + integration coverage. - Dual-read tests (PG + SQLite) for tts_config. Frontend (web + desktop): - `DynamicParamForm` with depends-on evaluation, split into fields/logic modules. Slider primitive added. - `AudioTagPicker` + `MultiSpeakerEditor` for Gemini. - `voice-picker` refactored toward portal UX; combobox tightened. - tts-capabilities API client + typed hooks. - i18n catalogs (en/vi/zh) expanded; parity tests guard key drift. - TTS page reorganised (voice-model-section removed; playground + credentials + provider-setup split cleanly). Docs: codebase-summary, project-changelog, tts-provider-capabilities.
69 lines
2.1 KiB
Go
69 lines
2.1 KiB
Go
package gemini
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"testing"
|
|
)
|
|
|
|
// TestWrap_RIFFHeader_Bytes verifies every field of the 44-byte RIFF/WAV header
|
|
// produced by Wrap() for a 1024-byte PCM payload.
|
|
func TestWrap_RIFFHeader_Bytes(t *testing.T) {
|
|
pcm := make([]byte, 1024)
|
|
wav := Wrap(pcm)
|
|
|
|
if !bytes.Equal(wav[0:4], []byte("RIFF")) {
|
|
t.Errorf("bytes[0:4] = %q, want RIFF", wav[0:4])
|
|
}
|
|
if got := binary.LittleEndian.Uint32(wav[4:8]); got != 36+1024 {
|
|
t.Errorf("chunk size = %d, want %d", got, 36+1024)
|
|
}
|
|
if !bytes.Equal(wav[8:12], []byte("WAVE")) {
|
|
t.Errorf("bytes[8:12] = %q, want WAVE", wav[8:12])
|
|
}
|
|
if !bytes.Equal(wav[12:16], []byte("fmt ")) {
|
|
t.Errorf("bytes[12:16] = %q, want 'fmt '", wav[12:16])
|
|
}
|
|
if got := binary.LittleEndian.Uint32(wav[16:20]); got != 16 {
|
|
t.Errorf("fmt chunk size = %d, want 16", got)
|
|
}
|
|
if got := binary.LittleEndian.Uint16(wav[20:22]); got != 1 {
|
|
t.Errorf("audio format = %d, want 1 (PCM)", got)
|
|
}
|
|
if got := binary.LittleEndian.Uint16(wav[22:24]); got != 1 {
|
|
t.Errorf("channels = %d, want 1 (mono)", got)
|
|
}
|
|
if got := binary.LittleEndian.Uint32(wav[24:28]); got != 24000 {
|
|
t.Errorf("sample rate = %d, want 24000", got)
|
|
}
|
|
if got := binary.LittleEndian.Uint32(wav[28:32]); got != 48000 {
|
|
t.Errorf("byte rate = %d, want 48000", got)
|
|
}
|
|
if got := binary.LittleEndian.Uint16(wav[32:34]); got != 2 {
|
|
t.Errorf("block align = %d, want 2", got)
|
|
}
|
|
if got := binary.LittleEndian.Uint16(wav[34:36]); got != 16 {
|
|
t.Errorf("bits per sample = %d, want 16", got)
|
|
}
|
|
if !bytes.Equal(wav[36:40], []byte("data")) {
|
|
t.Errorf("bytes[36:40] = %q, want 'data'", wav[36:40])
|
|
}
|
|
if got := binary.LittleEndian.Uint32(wav[40:44]); got != 1024 {
|
|
t.Errorf("data chunk size = %d, want 1024", got)
|
|
}
|
|
if !bytes.Equal(wav[44:], pcm) {
|
|
t.Error("PCM payload mismatch after WAV header")
|
|
}
|
|
}
|
|
|
|
// TestWrap_EmptyPCM verifies a 44-byte header + zero data payload.
|
|
func TestWrap_EmptyPCM(t *testing.T) {
|
|
wav := Wrap([]byte{})
|
|
if len(wav) != wavHeaderSize {
|
|
t.Errorf("len(wav) = %d, want %d", len(wav), wavHeaderSize)
|
|
}
|
|
if got := binary.LittleEndian.Uint32(wav[40:44]); got != 0 {
|
|
t.Errorf("data chunk size = %d, want 0", got)
|
|
}
|
|
}
|