talk is going to be published. Code comments, CLI output, error messages,
daemon log lines, MCP descriptions and results, test names and assertions,
README, spec, skill, build scripts and flake are now English. Only the
spoken announcement texts stay bilingual: the wrap result and the test
sentence are German or English depending on the detected language.
Language fallback is now configurable: `lang = "en" | "de"` (env
TALK_LANG, default en) is used when detection cannot tell (numbers, single
words, ties) and for talk's own announcements. langdetect.Detect takes the
fallback as an argument instead of hard-coding German. `talk setup` asks for
it; `talk doctor` and `talk status` show it.
Vocabulary pinned across packages so the process-boundary tests observe
stable strings: chain log lines ("chain: tier X skipped (not available)",
"falling back to the next tier", "spoken via X"), queue lines ("discarded:
quiet hours ... active", "replaced by toast", "max_age"), CLI notes
("speaking directly", "daemon started at", "no daemon", "nothing to cancel"),
status block with aligned English labels, MCP messages, origin guard 403.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
59 lines
1.7 KiB
Go
59 lines
1.7 KiB
Go
package audio
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// Sink names (part of the public contract; spec: audio.sink = "oto"|"null").
|
|
const (
|
|
NameOto = "oto"
|
|
NameNull = "null"
|
|
)
|
|
|
|
// Sink plays audio bytes. The bytes come from Provider.Synthesize; oto
|
|
// understands RIFF/WAVE (PCM, the delivery format of osbuiltin) and MP3
|
|
// (the delivery format of the cloud providers ElevenLabs/OpenAI/Edge).
|
|
type Sink interface {
|
|
// Play plays the data and blocks until the end or until ctx is
|
|
// cancelled.
|
|
Play(ctx context.Context, data []byte) error
|
|
|
|
// Name returns the sink name (audio.sink configuration).
|
|
Name() string
|
|
}
|
|
|
|
// NewSink returns the sink for the configured name ("oto" or "null"). log
|
|
// is an optional log output: the CLI passes its stderr so the null sink can
|
|
// write its discard line (nil = discard silently); the oto sink needs no
|
|
// log. Unknown names are a clear error.
|
|
func NewSink(name string, log io.Writer) (Sink, error) {
|
|
switch name {
|
|
case NameOto:
|
|
return OtoSink{}, nil
|
|
case NameNull:
|
|
return NullSink{W: log}, nil
|
|
default:
|
|
return nil, fmt.Errorf("unknown audio sink %q (allowed: %q, %q)", name, NameOto, NameNull)
|
|
}
|
|
}
|
|
|
|
// NullSink discards every announcement without sound; for CI, servers and
|
|
// tests. If W is set (NewSink passes the CLI's log output), Play logs one
|
|
// line about it so "runs through silently" stays visible.
|
|
type NullSink struct {
|
|
W io.Writer // optional log output; nil = discard silently
|
|
}
|
|
|
|
// Name implements Sink.
|
|
func (NullSink) Name() string { return NameNull }
|
|
|
|
// Play implements Sink: discards the data.
|
|
func (n NullSink) Play(ctx context.Context, data []byte) error {
|
|
if n.W != nil {
|
|
fmt.Fprintf(n.W, "audio: %d bytes discarded (null sink)\n", len(data))
|
|
}
|
|
return nil
|
|
}
|