Block > text2youtubevideo.sh

Vor einiger Zeit entdeckte ich durch Zufall wieder das kleine Programm espeak, was Text in Phoneme umwandelt, die dann von einem anderen Programm, z.B. mbrola in Sprache umgewandelt werden kann (die dann gefuehlt besser klingt als Microsoft Sam). Das an sich ist ja schonmal grossartig, und so habe ich einfach mal ein paar Stimmen runtergeladen und damit herumexperimentiert. Mbrola und acht deutsche Stimmen koennen unter ArchLinux aus dem AUR runtergeladen werden, espeak aus dem community-Repository:

$ yaourt -S mbrola mbrola-voices-de1 mbrola-voices-de2 \
    mbrola-voices-de3 mbrola-voices-de4 mbrola-voices-de5 \
    mbrola-voices-de6 mbrola-voices-de7 mbrola-voices-de8
# pacman -S espeak

Nach der Installation liess ich einfach mal einen kleinen Text von den verschiedenen Stimmen vorlesen:

text="Hallo, ich bin eine Computerstimme!"
espeak "$text" -vde
for i in $(seq 2 7) ; do # leider funktionieren nur die Stimmen 2-7
    espeak "$text" -vmb-de$i
done

Und ab da hatte mich der Ehrgeiz gepackt und ich wollte mehr ;)
Ich finde es absolut faszinierend, aus reinem Text durch diverse Tools sowas wie Bilder, PDFs, Videos, Audio, usw. entstehen zu lassen. So kam mir die Idee, einen Text vorlesen zu lassen, mit Hintergrundmusik und einer Videospur zu versehen und mit einem Thumbnail und Untertiteln auf YouTube hochzuladen.

Nachdem ich einen Text gefunden hatte (naemlich die Martin Luther Uebersetzung der Bibel von 1912), zerstueckelte ich ihn in passende Zeilenlaengen (mit fmt, also 75 Zeichen) und liess die ersten 100 Zeilen abwechselnd mit verschiedenen Stimmen und zeilenabhaengigem Pitch mit espeak vorlesen. Jede Zeile liess ich in eine eigene Wav-Datei ausgeben.

Hintergrundmusik

Die Hintergrundmusik fand ich schon etwas schwieriger. Angefangen habe ich mit einem awk-Script von kmkeen. Das gefiel mir auch schon ganz gut, allerdings war da nichts drin, was sich in Abhaengigkeit der Eingabe aendern wuerde, bzw. sah ich keine gute Moeglichkeit dazu.
Dann habe ich auf der Suche die ABC-Notation, scala und chasp entdeckt, was ich schonmal sehr geil fand, allerdings war es mir zu viel Aufwand, den Eingabetext entsprechend zu formatieren (nur Noten, dazu noch Notenlaengen usw.).
Und dann fand ich diesen Post, in dem durch ein kleines C-Programm 8-bit Musik generiert wird. Awesome!
Dazu z.B. einfach mal folgendes in eine Datei datei.c schreiben:

main(t){for(;;t++)putchar((t*5&t>>7)|(t*3&t>>10));}

Das mit gcc kompilieren:

$ gcc -w datei.c

Und ausfuehren, fuer unbegrenzte Musik:

$ ./a.out

Geil! :)
In dem Blogpost gibt es Links zu drei Videos, in denen einige Funktionen genannt werden. Und noch mehr Funktionen gibt es in diesem Thread.
Noch mehr zum Thema gibt es in diesem Post und es gibt sogar eine Website, auf der fuer jede Stereo-Seite eine eigene Funktion eingegeben und dann zusammen abgespielt werden kann. Awesome!

Jedenfalls war meine erste Idee, eine Funktion davon zu nehmen, und die Zahlen durch andere Zahlen auszutauschen, die mir von einer kleinen Funktion geliefert wird, die als Eingabe den Text bekommt, daraus den SHA-512-Hash bildet und mit tr nur die Zahlen ausgibt. Die ersten vier Zahlen davon ersetzen dann die Zahlen in dem C-Programm.
Allerdings gab es wegen zu grossen Zahlen Warnings, weswegen ich mich dann einfach entschieden habe, abhaengig von der Textlaenge modulo 10 eine von zehn Funktionen fuer das C-Programm auszuwaehlen. Ist nicht so toll, aber reicht fuer den Anfang. Verbesserungsvorschlaege sind sehr willkommen! :)

