############################################## # $Id: $ # # 98_Text2Speech.pm # # written by Tobias Faust 2013-10-23 # e-mail: tobias dot faust at online dot de # ############################################## ############################################## # EDITOR=nano # visudo # ALL ALL = NOPASSWD: /usr/bin/mplayer ############################################## package main; use strict; use warnings; use Blocking; use IO::File; use HttpUtils; use Digest::MD5 qw(md5_hex); use URI::Escape; use Data::Dumper; sub Text2Speech_OpenDev($); sub Text2Speech_CloseDev($); # SetParamName -> Anzahl Paramter my %sets = ( "tts" => "1", "volume" => "1" ); # path to mplayer my $mplayer = 'sudo /usr/bin/mplayer'; #my $mplayerOpts = '-nolirc -noconsolecontrols -http-header-fields "User-Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22m"'; my $mplayerOpts = '-nolirc -noconsolecontrols'; my $mplayerNoDebug = '-really-quiet'; my $mplayerAudioOpts = '-ao alsa:device='; #my $ttsAddr = 'http://translate.google.com/translate_tts?tl=de&q='; my $ttsHost = 'translate.google.com'; my $ttsPath = '/translate_tts?tl=de&q='; ########################## sub Text2Speech_Initialize($) { my ($hash) = @_; $hash->{WriteFn} = "Text2Speech_Write"; $hash->{ReadyFn} = "Text2Speech_Ready"; $hash->{DefFn} = "Text2Speech_Define"; $hash->{SetFn} = "Text2Speech_Set"; $hash->{UndefFn} = "Text2Speech_Undefine"; $hash->{AttrFn} = "Text2Speech_Attr"; $hash->{AttrList} = "disable:0,1". " TTS_Delemiter". " TTS_Ressource:Google,ESpeak". " TTS_CacheFileDir". " TTS_UseMP3Wrap:0,1". " TTS_MplayerCall". " TTS_SentenceAppendix". " TTS_FileMapping". " TTS_FileTemplateDir". " TTS_VolumeAdjust". " ".$readingFnAttributes; } ########################## # Define Text2Speech # Define Text2Speech host[:port][:SSL] [portpassword] ########################## sub Text2Speech_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t]+", $def); #$a[0]: Name #$a[1]: Type/Alias -> Text2Speech #$a[2]: definition #$a[3]: optional: portpasswd if(int(@a) < 3) { my $msg = "wrong syntax: define Text2Speech \n". "see at /etc/asound.conf\n". "or remote syntax: define Text2Speech host[:port][:SSL] [portpassword]"; Log3 $hash, 2, $msg; return $msg; } my $dev = $a[2]; if($dev =~ m/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/ ) { # Ein RemoteDevice ist angegeben # zb: 192.168.10.24:7272:SSL mypasswd if($dev =~ m/^(.*):SSL$/) { $dev = $1; $hash->{SSL} = 1; } if($dev !~ m/^.+:[0-9]+$/) { # host:port $dev = "$dev:7072"; } $hash->{Host} = $dev; $hash->{portpassword} = $a[3] if(@a == 4); $hash->{MODE} = "REMOTE"; } else { # Ein Alsadevice ist angegeben # pruefen, ob Alsa-Device in /etc/asound.conf definiert ist $hash->{MODE} = "DIRECT"; $hash->{ALSADEVICE} = $a[2]; } BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID})); delete($hash->{helper}{RUNNING_PID}); $hash->{STATE} = "Initialized"; return undef; } ##################################### sub Text2Speech_Undefine($$) { my ($hash, $arg) = @_; RemoveInternalTimer($hash); BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID})); Text2Speech_CloseDev($hash); return undef; } ################################### # Angabe des Delemiters: zb.: +af~ # + -> erzwinge das Trennen, auch wenn Textbaustein < 100Zeichen # - -> Trenne nur wenn Textbaustein > 100Zeichen # af -> add first -> füge den Delemiter am Satzanfang wieder hinzu # al -> add last -> füge den Delemiter am Satzende wieder hinzu # an -> add nothing -> Delemiter nicht wieder hinzufügen # ~ -> der Delemiter ################################### sub Text2Speech_Attr(@) { my @a = @_; my $do = 0; my $hash = $defs{$a[1]}; my $value = $a[3]; my $TTS_FileTemplateDir = AttrVal($hash->{NAME}, "TTS_FileTemplateDir", "templates"); my $TTS_CacheFileDir = AttrVal($hash->{NAME}, "TTS_CacheFileDir", "cache"); my $TTS_FileMapping = AttrVal($hash->{NAME}, "TTS_FileMapping", ""); # zb, silence:silence.mp3 ring:myringtone.mp3; if($a[2] eq "TTS_Delemiter" && $a[0] ne "del") { 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"); } elsif ($a[2] eq "TTS_Ressource") { return "This Attribute is only available in direct mode" if($hash->{MODE} ne "DIRECT"); } elsif ($a[2] eq "TTS_CacheFileDir") { return "This Attribute is only available in direct mode" if($hash->{MODE} ne "DIRECT"); } elsif ($a[2] eq "TTS_UseMP3Wrap") { return "This Attribute is only available in direct mode" if($hash->{MODE} ne "DIRECT"); return "Attribute TTS_UseMP3Wrap is required by Attribute TTS_SentenceAppendix! Please delete it first." if(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 "Attribute TTS_UseMP3Wrap is required!" unless(AttrVal($hash->{NAME}, "TTS_UseMP3Wrap", undef)); my $file = $TTS_CacheFileDir ."/". $value; return "File <".$file."> does not exists in CacheFileDir" if(! -e $file); } elsif ($a[2] eq "TTS_FileTemplateDir") { unless(-e ($TTS_CacheFileDir ."/". $value) or mkdir ($TTS_CacheFileDir ."/". $value)) { #Verzeichnis anlegen gescheitert return "Could not create directory: <$value>"; } } elsif ($a[2] eq "TTS_FileMapping") { #ueberpruefen, ob mp3 Template existiert my @FileTpl = split(" ", $TTS_FileMapping); for(my $j=0; $j<(@FileTpl); $j++) { my @FileTplPc = split(/:/, $FileTpl[$j]); return "file does not exist: <".$TTS_CacheFileDir ."/". $TTS_FileTemplateDir ."/". $FileTplPc[1] .">" unless (-e $TTS_CacheFileDir ."/". $TTS_FileTemplateDir ."/". $FileTplPc[1]); } } if($a[0] eq "set" && $a[2] eq "disable") { $do = (!defined($a[3]) || $a[3]) ? 1 : 2; } $do = 2 if($a[0] eq "del" && (!$a[2] || $a[2] eq "disable")); return if(!$do); $hash->{STATE} = ($do == 1 ? "disabled" : "Initialized"); return undef; } ##################################### sub Text2Speech_Ready($) { my ($hash) = @_; return Text2speech_OpenDev($hash, 1); } ######################## sub Text2Speech_OpenDev($) { my ($hash) = @_; my $dev = $hash->{Host}; my $name = $hash->{NAME}; Log3 $name, 4, "Text2Speech opening $name at $dev"; my $conn; if($hash->{SSL}) { eval "use IO::Socket::SSL"; Log3 $name, 1, $@ if($@); $conn = IO::Socket::SSL->new(PeerAddr => "$dev") if(!$@); } else { $conn = IO::Socket::INET->new(PeerAddr => $dev); } if(!$conn) { Log3($name, 3, "Text2Speech: Can't connect to $dev: $!"); $hash->{STATE} = "disconnected"; return ""; } else { $hash->{STATE} = "Initialized"; } $hash->{TCPDev} = $conn; $hash->{FD} = $conn->fileno(); Log3 $name, 4, "Text2Speech device opened ($name)"; syswrite($hash->{TCPDev}, $hash->{portpassword} . "\n") if($hash->{portpassword}); return undef; } ######################## sub Text2Speech_CloseDev($) { my ($hash) = @_; my $name = $hash->{NAME}; my $dev = $hash->{Host}; return if(!$dev); if($hash->{TCPDev}) { $hash->{TCPDev}->close(); Log3 $hash, 4, "Text2speech Device closed ($name)"; } delete($hash->{TCPDev}); delete($hash->{FD}); } ######################## sub Text2Speech_Write($$) { my ($hash,$msg) = @_; my $name = $hash->{NAME}; my $dev = $hash->{Host}; #my $call = "set tts tts Das ist ein Test."; my $call = "set $name tts $msg"; Text2Speech_OpenDev($hash) if(!$hash->{TCPDev}); #lets try again Text2Speech_OpenDev($hash) if(!$hash->{TCPDev}); if($hash->{TCPDev}) { Log3 $hash, 4, "Text2Speech: Write remote message to $dev: $call"; Log3 $hash, 3, "Text2Speech: Could not write remote message ($call) at " .$hash->{Host} if(!defined(syswrite($hash->{TCPDev}, "$call\n"))); Text2Speech_CloseDev($hash); } } ########################################################################### sub Text2Speech_Set($@) { my ($hash, @a) = @_; my $me = $hash->{NAME}; return "no set argument specified" if(int(@a) < 2); my $cmd = shift(@a); # Dummy $cmd = shift(@a); # DevName if(!defined($sets{$cmd})) { my $r = "Unknown argument $cmd, choose one of ".join(" ",sort keys %sets); return $r; } if($cmd ne "tts") { return "$cmd needs $sets{$cmd} parameter(s)" if(@a-$sets{$cmd} != 0); } # Abbruch falls Disabled return undef if(AttrVal($hash->{NAME}, "disable", "0") eq "1"); if($cmd eq "tts") { if($hash->{MODE} eq "DIRECT") { Text2Speech_PrepareSpeech($hash, join(" ", @a)); $hash->{helper}{RUNNING_PID} = BlockingCall("Text2Speech_DoIt", $hash, "Text2Speech_Done", 60, "Text2Speech_AbortFn", $hash) unless(exists($hash->{helper}{RUNNING_PID})); } elsif ($hash->{MODE} eq "REMOTE") { Text2Speech_Write($hash, join(" ", @a)); } else {return undef;} } elsif($cmd eq "volume") { my $vol = join(" ", @a); return "volume adjusting only available in direct mode" if($hash->{MODE} ne "DIRECT"); return "volume level expects 0..100 percent" if($vol !~ m/^([0-9]{1,3})$/ or $vol > 100); $hash->{VOLUME} = $vol if($vol <= 100); delete($hash->{VOLUME}) if($vol > 100); } return undef; } ##################################### # Bereitet den gesamten String vor. # Bei Nutzung Google wird dieser in ein Array # zerlegt mit jeweils einer maximalen # Stringlänge von 100Chars # # param1: $hash # param2: string to speech # ##################################### sub Text2Speech_PrepareSpeech($$) { my ($hash, $t) = @_; my $TTS_Ressource = AttrVal($hash->{NAME}, "TTS_Ressource", "Google"); my $TTS_Delemiter = AttrVal($hash->{NAME}, "TTS_Delemiter", undef); my $TTS_FileTpl = AttrVal($hash->{NAME}, "TTS_FileMapping", ""); # zb, silence:silence.mp3 ring:myringtone.mp3; im Text: mein Klingelton :ring: ist laut. my $TTS_FileTemplateDir = AttrVal($hash->{NAME}, "TTS_FileTemplateDir", "templates"); my $TTS_ForceSplit = 0; my $TTS_AddDelemiter; if($TTS_Delemiter && $TTS_Delemiter =~ m/^[+-]a[lfn]/i) { $TTS_ForceSplit = 1 if(substr($TTS_Delemiter,0,1) eq "+"); $TTS_ForceSplit = 0 if(substr($TTS_Delemiter,0,1) eq "-"); $TTS_AddDelemiter = substr($TTS_Delemiter,1,2); # af, al oder an $TTS_Delemiter = substr($TTS_Delemiter,3); } elsif (!$TTS_Delemiter) { # Default wenn Attr nicht gesetzt $TTS_Delemiter = "(?<=[\\.!?])\\s*"; $TTS_ForceSplit = 1; $TTS_AddDelemiter = ""; } if($TTS_Ressource eq "Google") { my @text; $t =~ s/ä/ae/g; $t =~ s/ö/oe/g; $t =~ s/ü/ue/g; $t =~ s/Ä/Ae/g; $t =~ s/Ö/Oe/g; $t =~ s/Ü/Ue/g; $t =~ s/ß/ss/g; @text = $hash->{helper}{Text2Speech} if($hash->{helper}{Text2Speech}[0]); push(@text, $t); my @FileTpl = split(" ", $TTS_FileTpl); my @FileTplPc; for(my $i=0; $i<(@FileTpl); $i++) { #splitte bei jedem Template auf @FileTplPc = split(/:/, $FileTpl[$i]); @text = Text2Speech_SplitString(\@text, 100, ":".$FileTplPc[0].":", 1, "as"); # splitte bei bspw: :ring: } @text = Text2Speech_SplitString(\@text, 100, $TTS_Delemiter, $TTS_ForceSplit, $TTS_AddDelemiter); @text = Text2Speech_SplitString(\@text, 100, "(?<=[\\.!?])\\s*", 0, ""); @text = Text2Speech_SplitString(\@text, 100, ",", 0, "al"); @text = Text2Speech_SplitString(\@text, 100, ";", 0, "al"); @text = Text2Speech_SplitString(\@text, 100, "und", 0, "af"); for(my $i=0; $i<(@text); $i++) { for(my $j=0; $j<(@FileTpl); $j++) { # entferne führende und abschließende Leerzeichen aus jedem Textbaustein $text[$i] =~ s/^\s+|\s+$//g; # ersetze die FileTemplates @FileTplPc = split(/:/, $FileTpl[$j]); $text[$i] = $TTS_FileTemplateDir ."/". $FileTplPc[1] if($text[$i] eq ":".$FileTplPc[0].":") } } @{$hash->{helper}{Text2Speech}} = @text; } else { push(@{$hash->{helper}{Text2Speech}}, $t); } } ##################################### # param1: array : Text 2 Speech # param2: string: MaxChar # param3: string: Delemiter # param4: int : 1 -> es wird am Delemiter gesplittet # 0 -> es wird nur gesplittet, wenn Stringlänge länger als MaxChar # param5: string: Add Delemiter to String? [al|af|as|] (AddLast/AddFirst/AddSingle) # # Splittet die Texte aus $hash->{helper}->{Text2Speech} anhand des # Delemiters, wenn die Stringlänge MaxChars übersteigt. # Ist "AddDelemiter" angegeben, so wird der Delemiter an den # String wieder angefügt ##################################### sub Text2Speech_SplitString(@$$$$){ my @text = @{$_[0]}; my $MaxChar = $_[1]; my $Delemiter = $_[2]; my $ForceSplit = $_[3]; my $AddDelemiter = $_[4]; my @newText; for(my $i=0; $i<(@text); $i++) { if((length($text[$i]) <= 100) && (!$ForceSplit)) { #Google kann nur 100zeichen push(@newText, $text[$i]); next; } my @b = split(/$Delemiter/, $text[$i]); for(my $j=0; $j<(@b); $j++) { $b[$j] = $b[$j] . $Delemiter if($AddDelemiter eq "al"); # Am Satzende wieder hinzufügen. $b[$j+1] = $Delemiter . $b[$j+1] if(($AddDelemiter eq "af") && ($b[$j+1])); # Am Satzanfang des nächsten Satzes wieder hinzufügen. push(@newText, $Delemiter) if($AddDelemiter eq "as" && $j>0); # AddSingle: füge Delemiter als EinzelSatz hinzu. Zb. bei FileTemplates push(@newText, $b[$j]); } } return @newText; } ##################################### # param1: hash : Hash # param2: string: Typ (mplayer oder mp3wrap oder ....) # param3: string: Datei # # Erstellt den Commandstring für den Systemaufruf ##################################### sub Text2Speech_BuildMplayerCmdString($$) { my ($hash, $file) = @_; my $cmd; my $TTS_MplayerCall = AttrVal($hash->{NAME}, "TTS_MplayerCall", $mplayer); my $TTS_VolumeAdjust = AttrVal($hash->{NAME}, "TTS_VolumeAdjust", 110); my $verbose = AttrVal($hash->{NAME}, "verbose", 3); if($hash->{VOLUME}) { # per: set volume <..> $mplayerOpts .= " -softvol -softvol-max ". $TTS_VolumeAdjust ." -volume " . $hash->{VOLUME}; } my $AlsaDevice = $hash->{ALSADEVICE}; if($AlsaDevice eq "none") { $AlsaDevice = ""; $mplayerAudioOpts = ""; } my $NoDebug = $mplayerNoDebug; $NoDebug = "" if($verbose >= 5); $cmd = $TTS_MplayerCall . " " . $mplayerAudioOpts . $AlsaDevice . " " .$NoDebug. " " . $mplayerOpts . " " . $file; return $cmd; } ##################################### # param1: hash : Hash # param2: string: Dateiname # param2: string: Text # # Holt den Text aus dem Google Translator als MP3Datei ##################################### sub Text2Speech_Download($$$) { my ($hash, $file, $text) = @_; my $HttpResponse; my $fh; Log3 $hash->{NAME}, 4, "Text2Speech: Hole URL: ". "http://" . $ttsHost . $ttsPath . uri_escape($text); $HttpResponse = GetHttpFile($ttsHost, $ttsPath . uri_escape($text)); $fh = new IO::File ">$file"; if(!defined($fh)) { Log3 $hash->{NAME}, 2, "Text2Speech: mp3 Datei <$file> konnte nicht angelegt werden."; return undef; } $fh->print($HttpResponse); Log3 $hash->{NAME}, 4, "Text2Speech: Schreibe mp3 in die Datei $file mit ".length($HttpResponse)." Bytes"; close($fh); } ##################################### sub Text2Speech_DoIt($) { my ($hash) = @_; my $TTS_CacheFileDir = AttrVal($hash->{NAME}, "TTS_CacheFileDir", "cache"); my $TTS_Ressource = AttrVal($hash->{NAME}, "TTS_Ressource", "Google"); my $verbose = AttrVal($hash->{NAME}, "verbose", 3); my $cmd; if($TTS_Ressource eq "Google") { my $filename; my $file; unless(-e $TTS_CacheFileDir or mkdir $TTS_CacheFileDir) { #Verzeichnis anlegen gescheitert Log3 $hash->{NAME}, 2, "Text2Speech: Angegebenes Verzeichnis $TTS_CacheFileDir konnte erstmalig nicht angelegt werden."; 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; my $TTS_SentenceAppendix = AttrVal($hash->{NAME}, "TTS_SentenceAppendix", undef); #muss eine mp3-Datei sein, ohne Pfadangabe my $TTS_FileTemplateDir = AttrVal($hash->{NAME}, "TTS_FileTemplateDir", "templates"); $TTS_SentenceAppendix = $TTS_CacheFileDir ."/". $TTS_FileTemplateDir ."/". $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($t) . ".mp3";} # falls eine bestimmte mp3-Datei gespielt werden soll $file = $TTS_CacheFileDir."/".$filename; if(-e $file) { push(@Mp3WrapFiles, $file); push(@Mp3WrapText, $t); #Text2Speech_WriteStats($hash, 0, $file, $t); } else {last;} } push(@Mp3WrapFiles, $TTS_SentenceAppendix) if($TTS_SentenceAppendix); if(scalar(@Mp3WrapFiles) >= 2) { 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"; 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); } if(-e $Mp3WrapFile) { $cmd = Text2Speech_BuildMplayerCmdString($hash, $Mp3WrapFile); Log3 $hash->{NAME}, 4, "Text2Speech:" .$cmd; system($cmd); #Text2Speech_WriteStats($hash, 1, $Mp3WrapFile, join(" ", @Mp3WrapText)); } else { Log3 $hash->{NAME}, 2, "Text2Speech: Mp3Wrap Datei konnte nicht angelegt werden."; } return $hash->{NAME} ."|". ($TTS_SentenceAppendix ? scalar(@Mp3WrapFiles)-1: scalar(@Mp3WrapFiles)) ."|". $Mp3WrapFile; } } if(-e $TTS_CacheFileDir."/".$hash->{helper}{Text2Speech}[0]) { # falls eine bestimmte mp3-Datei gespielt werden soll $filename = $hash->{helper}{Text2Speech}[0]; } else { $filename = md5_hex($hash->{helper}{Text2Speech}[0]) . ".mp3"; } $file = $TTS_CacheFileDir."/".$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]); } if(-e $file) { # Datei existiert jetzt $cmd = Text2Speech_BuildMplayerCmdString($hash, $file); Log3 $hash->{NAME}, 4, "Text2Speech:" .$cmd; system($cmd); } return $hash->{NAME}. "|". "1" ."|". $file; } elsif ($TTS_Ressource eq "ESpeak") { $cmd = "sudo espeak -vde+f3 -k5 -s150 \"" . $hash->{helper}{Text2Speech}[0] . "\""; Log3 $hash, 4, "Text2Speech:" .$cmd; system($cmd); } return $hash->{NAME}. "|". "1" ."|". ""; } #################################################### # Rückgabe der Blockingfunktion # param1: HashName # param2: Anzahl der abgearbeiteten Textbausteine # param3: Dateiname der abgespielt wurde #################################################### sub Text2Speech_Done($) { my ($string) = @_; return unless(defined($string)); my @a = split("\\|",$string); my $hash = $defs{shift(@a)}; my $tts_done = shift(@a); my $filename = shift(@a); if($filename) { my @text; for(my $i=0; $i<$tts_done; $i++) { push(@text, $hash->{helper}{Text2Speech}[$i]); } Text2Speech_WriteStats($hash, 1, $filename, join(" ", @text)); } delete($hash->{helper}{RUNNING_PID}); splice(@{$hash->{helper}{Text2Speech}}, 0, $tts_done); # erneutes aufrufen da ev. weiterer Text in der Warteschlange steht if(@{$hash->{helper}{Text2Speech}} > 0) { $hash->{helper}{RUNNING_PID} = BlockingCall("Text2Speech_DoIt", $hash, "Text2Speech_Done", 60, "Text2Speech_AbortFn", $hash); } } ##################################### sub Text2Speech_AbortFn($) { my ($hash) = @_; delete($hash->{helper}{RUNNING_PID}); Log3 $hash->{NAME}, 2, "Text2Speech: BlockingCall for ".$hash->{NAME}." was aborted"; } ##################################### # Hiermit werden Statistken per DbLogModul gesammelt # Wichitg zur Entscheidung welche Dateien aus dem Cache lange # nicht benutzt und somit gelöscht werden koennen. # # param1: hash # param2: int: 0=indirekt (über mp3wrap); 1=direkt abgespielt # param3: string: Datei # param4: string: Text der als mp3 abgespielt wird ##################################### sub Text2Speech_WriteStats($$$$){ my($hash, $typ, $file, $text) = @_; my $DbLogDev; #suche ein DbLogDevice return undef unless($modules{"DbLog"} && $modules{"DbLog"}{"LOADED"}); foreach my $key (keys(%defs)) { if($defs{$key}{TYPE} eq "DbLog") { $DbLogDev = $key; last; } } return undef if($defs{$DbLogDev}{STATE} ne "active"); # muss active sein! # den letzten Value von "Usage" ermitteln um dann die Staistik um 1 zu erhoehen. my @LastValue = DbLog_Get($defs{$DbLogDev}, "", "current", "array", "-", "-", $hash->{NAME} ."|". $file.":Usage"); my $NewValue = 1; $NewValue = $LastValue[0]{value} + 1 if($LastValue[0]); # DbLogHash, DbLogTable, TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT DbLog_Push($defs{$DbLogDev}, "Current", TimeNow(), $hash->{NAME} ."|". $file, $hash->{TYPE}, $text, "Usage", $NewValue, ""); } 1; =pod =begin html

