diff --git a/fhem/FHEM/98_Text2Speech.pm b/fhem/FHEM/98_Text2Speech.pm index ae1678f78..4ea136183 100644 --- a/fhem/FHEM/98_Text2Speech.pm +++ b/fhem/FHEM/98_Text2Speech.pm @@ -1,821 +1,827 @@ -############################################## -# $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". - " ".$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]}; - - 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"); - - if($a[2] eq "TTS_Delemiter") { - my $TTS_Delemiter = $a[3]; - return "wrong delemiter syntax: [+-]a[lfn]. \n". - " Example 1: +an~\n". - " Example 2: +al." if($TTS_Delemiter =~ 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 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,2})$/); - $hash->{VOLUME} = $vol if($vol ne 100); - delete($hash->{VOLUME}) if($vol eq 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_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/ß/sz/g; - - @text = $hash->{helper}{Text2Speech} if($hash->{helper}{Text2Speech}[0]); - push(@text, $t); - - @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"); - - @{$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? [pre|past|] -# -# 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, $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 $verbose = AttrVal($hash->{NAME}, "verbose", 3); - - if($hash->{VOLUME}) { # per: set volume <..> - $mplayerOpts .= " -softvol -softvol-max 110 -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; -} - -##################################### -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 $HttpResponse; - 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 @Mp3WrapArray; - foreach my $t (@{$hash->{helper}{Text2Speech}}) { - $filename = md5_hex($t) . ".mp3"; - $file = $TTS_CacheFileDir."/".$filename; - if(-e $file) { - push(@Mp3WrapArray, $file); - } else {last;} - } - - if(scalar(@Mp3WrapArray) >= 2) { - my $Mp3WrapPrefix = md5_hex(join("|", @Mp3WrapArray)); - my $Mp3WrapFile = $TTS_CacheFileDir ."/". $Mp3WrapPrefix . "_MP3WRAP.mp3"; - - if(! -e $Mp3WrapFile) { - $cmd = "mp3wrap " .$TTS_CacheFileDir. "/" .$Mp3WrapPrefix. ".mp3 " .join(" ", @Mp3WrapArray); - $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); - } else { - Log3 $hash->{NAME}, 2, "Text2Speech: Mp3Wrap Datei konnte nicht angelegt werden."; - } - - return $hash->{NAME} . "|". scalar(@Mp3WrapArray); - } - } - - $filename = md5_hex($hash->{helper}{Text2Speech}[0]) . ".mp3"; - $file = $TTS_CacheFileDir."/".$filename; - - Log3 $hash->{NAME}, 4, "Text2Speech: Bearbeite jetzt String: ". $hash->{helper}{Text2Speech}[0]; - - if(! -e $file) { # Datei existiert noch nicht im Cache - my $fh; - - Log3 $hash->{NAME}, 4, "Text2Speech: Hole URL: ". "http://" . $ttsHost . $ttsPath . uri_escape($hash->{helper}{Text2Speech}[0]); - $HttpResponse = GetHttpFile($ttsHost, $ttsPath . uri_escape($hash->{helper}{Text2Speech}[0])); - - $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); - } else { - Log3 $hash->{NAME}, 4, "Text2Speech: Datei <$file> bereits vorhanden, erneuter Download nicht notwendig"; - } - - if(-e $file) { # Datei existiert jetzt - $cmd = Text2Speech_BuildMplayerCmdString($hash, $file); - Log3 $hash->{NAME}, 4, "Text2Speech:" .$cmd; - system($cmd); - } - - } 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 -#################################################### -sub Text2Speech_Done($) { - my ($string) = @_; - return unless(defined($string)); - - my @a = split("\\|",$string); - my $hash = $defs{shift(@a)}; - my $tts_done = shift(@a); - #Log 1, "SleepDone: " . $string; - - 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"; -} - -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 -
    -
  • volume:
    - Setting up the volume audio response.
    - Notice: Only available in locally instances! -
  • -

- - -Get -
    N/A

- - -Attributes -
    -
  • TTS_Delemiter
    - 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
    - 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
    - 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
    - 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
    - Setting up the Mplayer system call.
    - Example: sudo /usr/bin/mplayer -
  • - -
  • 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 -
    -
  • volume:
    - Setzen der Ausgabe Lautstärke.
    - Achtung: Nur bei einem lokal definierter Text2Speech Instanz möglich! -
  • -

