From b070c83a17cf93a3a9c2fe4852d1a17934fdc2af Mon Sep 17 00:00:00 2001 From: "Tobias.Faust" <> Date: Tue, 7 Mar 2017 11:52:27 +0000 Subject: [PATCH] 98_Text2Speech: new server mode to serve audiofile for requestors git-svn-id: https://svn.fhem.de/fhem/trunk@13633 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 1 + fhem/FHEM/98_Text2Speech.pm | 174 ++++++++++++++++++++++++------------ 2 files changed, 118 insertions(+), 57 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index a7063f00c..438f6f935 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 98_Text2Speech: new server mode to serve audiofile for requestors - feature: at+notify: extend the FHEMWEB Wizard with a simple command modifier - feature: 42_SYSMON: support ssh login with public key - bugfix: 10_EQ3BT: fix lastChangeBy reading diff --git a/fhem/FHEM/98_Text2Speech.pm b/fhem/FHEM/98_Text2Speech.pm index 2edcee850..c9012c0b1 100644 --- a/fhem/FHEM/98_Text2Speech.pm +++ b/fhem/FHEM/98_Text2Speech.pm @@ -5,7 +5,7 @@ # 98_Text2Speech.pm # # written by Tobias Faust 2013-10-23 -# e-mail: tobias dot faust at online dot de +# e-mail: tobias dot faust at gmx dot net # ############################################## @@ -163,6 +163,7 @@ sub Text2Speech_Initialize($) " TTS_VolumeAdjust". " TTS_noStatisticsLog:1,0". " TTS_Language:".join(",", sort keys %{$language{"Google"}}). + " TTS_SpeakAsFastAsPossible:1,0". " ".$readingFnAttributes; } @@ -204,6 +205,10 @@ sub Text2Speech_Define($$) $hash->{portpassword} = $a[3] if(@a == 4); $hash->{MODE} = "REMOTE"; + } elsif (lc($dev) eq "none") { + # Ein DummyDevice, Serverdevice. Nur Generierung der mp3 TTS Dateien + $hash->{MODE} = "SERVER"; + undef $hash->{ALSADEVICE}; } else { # Ein Alsadevice ist angegeben # pruefen, ob Alsa-Device in /etc/asound.conf definiert ist @@ -254,21 +259,24 @@ sub Text2Speech_Attr(@) { return "wrong delemiter syntax: [+-]a[lfn]. \n". " Example 1: +an~\n". " Example 2: +al." if($value !~ m/^([+-]a[lfn]){0,1}(.){1}$/i); - return "This Attribute is only available in direct mode" if($hash->{MODE} ne "DIRECT"); + return "This Attribute is only available in direct or server mode" if($hash->{MODE} !~ m/(DIRECT|SERVER)/ ); } elsif ($a[2] eq "TTS_Ressource") { - return "This Attribute is only available in direct mode" if($hash->{MODE} ne "DIRECT"); + return "This Attribute is only available in direct or server mode" if($hash->{MODE} !~ m/(DIRECT|SERVER)/ ); } elsif ($a[2] eq "TTS_CacheFileDir") { - return "This Attribute is only available in direct mode" if($hash->{MODE} ne "DIRECT"); + return "This Attribute is only available in direct or server mode" if($hash->{MODE} !~ m/(DIRECT|SERVER)/ ); + } elsif ($a[2] eq "TTS_SpeakAsFastAsPossible") { + return "This Attribute is only available in direct or server mode" if($hash->{MODE} !~ m/(DIRECT|SERVER)/ ); + } elsif ($a[2] eq "TTS_UseMP3Wrap") { - return "This Attribute is only available in direct mode" if($hash->{MODE} ne "DIRECT"); + return "This Attribute is only available in direct or server mode" if($hash->{MODE} !~ m/(DIRECT|SERVER)/ ); return "Attribute TTS_UseMP3Wrap is required by Attribute TTS_SentenceAppendix! Please delete it first." if(($a[0] eq "del") && (AttrVal($hash->{NAME}, "TTS_SentenceAppendix", undef))); } elsif ($a[2] eq "TTS_SentenceAppendix") { - return "This Attribute is only available in direct mode" if($hash->{MODE} ne "DIRECT"); + return "This Attribute is only available in direct or server mode" if($hash->{MODE} !~ m/(DIRECT|SERVER)/ ); return "Attribute TTS_UseMP3Wrap is required!" unless(AttrVal($hash->{NAME}, "TTS_UseMP3Wrap", undef)); my $file = $TTS_CacheFileDir ."/". $value; @@ -444,10 +452,11 @@ sub Text2Speech_Set($@) } # Abbruch falls Disabled - return undef if(AttrVal($hash->{NAME}, "disable", "0") eq "1"); + return "no set cmd on a disabled device !" if(IsDisabled($me)); + if($cmd eq "tts") { - if($hash->{MODE} eq "DIRECT") { + if($hash->{MODE} eq "DIRECT" || $hash->{MODE} eq "SERVER") { readingsSingleUpdate($hash, "playing", "1", 1); Text2Speech_PrepareSpeech($hash, join(" ", @a)); $hash->{helper}{RUNNING_PID} = BlockingCall("Text2Speech_DoIt", $hash, "Text2Speech_Done", $TTS_TimeOut, "Text2Speech_AbortFn", $hash) unless(exists($hash->{helper}{RUNNING_PID})); @@ -789,40 +798,70 @@ sub Text2Speech_DoIt($) { return undef; } - if(AttrVal($hash->{NAME}, "TTS_UseMP3Wrap", 0)) { - # benutze das Tool MP3Wrap um bereits einzelne vorhandene Sprachdateien - # zusammenzuführen. Ziel: sauberer Sprachfluss - my @Mp3WrapFiles; - my @Mp3WrapText; - - $TTS_SentenceAppendix = $myFileTemplateDir ."/". $TTS_SentenceAppendix if($TTS_SentenceAppendix); - undef($TTS_SentenceAppendix) if($TTS_SentenceAppendix && (! -e $TTS_SentenceAppendix)); + my @Mp3WrapFiles; + my @Mp3WrapText; + + $TTS_SentenceAppendix = $myFileTemplateDir ."/". $TTS_SentenceAppendix if($TTS_SentenceAppendix); + undef($TTS_SentenceAppendix) if($TTS_SentenceAppendix && (! -e $TTS_SentenceAppendix)); - #Abspielliste erstellen - foreach my $t (@{$hash->{helper}{Text2Speech}}) { - if(-e $TTS_CacheFileDir."/".$t) { $filename = $t;} else {$filename = md5_hex($language{$TTS_Ressource}{$TTS_Language} ."|". $t) . ".mp3";} # falls eine bestimmte mp3-Datei gespielt werden soll + #Abspielliste erstellen + foreach my $t (@{$hash->{helper}{Text2Speech}}) { + if(-e $t) { + # falls eine bestimmte mp3-Datei mit absolutem Pfad gespielt werden soll + $filename = $t; + $file = $filename; + Log3 $hash->{NAME}, 4, "Text2Speech: $filename als direkte MP3 Datei erkannt!"; + } elsif(-e $TTS_CacheFileDir."/".$t) { + # falls eine bestimmte mp3-Datei mit relativem Pfad gespielt werden soll + $filename = $t; $file = $TTS_CacheFileDir."/".$filename; - if(-e $file) { - push(@Mp3WrapFiles, $file); - push(@Mp3WrapText, $t); - } else {last;} + Log3 $hash->{NAME}, 4, "Text2Speech: $filename als direkte MP3 Datei erkannt!"; + } else { + $filename = md5_hex($language{$TTS_Ressource}{$TTS_Language} ."|". $t) . ".mp3"; + $file = $TTS_CacheFileDir."/".$filename; + Log3 $hash->{NAME}, 4, "Text2Speech: Textbaustein ist keine direkte MP3 Datei, ermittle MD5 CacheNamen: $filename"; + } + + if(-e $file) { + push(@Mp3WrapFiles, $file); + push(@Mp3WrapText, $t); + } else { + # es befindet sich noch Text zum Download in der Queue + if (AttrVal($hash->{NAME}, "TTS_SpeakAsFastAsPossible", 0) == 0) { + Text2Speech_Download($hash, $file, $t); + if(-e $file) { + push(@Mp3WrapFiles, $file); + push(@Mp3WrapText, $t); + } + } else { + last; + } } - push(@Mp3WrapFiles, $TTS_SentenceAppendix) if($TTS_SentenceAppendix); + last if (AttrVal($hash->{NAME}, "TTS_UseMP3Wrap", 0) == 0); + # ohne mp3wrap darf nur ein Textbaustein verarbeitet werden + } - if(scalar(@Mp3WrapFiles) >= 2) { - Log3 $hash->{NAME}, 4, "Text2Speech: Bearbeite per MP3Wrap jetzt den Text: ". join(" ", @Mp3WrapText); + push(@Mp3WrapFiles, $TTS_SentenceAppendix) if($TTS_SentenceAppendix); + + if(scalar(@Mp3WrapFiles) >= 2 && AttrVal($hash->{NAME}, "TTS_UseMP3Wrap", 0) == 1) { + # benutze das Tool MP3Wrap um bereits einzelne vorhandene Sprachdateien + # zusammenzuführen. Ziel: sauberer Sprachfluss + Log3 $hash->{NAME}, 4, "Text2Speech: Bearbeite per MP3Wrap jetzt den Text: ". join(" ", @Mp3WrapText); - my $Mp3WrapPrefix = md5_hex(join("|", @Mp3WrapFiles)); - my $Mp3WrapFile = $TTS_CacheFileDir ."/". $Mp3WrapPrefix . "_MP3WRAP.mp3"; + my $Mp3WrapPrefix = md5_hex(join("|", @Mp3WrapFiles)); + my $Mp3WrapFile = $TTS_CacheFileDir ."/". $Mp3WrapPrefix . "_MP3WRAP.mp3"; - if(! -e $Mp3WrapFile) { - $cmd = "mp3wrap " .$TTS_CacheFileDir. "/" .$Mp3WrapPrefix. ".mp3 " .join(" ", @Mp3WrapFiles); - $cmd .= " >/dev/null" if($verbose < 5); + if(! -e $Mp3WrapFile) { + $cmd = "mp3wrap " .$TTS_CacheFileDir. "/" .$Mp3WrapPrefix. ".mp3 " .join(" ", @Mp3WrapFiles); + $cmd .= " >/dev/null" if($verbose < 5); - Log3 $hash->{NAME}, 4, "Text2Speech: " .$cmd; - system($cmd); - } + Log3 $hash->{NAME}, 4, "Text2Speech: " .$cmd; + system($cmd); + } + + if ($hash->{MODE} ne "SERVER") { + # im Falls Server, nicht die Datei abspielen if(-e $Mp3WrapFile) { $cmd = Text2Speech_BuildMplayerCmdString($hash, $Mp3WrapFile); $cmd .= " >/dev/null" if($verbose < 5); @@ -832,30 +871,15 @@ sub Text2Speech_DoIt($) { } else { Log3 $hash->{NAME}, 2, "Text2Speech: Mp3Wrap Datei konnte nicht angelegt werden."; } - - return $hash->{NAME} ."|". - ($TTS_SentenceAppendix ? scalar(@Mp3WrapFiles)-1: scalar(@Mp3WrapFiles)) ."|". - $Mp3WrapFile; } + + return $hash->{NAME} ."|". + ($TTS_SentenceAppendix ? scalar(@Mp3WrapFiles)-1: scalar(@Mp3WrapFiles)) ."|". + $Mp3WrapFile; } - Log3 $hash->{NAME}, 4, "Text2Speech: Bearbeite jetzt den Text: ". $hash->{helper}{Text2Speech}[0]; - if(-e $hash->{helper}{Text2Speech}[0]) { - # falls eine bestimmte mp3-Datei mit absolutem Pfad gespielt werden soll - $filename = $hash->{helper}{Text2Speech}[0]; - $file = $filename; - Log3 $hash->{NAME}, 4, "Text2Speech: $filename als direkte MP3 Datei erkannt!"; - } elsif(-e $TTS_CacheFileDir."/".$hash->{helper}{Text2Speech}[0]) { - # falls eine bestimmte mp3-Datei mit relativem Pfad gespielt werden soll - $filename = $hash->{helper}{Text2Speech}[0]; - $file = $TTS_CacheFileDir."/".$filename; - Log3 $hash->{NAME}, 4, "Text2Speech: $filename als direkte MP3 Datei erkannt!"; - } else { - $filename = md5_hex($language{$TTS_Ressource}{$TTS_Language} ."|". $hash->{helper}{Text2Speech}[0]) . ".mp3"; - $file = $TTS_CacheFileDir."/".$filename; - Log3 $hash->{NAME}, 4, "Text2Speech: Textbaustein ist keine direkte MP3 Datei, ermittle MD5 CacheNamen: $filename"; - } + Log3 $hash->{NAME}, 4, "Text2Speech: Bearbeite jetzt den Text: ". $hash->{helper}{Text2Speech}[0]; if(! -e $file) { # Datei existiert noch nicht im Cache Text2Speech_Download($hash, $file, $hash->{helper}{Text2Speech}[0]); @@ -863,7 +887,9 @@ sub Text2Speech_DoIt($) { Log3 $hash->{NAME}, 4, "Text2Speech: $file gefunden, kein Download"; } - if(-e $file) { # Datei existiert jetzt + if(-e $file && $hash->{MODE} ne "SERVER") { + # Datei existiert jetzt + # im Falls Server, nicht die Datei abspielen $cmd = Text2Speech_BuildMplayerCmdString($hash, $file); $cmd .= " >/dev/null" if($verbose < 5); @@ -899,6 +925,8 @@ sub Text2Speech_Done($) { push(@text, $hash->{helper}{Text2Speech}[$i]); } Text2Speech_WriteStats($hash, 1, $filename, join(" ", @text)) if (AttrVal($hash->{NAME},"TTS_noStatisticsLog", "0")==0); + + readingsSingleUpdate($hash, "lastFilename", $filename, 1); } delete($hash->{helper}{RUNNING_PID}); @@ -1024,6 +1052,14 @@ sub Text2Speech_WriteStats($$$$){ +
  • + Server Device + +
  • + @@ -1143,6 +1179,13 @@ sub Text2Speech_WriteStats($$$$){ please take care to cleanup your cachedirectory by yourself. +
  • TTS_speakAsFastAsPossible
    + Trying to get an speach as fast as possible. In case of not present audiobricks, you can hear a short break. + The audiobrick will be download at this time. In case of an presentation of all audiobricks at local cache, + this attribute has no impact.
    + Attribute only valid in case of an local or server instance. +
  • +
  • readingFnAttributes

  • disable
    @@ -1179,9 +1222,10 @@ sub Text2Speech_WriteStats($$$$){
  • +
  • + Server Device + +
  • + @@ -1256,7 +1309,7 @@ sub Text2Speech_WriteStats($$$$){ Hintergrund ist die Tatsache, das die Google Sprachengine nur 100Zeichen zulässt.
    Im Standard wird nach jedem Satzende geteilt. Ist ein einzelner Satz länger als 100 Zeichen, so wird zusätzlich nach Kommata, Semikolon und dem Verbindungswort und geteilt.
    - Achtung: Nur bei einem lokal definierter Text2Speech Instanz möglich und nur Nutzung der Google Sprachengine relevant! + Achtung: Nur bei einem lokal definierter Text2Speech Instanz möglich und nur bei Nutzung der Google Sprachengine relevant!
  • TTS_Ressource
    @@ -1356,6 +1409,13 @@ sub Text2Speech_WriteStats($$$$){ Wenn dieses hier dektiviert wird muss sich der User selbst darum kuemmern.
  • +
  • TTS_speakAsFastAsPossible
    + Es wird versucht, so schnell als möglich eine Sprachausgabe zu erzielen. Bei Sprachbausteinen die nicht bereits lokal vorliegen, + ist eine kurze Pause wahrnehmbar. Dann wird der benötigte Sprachbaustein nachgeladen. Liegen alle Sprachbausteine im Cache vor, + so hat dieses Attribut keine Auswirkung.
    + Attribut nur verfügbar bei einer lokalen oder Server Instanz +
  • +
  • readingFnAttributes