Text2Speech


    Define
      Local : define <name> Text2Speech <alsadevice>
      Remote: define <name> Text2Speech <host>[:<portnr>][:SSL] [portpassword]

      This module converts any text into speech with serveral possible providers. The Device can be defined as locally or remote device.

    • Local Device
        The output will be send to any connected audiodevice. For example external speakers connected per jack or with bluetooth speakers - connected per bluetooth dongle. Its important to install mplayer.
        apt-get install mplayer
        The given alsadevice has to be configured in /etc/asound.conf

        Special AlsaDevice: none
        The internal mplayer command will be without any audio directive if the given alsadevice is none. In this case mplayer is using the standard audiodevice.

        Example:
        define MyTTS Text2Speech hw=0.0
        define MyTTS Text2Speech none

    • Remote Device
        This module can configured as remote-device for client-server Environments. The Client has to be configured as local device.
        Notice: the Name of the locally instance has to be the same!
        • Host: setting up IP-adress
        • PortNr: setting up TelnetPort of FHEM; default: 7072
        • SSL: setting up if connect over SSL; default: no SSL
        • PortPassword: setting up the configured target telnet passwort

        Example:
        define MyTTS Text2Speech 192.168.178.10:7072 fhempasswd define MyTTS Text2Speech 192.168.178.10

