Skip to content

Instantly share code, notes, and snippets.

@nfreear
Created May 7, 2011 06:17
Show Gist options
  • Save nfreear/960257 to your computer and use it in GitHub Desktop.
Save nfreear/960257 to your computer and use it in GitHub Desktop.
getsound.pl: CGI script to bridge eSpeak TTS to the Web - WebAnywhere
#!/usr/bin/perl
#
# CGI script to bridge eSpeak TTS to the Web - WebAnywhere.
#
# @copyright (c)2008 University of Washington.
# @license http://www.opensource.org/licenses/bsd-license.php
# @source http://code.google.com/p/webanywhere/#trunk/tts/espeak/getsound.pl#r264
# Modifications, Nick Freear {http://freear.org.uk}
#
use strict;
use warnings;
use utf8;
use CGI qw(:standard unescape);
use Digest::MD5;
use File::Path;
use Encode;
# Paths - edit me.
my $lame = '/home/hgneng/bin/lame'; #'/opt/local/bin/lame'; #NDF.
my $espeak = '/home/hgneng/bin/espeak/speak'; #'/Applications/espeak/speak'; #NDF. Added.
my $log_dir= '/var/log/ekho'; #'/Applications/MAMP/logs';
my $request_log= "$log_dir/espeak_request.log"; #'/var/log/ekho/espeak.request';
my $error_log = "$log_dir/espeak_error.log"; #'/var/log/ekho/espeak.error';
my $cache_dir = '/var/cache/sounds'; #'/Applications/MAMP/tmp/cache_sounds';
my $ext = 'wav'; #NDF.
if ($lame) {
$ext = 'mp3';
}
#festival/getsound.pl - See %letters search-replace.
my $voice = 'en';
my $cache = 1;
my $text = param('text');
#error("Woops, parameter 'text=X' is required.") if !$text; #NDF. Added.
$text = "Error. Parameter text is required." if !$text;
$voice = param('lang') if (defined param('lang'));
$cache = param('cache') if (defined param('cache'));
$voice =~ s/\|/+/; #NDF. en%2Bf1.
$text = unescape($text);
# handle encoding
#`echo "raw: $text" >> /tmp/espeak.log`;
# decode MS %u infamous non-standard encoding in URL
while ($text =~ /([^%]*)%u(....)(.*)/) {
$text = $1 . chr(hex("0x$2")) . $3;
}
$text =~ s/^\s+//; # delete leading spaces
$text =~ s/\s+$//; # delete tailing spaces
$text =~ s/\"//g;
#`echo "speaking $text" >> /tmp/espeak.log`;
logRequest($text, $voice);
sendFileToClient(getMp3File($text, $voice, $ext), $text, $voice, $ext);
##### END OF MAIN #####
sub getMp3File {
my ($text, $voice, $ext) = @_;
# Constructs a filename based on the MD5 of the text.
my $md5 = Digest::MD5->new;
$md5->add(encode_utf8($text));
my $filename = $md5->b64digest;
$filename =~ s/[\/+\s]/_/g;
my $lc_filename = lc($filename);
my $first_dir = substr($lc_filename, 0, 1);
my $second_dir = substr($lc_filename, 1, 1);
my $third_dir = substr($lc_filename, 2, 1);
#my $enc_voice = $voice; #NDF. Encode '+m3' etc.
#$enc_voice =~ s/\+/_/;
my $final_dir = "$cache_dir/espeak-$voice/$first_dir/$second_dir/$third_dir";
my $final_filename = "$final_dir/$filename.$ext"; #NDF. Was .mp3.
# Ensure that the final directory actually exists.
mkpath $final_dir;
if((!(-e $final_filename)) || $cache == 0) {
if ($ext eq 'wav') { #NDF.
system("$espeak -v$voice -w $final_filename \"$text\" ");
} else {
system("$espeak -v$voice \"$text\" --stdout | $lame --preset voice -q 9 --vbr-new - $final_filename");
#Try to get 'duration' info!
#system("$espeak -v$voice \"$text\" --stdout | $lame --verbose --preset voice -q 9 --vbr-new - $final_filename >> /Applications/MAMP/logs/lame.log");
}
#
}
return $final_filename;
}
# output mp3 audio stream
sub sendFileToClient {
#my $file = shift;
my ($file, $text, $voice, $ext) = @_;
if (-e $file) {
my $size = -s $file;
my $filename = $file;
$filename =~ s#.*?(tmp|cache)#\$$1#; #NDF. Security.
if ($size) {
my $buff;
if ($ext eq 'wav') { #NDF.
print "Content-Type: audio/wav\n";
} else {
print "Content-Type: audio/mpeg\n";
}
print "Content-Length: $size\n";
print "Final-name: $filename\n";
print "X-Text: $text\n";
print "X-Voice: $voice\n"; #NDF.
##print "Expires: Tue, 12 Mar 2012 04:00:25 GMT\n\n";
print "\n";
open(FILE, $file);
while(read(FILE, $buff, 4096)) {
print $buff;
}
close(FILE);
} else {
error("Zero size audio file. Something wrong with eSpeak?");
}
} else {
error("Fail to generate audio file. Permission deny?");
}
}
# global variable: $request_log
sub logRequest {
my ($text, $voice) = @_;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
if (-s $request_log > 1000000) {
for (my $i = 4; $i > 0; --$i) {
if (-e "$request_log.$i") {
rename("$request_log.$i", "$request_log." . ($i + 1));
}
}
rename($request_log, "$request_log.1");
}
open(REQUEST_LOG, '>>', $request_log);
printf REQUEST_LOG "[%4d%02d%02d-%02d:%02d:%02d] [%s] %s\n",
1900 + $year, $mon, $mday, $hour, $min, $sec, $voice, $text;
close(REQUEST_LOG);
}
sub error() {
my $error = shift;
open(ERROR_LOG, '>>', "$error_log");
print ERROR_LOG "$error\n";
close(ERROR_LOG);
print "Status: 400 Bad Request\n";
#print "Status: 500 Internal Server Error\n"; #NDF.?
print "Content-type: text/html\n\n";
print "ERROR: " . $error;
exit(0);
}
1;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment