87 lines
3.2 KiB
Go
87 lines
3.2 KiB
Go
package provider
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"sort"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
// DefaultName ist der Provider, den die Kette nutzt, wenn kein --provider
|
||
|
|
// gepinnt ist. Task 02: osbuiltin; Task 03 ersetzt den Default durch die
|
||
|
|
// Fallback-Kette (ElevenLabs → OpenAI → Edge → osbuiltin).
|
||
|
|
const DefaultName = "osbuiltin"
|
||
|
|
|
||
|
|
// ErrNoByteOutput meldet, dass die Plattform keine über Synthesize
|
||
|
|
// beziehbaren Audio-Bytes liefern kann — konkret Linux mit speech-dispatcher
|
||
|
|
// (spd-say) ohne espeak: spd-say kann Audio nur selbst über den Daemon
|
||
|
|
// abspielen, nicht in einen Prozess schreiben. Aufrufer sollen auf
|
||
|
|
// DirectSpeaker ausweichen (siehe dort).
|
||
|
|
var ErrNoByteOutput = errors.New(
|
||
|
|
"diese Plattform kann keine Audio-Bytes liefern (Linux ohne espeak; spd-say spielt nur direkt)")
|
||
|
|
|
||
|
|
// Provider ist der Vertrag aller TTS-Backends von talk.
|
||
|
|
//
|
||
|
|
// Synthesize liefert dekodierbare Audio-Bytes; das Format ist von der
|
||
|
|
// Implementierung dokumentiert (osbuiltin: RIFF/WAVE mit PCM — von allen
|
||
|
|
// Sinks der Aufgabe abspielbar; Cloud-Provider ab Task 03: mp3).
|
||
|
|
type Provider interface {
|
||
|
|
// Name liefert den eindeutigen Providernamen (CLI --provider, Config).
|
||
|
|
Name() string
|
||
|
|
|
||
|
|
// Available meldet, ob der Provider auf diesem System überhaupt nutzbar
|
||
|
|
// ist (z. B. Binary oder Key vorhanden). Muss billig und ohne Neben-
|
||
|
|
// wirkungen sein; false heißt: Kette soll übersprungen werden.
|
||
|
|
Available(ctx context.Context) bool
|
||
|
|
|
||
|
|
// Synthesize erzeugt Audio-Bytes für text in der Sprache lang.
|
||
|
|
Synthesize(ctx context.Context, text, lang string) ([]byte, error)
|
||
|
|
}
|
||
|
|
|
||
|
|
// DirectSpeaker ist eine dokumentierte optionale Erweiterung über das
|
||
|
|
// Provider-Interface hinaus (kein Bestandteil des Basisvertrags): Provider,
|
||
|
|
// die Text ohne Umweg über Synthesize-Bytes selbst hörbar ausgeben können.
|
||
|
|
// Notwendig auf Linux-Systemen, deren OS-TTS (speech-dispatcher) keine
|
||
|
|
// Audio-Bytes an Prozesse liefern kann — dort spielt osbuiltin.SpeakDirect
|
||
|
|
// über spd-say selbst ab (blockierend bis zum Ende der Ansage).
|
||
|
|
//
|
||
|
|
// Task 03+ muss diese Erweiterung beachten: In der Kette ist SpeakDirect
|
||
|
|
// die letzte Stufe, wenn kein Provider Bytes liefern kann.
|
||
|
|
type DirectSpeaker interface {
|
||
|
|
// SpeakDirect gibt text in der Sprache lang direkt über das OS-TTS aus
|
||
|
|
// und blockiert bis zum Ende der Wiedergabe (oder bis ctx abgebrochen
|
||
|
|
// wird).
|
||
|
|
SpeakDirect(ctx context.Context, text, lang string) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// registry bildet Providernamen auf Konstruktoren; Task 03 ergänzt hier
|
||
|
|
// elevenlabs, openai und edge.
|
||
|
|
var registry = map[string]func() Provider{
|
||
|
|
"osbuiltin": func() Provider { return OSBuiltin{} },
|
||
|
|
}
|
||
|
|
|
||
|
|
// Names liefert die registrierten Providernamen (sortiert).
|
||
|
|
func Names() []string {
|
||
|
|
names := make([]string, 0, len(registry))
|
||
|
|
for name := range registry {
|
||
|
|
names = append(names, name)
|
||
|
|
}
|
||
|
|
sort.Strings(names)
|
||
|
|
return names
|
||
|
|
}
|
||
|
|
|
||
|
|
// New liefert den Provider mit dem angegebenen Namen; "" wählt den Default.
|
||
|
|
// Unbekannte (oder in diesem Stand noch nicht implementierte) Namen sind
|
||
|
|
// ein klarer Fehler.
|
||
|
|
func New(name string) (Provider, error) {
|
||
|
|
if name == "" {
|
||
|
|
name = DefaultName
|
||
|
|
}
|
||
|
|
ctor, ok := registry[name]
|
||
|
|
if !ok {
|
||
|
|
return nil, fmt.Errorf("unbekannter oder noch nicht implementierter Provider %q (in diesem Stand verfügbar: %s)",
|
||
|
|
name, strings.Join(Names(), ", "))
|
||
|
|
}
|
||
|
|
return ctor(), nil
|
||
|
|
}
|