Modified Weather.agi to Work with Cepstral

Download and install Cepstral following these directions.

http://www.oldskoolphreak.com/tfiles/voip/installing_app_cepstral.txt

One mistake is at the end of the doc.
"Now that Asterisk is installed/rebuilt, make sure your /etc/ld.so.conf file has
/opt/swift/libs""

Should read /opt/switf/lib (without the s)

Trixbox and FreePBX users that have the 1.2 branch of Asterisk may wish to use the instructions in this thread instead, but be sure to read the comments following the original post:

http://www.trixbox.org/modules/newbb/viewtopic.php?topic_id=3984&forum=1#forumpost37963

I have changed the wording and direction to be more friendly and point to the weather forecast at BWI airport but that should be pretty straightforward to modify to your liking and area.

Now overwrite weather.agi with this (this may no longer work due to changes on the weather.noaa.gov site, see below for an alternative):

#!/usr/bin/perl -w

use Asterisk::AGI;
use Net::FTP;

my $AGI = new Asterisk::AGI;
my $server = "weather.noaa.gov";
my $basepath = "data/forecasts";
my $custpath = "city/md";
my $filename = "baltimore-washington_intl_airport.txt";

my @lines;

$AGI->exec('Cepstral',"|\"National Weather Service's latest weather forecast for Baltimore Washingtion International Airport. Press 1 to skip ahead.\"");

$ftp = Net::FTP->new($server, Debug => 0)
or die "Cannot connect to $server: $@";


$ftp->login("anonymous","a\@b.com")
or die "Cannot login with anonymous/a\@b.com", $ftp->message;


$ftp->cwd("$basepath/$custpath")
or die "Cannot change working directory ", $ftp->message;

$ftp->binary;

$ftp->get($filename,"/tmp/$filename.$$") || die "Error while downloading /tmp/$filename.$$!!!\n", $ftp->message;

open(INPUT,"</tmp/$filename.$$") || die "Could not open /tmp/$filename.$$\n";
while(<INPUT>) {
if(/^\./) {
chomp;
s/^\.//;
push(@lines,$_);
print "$_\n";
}
}

foreach(@lines) {
$AGI->exec('Cepstral',"|\"$_\"");
}

$AGI->exec('Cepstral',"|\"That concludes the extended weather forcast, thank you and call again, goodbye.\"");

unlink("/tmp/$filesname.$$");

exit;

Alternative change

(Not by the original poster:) I further modified the weather.agi routine to eliminate the unnecessary and unnatural pauses between text lines, and to use the NWS zone forecasts instead of the city forecasts, which are apparently no longer available in some areas. Note that you will need to know what "zone" your county is in, and the only way to find that out may be to call the National Weather Service or a local broadcaster. Don't just pull up zone forecasts at random until you see your city mentioned in one of them, because often the same forecast text will be sent out to a number of the zones - only by using the zone that specifically includes your county will you get your local forecast every time you call.

(Zone Index can be found at:
ftp://tgftp.nws.noaa.gov/data/products/zones.txt )

(If you are more patient, another possible way to discover your local zone is to enter a URL in the following format:

http://www.crh.noaa.gov/product.php?site=DTX&issuedby=DTX&product=ZFP&format=CI&version=7&glossary=0

But replace both instances of "DTX" with the airport code for the nearest weather service office that issues forecasts for your county. You should see several zone forecasts and each will mention one or more counties and one or more zones in the headers near the top. For example, if you are lucky you will see something like:

MIZ069-012315-
OAKLAND-

Means that the forecast is only for Zone 69 which only covers Oakland County.

But if you are not that lucky, you will see something like:

MIZ059-066-067-073-074-011515-
CLINTON-EATON-INGHAM-CALHOUN-JACKSON-

Which means the forecast covers five zones and five counties. So you may need to access that page over several days to figure out which zone number corresponds to your county.)

There is a subroutine in this code that does some translations for correct pronunciation and cadence. This may sometimes require deliberate misspelling of certain words in the translated output. For example, WIND is pronounced as if you wanted to WIND an old-style alarm clock or an hand-cranked generator, so by deliberately misspelling it as WEND, it is correctly pronounced in the way you'd expect in a weather report. The word CLEAR had a weird effect - although the word was pronounced correctly, a small but disconcerting amout of dead silence immediately followed, and substituting the phonetic spelling KLEAR got rid of the silence. New words can be added to the subroutine if necessary to correct the pronunciation.

Added March 16, 2007: Some new code has been added to this routine that checks a different location for current watches, warnings and advisories before delivering the extended forecast. This part of the routine is new and probably still needs some work to compensate for the way the text is formatted (it comes in an XML file from the NWS web site, rather than from an FTP site). This also means that you now need to have the Perl XML::Simple and LWP::Simple modules installed on your system, in addition to the Asterisk::AGI and Net::FTP modules that were previously required. Check back here from time to time to see if this code has been modified, as it probably will be once I've had a chance to see more warnings/watches/advisories and make any necessary adjustments.

Note you will probably need to make some changes in the variables near the top, and perhaps in the $AGI->exec statements (particularly if you wish to use a different voice). This code also demonstrates the use of prosody tags to increase the rate of speech; you can discover other ways to use SSML markup tags at Using SSML with Cepstral Voices. Also note that due to length, some lines may display here as split when they really aren't, however if you copy-and-paste from what is displayed here to a text file on your system, the lines should be connected properly (at least it works that way when copying in Firefox, can't say for sure if it works that way in all browsers).

