A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1#!/usr/bin/perl -s
2# __________ __ ___.
3# Open \______ \ ____ ____ | | _\_ |__ _______ ___
4# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7# \/ \/ \/ \/ \/
8# $Id$
9#
10# Copyright (C) 2007 Jonas Häggqvist
11# Copyright (C) 2020 Solomon Peachy
12#
13# All files in this archive are subject to the GNU General Public License.
14# See the file COPYING in the source tree root for full license agreement.
15#
16# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17# KIND, either express or implied.
18
19use strict;
20use warnings;
21use utf8;
22use File::Basename;
23use File::Copy;
24use vars qw($V $C $t $l $e $E $s $S $i $v $f $F);
25use IPC::Open2;
26use IPC::Open3;
27use Digest::MD5 qw(md5_hex);
28use DirHandle;
29use open ':encoding(utf8)';
30use Encode::Locale;
31use Encode;
32use Unicode::Normalize;
33
34sub printusage {
35 print <<USAGE
36
37Usage: voice.pl [options] [path to dir]
38 -V
39 Create voice file. You must also specify -l, -i, and -t or -f
40
41 -C
42 Create .talk clips.
43
44 -t=<target>
45 Specify which target you want to build voicefile for. Must include
46 any features that target supports.
47
48 -f=<file>
49 Use existing voiceids file
50
51 -i=<target_id>
52 Numeric target id. Needed for voice building.
53
54 -l=<language>
55 Specify which language you want to build. Without .lang extension.
56
57 -e=<encoder>
58 Which encoder to use for voice strings
59
60 -E=<encoder options>
61 Which encoder options to use when compressing voice strings. Enclose
62 in double quotes if the options include spaces.
63
64 -s=<TTS engine>
65 Which TTS engine to use.
66
67 -S=<TTS engine options>
68 Options to pass to the TTS engine. Enclose in double quotes if the
69 options include spaces.
70
71 -F
72 Force the file to be regenerated even if present
73
74 -v
75 Be verbose
76USAGE
77;
78}
79
80my %festival_lang_map = (
81 'english' => 'english',
82 'english-us' => 'english',
83 'espanol' => 'spanish',
84 #'finnish' => 'finnish'
85 #'italiano' => 'italian',
86 #'czech' => 'czech',
87 #'welsh' => 'welsh'
88);
89
90my %gtts_lang_map = (
91 'english' => '-l en -t co.uk', # Always first, it's the golden master
92 'bulgarian' => '-l bg',
93 'chinese-simp' => '-l zh',
94 'czech' => '-l cs',
95 'dansk' => '-l da',
96 'deutsch' => '-l de',
97 'eesti' => '-l et',
98 'english-us' => '-l en -t us',
99 'espanol' => '-l es',
100# 'espanol' => '-l es -t mx',
101 'francais' => '-l fr',
102 'greek' => '-l el',
103 'italiano' => '-l it',
104 'japanese' => '-l ja',
105 'korean' => '-l ko',
106 'latviesu' => '-l lv',
107 'magyar' => '-l hu',
108 'moldoveneste' => '-l ro -t md',
109 'nederlands' => '-l nl',
110 'norsk' => '-l no',
111 'polski' => '-l pl',
112 'portugues-brasileiro' => '-l pt -t br',
113 'romaneste' => '-l ro',
114 'russian' => '-l ru',
115 'slovak' => '-l sk',
116 'srpski' => '-l sr',
117 'svenska' => '-l sv',
118 'turkce' => '-l tr',
119 'ukrainian' => '-l uk',
120 'vietnamese' => '-l vi',
121);
122
123my %espeak_lang_map = (
124 'english' => '-ven-gb -k 5', # Always first, it's the golden master
125 'bulgarian' => '-vbg',
126 'chinese-simp' => '-vzh',
127 'czech' => '-vcs',
128 'dansk' => '-vda',
129 'deutsch' => '-vde',
130 'eesti' => '-vet',
131 'english-us' => '-ven-us -k 5',
132 'espanol' => '-ves',
133# 'espanol' => '-ves -k 6',
134 'francais' => '-vfr-fr',
135 'greek' => '-vel',
136 'italiano' => '-vit',
137 'japanese' => '-vja',
138 'korean' => '-vko',
139 'latviesu' => '-vlv',
140 'magyar' => '-vhu',
141 'moldoveneste' => 'vro',
142 'nederlands' => '-vnl',
143 'norsk' => '-vno',
144 'polski' => '-vpl',
145 'portugues-brasileiro' => '-vpt-br',
146 'romaneste' => '-vro',
147 'russian' => '-vru',
148 'slovak' => '-vsk',
149 'srpski' => '-vsr',
150 'svenska' => '-vsv',
151 'turkce' => '-vtr',
152 'ukrainian' => '-vuk',
153 'vietnamese' => '-vvi',
154 );
155
156my %piper_lang_map = (
157 'english' => 'en_GB-semaine-medium.onnx', # Always first, it's the golden master
158 'bulgarian' => 'bg_BG-dimitar-medium.onnx',
159 'chinese-simp' => 'zh_CN-huayan-medium.onnx',
160 'czech' => 'cs_CZ-jirka-medium.onnx',
161 'dansk' => 'da_DK-talesyntese-medium.onnx',
162 'deutsch' => 'de_DE-thorsten-high.onnx',
163# 'eesti' => '-vet',
164 'english-us' => 'en_US-lessac-high.onnx',
165 'espanol' => 'es_ES-sharvard-medium.onnx',
166# 'espanol' => 'es_MX-claude-high.onnx',
167 'francais' => 'fr_FR-siwis-medium.onnx',
168 'greek' => 'el_GR-rapunzelina-medium.onnx',
169 'italiano' => 'it_IT-paola-medium.onnx',
170# 'japanese' => '-vja',
171# 'korean' => '-vko',
172 'latviesu' => 'lv_LV-aivars-medium.onnx',
173 'magyar' => 'hu_HU-anna-medium.onnx',
174 'nederlands' => 'nl_NL-mls-medium.onnx',
175 'moldoveneste' => 'ro_RO-mihai-medium.onnx',
176 'norsk' => 'no_NO-talesyntese-medium.onnx',
177 'polski' => 'pl_PL-gosia-medium.onnx',
178 'portugues-brasileiro' => 'pt_BR-faber-medium.onnx',
179 'russian' => 'ru_RU-irina-medium.onnx',
180 'romaneste' => 'ro_RO-mihai-medium.onnx',
181 'slovak' => 'sk_SK-lili-medium.onnx',
182 'srpski' => 'sr_RS-serbski_institut-medium.onnx',
183 'svenska' => 'sv_SE-nst-medium.onnx',
184 'turkce' => 'tr_TR-fettah-medium.onnx',
185 'ukrainian' => 'uk_UA-ukrainian_tts-medium',
186 'vietnamese' => 'vi_VN-vais1000-medium.onnx',
187);
188
189my $trim_thresh = 250; # Trim silence if over this, in ms
190my $force = 0; # Don't regenerate files already present
191
192# Initialize TTS engine. May return an object or value which will be passed
193# to voicestring and shutdown_tts
194sub init_tts {
195 our $verbose;
196 my ($tts_engine, $tts_engine_opts, $language) = @_;
197 my %ret = ("name" => $tts_engine);
198 $ret{"format"} = 'wav';
199 $ret{"ttsoptions"} = "";
200
201 # Don't use given/when here - it's not compatible with old perl versions
202 if ($tts_engine eq 'festival') {
203 print("> festival $tts_engine_opts --server\n") if $verbose;
204 # Open command, and filehandles for STDIN, STDOUT, STDERR
205 my $pid = open(FESTIVAL_SERVER, "| festival $tts_engine_opts --server > /dev/null 2>&1");
206 my $dummy = *FESTIVAL_SERVER; #suppress warning
207 $SIG{INT} = sub { kill TERM => $pid; print("foo"); panic_cleanup(); };
208 $SIG{KILL} = sub { kill TERM => $pid; print("boo"); panic_cleanup(); };
209 $ret{"pid"} = $pid;
210 if (defined($festival_lang_map{$language}) && $tts_engine_opts !~ /--language/) {
211 $ret{"ttsoptions"} = "--language $festival_lang_map{$language} ";
212 }
213 } elsif ($tts_engine eq 'piper') {
214 if (defined($piper_lang_map{$language}) && $tts_engine_opts !~ /--model/) {
215 die("Need PIPER_MODEL_DIR\n") if (!defined($ENV{'PIPER_MODEL_DIR'}));
216 $tts_engine_opts = "--model $ENV{PIPER_MODEL_DIR}/$piper_lang_map{$language} ";
217 }
218 my $cmd = "piper $tts_engine_opts --json-input";
219 print("> $cmd\n") if $verbose;
220
221 my $pid = open3(*CMD_IN, *CMD_OUT, *CMD_ERR, $cmd);
222 $SIG{INT} = sub { kill TERM => $pid; print("foo"); panic_cleanup(); };
223 $SIG{KILL} = sub { kill TERM => $pid; print("boo"); panic_cleanup(); };
224 $ret{"pid"} = $pid;
225 binmode(*CMD_IN, ':encoding(utf8)');
226 binmode(*CMD_OUT, ':encoding(utf8)');
227 binmode(*CMD_ERR, ':encoding(utf8)');
228
229 } elsif ($tts_engine eq 'sapi') {
230 my $toolsdir = dirname($0);
231 my $path = `cygpath $toolsdir -a -w`;
232 chomp($path);
233 $path = $path . '\\';
234 my $cmd = $path . "sapi_voice.vbs /language:$language $tts_engine_opts";
235 $cmd =~ s/\\/\\\\/g;
236 print("> cscript //nologo $cmd\n") if $verbose;
237 my $pid = open2(*CMD_OUT, *CMD_IN, "cscript //nologo $cmd");
238 binmode(*CMD_IN, ':encoding(utf16le)');
239 binmode(*CMD_OUT, ':encoding(utf16le)');
240 $SIG{INT} = sub { print(CMD_IN "QUIT\r\n"); panic_cleanup(); };
241 $SIG{KILL} = sub { print(CMD_IN "QUIT\r\n"); panic_cleanup(); };
242 print(CMD_IN "QUERY\tVENDOR\r\n");
243 my $vendor = readline(*CMD_OUT);
244 $vendor =~ s/\r\n//;
245 %ret = (%ret,
246 "stdin" => *CMD_IN,
247 "stdout" => *CMD_OUT,
248 "vendor" => $vendor);
249 } elsif ($tts_engine eq 'gtts') {
250 $ret{"format"} = 'mp3';
251 if (defined($gtts_lang_map{$language}) && $tts_engine_opts !~ /-l/) {
252 $ret{"ttsoptions"} = " $gtts_lang_map{$language} ";
253 }
254 } elsif ($tts_engine eq 'espeak' || $tts_engine eq 'espeak-ng') {
255 if (defined($espeak_lang_map{$language}) && $tts_engine_opts !~ /-v/) {
256 $ret{"ttsoptions"} = " $espeak_lang_map{$language} ";
257 }
258 }
259
260 return \%ret;
261}
262
263# Shutdown TTS engine if necessary.
264sub shutdown_tts {
265 my ($tts_object) = @_;
266 if ($$tts_object{'name'} eq 'festival') {
267 # Send SIGTERM to festival server
268 kill TERM => $$tts_object{"pid"};
269 }
270 elsif ($$tts_object{'name'} eq 'piper') {
271 # Send SIGTERM to piper
272 kill TERM => $$tts_object{"pid"};
273 }
274 elsif ($$tts_object{'name'} eq 'sapi') {
275 print({$$tts_object{"stdin"}} "QUIT\r\n");
276 close($$tts_object{"stdin"});
277 }
278}
279
280# Apply corrections to a voice-string to make it sound better
281sub correct_string {
282 our $verbose;
283 my ($string, $language, $tts_object) = @_;
284 my $orig = $string;
285 my $corrections = $tts_object->{"corrections"};
286
287 foreach (@$corrections) {
288 my $r = "s" . $_->{separator} . $_->{search} . $_->{separator}
289 . $_->{replace} . $_->{separator} . $_->{modifier};
290 eval ('$string =~' . "$r;");
291 }
292 if ($orig ne $string) {
293 printf("%s -> %s\n", $orig, $string) if $verbose;
294 }
295 return $string;
296}
297
298# Produce a wav file of the text given
299sub voicestring {
300 our $verbose;
301 my ($string, $output, $tts_engine_opts, $tts_object) = @_;
302 my $cmd;
303 my $name = $$tts_object{'name'};
304
305 $tts_engine_opts .= $$tts_object{"ttsoptions"};
306
307 # Normalize Unicode
308 $string = NFC($string);
309
310 printf("Generate \"%s\" with %s in file %s\n", $string, $name, $output) if $verbose;
311 if ($name eq 'festival') {
312 # festival_client lies to us, so we have to do awful soul-eating
313 # work with IPC::open3()
314 $cmd = "festival_client --server localhost --otype riff --ttw --output \"$output\"";
315 # Use festival-prolog.scm if it's there (created by user of tools/configure)
316 if (-f "festival-prolog.scm") {
317 $cmd .= " --prolog festival-prolog.scm";
318 }
319 print("> $cmd\n") if $verbose;
320 # Open command, and filehandles for STDIN, STDOUT, STDERR
321 my $pid = open3(*CMD_IN, *CMD_OUT, *CMD_ERR, $cmd);
322 # Put the string to speak into STDIN and close it
323 print(CMD_IN $string);
324 close(CMD_IN);
325 # Read all output from festival_client (because it LIES TO US)
326 while (<CMD_ERR>) {
327 }
328 close(CMD_OUT);
329 close(CMD_ERR);
330 }
331 elsif ($name eq 'piper') {
332 $cmd = "{ \"text\": \"$string\", \"output_file\": \"$output\" }";
333 print(">> $cmd\n") if $verbose;
334 print(CMD_IN "$cmd\n");
335 my $res = <CMD_OUT>;
336 $res = <CMD_ERR>;
337 }
338 elsif ($name eq 'flite') {
339 $cmd = "flite $tts_engine_opts -t \"$string\" \"$output\"";
340 print("> $cmd\n") if $verbose;
341 system($cmd);
342 }
343 elsif ($name eq 'espeak') {
344 $cmd = "espeak $tts_engine_opts -w \"$output\" --stdin";
345 print("> $cmd\n") if $verbose;
346 open(RBSPEAK, "| $cmd");
347 print RBSPEAK $string . "\n";
348 close(RBSPEAK);
349 }
350 elsif ($name eq 'espeak-ng') {
351 $cmd = "espeak-ng $tts_engine_opts -w \"$output\" --stdin";
352 print("> $cmd\n") if $verbose;
353 open(RBSPEAK, "| $cmd");
354 print RBSPEAK $string . "\n";
355 close(RBSPEAK);
356 }
357 elsif ($name eq 'sapi') {
358 print({$$tts_object{"stdin"}} "SPEAK\t$output\t$string\r\n");
359 }
360 elsif ($name eq 'swift') {
361 $cmd = "swift $tts_engine_opts -o \"$output\" \"$string\"";
362 print("> $cmd\n") if $verbose;
363 system($cmd);
364 }
365 elsif ($name eq 'rbspeak') {
366 # xxx: $tts_engine_opts isn't used
367 $cmd = "rbspeak $output";
368 print("> $cmd\n") if $verbose;
369 open(RBSPEAK, "| $cmd");
370 print RBSPEAK $string . "\n";
371 close(RBSPEAK);
372 }
373 elsif ($name eq 'mimic') {
374 $cmd = "mimic $tts_engine_opts -o $output";
375 print("> $cmd\n") if $verbose;
376 open(RBSPEAK, "| $cmd");
377 print RBSPEAK $string . "\n";
378 close(RBSPEAK);
379 }
380 elsif ($name eq 'gtts') {
381 $cmd = "gtts-cli $tts_engine_opts -o $output -";
382 print("> $cmd\n") if $verbose;
383 open(RBSPEAK, "| $cmd");
384 print RBSPEAK $string . "\n";
385 close(RBSPEAK);
386 }
387}
388
389# trim leading / trailing silence from the clip
390sub wavtrim {
391 our $verbose;
392 my ($file, $threshold, $tts_object) = @_;
393 printf("Trim \"%s\"\n", $file) if $verbose;
394 my $cmd = "wavtrim \"$file\" $threshold";
395 if ($$tts_object{"name"} eq "sapi") {
396 print({$$tts_object{"stdin"}} "EXEC\t$cmd\r\n");
397 }
398 else {
399 print("> $cmd\n") if $verbose;
400 `$cmd`;
401 }
402}
403
404# Encode a wav file into the given destination file
405sub encodewav {
406 our $verbose;
407 my ($input, $output, $encoder, $encoder_opts, $tts_object) = @_;
408 printf("Encode \"%s\" with %s in file %s\n", $input, $encoder, $output) if $verbose;
409 my $cmd = "$encoder $encoder_opts \"$input\" \"$output\"";
410 if ($$tts_object{"name"} eq "sapi") {
411 print({$$tts_object{"stdin"}} "EXEC\t$cmd\r\n");
412 }
413 else {
414 print("> $cmd\n") if $verbose;
415 `$cmd`;
416 }
417}
418
419# synchronize the clip generation / processing if it's running in another process
420sub synchronize {
421 my ($tts_object) = @_;
422 if ($$tts_object{"name"} eq "sapi") {
423 print({$$tts_object{"stdin"}} "SYNC\t42\r\n");
424 my $wait = readline($$tts_object{"stdout"});
425 #ignore what's actually returned
426 }
427}
428
429# Run genlang and create voice clips for each string
430sub generateclips {
431 our $verbose;
432 my ($language, $target, $encoder, $encoder_opts, $tts_object, $tts_engine_opts, $existingids) = @_;
433 my $english = dirname($0) . '/../apps/lang/english.lang';
434 my $langfile = dirname($0) . '/../apps/lang/' . $language . '.lang';
435 my $correctionsfile = dirname($0) . '/voice-corrections.txt';
436 my $idfile = "$language.vid";
437 my $updfile = "$language-update.lang";
438 my $id = '';
439 my $voice = '';
440 my $cmd;
441 my $pool_file;
442 my $i = 0;
443 local $| = 1; # make progress indicator work reliably
444
445 # First run the language through an update pass so any missing strings
446 # are backfilled from English. Without this, BADNESS.
447 if ($existingids) {
448 $idfile = $existingids;
449 } else {
450 $cmd = "updatelang $english $langfile $updfile";
451 print("> $cmd\n") if $verbose;
452 system($cmd);
453 $cmd = "genlang -o -t=$target -e=$english $updfile 2>/dev/null > $idfile";
454 print("> $cmd\n") if $verbose;
455 system($cmd);
456 }
457 open(VOICEFONTIDS, " < $idfile");
458
459 # add string corrections to tts_object.
460 my @corrects = ();
461 open(VOICEREGEXP, "<$correctionsfile") or die "Can't open corrections file!\n";
462 while(<VOICEREGEXP>) {
463 # get first character of line
464 my $line = $_;
465 my $separator = substr($_, 0, 1);
466 if($separator =~ m/\s+/) {
467 next;
468 }
469 chomp($line);
470 $line =~ s/^.//g; # remove separator at beginning
471 my ($lang, $engine, $vendor, $search, $replace, $modifier) = split(/$separator/, $line);
472
473 # does language match?
474 if($language !~ m/$lang/) {
475 next;
476 }
477 if($$tts_object{"name"} !~ m/$engine/) {
478 next;
479 }
480 my $v = $$tts_object{"vendor"} || ""; # vendor might be empty in $tts_object
481 if($v !~ m/$vendor/) {
482 next;
483 }
484 push @corrects, {separator => $separator, search => $search, replace => $replace, modifier => $modifier};
485
486 }
487 close(VOICEREGEXP);
488 $tts_object->{corrections} = [@corrects];
489
490 print("Generating voice clips");
491 print("\n") if $verbose;
492 for (<VOICEFONTIDS>) {
493 my $line = $_;
494 if ($line =~ /^id: (.*)$/) {
495 $id = $1;
496 }
497 elsif ($line =~ /^voice: "(.*)"$/) {
498 $voice = $1;
499 if ($id !~ /^NOT_USED_.*$/ && $voice ne "") {
500 my $wav = $id . '.wav';
501 my $enc = $id . '.enc';
502 my $format = $tts_object->{'format'};
503
504 # Print some progress information
505 if (++$i % 10 == 0 and !$verbose) {
506 print(".");
507 }
508
509 # Apply corrections to the string
510 $voice = correct_string($voice, $language, $tts_object);
511
512 # If we have a pool of snippets, see if the string exists there first
513 if (defined($ENV{'POOL'})) {
514 $pool_file = sprintf("%s/%s-%s.enc", $ENV{'POOL'},
515 md5_hex(Encode::encode_utf8("$voice ". $tts_object->{"name"}." $tts_engine_opts ".$tts_object->{"ttsoptions"}." $encoder_opts")),
516 $language);
517 if (-f $pool_file) {
518 printf("Re-using %s (%s) from pool\n", $id, $voice) if $verbose;
519# system("touch $pool_file"); # So we know it's still being used.
520 copy($pool_file, $enc);
521 }
522 }
523
524 # Don't generate encoded file if it already exists (probably from the POOL)
525 if (! -f $enc && !$force) {
526 if ($id eq "VOICE_PAUSE") {
527 print("Use distributed $wav\n") if $verbose;
528 copy(dirname($0)."/VOICE_PAUSE.wav", $wav);
529 } else {
530 voicestring($voice, $wav, $tts_engine_opts, $tts_object);
531 if ($format eq "wav") {
532 wavtrim($wav, $trim_thresh, $tts_object);
533 }
534 }
535 # Convert from mp3 to wav so we can use rbspeex
536 if ($format eq "mp3") {
537 system("ffmpeg -loglevel 0 -i $wav $id$wav");
538 rename("$id$wav","$wav");
539 $format = "wav";
540 }
541 if ($format eq "wav" || $id eq "VOICE_PAUSE") {
542 encodewav($wav, $enc, $encoder, $encoder_opts, $tts_object);
543 } else {
544 copy($wav, $enc);
545 }
546
547 synchronize($tts_object);
548 if (defined($ENV{'POOL'})) {
549 copy($enc, $pool_file);
550 }
551 unlink($wav);
552 }
553
554 # Special cases
555 if ($id eq "VOICE_INVALID_VOICE_FILE") {
556 copy ($enc, "InvalidVoice_$language.talk");
557 }
558 if ($id eq "VOICE_LANG_NAME") {
559 copy ($enc, "$language.lng.talk");
560 }
561
562 $voice = "";
563 $id = "";
564 }
565 }
566 }
567 close(VOICEFONTIDS);
568
569 print("\n");
570
571 unlink($updfile) if (-f $updfile);
572}
573
574# Assemble the voicefile
575sub createvoice {
576 our $verbose;
577 my ($language, $target_id, $existingids) = @_;
578 my $outfile = "";
579 my $vfile = "$language.vid";
580
581 if ($existingids) {
582 $vfile = $existingids;
583 }
584 $outfile = sprintf("%s.voice", $language);
585 printf("Saving voice file to %s\n", $outfile) if $verbose;
586 my $cmd = "voicefont '$vfile' $target_id ./ $outfile";
587 print("> $cmd\n") if $verbose;
588 my $output = `$cmd`;
589 print($output) if $verbose;
590 if (!$existingids) {
591 unlink("$vfile");
592 }
593}
594
595sub deleteencs() {
596 for (glob('*.enc')) {
597 unlink($_);
598 }
599 for (glob('*.wav')) {
600 unlink($_);
601 }
602}
603
604sub panic_cleanup {
605 deletencs();
606 die "moo";
607}
608
609# Generate .talk clips
610sub gentalkclips {
611 our $verbose;
612 my ($dir, $tts_object, $language, $encoder, $encoder_opts, $tts_engine_opts, $i) = @_;
613 my $d = new DirHandle $dir;
614
615 while (my $file = $d->read) {
616 $file = Encode::decode( locale_fs => $file);
617 my ($voice, $wav, $enc);
618 my $format = $tts_object->{'format'};
619
620 # Ignore dot-dirs and talk files
621 if ($file eq '.' || $file eq '..' || $file =~ /\.talk$/) {
622 next;
623 }
624
625 # Print some progress information
626 if (++$i % 10 == 0 and !$verbose) {
627 print(".");
628 }
629
630 $voice = $file;
631
632 # Convert some symbols to spaces
633 $voice =~ tr/_-/ /;
634
635 # Convert to a complete path
636 my $path = sprintf("%s/%s", $dir, $file);
637
638 $wav = sprintf("%s.talk.wav", $path);
639
640 if ( -d $path) { # Element is a dir
641 $enc = sprintf("%s/_dirname.talk", $path);
642 if (! -e "$path/talkclips.ignore") { # Skip directories containing "talkclips.ignore"
643 gentalkclips($path, $tts_object, $language, $encoder, $encoder_opts, $tts_engine_opts, $i);
644 }
645 } else { # Element is a file
646 $enc = sprintf("%s.talk", $path);
647 $voice =~ s/\.[^\.]*$//; # Trim extension
648 }
649
650 # Apply corrections
651 $voice = correct_string($voice, $language, $tts_object);
652
653 printf("Talkclip %s: %s\n", $enc, $voice) if $verbose;
654 # Don't generate encoded file if it already exists
655 next if (-f $enc && !$force);
656
657 voicestring($voice, $wav, $tts_engine_opts, $tts_object);
658 wavtrim($wav, $trim_thresh, $tts_object);
659
660 if ($format eq "mp3") {
661 system("ffmpeg -loglevel 0 -i $wav $voice$wav");
662 rename("$voice$wav","$wav");
663 $format = "wav";
664 }
665 if ($format eq "wav") {
666 encodewav($wav, $enc, $encoder, $encoder_opts, $tts_object);
667 } else {
668 copy($wav, $enc);
669 }
670 synchronize($tts_object);
671 unlink($wav);
672 }
673}
674
675
676# Check parameters
677my $printusage = 0;
678
679unless (defined($V) or defined($C)) { print("Missing either -V or -C\n"); $printusage = 1; }
680if (defined($V)) {
681 unless (defined($l)) { print("Missing -l argument\n"); $printusage = 1; }
682 unless (defined($i)) { print("Missing -i argument\n"); $printusage = 1; }
683 if (defined($t) && defined($f) ||
684 !defined($t) && !defined($f)) {
685 print("Missing either -t or -f argument\n"); $printusage = 1;
686 }
687}
688elsif (defined($C)) {
689 unless (defined($ARGV[0])) { print "Missing path argument\n"; $printusage = 1; }
690}
691
692$force = 1 if (defined($F));
693
694unless (defined($e)) { print("Missing -e argument\n"); $printusage = 1; }
695unless (defined($E)) { print("Missing -E argument\n"); $printusage = 1; }
696unless (defined($s)) { print("Missing -s argument\n"); $printusage = 1; }
697unless (defined($S)) { print("Missing -S argument\n"); $printusage = 1; }
698if ($printusage == 1) { printusage(); exit 1; }
699
700if (defined($v) or defined($ENV{'V'})) {
701 our $verbose = 1;
702}
703
704# add the tools dir to the path temporarily, for calling various tools
705$ENV{'PATH'} = dirname($0) . ':' . $ENV{'PATH'};
706
707# logging needs to be UTF8
708binmode(*STDOUT, ':encoding(utf8)');
709
710my $tts_object = init_tts($s, $S, $l);
711
712# Do what we're told
713if (defined($V) && $V == 1) {
714 # Only do the panic cleanup for voicefiles
715 $SIG{INT} = \&panic_cleanup;
716 $SIG{KILL} = \&panic_cleanup;
717
718 printf("Generating voice\n Target: %s\n Language: %s\n Encoder (options): %s (%s)\n TTS Engine (options): %s (%s)\n Pool directory: %s\n",
719 defined($t) ? $t : "unknown",
720 $l, $e, $E, $s, "$S $tts_object->{ttsoptions}", defined($ENV{'POOL'}) ? $ENV{'POOL'} : "<none>");
721 generateclips($l, $t, $e, $E, $tts_object, $S, $f);
722 shutdown_tts($tts_object);
723 createvoice($l, $i, $f);
724 deleteencs();
725} elsif ($C) {
726 printf("Generating .talk clips\n Path: %s\n Language: %s\n Encoder (options): %s (%s)\n TTS Engine (options): %s (%s)\n", $ARGV[0], $l, $e, $E, $s, $S);
727 gentalkclips($ARGV[0], $tts_object, $l, $e, $E, $S, 0);
728 shutdown_tts($tts_object);
729} else {
730 printusage();
731 exit 1;
732}