Skip to content

Commit

Permalink
When 'intkey' is set the script waits for user input.
Browse files Browse the repository at this point in the history
Documentation updates.
  • Loading branch information
zaf committed Dec 13, 2011
1 parent ef13aa9 commit 4149dc4
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 61 deletions.
58 changes: 37 additions & 21 deletions README
Expand Up @@ -2,18 +2,18 @@
Google TTS script for Asterisk
=============================================

This script makes use of Google's translate text to speech service
in order to redner text to speech and play it back to the user.
This script makes use of Google's translate text to speech service
in order to redner text to speech and play it back to the user.
It supports a variety of different languages.

------------
Requirements
------------
Perl The Perl Programming Language
perl-libwww The World-Wide Web library for Perl
sox Sound eXchange, sound processing program
mpg123 MPEG Audio Player and decoder
format_sln Raw slinear module for asterisk
Perl The Perl Programming Language
perl-libwww The World-Wide Web library for Perl
sox Sound eXchange, sound processing program
mpg123 MPEG Audio Player and decoder
format_sln Raw slinear module for asterisk
Internet access in order to contact google and get the voice data.

------------
Expand All @@ -26,31 +26,47 @@ To make sure check your /etc/asterisk/asterisk.conf file
-----
Usage
-----
agi(googletts.agi,text,language,intkey): This will invoke the Google TTS engine,
render the text string to speech and play it back to the user,
allowing any given interrupt keys to immediately terminate and return.
The script contacts google's TTS service in order to get the voice data
agi(googletts.agi,text,[language],[intkey]): This will invoke the Google TTS engine,
render the text string to speech and play it back to the user. If 'intkey' is set
the script will wait for user input. Any given interrupt keys will cause the playback
to immediately terminate and the dialplan to proceed to the matching extension (for use in IVR).

The script contacts google's TTS service in order to get the voice data
which then stores in a local cache (by default /tmp/) for future use.
Parameters like default language, enabling or disabling caching and cache dir
can be set up by editing the script.

--------
--------
Examples
--------
dialplan sample code for your extensions.conf
sample dialplan code for your extensions.conf

;GoogleTTS Demo
;PLayback messages to user

exten => 1234,1,Answer()

;;Play mesage in English:
exten => 1234,n,agi(googletts.agi,"This is a simple google text to speech test in english.",en,any)

exten => 1234,n,agi(googletts.agi,"This is a simple google text to speech test in english.",en)
;;Play message in Spanish
exten => 1234,n,agi(googletts.agi,"Esta es una simple prueba en español.",es,any)

exten => 1234,n,agi(googletts.agi,"Esta es una simple prueba en español.",es)
;;Play message in Greek
exten => 1234,n,agi(googletts,agi,"Αυτό είναι ένα απλό τέστ στα ελληνικά.",el,any)
exten => 1234,n,agi(googletts,agi,"Αυτό είναι ένα απλό τέστ στα ελληνικά.",el)

;A simple dynamic IVR using GoogleTTS

[my_ivr]
exten => s,1,Answer()
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,agi(googletts.agi,"Welcome to my small interactive voice response menu.",en)
;;Wait for digit:
exten => s,n(start),agi(googletts.agi,"Please dial a digit.",en,any)

;;PLayback the name of the digit and wait for another one:
exten => _X,1,agi(googletts.agi,"You just pressed ${EXTEN}. Try another one please.",en,any)

exten => i,1,agi(googletts.agi,"Invalid extension.",en)
exten => i,n,goto(s,start)
exten => h,1,Hangup()

-------------------
Supported Languages
Expand Down Expand Up @@ -78,6 +94,6 @@ Supported Languages
-------
License
-------
The GoogleTTS script for asterisk is distributed under the GNU General Public
The GoogleTTS script for asterisk is distributed under the GNU General Public
License v2. See COPYING for details.

