From 22c3142a7940ad536de3122da3d33ecd2e65500b Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Sun, 31 May 2026 21:13:44 +0200 Subject: [PATCH] refactor(cli): move Cloud TTS under `speaker tts-cloud`, use global --host Replaces the awkward top-level `tts speak --speaker-host` with a `speaker tts-cloud` subcommand that sits alongside the existing `speaker tts` and uses the global --host flag (--device still works as an alternative). The two are now clearly related: `speaker tts` sends a Google Translate URL straight to the speaker, while `speaker tts-cloud` routes through the service for server-side synthesis (Cloud TTS) and playback. --speaker-host is gone. Co-Authored-By: Claude Opus 4.8 (1M context) --- cmd/soundtouch-cli/cmd_tts.go | 57 +++++++------------ cmd/soundtouch-cli/main.go | 2 +- .../docs/reference/SPEAKER-ENDPOINT.md | 15 +++-- 3 files changed, 32 insertions(+), 42 deletions(-) diff --git a/cmd/soundtouch-cli/cmd_tts.go b/cmd/soundtouch-cli/cmd_tts.go index e151520..0b89c14 100644 --- a/cmd/soundtouch-cli/cmd_tts.go +++ b/cmd/soundtouch-cli/cmd_tts.go @@ -11,30 +11,21 @@ import ( "github.com/urfave/cli/v2" ) -// ttsCommand assembles the `soundtouch-cli tts …` command group. Unlike -// `speaker tts` (which talks to a speaker directly using the Google Translate -// URL), these subcommands call the AfterTouch service, which synthesizes audio -// with the configured provider (e.g. Google Cloud TTS) and plays it on a -// speaker. They require --service-url. -func ttsCommand() *cli.Command { +// ttsCloudCmd is the `speaker tts-cloud` subcommand. Unlike `speaker tts` +// (which sends a Google Translate URL straight to the speaker), this routes +// through the AfterTouch service, which synthesizes the audio with the +// configured provider (e.g. Google Cloud TTS), hosts it, and plays it on the +// speaker. It therefore needs --service-url. Target the speaker with the global +// --host, or with --device (resolved to an IP by the service). +func ttsCloudCmd() *cli.Command { return &cli.Command{ - Name: "tts", - Usage: "Text-to-speech via the AfterTouch service (Google Cloud TTS or Google Translate)", - Description: "Sends text to the AfterTouch service, which synthesizes audio (or builds a\n" + - "direct URL) and plays it on a speaker via the /speaker endpoint.\n\n" + - "This differs from 'speaker tts', which talks to a speaker directly using\n" + - "the Google Translate URL. Use 'tts speak' for the service's configured\n" + - "provider (e.g. Google Cloud TTS).", - Subcommands: []*cli.Command{ - ttsSpeakCmd(), - }, - } -} - -func ttsSpeakCmd() *cli.Command { - return &cli.Command{ - Name: "speak", - Usage: "Synthesize text and play it on a speaker via the AfterTouch service", + Name: "tts-cloud", + Usage: "Speak text via the AfterTouch service (Google Cloud TTS), synthesized server-side", + Description: "Routes through the AfterTouch service (requires --service-url), which\n" + + "synthesizes the audio with the configured provider, hosts it, and plays it\n" + + "on the speaker. Target the speaker with the global --host or with --device.\n\n" + + "Contrast with 'speaker tts', which sends a Google Translate URL directly to\n" + + "the speaker without involving the service.", Flags: append(CloudCommonFlags, &cli.StringFlag{ Name: "text", @@ -45,11 +36,7 @@ func ttsSpeakCmd() *cli.Command { &cli.StringFlag{ Name: "device", Aliases: []string{"d"}, - Usage: "Target device ID (the service resolves it to an IP)", - }, - &cli.StringFlag{ - Name: "speaker-host", - Usage: "Target speaker IP/hostname (alternative to --device)", + Usage: "Target device ID (the service resolves it to an IP); alternative to --host", }, &cli.StringFlag{ Name: "language", @@ -71,17 +58,17 @@ func ttsSpeakCmd() *cli.Command { Value: "radio", }, ), - Action: ttsSpeak, + Action: ttsCloud, } } -func ttsSpeak(c *cli.Context) error { +func ttsCloud(c *cli.Context) error { serviceURL := strings.TrimRight(c.String("service-url"), "/") device := c.String("device") - speakerHost := c.String("speaker-host") + host := c.String("host") // global flag - if device == "" && speakerHost == "" { - return fmt.Errorf("one of --device or --speaker-host is required") + if device == "" && host == "" { + return fmt.Errorf("one of --host or --device is required") } payload := map[string]interface{}{"text": c.String("text")} @@ -89,8 +76,8 @@ func ttsSpeak(c *cli.Context) error { payload["deviceId"] = device } - if speakerHost != "" { - payload["host"] = speakerHost + if host != "" { + payload["host"] = host } if l := c.String("language"); l != "" { diff --git a/cmd/soundtouch-cli/main.go b/cmd/soundtouch-cli/main.go index 318be4b..8c02b4f 100644 --- a/cmd/soundtouch-cli/main.go +++ b/cmd/soundtouch-cli/main.go @@ -1941,6 +1941,7 @@ func main() { Action: playNotificationBeep, Before: RequireHost, }, + ttsCloudCmd(), { Name: "help", Usage: "Show detailed help about speaker functionality", @@ -2315,7 +2316,6 @@ func main() { // AfterTouch service management (sources, accounts, devices). // Defined in cmd_cloud.go. app.Commands = append(app.Commands, cloudCommand()) - app.Commands = append(app.Commands, ttsCommand()) // Sort commands alphabetically (including subcommands and flags recursively) sortCommands(app.Commands) diff --git a/docs/content/docs/reference/SPEAKER-ENDPOINT.md b/docs/content/docs/reference/SPEAKER-ENDPOINT.md index 41e2294..0ff5c20 100644 --- a/docs/content/docs/reference/SPEAKER-ENDPOINT.md +++ b/docs/content/docs/reference/SPEAKER-ENDPOINT.md @@ -317,16 +317,19 @@ curl -X POST http://soundtouch.local:8000/setup/tts/speak \ -d '{"host":"192.0.2.100","text":"Dinner is ready"}' ``` -`deviceId` may be used instead of `host` (the service resolves it to an IP from -its datastore). Optional fields: `language`, `voice`, `volume`. +`deviceId` may be used instead of `host` (the service resolves it to an IP from its datastore). Optional fields: `language`, `voice`, `volume`, and `method` +(`radio`, the default LOCAL_INTERNET_RADIO path, or `speaker`, the /speaker +notification path that ducks and resumes playback). -CLI (calls the service, not the speaker directly): +CLI (`speaker tts-cloud` routes through the service for Cloud TTS, in contrast +to `speaker tts` which sends a Google Translate URL straight to the speaker): ```bash -soundtouch-cli tts speak \ +soundtouch-cli speaker tts-cloud \ --service-url http://soundtouch.local:8000 \ - --speaker-host 192.0.2.100 \ - --text "Dinner is ready" + --host 192.0.2.100 \ + --text "Dinner is ready" \ + --method speaker ``` Web UI: the TTS source view (and the Play URL view) include a "Say something…"