mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-10 12:46:13 +00:00
Point the CLI's service calls at the new canonical paths: tts speak (/api/setup/tts/speak) and the CA bundle fetch (/api/setup/ca.crt), plus the user-facing message and doc comment. No behavior change; legacy paths still work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
128 lines
3.6 KiB
Go
128 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
// 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-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",
|
|
Aliases: []string{"t"},
|
|
Usage: "Text to speak",
|
|
Required: true,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "device",
|
|
Aliases: []string{"d"},
|
|
Usage: "Target device ID (the service resolves it to an IP); alternative to --host",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "language",
|
|
Aliases: []string{"l"},
|
|
Usage: "Language code (provider-specific; defaults to the service setting)",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "voice",
|
|
Usage: "Voice name (Google Cloud TTS; ignored by the translate provider)",
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "volume",
|
|
Aliases: []string{"v"},
|
|
Usage: "Playback volume (0-100, 0 = service default; only honoured by --method speaker)",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "method",
|
|
Usage: "Playback method: 'speaker' (/speaker notification, ducks+resumes, supports volume) or 'radio' (LOCAL_INTERNET_RADIO, no app_key, replaces source)",
|
|
Value: "speaker",
|
|
},
|
|
),
|
|
Action: ttsCloud,
|
|
}
|
|
}
|
|
|
|
func ttsCloud(c *cli.Context) error {
|
|
serviceURL := strings.TrimRight(c.String("service-url"), "/")
|
|
device := c.String("device")
|
|
host := c.String("host") // global flag
|
|
|
|
if device == "" && host == "" {
|
|
return fmt.Errorf("one of --host or --device is required")
|
|
}
|
|
|
|
payload := map[string]interface{}{"text": c.String("text")}
|
|
if device != "" {
|
|
payload["deviceId"] = device
|
|
}
|
|
|
|
if host != "" {
|
|
payload["host"] = host
|
|
}
|
|
|
|
if l := c.String("language"); l != "" {
|
|
payload["language"] = l
|
|
}
|
|
|
|
if v := c.String("voice"); v != "" {
|
|
payload["voice"] = v
|
|
}
|
|
|
|
if c.IsSet("volume") {
|
|
payload["volume"] = c.Int("volume")
|
|
}
|
|
|
|
if m := c.String("method"); m != "" {
|
|
payload["method"] = m
|
|
}
|
|
|
|
body, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodPost, serviceURL+"/api/setup/tts/speak", bytes.NewReader(body))
|
|
if err != nil {
|
|
return fmt.Errorf("build request: %w", err)
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("request failed: %w", err)
|
|
}
|
|
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<12))
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("service returned %d: %s", resp.StatusCode, strings.TrimSpace(string(respBody)))
|
|
}
|
|
|
|
PrintSuccess(fmt.Sprintf("Spoke %q", c.String("text")))
|
|
|
|
return nil
|
|
}
|