- - -Get -
    N/A

- - -Attribute -
    -
  • TTS_Delemiter
    - 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
    - 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
    - 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
    - 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
    - Angabe der Systemaufrufes zu Mplayer.
    - Beispiel: sudo /usr/bin/mplayer -
  • - -
  • 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 +############################################## +# $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". + " ".$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]}; + + 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"); + + if($a[2] eq "TTS_Delemiter") { + my $TTS_Delemiter = $a[3]; + return "wrong delemiter syntax: [+-]a[lfn]. \n". + " Example 1: +an~\n". + " Example 2: +al." if($TTS_Delemiter =~ 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 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,2})$/); + $hash->{VOLUME} = $vol if($vol ne 100); + delete($hash->{VOLUME}) if($vol eq 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_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/ß/sz/g; + + @text = $hash->{helper}{Text2Speech} if($hash->{helper}{Text2Speech}[0]); + push(@text, $t); + + @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"); + + @{$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? [pre|past|] +# +# 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, $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 $verbose = AttrVal($hash->{NAME}, "verbose", 3); + + if($hash->{VOLUME}) { # per: set volume <..> + $mplayerOpts .= " -softvol -softvol-max 110 -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; +} + +##################################### +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 $HttpResponse; + 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 @Mp3WrapArray; + foreach my $t (@{$hash->{helper}{Text2Speech}}) { + $filename = md5_hex($t) . ".mp3"; + $file = $TTS_CacheFileDir."/".$filename; + if(-e $file) { + push(@Mp3WrapArray, $file); + } else {last;} + } + + if(scalar(@Mp3WrapArray) >= 2) { + my $Mp3WrapPrefix = md5_hex(join("|", @Mp3WrapArray)); + my $Mp3WrapFile = $TTS_CacheFileDir ."/". $Mp3WrapPrefix . "_MP3WRAP.mp3"; + + if(! -e $Mp3WrapFile) { + $cmd = "mp3wrap " .$TTS_CacheFileDir. "/" .$Mp3WrapPrefix. ".mp3 " .join(" ", @Mp3WrapArray); + $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); + } else { + Log3 $hash->{NAME}, 2, "Text2Speech: Mp3Wrap Datei konnte nicht angelegt werden."; + } + + return $hash->{NAME} . "|". scalar(@Mp3WrapArray); + } + } + + $filename = md5_hex($hash->{helper}{Text2Speech}[0]) . ".mp3"; + $file = $TTS_CacheFileDir."/".$filename; + + Log3 $hash->{NAME}, 4, "Text2Speech: Bearbeite jetzt String: ". $hash->{helper}{Text2Speech}[0]; + + if(! -e $file) { # Datei existiert noch nicht im Cache + my $fh; + + Log3 $hash->{NAME}, 4, "Text2Speech: Hole URL: ". "http://" . $ttsHost . $ttsPath . uri_escape($hash->{helper}{Text2Speech}[0]); + $HttpResponse = GetHttpFile($ttsHost, $ttsPath . uri_escape($hash->{helper}{Text2Speech}[0])); + + $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); + } else { + Log3 $hash->{NAME}, 4, "Text2Speech: Datei <$file> bereits vorhanden, erneuter Download nicht notwendig"; + } + + if(-e $file) { # Datei existiert jetzt + $cmd = Text2Speech_BuildMplayerCmdString($hash, $file); + Log3 $hash->{NAME}, 4, "Text2Speech:" .$cmd; + system($cmd); + } + + } 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 +#################################################### +sub Text2Speech_Done($) { + my ($string) = @_; + return unless(defined($string)); + + my @a = split("\\|",$string); + my $hash = $defs{shift(@a)}; + my $tts_done = shift(@a); + #Log 1, "SleepDone: " . $string; + + 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"; +} + +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
    + 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
    + 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
    + 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
    + 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
    + Setting up the Mplayer system call.
    + Example: sudo /usr/bin/mplayer +
  • + +
  • 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
    + 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
    + 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
    + 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
    + 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
    + Angabe der Systemaufrufes zu Mplayer.
    + Beispiel: sudo /usr/bin/mplayer +
  • + +
  • 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 \ No newline at end of file