source: lliurex-tts/trunk/fuentes/simple-google-tts/speak.pl @ 4721

Last change on this file since 4721 was 4721, checked in by joamuran, 3 years ago

included code

  • Property svn:executable set to *
File size: 11.1 KB
Line 
1#!/usr/bin/perl
2
3#--------------------------------------------------
4#
5# Copyright 2012 Michal Fapso (https://github.com/michalfapso)
6#
7# Modified by Glutanimate (https://github.com/glutanimate)
8#
9# Usage:
10# ./speak.pl en input.txt output.mp3
11#
12# Prerequisites:
13# sudo apt-get install libwww-perl libwww-mechanize-perl libhtml-tree-perl sox libsox-fmt-mp3
14#
15# Compiling sox:
16# Older versions of sox package might not have the support for mp3 codec,
17# so just download sox from http://sox.sourceforge.net/
18# install packages libmp3lame-dev libmad0-dev
19# and compile sox
20#
21# List of language code names for Google TTS:
22#       af      Afrikaans
23#       sq      Albanian
24#       am      Amharic
25#       ar      Arabic
26#       hy      Armenian
27#       az      Azerbaijani
28#       eu      Basque
29#       be      Belarusian
30#       bn      Bengali
31#       bh      Bihari
32#       bs      Bosnian
33#       br      Breton
34#       bg      Bulgarian
35#       km      Cambodian
36#       ca      Catalan
37#       zh-CN   Chinese (Simplified)
38#       zh-TW   Chinese (Traditional)
39#       co      Corsican
40#       hr      Croatian
41#       cs      Czech
42#       da      Danish
43#       nl      Dutch
44#       en      English
45#       eo      Esperanto
46#       et      Estonian
47#       fo      Faroese
48#       tl      Filipino
49#       fi      Finnish
50#       fr      French
51#       fy      Frisian
52#       gl      Galician
53#       ka      Georgian
54#       de      German
55#       el      Greek
56#       gn      Guarani
57#       gu      Gujarati
58#       ha      Hausa
59#       iw      Hebrew
60#       hi      Hindi
61#       hu      Hungarian
62#       is      Icelandic
63#       id      Indonesian
64#       ia      Interlingua
65#       ga      Irish
66#       it      Italian
67#       ja      Japanese
68#       jw      Javanese
69#       kn      Kannada
70#       kk      Kazakh
71#       rw      Kinyarwanda
72#       rn      Kirundi
73#       ko      Korean
74#       ku      Kurdish
75#       ky      Kyrgyz
76#       lo      Laothian
77#       la      Latin
78#       lv      Latvian
79#       ln      Lingala
80#       lt      Lithuanian
81#       mk      Macedonian
82#       mg      Malagasy
83#       ms      Malay
84#       ml      Malayalam
85#       mt      Maltese
86#       mi      Maori
87#       mr      Marathi
88#       mo      Moldavian
89#       mn      Mongolian
90#       sr-ME   Montenegrin
91#       ne      Nepali
92#       no      Norwegian
93#       nn      Norwegian (Nynorsk)
94#       oc      Occitan
95#       or      Oriya
96#       om      Oromo
97#       ps      Pashto
98#       fa      Persian
99#       pl      Polish
100#       pt-BR   Portuguese (Brazil)
101#       pt-PT   Portuguese (Portugal)
102#       pa      Punjabi
103#       qu      Quechua
104#       ro      Romanian
105#       rm      Romansh
106#       ru      Russian
107#       gd      Scots Gaelic
108#       sr      Serbian
109#       sh      Serbo-Croatian
110#       st      Sesotho
111#       sn      Shona
112#       sd      Sindhi
113#       si      Sinhalese
114#       sk      Slovak
115#       sl      Slovenian
116#       so      Somali
117#       es      Spanish
118#       su      Sundanese
119#       sw      Swahili
120#       sv      Swedish
121#       tg      Tajik
122#       ta      Tamil
123#       tt      Tatar
124#       te      Telugu
125#       th      Thai
126#       ti      Tigrinya
127#       to      Tonga
128#       tr      Turkish
129#       tk      Turkmen
130#       tw      Twi
131#       ug      Uighur
132#       uk      Ukrainian
133#       ur      Urdu
134#       uz      Uzbek
135#       vi      Vietnamese
136#       cy      Welsh
137#       xh      Xhosa
138#       yi      Yiddish
139#       yo      Yoruba
140#       zu      Zulu
141#--------------------------------------------------
142
143use strict;
144
145use File::Path qw( rmtree );
146use HTTP::Cookies;
147use WWW::Mechanize;
148use LWP;
149use HTML::TreeBuilder;
150use Data::Dumper;
151$Data::Dumper::Maxdepth = 2;
152
153if (scalar(@ARGV) != 3) {
154        print STDERR "Usage: $0 LANGUAGE IN.txt OUT.mp3\n";
155        print STDERR "\n";
156        print STDERR "Examples: \n";
157        print STDERR "    echo \"Hello world\" | ./speak.pl en speech.mp3\n";
158        print STDERR "    cat file.txt       | ./speak.pl en speech.mp3\n";
159        exit;
160}
161
162my $language = $ARGV[0]; # sk | en | cs | ...
163my $textfile_in = $ARGV[1];
164my $all_mp3_out = $ARGV[2];
165
166my $SENTENCE_MAX_CHARACTERS = 100; # limit for google tts
167my $TMP_DIR = "$all_mp3_out.tmp";
168my $RECAPTCHA_URL = "http://www.google.com/sorry/?continue=http%3A%2F%2Ftranslate.google.com%2Ftranslate_tts%3Ftl=en%26q=Your+identity+was+successfuly+confirmed.";
169my $RECAPTCHA_SLEEP_SECONDS = 60;
170my $SYSTEM_WEBBROWSER = "firefox";
171my $MAX_OPENED_FILES = 1000;
172mkdir $TMP_DIR;
173
174my $silence_duration_paragraphs = 0.8;
175my $silence_duration_sentences  = 0.2;
176my $silence_duration_comma      = 0.1;
177my $silence_duration_brace      = 0.1;
178my $silence_duration_semicolon  = 0.2;
179my $silence_duration_words      = 0.05;
180
181my @headers = (
182'Host' => 'translate.google.com',
183'User-Agent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36',
184'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
185'Accept-Language' => 'en-us,en;q=0.5',
186'Accept-Encoding' => 'gzip,deflate',
187'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
188'Keep-Alive' => '300',
189'Connection' => 'keep-alive',
190);
191
192my $cookie_jar = HTTP::Cookies->new(hide_cookie2 => 1);
193
194my $mech = WWW::Mechanize->new(autocheck => 0, cookie_jar => $cookie_jar);
195$mech->agent_alias( 'Windows IE 6' );
196$mech->add_header( "Connection" => "keep-alive" );
197$mech->add_header( "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
198$mech->add_header( "Accept-Language" => "en-us;q=0.5,en;q=0.3");
199
200my $browser = LWP::UserAgent->new;
201
202my $referer = "";
203
204my @all_mp3s = ();
205my $sentence_idx = 0;
206my $tts_requests_counter = 0;
207my $sample_rate = 0;
208# For each input line
209open(IN, $textfile_in) or die("ERROR: Can not open file '$textfile_in'");
210while (my $line = <IN>)
211{
212        chomp($line);
213        print "line: $line\n";
214        # Check for empty lines - paragraphs separator
215        if ($line =~ /^\s*$/) {
216                if ($sample_rate != 0) {
217                        push @all_mp3s, SilenceToMp3($sentence_idx++, $silence_duration_paragraphs, $sample_rate);
218                }
219        } else {
220                my @words = split(/\s+/, $line);
221                my $sentence = "";
222                # For each word
223                for (my $i=0; $i<scalar(@words); $i++) 
224                {
225                        my $word = $words[$i];
226                        $sentence .= " $word"; # add another word to the sentence
227                        my $say = 0;
228                        my $silence_duration = 0.0;
229                        if (length($sentence) >= $SENTENCE_MAX_CHARACTERS) {
230                                # Remove the last word;
231                                $sentence = substr($sentence, 0, length($sentence)-length($word)-1); 
232                                $say = 1;
233                                $silence_duration = $silence_duration_words;
234                                $i --; # one word back
235                        }
236                        # If a separator was found
237                        elsif (substr($word, length($word)-1, 1) =~ /[.!?]/ ) {
238                                $say = 1;
239                                $silence_duration = $silence_duration_sentences;
240                        }
241                        elsif (substr($word, length($word)-1, 1) eq ",") {
242                                $say = 1;
243                                $silence_duration = $silence_duration_comma;
244                        }
245                        elsif (substr($word, length($word)-1, 1) eq ";") {
246                                $say = 1;
247                                $silence_duration = $silence_duration_semicolon;
248                        }
249                        elsif (substr($word, length($word)-1, 1) eq ")") {
250                                $say = 1;
251                                $silence_duration = $silence_duration_brace;
252                        }
253                        # If there are no more words
254                        elsif ($i == scalar(@words)-1) {
255                                $say = 1;
256                                $silence_duration = $silence_duration_words;
257                        }
258
259                        if ($say) {
260                                print "sentence[$tts_requests_counter]: $sentence\n";
261                                my $trimmed_mp3 = TrimSilence( SentenceToMp3($sentence, $sentence_idx++) );
262                                my $trimmed_mp3_sample_rate = `soxi -r $trimmed_mp3`;
263                                chomp($trimmed_mp3_sample_rate);
264                                if ($sample_rate == 0) {
265                                        $sample_rate = $trimmed_mp3_sample_rate;
266                                }
267                                if ($sample_rate != $trimmed_mp3_sample_rate) {
268                                        die("Error: sample rate of '$trimmed_mp3' differs from the sample rate of previous files.");
269                                }
270                                #print "trimmed_mp3_sample_rate: $trimmed_mp3_sample_rate\n";
271                                push @all_mp3s, $trimmed_mp3;
272                                push @all_mp3s, SilenceToMp3($sentence_idx++, $silence_duration, $sample_rate);
273                                $tts_requests_counter ++;
274                                $sentence = ""; # start a new sentence
275                        }
276                }
277        }
278}
279
280print "Concatenate: @all_mp3s\n";
281print "Writing output to $all_mp3_out...";
282JoinMp3s(\@all_mp3s, $all_mp3_out);
283print "done\n";
284rmtree( $TMP_DIR );
285
286sub JoinMp3s() {
287        my $mp3s_ref = shift;
288        my $mp3_out = shift;
289        my $depth = shift || 0;
290
291#       print "JoinMp3s(".join(" ",@{$mp3s_ref}).", $mp3_out, $depth)\n";
292
293        #--------------------------------------------------
294        # Problem if the number of mp3s exceeds the max number of opened files per process
295        # The audio files should be concatenated by smaller chunks
296        #--------------------------------------------------
297        if (scalar(@{$mp3s_ref}) < $MAX_OPENED_FILES) {
298                Exec("sox @{$mp3s_ref} $mp3_out");
299        } else {
300                my @subset_mp3s_out = ();
301                my @subset_mp3s = ();
302                my $sub_idx = 0;
303                for (my $i = 0; $i < scalar(@{$mp3s_ref}); $i++) {
304                        push (@subset_mp3s, $mp3s_ref->[$i]);
305                        if (scalar(@subset_mp3s) >= $MAX_OPENED_FILES-1 || $i == scalar(@{$mp3s_ref})-1) {
306                                my $sub_mp3_out = "$TMP_DIR/subjoin_".$depth."_$sub_idx.mp3"; $sub_idx++;
307                                JoinMp3s(\@subset_mp3s, $sub_mp3_out, $depth+1);
308                                push (@subset_mp3s_out, $sub_mp3_out);
309                                @subset_mp3s = ();
310                        }
311                }
312                JoinMp3s(\@subset_mp3s_out, $mp3_out, $depth+1);
313        }
314}
315
316sub SilenceToMp3() {
317        my $idx = shift;
318        my $duration = shift;
319        my $sample_rate = shift;
320
321        my $mp3_out = sprintf("$TMP_DIR/%04d_sil.mp3", $sentence_idx);
322        Exec("sox -n -r $sample_rate $mp3_out trim 0.0 $duration");
323        return $mp3_out;
324}
325
326sub SentenceToMp3() {
327        my $sentence     = shift;
328        my $sentence_idx = shift;
329
330        $sentence =~ s/ /+/g;
331        if (length($sentence) > $SENTENCE_MAX_CHARACTERS) {
332                die ("ERROR: sentence has more than $SENTENCE_MAX_CHARACTERS characters: '$sentence'");
333        }
334       
335        my $mp3_out = sprintf("$TMP_DIR/%04d.mp3", $sentence_idx);
336
337        my $resp = GetSentenceResponse_CaptchaAware($sentence); # NOT WORKING YET
338
339        if (length($resp) == 0) {
340                print "EMPTY SENTENCE: '$sentence'\n";
341                return "";
342        }
343        open(FILE,">$mp3_out");
344        print FILE $resp;
345        close(FILE);
346        return $mp3_out;
347}
348
349sub GetSentenceResponse() {
350        my $sentence = shift;
351        my $amptk = int(rand(1000000)) . '|' . int(rand(1000000));
352        my $resp = $browser->get("https://translate.google.com/translate_tts?ie=UTF-8&tl=$language&q=$sentence&total=1&idx=0&client=tw-ob&tk=$amptk");
353
354        if ($resp->content =~ "^<!DOCTYPE" ||
355                $resp->content =~ "^<html>") 
356        {
357                die("ERROR: expecting MP3 data, but got a HTML page!");
358        }
359        return $resp->content;
360}
361
362sub GetSentenceResponse_CaptchaAware() {
363        my $sentence = shift;
364
365        my $recaptcha_waiting = 0;
366        print "URL: https://translate.google.com/translate_tts?ie=UTF-8&tl=$language&q=$sentence&total=1&idx=0&client=tw-ob\n";
367        while (1) {
368                my $amptk = int(rand(1000000)) . '|' . int(rand(1000000));
369                my $url = "https://translate.google.com/translate_tts?ie=UTF-8&tl=$language&q=$sentence&total=1&idx=0&client=tw-ob&tk=$amptk";
370                $mech->get($url); $mech->add_header( Referer => "$referer" ); $referer = $url;
371                if ($mech->response()->content() =~ /^<!DOCTYPE/ || 
372                        $mech->response()->content() =~ /^<html>/) 
373                {
374                        my $tree = HTML::TreeBuilder->new();
375                        $tree->parse_content($mech->response()->content());
376                        print "HTML response: ".$tree->as_text()."\n";
377
378                        if (!$recaptcha_waiting) {
379                                $recaptcha_waiting = 1; 
380                                print "We have to wait\n";
381                        }
382                        print ".";
383                        sleep($RECAPTCHA_SLEEP_SECONDS);
384                        next;
385
386                        my $captcha_img_url = "http://translate.google.com".$tree->look_down("_tag", "img")->attr("src");
387                        print "img: ".$captcha_img_url;
388                        my $mech2 = $mech->clone();
389                        $referer = "http://www.google.com/sorry/?continue=$url";
390                        $mech2->add_header( Referer => "$referer" );
391                        $mech2->get($captcha_img_url, ':content_file' => 'captcha.jpg'); 
392                       
393#                       print "\n\n".$mech->response()->content()."\n\n";
394       
395                        print "enter captcha here: ";
396                        my $val = <STDIN>;
397                        print "val: $val\n";
398
399                        # TODO: THIS DOES NOT WORK! MAYBE WAITING FOR HALF AN HOUR WOULD BE BETTER
400                        $mech->add_header( Referer => "$referer" );
401                        my $res = $mech->submit_form(with_fields => {captcha => "$val"});
402                        print "response: ".$res->content."\n";
403                } else {
404#                       print "MP3 response\n";
405                        last;
406                }
407                sleep($RECAPTCHA_SLEEP_SECONDS);
408                PrintWaitingDot();
409        }
410        if ($recaptcha_waiting) { print "\n"; }
411        return $mech->response()->content();
412}
413
414sub PrintWaitingDot() {
415        select STDOUT;
416        print ".";
417        $|=1;
418}
419
420sub TrimSilence() {
421        my $mp3 = shift;
422
423        if ($mp3 eq "") {
424                return "";
425        }
426
427        my $mp3_out = $mp3;
428        $mp3_out =~ s/\.mp3$/_trim.mp3/;
429        Exec("
430        sox $mp3 -p silence 1 0.1 -60d \\
431        | sox -p -p reverse \\
432        | sox -p -p silence 1 0.1 -60d \\
433        | sox -p $mp3_out reverse
434        ");
435        return $mp3_out;
436}
437
438sub Exec() {
439        my $cmd = shift;
440#       print "exec $cmd\n";
441        system $cmd;
442        return;
443}
Note: See TracBrowser for help on using the repository browser.