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

34 lines
1004 B
Go

package tts
import (
"context"
"github.com/gesellix/bose-soundtouch/pkg/models"
)
// ProviderTranslate is the identifier for the Google Translate provider.
const ProviderTranslate = "translate"
// TranslateProvider hands the speaker an undocumented Google Translate TTS URL
// to fetch directly. No credentials, no local hosting; quality and length are
// limited and the endpoint can change without notice.
type TranslateProvider struct{}
// NewTranslateProvider returns a Translate provider.
func NewTranslateProvider() *TranslateProvider {
return &TranslateProvider{}
}
// Name implements Provider.
func (p *TranslateProvider) Name() string { return ProviderTranslate }
// Synthesize returns a Result whose DirectURL points at the Translate endpoint.
func (p *TranslateProvider) Synthesize(_ context.Context, req Request) (Result, error) {
language := req.Language
if language == "" {
language = "EN"
}
return Result{DirectURL: models.BuildTranslateTTSURL(req.Text, language)}, nil
}