TTS in der Console

Befehl aus MacOS nachgebaut: say

TTS in der Console
Photo by Bogomil Mihaylov / Unsplash

Sprachausgabe unter Linux war bisher nicht wirklich ein Hörvergnügen. Wie ich meinen Tools trotzdem eine natürliche Stimme verliehen habe, beschreibe ich im Folgenden.

Lokale Stimmen

Wenn man nach Sprachausgabe unter Linux sucht, wird man oft auf espeak oder festival verwiesen. Doch diese Stimmen klingen so robotisch, dass sie in der Musik-Scene  noch am ehesten als Effekt zu gebrauchen sind.

Es ist natürlich möglich mit lokaler KI auf das Problem loszugehen. Das stellte für mich jedoch keine Lösung dar, da ich die Sprachausgabe für eine Telefonanlage auf einem RaspberryPi einsetzen wollte. Diese Rechenleistung stand also nicht zur Verfügung. Wie kommt man also zu einer angenehmen Sprache, die sich zum Beipiel für eine dynamisch ändernde Menüführung oder das Vorlesen von Zuständen der Hausautomation eignet?

Stimme aus dem Netz

Wenn man im Internet nach freien KI Stimmen sucht (z.B. mit "free ai tts"), findet man einige Anbieter, auf deren Webseite man Text eingeben kann. Diesen Text kann man sich im Browser anhören oder auch als MP3-Datei herunterladen. Ich habe mich damals aus all den Suchergebnissen für ttsmp3.com entschieden.

Free Text-To-Speech for 28+ languages & MP3 Download | ttsMP3.com
Easily convert text to natural US English voice and 50+ languages/accents for free. Listen online or download as MP3.

Hier kann man seinen Text in ein Formular eingeben und als MP3-Datei herunterladen.

Doch all das brauche ich in der Console. Daher müssen alle Schritte automatisch stattfinden. Wie kann das also nachgebildet werden?

Analyse des Netzwerkverkehrs

Ich rufe die Webseite zunächst mit dem Browser auf und öffne die Werkzeuge für Webentwickler:

In dem neu hinzugekommenen Bereich wähle ich Netzwerkanalyse aus. Zur Probe lade ich ein MP3-File herunter und klicke anschließend auf das erste Paket, das mir angezeigt wird. Es öffnet sich ein neuer Bereich, in dem ich Anfrage auswähle:

Hier kann ich bereits sehen, was mein Browser gesendet hat. Will ich das an der Console nachvollziehen, genügt ein Rechtsklick auf dieses Paket:

Ich wähle aus, dass ich den Request als cURL-Befehl in die Zwischenablage kopieren möchte. Das sieht dann so aus:

curl 'https://ttsmp3.com/makemp3_new.php' \
--compressed \
-X POST \  
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20140101 Firefox/132.0' \  
-H 'Accept: */*' \  
-H 'Accept-Language: de,en-US;q=0.7,en;q=0.3' \  
-H 'Accept-Encoding: gzip, deflate, br' \  
-H 'Referer: https://ttsmp3.com/' \  
-H 'content-type: application/x-www-form-urlencoded' \  
-H 'Origin: https://ttsmp3.com' \  
-H 'Connection: keep-alive' \  
-H 'Sec-Fetch-Dest: empty' \  
-H 'Sec-Fetch-Mode: cors' \  
-H 'Sec-Fetch-Site: same-origin' \
--data-raw 'msg=XXXXXXXXXXXXX&lang=Hans&source=ttsmp3'

In der letzten Zeile des Kommandos finde ich meine Eingabe auf der Webseite wieder. Das werde ich später durch eine Variable austauschen.

Doch zuerst starte ich das Kommando in der Konsole und schaue mir die Ausgabe an:

{
  "Error": 0,
  "Speaker": "Hans",
  "Cached": 0,
  "Text": "XXXXXXXXXXXXX",
  "tasktype": "direct",
  "success": 1,
  "URL": "https://ttsmp3.com/dlmp3.php?mp3=f93ba016b5e1aa4c07979fcdc89aab31.mp3",
  "MP3": "f93ba016b5e1aa4c07979fcdc89aab31.mp3"
}

Offensichtlich wird meinem Browser mitgeteilt, unter welcher URL das MP3-File heruntergeladen werden kann. Und das führt zu den zwei weiteren Paketen, die im Analysebereich des Browsers zu erkennen sind.