Set
  • tts:
    Giving a text to translate into audio.
  • volume:
    Setting up the volume audio response.
    Notice: Only available in locally instances!

Get
    N/A

Attributes
  • TTS_Delemiter
    optional: By using the google engine, its not possible to convert more than 100 characters in a single audio brick. With a delemiter the audio brick will be split at this character. A delemiter must be a single character.!
    By default, ech audio brick will be split at sentence end. Is a single sentence longer than 100 characters, the sentence will be split additionally at comma, semicolon and the word and.
    Notice: Only available in locally instances with Google engine!
  • TTS_Ressource
    optional: Selection of the Translator Engine
    Notice: Only available in locally instances!
    • Google
      Using the Google Engine. It´s nessessary to have internet access. This engine is the recommend engine because the quality is fantastic. This engine is using by default.
    • ESpeak
      Using the ESpeak Engine. Installation of the espeak sourcen is required.
      apt-get install espeak
  • TTS_CacheFileDir
    optional: The downloaded Goole audio bricks are saved in this folder for reusing. No automatically implemented deleting are available.
    Default: cache/
    Notice: Only available in locally instances!
  • TTS_UseMP3Wrap
    optional: To become a liquid audio response its recommend to use the tool mp3wrap. Each downloaded audio bricks are concatinated to a single audio file to play with mplayer.
    Installtion of the mp3wrap source is required.
    apt-get install mp3wrap
    Notice: Only available in locally instances!
  • TTS_MplayerCall
    optional: Setting up the Mplayer system call. The following example is default.
    Example: sudo /usr/bin/mplayer
  • TTS_SentenceAppendix
    Optional: Definition of one mp3-file to append each time of audio response.
    Using of Mp3Wrap is required. The audio bricks has to be downloaded before into CacheFileDir. Example: silence.mp3
  • TTS_FileMapping
    Definition of mp3files with a custom templatedefinition. Separated by space. All templatedefinitions can used in audiobricks by i>tts. The definition must begin and end with e colon. The mp3files must saved in the given directory by TTS_FIleTemplateDir.
    attr myTTS TTS_FileMapping ring:ringtone.mp3 beep:MyBeep.mp3
    set MyTTS tts Attention: This is my ringtone :ring: Its loud?
  • TTS_FileTemplateDir
    Directory to save all mp3-files are defined in TTS_FileMapping und TTS_SentenceAppendix
    Optional, Default: cache/templates
  • readingFnAttributes

  • disable
    If this attribute is activated, the soundoutput will be disabled.
    Possible values: 0 => not disabled , 1 => disabled
    Default Value is 0 (not disabled)

  • verbose
    4: each step will be logged
    5: Additionally the individual debug informations from mplayer and mp3wrap will be logged