#!/usr/bin/perl

use Asterisk::AGI;
use Net::FTP;
use XML::Simple;
use LWP::Simple;

$AGI = new Asterisk::AGI;
$xml = new XML::Simple;
$warnings = "http://www.weather.gov/alerts/wwarssget.php?zone=MIZ076";
$server = "tgftp.nws.noaa.gov";
$basepath = "data/forecasts";
$custpath = "zone/mi";
$filename = "miz076.txt";
$location = "Detroit";
$tempfile = "/tmp/$filename.$$";
$prevline = "";

# The following values must be in UPPERCASE
$county = "WAYNE";
$tzd = "EDT";
$tzdlong = "EASTERN DAYLIGHT TIME";
$tzs = "EST";
$tzslong = "EASTERN STANDARD TIME";

sub dosubs {
$prevline =~ s/^\.+//;
$prevline =~ s/\.+\W/\,/g;
$prevline =~ s/\bAM\b/A M/g;
$prevline =~ s/\b20S\b/TWENTIES/g;
$prevline =~ s/\b30S\b/THIRTIES/g;
$prevline =~ s/\b40S\b/FORTIES/g;
$prevline =~ s/\b50S\b/FIFTIES/g;
$prevline =~ s/\b60S\b/SIXTIES/g;
$prevline =~ s/\b70S\b/SEVENTIES/g;
$prevline =~ s/\b80S\b/EIGHTIES/g;
$prevline =~ s/\b90S\b/NINTIES/g;
$prevline =~ s/\b100S\b/HUNDREDS/g;
$prevline =~ s/\bCLEAR\b/KLEAR/g;
$prevline =~ s/\bDAYBREAK\b/DAY BRAKE/g;
$prevline =~ s/\bFOR MORE INFORMATION\b.*//g;
$prevline =~ s/\bMIDDAY\b/MID DAY/g;
$prevline =~ s/\bWIND\b/WEND/g;
$prevline =~ s/\b$tzd\b/$tzdlong/g;
$prevline =~ s/\b$tzs\b/$tzslong/g;
$prevline = "<prosody rate='1.1'>".$prevline."</prosody>";
}

$AGI->exec('AGI',"cepstral.pl|David^\"<prosody rate='1.2'>Here is the latest weather forecast for $location:</prosody>\"");

$data = $xml->XMLin(get($warnings), forcearray => [ 'item' ]);
foreach $e (@{$data->{channel}->{item}})
{
$prevline = $e->{description};
$prevline =~ s/\s*\$\$.*//g;
($prevline,$last) = split(/<br>\s*\.(?=NOW)/, $prevline);
if ($last ne "") {
$prevline = $last;
}
$prevline =~ s/\s*<br>\s*/ /g;
@warnings = split(/\*/, $prevline);
foreach $prevline (@warnings) {
dosubs;
$AGI->exec('AGI',"cepstral.pl|David^\"$prevline\"");
}
}

$ftp = Net::FTP->new($server, Debug => 0)
or die "Cannot connect to $server: $@";


$ftp->login("anonymous","a\@b.com")
or die "Cannot login with anonymous/a\@b.com", $ftp->message;


$ftp->cwd("$basepath/$custpath")
or die "Cannot change working directory ", $ftp->message;

$ftp->binary;

$ftp->get($filename,"$tempfile") || die "Error while downloading $tempfile!!!\n", $ftp->message;

open(INPUT,"<$tempfile") || die "Could not open $tempfile\n";

while(<INPUT>) {
chomp;
last if /$county/;
}

while(<INPUT>) {
chomp;
last if /$tzd/;
last if /$tzs/;
}

while(<INPUT>) {
chomp;
$prevline = $_;
last if /^\./;
}

