Files
Tobias GesellchenandClaude Opus 4.8 c852d07da1 feat(tts): add Google Cloud Text-to-Speech via a pluggable provider
Adds text-to-speech that synthesizes higher-quality audio (Google Cloud
TTS) and plays it on a speaker via the /speaker endpoint. Because Cloud
TTS returns audio bytes (not a fetchable URL), the service caches the
clip and hosts it at GET /media/tts/{id}, mirroring the "ding" endpoint,
then points the speaker at that local URL.

The design is a pluggable Provider interface (pkg/service/tts) wrapping
two modes:
- translate: hands the speaker the (undocumented) Google Translate URL
  directly (no credentials), reusing models.BuildTranslateTTSURL.
- google-cloud: REST API key auth (no SDK/gRPC), bytes cached locally.

Surfaces:
- service: POST /mgmt/tts/speak, GET /mgmt/tts/config, GET /media/tts/{id};
  configured via TTS_PROVIDER / TTS_GOOGLE_API_KEY / TTS_LANGUAGE /
  TTS_VOICE / TTS_APP_KEY / TTS_VOLUME.
- CLI: `soundtouch-cli tts speak` (calls the service with mgmt Basic Auth).
- web: a "TTS" source view (like Play URL / TuneIn), proxied to the
  service via /api/device-speak/{id}.

The /speaker app_key requirement and model limitations still apply; see
docs/content/docs/reference/SPEAKER-ENDPOINT.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 22:35:31 +02:00

50 lines
1.9 KiB
Go

// Package tts turns text into speaker-playable audio for the local service.
//
// SoundTouch speakers play a notification by fetching a URL themselves (via the
// /speaker endpoint). Two delivery shapes exist behind a single Provider
// interface:
//
// - Direct-URL providers (e.g. Google Translate) hand the speaker a URL it
// fetches directly. No local hosting needed; Result.DirectURL is set.
// - Synthesizing providers (e.g. Google Cloud TTS) return audio *bytes*. The
// Service caches them and serves them from a local /media/tts/{id} URL that
// the speaker can reach. Result.Audio is set.
//
// The Service (service.go) hides this distinction: callers ask it to Prepare a
// Request and get back a single playable URL.
package tts
import "context"
// Audio format identifiers for a Request.
const (
FormatMP3 = "mp3"
FormatWAV = "wav"
)
// Request describes one synthesis. Language and Voice are provider-specific:
// the Translate provider expects a short code like "EN"; Google Cloud expects a
// BCP-47 tag like "en-US" plus an optional voice name. The active provider is
// fixed per deployment, so the configured defaults are matched to it.
type Request struct {
Text string
Language string
Voice string
Format string // FormatMP3 (default) or FormatWAV
}
// Result is what a Provider returns. Exactly one of DirectURL or Audio is set.
type Result struct {
Audio []byte // synthesized bytes; nil for direct-URL providers
ContentType string // e.g. "audio/mpeg"; set alongside Audio
DirectURL string // speaker-fetchable URL; set instead of Audio
}
// Provider converts text to either a direct URL or audio bytes.
type Provider interface {
// Name returns the provider identifier (e.g. "translate", "google-cloud").
Name() string
// Synthesize converts req into a Result.
Synthesize(ctx context.Context, req Request) (Result, error)
}