TTS mit Amazon / Ivona statt Google Translate

Moin zusammen,

ich habe die letzten Jahre meine Sprachausgaben immer mit Google Translate erstellt, so wie es ja einige hier im Forum auch machen. Die Sprachqualität war ja auch in Ordnung. Nur leider hat Google den Dienst ja abgekündigt und er ist aktuell nur noch mit Krücken (im Ansatz) nutzbar.

Deshalb habe ich nun eine andere Lösung gesucht und bei Ivona(.com) - einer Amazon Tochter - gefunden. Wie ich gelesen habe nutzen ja einige von Euch schon deren Stimmen in der Windows Sprachausgabe. Ivona bietet aber auch eine Web-API an mit der man die Sprach-MP3s aus der Cloud holen kann. In der kostenlosen Version lassen sich bis zu 50.000 Sprachdateien von 200 Zeichen länge pro Monat erstellen. Das sollte für die meisten hier reichen :slight_smile:

Man benötigt einen kostenlosen Developer-Account, den man sich auf deren Seite klicken kann. Danach erstellt man sich dort noch einen Satz Credentials, die in der nachfolgenden Klasse benötigt werden. Der Download einer MP3-Sprachausgabe lässt sich dann folgendermaßen anstoßen:


$a = new IVONA_TTS();
$a->save_mp3("Fischers Fritze fischt frische Fische", PFAD_ZUR_DATEI);

So, hier nun die Klasse:


<?
///////////////////////////////////////////////////////////////////////////////////////
//   IVONA_TTS             ////////////////////////////////////////////////////////////
//    by Titus 15.10.2015  ////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////

class IVONA_TTS
{
    static  $utc_tz;
    const   ACCESS_KEY = 'AAA'; // HIER DEINEN ACCESS KEY EINTRAGEN!!
    const   SECRET_KEY = 'BBB'; // HIER DEINEN SECRET KEY EINTRAGEN!!
    
    function save_mp3($text, $filename, $language="de-DE", $voice="Marlene", $rate="medium", $volume="loud") {
         $payload['Input'] = array();
			$payload['Input']['Data'] = utf8_encode($text);//{"Input":{"Data":"Hello world"}}'
			$payload['Parameters']['Rate'] = $rate;
			$payload['Parameters']['Volume'] = $volume;
			$payload['Voice']['Name'] = $voice;
			$payload['Voice']['Language'] = $language;
			
			$payload = json_encode($payload); 
			$mp3 = $this->get_mp3($payload);
			file_put_contents($filename, $mp3);
	 }

