# TTS (https://docs.pixl.lennox.zip/sdk/tts)

> Texte vorlesen lassen und im Overlay abspielen

pixl kann Texte von einer Stimme vorlesen lassen. Das Audio erzeugt pixl, abgespielt wird es in deinen Overlays. Dafür reichen ein paar Zeilen mit dem SDK.

Was vorgelesen wird, kommt aus zwei Richtungen:

- **Dein eigener Code** mit `pixl.tts.speak()`, z.B. für Kanalpunkte oder eigene Alerts
- **Das Dashboard**, über „An Overlays senden“ auf der TTS-Seite

Jedes Mal schickt pixl ein `tts.ready` an alle verbundenen Overlays. Ein Overlay mit `pixl.tts.player()` spielt es dann ab.

## Abspielen im Overlay [#abspielen-im-overlay]

Der Schlüssel braucht das Recht **TTS abspielen** (`tts:read`).

```html
<script src="https://pixl.lennox.zip/sdk/pixl.js"></script>
<script>
  const pixl = new Pixl({ key: "pixl_DEIN_SCHLÜSSEL" });
  pixl.tts.player({ volume: 0.8 });
</script>
```

Das ist schon alles. Der Player spielt alles der Reihe nach ab und hält sich an „Überspringen“ und „Alles stoppen“ aus dem Dashboard. In OBS und in StreamElements Custom Widgets darf er sofort abspielen. In einem normalen Browserfenster musst du einmal auf die Seite klicken, sonst blockiert der Browser den Ton.

### Optionen [#optionen]