=end html =begin html_DE

Text2Speech


    Define
      Local : define <name> Text2Speech <alsadevice>
      Remote: define <name> Text2Speech <host>[:<portnr>][:SSL] [portpassword]

      Das Modul wandelt Text mittels verschiedener Provider/Ressourcen in Sprache um. Dabei kann das Device als Remote oder Lokales Device konfiguriert werden.

    • Local Device
        Die Ausgabe erfolgt auf angeschlossenen Audiodevices, zb. Lautsprecher direkt am Gerät oder per Bluetooth-Lautsprecher per Mplayer. Dazu ist Mplayer zu installieren.
        apt-get install mplayer
        Das angegebene Alsadevice ist in der /etc/asound.conf zu konfigurieren.

        Special AlsaDevice: none
        Ist als Alsa-Device none angegeben, so wird mplayer ohne eine Audiodevice Angabe aufgerufen. Dementsprechend verwendet mplayer das Standard Audio Ausgabedevice.

        Beispiel:
        define MyTTS Text2Speech hw=0.0
        define MyTTS Text2Speech none

    • Remote Device
        Das Modul ist Client-Server fäas bedeutet, das auf der Haupt-FHEM Installation eine Text2Speech-Instanz als Remote definiert wird. Auf dem Client wird Text2Speech als Local definiert. Die Sprachausgabe erfolgt auf der lokalen Instanz.
        Zu beachten ist, das die Text2Speech Instanz (Definition als local Device) auf dem Zieldevice identisch benannt ist.
        • Host: Angabe der IP-Adresse
        • PortNr: Angabe des TelnetPorts von FHEM; default: 7072
        • SSL: Angabe ob der der Zugriff per SSL erfolgen soll oder nicht; default: kein SSL
        • PortPassword: Angabe des in der Ziel-FHEM-Installtion angegebene Telnet Portpasswort

        Beispiel:
        define MyTTS Text2Speech 192.168.178.10:7072 fhempasswd define MyTTS Text2Speech 192.168.178.10

