http://qs321.pair.com?node_id=465955
Category: Chatterbox Clients
Author/Contact Info atcroft
Description:

Retrieves data from the Chatterbox (CB) XML ticker, and passes it to a running Festival server instance for output as simulated speech. Uses the LWP::Simple module to retrieve the data, XML::Simple to process the data, and the Festival::Client module to communicate with the Festival server. It uses the epoch time of each comment to determine whether or not it has output a particular line to the Festival server already.

Known issues -

  • The fixed delay between sending lines of text to the Festival server was a kludge to resolve an issue seen while testing, where sending the lines too quickly appeared to result in some of them being "lost"-although I am not sure where it might be occurring. On my system, 7s seemed to work okay for this value.
  • The delay setting of 90s between CB retrievals was an attempt to be a nice netizen.
  • Should it take longer than the CB retrieval delay to output all of the waiting content (because of the speech delay being used), the code will sleep for only 1s before the next retrieval.
  • The time for the program to run is based off a period to run for, again attempting to be a good netizen and not wanting to leave copies running that might be taking up resources.
  • What is read is the raw CB text, so it may sound a little strange. This was as much proof of concept as anything, so no attempt was made to make the content sent to Festival more understandable. So, when running, you may hear things such as URLS, left/right brackets, and some tags actually spoken.

Bugs - Aside from the "Known Issues" above, none known yet (the latter being the operative word).

Update: 12 Jun 2005 - Found bug in computing $max_retrievals (parentheses were not placed correctly). Code updated with correct version.

Update: 12 Jun 2005 - Received a question from demerphq wondering why I had used the epoch value rather than the message_id. The reason was because I wasn't paying attention. Code updated to use message_id rather than epoch. I do appreciate the feedback, demerphq-thank you. (And anyone else seeing anything amiss, please let me know.)

#!/usr/bin/perl

use strict;
use warnings;

use Festival::Client;
use LWP::Simple;
use XML::Simple;

$| = 1;

# Information for Festival server to connect to
#   (undef as port value to use default port)
my %festival_server = (
    host => q{localhost},
    port => undef,
);

# The following values are in seconds.
# $delay - approximate delay between CB XML Ticker retrievals
# $speech_delay - approximate delay between spoken lines
my $delay        = 90;
my $speech_delay = 7;

# $seen - holds the message_id of last message seen
# $max_retrievals - number of times to retrieve CB content
#   before exiting (as I did not want to allow the process
#   to run forever). This is computed as time to run divided
#   by the retrieval delay (both in seconds).
my $seen = 0;
my ($max_retrievals);
{
    # Time period to run for. (0d 0h 30m 0s, for testing)
    my %to_run = (
        days    => 0,
        hours   => 0,
        minutes => 30,
        seconds => 0,
    );
    $max_retrievals = int(
        (
            (
                ( $to_run{days} * 24 + $to_run{hours} ) * 60 +
                  $to_run{minutes}
            ) * 60 + $to_run{seconds}
        ) / $delay
    );
}

# Display CB content and number of messages spoken during
#   retrieval period (set true to display)
my $display_messages = 0;