150 changes: 110 additions & 40 deletions googletts.agi
Expand Up @@ -12,19 +12,22 @@
# -----
# Usage
# -----
# agi(googletts.agi,"text",language,intkey): This will invoke the Google TTS engine,
# render the text string to speech and play it back to the user,
# allowing any given interrupt keys to immediately terminate and return.
# agi(googletts.agi,"text",[language],[intkey]): This will invoke the Google TTS engine,
# render the text string to speech and play it back to the user. If 'intkey' is set the
# script will wait for user input. Any given interrupt keys will cause the playback
# to immediately terminate and the dialplan to proceed to the matching extension
# (this is mainly for use in IVR, see README for examples).
#
# The script contacts google's TTS service in order to get the voice data
# which then stores in a local cache (by default /tmp/) for future use.
#
#
# Parameters like default language, sample rate, caching and cache dir
# can be set up by altering the following variables:
# Default langeuage: $lang
# Sample rate: $samplerate
# Chace: $usecache
# Chache directory: $cachedir
#
#

use warnings;
use strict;
Expand All @@ -36,21 +39,33 @@ use LWP::UserAgent;

$| = 1;

# ----------------------------- #
# User defined parameters: #
# ----------------------------- #
# Default language #
my $lang = "en"; #
# Output audio sample rate #
my $samplerate = 8000; #
# Use of cache mechanism #
my $usecache = 1; #
# Cache directory path #
my $cachedir = "/tmp"; #
# ----------------------------- #