Im dritten Paket kann ich sehen, dass der Medientyp mp3 ist. Hier kann der Browser dann das File herunterladen. Ich kopiere mir daher, wie beim ersten Paket, den cURL-Befehl in die Zwischenablage. Und das sieht dann so aus:

curl 'https://ttsmp3.com/dlmp3.php?mp3=f93ba016b5e1aa4c07979fcdc89aab31.mp3&location=direct' \  
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/132.0' \  
-H 'Accept: */*' \  
-H 'Accept-Language: de,en-US;q=0.7,en;q=0.3' \  
-H 'Accept-Encoding: gzip, deflate, br' \  
-H 'Referer: https://ttsmp3.com/' \  
-H 'Connection: keep-alive' \  
-H 'Sec-Fetch-Dest: empty' \  
-H 'Sec-Fetch-Mode: cors' \  
-H 'Sec-Fetch-Site: same-origin' \  
-H 'TE: trailers'

Führe ich diesen Befehl nun aus, dann erhalte ich eine Fehlermeldung, die besagt, dass die erhaltenen Daten keinen reinen Text enthalten. Aber das konnte ich ja bereits im Browser sehen: Das Ausgabetyp ist mp3. Daher füge ich --output /tmp/tts.mp3 an und speichere die Datei ab. Zur Kontrolle höre ich mir den Inhalt kurz an:

play -q /tmp/tts.mp3

Und tatsächlich: Erfolg!

Aufnahme in die .bashrc

Da ich nun alle Arbeitsschritte durchgespielt habe, kann ich diese in einer function() zusammenfassen:

say(){
WHATTOSAY=$(echo $* | sed -e 's/"//g;s/ /%20/g')

URL=$(curl 'https://ttsmp3.com/makemp3_new.php' \
  --compressed \
  -X POST \
  -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0' \
  -H 'Accept: */*' \
  -H 'Accept-Language: de,en-US;q=0.7,en;q=0.3' \
  -H 'Accept-Encoding: gzip, deflate, br' \
  -H 'Referer: https://ttsmp3.com/' \
  -H 'content-type: application/x-www-form-urlencoded' \
  -H 'Origin: https://ttsmp3.com' \
  -H 'Connection: keep-alive' \
  -H 'Sec-Fetch-Dest: empty' \
  -H 'Sec-Fetch-Mode: cors' \
  -H 'Sec-Fetch-Site: same-origin' \
  -H 'TE: trailers' \
  --data-raw "msg=${WHATTOSAY}&lang=Hans&source=ttsmp3" 2>/dev/null |
    jq .URL |
    sed -e 's/"//g')
    
curl "$URL" \
  -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0' \
  -H 'Accept: audio/webm,audio/ogg,audio/wav,audio/*;q=0.9,application/ogg;q=0.7,video/*;q=0.6,*/*;q=0.5' \
  -H 'Accept-Language: de,en-US;q=0.7,en;q=0.3' \
  -H 'Range: bytes=0-' \
  -H 'Connection: keep-alive' \
  -H 'Referer: https://ttsmp3.com/' \
  -H 'Sec-Fetch-Dest: audio' \
  -H 'Sec-Fetch-Mode: no-cors' \
  -H 'Sec-Fetch-Site: same-origin' \
  -H 'Accept-Encoding: identity' \
  -H 'TE: trailers' \
  -o /tmp/tts.mp3 2> /dev/null
  
play -q /tmp/tts.mp3
}

Pastet man den obigen Text in die Console, kann man den Befehl say direkt ausprobieren:

michael@homelab:~ $ say Wenn man Kontakt zum Internet hat, läuft die Spracherzeugung ziemlich geschmeidig.

Und das führt dann zu dieser Sprachausgabe:

audio-thumbnail
Sprachausgabe
0:00
/0:04

Da das bei mir gut funktioniert hat, füge ich diesen Textabschnitt meiner .bashrc hinzu und habe damit mein Arsenal um den Befehl say  erweitert.

Fazit

Nicht nur auf der Telefonanlage hat sich dieses Vorgehen gut bewährt. Ich nutze dieses Verfahren auch unter Tasker auf meinem Android-SmartPhone. Auch wenn ich mal ein MP3-File brauche, mit einem statischen Inhalt, der sich nie ändern wird. Dann kopiere ich /tmp/tts.mp3 einfach dahin, wo ich es brauche.

Vor allem bei Systemen mit sehr geringer Leistungsaufnahme läßt sich dieses Script hervorragend einsetzen.