3 # Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 # Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 # Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 # Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 # Copyright (C) 2007 Jonas Häggqvist
12 # All files in this archive are subject to the GNU General Public License.
13 # See the file COPYING in the source tree root for full license agreement.
15 # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 # KIND, either express or implied.
23 use vars
qw($V $C $t $l $e $E $s $S $i $v);
26 use Digest::MD5 qw(md5_hex);
31 Usage: voice.pl [options] [path to dir]
33 Create voice file. You must also specify -t and -l.
39 Specify which target you want to build voicefile for. Must include
40 any features that target supports.
43 Numeric target id. Needed for voice building.
46 Specify which language you want to build. Without .lang extension.
49 Which encoder to use for voice strings
52 Which encoder options to use when compressing voice strings. Enclose
53 in double quotes if the options include spaces.
56 Which TTS engine to use.
58 -S=<TTS engine options>
59 Options to pass to the TTS engine. Enclose in double quotes if the
60 options include spaces.
68 # Initialize TTS engine. May return an object or value which will be passed
69 # to voicestring and shutdown_tts
72 my ($tts_engine, $tts_engine_opts, $language) = @_;
73 my %ret = ("name" => $tts_engine);
76 print("> festival $tts_engine_opts --server\n") if $verbose;
77 my $pid = open(FESTIVAL_SERVER
, "| festival $tts_engine_opts --server > /dev/null 2>&1");
78 my $dummy = *FESTIVAL_SERVER
; #suppress warning
79 $SIG{INT
} = sub { kill TERM
=> $pid; print("foo"); panic_cleanup
(); };
80 $SIG{KILL
} = sub { kill TERM
=> $pid; print("boo"); panic_cleanup
(); };
84 my $toolsdir = dirname
($0);
85 my $path = `cygpath $toolsdir -a -w`;
88 my $cmd = $path . "sapi_voice.vbs /language:$language $tts_engine_opts";
90 print("> cscript //nologo $cmd\n") if $verbose;
91 my $pid = open2
(*CMD_OUT
, *CMD_IN
, "cscript //nologo $cmd");
92 $SIG{INT
} = sub { print(CMD_IN
"QUIT\r\n"); panic_cleanup
(); };
93 $SIG{KILL
} = sub { print(CMD_IN
"QUIT\r\n"); panic_cleanup
(); };
94 print(CMD_IN
"QUERY\tVENDOR\r\n");
95 my $vendor = readline(CMD_OUT
);
100 "toolspath" => $path,
101 "vendor" => $vendor);
107 # Shutdown TTS engine if necessary.
109 my ($tts_object) = @_;
110 switch
($$tts_object{"name"}) {
112 # Send SIGTERM to festival server
113 kill TERM
=> $$tts_object{"pid"};
116 print({$$tts_object{"stdin"}} "QUIT\r\n");
117 close($$tts_object{"stdin"});
122 # Apply corrections to a voice-string to make it sound better
125 my ($string, $language, $tts_object) = @_;
128 # General for all engines and languages
129 $string =~ s/USB/U S B/g;
130 $string =~ s/ID3/I D 3/g;
133 switch
($$tts_object{"name"}) {
134 case
["sapi","festival"] {
135 $string =~ s/plugin(s?)/plug-in$1/ig;
140 # for all german engines (e.g. for english words)
141 $string =~ s/alkaline/alkalein/ig;
142 $string =~ s/byte(s?)/beit$1/ig;
143 $string =~ s/clip(s?)/klipp$1/ig;
144 $string =~ s/cuesheet/kjuschiet/ig;
145 $string =~ s/dither/didder/ig;
146 $string =~ s/equalizer/iquileiser/ig;
147 $string =~ s/\bflash\b/fläsh/ig;
148 $string =~ s/\bfirmware(s?)\b/firmwer$1/ig;
149 $string =~ s/\bI D 3 tag\b/I D 3 täg/ig; # can't just use "tag" here
150 $string =~ s/\bloudness\b/laudness/ig;
151 $string =~ s/\bunicode\b/unikod/ig;
152 switch
($$tts_object{"name"}) {
153 case
"sapi" { # just for SAPI
154 switch
($$tts_object{"vendor"}) {
156 $string =~ s/alphabet/alfabet/ig;
157 $string =~ s/ampere/amper/ig;
158 $string =~ s/\bdezibel\b/de-zibell/ig;
159 $string =~ s/diddering/didde-ring/ig;
160 $string =~ s/energie\b/ener-gie/ig;
161 $string =~ s/\bnumerisch\b/numehrisch/ig;
162 $string =~ s/\brücklauf\b/rück-lauf/ig;
163 $string =~ s/\bsuchlauf\b/such-lauf/ig;
170 if ($orig ne $string) {
171 printf("%s -> %s\n", $orig, $string) if $verbose;
176 # Produce a wav file of the text given
179 my ($string, $output, $tts_engine_opts, $tts_object) = @_;
181 printf("Generate \"%s\" with %s in file %s\n", $string, $$tts_object{"name"}, $output) if $verbose;
182 switch
($$tts_object{"name"}) {
184 # festival_client lies to us, so we have to do awful soul-eating
185 # work with IPC::open3()
186 $cmd = "festival_client --server localhost --otype riff --ttw --output \"$output\"";
187 print("> $cmd\n") if $verbose;
188 # Open command, and filehandles for STDIN, STDOUT, STDERR
189 my $pid = open3
(*CMD_IN
, *CMD_OUT
, *CMD_ERR
, $cmd);
190 # Put the string to speak into STDIN and close it
191 print(CMD_IN
$string);
193 # Read all output from festival_client (because it LIES TO US)
200 $cmd = "flite $tts_engine_opts -t \"$string\" \"$output\"";
201 print("> $cmd\n") if $verbose;
205 # xxx: $tts_engine_opts isn't used
206 $cmd = "espeak $tts_engine_opts -w $output";
207 print("> $cmd\n") if $verbose;
208 open(ESPEAK
, "| $cmd");
209 print ESPEAK
$string . "\n";
213 print({$$tts_object{"stdin"}} "SPEAK\t$output\t$string\r\n");
216 $cmd = "swift $tts_engine_opts -o $output \"$string\"";
217 print("> $cmd\n") if $verbose;
223 # trim leading / trailing silence from the clip
226 my ($file, $threshold, $tts_object) = @_;
227 printf("Trim \"%s\"\n", $file) if $verbose;
228 if ($$tts_object{"name"} eq "sapi") {
229 my $cmd = $$tts_object{"toolspath"}."wavtrim $file $threshold";
230 print({$$tts_object{"stdin"}} "EXEC\t$cmd\r\n");
233 my $cmd = dirname
($0) . "/wavtrim $file $threshold";
234 print("> $cmd\n") if $verbose;
239 # Encode a wav file into the given destination file
242 my ($input, $output, $encoder, $encoder_opts, $tts_object) = @_;
244 printf("Encode \"%s\" with %s in file %s\n", $input, $encoder, $output) if $verbose;
247 $cmd = "lame $encoder_opts \"$input\" \"$output\"";
250 $cmd = "oggenc $encoder_opts \"$input\" -o \"$output\"";
253 $cmd = "speexenc $encoder_opts \"$input\" \"$output\"";
256 if ($$tts_object{"name"} eq "sapi") {
257 print({$$tts_object{"stdin"}} "EXEC\t$cmd\r\n");
260 print("> $cmd\n") if $verbose;
265 # synchronize the clip generation / processing if it's running in another process
267 my ($tts_object) = @_;
268 if ($$tts_object{"name"} eq "sapi") {
269 print({$$tts_object{"stdin"}} "SYNC\t42\r\n");
270 my $wait = readline($$tts_object{"stdout"});
271 #ignore what's actually returned
275 # Run genlang and create voice clips for each string
278 my ($language, $target, $encoder, $encoder_opts, $tts_engine, $tts_engine_opts) = @_;
279 my $genlang = dirname
($0) . '/genlang';
280 my $english = dirname
($0) . '/../apps/lang/english.lang';
281 my $langfile = dirname
($0) . '/../apps/lang/' . $language . '.lang';
284 my $cmd = "$genlang -o -t=$target -e=$english $langfile 2>/dev/null";
286 open(VOICEFONTIDS
, "> voicefontids");
288 local $| = 1; # make progress indicator work reliably
290 my $tts_object = init_tts
($tts_engine, $tts_engine_opts, $language);
291 print("Generating voice clips");
292 print("\n") if $verbose;
295 print(VOICEFONTIDS
$line);
296 if ($line =~ /^id: (.*)$/) {
299 elsif ($line =~ /^voice: "(.*)"$/) {
301 if ($id !~ /^NOT_USED_.*$/ && $voice ne "") {
302 my $wav = $id . '.wav';
303 my $mp3 = $id . '.mp3';
305 # Print some progress information
306 if (++$i % 10 == 0 and !$verbose) {
310 # Apply corrections to the string
311 $voice = correct_string
($voice, $language, $tts_object);
313 # If we have a pool of snippets, see if the string exists there first
314 if (defined($ENV{'POOL'})) {
315 $pool_file = sprintf("%s/%s-%s.mp3", $ENV{'POOL'},
316 md5_hex
("$voice $tts_engine $tts_engine_opts $encoder_opts"),
319 printf("Re-using %s (%s) from pool\n", $id, $voice) if $verbose;
320 copy
($pool_file, $mp3);
324 # Don't generate MP3 if it already exists (probably from the POOL)
326 if ($id eq "VOICE_PAUSE") {
327 print("Use distributed $wav\n") if $verbose;
328 copy
(dirname
($0)."/VOICE_PAUSE.wav", $wav);
331 voicestring
($voice, $wav, $tts_engine_opts, $tts_object);
332 wavtrim
($wav, 500, $tts_object);
333 # 500 seems to be a reasonable default for now
336 encodewav
($wav, $mp3, $encoder, $encoder_opts, $tts_object);
337 synchronize
($tts_object);
338 if (defined($ENV{'POOL'})) {
339 copy
($mp3, $pool_file);
350 shutdown_tts
($tts_object);
353 # Assemble the voicefile
356 my ($language, $target_id) = @_;
357 my $voicefont = dirname
($0) . '/voicefont';
361 $outfile = sprintf("%s%s.voice", $language, ($i++ == 0 ?
'' : '-'.$i));
362 } while (-f
$outfile);
363 printf("Saving voice file to %s\n", $outfile) if $verbose;
364 my $cmd = "$voicefont 'voicefontids' $target_id ./ $outfile";
365 print("> $cmd\n") if $verbose;
367 print($output) if $verbose;
371 for (glob('*.mp3')) {
374 for (glob('*.wav')) {
386 unless (defined($V) or defined($C)) { print("Missing either -V or -C\n"); $printusage = 1; }
388 unless (defined($t)) { print("Missing -t argument\n"); $printusage = 1; }
389 unless (defined($l)) { print("Missing -l argument\n"); $printusage = 1; }
390 unless (defined($i)) { print("Missing -i argument\n"); $printusage = 1; }
392 elsif (defined($C)) {
393 unless (defined($ARGV[0])) { print "Missing path argument\n"; $printusage = 1; }
395 unless (defined($e)) { print("Missing -e argument\n"); $printusage = 1; }
396 unless (defined($E)) { print("Missing -E argument\n"); $printusage = 1; }
397 unless (defined($s)) { print("Missing -s argument\n"); $printusage = 1; }
398 unless (defined($S)) { print("Missing -S argument\n"); $printusage = 1; }
399 if ($printusage == 1) { printusage
(); exit 1; }
401 $SIG{INT
} = \
&panic_cleanup
;
402 $SIG{KILL
} = \
&panic_cleanup
;
404 if (defined($v) or defined($ENV{'V'})) {
411 printf("Generating voice\n Target: %s\n Language: %s\n Encoder (options): %s (%s)\n TTS Engine (options): %s (%s)\n",
412 $t, $l, $e, $E, $s, $S);
413 generateclips
($l, $t, $e, $E, $s, $S);
418 # xxx: Implement .talk clip generation