    function get_mp3( $payload )
    {
        if( !self::$utc_tz ) {
            self::$utc_tz = new \DateTimeZone( 'UTC' );
		  }
        $datestamp  = new \DateTime( "now", self::$utc_tz );
        $longdate   = $datestamp->format( "Ymd\\THis\\Z");
        $shortdate  = $datestamp->format( "Ymd" );
        // establish the signing key
        {
            $ksecret    = 'AWS4' . self::SECRET_KEY;
            $kdate      = hash_hmac( 'sha256', $shortdate, $ksecret, true );
            $kregion    = hash_hmac( 'sha256', 'eu-west-1', $kdate, true );
            $kservice   = hash_hmac( 'sha256', 'tts', $kregion, true );
            $ksigning   = hash_hmac( 'sha256', 'aws4_request', $kservice, true );
        }
        // command parameters
        $params     = array(
            'host'          => 'tts.eu-west-1.ivonacloud.com',
            'content-type'  => 'application/json',
            'x-amz-content-sha256' => hash( 'sha256', $payload ),
            'x-amz-date'    => $longdate,
        );
        $canonical_request  = $this->createCanonicalRequest( $params, $payload );
        $signed_request     = hash( 'sha256', $canonical_request );
        $sign_string        = "AWS4-HMAC-SHA256
{$longdate}
$shortdate/eu-west-1/tts/aws4_request
" . $signed_request;
        $signature          = hash_hmac( 'sha256', $sign_string, $ksigning );
        $params['Authorization'] = "AWS4-HMAC-SHA256 Credential=" . self::ACCESS_KEY . "/$shortdate/eu-west-1/tts/aws4_request, " .
                                   "SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, " .
                                   "Signature=$signature";
		  $params['content-length'] = strlen( $payload ) ;
        /*
         * Execute Crafted Request
         */
        $url    = "https://tts.eu-west-1.ivonacloud.com/CreateSpeech";
        $ch     = curl_init();
        $curl_headers = array();
        foreach( $params as $p => $k )
            $curl_headers[] = $p . ": " . $k;
        curl_setopt($ch, CURLOPT_URL,$url);
        curl_setopt($ch, CURLOPT_POST,1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
        curl_setopt($ch, CURLOPT_TCP_NODELAY, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false );
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false );
        // debug opts
        {
            curl_setopt($ch, CURLOPT_VERBOSE, true);
            $verbose = fopen('php://temp', 'rw+');
            curl_setopt($ch, CURLOPT_STDERR, $verbose);
            $result = curl_exec($ch); // raw result
            rewind($verbose);
            $verboseLog = stream_get_contents($verbose);
            #echo "Verbose information:
<pre>", htmlspecialchars($verboseLog), "</pre>
";
        }
        return $result;
    }
    private function createCanonicalRequest( Array $params, $payload )
    {
        $canonical_request      = array();
        $canonical_request[]    = 'POST';
        $canonical_request[]    = '/CreateSpeech';
        $canonical_request[]    = '';
        $can_headers            = array(
          'host' => 'tts.eu-west-1.ivonacloud.com'
        );
        foreach( $params as $k => $v )
            $can_headers[ strtolower( $k ) ] = trim( $v );
        uksort( $can_headers, 'strcmp' );
        foreach ( $can_headers as $k => $v )
            $canonical_request[] = $k . ':' . $v;
        $canonical_request[] = '';
        $canonical_request[] = implode( ';', array_keys( $can_headers ) );
        $canonical_request[] = hash( 'sha256', $payload );
        $canonical_request = implode( "
", $canonical_request );
        return $canonical_request;
    }
}
?>

Ich hoffe das ist für den einen oder anderen hier nützlich. Zudem ist auch die Qualität der Sprachausgaben bei Ivona um Welten besser als bei Google!

Wer noch was verbessern kann ist natürlich herzlich eingeladen :slight_smile:

Ja Cool, das klappt und die Stimme klingt recht gut.
Mal noch ein wenig experimentieren aber ich schätze ich nehm das Tool.

Danke für den Tipp und die Arbeit

Hi,

klappt super - Stimme ist Klasse.

DANKE!

Herbertf

Hallo,

das Problem hat sich erledigt. PC uhrzeit muss synchron sein

MFG

Hallo Titus,

kannst Du mir verraten wo ich das Skript für die Klasse reinkopieren muss?

Bei der Erstellung des Accounts für Ivona werden diverse Daten abgefragt. Müssen diese auch für den eigentlich kostenlosen Account angegeben werden oder war ich vielleicht auf einer falschen Seite (https://www.ivona.com/us/account/speechcloud/creation/).

Ich wollte natürlich kein „Abo“ an der Backe haben;)

Gruß

Axel

Moin,

es ist egal in welches Skript Du die Klasse kopierst. Du musst die entsprechende Datei anschliessend nur per „include“ in dein Sprachausgabeskript einbinden.

Registrieren muss man sich, ja. Ein Abo ist es nicht, das müsstest Du separat abschliessen. Bei mind. 1000 € pro Monat wirst Du aber vermutlich davon absehen wollen :wink:

Hallo Titus,

nach dem ich alles gemacht habe kommt bei folgendem Skript:

include 'IVONA_TTS.ips.php';
$a = new IVONA_TTS();
$a->save_mp3("Fischers Fritze fischt frische Fische",  "/sonos/");

folgende Fehlermeldung:

Fatal error: Call to undefined function hash_hmac() in /usr/share/symcon/scripts/IVONA_TTS.ips.php on line 37

In dem Skript ist in der Zeile 37 folgender Eintrag zu finden:

$kdate      = hash_hmac( 'sha256', $shortdate, $ksecret, true );

Die Uhrzeit des Raspberry ist korrekt eingerichtet.

Kannst Du mir bei der Fehlermeldung helfen?

Gruß

Axel

Dann scheint das PHP-Hash-Modul auf dem Raspberry zu fehlen. Genaueres kann ich mangels RasPi leider nicht sagen.

ja, unter linux fehlt ips noch so einiges an PHP Funktionen.

Ich habe einfach PHP5 nachinstalliert ( apt-get install php5 ) und starte Titus script, leicht angepasst, extern.

hier der IPS teil (mp3 files die schon existieren werden nicht online neu generiert).
Bei Bedarf starte ich auch noch Pulseaudio auf dem Headless ubuntu 64bit server.
Btw. Wieso muss symcon eigentlich als root laufen? Nich schoen.


<?
/*
//verify that pulseaudio is running for root
$ps = shell_exec('ps -A | grep pulseaudio');
if($ps == "")
{
	echo "starting pulseaudio";
	echo shell_exec('pulseaudio --system -D');
}
*/

$say = "hallo welt!";


if (is_readable($say .".mp3"))
{
	echo "we have it
";
}
else
{
	echo "we don't have it
";
	echo shell_exec('php ivona.php "' .$say .'"');
}

$escaped_filename = str_replace(" ", "\ ", $say) .".mp3";
echo shell_exec('mpg123 ' .$escaped_filename);

?>

und hier das angepasste Script, welches momentan bei mir unter /usr/share/symcon/scripts/invona.php liegt (work in progress):


<?php
///////////////////////////////////////////////////////////////////////////////////////
//   IVONA_TTS             ////////////////////////////////////////////////////////////
//    by Titus 15.10.2015  ////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////

class IVONA_TTS
{ 
    static  $utc_tz;
    const   ACCESS_KEY = 'xx'; // HIER DEINEN ACCESS KEY EINTRAGEN!!
    const   SECRET_KEY = 'xx'; // HIER DEINEN SECRET KEY EINTRAGEN!!

