Wir basteln uns einen Sprachroboter ("robo")

Wie bringt man seine Ubuntubox dazu, beliebige Texte (auch dynamisch generierte) mit einer Computerstimme vorlesen zu lassen?

Kurzer Hinweis für alle, die nur eine schnelle Lösung suchen, um statische Texte in Sprache umzuwandeln: Die in diesem Artikel beschriebene Lösung habe ich inzwischen als Online-Tool OTAC implementiert. Einfach Text eingeben – zurück kommt die Audio-Datei.

Der hier beschriebene Weg ist dabei nur einer von mehreren möglichen. Damit der Computer sagt, was wir wollen, basteln wir uns ein kleines Skript mit dem Namen “robo”, das auf der Sprachsynthetisierungssoftware “festival” aufbaut. Außerdem benötigen wir eine Möglichkeit, die durch diese Software erzeugten wav-Dateien über die Konsole abzuspielen. Hier benutze ich den Befehl “play” aus dem Paket “sox”. Unter Ubuntu installieren wir die benötigten Pakete mit:

sudo apt-get install festival sox

robo wav ubuntu text2wave sox skript shell script robo open source linux konsole festival bash

“festival” beinhaltet den Befehl “text2wave”, der (nomen est omen!) eine Textdatei in eine wav-Datei umwandeln kann. Beispiel:

text2wave /etc/hostname -o /tmp/hostname.wav

Obiger Befehl liest die Datei /etc/hostname (den Namen des Computers, hier: “ubuntukiste”) ein, wandelt den Text nach wav um und legt die generierte Sounddatei als /tmp/hostname.wav ab. Spielen wir nun die Datei /tmp/hostname.wav mit einem beliebigen Player (hier: play) ab, sagt der Computer: “ubuntukiste”. Für den Einzelgebrauch ist dies mehr als gut genug, doch wir wollen uns den Ablauf in einem Skript namens “robo” möglichst so automatisieren, dass all diese Schritte zusammengefasst werden und “robo” auch skriptgenerierte Texte vorlesen kann:

#!/bin/sh
TXTTMP=$HOME/.tmp.txt           # where to save text
WAVTMP=$HOME/.tmp.wav           # where to save audio
echo $@ > $TXTTMP               # save input as text
text2wave $TXTTMP -o $WAVTMP    # convert to audio
echo `cat $TXTTMP`              # write to console
play -q   $WAVTMP               # play as audio
exit                            # bye bye

[Update] Die jeweils aktuellste Version des Skripts kann hier heruntergeladen werden: [/Update]

Das Skript speichern wir nun als /usr/local/bin/robo ab und machen es ausführbar:

sudo chmod +x /usr/local/bin/robo

Im Prinzip ist unser kleiner Sprachroboter nun fertig und einsetzbar.

Direkte Texteingabe:
Damit “robo” z.B. “hello world” sagt starten wir ihn mit:

robo hello world

Vorhandene Textdatei einlesen:
“robo” soll uns nun die Datei /home/ubufreak/greetings vorlesen, in die wir den Satz “hello ubuntuusers, how are you?” hinterlegt haben:

robo `cat /home/ubufreak/greetings`

Dynamische Texte: Zur Erinnerung: Alles zwischen den seltsam anmutenden Anführungszeichen (`backticks`) wird als Befehlssubstitution ausgeführt, d.h. der zwischen den `backticks` stehende Befehl wird zuerst ausgeführt und dann die Ausgabe dieses Befehls an “robo” als Argument übergeben. Dies können wir uns auch für andere Befehle als “cat” zu Nutze machen. Beispiel:

robo `uname -r`

Mit obigem Befehl wird uns “robo” die aktuelle Kernelversion vorlesen. Ein weiteres Beispiel:

robo "The time ... `date +'%H:%M'`"

Mit diesem Befehl liest “robo” die aktuelle Zeit ein und sagt dann: “The time: 8:37″. Den ersten Teil nimmt “robo” dabei als direkten Text auf, die Zeit selbst über die dynamisch generierte Kommandosubstitution. (In solchen Fällen die komplette Befehlskette mit regulären Anführungszeichen einrahmen). So ein Miniskript können wir z.B. in der Datei /etc/cron.hourly ablegen und ausführbar machen, damit “robo” uns die Zeit jede Stunde ansagt.