sha512sum input.txt | awk '{print $1}' | tr '[:alpha:]' ' ' | while read a b c d line ; do
    echo "main(t){for(;;t++)putchar((t*$a&t>>$b)|(t*$c&t>>$d));}"
done

Eine weitere Idee war es, nur wenig Output von diesem Programm zu verwenden, um es dann mit paulstretch (leider nur eine GUI) oder rubberband zu stretchen, allerdings fand ich das nicht so passend, da muesste ein besserer Input her.

Konkatenierung der Textstimmen & Vermischung mit der Hintergrundmusik

Am Anfang dachte ich noch, die Konkatenierung der Textstimmen ist ja recht trivial, einfach mit cat die Dateien konkatenieren. Aber ja, geht natuerlich nicht (Header und so). Ich habe dann eine Loesung mit ffmpeg gefunden (Quelle):

ffmpeg -y -f concat -i <( for f in espeak-*.wav ; do echo "file '$f'" ; \
    done ) -c copy concat.wav -loglevel quiet

Das klappte auch wunderbar. Erst spaeter fiel mir dann auf, dass die Laenge der einzelnen .wav-Dateien von der Laenge von concat.wav abweicht. Nachdem ich mir die Dateien mit soxi espeak-*.wav mal genauer angesehen habe, stellte ich fest, dass die Sample Rate mal 22050 und mal 16000 ist.
ffmpeg ist dann so nett, und wandelt das anscheinend um, womit manche Stimmen dann langsamer sind. Das wollte ich natuerlich nicht, daher habe ich die Sample Rate der .wav-Dateien mit sox alle auf 16k gesetzt und bei der Gelegenheit auch gleich mit einem 2. Channel versehen:

for i in espeak-*.wav ; do sox $i -r 16k -c 2 $i.wav ; mv $i.wav $i ; done

Damit waren die Laengen der einzelnen Dateien (fast) identisch mit der von ffmpeg (Quelle):

$ echo $(soxi -D espeak-*.wav | tr '\n' '+' | sed 's/+$//') | bc
45.917001
$ soxi -D concat.wav 
45.917000

Soweit so gut, nun wollte ich also alles zusammenmischen. Allerdings musste ich die Hintergrundmusik erst noch von Raw Audio in etwas geeignetes umwandeln, und dann noch auf die passende Laenge zurechtschneiden. Auch hier ist sox wieder ein sehr gutes Tool.
Aber zuerst mal musste ich etwas Musik generieren und in eine Datei schreiben lassen:

$ ./a.out | head -n 1M > audio.raw

Update (2015-12-17): meillo hat angemerkt, dass head hier nicht portabel ist, besser waere folgendes mit dd:

$ ./a.out | dd bs=1024 count=1024 > audio.raw

Ok, nun noch die Laenge von concat.wav bestimmen, um die Laenge von audio.raw dann direkt zu begrenzen (Quelle raw2wav, Quelle trim):

$ sox -r 16k -e signed -b 16 -c 1 -v -0.1 audio.raw audio.wav \
    trim 0 $(soxi -D concat.wav)

Direkt alles in einem Schritt :) Zuerst die Sample Rate auf 16k setzen, das ganze als signed, 16-Bit, Stereo und etwas leiser umwandeln und eben noch mit trim begrenzen.
Und damit musste ich nur noch alles zusammenmischen, und zwar wieder mit sox:

sox -m audio.wav concat.wav output.wav

Update: Ich habe mich nun dazu entschlossen, den letzten Schritt nicht zu machen sondern stattdessen einfach concat.wav nach output.wav zu kopieren, sprich, die Hintergrundmusik wegzulassen, da sie mit den Einstellungen einfach zu nervig war.

Awesome, was kann dieses Tool eigentlich nicht? :D
Hier mal 15 Beispiele, was so alles mit sox moeglich ist ;)

Video

Das Video hinzufuegen war schon etwas schwieriger. Vor allem wusste ich erst mal ueberhaupt nicht, was ich da nehmen sollte. Es sollte ja schon etwas mit dem Eingabetext zu tun haben ..
Beim Stoebern fand ich dann diese Frage bei StackExchange, wobei mir die Antworten schonmal gut gefielen. Weiter gefunden habe ich was mit gstreamer, allerdings war ich zu faul, mich damit zu beschaeftigen, wie das mit wav anstatt mp3 funktioniert. Und dann noch eine Frage.
Die erste Antwort hat mir gut gefallen, allerdings war das etwas zuviel auf einmal. Ich habe mich dann einfach fuer avectorscope entschieden:

ffmpeg -i output.wav -filter_complex avectorscope=s=1920x1080 -y \
    -acodec aac -strict -2 video.mp4

Zuerst hatte ich noch die Idee, das ganze einfach nur mit Bildern zu machen, allerdings waere das nicht so dynamisch gewesen. Mit sox ist es ziemlich einfach, ein Spectrogram zu erzeugen, allerdings laesst sich damit zusammen mit gnuplot auch leicht eine Waveform erstellen.

Thumbnail

Fuer YouTube sollte es dann auch ein eigenes Thumbnail geben. Hier war zuerst meine Idee, die einzelnen Ziffern des Eingabetexts ins Hexadezimalsystem umzuwandeln um dann Hexadezimal-3-Tupel als RGB-Wert zu verwenden, um dann Pixel fuer Pixel ein Bild zu erstellen.
Nach einiger Suche fand ich dann eine gute Vorangehensweise, und zwar mit dd und convert! Krass!
Zwar nicht genau das, was ich wollte, aber es erfuellte den Zweck einer Thumbnailerstellung ;)

dd if=input.txt bs=$((1280*720*3)) count=1 | convert -size \
    1280x720 -depth 8 rgb:- thumbnail.png

Das war mir dann aber doch etwas zu unschoen, und dann fiel mir wieder gmic und mein Script zum Erstellen von Bildschirmhintergruenden ein.
Meine Idee war dann, den MD5-Hash des Eingabetexts in 16 2-Tupel zu zerlegen um somit 4 RGBA Werte zu erhalten (Quelle). Damit konnte ich dann den gmic-Befehl fuettern, um ein Verlaufsbild zu erstellen:

hex=$(md5sum input.txt | awk '{print $1}' | tr '[:lower:]' '[:upper:]')
hex1=$(cut -c-8 <<<$hex)
hex2=$(cut -c9-16 <<<$hex)
hex3=$(cut -c17-24 <<<$hex)
hex4=$(cut -c25-32 <<<$hex)
rgba1=$(printf "%d,%d,%d,%d" 0x${hex1:0:2} 0x${hex1:2:2} 0x${hex1:4:2} 0x${hex1:6:2})
rgba2=$(printf "%d,%d,%d,%d" 0x${hex2:0:2} 0x${hex2:2:2} 0x${hex2:4:2} 0x${hex2:6:2})
rgba3=$(printf "%d,%d,%d,%d" 0x${hex3:0:2} 0x${hex3:2:2} 0x${hex3:4:2} 0x${hex3:6:2})
rgba4=$(printf "%d,%d,%d,%d" 0x${hex4:0:2} 0x${hex4:2:2} 0x${hex4:4:2} 0x${hex4:6:2})

gmic 1280,720,1,3 -gimp_corner_gradient $rgba1,$rgba2,$rgba3,$rgba4 -o $thumbnail

Die Aufloesung haelt sich hier an die Best Pratices zur Thumbnailerstellung von YouTube.

Eine Beschriftung wollte ich dann auch noch haben, wobei sich dann recht einfach mit convert machen laesst:

text="Ich bin eine Beschriftung."
convert -background '#0008' -fill white -gravity center -size \
    1280x180 caption:"$text" thumbnail.png +swap -gravity south \
    -composite thumbnail_neu.png

Thumbnail fuer YouTube

Untertitel

Zu guter Letzt wollte ich, wenn ich schonmal dabei bin, auch noch Untertitel fuer das Video haben. Das ist ja in meinem Fall keine grosse Sache, ich habe ja die Textzeilen und die Laenge der Textstimmen kann ich ja bestimmen.
Bei der Erstellung der Textstimmen habe ich also einfach nur fuer jede Textzeile eine srt-kompatible Form daraus gemacht. Die Laenge der Textstimmen habe ich dabei von Sekunden mit einer kleinen Funktion in hh:mm:ss:ms umgewandelt.
Awesome! :)

Alles zusammen

Die einzelnen Dateien gibt es hier zum runterladen:

Das Video (dank <video>-Tag im Browser abspielbar):

Und hier das Script zur Erstellung auf GitHub:

Geschrieben: 2015-11-25, 22:37 - Tags: linux, bibel, youtube