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 $string =~ s/plugin(s?)/plug-in$1/ig;
136 # for all german engines (e.g. for english words)
137 $string =~ s/alkaline/alkalein/ig;
138 $string =~ s/byte(s?)/beit$1/ig;
139 $string =~ s/clip(s?)/klipp$1/ig;
140 $string =~ s/cuesheet/kjuschiet/ig;
141 $string =~ s/dither/didder/ig;
142 $string =~ s/equalizer/iquileiser/ig;
143 $string =~ s/\bflash\b/fläsh/ig;
144 $string =~ s/\bfirmware(s?)\b/firmwer$1/ig;
145 $string =~ s/\bI D 3 tag\b/I D 3 täg/ig; # can't just use "tag" here
146 $string =~ s/\bloudness\b/laudness/ig;
147 $string =~ s/\bunicode\b/unikod/ig;
148 switch
($$tts_object{"name"}) {
149 case
"sapi" { # just for SAPI
150 switch
($$tts_object{"vendor"}) {
152 $string =~ s/alphabet/alfabet/ig;
153 $string =~ s/ampere/amper/ig;
154 $string =~ s/\bdezibel\b/de-zibell/ig;
155 $string =~ s/diddering/didde-ring/ig;
156 $string =~ s/energie\b/ener-gie/ig;
157 $string =~ s/\bnumerisch\b/numehrisch/ig;
158 $string =~ s/\brücklauf\b/rück-lauf/ig;
159 $string =~ s/\bsuchlauf\b/such-lauf/ig;
166 if ($orig ne $string) {
167 printf("%s -> %s\n", $orig, $string) if $verbose;
172 # Produce a wav file of the text given
175 my ($string, $output, $tts_engine_opts, $tts_object) = @_;
177 printf("Generate \"%s\" with %s in file %s\n", $string, $$tts_object{"name"}, $output) if $verbose;
178 switch
($$tts_object{"name"}) {
180 # festival_client lies to us, so we have to do awful soul-eating
181 # work with IPC::open3()
182 $cmd = "festival_client --server localhost --otype riff --ttw --output \"$output\"";
183 print("> $cmd\n") if $verbose;
184 # Open command, and filehandles for STDIN, STDOUT, STDERR
185 my $pid = open3
(*CMD_IN
, *CMD_OUT
, *CMD_ERR
, $cmd);
186 # Put the string to speak into STDIN and close it
187 print(CMD_IN
$string);
189 # Read all output from festival_client (because it LIES TO US)
196 $cmd = "flite $tts_engine_opts -t \"$string\" \"$output\"";
197 print("> $cmd\n") if $verbose;
201 # xxx: $tts_engine_opts isn't used
202 $cmd = "espeak $tts_engine_opts -w $output";
203 print("> $cmd\n") if $verbose;
204 open(ESPEAK
, "| $cmd");
205 print ESPEAK
$string . "\n";
209 print({$$tts_object{"stdin"}} "SPEAK\t$output\t$string\r\n");
212 $cmd = "swift $tts_engine_opts -o $output \"$string\"";
213 print("> $cmd\n") if $verbose;
219 # trim leading / trailing silence from the clip
222 my ($file, $threshold, $tts_object) = @_;
223 printf("Trim \"%s\"\n", $file) if $verbose;
224 if ($$tts_object{"name"} eq "sapi") {
225 my $cmd = $$tts_object{"toolspath"}."wavtrim $file $threshold";
226 print({$$tts_object{"stdin"}} "EXEC\t$cmd\r\n");
229 my $cmd = dirname
($0) . "/wavtrim $file $threshold";
230 print("> $cmd\n") if $verbose;
235 # Encode a wav file into the given destination file
238 my ($input, $output, $encoder, $encoder_opts, $tts_object) = @_;
240 printf("Encode \"%s\" with %s in file %s\n", $input, $encoder, $output) if $verbose;
243 $cmd = "lame $encoder_opts \"$input\" \"$output\"";
246 $cmd = "oggenc $encoder_opts \"$input\" -o \"$output\"";
249 $cmd = "speexenc $encoder_opts \"$input\" \"$output\"";
252 if ($$tts_object{"name"} eq "sapi") {
253 print({$$tts_object{"stdin"}} "EXEC\t$cmd\r\n");
256 print("> $cmd\n") if $verbose;
261 # synchronize the clip generation / processing if it's running in another process
263 my ($tts_object) = @_;
264 if ($$tts_object{"name"} eq "sapi") {
265 print({$$tts_object{"stdin"}} "SYNC\t42\r\n");
266 my $wait = readline($$tts_object{"stdout"});
267 #ignore what's actually returned
271 # Run genlang and create voice clips for each string
274 my ($language, $target, $encoder, $encoder_opts, $tts_engine, $tts_engine_opts) = @_;
275 my $genlang = dirname
($0) . '/genlang';
276 my $english = dirname
($0) . '/../apps/lang/english.lang';
277 my $langfile = dirname
($0) . '/../apps/lang/' . $language . '.lang';
280 my $cmd = "$genlang -o -t=$target -e=$english $langfile 2>/dev/null";
282 open(VOICEFONTIDS
, "> voicefontids");
284 local $| = 1; # make progress indicator work reliably
286 my $tts_object = init_tts
($tts_engine, $tts_engine_opts, $language);
287 print("Generating voice clips");
288 print("\n") if $verbose;
291 print(VOICEFONTIDS
$line);
292 if ($line =~ /^id: (.*)$/) {
295 elsif ($line =~ /^voice: "(.*)"$/) {
297 if ($id !~ /^NOT_USED_.*$/ && $voice ne "") {
298 my $wav = $id . '.wav';
299 my $mp3 = $id . '.mp3';
301 # Print some progress information
302 if (++$i % 10 == 0 and !$verbose) {
306 # Apply corrections to the string
307 $voice = correct_string
($voice, $language, $tts_object);
309 # If we have a pool of snippets, see if the string exists there first
310 if (defined($ENV{'POOL'})) {
311 $pool_file = sprintf("%s/%s-%s.mp3", $ENV{'POOL'},
312 md5_hex
("$voice $tts_engine $tts_engine_opts $encoder_opts"),
315 printf("Re-using %s (%s) from pool\n", $id, $voice) if $verbose;
316 copy
($pool_file, $mp3);
320 # Don't generate MP3 if it already exists (probably from the POOL)
322 if ($id eq "VOICE_PAUSE") {
323 print("Use distributed $wav\n") if $verbose;
324 copy
(dirname
($0)."/VOICE_PAUSE.wav", $wav);
327 voicestring
($voice, $wav, $tts_engine_opts, $tts_object);
328 wavtrim
($wav, 500, $tts_object);
329 # 500 seems to be a reasonable default for now
332 encodewav
($wav, $mp3, $encoder, $encoder_opts, $tts_object);
333 synchronize
($tts_object);
334 if (defined($ENV{'POOL'})) {
335 copy
($mp3, $pool_file);
346 shutdown_tts
($tts_object);
349 # Assemble the voicefile
352 my ($language, $target_id) = @_;
353 my $voicefont = dirname
($0) . '/voicefont';
357 $outfile = sprintf("%s%s.voice", $language, ($i++ == 0 ?
'' : '-'.$i));
358 } while (-f
$outfile);
359 printf("Saving voice file to %s\n", $outfile) if $verbose;
360 my $cmd = "$voicefont 'voicefontids' $target_id ./ $outfile";
361 print("> $cmd\n") if $verbose;
363 print($output) if $verbose;
367 for (glob('*.mp3')) {
370 for (glob('*.wav')) {
382 unless (defined($V) or defined($C)) { print("Missing either -V or -C\n"); $printusage = 1; }
384 unless (defined($t)) { print("Missing -t argument\n"); $printusage = 1; }
385 unless (defined($l)) { print("Missing -l argument\n"); $printusage = 1; }
386 unless (defined($i)) { print("Missing -i argument\n"); $printusage = 1; }
388 elsif (defined($C)) {
389 unless (defined($ARGV[0])) { print "Missing path argument\n"; $printusage = 1; }
391 unless (defined($e)) { print("Missing -e argument\n"); $printusage = 1; }
392 unless (defined($E)) { print("Missing -E argument\n"); $printusage = 1; }
393 unless (defined($s)) { print("Missing -s argument\n"); $printusage = 1; }
394 unless (defined($S)) { print("Missing -S argument\n"); $printusage = 1; }
395 if ($printusage == 1) { printusage
(); exit 1; }
397 $SIG{INT
} = \
&panic_cleanup
;
398 $SIG{KILL
} = \
&panic_cleanup
;
400 if (defined($v) or defined($ENV{'V'})) {
407 printf("Generating voice\n Target: %s\n Language: %s\n Encoder (options): %s (%s)\n TTS Engine (options): %s (%s)\n",
408 $t, $l, $e, $E, $s, $S);
409 generateclips
($l, $t, $e, $E, $s, $S);
414 # xxx: Implement .talk clip generation