| Option | Standard | Bedeutung |
|---|---|---|
| `volume` | `1` | Lautstärke von 0 bis 1 |
| `ignoreReplays` | `false` | Erneut gesendete Einträge („Nochmal senden“ im Verlauf) nicht abspielen |
| `gapMs` | `500` | Pause zwischen zwei Einträgen |
| `maxQueue` | `20` | So viele dürfen warten, ältere fallen raus |
| `onStart(item)` | | Läuft, bevor ein Eintrag startet, z.B. für Untertitel |
| `onEnd(item, { skipped })` | | Läuft, wenn ein Eintrag fertig oder übersprungen ist |
| `onWord(word, item)` | | Läuft bei jedem Wort, sobald es gesprochen wird. Siehe [Wort für Wort](#wort-für-wort) |

`item` hat `id`, `text` (so wie vorgelesen, bei sehr langen Texten gekürzt, dann ist `textTruncated` gesetzt), `voice`, `source` (`api` oder `dashboard`), `replay` und mit `onWord` die Zeitmarken in `marks`.

Der Player hat außerdem `skip()`, `clear()`, `setVolume(v)`, `pending` (wartende Einträge) und `destroy()`.

```js
const subtitle = document.querySelector("#subtitle");
pixl.tts.player({
  onStart: (item) => (subtitle.textContent = item.text),
  onEnd: () => (subtitle.textContent = ""),
});
```

### Wort für Wort [#wort-für-wort]

Mit `onWord` läuft der Text synchron zum Audio mit, z.B. für Untertitel im Karaoke-Stil. `word` hat `word` (das Wort), `index` (Position im Satz), `start` und `end` (Millisekunden ab Start des Audios). Die Zeitmarken aller Wörter stehen schon in `onStart` als `item.marks` bereit, so kannst du den ganzen Satz vorab aufbauen und dann Wort für Wort hervorheben:

```js
const subtitle = document.querySelector("#subtitle");
pixl.tts.player({
  onStart: (item) => {
    subtitle.innerHTML = (item.marks ?? []).map((m) => `<span>${m.word}</span>`).join(" ");
  },
  onWord: (word) => subtitle.children[word.index]?.classList.add("spoken"),
  onEnd: () => (subtitle.innerHTML = ""),
});
```

Gut zu wissen:

- Zeitmarken kosten beim Anbieter extra, ungefähr so viel wie das Audio noch einmal. Der Player lädt sie deshalb nur, wenn `onWord` gesetzt ist. Für denselben Text entstehen sie nur einmal.
- Satzzeichen tauchen nicht als eigene Wörter auf, Zahlen mit Einheit können ein Wort sein („5 Euro“). Baue Untertitel deshalb aus `item.marks`, nicht aus `item.text`.
- Gibt es keine Zeitmarken (Fehler, Stimme ohne diese Fähigkeit), wird trotzdem vorgelesen, nur `onWord` bleibt stumm.
- Selbst abrufen geht mit `await pixl.tts.marks(id)`.

## Selbst vorlesen lassen [#selbst-vorlesen-lassen]

Der Schlüssel braucht das Recht **TTS auslösen** (`tts:write`). Jeder Aufruf verbraucht Credits deines Kanals, gib das Recht also nur Schlüsseln, die es wirklich brauchen.

```js
const spoken = await pixl.tts.speak("Danke fürs Einlösen!", { voice: "polly:vicki" });
// { id, text, voice }
```

Ohne `voice` gilt die Standardstimme des Kanals. `speak()` spielt nichts ab, das macht der Player im Overlay.

Mit `filters` legst du fest, was pixl am Text bereinigt (siehe [Was pixl am Text macht](#was-pixl-am-text-macht)). Standardmäßig ist alles an:

```js
pixl.tts.speak(text, { filters: false });                   // Links, Wiederholungen und gesperrte Wörter nicht filtern
pixl.tts.speak(text, { filters: { links: false } });        // nur Links drin lassen
pixl.tts.speak(text, { filters: { blockedWords: false } }); // gesperrte Wörter ignorieren
```

| Filter | Bedeutung |
|---|---|
| `links` | Links werden weggelassen |
| `repeats` | Wiederholungen werden gekürzt |
| `blockedWords` | Text mit gesperrtem Wort wird abgelehnt |

Unsichtbare Zeichen entfernt pixl immer, und der Text wird immer auf die eingestellte Länge gekürzt. Texte aus dem Dashboard filtert pixl immer, die Option gilt nur für deine eigenen Aufrufe. Geht etwas schief, wirft `speak()` einen `PixlApiError` mit `status` und `problem`:

| `problem` | Status | Bedeutung |
|---|---|---|
| `disabled` | `403` | Kommt nicht mehr vor, ausgeschaltetes TTS meldet jetzt `module_disabled`. Prüf beide, wenn dein Widget schon auf `disabled` reagiert |
| `module_disabled` | `403` | Das Modul TTS ist in den Kanal-Einstellungen aus. Gilt für alle TTS-Endpunkte, auch Audio und Stimmen |
| `quota` | `429` | Credits für diesen Monat aufgebraucht |
| `global_limit` | `429` | Der Server hat sein Tageslimit erreicht, morgen geht es wieder |
| `blocked` | `422` | Der Text enthält ein gesperrtes Wort |
| `empty` | `422` | Nach dem Bereinigen ist nichts übrig |
| `unknown_voice` | `422` | Diese Stimme gibt es nicht |
| `not_configured`, `unavailable` | `503` | TTS ist auf dem Server nicht eingerichtet oder gerade nicht erreichbar |

## Stimmen [#stimmen]

Die Standardstimmen gibt es immer:

| ID | Name | Sprache |
|---|---|---|
| `polly:hans` | Hans | Deutsch, die bekannte Spenden-Stimme |
| `polly:marlene` | Marlene | Deutsch |
| `polly:vicki` | Vicki | Deutsch |
| `polly:brian` | Brian | Englisch |

Dazu kommen KI-Stimmen (`tier: "ai"`, IDs wie `fish:3f2c…`), die pixl nach und nach in den Katalog aufnimmt. Sie klingen deutlich natürlicher und kosten mehr Credits pro Zeichen, wie viele steht in `creditsPerChar`. Eigene Stimmen, die im Kanal eingesprochen wurden (`tier: "custom"`), haben dasselbe Format und gibt es nur in diesem Kanal. Die vollständige Liste samt Standardstimme des Kanals liefert `await pixl.tts.voices()`.

Die ID einer Stimme kopierst du in pixl unter TTS → Einstellungen direkt bei der Stimme. Du kannst sie fest in dein Widget schreiben. KI-Stimmen können aber wegfallen, dann antwortet `speak()` mit `unknown_voice`. Lass in dem Fall `voice` weg, dann liest die Standardstimme des Kanals vor.

## Was pixl am Text macht [#was-pixl-am-text-macht]

Bevor etwas vorgelesen wird, bereinigt pixl den Text (bei `speak()` lassen sich Links, Wiederholungen und gesperrte Wörter [abschalten](#selbst-vorlesen-lassen)):

- Links werden weggelassen
- Wiederholungen werden gekürzt: aus „HYPEEEEEE“ wird „HYPEEE“, aus zehnmal demselben Wort dreimal
- Unsichtbare Zeichen fliegen raus
- Der Text wird auf die eingestellte Länge gekürzt
- Enthält er ein gesperrtes Wort, wird er nicht vorgelesen. Erkannt werden auch Schreibweisen wie „b4d“ oder „b.a.d“

## Credits [#credits]

Jeder Kanal hat Credits, 1 Credit ist 1 Zeichen mit einer Standardstimme. KI-Stimmen kosten mehrere Credits pro Zeichen (`creditsPerChar` in `voices()`). Es gibt zwei Arten: ein **Monatsbudget**, das es jeden Monatsersten neu gibt (Reste verfallen), oder ein **Guthaben**, das nicht abläuft und beim Vorlesen abgebucht wird. `speak()` und Ausprobieren im Dashboard verbrauchen Credits. Derselbe Text mit derselben Stimme kostet nur einmal, solange er im Zwischenspeicher liegt. Wie viele noch übrig sind, siehst du oben auf der TTS-Seite in pixl.

## Ohne SDK [#ohne-sdk]

Die Endpunkte stehen in der [HTTP-API](/sdk/api#tts).