# Setup
my $xs         = new XML::Simple;
my $cb_xml_url =
  q{http://www.perlmonks.org/index.pl?node_id=207304};
my $fs =
  Festival::Client->new( $festival_server{host},
    $festival_server{port} )
  or die (
    qq{Could not connect to Festival server at }
      . join (
        ':',
        $festival_server{host},
        (
            defined(
                $festival_server{port}
                ? $festival_server{port}
                : 1314
            )
        )
      )
      . qq{: $!\n}
  );

my $format = sprintf( qq{(%%0%dd) %%s - %%s: %%s\n},
    length($max_retrievals) );
print length($max_retrievals), " ", $max_retrievals, "\n";

while ($max_retrievals) {
    my $said = 0;

    my $cb_xml = get($cb_xml_url)
      or die (qq{Could not retrieve CB XML ticker: $!\n});
    my $ref = $xs->XMLin( $cb_xml, ForceArray => [q{message}] )
      or die (qq{Could not parse CB XML: $!\n});

    # print Data::Dumper->Dump( [ \$ref ], [qw(*ref)] ), "\n";

    # Check to see if there are message entries
    if ( exists( $ref->{message} ) ) {

        # print ref( $ref->{message} ), "\n";
        foreach my $message ( @{ $ref->{message} } ) {
            if ( $message->{message_id} > $seen ) {
                $seen = $message->{message_id};

                $fs->say(
                    join ( qq{\t},
                        $message->{author}, $message->{text} )
                      . qq{\n}
                );
                $said++;
                printf $format, $max_retrievals,
                  scalar localtime $message->{epoch},
                  $message->{author}, $message->{text}
                  if ($display_messages);

              # I experienced a problem with the content being
              #   spoken being "lost" if I did not delay between
              #   sending lines of content. Thus, the delay here.
                sleep($speech_delay);
            }
        }
        print qq{Said: $said\n} if ($display_messages);
    }
    $max_retrievals--;
    sleep(
        $speech_delay * $said < $delay
        ? $delay - ( $speech_delay * $said )
        : 1
    );
}
Replies are listed 'Best First'.
Re: Chatterbox to Festival Server (TTS output)
by planetscape (Chancellor) on Jul 13, 2005 at 04:48 UTC

    Thank you, atcroft!

    Because I run WinXP and had trouble installing Festival under Cygwin, I modified this (slightly) to use Microsoft Direct Speech Synthesis (part of the Microsoft Speech API). My modifications were based largely on code found here, and of course, lots of assistance from, and hand-holding by, atcroft himself. (Thanks again!)

    (99.99% of this is still atcroft's work, and he deserves the credit.)


    #!/usr/bin/perl # by [atcroft] - from [id://465955] # *minor* mods by [planetscape], based largely on code found here: # http://www.windowsitpro.com/WindowsScripting/Article/ArticleID/20796 +/20796.html # CB2Speech.pl use strict; use warnings; use LWP::Simple; use XML::Simple; use Win32::OLE qw( EVENTS ); my $DirectSS = new Win32::OLE( "{EEE78591-FE22-11D0-8BEF-0060081841DE} +" ); Win32::OLE->WithEvents( $DirectSS, \&ProcessEvents, "{EEE78597-FE22-11 +D0-8BEF-0060081841DE}" ); my $fSpeaking; $| = 1; my $delay = 90; my $seen = 0; my ($max_retrievals); { my %to_run = ( days => 0, hours => 0, minutes => 30, seconds => 0, ); $max_retrievals = int( ( ( ( $to_run{days} * 24 + $to_run{hours} ) * 60 + $to_run{minutes} ) * 60 + $to_run{seconds} ) / $delay ); } my $display_messages = 0; my $xs = new XML::Simple; my $cb_xml_url = q{http://www.perlmonks.org/index.pl?node_id=207304}; my $format = sprintf( qq{(%%0%dd) %%s - %%s: %%s\n}, length($max_retrievals) ); print length($max_retrievals), " ", $max_retrievals, "\n"; while ($max_retrievals) { my $said = 0; my $cb_xml = get($cb_xml_url) or die (qq{Could not retrieve CB XML ticker: $!\n}); my $ref = $xs->XMLin( $cb_xml, ForceArray => [q{message}] ) or die (qq{Could not parse CB XML: $!\n}); if ( exists( $ref->{message} ) ) { foreach my $message ( @{ $ref->{message} } ) { if ( $message->{message_id} > $seen ) { $seen = $message->{message_id}; my $Phrase = (join ( qq{\t}, $message->{author}, $message->{text} ) . qq{\n} ); $DirectSS->Speak( $Phrase ); $fSpeaking = 1; while( $fSpeaking ) { Win32::OLE->SpinMessageLoop(); Win32::Sleep( 100 ); } $said++; printf $format, $max_retrievals, scalar localtime $message->{epoch}, $message->{author}, $message->{text} if ($display_messages); } } print qq{Said: $said\n} if ($display_messages); } $max_retrievals--; sleep($delay); } sub ProcessEvents { my( $Obj, $Event, @Args ) = @_; if( 4 == $Event ) { $fSpeaking = 0; } }
    planetscape