my %AGI;
my @text;
my $line;
my $result;
my $input;
my $res;
my $filename;
my $fexten;
my $request;
my $response;
my @result;
my $scriptname;
my $lang = "en";
my $samplerate = 8000;
my $usecache = 1;
my $cachedir = "/tmp";
my $intkey = "";
my $tmpdir = "/tmp";
my $timeout = 5000;
my $url = "http://translate.google.com/translate_tts";
my $sox = `/usr/bin/which sox`;
my $mpg123 = `/usr/bin/which mpg123`;
Expand All @@ -72,7 +87,7 @@ if (!$AGI{arg_1}) {
# Check for required programs #
if (!$sox || !$mpg123) {
print STDERR "$scriptname sox or mpg123 is missing. Aborting.\n";
exit 0;
exit -1;
} else {
chomp($sox);
chomp($mpg123);
Expand All @@ -81,10 +96,11 @@ if (!$sox || !$mpg123) {
# Sanitising input #
$AGI{arg_1} =~ s/[\\\/|*~<>^\(\)\[\]\{\}\n\r]/ /g;
$AGI{arg_1} =~ s/&/%26/g;
$AGI{arg_1} =~ s/\+/%2B/g;
$AGI{arg_1} =~ s/^\s+|\s+$//g;
if (length($AGI{arg_1}) == 0) {
print STDERR "$scriptname No text passed for synthesis.\n";
exit 0;
exit -1;
} elsif (length($AGI{arg_1}) > 100) {
# Split input to comply with google tts requirements #
$AGI{arg_1} .= ".";
Expand All @@ -94,11 +110,15 @@ if (length($AGI{arg_1}) == 0) {
}

# Setting language #
$lang = $AGI{arg_2} if ($AGI{arg_2} =~ /^[a-z]{2}(-[a-zA-Z]{2,6})?$/);
if ($AGI{arg_2}) {
$lang = $AGI{arg_2} if ($AGI{arg_2} =~ /^[a-z]{2}(-[a-zA-Z]{2,6})?$/);
}

# Setting interrupt keys #
$intkey = "0123456789#*" if ($AGI{arg_3} eq "any");
$intkey = $AGI{arg_3} if ($AGI{arg_3} =~ /^[0-9*#]+$/);
if ($AGI{arg_3}) {
$intkey = "0123456789#*" if ($AGI{arg_3} eq "any");
$intkey = $AGI{arg_3} if ($AGI{arg_3} =~ /^[0-9*#]+$/);
}

# Setting filename extension according to sample rate. #
if ($samplerate == 8000) {
Expand All @@ -122,11 +142,21 @@ if ($usecache && ((length($cachedir) + 38) > 2048)) {

# Answer channel if not already answered #
print "CHANNEL STATUS\n";
$result = <STDIN>;
if (&checkresult($result) == 4) {
$input = <STDIN>;
@result = &checkinput($input);
if ($result[0] == 4) {
print "ANSWER\n";
$result = <STDIN>;
&checkresult($result);
$input = <STDIN>;
&checkinput($input);
}

# Get digit timeout #
print "GET VARIABLE TIMEOUT(digit)\n";
$input = <STDIN>;
@result = &checkinput($input);
if ($result[0] == 1) {
$result[1] =~ s/[\(\).]//g;
$timeout = $result[1];
}

my $ua = LWP::UserAgent->new;
Expand All @@ -140,18 +170,20 @@ foreach $line (@text) {
$filename = md5_hex($line);
# Stream file from cache if it exists #
if (-r "$cachedir/$filename.$fexten") {
print STDERR "$scriptname File already in cache.\n";
print "STREAM FILE $cachedir/$filename \"$intkey\"\n";
$result = <STDIN>;
&checkresult($result);
#print STDERR "$scriptname File already in cache.\n";
$res = &playback("$cachedir/$filename", $intkey);
last if ($res > 0);
next;
}
}
# Format text string, convert whitespace to + #
$line =~ s/\s+/+/g;
$request = HTTP::Request->new('GET' => "$url?tl=$lang&q=$line");
$response = $ua->request($request);
if ($response->is_success) {
if (!$response->is_success) {
print STDERR "$scriptname Failed to fetch file.\n";
exit -1;
} else {
my ($fh, $tmpname) = tempfile("ggl_XXXXXX", DIR => $tmpdir, UNLINK => 1);
$SIG{"HUP"} = \&cleanup("$tmpname");
open($fh, ">", "$tmpname.mp3") or die "$scriptname Cannot open file: $!";
Expand All @@ -162,52 +194,90 @@ foreach $line (@text) {
if ($? == -1) {
print STDERR "$scriptname Failed to execute mpg123: $!\n";
&cleanup("$tmpname");
last;
exit -1;
}
system($sox, $tmpname.".wav", "-r", $samplerate, "-t", "raw", "$tmpname.$fexten");
if ($? == -1) {
print STDERR "$scriptname Failed to execute sox: $!\n";
&cleanup("$tmpname");
last;
exit -1;
}
print "STREAM FILE $tmpname \"$intkey\"\n";
my $result = <STDIN>;
&checkresult($result);
$res = &playback($tmpname, $intkey);
# Save file in cache #
if ($usecache) {
mkpath("$cachedir") if (!(-d "$cachedir"));
print STDERR "$scriptname Saving file to cache.\n";
copy("$tmpname.$fexten", "$cachedir/$filename.$fexten");
}
&cleanup("$tmpname");
} else {
print STDERR "$scriptname Failed to fetch file.\n";
last if ($res > 0);
}
}

if ($intkey ne "" && $res == 0) {
print "WAIT FOR DIGIT $timeout\n";
$input = <STDIN>;
@result = &checkinput($input);
if ($result[0] > 0) {
print "SET EXTENSION ", chr($result[0]), "\n";
print "SET PRIORITY 1\n";
}
}
exit 0;

sub cleanup {
my ($name) = @_;
my $exten;
foreach $exten ("mp3", "wav", $fexten) {
unlink("$name.$exten") if (-w "$name.$exten");
my $file_exten;
foreach $file_exten ("mp3", "wav", $fexten) {
unlink("$name.$file_exten") if (-w "$name.$file_exten");
}
}

sub checkresult {
sub checkinput {
my ($res) = @_;
my @values;

chomp $res;
if ($res =~ /^200/) {
$res =~ /result=(-?\d+)/;
$res =~ /result=(-?\d+)\s?(.*)/;
if (!length($1)) {
print STDERR "$scriptname Command filed: ($res)\n";
return -1;
@values = ("-1");
} else {
print STDERR "$scriptname Command returned: ($res)\n";
$1;
#print STDERR "$scriptname Command returned: ($res)\n";
@values = ("$1", "$2");
}
} else {
print STDERR "$scriptname Unexpected result '$res'\n";
return -1;
@values = ("-1");
}
}

sub playback {
my ($file, $keys) = @_;
my $response;
my @result;

if ($keys eq "") {
print "STREAM FILE $file \"\"\n";
$response = <STDIN>;
@result = &checkinput($response);
if ($result[0] == -1) {
print STDERR "$scriptname Failed to play $file.\n";
return -1;
}
} else {
print "STREAM FILE $file \"$keys\"\n";
$response = <STDIN>;
@result = &checkinput($response);
if ($result[0] > 0) {
print "SET EXTENSION ", chr($result[0]), "\n";
print "SET PRIORITY 1\n";
return $result[0];
} elsif ($result[0] == -1) {
print STDERR "$scriptname Failed to play $file.\n";
return -1;
}
}
return 0;
}

0 comments on commit 4149dc4

Please sign in to comment.