A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 732 lines 24 kB view raw
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}