2007-08-25 22:00:13 +00:00
|
|
|
#!/usr/bin/perl -s
|
|
|
|
# __________ __ ___.
|
|
|
|
# Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
|
|
# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
|
|
# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
|
|
# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
|
|
# \/ \/ \/ \/ \/
|
2020-07-08 23:05:09 +00:00
|
|
|
# $Id$
|
2007-08-25 22:00:13 +00:00
|
|
|
#
|
|
|
|
# Copyright (C) 2007 Jonas Häggqvist
|
|
|
|
#
|
|
|
|
# All files in this archive are subject to the GNU General Public License.
|
|
|
|
# See the file COPYING in the source tree root for full license agreement.
|
|
|
|
#
|
|
|
|
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
|
|
# KIND, either express or implied.
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
use File::Basename;
|
|
|
|
use File::Copy;
|
2020-07-19 04:54:25 +00:00
|
|
|
use vars qw($V $C $t $l $e $E $s $S $i $v $f);
|
2007-09-01 08:38:10 +00:00
|
|
|
use IPC::Open2;
|
2007-08-25 22:00:13 +00:00
|
|
|
use IPC::Open3;
|
|
|
|
use Digest::MD5 qw(md5_hex);
|
2007-11-16 19:59:09 +00:00
|
|
|
use DirHandle;
|
2008-08-19 21:23:00 +00:00
|
|
|
use open ':encoding(utf8)';
|
|
|
|
use open ':std';
|
2020-07-11 00:40:36 +00:00
|
|
|
use utf8;
|
2007-08-25 22:00:13 +00:00
|
|
|
|
|
|
|
sub printusage {
|
|
|
|
print <<USAGE
|
|
|
|
|
|
|
|
Usage: voice.pl [options] [path to dir]
|
|
|
|
-V
|
2020-07-19 04:54:25 +00:00
|
|
|
Create voice file. You must also specify -l, -i, and -t or -f
|
2020-07-08 23:05:09 +00:00
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
-C
|
|
|
|
Create .talk clips.
|
|
|
|
|
|
|
|
-t=<target>
|
|
|
|
Specify which target you want to build voicefile for. Must include
|
|
|
|
any features that target supports.
|
2020-07-08 23:05:09 +00:00
|
|
|
|
2020-07-19 04:54:25 +00:00
|
|
|
-f=<file> Use existing voiceids file
|
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
-i=<target_id>
|
|
|
|
Numeric target id. Needed for voice building.
|
2020-07-08 23:05:09 +00:00
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
-l=<language>
|
|
|
|
Specify which language you want to build. Without .lang extension.
|
2020-07-08 23:05:09 +00:00
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
-e=<encoder>
|
|
|
|
Which encoder to use for voice strings
|
|
|
|
|
|
|
|
-E=<encoder options>
|
|
|
|
Which encoder options to use when compressing voice strings. Enclose
|
|
|
|
in double quotes if the options include spaces.
|
2020-07-08 23:05:09 +00:00
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
-s=<TTS engine>
|
|
|
|
Which TTS engine to use.
|
2020-07-08 23:05:09 +00:00
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
-S=<TTS engine options>
|
|
|
|
Options to pass to the TTS engine. Enclose in double quotes if the
|
|
|
|
options include spaces.
|
2020-07-08 23:05:09 +00:00
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
-v
|
|
|
|
Be verbose
|
|
|
|
USAGE
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2020-07-09 11:33:19 +00:00
|
|
|
my %festival_lang_map = (
|
2020-07-08 23:05:09 +00:00
|
|
|
'english' => 'english',
|
|
|
|
'english-us' => 'english',
|
|
|
|
'espanol' => 'spanish',
|
|
|
|
#'finnish' => 'finnish'
|
|
|
|
#'italiano' => 'italian',
|
|
|
|
#'czech' => 'czech',
|
|
|
|
#'welsh' => 'welsh'
|
2020-07-09 11:33:19 +00:00
|
|
|
);
|
2020-07-08 23:05:09 +00:00
|
|
|
|
2020-07-09 11:33:19 +00:00
|
|
|
my %gtts_lang_map = (
|
2020-07-08 23:05:09 +00:00
|
|
|
'english' => 'en-gb', # Always first, it's the golden master
|
2020-07-10 23:49:35 +00:00
|
|
|
'czech' => 'cs', # not supported
|
2020-07-08 23:05:09 +00:00
|
|
|
'deutsch' => 'de',
|
2020-07-10 23:49:35 +00:00
|
|
|
'english-us' => 'en-us',
|
|
|
|
'espanol' => 'es-es',
|
2020-07-08 23:05:09 +00:00
|
|
|
'francais' => 'fr-fr',
|
2020-07-11 17:52:14 +00:00
|
|
|
'greek' => 'el',
|
2020-07-10 23:49:35 +00:00
|
|
|
'magyar' => 'hu',
|
|
|
|
'italiano' => 'it',
|
|
|
|
# 'nederlands' => 'nl', # not supported
|
2020-07-08 23:05:09 +00:00
|
|
|
'norsk' => 'no',
|
|
|
|
'polski' => 'pl',
|
|
|
|
'russian' => 'ru',
|
|
|
|
'slovak' => 'sk',
|
2020-07-10 23:49:35 +00:00
|
|
|
'srpski' => 'sr',
|
|
|
|
'svenska' => 'sv',
|
|
|
|
'turkce' => 'tr',
|
2020-07-09 11:33:19 +00:00
|
|
|
);
|
2020-07-08 23:05:09 +00:00
|
|
|
|
2020-07-10 23:13:51 +00:00
|
|
|
my %espeak_lang_map = (
|
|
|
|
'english' => 'en-gb', # Always first, it's the golden master
|
2020-07-10 23:49:35 +00:00
|
|
|
'czech' => 'cs',
|
2020-07-10 23:13:51 +00:00
|
|
|
'deutsch' => 'de',
|
2020-07-10 23:49:35 +00:00
|
|
|
'english-us' => 'en-us',
|
|
|
|
'espanol' => 'es',
|
2020-07-10 23:13:51 +00:00
|
|
|
'francais' => 'fr-fr',
|
2020-07-10 23:49:35 +00:00
|
|
|
'greek' => 'el',
|
|
|
|
'nederlands' => 'nl',
|
|
|
|
'magyar' => 'hu',
|
|
|
|
'italiano' => 'it',
|
|
|
|
'japanese' => 'ja',
|
2020-07-10 23:13:51 +00:00
|
|
|
'norsk' => 'no',
|
|
|
|
'polski' => 'pl',
|
|
|
|
'russian' => 'ru',
|
|
|
|
'slovak' => 'sk',
|
2020-07-10 23:49:35 +00:00
|
|
|
'srpski' => 'sr',
|
|
|
|
'svenska' => 'sv',
|
|
|
|
'turkce' => 'tr',
|
2020-07-10 23:13:51 +00:00
|
|
|
);
|
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
# Initialize TTS engine. May return an object or value which will be passed
|
|
|
|
# to voicestring and shutdown_tts
|
|
|
|
sub init_tts {
|
|
|
|
our $verbose;
|
|
|
|
my ($tts_engine, $tts_engine_opts, $language) = @_;
|
2007-09-01 08:38:10 +00:00
|
|
|
my %ret = ("name" => $tts_engine);
|
2020-07-08 23:05:09 +00:00
|
|
|
$ret{"format"} = 'wav';
|
|
|
|
$ret{"ttsoptions"} = "";
|
|
|
|
|
2011-08-16 19:26:24 +00:00
|
|
|
# Don't use given/when here - it's not compatible with old perl versions
|
|
|
|
if ($tts_engine eq 'festival') {
|
|
|
|
print("> festival $tts_engine_opts --server\n") if $verbose;
|
|
|
|
my $pid = open(FESTIVAL_SERVER, "| festival $tts_engine_opts --server > /dev/null 2>&1");
|
|
|
|
my $dummy = *FESTIVAL_SERVER; #suppress warning
|
|
|
|
$SIG{INT} = sub { kill TERM => $pid; print("foo"); panic_cleanup(); };
|
|
|
|
$SIG{KILL} = sub { kill TERM => $pid; print("boo"); panic_cleanup(); };
|
|
|
|
$ret{"pid"} = $pid;
|
2020-07-10 23:13:51 +00:00
|
|
|
if (defined($festival_lang_map{$language}) && $tts_engine_opts !~ /--language/) {
|
|
|
|
$ret{"ttsoptions"} = "--language $festival_lang_map{$language} ";
|
2020-07-08 23:05:09 +00:00
|
|
|
}
|
|
|
|
} elsif ($tts_engine eq 'sapi') {
|
2011-08-16 19:26:24 +00:00
|
|
|
my $toolsdir = dirname($0);
|
|
|
|
my $path = `cygpath $toolsdir -a -w`;
|
|
|
|
chomp($path);
|
|
|
|
$path = $path . '\\';
|
|
|
|
my $cmd = $path . "sapi_voice.vbs /language:$language $tts_engine_opts";
|
|
|
|
$cmd =~ s/\\/\\\\/g;
|
|
|
|
print("> cscript //nologo $cmd\n") if $verbose;
|
|
|
|
my $pid = open2(*CMD_OUT, *CMD_IN, "cscript //nologo $cmd");
|
|
|
|
binmode(*CMD_IN, ':encoding(utf16le)');
|
|
|
|
binmode(*CMD_OUT, ':encoding(utf16le)');
|
|
|
|
$SIG{INT} = sub { print(CMD_IN "QUIT\r\n"); panic_cleanup(); };
|
|
|
|
$SIG{KILL} = sub { print(CMD_IN "QUIT\r\n"); panic_cleanup(); };
|
|
|
|
print(CMD_IN "QUERY\tVENDOR\r\n");
|
|
|
|
my $vendor = readline(*CMD_OUT);
|
|
|
|
$vendor =~ s/\r\n//;
|
|
|
|
%ret = (%ret,
|
|
|
|
"stdin" => *CMD_IN,
|
|
|
|
"stdout" => *CMD_OUT,
|
|
|
|
"vendor" => $vendor);
|
2020-07-08 23:05:09 +00:00
|
|
|
} elsif ($tts_engine eq 'gtts') {
|
|
|
|
$ret{"format"} = 'mp3';
|
2020-07-10 23:13:51 +00:00
|
|
|
if (defined($gtts_lang_map{$language}) && $tts_engine_opts !~ /-l/) {
|
2020-07-08 23:05:09 +00:00
|
|
|
$ret{"ttsoptions"} = "-l $gtts_lang_map{$language} ";
|
|
|
|
}
|
2020-07-11 15:21:26 +00:00
|
|
|
} elsif ($tts_engine eq 'espeak' || $tts_engine eq 'espeak-ng') {
|
2020-07-10 23:13:51 +00:00
|
|
|
if (defined($espeak_lang_map{$language}) && $tts_engine_opts !~ /-v/) {
|
2020-07-11 17:52:14 +00:00
|
|
|
$ret{"ttsoptions"} = "-v$espeak_lang_map{$language} ";
|
2020-07-10 23:13:51 +00:00
|
|
|
}
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
2020-07-10 23:13:51 +00:00
|
|
|
|
2007-09-01 08:38:10 +00:00
|
|
|
return \%ret;
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Shutdown TTS engine if necessary.
|
|
|
|
sub shutdown_tts {
|
2007-09-01 08:38:10 +00:00
|
|
|
my ($tts_object) = @_;
|
2011-08-16 19:26:24 +00:00
|
|
|
if ($$tts_object{'name'} eq 'festival') {
|
|
|
|
# Send SIGTERM to festival server
|
|
|
|
kill TERM => $$tts_object{"pid"};
|
|
|
|
}
|
|
|
|
elsif ($$tts_object{'name'} eq 'sapi') {
|
|
|
|
print({$$tts_object{"stdin"}} "QUIT\r\n");
|
|
|
|
close($$tts_object{"stdin"});
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Apply corrections to a voice-string to make it sound better
|
|
|
|
sub correct_string {
|
|
|
|
our $verbose;
|
2007-09-01 08:38:10 +00:00
|
|
|
my ($string, $language, $tts_object) = @_;
|
2007-08-25 22:00:13 +00:00
|
|
|
my $orig = $string;
|
2011-03-02 18:29:38 +00:00
|
|
|
my $corrections = $tts_object->{"corrections"};
|
2007-08-25 22:00:13 +00:00
|
|
|
|
2011-03-02 18:29:38 +00:00
|
|
|
foreach (@$corrections) {
|
|
|
|
my $r = "s" . $_->{separator} . $_->{search} . $_->{separator}
|
|
|
|
. $_->{replace} . $_->{separator} . $_->{modifier};
|
|
|
|
eval ('$string =~' . "$r;");
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
|
|
|
if ($orig ne $string) {
|
|
|
|
printf("%s -> %s\n", $orig, $string) if $verbose;
|
|
|
|
}
|
|
|
|
return $string;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Produce a wav file of the text given
|
|
|
|
sub voicestring {
|
|
|
|
our $verbose;
|
2007-09-01 08:38:10 +00:00
|
|
|
my ($string, $output, $tts_engine_opts, $tts_object) = @_;
|
2007-08-25 22:00:13 +00:00
|
|
|
my $cmd;
|
2011-08-16 19:26:24 +00:00
|
|
|
my $name = $$tts_object{'name'};
|
2020-07-08 23:05:09 +00:00
|
|
|
|
|
|
|
$tts_engine_opts .= $$tts_object{"ttsoptions"};
|
|
|
|
|
2011-08-16 19:26:24 +00:00
|
|
|
printf("Generate \"%s\" with %s in file %s\n", $string, $name, $output) if $verbose;
|
|
|
|
if ($name eq 'festival') {
|
|
|
|
# festival_client lies to us, so we have to do awful soul-eating
|
|
|
|
# work with IPC::open3()
|
|
|
|
$cmd = "festival_client --server localhost --otype riff --ttw --output \"$output\"";
|
|
|
|
# Use festival-prolog.scm if it's there (created by user of tools/configure)
|
|
|
|
if (-f "festival-prolog.scm") {
|
|
|
|
$cmd .= " --prolog festival-prolog.scm";
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
2011-08-16 19:26:24 +00:00
|
|
|
print("> $cmd\n") if $verbose;
|
|
|
|
# Open command, and filehandles for STDIN, STDOUT, STDERR
|
|
|
|
my $pid = open3(*CMD_IN, *CMD_OUT, *CMD_ERR, $cmd);
|
|
|
|
# Put the string to speak into STDIN and close it
|
|
|
|
print(CMD_IN $string);
|
|
|
|
close(CMD_IN);
|
|
|
|
# Read all output from festival_client (because it LIES TO US)
|
|
|
|
while (<CMD_ERR>) {
|
2007-09-01 20:03:20 +00:00
|
|
|
}
|
2011-08-16 19:26:24 +00:00
|
|
|
close(CMD_OUT);
|
|
|
|
close(CMD_ERR);
|
|
|
|
}
|
|
|
|
elsif ($name eq 'flite') {
|
|
|
|
$cmd = "flite $tts_engine_opts -t \"$string\" \"$output\"";
|
|
|
|
print("> $cmd\n") if $verbose;
|
2020-07-08 23:05:09 +00:00
|
|
|
system($cmd);
|
2011-08-16 19:26:24 +00:00
|
|
|
}
|
|
|
|
elsif ($name eq 'espeak') {
|
2020-07-11 15:21:26 +00:00
|
|
|
$cmd = "espeak $tts_engine_opts -w \"$output\" --stdin";
|
2011-08-16 19:26:24 +00:00
|
|
|
print("> $cmd\n") if $verbose;
|
2020-07-11 15:21:26 +00:00
|
|
|
open(RBSPEAK, "| $cmd");
|
|
|
|
print RBSPEAK $string . "\n";
|
|
|
|
close(RBSPEAK);
|
2011-08-16 19:26:24 +00:00
|
|
|
}
|
2020-07-11 14:05:40 +00:00
|
|
|
elsif ($name eq 'espeak-ng') {
|
2020-07-11 15:21:26 +00:00
|
|
|
$cmd = "espeak-ng $tts_engine_opts -w \"$output\" --stdin";
|
2020-07-11 14:05:40 +00:00
|
|
|
print("> $cmd\n") if $verbose;
|
2020-07-11 15:21:26 +00:00
|
|
|
open(RBSPEAK, "| $cmd");
|
|
|
|
print RBSPEAK $string . "\n";
|
|
|
|
close(RBSPEAK);
|
2020-07-11 14:05:40 +00:00
|
|
|
}
|
2011-08-16 19:26:24 +00:00
|
|
|
elsif ($name eq 'sapi') {
|
|
|
|
print({$$tts_object{"stdin"}} "SPEAK\t$output\t$string\r\n");
|
|
|
|
}
|
|
|
|
elsif ($name eq 'swift') {
|
|
|
|
$cmd = "swift $tts_engine_opts -o \"$output\" \"$string\"";
|
|
|
|
print("> $cmd\n") if $verbose;
|
|
|
|
system($cmd);
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
2018-12-23 01:26:09 +00:00
|
|
|
elsif ($name eq 'rbspeak') {
|
|
|
|
# xxx: $tts_engine_opts isn't used
|
|
|
|
$cmd = "rbspeak $output";
|
|
|
|
print("> $cmd\n") if $verbose;
|
|
|
|
open(RBSPEAK, "| $cmd");
|
|
|
|
print RBSPEAK $string . "\n";
|
|
|
|
close(RBSPEAK);
|
|
|
|
}
|
2020-04-16 19:03:27 +00:00
|
|
|
elsif ($name eq 'mimic') {
|
2020-07-11 15:21:26 +00:00
|
|
|
$cmd = "mimic $tts_engine_opts -o $output";
|
2020-07-08 23:05:09 +00:00
|
|
|
print("> $cmd\n") if $verbose;
|
2020-07-11 15:21:26 +00:00
|
|
|
open(RBSPEAK, "| $cmd");
|
|
|
|
print RBSPEAK $string . "\n";
|
|
|
|
close(RBSPEAK);
|
2020-07-08 23:05:09 +00:00
|
|
|
}
|
|
|
|
elsif ($name eq 'gtts') {
|
2020-07-11 15:21:26 +00:00
|
|
|
$cmd = "gtts-cli $tts_engine_opts -o $output -";
|
2020-07-08 23:05:09 +00:00
|
|
|
print("> $cmd\n") if $verbose;
|
2020-07-11 15:21:26 +00:00
|
|
|
open(RBSPEAK, "| $cmd");
|
|
|
|
print RBSPEAK $string . "\n";
|
|
|
|
close(RBSPEAK);
|
2020-04-16 19:03:27 +00:00
|
|
|
}
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
|
|
|
|
2007-09-01 08:38:10 +00:00
|
|
|
# trim leading / trailing silence from the clip
|
|
|
|
sub wavtrim {
|
|
|
|
our $verbose;
|
|
|
|
my ($file, $threshold, $tts_object) = @_;
|
|
|
|
printf("Trim \"%s\"\n", $file) if $verbose;
|
2007-11-18 18:44:45 +00:00
|
|
|
my $cmd = "wavtrim \"$file\" $threshold";
|
2007-09-02 22:32:34 +00:00
|
|
|
if ($$tts_object{"name"} eq "sapi") {
|
2008-08-19 21:23:00 +00:00
|
|
|
print({$$tts_object{"stdin"}} "EXEC\t$cmd\r\n");
|
2007-09-01 08:38:10 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
print("> $cmd\n") if $verbose;
|
|
|
|
`$cmd`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
# Encode a wav file into the given destination file
|
|
|
|
sub encodewav {
|
|
|
|
our $verbose;
|
2007-09-01 08:38:10 +00:00
|
|
|
my ($input, $output, $encoder, $encoder_opts, $tts_object) = @_;
|
2007-08-25 22:00:13 +00:00
|
|
|
printf("Encode \"%s\" with %s in file %s\n", $input, $encoder, $output) if $verbose;
|
2007-11-18 18:44:45 +00:00
|
|
|
my $cmd = "$encoder $encoder_opts \"$input\" \"$output\"";
|
2007-09-02 22:32:34 +00:00
|
|
|
if ($$tts_object{"name"} eq "sapi") {
|
2008-08-19 21:23:00 +00:00
|
|
|
print({$$tts_object{"stdin"}} "EXEC\t$cmd\r\n");
|
2007-09-01 08:38:10 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
print("> $cmd\n") if $verbose;
|
|
|
|
`$cmd`;
|
|
|
|
}
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
|
|
|
|
2007-09-01 08:38:10 +00:00
|
|
|
# synchronize the clip generation / processing if it's running in another process
|
|
|
|
sub synchronize {
|
|
|
|
my ($tts_object) = @_;
|
2007-09-02 22:32:34 +00:00
|
|
|
if ($$tts_object{"name"} eq "sapi") {
|
2008-08-19 21:23:00 +00:00
|
|
|
print({$$tts_object{"stdin"}} "SYNC\t42\r\n");
|
2007-09-01 08:38:10 +00:00
|
|
|
my $wait = readline($$tts_object{"stdout"});
|
|
|
|
#ignore what's actually returned
|
|
|
|
}
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Run genlang and create voice clips for each string
|
|
|
|
sub generateclips {
|
|
|
|
our $verbose;
|
2020-07-19 04:54:25 +00:00
|
|
|
my ($language, $target, $encoder, $encoder_opts, $tts_engine, $tts_engine_opts, $existingids) = @_;
|
2007-08-25 22:00:13 +00:00
|
|
|
my $english = dirname($0) . '/../apps/lang/english.lang';
|
|
|
|
my $langfile = dirname($0) . '/../apps/lang/' . $language . '.lang';
|
2011-03-02 18:29:38 +00:00
|
|
|
my $correctionsfile = dirname($0) . '/voice-corrections.txt';
|
2020-07-19 04:54:25 +00:00
|
|
|
my $idfile = "$language.vid";
|
|
|
|
my $updfile = "$language-update.lang";
|
2007-08-25 22:00:13 +00:00
|
|
|
my $id = '';
|
|
|
|
my $voice = '';
|
2020-07-19 04:54:25 +00:00
|
|
|
my $cmd;
|
2007-08-25 22:00:13 +00:00
|
|
|
my $pool_file;
|
|
|
|
my $i = 0;
|
2007-09-02 00:50:08 +00:00
|
|
|
local $| = 1; # make progress indicator work reliably
|
2007-08-25 22:00:13 +00:00
|
|
|
|
2020-07-12 20:50:53 +00:00
|
|
|
# First run the language through an update pass so any missing strings
|
|
|
|
# are backfilled from English. Without this, BADNESS.
|
2020-07-19 04:54:25 +00:00
|
|
|
if ($existingids) {
|
|
|
|
$idfile = $existingids;
|
|
|
|
} else {
|
|
|
|
$cmd = "genlang -u -e=$english $langfile > $updfile";
|
|
|
|
print("> $cmd\n") if $verbose;
|
|
|
|
system($cmd);
|
|
|
|
$cmd = "genlang -o -t=$target -e=$english $updfile 2>/dev/null > $idfile";
|
|
|
|
print("> $cmd\n") if $verbose;
|
|
|
|
system($cmd);
|
|
|
|
}
|
|
|
|
open(VOICEFONTIDS, " < $idfile");
|
2020-07-12 20:50:53 +00:00
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
my $tts_object = init_tts($tts_engine, $tts_engine_opts, $language);
|
2011-03-02 18:29:38 +00:00
|
|
|
# add string corrections to tts_object.
|
|
|
|
my @corrects = ();
|
|
|
|
open(VOICEREGEXP, "<$correctionsfile") or die "Can't open corrections file!\n";
|
|
|
|
while(<VOICEREGEXP>) {
|
|
|
|
# get first character of line
|
|
|
|
my $line = $_;
|
|
|
|
my $separator = substr($_, 0, 1);
|
|
|
|
if($separator =~ m/\s+/) {
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
chomp($line);
|
|
|
|
$line =~ s/^.//g; # remove separator at beginning
|
|
|
|
my ($lang, $engine, $vendor, $search, $replace, $modifier) = split(/$separator/, $line);
|
|
|
|
|
|
|
|
# does language match?
|
|
|
|
if($language !~ m/$lang/) {
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
if($$tts_object{"name"} !~ m/$engine/) {
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
my $v = $$tts_object{"vendor"} || ""; # vendor might be empty in $tts_object
|
|
|
|
if($v !~ m/$vendor/) {
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
push @corrects, {separator => $separator, search => $search, replace => $replace, modifier => $modifier};
|
|
|
|
|
|
|
|
}
|
|
|
|
close(VOICEREGEXP);
|
|
|
|
$tts_object->{corrections} = [@corrects];
|
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
print("Generating voice clips");
|
|
|
|
print("\n") if $verbose;
|
2020-07-19 04:54:25 +00:00
|
|
|
for (<VOICEFONTIDS>) {
|
2007-08-25 22:00:13 +00:00
|
|
|
my $line = $_;
|
|
|
|
if ($line =~ /^id: (.*)$/) {
|
|
|
|
$id = $1;
|
|
|
|
}
|
|
|
|
elsif ($line =~ /^voice: "(.*)"$/) {
|
|
|
|
$voice = $1;
|
|
|
|
if ($id !~ /^NOT_USED_.*$/ && $voice ne "") {
|
|
|
|
my $wav = $id . '.wav';
|
2020-07-19 04:54:25 +00:00
|
|
|
my $enc = $id . '.mp3';
|
2007-08-25 22:00:13 +00:00
|
|
|
|
|
|
|
# Print some progress information
|
|
|
|
if (++$i % 10 == 0 and !$verbose) {
|
|
|
|
print(".");
|
|
|
|
}
|
|
|
|
|
|
|
|
# Apply corrections to the string
|
2007-09-04 22:03:05 +00:00
|
|
|
$voice = correct_string($voice, $language, $tts_object);
|
2007-08-25 22:00:13 +00:00
|
|
|
|
2007-09-04 22:03:05 +00:00
|
|
|
# If we have a pool of snippets, see if the string exists there first
|
2007-08-25 22:00:13 +00:00
|
|
|
if (defined($ENV{'POOL'})) {
|
2020-04-16 19:03:27 +00:00
|
|
|
$pool_file = sprintf("%s/%s-%s.mp3", $ENV{'POOL'},
|
2020-07-11 00:40:36 +00:00
|
|
|
md5_hex(Encode::encode_utf8("$voice $tts_engine $tts_engine_opts $encoder_opts")),
|
2007-09-04 22:03:05 +00:00
|
|
|
$language);
|
2007-08-25 22:00:13 +00:00
|
|
|
if (-f $pool_file) {
|
|
|
|
printf("Re-using %s (%s) from pool\n", $id, $voice) if $verbose;
|
2020-07-19 04:54:25 +00:00
|
|
|
copy($pool_file, $enc);
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-19 04:54:25 +00:00
|
|
|
# Don't generate encoded file if it already exists (probably from the POOL)
|
|
|
|
if (! -f $enc) {
|
2007-08-25 22:00:13 +00:00
|
|
|
if ($id eq "VOICE_PAUSE") {
|
|
|
|
print("Use distributed $wav\n") if $verbose;
|
|
|
|
copy(dirname($0)."/VOICE_PAUSE.wav", $wav);
|
2020-07-08 23:05:09 +00:00
|
|
|
} else {
|
|
|
|
voicestring($voice, $wav, $tts_engine_opts, $tts_object);
|
|
|
|
if ($tts_object->{'format'} eq "wav") {
|
|
|
|
wavtrim($wav, 500, $tts_object);
|
|
|
|
# 500 seems to be a reasonable default for now
|
|
|
|
}
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
2020-07-08 23:05:09 +00:00
|
|
|
if ($tts_object->{'format'} eq "wav" || $id eq "VOICE_PAUSE") {
|
2020-07-19 04:54:25 +00:00
|
|
|
encodewav($wav, $enc, $encoder, $encoder_opts, $tts_object);
|
2020-07-08 23:05:09 +00:00
|
|
|
} else {
|
2020-07-19 04:54:25 +00:00
|
|
|
copy($wav, $enc);
|
2020-07-08 23:05:09 +00:00
|
|
|
}
|
2007-08-25 22:00:13 +00:00
|
|
|
|
2007-09-01 08:38:10 +00:00
|
|
|
synchronize($tts_object);
|
2007-08-25 22:00:13 +00:00
|
|
|
if (defined($ENV{'POOL'})) {
|
2020-07-19 04:54:25 +00:00
|
|
|
copy($enc, $pool_file);
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
|
|
|
unlink($wav);
|
|
|
|
}
|
|
|
|
$voice = "";
|
|
|
|
$id = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
close(VOICEFONTIDS);
|
2020-07-19 04:54:25 +00:00
|
|
|
|
|
|
|
print("\n");
|
|
|
|
|
|
|
|
unlink($updfile) if (-f $updfile);
|
2007-09-01 08:38:10 +00:00
|
|
|
shutdown_tts($tts_object);
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Assemble the voicefile
|
|
|
|
sub createvoice {
|
|
|
|
our $verbose;
|
2020-07-19 04:54:25 +00:00
|
|
|
my ($language, $target_id, $existingids) = @_;
|
2007-08-25 22:00:13 +00:00
|
|
|
my $outfile = "";
|
2020-07-19 04:54:25 +00:00
|
|
|
my $vfile = "$language.vid";
|
|
|
|
|
|
|
|
if ($existingids) {
|
|
|
|
$vfile = $existingids;
|
|
|
|
}
|
2007-11-27 13:47:52 +00:00
|
|
|
$outfile = sprintf("%s.voice", $language);
|
2007-08-25 22:00:13 +00:00
|
|
|
printf("Saving voice file to %s\n", $outfile) if $verbose;
|
2020-07-19 04:54:25 +00:00
|
|
|
my $cmd = "voicefont '$vfile' $target_id ./ $outfile";
|
2007-08-25 22:00:13 +00:00
|
|
|
print("> $cmd\n") if $verbose;
|
|
|
|
my $output = `$cmd`;
|
|
|
|
print($output) if $verbose;
|
2020-07-19 04:54:25 +00:00
|
|
|
if (!$existingids) {
|
|
|
|
unlink("$vfile");
|
|
|
|
}
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
|
|
|
|
2020-07-19 04:54:25 +00:00
|
|
|
sub deleteencs() {
|
2007-08-25 22:00:13 +00:00
|
|
|
for (glob('*.mp3')) {
|
|
|
|
unlink($_);
|
|
|
|
}
|
|
|
|
for (glob('*.wav')) {
|
|
|
|
unlink($_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub panic_cleanup {
|
2020-07-19 04:54:25 +00:00
|
|
|
deletencs();
|
2007-08-25 22:00:13 +00:00
|
|
|
die "moo";
|
|
|
|
}
|
|
|
|
|
2007-11-16 19:59:09 +00:00
|
|
|
# Generate .talk clips
|
|
|
|
sub gentalkclips {
|
|
|
|
our $verbose;
|
|
|
|
my ($dir, $tts_object, $encoder, $encoder_opts, $tts_engine_opts, $i) = @_;
|
|
|
|
my $d = new DirHandle $dir;
|
|
|
|
while (my $file = $d->read) {
|
2020-07-19 04:54:25 +00:00
|
|
|
my ($voice, $wav, $enc);
|
2007-11-16 19:59:09 +00:00
|
|
|
# Print some progress information
|
|
|
|
if (++$i % 10 == 0 and !$verbose) {
|
|
|
|
print(".");
|
|
|
|
}
|
|
|
|
|
|
|
|
# Convert to a complete path
|
|
|
|
my $path = sprintf("%s/%s", $dir, $file);
|
2007-11-16 20:02:02 +00:00
|
|
|
|
|
|
|
$voice = $file;
|
|
|
|
$wav = sprintf("%s.talk.wav", $path);
|
|
|
|
|
2007-11-16 19:59:09 +00:00
|
|
|
# Ignore dot-dirs and talk files
|
|
|
|
if ($file eq '.' || $file eq '..' || $file =~ /\.talk$/) {
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
# Element is a dir
|
|
|
|
if ( -d $path) {
|
2007-11-16 23:42:18 +00:00
|
|
|
gentalkclips($path, $tts_object, $encoder, $encoder_opts, $tts_engine_opts, $i);
|
2020-07-19 04:54:25 +00:00
|
|
|
$enc = sprintf("%s/_dirname.talk", $path);
|
2007-11-16 19:59:09 +00:00
|
|
|
}
|
|
|
|
# Element is a file
|
|
|
|
else {
|
2020-07-19 04:54:25 +00:00
|
|
|
$enc = sprintf("%s.talk", $path);
|
2007-11-16 19:59:09 +00:00
|
|
|
$voice =~ s/\.[^\.]*$//; # Trim extension
|
|
|
|
}
|
|
|
|
|
2020-07-19 04:54:25 +00:00
|
|
|
printf("Talkclip %s: %s", $enc, $voice) if $verbose;
|
2007-11-16 19:59:09 +00:00
|
|
|
|
|
|
|
voicestring($voice, $wav, $tts_engine_opts, $tts_object);
|
|
|
|
wavtrim($wav, 500, $tts_object);
|
|
|
|
# 500 seems to be a reasonable default for now
|
2020-07-19 04:54:25 +00:00
|
|
|
encodewav($wav, $enc, $encoder, $encoder_opts, $tts_object);
|
2007-11-19 23:24:27 +00:00
|
|
|
synchronize($tts_object);
|
2007-11-16 19:59:09 +00:00
|
|
|
unlink($wav);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
# Check parameters
|
|
|
|
my $printusage = 0;
|
|
|
|
unless (defined($V) or defined($C)) { print("Missing either -V or -C\n"); $printusage = 1; }
|
|
|
|
if (defined($V)) {
|
|
|
|
unless (defined($l)) { print("Missing -l argument\n"); $printusage = 1; }
|
|
|
|
unless (defined($i)) { print("Missing -i argument\n"); $printusage = 1; }
|
2020-07-19 04:54:25 +00:00
|
|
|
if (defined($t) && defined($f) ||
|
|
|
|
!defined($t) && !defined($f)) {
|
|
|
|
print("Missing either -t or -f argument\n"); $printusage = 1;
|
|
|
|
}
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
|
|
|
elsif (defined($C)) {
|
|
|
|
unless (defined($ARGV[0])) { print "Missing path argument\n"; $printusage = 1; }
|
|
|
|
}
|
|
|
|
unless (defined($e)) { print("Missing -e argument\n"); $printusage = 1; }
|
|
|
|
unless (defined($E)) { print("Missing -E argument\n"); $printusage = 1; }
|
|
|
|
unless (defined($s)) { print("Missing -s argument\n"); $printusage = 1; }
|
|
|
|
unless (defined($S)) { print("Missing -S argument\n"); $printusage = 1; }
|
|
|
|
if ($printusage == 1) { printusage(); exit 1; }
|
|
|
|
|
|
|
|
if (defined($v) or defined($ENV{'V'})) {
|
|
|
|
our $verbose = 1;
|
|
|
|
}
|
|
|
|
|
2007-11-18 18:44:45 +00:00
|
|
|
# add the tools dir to the path temporarily, for calling various tools
|
|
|
|
$ENV{'PATH'} = dirname($0) . ':' . $ENV{'PATH'};
|
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
# Do what we're told
|
|
|
|
if ($V == 1) {
|
2007-11-16 19:59:09 +00:00
|
|
|
# Only do the panic cleanup for voicefiles
|
|
|
|
$SIG{INT} = \&panic_cleanup;
|
|
|
|
$SIG{KILL} = \&panic_cleanup;
|
|
|
|
|
2007-08-25 22:00:13 +00:00
|
|
|
printf("Generating voice\n Target: %s\n Language: %s\n Encoder (options): %s (%s)\n TTS Engine (options): %s (%s)\n",
|
2020-07-19 04:54:25 +00:00
|
|
|
defined($t) ? $t : "unknown",
|
|
|
|
$l, $e, $E, $s, $S);
|
|
|
|
generateclips($l, $t, $e, $E, $s, $S, $f);
|
|
|
|
createvoice($l, $i, $f);
|
|
|
|
deleteencs();
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
|
|
|
elsif ($C) {
|
2007-11-16 19:59:09 +00:00
|
|
|
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);
|
|
|
|
my $tts_object = init_tts($s, $S, $l);
|
2007-11-18 17:10:50 +00:00
|
|
|
gentalkclips($ARGV[0], $tts_object, $e, $E, $S, 0);
|
2007-11-16 19:59:09 +00:00
|
|
|
shutdown_tts($tts_object);
|
2007-08-25 22:00:13 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
printusage();
|
|
|
|
exit 1;
|
|
|
|
}
|