Weierführende Infos: Damit sollten die grundlegenden Möglichkeiten von “robo” deutlich geworden sein. Grundsätzlich sollte “robo” auch unter anderen Linuxsystemen funktionieren. “festival” ist standardmäßig mit einer männlichen englischsprachigen Stimme konfiguriert. Eine deutsche Stimme gibt es noch nicht. Längere Textdateien benötigen eine gewisse Zeit zum konvertieren. Mit “robo” könnten wir z.B. auch bei sicherheitsrelevanten Ereignissen zeitnah alarmiert werden, z.B. könnten wir ein kleines Skript schreiben, dass regelmäßig die Logfiles nach bestimmten Ereignissen (z.B. Zugriffsversuch per SSH) grept und dies umgehend an uns verpetzt: “Attention! Secure Shell connection attempt from IP ***.***.***.***”.

Viel Spaß mit “robo”!

Comments

  1. Man sollte aber darauf verzichten, allzu lange Texte vorlesen zu lassen, da die Erstellung der Sounddateien recht lange benötigt. Aber um Status-Skripte mit sinnvolleren Lautmeldungen als dem üblichen Piepsen zu versehen, reicht es allemal aus. (Sonderzeichen sollte man aber vorher entfernen)

    Klasse Skript!

  2. ich wuerde vor dem exit noch die zwei zeilen
    rm $TXTTMP
    rm $WAVTMP
    einfuegen, damit die temporaeren Dateien auch wieder geloescht werden.

    ansonsten sehr nettes skript

  3. Ich habe eine umsetzung für deutsche Sprachausgabe mit mbrola (http://tcts.fpms.ac.be/synthesis/mbrola.html) gemacht. Dort wird zunächst ein Text in eine Phonetikdatei umgewandelt und in einem zweiten Schritt aus dieser *.pho eine *.wav gemacht.
    Die Sprachausgabe wirkt recht natürlich. Auf der obigen Seite gibts irgendwo auch Klangbeispiele.
    Die ganzen Temporären Dateien lege ich dabei im Verzeichnis /dev/shm/ ab. Da es sich dabei quasi um eine Ramdisk handelt vermeide ich viele Festplattenzugriffe und erhöhe die Geschwindigkeit deutlich.
    Wenn jemand mein (etwas hässliches) Skript möchte, dann kann er sich gerne melden.

    Jetzt noch ne (fast offtopic) Frage: Wie kann ich, wie im Artikel erwähnt, aus den Logdateien Zugriffsversuche auf ssh grepen?

    Gruß Julian

  4. Warum pipest du die Daten nicht einfach, statt temporäre Dateien zu erzeugen?

    echo “hello” | text2wave | aplay

    hat doch den gleichen Effekt… Ich habe aplay statt play genommen, um kein sox installieren zu müssen (Wenn man PulseAudio verwendet, kann man statt aplay auch paplay nehmen)

  5. Ich habe gerade herausgefunden, wie man eine deutsche Sprachausgabe erhält. Die Qualität ist zwar nicht sonderlich, aber vielleicht interessiert es den ein oder anderen.

    Zuerst einmal sollte espeak installiert sein:

    sudo aptitude install espeak

    Das Skript zur Ausgabe sieht dann folgendermaßen aus:

    #!/bin/sh
    TXTTMP=$HOME/.tmp.txt
    echo $@ > $TXTTMP
    echo `cat $TXTTMP`
    espeak -v de -f $TXTTMP
    exit

    Vielleicht findet jemand noch eine Möglichkeit, den Umweg über die Textdatei weg zu lassen? Mir ist es noch nicht gelungen.

    Die möglichen Sprachen kann man sich via

    espeak –voices

    ausgeben lassen.

    Viel Erfolg!

  6. Okay, hab es nun doch herausgefunden:

    #!/bin/sh
    echo $@ | espeak -v de
    exit

    So fällt der Umweg über die Textdatei weg. Allerdings weiß ich jetzt nicht, ob das ressourcensparender ist ;-)

  7. @Plippo:
    Also wenn ich ein WAV in aplay gepiped habe, dann hat er das nicht abgespielt. Er hatte dann irgendein porblem mit der Samplingrate oder so. Also musste ich den Schritt über eine WAV-Datei gehen.
    Dieses Problem kann eventuell ja auch Mbrola-spezifisch sein.

    Gruß Julian

  8. @ALLE: Vielen Dank für Euer feedback
    @Mathias: Längere Textdateien breche ich für gewöhnlich in kleinere Teile, zb:
    robo `cat text1`
    robo `cat text2`
    @piccolino81 und Julian:
    Zum Thema “deutsche Stimme” und espeaks: Da ich mehrere Jahre nur Englisch gesprochen habe und immer noch viel Englisch spreche, war die “deutsche Stimme” nie eine Bedingung für mich. Ich werde Eure Idee dazu jedoch bald mal ausprobieren und bin gespannt wie sich das unter espeaks und Deutsch anhört.
    @knopwob & plippo
    Gute Idee von knopwob, temp. dateien zu löschen, auf jeden Fall besser als meine. Noch besser natürlich der Ansatz von plippo, den ganzen Kram einfach zu pipen. Dann gibt’s auch keine temporären Dateien, die man nicht löschen braucht. Funzt gut. Werde das bald mal auf performance hin testen…
    @Julian: greppen nach ssh: Ist nicht so einfach auf die Schnelle zu beantworten. Solche events sollten aber in /var/log/kern.log oder /var/log/syslog auflaufen. Der entscheidende string wäre dann: DPT=22 (Destination Port ssh). Sobald ein grep mindestens eine Zeile ausspuckt, deutet dies auf einen Verbindungsversuch auf Port 22 hin. (Sorry, sehr rudimentär, gilt aber auch für outbound!)
    @ Julian und plippo:
    Thema play oder aplay
    aplay -q funktioniert bestens (bei mir auch durch die pipe). sox nehme ich, weil ich es eh installiert habe, es kann noch viel mehr tolle sachen.

  9. 1. echo `cat bla` ist Quatsch! cat bla reicht völlig.
    2. ein exit am Ende des Script ist Quatsch! exit ist für Funktionen. Es würde am Ende der Datei nur Sinn machen, wenn man einen Exitcode != 0 erzeugen will, und auch das ist am Ende der Datei völlig unerwünscht. Höchstens in ner if([[ $fehler == true ]] && exit 1 oder so am Ende).
    3. TMP-Dateien werden in /tmp erzeugt. Man kann dazu auch wunderschön mktemp -d verwenden, und in diesem Dir dann die Dateien. Dann kann man auch das löschen weglassen, da das bei nem Neustart eh entfällt.
    4. “Eine deutsche Stimme gibt es noch nicht.” komisch was hör ich dann gerade?

  10. @linopolus:
    1. Der Quatsch funzt zwar auch so, aber deine ist wohl die bessere Lösung
    2. Der Quatsch funzt zwar auch so, aber deine ist wohl die bessere Lösung
    3. ~/.tmp/* funzt zwar auch so, aber deine ist wohl die bessere Lösung
    4. Wo hast Du denn die deutsche Stimme für Festival her? In den Paketquellen konnte ich keine finden. Wäre Super, wenn Du so nett wärst, diese Info weiterzureichen…

  11. Hab Arch Linux und daher keine Ahnung obs da n Paket in Ubuntu gibt. In Arch Linux installier ich auf jeden Fall ausm AUR mbrola-voices-de{1..8} oder so ähnlich.

  12. Ich bin begeistert ans Forschen gegangen, und habe nun dein Robo mit mbrola zur deutschen Sprache überredet. Ich habe im Grunde mbrola nach der Anleitung: installiert. (es sind noch zwei kleine Fehler drin. Es müssen die beiden Dateien /usr/share/txt2pho/pipefilt/pipefilt und /usr/share/txt2pho/preproc/preproc ausführbar gemacht werden.

    Dein Script wird nur an einer Stelle geändert:

    sayit $TXTTMP -o $WAVTMP

    Nun die beiden Scripte say und sayit :

    #!/bin/bash (say)
    ROOT=/usr/share/txt2pho # Where are the needed files?
    VOICE=/usr/share/mbrola/de2/de2 # Path to the mbrola-voice
    SEX=m # m/f Which sex has your voice?
    if [ "$1" = "-u" ]; then
    conversion=”| recode UTF-8..lat1″
    file=$2
    else
    conversion=””
    file=$1
    fi
    eval “cat $file $conversion |
    sed ‘s/@/ ät /g’ |
    $ROOT/pipefilt/pipefilt |
    $ROOT/preproc/preproc $ROOT/preproc/Rules.lst $ROOT/preproc/Hadifix.abk |
    $ROOT/txt2pho -$SEX -p $ROOT/data/ |
    mbrola $VOICE – -.au |
    play -q -t au -”

    ———————————
    #!/bin/bash (sayit)
    #by vago
    #
    if [ "$*" = "" ]; then
    echo “usage: sayit Hello World ”
    exit 1
    else
    echo $* | say
    fi

  13. Ach so zur syntax:

    echo “Hallo Computer. Sag was.” | say

    say textdatei.txt

    sayit Hallo Computer. Sag was.

    Schönen Gruß Roughtrade (Ubuntuusers.de)

  14. So, geschafft! Dank Roughtrade und seinem Hinweis auf hat es dann endlich nach etlichen Fehlermeldungen geklappt.

    Wichtig war die Erstellung der /etc/txt2pho, ansonsten klappt es nämlich nicht – noch nicht mal durch die manuelle Übergabe.

    Ich habe das Skript von Roughtrade ein wenig gekürzt und als say.sh gespeichert:

    #/bin/bash
    ROOT=/usr/share/txt2pho
    VOICE=/usr/share/mbrola/de6/de6
    SEX=m
    echo $@ |
    $ROOT/txt2pho -$SEX |
    mbrola $VOICE – -.au |
    play -q -t au –

    Verwendung:
    ./say.sh Hallo Welt
    cat datei | ./say.sh

    Grüße
    Piccolino81

  15. @plippo
    Zum Thema: “Warum das ganze nicht pipen?”
    Wie Julian ja berichtet, funktioniert das bei ihm nicht mit aplay. Seltsamerweise habe ich genau das Gegenteil erfahren. Deine (eigentlich bessere Idee – eleganter und schöner code) funktioniert bei mir mit aplay wunderbar, genau so wie du es vorgeschlagen hast – doch bei mir funktioniert es nicht mit “play”! Das text2wave mosert dann rum, es wäre kein inputfile angegeben. Ohne Pipe und mit temporären Dateien und mit “play” hat das Skript bei mir seit 2 Jahren 100 % problemlos funktioniert. Auch wenn der Code nicht so elegant ist, halte ich es da einfach mit der leicht abgewandelten Devise: “Never change a running script”.

  16. @ piccolino81, roughtrade, Julian,
    Zum Thema “Deutsche Stimme”: Wow, da habt Ihr Euch ja mächtig ins Zeug gelegt und richtig Recherche betrieben. Das Thema scheint ja nicht so ganz einfach zu sein. Ich befürchte fast, dass nur ein kleiner Teil der User, die Früchte Eurer Arbeit ernten kann, da diese sich in einer Reihe von Kommentaren “verstecken”. Eure Mühe hätte eigentlich einen eigenen Howto-Beitrag oder gar Wiki-Eintrag verdient…

  17. @linuxnetzer
    Ich kann Dir gerne die detaillierte Anleitung für die deutsche Version via Mail schicken. Dann wäre es doch ein guter Stoff für einen neuen Blog Artikel, der dann ja auch automatisch im Planet landet, wo ich Dich ja auch gefunden habe. Selbst einen WIKI Artikel zu schreiben, traue ich mir noch nicht zu.

  18. @roughtrade
    ok, wie per email vereinbart bin freue ich mich auf die anleitung. Zusammen kriegen wir dann hoffentlich n schönes howto zusammen, wie das ganze auch auf deutsch funzt. Greetinx to the all fishheads from franconia!
    Stay tuned…

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>