    function save_mp3($text, $filename, $language="de-DE", $voice="Marlene", $rate="medium", $volume="loud") {
         $payload['Input'] = array();
            $payload['Input']['Data'] = utf8_encode($text);//{"Input":{"Data":"Hello world"}}'
            $payload['Parameters']['Rate'] = $rate;
            $payload['Parameters']['Volume'] = $volume;
            $payload['Voice']['Name'] = $voice;
            $payload['Voice']['Language'] = $language;
            
            $payload = json_encode($payload); 
            $mp3 = $this->get_mp3($payload);
            file_put_contents($filename, $mp3);
     }

    function get_mp3( $payload )
    {
        if( !self::$utc_tz ) {
            self::$utc_tz = new \DateTimeZone( 'UTC' );
          }
        $datestamp  = new \DateTime( "now", self::$utc_tz );
        $longdate   = $datestamp->format( "Ymd\\THis\\Z");
        $shortdate  = $datestamp->format( "Ymd" );
        // establish the signing key
        {
            $ksecret    = 'AWS4' . self::SECRET_KEY;
            $kdate      = hash_hmac( 'sha256', $shortdate, $ksecret, true );
            $kregion    = hash_hmac( 'sha256', 'eu-west-1', $kdate, true );
            $kservice   = hash_hmac( 'sha256', 'tts', $kregion, true );
            $ksigning   = hash_hmac( 'sha256', 'aws4_request', $kservice, true );
        }
        // command parameters
        $params     = array(
            'host'          => 'tts.eu-west-1.ivonacloud.com',
            'content-type'  => 'application/json',
            'x-amz-content-sha256' => hash( 'sha256', $payload ),
            'x-amz-date'    => $longdate,
        );
        $canonical_request  = $this->createCanonicalRequest( $params, $payload );
        $signed_request     = hash( 'sha256', $canonical_request );
        $sign_string        = "AWS4-HMAC-SHA256
{$longdate}
$shortdate/eu-west-1/tts/aws4_request
" . $signed_request;
        $signature          = hash_hmac( 'sha256', $sign_string, $ksigning );
        $params['Authorization'] = "AWS4-HMAC-SHA256 Credential=" . self::ACCESS_KEY . "/$shortdate/eu-west-1/tts/aws4_request, " .
                                   "SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, " .
                                   "Signature=$signature";
          $params['content-length'] = strlen( $payload ) ;
        /*
         * Execute Crafted Request
         */
        $url    = "https://tts.eu-west-1.ivonacloud.com/CreateSpeech";
        $ch     = curl_init();
        $curl_headers = array();
        foreach( $params as $p => $k )
            $curl_headers[] = $p . ": " . $k;
        curl_setopt($ch, CURLOPT_URL,$url);
        curl_setopt($ch, CURLOPT_POST,1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
        curl_setopt($ch, CURLOPT_TCP_NODELAY, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false );
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false );
        // debug opts
        {
            curl_setopt($ch, CURLOPT_VERBOSE, true);
            $verbose = fopen('php://temp', 'rw+');
            curl_setopt($ch, CURLOPT_STDERR, $verbose);
            $result = curl_exec($ch); // raw result
            rewind($verbose);
            $verboseLog = stream_get_contents($verbose);
            #echo "Verbose information:
<pre>", htmlspecialchars($verboseLog), "</pre>
";
        }
        return $result;
    }