while(<INPUT>) {
chomp;
if(/^\./) {
dosubs;
$AGI->exec('AGI',"cepstral.pl|David^\"$prevline\"");
$prevline = $_;
} else {
$prevline .= " ".$_;
}
}

dosubs;
$prevline =~ s/\s*\$+.*(?=<\/prosody>)//;
$AGI->exec('AGI',"cepstral.pl|David^\"$prevline\"");
$AGI->exec('AGI',"cepstral.pl|David^\"<prosody rate='1.2'>That concludes the extended weather forecast for $location.</prosody>\"");

unlink("$tempfile");

exit;

The above assumes that you have a cepstral.pl that looks like this (adapted from a Trixbox forum post and subsequent comments at http://www.trixbox.org/modules/newbb/viewtopic.php?topic_id=3984&forum=1#forumpost37963):

#!/usr/bin/perl

use Asterisk::AGI;
use File::Basename;
require Data::UUID;

$AGI = new Asterisk::AGI;

my $ug = new Data::UUID;

my $timestamp = gmtime;
my %input = $AGI->ReadParse();
my ($text)=@ARGV;
my $voicename = "Diane";
my $idx = index($text, '^');
if ( $idx > 0 ) {
$voicename = substr( $text, 0, $idx );
$text = substr( $text, $idx + 1);
}
my $hash = $ug->create_str;
my $sounddir = "/var/lib/asterisk/sounds/tts";
my $wavefile = "$sounddir/"."tts-$hash.wav";
my $t2wp= "/opt/swift/bin/";

unless (-f $wavefile) {
open(fileOUT, ">$sounddir"."/say-text-$hash.txt");
print fileOUT "$text";
close(fileOUT);
my $execf=$t2wp."swift -n $voicename -f $sounddir/say-text-$hash.txt -p audio/channels=1,audio/sampling-rate=8000,audio/deadair=2 -o $wavefile";
system($execf);
unlink($sounddir."/say-text-$hash.txt");
}

$AGI->stream_file('tts/'.basename($wavefile,".wav"));

Yet another alternate method
Asterisk tips Weather PHP - Standard weather for 1 location you define
Asterisk tips Airport Weather PHP - Pulls the XML file from the weather site for airport codes you define
Download and install Cepstral following these directions.

http://www.oldskoolphreak.com/tfiles/voip/installing_app_cepstral.txt

One mistake is at the end of the doc.
"Now that Asterisk is installed/rebuilt, make sure your /etc/ld.so.conf file has
/opt/swift/libs""

Should read /opt/switf/lib (without the s)

Trixbox and FreePBX users that have the 1.2 branch of Asterisk may wish to use the instructions in this thread instead, but be sure to read the comments following the original post:

http://www.trixbox.org/modules/newbb/viewtopic.php?topic_id=3984&forum=1#forumpost37963

I have changed the wording and direction to be more friendly and point to the weather forecast at BWI airport but that should be pretty straightforward to modify to your liking and area.

Now overwrite weather.agi with this (this may no longer work due to changes on the weather.noaa.gov site, see below for an alternative):

#!/usr/bin/perl -w

use Asterisk::AGI;
use Net::FTP;

my $AGI = new Asterisk::AGI;
my $server = "weather.noaa.gov";
my $basepath = "data/forecasts";
my $custpath = "city/md";
my $filename = "baltimore-washington_intl_airport.txt";

my @lines;

$AGI->exec('Cepstral',"|\"National Weather Service's latest weather forecast for Baltimore Washingtion International Airport. Press 1 to skip ahead.\"");

$ftp = Net::FTP->new($server, Debug => 0)
or die "Cannot connect to $server: $@";


$ftp->login("anonymous","a\@b.com")
or die "Cannot login with anonymous/a\@b.com", $ftp->message;


$ftp->cwd("$basepath/$custpath")
or die "Cannot change working directory ", $ftp->message;

$ftp->binary;

$ftp->get($filename,"/tmp/$filename.$$") || die "Error while downloading /tmp/$filename.$$!!!\n", $ftp->message;

open(INPUT,"</tmp/$filename.$$") || die "Could not open /tmp/$filename.$$\n";
while(<INPUT>) {
if(/^\./) {
chomp;
s/^\.//;
push(@lines,$_);
print "$_\n";
}
}

foreach(@lines) {
$AGI->exec('Cepstral',"|\"$_\"");
}

$AGI->exec('Cepstral',"|\"That concludes the extended weather forcast, thank you and call again, goodbye.\"");

unlink("/tmp/$filesname.$$");

exit;

Alternative change

(Not by the original poster:) I further modified the weather.agi routine to eliminate the unnecessary and unnatural pauses between text lines, and to use the NWS zone forecasts instead of the city forecasts, which are apparently no longer available in some areas. Note that you will need to know what "zone" your county is in, and the only way to find that out may be to call the National Weather Service or a local broadcaster. Don't just pull up zone forecasts at random until you see your city mentioned in one of them, because often the same forecast text will be sent out to a number of the zones - only by using the zone that specifically includes your county will you get your local forecast every time you call.

(Zone Index can be found at:
ftp://tgftp.nws.noaa.gov/data/products/zones.txt )

(If you are more patient, another possible way to discover your local zone is to enter a URL in the following format:

http://www.crh.noaa.gov/product.php?site=DTX&issuedby=DTX&product=ZFP&format=CI&version=7&glossary=0

But replace both instances of "DTX" with the airport code for the nearest weather service office that issues forecasts for your county. You should see several zone forecasts and each will mention one or more counties and one or more zones in the headers near the top. For example, if you are lucky you will see something like:

MIZ069-012315-
OAKLAND-

Means that the forecast is only for Zone 69 which only covers Oakland County.

But if you are not that lucky, you will see something like:

MIZ059-066-067-073-074-011515-
CLINTON-EATON-INGHAM-CALHOUN-JACKSON-

Which means the forecast covers five zones and five counties. So you may need to access that page over several days to figure out which zone number corresponds to your county.)

There is a subroutine in this code that does some translations for correct pronunciation and cadence. This may sometimes require deliberate misspelling of certain words in the translated output. For example, WIND is pronounced as if you wanted to WIND an old-style alarm clock or an hand-cranked generator, so by deliberately misspelling it as WEND, it is correctly pronounced in the way you'd expect in a weather report. The word CLEAR had a weird effect - although the word was pronounced correctly, a small but disconcerting amout of dead silence immediately followed, and substituting the phonetic spelling KLEAR got rid of the silence. New words can be added to the subroutine if necessary to correct the pronunciation.

Added March 16, 2007: Some new code has been added to this routine that checks a different location for current watches, warnings and advisories before delivering the extended forecast. This part of the routine is new and probably still needs some work to compensate for the way the text is formatted (it comes in an XML file from the NWS web site, rather than from an FTP site). This also means that you now need to have the Perl XML::Simple and LWP::Simple modules installed on your system, in addition to the Asterisk::AGI and Net::FTP modules that were previously required. Check back here from time to time to see if this code has been modified, as it probably will be once I've had a chance to see more warnings/watches/advisories and make any necessary adjustments.

Note you will probably need to make some changes in the variables near the top, and perhaps in the $AGI->exec statements (particularly if you wish to use a different voice). This code also demonstrates the use of prosody tags to increase the rate of speech; you can discover other ways to use SSML markup tags at Using SSML with Cepstral Voices. Also note that due to length, some lines may display here as split when they really aren't, however if you copy-and-paste from what is displayed here to a text file on your system, the lines should be connected properly (at least it works that way when copying in Firefox, can't say for sure if it works that way in all browsers).

#!/usr/bin/perl

use Asterisk::AGI;
use Net::FTP;
use XML::Simple;
use LWP::Simple;

$AGI = new Asterisk::AGI;
$xml = new XML::Simple;
$warnings = "http://www.weather.gov/alerts/wwarssget.php?zone=MIZ076";
$server = "tgftp.nws.noaa.gov";
$basepath = "data/forecasts";
$custpath = "zone/mi";
$filename = "miz076.txt";
$location = "Detroit";
$tempfile = "/tmp/$filename.$$";
$prevline = "";

# The following values must be in UPPERCASE
$county = "WAYNE";
$tzd = "EDT";
$tzdlong = "EASTERN DAYLIGHT TIME";
$tzs = "EST";
$tzslong = "EASTERN STANDARD TIME";

sub dosubs {
$prevline =~ s/^\.+//;
$prevline =~ s/\.+\W/\,/g;
$prevline =~ s/\bAM\b/A M/g;
$prevline =~ s/\b20S\b/TWENTIES/g;
$prevline =~ s/\b30S\b/THIRTIES/g;
$prevline =~ s/\b40S\b/FORTIES/g;
$prevline =~ s/\b50S\b/FIFTIES/g;
$prevline =~ s/\b60S\b/SIXTIES/g;
$prevline =~ s/\b70S\b/SEVENTIES/g;
$prevline =~ s/\b80S\b/EIGHTIES/g;
$prevline =~ s/\b90S\b/NINTIES/g;
$prevline =~ s/\b100S\b/HUNDREDS/g;
$prevline =~ s/\bCLEAR\b/KLEAR/g;
$prevline =~ s/\bDAYBREAK\b/DAY BRAKE/g;
$prevline =~ s/\bFOR MORE INFORMATION\b.*//g;
$prevline =~ s/\bMIDDAY\b/MID DAY/g;
$prevline =~ s/\bWIND\b/WEND/g;
$prevline =~ s/\b$tzd\b/$tzdlong/g;
$prevline =~ s/\b$tzs\b/$tzslong/g;
$prevline = "<prosody rate='1.1'>".$prevline."</prosody>";
}

$AGI->exec('AGI',"cepstral.pl|David^\"<prosody rate='1.2'>Here is the latest weather forecast for $location:</prosody>\"");

$data = $xml->XMLin(get($warnings), forcearray => [ 'item' ]);
foreach $e (@{$data->{channel}->{item}})
{
$prevline = $e->{description};
$prevline =~ s/\s*\$\$.*//g;
($prevline,$last) = split(/<br>\s*\.(?=NOW)/, $prevline);
if ($last ne "") {
$prevline = $last;
}
$prevline =~ s/\s*<br>\s*/ /g;
@warnings = split(/\*/, $prevline);
foreach $prevline (@warnings) {
dosubs;
$AGI->exec('AGI',"cepstral.pl|David^\"$prevline\"");
}
}

$ftp = Net::FTP->new($server, Debug => 0)
or die "Cannot connect to $server: $@";


$ftp->login("anonymous","a\@b.com")
or die "Cannot login with anonymous/a\@b.com", $ftp->message;


$ftp->cwd("$basepath/$custpath")
or die "Cannot change working directory ", $ftp->message;

$ftp->binary;

$ftp->get($filename,"$tempfile") || die "Error while downloading $tempfile!!!\n", $ftp->message;

open(INPUT,"<$tempfile") || die "Could not open $tempfile\n";

while(<INPUT>) {
chomp;
last if /$county/;
}

while(<INPUT>) {
chomp;
last if /$tzd/;
last if /$tzs/;
}

while(<INPUT>) {
chomp;
$prevline = $_;
last if /^\./;
}

while(<INPUT>) {
chomp;
if(/^\./) {
dosubs;
$AGI->exec('AGI',"cepstral.pl|David^\"$prevline\"");
$prevline = $_;
} else {
$prevline .= " ".$_;
}
}

dosubs;
$prevline =~ s/\s*\$+.*(?=<\/prosody>)//;
$AGI->exec('AGI',"cepstral.pl|David^\"$prevline\"");
$AGI->exec('AGI',"cepstral.pl|David^\"<prosody rate='1.2'>That concludes the extended weather forecast for $location.</prosody>\"");

unlink("$tempfile");

exit;

The above assumes that you have a cepstral.pl that looks like this (adapted from a Trixbox forum post and subsequent comments at http://www.trixbox.org/modules/newbb/viewtopic.php?topic_id=3984&forum=1#forumpost37963):

#!/usr/bin/perl

use Asterisk::AGI;
use File::Basename;
require Data::UUID;

$AGI = new Asterisk::AGI;

my $ug = new Data::UUID;

my $timestamp = gmtime;
my %input = $AGI->ReadParse();
my ($text)=@ARGV;
my $voicename = "Diane";
my $idx = index($text, '^');
if ( $idx > 0 ) {
$voicename = substr( $text, 0, $idx );
$text = substr( $text, $idx + 1);
}
my $hash = $ug->create_str;
my $sounddir = "/var/lib/asterisk/sounds/tts";
my $wavefile = "$sounddir/"."tts-$hash.wav";
my $t2wp= "/opt/swift/bin/";

unless (-f $wavefile) {
open(fileOUT, ">$sounddir"."/say-text-$hash.txt");
print fileOUT "$text";
close(fileOUT);
my $execf=$t2wp."swift -n $voicename -f $sounddir/say-text-$hash.txt -p audio/channels=1,audio/sampling-rate=8000,audio/deadair=2 -o $wavefile";
system($execf);
unlink($sounddir."/say-text-$hash.txt");
}

$AGI->stream_file('tts/'.basename($wavefile,".wav"));

Yet another alternate method
Asterisk tips Weather PHP - Standard weather for 1 location you define
Asterisk tips Airport Weather PHP - Pulls the XML file from the weather site for airport codes you define
Created by: stotaro, Last modification: Thu 15 of Jul, 2010 (05:03 UTC) by newacct
Please update this page with new information, just login and click on the "Edit" or "Discussion" tab. Get a free login here: Register Thanks! - Find us on Google+