98_Text2Speech.pm 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387
  1. ##############################################
  2. # $Id: 98_Text2Speech.pm 13351 2017-02-07 09:46:11Z Tobias.Faust $
  3. #
  4. # 98_Text2Speech.pm
  5. #
  6. # written by Tobias Faust 2013-10-23
  7. # e-mail: tobias dot faust at online dot de
  8. #
  9. ##############################################
  10. ##############################################
  11. # EDITOR=nano
  12. # visudo
  13. # ALL ALL = NOPASSWD: /usr/bin/mplayer
  14. ##############################################
  15. # VoiceRSS: http://www.voicerss.org/api/documentation.aspx
  16. package main;
  17. use strict;
  18. use warnings;
  19. use Blocking;
  20. use IO::File;
  21. use HttpUtils;
  22. use Digest::MD5 qw(md5_hex);
  23. use URI::Escape;
  24. use Data::Dumper;
  25. use lib ('./FHEM/lib', './lib');
  26. sub Text2Speech_OpenDev($);
  27. sub Text2Speech_CloseDev($);
  28. # SetParamName -> Anzahl Paramter
  29. my %sets = (
  30. "tts" => "1",
  31. "volume" => "1"
  32. );
  33. # path to mplayer
  34. my $mplayer = 'sudo /usr/bin/mplayer';
  35. #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"';
  36. my $mplayerOpts = '-nolirc -noconsolecontrols';
  37. my $mplayerNoDebug = '-really-quiet';
  38. my $mplayerAudioOpts = '-ao alsa:device=';
  39. #my $ttsAddr = 'http://translate.google.com/translate_tts?tl=de&q=';
  40. my %ttsHost = ("Google" => "translate.google.com",
  41. "VoiceRSS" => "api.voicerss.org"
  42. );
  43. my %ttsLang = ("Google" => "tl=",
  44. "VoiceRSS" => "hl="
  45. );
  46. my %ttsQuery = ("Google" => "q=",
  47. "VoiceRSS" => "src="
  48. );
  49. my %ttsPath = ("Google" => "/translate_tts?",
  50. "VoiceRSS" => "/?"
  51. );
  52. my %ttsAddon = ("Google" => "client=tw-ob",
  53. "VoiceRSS" => ""
  54. );
  55. my %ttsAPIKey = ("Google" => "", # kein APIKey nötig
  56. "VoiceRSS" => "key="
  57. );
  58. my %ttsUser = ("Google" => "", # kein Username nötig
  59. "VoiceRSS" => "" # kein Username nötig
  60. );
  61. my %ttsSpeed = ("Google" => "",
  62. "VoiceRSS" => "r="
  63. );
  64. my %ttsQuality = ("Google" => "",
  65. "VoiceRSS" => "f="
  66. );
  67. my %ttsMaxChar = ("Google" => 100,
  68. "VoiceRSS" => 300,
  69. "SVOX-pico" => 1000
  70. );
  71. my %language = ("Google" => {"Deutsch" => "de",
  72. "English-US" => "en-us",
  73. "Schwedisch" => "sv",
  74. "Indian-Hindi" => "hi",
  75. "Arabic" => "ar",
  76. "France" => "fr",
  77. "Spain" => "es",
  78. "Italian" => "it",
  79. "Chinese" => "cn"
  80. },
  81. "VoiceRSS" => {"Deutsch" => "de-de",
  82. "English-US" => "en-us",
  83. "Schwedisch" => "sv-se",
  84. "Indian-Hindi" => "en-in", # gibts nicht
  85. "Arabic" => "en-us", # gibts nicht
  86. "France" => "fr-fr",
  87. "Spain" => "es-es",
  88. "Italian" => "it-it",
  89. "Chinese" => "zh-cn"
  90. },
  91. "SVOX-pico" => {"Deutsch" => "de-DE",
  92. "English-US" => "en-US",
  93. "Schwedisch" => "en-US", # gibts nicht
  94. "Indian-Hindi" => "en-US", # gibts nicht
  95. "Arabic" => "en-US", # gibts nicht
  96. "France" => "fr-FR",
  97. "Spain" => "es-ES",
  98. "Italian" => "it-IT",
  99. "Chinese" => "en-US" # gibts nicht
  100. }
  101. );
  102. ##########################
  103. sub Text2Speech_Initialize($)
  104. {
  105. my ($hash) = @_;
  106. $hash->{WriteFn} = "Text2Speech_Write";
  107. $hash->{ReadyFn} = "Text2Speech_Ready";
  108. $hash->{DefFn} = "Text2Speech_Define";
  109. $hash->{SetFn} = "Text2Speech_Set";
  110. $hash->{UndefFn} = "Text2Speech_Undefine";
  111. $hash->{AttrFn} = "Text2Speech_Attr";
  112. $hash->{AttrList} = "disable:0,1".
  113. " TTS_Delemiter".
  114. " TTS_Ressource:ESpeak,SVOX-pico,". join(",", sort keys %ttsHost).
  115. " TTS_APIKey".
  116. " TTS_User".
  117. " TTS_Quality:".
  118. "48khz_16bit_stereo,".
  119. "48khz_16bit_mono,".
  120. "48khz_8bit_stereo,".
  121. "48khz_8bit_mono".
  122. "44khz_16bit_stereo,".
  123. "44khz_16bit_mono,".
  124. "44khz_8bit_stereo,".
  125. "44khz_8bit_mono".
  126. "32khz_16bit_stereo,".
  127. "32khz_16bit_mono,".
  128. "32khz_8bit_stereo,".
  129. "32khz_8bit_mono".
  130. "24khz_16bit_stereo,".
  131. "24khz_16bit_mono,".
  132. "24khz_8bit_stereo,".
  133. "24khz_8bit_mono".
  134. "22khz_16bit_stereo,".
  135. "22khz_16bit_mono,".
  136. "22khz_8bit_stereo,".
  137. "22khz_8bit_mono".
  138. "16khz_16bit_stereo,".
  139. "16khz_16bit_mono,".
  140. "16khz_8bit_stereo,".
  141. "16khz_8bit_mono".
  142. "8khz_16bit_stereo,".
  143. "8khz_16bit_mono,".
  144. "8khz_8bit_stereo,".
  145. "8khz_8bit_mono".
  146. " TTS_Speed:-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10".
  147. " TTS_TimeOut".
  148. " TTS_CacheFileDir".
  149. " TTS_UseMP3Wrap:0,1".
  150. " TTS_MplayerCall".
  151. " TTS_SentenceAppendix".
  152. " TTS_FileMapping".
  153. " TTS_FileTemplateDir".
  154. " TTS_VolumeAdjust".
  155. " TTS_noStatisticsLog:1,0".
  156. " TTS_Language:".join(",", sort keys %{$language{"Google"}}).
  157. " ".$readingFnAttributes;
  158. }
  159. ##########################
  160. # Define <tts> Text2Speech <alsa-device>
  161. # Define <tts> Text2Speech host[:port][:SSL] [portpassword]
  162. ##########################
  163. sub Text2Speech_Define($$)
  164. {
  165. my ($hash, $def) = @_;
  166. my @a = split("[ \t]+", $def);
  167. #$a[0]: Name
  168. #$a[1]: Type/Alias -> Text2Speech
  169. #$a[2]: definition
  170. #$a[3]: optional: portpasswd
  171. if(int(@a) < 3) {
  172. my $msg = "wrong syntax: define <name> Text2Speech <alsa-device>\n".
  173. "see at /etc/asound.conf\n".
  174. "or remote syntax: define <name> Text2Speech host[:port][:SSL] [portpassword]";
  175. Log3 $hash, 2, $msg;
  176. return $msg;
  177. }
  178. my $dev = $a[2];
  179. if($dev =~ m/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/ ) {
  180. # Ein RemoteDevice ist angegeben
  181. # zb: 192.168.10.24:7272:SSL mypasswd
  182. if($dev =~ m/^(.*):SSL$/) {
  183. $dev = $1;
  184. $hash->{SSL} = 1;
  185. }
  186. if($dev !~ m/^.+:[0-9]+$/) { # host:port
  187. $dev = "$dev:7072";
  188. }
  189. $hash->{Host} = $dev;
  190. $hash->{portpassword} = $a[3] if(@a == 4);
  191. $hash->{MODE} = "REMOTE";
  192. } else {
  193. # Ein Alsadevice ist angegeben
  194. # pruefen, ob Alsa-Device in /etc/asound.conf definiert ist
  195. $hash->{MODE} = "DIRECT";
  196. $hash->{ALSADEVICE} = $a[2];
  197. }
  198. BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID}));
  199. delete($hash->{helper}{RUNNING_PID});
  200. $hash->{STATE} = "Initialized";
  201. return undef;
  202. }
  203. #####################################
  204. sub Text2Speech_Undefine($$)
  205. {
  206. my ($hash, $arg) = @_;
  207. RemoveInternalTimer($hash);
  208. BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID}));
  209. Text2Speech_CloseDev($hash);
  210. return undef;
  211. }
  212. ###################################
  213. # Angabe des Delemiters: zb.: +af~
  214. # + -> erzwinge das Trennen, auch wenn Textbaustein < 100Zeichen
  215. # - -> Trenne nur wenn Textbaustein > 100Zeichen
  216. # af -> add first -> füge den Delemiter am Satzanfang wieder hinzu
  217. # al -> add last -> füge den Delemiter am Satzende wieder hinzu
  218. # an -> add nothing -> Delemiter nicht wieder hinzufügen
  219. # ~ -> der Delemiter
  220. ###################################
  221. sub Text2Speech_Attr(@) {
  222. my @a = @_;
  223. my $do = 0;
  224. my $hash = $defs{$a[1]};
  225. my $value = $a[3];
  226. my $TTS_FileTemplateDir = AttrVal($hash->{NAME}, "TTS_FileTemplateDir", "templates");
  227. my $TTS_CacheFileDir = AttrVal($hash->{NAME}, "TTS_CacheFileDir", "cache");
  228. my $TTS_FileMapping = AttrVal($hash->{NAME}, "TTS_FileMapping", ""); # zb, silence:silence.mp3 ring:myringtone.mp3;
  229. if($a[2] eq "TTS_Delemiter" && $a[0] ne "del") {
  230. return "wrong delemiter syntax: [+-]a[lfn]. \n".
  231. " Example 1: +an~\n".
  232. " Example 2: +al." if($value !~ m/^([+-]a[lfn]){0,1}(.){1}$/i);
  233. return "This Attribute is only available in direct mode" if($hash->{MODE} ne "DIRECT");
  234. } elsif ($a[2] eq "TTS_Ressource") {
  235. return "This Attribute is only available in direct mode" if($hash->{MODE} ne "DIRECT");
  236. } elsif ($a[2] eq "TTS_CacheFileDir") {
  237. return "This Attribute is only available in direct mode" if($hash->{MODE} ne "DIRECT");
  238. } elsif ($a[2] eq "TTS_UseMP3Wrap") {
  239. return "This Attribute is only available in direct mode" if($hash->{MODE} ne "DIRECT");
  240. return "Attribute TTS_UseMP3Wrap is required by Attribute TTS_SentenceAppendix! Please delete it first."
  241. if(($a[0] eq "del") && (AttrVal($hash->{NAME}, "TTS_SentenceAppendix", undef)));
  242. } elsif ($a[2] eq "TTS_SentenceAppendix") {
  243. return "This Attribute is only available in direct mode" if($hash->{MODE} ne "DIRECT");
  244. return "Attribute TTS_UseMP3Wrap is required!" unless(AttrVal($hash->{NAME}, "TTS_UseMP3Wrap", undef));
  245. my $file = $TTS_CacheFileDir ."/". $value;
  246. return "File <".$file."> does not exists in CacheFileDir" if(! -e $file);
  247. } elsif ($a[2] eq "TTS_FileTemplateDir") {
  248. # Verzeichnis beginnt mit /, dann absoluter Pfad, sonst Unterpfad von $TTS_CacheFileDir
  249. my $newDir;
  250. if($value =~ m/^\/.*/) { $newDir = $value; } else { $newDir = $TTS_CacheFileDir ."/". $value;}
  251. unless(-e ($newDir) or mkdir ($newDir)) {
  252. #Verzeichnis anlegen gescheitert
  253. return "Could not create directory: <$value>";
  254. }
  255. } elsif ($a[2] eq "TTS_TimeOut") {
  256. return "Only Numbers allowed" if ($value !~ m/[0-9]+/);
  257. } elsif ($a[2] eq "TTS_FileMapping") {
  258. #Bsp: silence:silence.mp3 pling:mypling,mp3
  259. #ueberpruefen, ob mp3 Template existiert
  260. my @FileTpl = split(" ", $TTS_FileMapping);
  261. my $newDir;
  262. for(my $j=0; $j<(@FileTpl); $j++) {
  263. my @FileTplPc = split(/:/, $FileTpl[$j]);
  264. if($TTS_FileTemplateDir =~ m/^\/.*/) { $newDir = $TTS_FileTemplateDir; } else { $newDir = $TTS_CacheFileDir ."/". $TTS_FileTemplateDir;}
  265. return "file does not exist: <".$newDir ."/". $FileTplPc[1] .">"
  266. unless (-e $newDir ."/". $FileTplPc[1]);
  267. }
  268. }
  269. if($a[0] eq "set" && $a[2] eq "disable") {
  270. $do = (!defined($a[3]) || $a[3]) ? 1 : 2;
  271. }
  272. $do = 2 if($a[0] eq "del" && (!$a[2] || $a[2] eq "disable"));
  273. return if(!$do);
  274. $hash->{STATE} = ($do == 1 ? "disabled" : "Initialized");
  275. return undef;
  276. }
  277. #####################################
  278. sub Text2Speech_Ready($)
  279. {
  280. my ($hash) = @_;
  281. return Text2speech_OpenDev($hash, 1);
  282. }
  283. ########################
  284. sub Text2Speech_OpenDev($) {
  285. my ($hash) = @_;
  286. my $dev = $hash->{Host};
  287. my $name = $hash->{NAME};
  288. Log3 $name, 4, "Text2Speech opening $name at $dev";
  289. my $conn;
  290. if($hash->{SSL}) {
  291. eval "use IO::Socket::SSL";
  292. Log3 $name, 1, $@ if($@);
  293. $conn = IO::Socket::SSL->new(PeerAddr => "$dev") if(!$@);
  294. } else {
  295. $conn = IO::Socket::INET->new(PeerAddr => $dev);
  296. }
  297. if(!$conn) {
  298. Log3($name, 3, "Text2Speech: Can't connect to $dev: $!");
  299. $hash->{STATE} = "disconnected";
  300. return "";
  301. } else {
  302. $hash->{STATE} = "Initialized";
  303. }
  304. $hash->{TCPDev} = $conn;
  305. $hash->{FD} = $conn->fileno();
  306. Log3 $name, 4, "Text2Speech device opened ($name)";
  307. syswrite($hash->{TCPDev}, $hash->{portpassword} . "\n")
  308. if($hash->{portpassword});
  309. return undef;
  310. }
  311. ########################
  312. sub Text2Speech_CloseDev($) {
  313. my ($hash) = @_;
  314. my $name = $hash->{NAME};
  315. my $dev = $hash->{Host};
  316. return if(!$dev);
  317. if($hash->{TCPDev}) {
  318. $hash->{TCPDev}->close();
  319. Log3 $hash, 4, "Text2speech Device closed ($name)";
  320. }
  321. delete($hash->{TCPDev});
  322. delete($hash->{FD});
  323. }
  324. ########################
  325. sub Text2Speech_Write($$) {
  326. my ($hash,$msg) = @_;
  327. my $name = $hash->{NAME};
  328. my $dev = $hash->{Host};
  329. #my $call = "set tts tts Das ist ein Test.";
  330. my $call = "set $name $msg";
  331. #Prüfen ob PRESENCE vorhanden und present
  332. my $isPresent = 0;
  333. my $hasPRESENCE = 0;
  334. my $devname="";
  335. if ($hash->{MODE} eq "REMOTE") {
  336. foreach $devname (devspec2array("TYPE=PRESENCE")) {
  337. if (defined $defs{$devname}->{ADDRESS} && $dev) {
  338. if ($dev =~ $defs{$devname}->{ADDRESS}) {
  339. $hasPRESENCE = 1;
  340. $isPresent = 1 if (ReadingsVal($devname,"presence","unknown") eq "present");
  341. last;
  342. }
  343. }
  344. }
  345. }
  346. if ($hasPRESENCE) {
  347. Log3 $hash, 4, "Text2Speech($name): found PRESENCE Device $devname for host: $dev, it\'s state is: ".($isPresent ? "present" : "absent");
  348. Text2Speech_OpenDev($hash) if(!$hash->{TCPDev} && $isPresent);
  349. #lets try again
  350. Text2Speech_OpenDev($hash) if(!$hash->{TCPDev} && $isPresent);
  351. } else {
  352. Log3 $hash, 4, "Text2Speech($name): no proper PRESENCE Device for host: $dev";
  353. Text2Speech_OpenDev($hash) if(!$hash->{TCPDev});
  354. #lets try again
  355. Text2Speech_OpenDev($hash) if(!$hash->{TCPDev});
  356. }
  357. if($hash->{TCPDev}) {
  358. Log3 $hash, 4, "Text2Speech: Write remote message to $dev: $call";
  359. Log3 $hash, 3, "Text2Speech: Could not write remote message ($call) at " .$hash->{Host} if(!defined(syswrite($hash->{TCPDev}, "$call\n")));
  360. Text2Speech_CloseDev($hash);
  361. }
  362. }
  363. ###########################################################################
  364. sub Text2Speech_Set($@)
  365. {
  366. my ($hash, @a) = @_;
  367. my $me = $hash->{NAME};
  368. my $TTS_APIKey = AttrVal($hash->{NAME}, "TTS_APIKey", undef);
  369. my $TTS_User = AttrVal($hash->{NAME}, "TTS_User", undef);
  370. my $TTS_Ressource = AttrVal($hash->{NAME}, "TTS_Ressource", "Google");
  371. my $TTS_TimeOut = AttrVal($hash->{NAME}, "TTS_TimeOut", 60);
  372. return "no set argument specified" if(int(@a) < 2);
  373. return "No APIKey specified" if (length($ttsAPIKey{$TTS_Ressource})>0 && !defined($TTS_APIKey));
  374. return "No Username for TTS Access specified" if (length($ttsUser{$TTS_Ressource})>0 && !defined($TTS_User));
  375. my $cmd = shift(@a); # Dummy
  376. $cmd = shift(@a); # DevName
  377. if(!defined($sets{$cmd})) {
  378. my $r = "Unknown argument $cmd, choose one of ".join(" ",sort keys %sets);
  379. return $r;
  380. }
  381. if($cmd ne "tts") {
  382. return "$cmd needs $sets{$cmd} parameter(s)" if(@a-$sets{$cmd} != 0);
  383. }
  384. # Abbruch falls Disabled
  385. return undef if(AttrVal($hash->{NAME}, "disable", "0") eq "1");
  386. if($cmd eq "tts") {
  387. if($hash->{MODE} eq "DIRECT") {
  388. readingsSingleUpdate($hash, "playing", "1", 1);
  389. Text2Speech_PrepareSpeech($hash, join(" ", @a));
  390. $hash->{helper}{RUNNING_PID} = BlockingCall("Text2Speech_DoIt", $hash, "Text2Speech_Done", $TTS_TimeOut, "Text2Speech_AbortFn", $hash) unless(exists($hash->{helper}{RUNNING_PID}));
  391. } elsif ($hash->{MODE} eq "REMOTE") {
  392. Text2Speech_Write($hash, "tts " . join(" ", @a));
  393. } else {return undef;}
  394. } elsif($cmd eq "volume") {
  395. my $vol = join(" ", @a);
  396. return "volume level expects 0..100 percent" if($vol !~ m/^([0-9]{1,3})$/ or $vol > 100);
  397. if($hash->{MODE} eq "DIRECT") {
  398. $hash->{VOLUME} = $vol if($vol <= 100);
  399. delete($hash->{VOLUME}) if($vol > 100);
  400. } elsif ($hash->{MODE} eq "REMOTE") {
  401. Text2Speech_Write($hash, "volume $vol");
  402. } else {return undef;}
  403. readingsSingleUpdate($hash, "volume", (($vol>100)?0:$vol), 1);
  404. }
  405. return undef;
  406. }
  407. #####################################
  408. # Bereitet den gesamten String vor.
  409. # Bei Nutzung Google wird dieser in ein Array
  410. # zerlegt mit jeweils einer maximalen
  411. # Stringlänge von 100Chars
  412. #
  413. # param1: $hash
  414. # param2: string to speech
  415. #
  416. #####################################
  417. sub Text2Speech_PrepareSpeech($$) {
  418. my ($hash, $t) = @_;
  419. my $me = $hash->{NAME};
  420. my $TTS_Ressource = AttrVal($hash->{NAME}, "TTS_Ressource", "Google");
  421. my $TTS_Delemiter = AttrVal($hash->{NAME}, "TTS_Delemiter", undef);
  422. my $TTS_FileTpl = AttrVal($hash->{NAME}, "TTS_FileMapping", ""); # zb, silence:silence.mp3 ring:myringtone.mp3; im Text: mein Klingelton :ring: ist laut.
  423. my $TTS_FileTemplateDir = AttrVal($hash->{NAME}, "TTS_FileTemplateDir", "templates");
  424. my $TTS_ForceSplit = 0;
  425. my $TTS_AddDelemiter;
  426. if($TTS_Delemiter && $TTS_Delemiter =~ m/^[+-]a[lfn]/i) {
  427. $TTS_ForceSplit = 1 if(substr($TTS_Delemiter,0,1) eq "+");
  428. $TTS_ForceSplit = 0 if(substr($TTS_Delemiter,0,1) eq "-");
  429. $TTS_AddDelemiter = substr($TTS_Delemiter,1,2); # af, al oder an
  430. $TTS_Delemiter = substr($TTS_Delemiter,3);
  431. } elsif (!$TTS_Delemiter) { # Default wenn Attr nicht gesetzt
  432. $TTS_Delemiter = "(?<=[\\.!?])\\s*";
  433. $TTS_ForceSplit = 1;
  434. $TTS_AddDelemiter = "";
  435. }
  436. my @text;
  437. # ersetze Sonderzeichen die Google nicht auflösen kann
  438. if($TTS_Ressource eq "Google") {
  439. $t =~ s/ä/ae/g;
  440. $t =~ s/ö/oe/g;
  441. $t =~ s/ü/ue/g;
  442. $t =~ s/Ä/Ae/g;
  443. $t =~ s/Ö/Oe/g;
  444. $t =~ s/Ü/Ue/g;
  445. $t =~ s/ß/ss/g;
  446. }
  447. @text = $hash->{helper}{Text2Speech} if($hash->{helper}{Text2Speech}[0]); #vorhandene Queue, neuen Sprachbaustein hinten anfuegen
  448. push(@text, $t);
  449. # hole alle Filetemplates
  450. my @FileTpl = split(" ", $TTS_FileTpl);
  451. my @FileTplPc;
  452. # bei Angabe direkter MP3-Files wird hier ein temporäres Template vergeben
  453. for(my $i=0; $i<(@text); $i++) {
  454. @FileTplPc = ($text[$i] =~ /:(\w+.+[mp3|ogg|wav]):/g);
  455. for(my $j=0; $j<(@FileTplPc); $j++) {
  456. my $time = time();
  457. $time =~ s/\.//g;
  458. my $tpl = "FileTpl_".$time."_#".$i; #eindeutige Templatedefinition schaffen
  459. Log3 $hash, 4, "$me: Angabe einer direkten MP3-Datei gefunden: $FileTplPc[$i] => $tpl";
  460. push(@FileTpl, $tpl.":".$FileTplPc[$j]); #zb: FileTpl_123645875_#0:/ring.mp3
  461. $text[$i] =~ s/$FileTplPc[$j]/$tpl/g; # Ersetze die DateiDefinition gegen ein Template
  462. }
  463. }
  464. #iteriere durch die Sprachbausteine und splitte den Text bei den Filetemplates auf
  465. for(my $i=0; $i<(@text); $i++) {
  466. my $cutter = '#!#'; #eindeutigen Cutter als Delemiter bei den Filetemplates vergeben
  467. @FileTplPc = ($text[$i] =~ /:([^:]+):/g);
  468. for(my $j=0; $j<(@FileTplPc); $j++) {
  469. $text[$i] =~ s/:$FileTplPc[$j]:/$cutter$FileTplPc[$j]$cutter/g;
  470. }
  471. @text = Text2Speech_SplitString(\@text, 0, $cutter, 1, "");
  472. }
  473. @text = Text2Speech_SplitString(\@text, $ttsMaxChar{$TTS_Ressource}, $TTS_Delemiter, $TTS_ForceSplit, $TTS_AddDelemiter);
  474. @text = Text2Speech_SplitString(\@text, $ttsMaxChar{$TTS_Ressource}, "(?<=[\\.!?])\\s*", 0, "");
  475. @text = Text2Speech_SplitString(\@text, $ttsMaxChar{$TTS_Ressource}, ",", 0, "al");
  476. @text = Text2Speech_SplitString(\@text, $ttsMaxChar{$TTS_Ressource}, ";", 0, "al");
  477. @text = Text2Speech_SplitString(\@text, $ttsMaxChar{$TTS_Ressource}, "und", 0, "af");
  478. Log3 $hash, 4, "$me: Auflistung der Textbausteine nach Aufbereitung:";
  479. for(my $i=0; $i<(@text); $i++) {
  480. # entferne führende und abschließende Leerzeichen aus jedem Textbaustein
  481. $text[$i] =~ s/^\s+|\s+$//g;
  482. for(my $j=0; $j<(@FileTpl); $j++) {
  483. # ersetze die FileTemplates mit den echten MP3-Files
  484. @FileTplPc = split(/:/, $FileTpl[$j]);
  485. $text[$i] = $TTS_FileTemplateDir ."/". $FileTplPc[1] if($text[$i] eq $FileTplPc[0]);
  486. }
  487. Log3 $hash, 4, "$me: $i => ".$text[$i];
  488. }
  489. @{$hash->{helper}{Text2Speech}} = @text;
  490. }
  491. #####################################
  492. # param1: array : Text 2 Speech
  493. # param2: string: MaxChar
  494. # param3: string: Delemiter
  495. # param4: int : 1 -> es wird am Delemiter gesplittet
  496. # 0 -> es wird nur gesplittet, wenn Stringlänge länger als MaxChar
  497. # param5: string: Add Delemiter to String? [al|af|<empty>] (AddLast/AddFirst)
  498. #
  499. # Splittet die Texte aus $hash->{helper}->{Text2Speech} anhand des
  500. # Delemiters, wenn die Stringlänge MaxChars übersteigt.
  501. # Ist "AddDelemiter" angegeben, so wird der Delemiter an den
  502. # String wieder angefügt
  503. #####################################
  504. sub Text2Speech_SplitString($$$$$){
  505. my @text = @{shift()};
  506. my $MaxChar = shift;
  507. my $Delemiter = shift;
  508. my $ForceSplit = shift;
  509. my $AddDelemiter = shift;
  510. my @newText;
  511. for(my $i=0; $i<(@text); $i++) {
  512. if((length($text[$i]) <= $MaxChar) && (!$ForceSplit)) { #Google kann nur 100zeichen
  513. push(@newText, $text[$i]);
  514. next;
  515. }
  516. my @b = split(/$Delemiter/, $text[$i]);
  517. for(my $j=0; $j<(@b); $j++) {
  518. $b[$j] = $b[$j] . $Delemiter if($AddDelemiter eq "al"); # Am Satzende wieder hinzufügen.
  519. $b[$j+1] = $Delemiter . $b[$j+1] if(($AddDelemiter eq "af") && ($b[$j+1])); # Am Satzanfang des nächsten Satzes wieder hinzufügen.
  520. push(@newText, $b[$j]);
  521. }
  522. }
  523. return @newText;
  524. }
  525. #####################################
  526. # param1: hash : Hash
  527. # param2: string: Typ (mplayer oder mp3wrap oder ....)
  528. # param3: string: Datei
  529. #
  530. # Erstellt den Commandstring für den Systemaufruf
  531. #####################################
  532. sub Text2Speech_BuildMplayerCmdString($$) {
  533. my ($hash, $file) = @_;
  534. my $cmd;
  535. my $TTS_MplayerCall = AttrVal($hash->{NAME}, "TTS_MplayerCall", $mplayer);
  536. my $TTS_VolumeAdjust = AttrVal($hash->{NAME}, "TTS_VolumeAdjust", 110);
  537. my $verbose = AttrVal($hash->{NAME}, "verbose", 3);
  538. if($hash->{VOLUME}) { # per: set <name> volume <..>
  539. $mplayerOpts .= " -softvol -softvol-max ". $TTS_VolumeAdjust ." -volume " . $hash->{VOLUME};
  540. }
  541. my $AlsaDevice = $hash->{ALSADEVICE};
  542. if($AlsaDevice eq "none") {
  543. $AlsaDevice = "";
  544. $mplayerAudioOpts = "";
  545. }
  546. my $NoDebug = $mplayerNoDebug;
  547. $NoDebug = "" if($verbose >= 5);
  548. $cmd = $TTS_MplayerCall . " " . $mplayerAudioOpts . $AlsaDevice . " " .$NoDebug. " " . $mplayerOpts . " " . $file;
  549. my $mp3Duration = Text2Speech_CalcMP3Duration($hash, $file);
  550. BlockingInformParent("Text2Speech_readingsSingleUpdateByName", [$hash->{NAME}, "duration", "$mp3Duration"], 0);
  551. BlockingInformParent("Text2Speech_readingsSingleUpdateByName", [$hash->{NAME}, "endTime", "00:00:00"], 0);
  552. return $cmd;
  553. }
  554. sub Text2Speech_readingsSingleUpdateByName($$$) {
  555. my ($devName, $readingName, $readingVal) = @_;
  556. my $hash = $defs{$devName};
  557. #Log3 $hash, 4, "Text2Speech_readingsSingleUpdateByName: Dev:$devName Reading:$readingName Val:$readingVal";
  558. readingsSingleUpdate($defs{$devName}, $readingName, $readingVal, 1);
  559. }
  560. #####################################
  561. # param1: string: MP3 Datei inkl. Pfad
  562. #
  563. # Ermittelt die Abspieldauer einer MP3 und gibt die Zeit in Sekunden zurück.
  564. # Die Abspielzeit wird auf eine ganze Zahl gerundet
  565. #####################################
  566. sub Text2Speech_CalcMP3Duration($$) {
  567. my $time;
  568. my ($hash, $file) = @_;
  569. eval {
  570. use MP3::Info;
  571. my $tag = get_mp3info($file);
  572. if ($tag && defined($tag->{SECS})) {
  573. $time = int($tag->{SECS}+0.5);
  574. Log3 $hash, 4, "Text2Speech_CalcMP3Duration: $file hat eine Länge von $time Sekunden.";
  575. }
  576. };
  577. if ($@) {
  578. Log3 $hash, 2, "Text2Speech_CalcMP3Duration: Bei der MP3-Längenermittlung ist ein Fehler aufgetreten: $@";
  579. return undef;
  580. }
  581. return $time;
  582. }
  583. #####################################
  584. # param1: hash : Hash
  585. # param2: string: Dateiname
  586. # param2: string: Text
  587. #
  588. # Holt den Text mithilfe der entsprechenden TTS_Ressource
  589. #####################################
  590. sub Text2Speech_Download($$$) {
  591. my ($hash, $file, $text) = @_;
  592. my $TTS_Ressource = AttrVal($hash->{NAME}, "TTS_Ressource", "Google");
  593. my $TTS_User = AttrVal($hash->{NAME}, "TTS_User", "");
  594. my $TTS_APIKey = AttrVal($hash->{NAME}, "TTS_APIKey", "");
  595. my $TTS_Language = AttrVal($hash->{NAME}, "TTS_Language", "Deutsch");
  596. my $TTS_Quality = AttrVal($hash->{NAME}, "TTS_Quality", "");
  597. my $TTS_Speed = AttrVal($hash->{NAME}, "TTS_Speed", "");
  598. my $cmd;
  599. if($TTS_Ressource =~ m/(Google|VoiceRSS)/) {
  600. my $HttpResponse;
  601. my $HttpResponseErr;
  602. my $fh;
  603. my $url = "http://" . $ttsHost{$TTS_Ressource} . $ttsPath{$TTS_Ressource};
  604. $url .= $ttsLang{$TTS_Ressource};
  605. $url .= $language{$TTS_Ressource}{$TTS_Language};
  606. $url .= "&" . $ttsAddon{$TTS_Ressource} if(length($ttsAddon{$TTS_Ressource})>0);
  607. $url .= "&" . $ttsUser{$TTS_Ressource} . $TTS_User if(length($ttsUser{$TTS_Ressource})>0);
  608. $url .= "&" . $ttsAPIKey{$TTS_Ressource} . $TTS_APIKey if(length($ttsAPIKey{$TTS_Ressource})>0);
  609. $url .= "&" . $ttsQuality{$TTS_Ressource} . $TTS_Quality if(length($ttsQuality{$TTS_Ressource})>0);
  610. $url .= "&" . $ttsSpeed{$TTS_Ressource} . $TTS_Speed if(length($ttsSpeed{$TTS_Ressource})>0);
  611. $url .= "&" . $ttsQuery{$TTS_Ressource} . uri_escape($text);
  612. Log3 $hash->{NAME}, 4, "Text2Speech: Verwende ".$TTS_Ressource." OnlineResource zum Download";
  613. Log3 $hash->{NAME}, 4, "Text2Speech: Hole URL: ". $url;
  614. #$HttpResponse = GetHttpFile($ttsHost, $ttsPath . $ttsLang . $language{$TTS_Ressource}{$TTS_Language} . "&" . $ttsQuery . uri_escape($text));
  615. my $param = {
  616. url => $url,
  617. timeout => 5,
  618. hash => $hash, # Muss gesetzt werden, damit die Callback funktion wieder $hash hat
  619. method => "GET" # Lesen von Inhalten
  620. #httpversion => "1.1",
  621. #header => "User-Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22m" # Den Header gemäss abzufragender Daten ändern
  622. #header => "agent: Mozilla/1.22\r\nUser-Agent: Mozilla/1.22"
  623. };
  624. ($HttpResponseErr, $HttpResponse) = HttpUtils_BlockingGet($param);
  625. if(length($HttpResponseErr) > 0) {
  626. Log3 $hash->{NAME}, 3, "Text2Speech: Fehler beim abrufen der Daten von " .$TTS_Ressource. " Translator";
  627. Log3 $hash->{NAME}, 3, "Text2Speech: " . $HttpResponseErr;
  628. }
  629. $fh = new IO::File ">$file";
  630. if(!defined($fh)) {
  631. Log3 $hash->{NAME}, 2, "Text2Speech: mp3 Datei <$file> konnte nicht angelegt werden.";
  632. return undef;
  633. }
  634. $fh->print($HttpResponse);
  635. Log3 $hash->{NAME}, 4, "Text2Speech: Schreibe mp3 in die Datei $file mit ".length($HttpResponse)." Bytes";
  636. close($fh);
  637. } elsif ($TTS_Ressource eq "ESpeak") {
  638. my $FileWav = $file . ".wav";
  639. $cmd = "sudo espeak -vde+f3 -k5 -s150 \"" . $text . "\">\"" . $FileWav . "\"";
  640. Log3 $hash, 4, "Text2Speech:" .$cmd;
  641. system($cmd);
  642. $cmd = "lame \"" . $FileWav . "\" \"" . $file . "\"";
  643. Log3 $hash, 4, "Text2Speech:" .$cmd;
  644. system($cmd);
  645. unlink $FileWav;
  646. } elsif ($TTS_Ressource eq "SVOX-pico") {
  647. my $FileWav = $file . ".wav";
  648. $cmd = "pico2wave --lang=" . $language{$TTS_Ressource}{$TTS_Language} . " --wave=\"" . $FileWav . "\" \"" . $text . "\"";
  649. Log3 $hash, 4, "Text2Speech:" .$cmd;
  650. system($cmd);
  651. $cmd = "lame \"" . $FileWav . "\" \"" . $file . "\"";
  652. Log3 $hash, 4, "Text2Speech:" .$cmd;
  653. system($cmd);
  654. unlink $FileWav;
  655. }
  656. }
  657. #####################################
  658. sub Text2Speech_DoIt($) {
  659. my ($hash) = @_;
  660. my $TTS_CacheFileDir = AttrVal($hash->{NAME}, "TTS_CacheFileDir", "cache");
  661. my $TTS_Ressource = AttrVal($hash->{NAME}, "TTS_Ressource", "Google");
  662. my $TTS_Language = AttrVal($hash->{NAME}, "TTS_Language", "Deutsch");
  663. my $TTS_SentenceAppendix = AttrVal($hash->{NAME}, "TTS_SentenceAppendix", undef); #muss eine mp3-Datei sein, ohne Pfadangabe
  664. my $TTS_FileTemplateDir = AttrVal($hash->{NAME}, "TTS_FileTemplateDir", "templates");
  665. my $myFileTemplateDir;
  666. if($TTS_FileTemplateDir =~ m/^\/.*/) { $myFileTemplateDir = $TTS_FileTemplateDir; } else { $myFileTemplateDir = $TTS_CacheFileDir ."/". $TTS_FileTemplateDir;}
  667. my $verbose = AttrVal($hash->{NAME}, "verbose", 3);
  668. my $cmd;
  669. Log3 $hash->{NAME}, 4, "Verwende TTS Spracheinstellung: ".$TTS_Language;
  670. my $filename;
  671. my $file;
  672. unless(-e $TTS_CacheFileDir or mkdir $TTS_CacheFileDir) {
  673. #Verzeichnis anlegen gescheitert
  674. Log3 $hash->{NAME}, 2, "Text2Speech: Angegebenes Verzeichnis $TTS_CacheFileDir konnte erstmalig nicht angelegt werden.";
  675. return undef;
  676. }
  677. if(AttrVal($hash->{NAME}, "TTS_UseMP3Wrap", 0)) {
  678. # benutze das Tool MP3Wrap um bereits einzelne vorhandene Sprachdateien
  679. # zusammenzuführen. Ziel: sauberer Sprachfluss
  680. my @Mp3WrapFiles;
  681. my @Mp3WrapText;
  682. $TTS_SentenceAppendix = $myFileTemplateDir ."/". $TTS_SentenceAppendix if($TTS_SentenceAppendix);
  683. undef($TTS_SentenceAppendix) if($TTS_SentenceAppendix && (! -e $TTS_SentenceAppendix));
  684. #Abspielliste erstellen
  685. foreach my $t (@{$hash->{helper}{Text2Speech}}) {
  686. 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
  687. $file = $TTS_CacheFileDir."/".$filename;
  688. if(-e $file) {
  689. push(@Mp3WrapFiles, $file);
  690. push(@Mp3WrapText, $t);
  691. } else {last;}
  692. }
  693. push(@Mp3WrapFiles, $TTS_SentenceAppendix) if($TTS_SentenceAppendix);
  694. if(scalar(@Mp3WrapFiles) >= 2) {
  695. Log3 $hash->{NAME}, 4, "Text2Speech: Bearbeite per MP3Wrap jetzt den Text: ". join(" ", @Mp3WrapText);
  696. my $Mp3WrapPrefix = md5_hex(join("|", @Mp3WrapFiles));
  697. my $Mp3WrapFile = $TTS_CacheFileDir ."/". $Mp3WrapPrefix . "_MP3WRAP.mp3";
  698. if(! -e $Mp3WrapFile) {
  699. $cmd = "mp3wrap " .$TTS_CacheFileDir. "/" .$Mp3WrapPrefix. ".mp3 " .join(" ", @Mp3WrapFiles);
  700. $cmd .= " >/dev/null" if($verbose < 5);
  701. Log3 $hash->{NAME}, 4, "Text2Speech: " .$cmd;
  702. system($cmd);
  703. }
  704. if(-e $Mp3WrapFile) {
  705. $cmd = Text2Speech_BuildMplayerCmdString($hash, $Mp3WrapFile);
  706. $cmd .= " >/dev/null" if($verbose < 5);
  707. Log3 $hash->{NAME}, 4, "Text2Speech:" .$cmd;
  708. system($cmd);
  709. } else {
  710. Log3 $hash->{NAME}, 2, "Text2Speech: Mp3Wrap Datei konnte nicht angelegt werden.";
  711. }
  712. return $hash->{NAME} ."|".
  713. ($TTS_SentenceAppendix ? scalar(@Mp3WrapFiles)-1: scalar(@Mp3WrapFiles)) ."|".
  714. $Mp3WrapFile;
  715. }
  716. }
  717. Log3 $hash->{NAME}, 4, "Text2Speech: Bearbeite jetzt den Text: ". $hash->{helper}{Text2Speech}[0];
  718. if(-e $hash->{helper}{Text2Speech}[0]) {
  719. # falls eine bestimmte mp3-Datei mit absolutem Pfad gespielt werden soll
  720. $filename = $hash->{helper}{Text2Speech}[0];
  721. $file = $filename;
  722. Log3 $hash->{NAME}, 4, "Text2Speech: $filename als direkte MP3 Datei erkannt!";
  723. } elsif(-e $TTS_CacheFileDir."/".$hash->{helper}{Text2Speech}[0]) {
  724. # falls eine bestimmte mp3-Datei mit relativem Pfad gespielt werden soll
  725. $filename = $hash->{helper}{Text2Speech}[0];
  726. $file = $TTS_CacheFileDir."/".$filename;
  727. Log3 $hash->{NAME}, 4, "Text2Speech: $filename als direkte MP3 Datei erkannt!";
  728. } else {
  729. $filename = md5_hex($language{$TTS_Ressource}{$TTS_Language} ."|". $hash->{helper}{Text2Speech}[0]) . ".mp3";
  730. $file = $TTS_CacheFileDir."/".$filename;
  731. Log3 $hash->{NAME}, 4, "Text2Speech: Textbaustein ist keine direkte MP3 Datei, ermittle MD5 CacheNamen: $filename";
  732. }
  733. if(! -e $file) { # Datei existiert noch nicht im Cache
  734. Text2Speech_Download($hash, $file, $hash->{helper}{Text2Speech}[0]);
  735. } else {
  736. Log3 $hash->{NAME}, 4, "Text2Speech: $file gefunden, kein Download";
  737. }
  738. if(-e $file) { # Datei existiert jetzt
  739. $cmd = Text2Speech_BuildMplayerCmdString($hash, $file);
  740. $cmd .= " >/dev/null" if($verbose < 5);
  741. Log3 $hash->{NAME}, 4, "Text2Speech:" .$cmd;
  742. system($cmd);
  743. }
  744. return $hash->{NAME}. "|".
  745. "1" ."|".
  746. $file;
  747. }
  748. ####################################################
  749. # Rückgabe der Blockingfunktion
  750. # param1: HashName
  751. # param2: Anzahl der abgearbeiteten Textbausteine
  752. # param3: Dateiname der abgespielt wurde
  753. ####################################################
  754. sub Text2Speech_Done($) {
  755. my ($string) = @_;
  756. return unless(defined($string));
  757. my @a = split("\\|",$string);
  758. my $hash = $defs{shift(@a)};
  759. my $tts_done = shift(@a);
  760. my $filename = shift(@a);
  761. my $TTS_TimeOut = AttrVal($hash->{NAME}, "TTS_TimeOut", 60);
  762. if($filename) {
  763. my @text;
  764. for(my $i=0; $i<$tts_done; $i++) {
  765. push(@text, $hash->{helper}{Text2Speech}[$i]);
  766. }
  767. Text2Speech_WriteStats($hash, 1, $filename, join(" ", @text)) if (AttrVal($hash->{NAME},"TTS_noStatisticsLog", "0")==0);
  768. }
  769. delete($hash->{helper}{RUNNING_PID});
  770. splice(@{$hash->{helper}{Text2Speech}}, 0, $tts_done);
  771. # erneutes aufrufen da ev. weiterer Text in der Warteschlange steht
  772. if(@{$hash->{helper}{Text2Speech}} > 0) {
  773. $hash->{helper}{RUNNING_PID} = BlockingCall("Text2Speech_DoIt", $hash, "Text2Speech_Done", $TTS_TimeOut, "Text2Speech_AbortFn", $hash);
  774. } else {
  775. readingsSingleUpdate($hash, "playing", "0", 1);
  776. }
  777. }
  778. #####################################
  779. sub Text2Speech_AbortFn($) {
  780. my ($hash) = @_;
  781. delete($hash->{helper}{RUNNING_PID});
  782. Log3 $hash->{NAME}, 2, "Text2Speech: BlockingCall for ".$hash->{NAME}." was aborted";
  783. readingsSingleUpdate($hash, "playing", "0", 1);
  784. }
  785. #####################################
  786. # Hiermit werden Statistken per DbLogModul gesammelt
  787. # Wichitg zur Entscheidung welche Dateien aus dem Cache lange
  788. # nicht benutzt und somit gelöscht werden koennen.
  789. #
  790. # param1: hash
  791. # param2: int: 0=indirekt (über mp3wrap); 1=direkt abgespielt
  792. # param3: string: Datei
  793. # param4: string: Text der als mp3 abgespielt wird
  794. #####################################
  795. sub Text2Speech_WriteStats($$$$){
  796. my($hash, $typ, $file, $text) = @_;
  797. my $DbLogDev;
  798. #suche ein DbLogDevice
  799. return undef unless($modules{"DbLog"} && $modules{"DbLog"}{"LOADED"});
  800. foreach my $key (keys(%defs)) {
  801. if($defs{$key}{TYPE} eq "DbLog") {
  802. $DbLogDev = $key;
  803. last;
  804. }
  805. }
  806. return undef if($defs{$DbLogDev}{STATE} !~ m/(active|connected)/); # muss active sein!
  807. my $logdevice = $hash->{NAME} ."|". $file;
  808. # den letzten Value von "Usage" ermitteln um dann die Statistik um 1 zu erhoehen.
  809. my @LastValue = DbLog_Get($defs{$DbLogDev}, "", "current", "array", "-", "-", $logdevice.":Usage");
  810. my $NewValue = 1;
  811. $NewValue = $LastValue[0]{value} + 1 if($LastValue[0]);
  812. my $cmd;
  813. if ($NewValue == 1) {
  814. $cmd = "INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (\
  815. '".TimeNow()."','".$logdevice."','".$hash->{TYPE}."','".$text."','Usage','".$NewValue."','')";
  816. } else {
  817. $cmd = "UPDATE current SET VALUE = '".$NewValue."', TIMESTAMP = '".TimeNow()."' WHERE DEVICE ='".$logdevice."'";
  818. }
  819. DbLog_ExecSQL($defs{$DbLogDev}, $cmd);
  820. }
  821. 1;
  822. =pod
  823. =item helper
  824. =item summary speaks given text via loudspeaker
  825. =item summary_DE wandelt uebergebenen Text um fuer Ausgabe auf Lautsprecher
  826. =begin html
  827. <a name="Text2Speech"></a>
  828. <h3>Text2Speech</h3>
  829. <ul>
  830. <br>
  831. <a name="Text2Speechdefine"></a>
  832. <b>Define</b>
  833. <ul>
  834. <b>Local : </b><code>define &lt;name&gt; Text2Speech &lt;alsadevice&gt;</code><br>
  835. <b>Remote: </b><code>define &lt;name&gt; Text2Speech &lt;host&gt;[:&lt;portnr&gt;][:SSL] [portpassword]</code>
  836. <p>
  837. This module converts any text into speech with serveral possible providers. The Device can be defined as locally
  838. or remote device.
  839. </p>
  840. <li>
  841. <b>Local Device</b><br>
  842. <ul>
  843. The output will be send to any connected audiodevice. For example external speakers connected per jack
  844. or with bluetooth speakers - connected per bluetooth dongle. Its important to install mplayer.<br>
  845. <code>apt-get install mplayer</code><br>
  846. The given alsadevice has to be configured in <code>/etc/asound.conf</code>
  847. <p>
  848. <b>Special AlsaDevice: </b><i>none</i><br>
  849. The internal mplayer command will be without any audio directive if the given alsadevice is <i>none</i>.
  850. In this case mplayer is using the standard audiodevice.
  851. </p>
  852. <p>
  853. <b>Example:</b><br>
  854. <code>define MyTTS Text2Speech hw=0.0</code><br>
  855. <code>define MyTTS Text2Speech none</code>
  856. </p>
  857. </ul>
  858. </li>
  859. <li>
  860. <b>Remote Device</b><br>
  861. <ul>
  862. This module can configured as remote-device for client-server Environments. The Client has to be configured
  863. as local device.<br>
  864. Notice: the Name of the locally instance has to be the same!
  865. <ul>
  866. <li>Host: setting up IP-adress</li>
  867. <li>PortNr: setting up TelnetPort of FHEM; default: 7072</li>
  868. <li>SSL: setting up if connect over SSL; default: no SSL</li>
  869. <li>PortPassword: setting up the configured target telnet passwort</li>
  870. </ul>
  871. <p>
  872. <b>Example:</b><br>
  873. <code>define MyTTS Text2Speech 192.168.178.10:7072 fhempasswd</code>
  874. <code>define MyTTS Text2Speech 192.168.178.10</code>
  875. </p>
  876. If a PRESENCE Device is avilable for the host IP-address, than this will be used to detect the reachability instead of the blocking internal method.
  877. </ul>
  878. </li>
  879. </ul>
  880. </ul>
  881. <a name="Text2Speechset"></a>
  882. <b>Set</b>
  883. <ul>
  884. <li><b>tts</b>:<br>
  885. Giving a text to translate into audio. You play set mp3-files directly. In this case you have to enclosure them with a single colon before and after the declaration.
  886. The files must save under the directory of given <i>TTS_FileTemplateDir</i>.
  887. Please note: The text doesn´t have any colons itself.
  888. </li>
  889. <li><b>volume</b>:<br>
  890. Setting up the volume audio response.<br>
  891. Notice: Only available in locally instances!
  892. </li>
  893. </ul><br>
  894. <a name="Text2Speechget"></a>
  895. <b>Get</b>
  896. <ul>N/A</ul><br>
  897. <a name="Text2Speechattr"></a>
  898. <b>Attributes</b>
  899. <ul>
  900. <li>TTS_Delemiter<br>
  901. optional: By using the google engine, its not possible to convert more than 100 characters in a single audio brick.
  902. With a delemiter the audio brick will be split at this character. A delemiter must be a single character.!<br>
  903. By default, ech audio brick will be split at sentence end. Is a single sentence longer than 100 characters,
  904. the sentence will be split additionally at comma, semicolon and the word <i>and</i>.<br>
  905. Notice: Only available in locally instances with Google engine!
  906. </li>
  907. <li>TTS_Ressource<br>
  908. optional: Selection of the Translator Engine<br>
  909. Notice: Only available in locally instances!
  910. <ul>
  911. <li>Google<br>
  912. Using the Google Engine. It´s nessessary to have internet access. This engine is the recommend engine
  913. because the quality is fantastic. This engine is using by default.
  914. </li>
  915. <li>VoiceRSS<br>
  916. Using the VoiceRSS Engine. Its a free engine till 350 requests per day. If you need more, you have to pay.
  917. It´s nessessary to have internet access. This engine is the 2nd recommend engine
  918. because the quality is also fantastic. To use this engine you need an APIKey (see TTS_APIKey)
  919. </li>
  920. <li>ESpeak<br>
  921. Using the ESpeak Engine. Installation Espeak and lame is required.<br>
  922. <code>apt-get install espeak lame</code>
  923. </li>
  924. <li>SVOX-pico<br>
  925. Using the SVOX-Pico TTS-Engine (from the AOSP).<br>
  926. Installation of the engine and <code>lame</code> is required:<br>
  927. <code>sudo apt-get install libttspico-utils lame</code><br><br>
  928. On ARM/Raspbian the package <code>libttspico-utils</code>,<br>
  929. so you may have to compile it yourself or use the precompiled package from <a target="_blank" href"http://www.robotnet.de/2014/03/20/sprich-freund-und-tritt-ein-sprachausgabe-fur-den-rasberry-pi/">this guide</a>, in short:<br>
  930. <code>sudo apt-get install libpopt-dev lame</code><br>
  931. <code>cd /tmp</code><br>
  932. <code>wget http://www.dr-bischoff.de/raspi/pico2wave.deb</code><br>
  933. <code>sudo dpkg --install pico2wave.deb</code>
  934. </li>
  935. </ul>
  936. </li>
  937. <li>TTS_APIKey<br>
  938. An APIKey its needed if you want to use VoiceRSS. You have to register at the following page:<br>
  939. http://www.voicerss.org/registration.aspx <br>
  940. After this, you will get your personal APIKey.
  941. </li>
  942. <li>TTS_User<br>
  943. Actual without any usage. Needed in case if a TTS Engine need an username and an apikey for each request.
  944. </li>
  945. <li>TTS_CacheFileDir<br>
  946. optional: The downloaded Goole audio bricks are saved in this folder for reusing.
  947. No automatically implemented deleting are available.<br>
  948. Default: <i>cache/</i><br>
  949. Notice: Only available in locally instances!
  950. </li>
  951. <li>TTS_UseMP3Wrap<br>
  952. optional: To become a liquid audio response its recommend to use the tool mp3wrap.
  953. Each downloaded audio bricks are concatinated to a single audio file to play with mplayer.<br>
  954. Installtion of the mp3wrap source is required.<br>
  955. <code>apt-get install mp3wrap</code><br>
  956. Notice: Only available in locally instances!
  957. </li>
  958. <li>TTS_MplayerCall<br>
  959. optional: Setting up the Mplayer system call. The following example is default.<br>
  960. Example: <code>sudo /usr/bin/mplayer</code>
  961. </li>
  962. <li>TTS_SentenceAppendix<br>
  963. Optional: Definition of one mp3-file to append each time of audio response.<br>
  964. Using of Mp3Wrap is required. The audio bricks has to be downloaded before into CacheFileDir.
  965. Example: <code>silence.mp3</code>
  966. </li>
  967. <li>TTS_FileMapping<br>
  968. Definition of mp3files with a custom templatedefinition. Separated by space.
  969. All templatedefinitions can used in audiobricks by <i>tts</i>.
  970. The definition must begin and end with e colon.
  971. The mp3files must saved in the given directory by <i>TTS_FIleTemplateDir</i>.<br>
  972. <code>attr myTTS TTS_FileMapping ring:ringtone.mp3 beep:MyBeep.mp3</code><br>
  973. <code>set MyTTS tts Attention: This is my ringtone :ring: Its loud?</code>
  974. </li>
  975. <li>TTS_FileTemplateDir<br>
  976. Directory to save all mp3-files are defined in <i>TTS_FileMapping</i> und <i>TTS_SentenceAppendix</i><br>
  977. Optional, Default: <code>cache/templates</code>
  978. </li>
  979. <li>TTS_noStatisticsLog<br>
  980. If set to <b>1</b>, it prevents logging statistics to DbLog Devices, default is <b>0</b><br>
  981. But please notice: this looging is important to able to delete longer unused cachefiles. If you disable this
  982. please take care to cleanup your cachedirectory by yourself.
  983. </li>
  984. <li><a href="#readingFnAttributes">readingFnAttributes</a></li><br>
  985. <li><a href="#disable">disable</a><br>
  986. If this attribute is activated, the soundoutput will be disabled.<br>
  987. Possible values: 0 => not disabled , 1 => disabled<br>
  988. Default Value is 0 (not disabled)<br><br>
  989. </li>
  990. <li><a href="#verbose">verbose</a><br>
  991. <b>4:</b> each step will be logged<br>
  992. <b>5:</b> Additionally the individual debug informations from mplayer and mp3wrap will be logged
  993. </li>
  994. </ul><br>
  995. <a name="Text2SpeechExamples"></a>
  996. <b>Beispiele</b>
  997. <ul>
  998. <code>define MyTTS Text2Speech hw=0.0</code><br>
  999. <code>set MyTTS tts Die Alarmanlage ist bereit.</code><br>
  1000. <code>set MyTTS tts :beep.mp3:</code><br>
  1001. <code>set MyTTS tts :mytemplates/alarm.mp3:Die Alarmanlage ist bereit.:ring.mp3:</code><br>
  1002. </ul>
  1003. =end html
  1004. =begin html_DE
  1005. <a name="Text2Speech"></a>
  1006. <h3>Text2Speech</h3>
  1007. <ul>
  1008. <br>
  1009. <a name="Text2Speechdefine"></a>
  1010. <b>Define</b>
  1011. <ul>
  1012. <b>Local : </b><code>define &lt;name&gt; Text2Speech &lt;alsadevice&gt;</code><br>
  1013. <b>Remote: </b><code>define &lt;name&gt; Text2Speech &lt;host&gt;[:&lt;portnr&gt;][:SSL] [portpassword]</code>
  1014. <p>
  1015. Das Modul wandelt Text mittels verschiedener Provider/Ressourcen in Sprache um. Dabei kann das Device als
  1016. Remote oder Lokales Device konfiguriert werden.
  1017. </p>
  1018. <li>
  1019. <b>Local Device</b><br>
  1020. <ul>
  1021. Die Ausgabe erfolgt auf angeschlossenen Audiodevices, zb. Lautsprecher direkt am Ger&auml;t oder per
  1022. Bluetooth-Lautsprecher per Mplayer. Dazu ist Mplayer zu installieren.<br>
  1023. <code>apt-get install mplayer</code><br>
  1024. Das angegebene Alsadevice ist in der <code>/etc/asound.conf</code> zu konfigurieren.
  1025. <p>
  1026. <b>Special AlsaDevice: </b><i>none</i><br>
  1027. Ist als Alsa-Device <i>none</i> angegeben, so wird mplayer ohne eine Audiodevice Angabe aufgerufen.
  1028. Dementsprechend verwendet mplayer das Standard Audio Ausgabedevice.
  1029. </p>
  1030. <p>
  1031. <b>Beispiel:</b><br>
  1032. <code>define MyTTS Text2Speech hw=0.0</code><br>
  1033. <code>define MyTTS Text2Speech none</code>
  1034. </p>
  1035. </ul>
  1036. </li>
  1037. <li>
  1038. <b>Remote Device</b><br>
  1039. <ul>
  1040. Das Modul ist Client-Server f&auml;as bedeutet, das auf der Haupt-FHEM Installation eine Text2Speech-Instanz
  1041. als Remote definiert wird. Auf dem Client wird Text2Speech als Local definiert. Die Sprachausgabe erfolgt auf
  1042. der lokalen Instanz.<br>
  1043. Zu beachten ist, das die Text2Speech Instanz (Definition als local Device) auf dem Zieldevice identisch benannt ist.
  1044. <ul>
  1045. <li>Host: Angabe der IP-Adresse</li>
  1046. <li>PortNr: Angabe des TelnetPorts von FHEM; default: 7072</li>
  1047. <li>SSL: Angabe ob der der Zugriff per SSL erfolgen soll oder nicht; default: kein SSL</li>
  1048. <li>PortPassword: Angabe des in der Ziel-FHEM-Installtion angegebene Telnet Portpasswort</li>
  1049. </ul>
  1050. <p>
  1051. <b>Beispiel:</b><br>
  1052. <code>define MyTTS Text2Speech 192.168.178.10:7072 fhempasswd</code>
  1053. <code>define MyTTS Text2Speech 192.168.178.10</code>
  1054. </p>
  1055. Wenn ein PRESENCE Gerät die Host IP-Adresse abfragt, wird die blockierende interne Prüfung auf Erreichbarkeit umgangen und das PRESENCE Gerät genutzt.
  1056. </ul>
  1057. </li>
  1058. </ul>
  1059. </ul>
  1060. <a name="Text2Speechset"></a>
  1061. <b>Set</b>
  1062. <ul>
  1063. <li><b>tts</b>:<br>
  1064. Setzen eines Textes zur Sprachausgabe. Um mp3-Dateien direkt auszugeben, müssen diese mit f&uuml;hrenden
  1065. und schließenden Doppelpunkten angegebenen sein. Die MP3-Dateien müssen unterhalb des Verzeichnisses <i>TTS_FileTemplateDir</i> gespeichert sein.<br>
  1066. Der Text selbst darf deshalb selbst keine Doppelpunte beinhalten. Siehe Beispiele.
  1067. </li>
  1068. <li><b>volume</b>:<br>
  1069. Setzen der Ausgabe Lautst&auml;rke.<br>
  1070. Achtung: Nur bei einem lokal definierter Text2Speech Instanz m&ouml;glich!
  1071. </li>
  1072. </ul><br>
  1073. <a name="Text2Speechget"></a>
  1074. <b>Get</b>
  1075. <ul>N/A</ul><br>
  1076. <a name="Text2Speechattr"></a>
  1077. <b>Attribute</b>
  1078. <ul>
  1079. <li>TTS_Delemiter<br>
  1080. Optional: Wird ein Delemiter angegeben, so wird der Sprachbaustein an dieser Stelle geteilt.
  1081. Als Delemiter ist nur ein einzelnes Zeichen zul&auml;ssig.
  1082. Hintergrund ist die Tatsache, das die Google Sprachengine nur 100Zeichen zul&auml;sst.<br>
  1083. Im Standard wird nach jedem Satzende geteilt. Ist ein einzelner Satz l&auml;nger als 100 Zeichen,
  1084. so wird zus&auml;tzlich nach Kommata, Semikolon und dem Verbindungswort <i>und</i> geteilt.<br>
  1085. Achtung: Nur bei einem lokal definierter Text2Speech Instanz m&ouml;glich und nur Nutzung der Google Sprachengine relevant!
  1086. </li>
  1087. <li>TTS_Ressource<br>
  1088. Optional: Auswahl der Sprachengine<br>
  1089. Achtung: Nur bei einem lokal definierter Text2Speech Instanz m&ouml;glich!
  1090. <ul>
  1091. <li>Google<br>
  1092. Nutzung der Google Sprachengine. Ein Internetzugriff ist notwendig! Aufgrund der Qualit&auml;t ist der
  1093. Einsatz diese Engine zu empfehlen und der Standard.
  1094. </li>
  1095. <li>VoiceRSS<br>
  1096. Nutzung der VoiceRSS Sprachengine. Die Nutzung ist frei bis zu 350 Anfragen pro Tag.
  1097. Wenn mehr benötigt werden ist ein Bezahlmodell wählbar. Ein Internetzugriff ist notwendig!
  1098. Aufgrund der Qualit&auml;t ist der Einsatz diese Engine ebenfalls zu empfehlen.
  1099. Wenn diese Engine benutzt wird, ist ein APIKey notwendig (siehe TTXS_APIKey)
  1100. </li>
  1101. <li>ESpeak<br>
  1102. Nutzung der ESpeak Offline Sprachengine. Die Qualit&auml; ist schlechter als die Google Engine.
  1103. ESpeak und lame sind vor der Nutzung zu installieren.<br>
  1104. <code>apt-get install espeak lame</code>
  1105. </li>
  1106. <li>SVOX-pico<br>
  1107. Nutzung der SVOX-Pico TTS-Engine (aus dem AOSP).<br>
  1108. Die Sprachengine sowie <code>lame</code> müssen installiert sein:<br>
  1109. <code>sudo apt-get install libttspico-utils lame</code><br><br>
  1110. Für ARM/Raspbian sind die <code>libttspico-utils</code> leider nicht verfügbar,<br>
  1111. deswegen müsste man diese selbst kompilieren oder das vorkompilierte Paket aus <a target="_blank" href"http://www.robotnet.de/2014/03/20/sprich-freund-und-tritt-ein-sprachausgabe-fur-den-rasberry-pi/">dieser Anleitung</a> verwenden, in aller K&uuml;rze:<br>
  1112. <code>sudo apt-get install libpopt-dev lame</code><br>
  1113. <code>cd /tmp</code><br>
  1114. <code>wget http://www.dr-bischoff.de/raspi/pico2wave.deb</code><br>
  1115. <code>sudo dpkg --install pico2wave.deb</code>
  1116. </li>
  1117. </ul>
  1118. </li>
  1119. <li>TTS_APIKey<br>
  1120. Wenn VoiceRSS genutzt wird, ist ein APIKey notwendig. Um diesen zu erhalten ist eine vorherige
  1121. Registrierung notwendig. Anschließend erhält man den APIKey <br>
  1122. http://www.voicerss.org/registration.aspx <br>
  1123. </li>
  1124. <li>TTS_User<br>
  1125. Bisher ohne Benutzung. Falls eine Sprachengine zusätzlich zum APIKey einen Usernamen im Request verlangt.
  1126. </li>
  1127. <li>TTS_CacheFileDir<br>
  1128. Optional: Die per Google geladenen Sprachbausteine werden in diesem Verzeichnis zur Wiedeverwendung abgelegt.
  1129. Es findet zurZEit keine automatisierte L&ouml;schung statt.<br>
  1130. Default: <i>cache/</i><br>
  1131. Achtung: Nur bei einem lokal definierter Text2Speech Instanz m&ouml;glich!
  1132. </li>
  1133. <li>TTS_UseMP3Wrap<br>
  1134. Optional: F&uuml;r eine fl&uuml;ssige Sprachausgabe ist es zu empfehlen, die einzelnen vorher per Google
  1135. geladenen Sprachbausteine zu einem einzelnen Sprachbaustein zusammenfassen zu lassen bevor dieses per
  1136. Mplayer ausgegeben werden. Dazu muss Mp3Wrap installiert werden.<br>
  1137. <code>apt-get install mp3wrap</code><br>
  1138. Achtung: Nur bei einem lokal definierter Text2Speech Instanz m&ouml;glich!
  1139. </li>
  1140. <li>TTS_MplayerCall<br>
  1141. Optional: Angabe der Systemaufrufes zu Mplayer. Das folgende Beispiel ist der Standardaufruf.<br>
  1142. Beispiel: <code>sudo /usr/bin/mplayer</code>
  1143. </li>
  1144. <li>TTS_SentenceAppendix<br>
  1145. Optional: Angabe einer mp3-Datei die mit jeder Sprachausgabe am Ende ausgegeben wird.<br>
  1146. Voraussetzung ist die Nutzung von MP3Wrap. Die Sprachbausteine müssen bereits als mp3 im
  1147. CacheFileDir vorliegen.
  1148. Beispiel: <code>silence.mp3</code>
  1149. </li>
  1150. <li>TTS_FileMapping<br>
  1151. Angabe von m&ouml;glichen MP3-Dateien mit deren Templatedefinition. Getrennt duch Leerzeichen.
  1152. Die Templatedefinitionen können in den per <i>tts</i> &uuml;bergebenen Sprachbausteinen verwendet werden
  1153. und m&uuml;ssen mit einem beginnenden und endenden Doppelpunkt angegeben werden.
  1154. Die Dateien müssen im Verzeichnis <i>TTS_FileTemplateDir</i> gespeichert sein.<br>
  1155. <code>attr myTTS TTS_FileMapping ring:ringtone.mp3 beep:MyBeep.mp3</code><br>
  1156. <code>set MyTTS tts Achtung: hier kommt mein Klingelton :ring: War der laut?</code>
  1157. </li>
  1158. <li>TTS_FileTemplateDir<br>
  1159. Verzeichnis, in dem die per <i>TTS_FileMapping</i> und <i>TTS_SentenceAppendix</i> definierten
  1160. MP3-Dateien gespeichert sind.<br>
  1161. Optional, Default: <code>cache/templates</code>
  1162. </li>
  1163. <li>TTS_VolumeAdjust<br>
  1164. Anhebung der Grundlautstärke zur Anpassung an die angeschlossenen Lautsprecher. <br>
  1165. Default: 110<br>
  1166. <code>attr myTTS TTS_VolumeAdjust 400</code><br>
  1167. </li>
  1168. <li>TTS_noStatisticsLog<br>
  1169. <b>1</b>, verhindert das Loggen von Statistikdaten in DbLog Geräten, default ist <b>0</b><br>
  1170. Bitte zur Beachtung: Das Logging ist wichtig um alte, lang nicht genutzte Cachedateien automatisiert zu loeschen.
  1171. Wenn dieses hier dektiviert wird muss sich der User selbst darum kuemmern.
  1172. </li>
  1173. <li><a href="#readingFnAttributes">readingFnAttributes</a>
  1174. </li><br>
  1175. <li><a href="#disable">disable</a><br>
  1176. If this attribute is activated, the soundoutput will be disabled.<br>
  1177. Possible values: 0 => not disabled , 1 => disabled<br>
  1178. Default Value is 0 (not disabled)<br><br>
  1179. </li>
  1180. <li><a href="#verbose">verbose</a><br>
  1181. <b>4:</b> Alle Zwischenschritte der Verarbeitung werden ausgegeben<br>
  1182. <b>5:</b> Zus&auml;tzlich werden auch die Meldungen von Mplayer und Mp3Wrap ausgegeben
  1183. </li>
  1184. </ul><br>
  1185. <a name="Text2SpeechExamples"></a>
  1186. <b>Beispiele</b>
  1187. <ul>
  1188. <code>define MyTTS Text2Speech hw=0.0</code><br>
  1189. <code>set MyTTS tts Die Alarmanlage ist bereit.</code><br>
  1190. <code>set MyTTS tts :beep.mp3:</code><br>
  1191. <code>set MyTTS tts :mytemplates/alarm.mp3:Die Alarmanlage ist bereit.:ring.mp3:</code><br>
  1192. </ul>
  1193. =end html_DE
  1194. =cut