Set
  • tts:
    Setzen eines Textes zur Sprachausgabe.
  • volume:
    Setzen der Ausgabe Lautstärke.
    Achtung: Nur bei einem lokal definierter Text2Speech Instanz möglich!

Get
    N/A

Attribute
  • TTS_Delemiter
    Optional: Wird ein Delemiter angegeben, so wird der Sprachbaustein an dieser Stelle geteilt. Als Delemiter ist nur ein einzelnes Zeichen zulässig. 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!
  • TTS_Ressource
    Optional: Auswahl der Sprachengine
    Achtung: Nur bei einem lokal definierter Text2Speech Instanz möglich!
    • Google
      Nutzung der GoogleSprachengine. Ein Internetzugriff ist notwendig! Aufgrund der Qualität ist der Einsatz diese Engine zu empfehlen und der Standard.
    • ESpeak
      Nutzung der ESpeak Offline Sprachengine. Die Qualitä ist schlechter als die Google Engine. ESpeak ist vor der Nutzung zu installieren.
      apt-get install espeak
  • TTS_CacheFileDir
    Optional: Die per Google geladenen Sprachbausteine werden in diesem Verzeichnis zur Wiedeverwendung abgelegt. Es findet zurZEit keine automatisierte Löschung statt.
    Default: cache/
    Achtung: Nur bei einem lokal definierter Text2Speech Instanz möglich!
  • TTS_UseMP3Wrap
    Optional: Für eine flüssige Sprachausgabe ist es zu empfehlen, die einzelnen vorher per Google geladenen Sprachbausteine zu einem einzelnen Sprachbaustein zusammenfassen zu lassen bevor dieses per Mplayer ausgegeben werden. Dazu muss Mp3Wrap installiert werden.
    apt-get install mp3wrap
    Achtung: Nur bei einem lokal definierter Text2Speech Instanz möglich!
  • TTS_MplayerCall
    Optional: Angabe der Systemaufrufes zu Mplayer. Das folgende Beispiel ist der Standardaufruf.
    Beispiel: sudo /usr/bin/mplayer
  • TTS_SentenceAppendix
    Optional: Angabe einer mp3-Datei die mit jeder Sprachausgabe am Ende ausgegeben wird.
    Voraussetzung ist die Nutzung von MP3Wrap. Die Sprachbausteine müssen bereits als mp3 im CacheFileDir vorliegen. Beispiel: silence.mp3
  • TTS_FileMapping
    Angabe von möglichen MP3-Dateien mit deren Templatedefinition. Getrennt duch Leerzeichen. Die Templatedefinitionen können in den per tts übergebenen Sprachbausteinen verwendet werden und müssen mit einem beginnenden und endenden Doppelpunkt angegeben werden. Die Dateien müssen im Verzeichnis TTS_FIleTemplateDir gespeichert sein.
    attr myTTS TTS_FileMapping ring:ringtone.mp3 beep:MyBeep.mp3
    set MyTTS tts Achtung: hier kommt mein Klingelton :ring: War der laut?
  • TTS_FileTemplateDir
    Verzeichnis, in dem die per TTS_FileMapping und TTS_SentenceAppendix definierten MP3-Dateien gespeichert sind.
    Optional, Default: cache/templates
  • readingFnAttributes

  • disable
    If this attribute is activated, the soundoutput will be disabled.
    Possible values: 0 => not disabled , 1 => disabled
    Default Value is 0 (not disabled)

  • verbose
    4: Alle Zwischenschritte der Verarbeitung werden ausgegeben
    5: Zusätzlich werden auch die Meldungen von Mplayer und Mp3Wrap ausgegeben
=end html_DE =cut