    private function createCanonicalRequest( Array $params, $payload )
    {
        $canonical_request      = array();
        $canonical_request[]    = 'POST';
        $canonical_request[]    = '/CreateSpeech';
        $canonical_request[]    = '';
        $can_headers            = array(
          'host' => 'tts.eu-west-1.ivonacloud.com'
        );
        foreach( $params as $k => $v )
            $can_headers[ strtolower( $k ) ] = trim( $v );
        uksort( $can_headers, 'strcmp' );
        foreach ( $can_headers as $k => $v )
            $canonical_request[] = $k . ':' . $v;
        $canonical_request[] = '';
        $canonical_request[] = implode( ';', array_keys( $can_headers ) );
        $canonical_request[] = hash( 'sha256', $payload );
        $canonical_request = implode( "
", $canonical_request );
        return $canonical_request;
    }
}

$a = new IVONA_TTS();
$a->save_mp3($argv[1], $argv[1] .".mp3");

?>

Gruss, Michael

Hallo,

Ich sehe schon, mal wieder ein Fall von zwei Dumme, ein Gedanke. Wobei ich nur die hash Funktionen extern aufrufe.

Ich habe das ganze gleich in ein Modul gekippt:
Ivona TTS Modul

Paresy hat gesagt, er wird die hash Funktionen im nächsten PHP Update mit aufnehmen. Bis dahin ist der Workaround drin. Der ist aber automatisch inaktiv ist, sobald die hash Funktionen verfügbar sind…

Gruß,
Thorsten

Nabend!

Habe die Funktion auch mal testen wollen, allerdings kommt bei mir beim Aufruf von


<?
include 'ivona_tts.ips.php';
$a = new IVONA_TTS();
$a->save_mp3("Fischers Fritze fischt frische Fische", "/media/");
?>

folgender Fehler:

Warning:  file_put_contents(/media/): failed to open stream: Invalid argument in [Media\Text to Speech - Ivona\Ivona] on line 23

Wo liegt das Problem?

Niemand eine Idee?! Bekomme is leider nicht ans laufen.

Hi,

versuch mal

<? include 'ivona_tts.ips.php'; $a = new IVONA_TTS(); $a->save_mp3("Fischers Fritze fischt frische Fische", "/media/test.mp3"); ?>

Grüsse
Dave

Hast Du mal einen absoluten Pfad versucht („c:\ip-symcon\media“)? Und natürlich einen Dateinamen anhängen, wie Dave schon schrieb.

Hatte auch grad das getestet und mit folgender Pfad Angabe hat es bei mir die mp3 Datei geschrieben: „c:/ip-symcon/media/123.mp3“
Wichtig: Schrägstriche / verwenden statt Backslash !!

Yay, so funktioniert es! Vielen Dank!

Kann ich eigentlich auch direkt eine Audioausgabe gestalten oder geht es nur über erstellung und speicherung der mp3 und anschließend wiedergabe mit z.B. der Mediaplayer Instanz!?

Hi, ich sende meine gespeicherte mp3 über den Befehl „WFC_AudioNotification“ an den Client. Diese wird dann vom Browser, der das Webfront offen hat, abgespielt.

Eine direkte Wiedergabe würde ja ggf. der Server ausgeben, ich denke das dürfte in den meisten Fällen nicht passen.

Bin gespannt ob es weitere sinnvollere Lösungen gibt…

@Titus Danke danke! Hatte den Key schon, und hab’s dennoch mit php selbst nicht geschafft! Funzt perfekt!
lg, Christian

Freut mich zu hören :slight_smile: Bitte gerne!