<?xml version="1.0" encoding="windows-1252"?>
<node id="71822" title="monkyack.pl" created="2001-04-11 19:22:42" updated="2005-08-13 13:47:02">
<type id="1748">
sourcecode</type>
<author id="9270">
jcwren</author>
<data>
<field name="doctext">
&lt;code&gt;
#!/usr/local/bin/perl -w

#
#  Invoke with './monkyack.pl'
#
#  This script delivers the chatterbox as MP3 encoded streaming audio.  It
#  uses the festival/MBROLA text-to-speech (TTS) system to create the audio
#  on the fly.
#
#  The audio quality is a little funky, since there's a lot of special symbols
#  and 'odd words' (like user names) that the TTS has to contend with.  On top
#  of that, I encode at 8K bits per second because of my limited bandwidth.
#  Nonetheless, it's kind of amusing to listen to the chatterbox.
#
#  Requires that the festival/MBROLA server be installed, preferrably on a
#  local machine.  Needs lame 3.88beta or higher (8K bits requires MPEG III
#  Layer 2.5) for the MP3 encoding.  Also needs libshout and Shout.pm.  Last,
#  but not least, you'll need a ShoutCast/IcyCast server to connect to.  I
#  I suggest the ShoutCast server.
#
#  festival/MBROLA   - http://www.cstr.ed.ac.uk/projects/festival/
#  lame              - http://www.lame.org
#  ShoutCast server  - http://www.shoutcast.com/download/
#  IcyCast server    - http://www.icecast.org/download.html
#  libshout          - http://developer.icecast.org/libshout/
#  Shout.pm          - http://developer.icecast.org/libshout/
#  LWP::Simple       - http://cpan.org
#  XML::Twig         - http://cpan.org (yay, mirod!  Great stuff!)
#  HTML::Entities    - http://cpan.org
#
#  Notes and aimless meanderings through my thoughts:
#
#    I started out using mp3stream from http://www.prilnari.com/mp3stream/  I
#    wrote a complete Inline wrapper set for it, only to find that while it
#    would often work, it would frequently disconnect from the ShoutCast
#    server for no apparent reason.  Found the Shout.pm reference in the
#    [Internet Radio] node, switched to that.  Much better success.
#
#    I've been using festival/MBROLA for a good while (I use it to TTS my
#    weather station), and just kept reusing my old socket code.  Works, but
#    it's not good enough to publish.  So I decided that I would move into
#    the 90's, and use Speech::Synthesizer.  It's not very good, and I'm being
#    really nice.  It didn't have a couple of necessary methods, and the way
#    it's written, there was no decent way to subclass them.  The main problem
#    was that give input with 2 or 3 sentences in it, it would only speak the
#    last sentence.  What the heck good is that?  Wasn't this ever tested?
#    So, instead I just call festival-client, and pass a temp file with the
#    text (supposed to be able to read from STDIN, but when kicking it off
#    with an open statement, it hangs.
#
#    Now I decided to look for that cute bit of code someone write a while
#    back for embedding images in the __DATA__ section of a script, and
#    decoding them on the fly.  I find the reference to it at [Hex Embedded
#    Images], but the code is on his no-longer-up webserver.  In passing, I
#    find that [httptech] has done something similiar, about 10 months ago.
#    It speaks the chatterbox, but only to the local machine.  It took the
#    shininess off a bit, because I didn't think this had been done before.
#    It's at [MonkTalk], which, oddly enough, this script was called when I
#    first started.  I changed the name, of course.
#
#    I've been using WinAmp 2.74 and XMMS 1.2.4.  WinAmp sounds far better
#    but periodically disconnects from the server, for no apparent reason
#    (gotta love error messages.  Or lack thereof...)  XMMS, on the other,
#    stays connect through hell and high water, but sounds like crap on a
#    8K stream.
#
#  Copyright 2000,2001(c) J.C.Wren   jcwren@jcwren.com
#  A production of Twitching Monk Software
#  No rights reserved, use as you see fit.
#  I'd like to know about it, though, just for kicks.
#
#  Version 1.00.00 - 2001/04/11 - Initial incarnation
#  Version 1.00.10 - 2001/04/12 - Fix for XML with high bit set
#

use strict;
use LWP::Simple;
use XML::Twig;
use HTML::Entities;
use POSIX ":sys_wait_h";
use Shout;

#
#  This to change to tailor the system
#
use constant cSCIP            =&gt; 'localhost';
use constant cSCPort          =&gt; 8001;
use constant cSCPassword      =&gt; 'montyhall';
use constant cSCBitRate       =&gt; 8;
use constant cSCName          =&gt; 'Chatterbox';
use constant cSCGenre         =&gt; 'Talk Radio';
use constant cSCURL           =&gt; 'http://www.tinymicros.com:8000';
use constant cSCDescription   =&gt; 'Perlmonks Chatterbox encoded by Festival/MBROLA';
use constant cFestivalSever   =&gt; 'localhost';
use constant cTTS             =&gt; '/usr/bin/festival_client --prolog mt.parms --output _temp.wav --otype wav --ttw _temp.txt';
use constant cMP3Encoder      =&gt; '/usr/local/bin/lame --silent -a -m m -b 8 -s 16 _temp.wav _temp.mp3 &gt;/dev/null';
use constant cSilence         =&gt; 'silence8m.mp3';
use constant cPerlMonks       =&gt; 'http://www.perlmonks.org/index.pl?node=chatterbox+xml+ticker';

#
#  Pretty simplistic main...
#
{
   createParmsFile () or die "Can't create Festival parameter file: $!";

   my $result = fork ();

   die ("Can't fork off a child") if !defined ($result);

   $result ? playTalkies ($result) : makeTalkies ();
}

#
#  The parent.  Scans the directory every 3 or more seconds (depending on the
#  length of the playlist), and sends the files to the server to be played.
#
sub playTalkies
{
   @_ &gt;= 1 || die "Incorrect number of arguments";

   my $kidpid = shift;
   my $conn = new Shout;

   $conn-&gt;ip (cSCIP);
   $conn-&gt;port (cSCPort);
   $conn-&gt;password (cSCPassword);
   $conn-&gt;name (cSCName);
   $conn-&gt;bitrate (cSCBitRate);
   $conn-&gt;genre (cSCGenre);
   $conn-&gt;description (cSCDescription);
   $conn-&gt;url (cSCURL);
   $conn-&gt;icy_compat (1);
   $conn-&gt;ispublic (0);

   die "Can't connect to server" if (!$conn-&gt;connect);

   while (1)
   {
      opendir (DIR, ".") or die "Can't read directory: $!";

      my @playlist = sort grep { /\d{14}\.mp3/ &amp;&amp; -f "./$_" } readdir (DIR);

      close (DIR);

      if (!scalar (@playlist))
      {
         playFile ($conn, cSilence) or die "Error while playing: " . $conn-&gt;error . "\n";
      }
      else
      {
         foreach (@playlist)
         {
            playFile ($conn, $_) or die "Error while playing $_: " . $conn-&gt;error . "\n";
            unlink $_ or die "Can't unlink file: $!";
         }
      }

      die "Child died!\n" if (waitpid ($kidpid, &amp;WNOHANG) == -1);
   }
}

#
#  Send a file to the Shoutcast/Icycast server
#
sub playFile
{
   @_ &gt;= 2 || die "Incorrect number of arguments";

   my ($conn, $file) = @_;
   my ($buff, $len);

   print scalar localtime, " Playing  $file\n";

   open (MP3, "&lt; $file") or return 0;

   while (($len = sysread (MP3, $buff, 1024)) &gt; 0)
   {
      if (!$conn-&gt;sendData ($buff, $len))
      {
         close MP3;
         return 0;
      }

      $conn-&gt;sleep;
   }

   close MP3;

   return 1;
}

#
#  Child thread that looks for new XML, and runs the text through festival
#
sub makeTalkies
{
   my %hcbxml = ();

   while (1)
   {
      getChatterXML (\%hcbxml) or die;

      chatterXmlToSpeech (\%hcbxml);

      sleep (10);
   }
}

#
#  Run through the list return by getChatterXML(), and create MP3 files with the authors
#  name and text.  The file name is the timestamp of the xml node.  If all the comments
#  are already encoded, simply return (avoid overhead of opening unnecessary festival
#  connections)
#
sub chatterXmlToSpeech
{
   @_ &gt;= 1 || die "Incorrect number of arguments";

   my $hcbxml = shift;
   my $encoded = 1;

   $encoded &amp;= $hcbxml-&gt;{$_}-&gt;{encoded} foreach keys (%$hcbxml);

   return if $encoded;

   foreach my $line (sort keys %$hcbxml)
   {
      if (!$hcbxml-&gt;{$line}-&gt;{encoded})
      {
         my $text;

         #
         #  Some minor hacking to get rid of the more problematic characters.
         #  Some rule based logic would be nice, but too much work.
         #
         $text = $hcbxml-&gt;{$line}-&gt;{text};
         $text =~ s/[\[\]\(\)\{\}"]//g;
         $text =~ s/[:;\|\/]/ /g;
         $text .= '.';

         print scalar localtime, " Encoding $line\n";

         open (TEMPFILE, "&gt;_temp.txt") or die "Can't open _temp.txt: $!";
         print TEMPFILE $hcbxml-&gt;{$line}-&gt;{author} . ", " . $text;
         close TEMPFILE;
         system (cTTS) and die "Can't run TTS encoder: $!";
         system (cMP3Encoder) and die "Can't run MP3 encoder: $!";
         rename ("_temp.mp3", "$line.mp3") or die;
         unlink ("_temp.txt");

         $hcbxml-&gt;{$line}-&gt;{encoded} = 1;
      }
   }
}

#
#  Fetch the chatterbox XML page, and return a hash, keyed by time, with the
#  author, user_id, and text fields from the XML as an anon-hash.
#
sub getChatterXML
{
   @_ &gt;= 1 || die "Incorrect number of arguments";

   my $rhcbxml = shift;
   my $xml;

   $LWP::Simple::FULL_LWP = 1;

   if ((my $xml = get (cPerlMonks)))
   {
      #
      #  Survive an [epoptai] DoS attack
      #
      $xml =~ s/[\r\n\t]//g;
      $xml =~ tr/\x80-\xff/_/;
      $xml =~ tr/\x00-\x1f/_/;

      my $twig = new XML::Twig (TwigRoots =&gt;
               { message =&gt; sub { my ($t, $node) = @_;
                                  my $text = decode_entities ($node-&gt;text ());
                                  $text =~ s/&amp;apos;/'/g;
                                  $text =~ tr/[\r\n]//d;
                                  $rhcbxml-&gt;{$node-&gt;att ('time')} = {'author'  =&gt; $node-&gt;att ('author'),
                                                                     'user_id' =&gt; $node-&gt;att ('user_id'),
                                                                     'text'    =&gt; $text,
                                                                     'encoded' =&gt; (defined ($rhcbxml-&gt;{$node-&gt;att ('time')}) ? $rhcbxml-&gt;{$node-&gt;att ('time')}-&gt;{encoded} : 0)
                                                                    };
                                  $t-&gt;purge;
                                }
               });

      $twig-&gt;parse ($xml);

      return (1);
   }

   return 0;
}

#
#  festival_clients won't take the parms as a string, so we have to
#  create a temp file.  Rather than lose the data somewhere, it's
#  at the end of the program.
#
sub createParmsFile
{
   local $/ = undef;

   my $prolog = &lt;DATA&gt;;

   open (TEMPFILE, "&gt;mt.parms") or return 0;
   print TEMPFILE $prolog;
   close TEMPFILE;

   return 1;
}

__DATA__
(voice_kal_diphone)
(Parameter.set 'Duration_Stretch 1.1)
&lt;/code&gt;
&lt;hr&gt;
&lt;p&gt;You'll also need this uuencoded file.  This is the 3 seconds of silence that gets streamed when no one is talking.&lt;/p&gt;
&lt;code&gt;
begin 644 silence8m.mp3
M_^,8Q``,TA6$`8H0`/\31T+P%'^+8\=?\G&amp;Y`W_XSD(I___'),46.W__^+.0
M@A[E%____C@G3__^\WA]W__RYI75]T,6U*B,_^,8Q`&lt;,."HL`8\0`#HUH+`1
M'F13KI4A?DZM!]7SCS+59&lt;/H_K+J=;:&amp;%'_Z=Z'UI&gt;,E__1_U@3IPT97'6]:
M\[:7L&lt;ES4;4W_^,8Q!$,8"XD`&lt;\8`%M530,Z`R_MZ*/9;0*,&lt;\5L+%Z]V,:U
M8!EK%Z[+U9BRN@:$;,TG&lt;R)6B.*BK!,9F0$3&lt;]%A+5F:]$W1_^,8Q!H,2$8D
M`'A$`#T4A'O*JL[&amp;,%"2*._&amp;Q_UF;+G7,Z65U0DK:OUBU&amp;8Z``:-!U*DM0QE
M!IY9GO6^JDW/0RLO*DQT96@J_^,8Q",-X"(D`'B&amp;`,-AIEAOK;Q&lt;ZS:O?6QA
M#MH\ZA?:E0'BDH*2_I`;ZN1J/*%BP(%#0?0HTU@IR,/CR[%:6`V]JON37L/&amp;
M_^,8Q"8.X$8@`,#&amp;`,/[ZQ"&gt;'.&lt;_:6;Q56%S&gt;+O2A=MC(E4#-:A'^U@`4"*A
M.L\X-O,*$]M5CNI96A]M(LZ9&gt;5I0Q8V=E(9T_^,8Q"4,Z!8D`,#&amp;`#\&gt;MD_I
MS6F]G]&amp;ZVE?N2@8CPP&lt;1;*)@.J-+8/)YLX+./M&gt;2KJ&lt;P#*,*W'6'!]G2QMVR
MY".]C0&amp;@M)^P_^,8Q"P-&lt;"HD`&amp;#``-KU=M5)[WIFN%(,9.V;M!@D'!X?-Y1Q
M\:K:%GK/W,LL=M0Q:+UN+N'*H:Q;#6Y*;][I];*=6K3HH3B]_^,8Q#$-6!H@
M`,#$`([0K5'U*&amp;MY-S]-99PP48(GR#AY,&gt;X/AI!!B7(G4,?=M,1=QO&lt;-4]B_
M.V[&amp;.BZ-M:W]%PSZMZKX&amp;=W&lt;_^,8Q#8-L"XD`,"&amp;`%D9K8Q;@A(5(!-^9FA=
M9"XVQ2%U^AZEDMJRS]J6*)%;[D]KYI]WO+V6.6Y*E4M&gt;Q$U5UK&gt;E$!&gt;&gt;6+@$
M_^,8Q#H,\!8D`'@```8+,6*34&amp;YE"SE]=(UBT8X]#L&lt;&amp;GH[R"SFMB5M6BD_Y
M(LK51&lt;&gt;6Y"+D[-2R7,T);*H'LR*1JDBFE89=_^,8Q$$-J!H@`'@``%A,CVD#
M[J7+94\J*"K$,RE0H[G\OQ,E6O*KV"$ZE^HRE9]:R"ND#/5?=&gt;A%^1H2)8KU
M[_7D%B&amp;=X@YP_^,8Q$4-X!X@`'A$`&amp;&amp;9&lt;XHJ&gt;?)41YJ?T4MC74#[$H&gt;8D"&lt;Y
MQIW?H&gt;^8&lt;^O^O]/3JL2!:`Z1_L41CL$8)C0*009+%PX(C9&lt;5_^,8Q$@-`$8D
M`,"$`'T%LA88*!1B[6(DV,R&gt;?DR3J[_`*3!:Q9I+5`12%(&gt;[8X7):GMJWCZU
MWN15$2&amp;G3\6KU6&lt;8U-W8=EE"_^,8Q$\/R#HD`'A&amp;`&amp;"#BCE-9.E:'B)WD'J6
M$6;!W@8#'+X5)GZ&amp;)MH(*1=H4941&lt;`?]TJ*=E5T4&amp;^NI#M/?IA3*#=8T6,"-
M_^,8Q$H.P$8@`,#$``\T.&lt;QQ9NX":++!&amp;[&lt;8ZK%Y343/E+,/(J&gt;QEKGWI:EA
M`S6[MBNM2@5F&lt;JU(]H'#"3EPLUA&lt;X'$-N8EM_^,8Q$H-@#XD`,#$`&amp;I`YAK6
MUJM'.*7[&gt;[NH&gt;960T]CE*%J(8V=RKVZKT@SI&amp;E`Z!,0XHP=47%$.4E:DTL&gt;J
MMR6#-*WKC#XN_^,8Q$\-`"XD`,"&amp;`+6]AH@D705.@CWL*.&lt;$D(N1UL9UW)7&lt;
ME*QP21[4-W[5*@1S1`E@QCBH:&amp;*."LJ]6O"F/;5M497&lt;LHYS_^,8Q%8.R"(@
M`##&amp;`!@PB9298E[VI&lt;QI$R42IB!6+RQ^!'%VG&lt;FMH$UO&lt;DQ_M54'X\U2-&lt;11
M:'D&amp;%"`(OWU1B5:$WKD7.VG\_^,8Q%4.V"8@`'B&amp;``'ZT"Z&gt;=40[IA)\`10^
M,Z1&gt;^NEVR\N@Y=L:+AXF&lt;LI:EX.(M%J?$Y&gt;YZP$7%T-F`,C8C2RT_I0L;IF4
M_^,8Q%0,^"(D`'C&amp;`%!T^Y3MZ71A584`R"*9U[W\Q9JUO',D+$)GZ6J1*&amp;/K
M+]V;!`T*&amp;Q9Q@XXTP7FSRQZP)I'IL^AA3RC&gt;_^,8Q%L/.#(@`,#``,J2D7IJ
M1:?GFNJJ[JM]CWU=VO12(BO]24V\50U4,.IHZ9&amp;4+/BQ++C2-5SQQN]3'#5&amp;
M*FJ8`*L&gt;6&gt;JH_^,8Q%D,&gt;"8D`,#&amp;`&amp;T%106OUH62N2=WF#H?]69)NRY=FCIJ
M&amp;*SS1WG@Y@)`\E`OI.ATBT/$23[*T*IH8]!1)-+(HYYH2+9[_^,8Q&amp;(/4#H@
M`,#&amp;`$JF*G#.N)EZ)W&lt;\J@B;K5UV93I9.EH0@N#;=FAJA,'"Z'E%#%%T+K;+
M*-F3@NR0M4E34O[4%]3V)/#Q_^,8Q%\.&lt;"(D`'C``,ZS8NI_,!E&gt;YGNM)J=_
M[+'6(@52Z``9!("K$@:(I$9IRQ(?N&amp;J%C"WZ"+%Y^C=-T[W)]2`I:E&gt;?NW64
M_^,8Q&amp;`-L"X@`'C``+NSO_O&lt;FZLC&lt;^@&gt;1+[-BW?.$AV(#(XJI]9IZ%'XH$8R
MID]&lt;F@TEB1?D$;C3DWH2A`'U#$#SJE]B+U*B_^,8Q&amp;0,P`HD`(#``%_E*%-:
MQSD!+C8:Q*X&gt;[6NAPX,J$(/--C2`$H%%%,Y1#[7+[[U10-44'7'(J[74A@N_
M#&lt;XBBU(`%=B;_^,8Q&amp;P.L"H@`,"``-")06ZF(6[&lt;G;IJ!61FN`]BH%SD?R.M
MDI8&amp;PY8Y3AS3VBQ1AM%0?;M'/F0:.KX&lt;A`_&gt;//H%7N1&gt;&lt;4A+_^,8Q&amp;P.T"(D
M`,"&amp;`(&gt;]G,#&lt;S-_%51SN?915!`1GW&lt;\!H\'&lt;3`Z$6/38PE0=-JNH:BUHMU`U
M&gt;P^&gt;&lt;Q!*V-L)O1'VN(W#EW/)_^,8Q&amp;L/2'(@`'C$!*.+M14O=M29-.%XS:XB
M`$I.FM[*]'4E%&amp;=#&lt;`%R!U*@D(E"RQ=UQ]I)*;%F4EDJT-.ZVPHRZ55JC:;2
M_^,8Q&amp;@.N"X@`'C&amp;`!,/=?O4.0;H$M%IA(JS^5M'U0Y#'^XHT1.GS`A&amp;#@&amp;X
M"UC&gt;6%NJNQZ$$6-0=N%&lt;ZLE9N:A`Y:HN]!"*_^,8Q&amp;@/B#(@`'C&amp;`#'L,33J
MM'4S5I]I[:#1I0&gt;TVWS28\$`L$1@N74&gt;"3F'9G,MN=H4E+6_XQUO&gt;QZ=ROWM
M4],:B0N='+EK_^,8Q&amp;0-X!8@`'A&amp;`";JW]E&gt;I0)1ZXTAY67'$$J'PLX6Y5QP
M&gt;KDH^^[8*L-$]UL67%QM#1B%!IC'L/SB#]/IO0VA6ZMAY_WZ_^,8Q&amp;&lt;,8!(D
M`'@``)4,:]4]A*NHA)T).CZVF#"A8L\\!14*EC5&lt;6;(#Q8.AQ%J^Z*'&amp;15W,
MK2\43?&lt;]1V1:RQOL&amp;,JG%N8[_^,8Q'`-4!HD`'B``&amp;_YH7K5#PWZ8^PTX-!Y
MZ4-)=(LU&lt;E6E#HX],UD5M216MSU!)Q`9-3;;Q4&gt;S7J$4W#@^I6A*M;"#N?8V
M_^,8Q'4/D%(@`,&amp;&amp;`+V%E0\H:YVF&gt;-!\N&lt;%VM/TECR&lt;@FXNSN8!5ET+VZ4%Q
M=G"Q&lt;LUXJ];;`(^.M82KO5&gt;;W.'&gt;GKE152Z9_^,8Q'$.$`XD`'@``#4J`XN!
M_7CP6`!1L1$@&amp;4(DPZ&lt;/VDWM3'L&gt;^O4ME8:;L%5'SRT':$)7IS[C1J*F""TK
MNA+MO=5&lt;A"K[_^,8Q',.2!X@`'A``,3U%$4,3&amp;*:E^2-0%C'"2&lt;@-+,JFY$=
M%_VAU#:VKI;+66V,O1U.%[C:WEZ$%[::*E$$E:K6(U_=%QKE_^,8Q'0/*"8D
M`'B&amp;`*H&amp;JB'S;II8L=8&gt;&amp;L!H+F]Q0RJ@I")#N8_XNV\V/+'*&lt;%T%;67D9_\_
MZ=)["6.0^0_T)@9%(NFK(PG,_^,8Q'(-R#8D`,#"`#"`9'))&amp;2=ZA6'!(ZFI
M19=3Z&amp;.IFB`_((#:T-:0*I%1NM9^FE+&amp;RKG,A1^38*V4H"RGR\UTKA&gt;(JK/K
M_^,8Q'4,&lt;!8D`'B&amp;`$8DD(6T$&amp;LL3)OV/Z94QN6?-$G+FSB]V]SH+/8@&gt;CI6
MXX?&gt;8Y1&amp;Q7R-9QY&gt;+C^M#TT(S,3&lt;UC:8+'$U_^,8Q'X/&lt;#(@`(#&amp;`""A0*@=
M@),#FD69&amp;NRBFB'+VJ&amp;R*3;!E;;VNGD"VM"T*$TX;N6E*%#85NJ&amp;]RD6_[VW
M+1F1]LSGIA3D_^,8Q'L-:#8@`'A$``PH&lt;\P&amp;@,Y3RSFQH&amp;&gt;Q]8U04?U1[I)@
M&amp;%!81DUR1ZH\7"R2#6ZT'$_TL#YE3':.*_.11T6K!=&lt;MG&amp;&amp;&lt;_^,8Q(`/,$(@
M`,#&amp;`/%R0L/:,I?'.0OG+,M2IQ&amp;I8QZ;Q5+%J:A\&lt;Z%)A:!XX]*YJUJ)0N*9
M:[;23BC:*ZT:@W]/%Z6^H(30_^,8Q'X/0"8@`'B&amp;`,YX?'GP,N./OF'];Y]0
M25A`N(HG4,8FQPS,J*1I%\#KT\8J06:I`*V(;JK-,,,WNZ?"J19-B*0-8Z8P
M_^,8Q'P-L#(@`&amp;B``-1A@+A\@T2#EWYDX':G(VOO@)3L:7%+JM;3A:PA:M2D
M\&gt;VS0OKW_YQE:OGEJ@/B2&gt;5A`8-G`2$:R`(6_^,8Q(`/0"X@`,#``%"#5;D5
M,O56_E;2R4/:A'&gt;NVACF*ZGS2AE]%+"],@BV[WT,3_AN)JO?+;,SH`5#K,08
M($6H;%33B#$(_^,8Q'X-""XD`,#&amp;`*"#&amp;+L&lt;D8NU&gt;E6S&amp;2W&lt;Q(&amp;04TFSCDL7
M$GUH&lt;]%C*-\BB.\HB@L8J@4UGH@-40)#Z@6'7!UAV)1=@J@%_^,8Q(0,\!8D
M`'@``$N@#WHFS1,9L0D).N)2RB8=?BSV)&lt;XNM&gt;WF&amp;\GO1[GK=T5$4J)\5A*1
M'[&lt;D"!A`'YA7*3KR^A52:SEW_^,8Q(L.(#XD`,#&amp;`,N*Q4#,,,%+!@,T$45)
M?1ZE4+&lt;AS9&amp;SK&gt;S&lt;6^CUU0H&amp;+4,3`1C%"IRHB56UZ1(TS4VJI-CG\8Z*.(O8
M_^,8Q(T/&lt;"X@`'B$`*6&amp;R#"S)AB"0]8O/W3CS3TM0YK.Z]Y'VV(HYER$JB+,
MAA=-264P16N&gt;&lt;(69#RR(J8.`05"8N\Y&gt;7M2U_^,8Q(H,2!(D`'A$``R9N!-S
M4"G552DV5':J%JSISO732/&gt;66P^Y"F:?J.-5Z`B&amp;YS2D\["`3"&lt;I%VHFSRGB
MP^VI`=`TRNQY_^,8Q),.B!X@`&amp;!``"R[].9I&lt;*:W:PZH/C:`2FDLB:-;&gt;TJA
M2BRK^M@AK$WW2S%U(HYN1'?NC&amp;0MC=(D!%@^7L;&gt;P,D#V&amp;E9_^,8Q),/@$(@
M`,&amp;&amp;`"+'14E8Y!HZUK=;&gt;%&amp;E[#]^U:MHLZ@;I&amp;UE=;'J32QCK]W4`&lt;(C(I&amp;J
M`80#6Z`,#6L&gt;('!`0BA%$P:&gt;_^,8Q)`/&amp;"H@`'B$`)=9V8:&gt;+/OT]@TE0@@]
MPW%4')_E?BBU;RS:6,ZJWL8A5NH2;`T,TN5A'``@$1*E99"`H*C*'D$1SG'E
M_^,8Q(X.N$(@`,#$`*5V.6@!,/%!9K!H0=W.9[4/N##5\&gt;%J=^%YZO:IFBMJ
M#&amp;L4#:T5#B5Y^U3Y@`QV!PX4"-SQ=%EXG64&amp;_^,8Q(X.@#(@`'F&amp;`"0LY#%"
MV:`PX5N[20XK%RUUZ6DYG"+]%E[,\&gt;?B)IQIAB)AVG\2:0#$UE3:%S"QH.BH
M3#YTZXBXL+#!_^,8Q(\/F"8D`,#&amp;`$1&amp;6WG&amp;4.RI2%3=$V\@\X+,%:'DV_/O
MI9&gt;J4(DRR&gt;V/J6ON43?WO075$DPN!&lt;,ZP6#.?*:0$Z4&gt;M`-=_^,8Q(L/`#(D
M`,"``&amp;:NG9N]#\,['N&lt;)'GW(+9BH=IJ?C_]=T[^6:VS574H:(_DLKY8CJE&lt;@
M*8&lt;&lt;W)N3NBXM%+&amp;A0F)&gt;FFE+_^,8Q(H/$"H@`'B&amp;`(D*YZXBYVPC0SL1,;9R
MJY]SNY$WOF%5D@.FC#KRQ0A0,$D`05;'98B#;KJP$A%ZX\JD;0UB'8H7`[5.
M_^,8Q(@,&amp;"HD`,"``$*H+=A$6M-)*I79W7IK/TO*(H!IG2V^ZBHB907JENE=
M11#2AP8ZV4!`HQ36HLN&gt;]4`O$5L0W4`A8M%E_^,8Q)(-`"(D`,#$`*X*G2E[
MUHB,H0A]%0TO:UQ^+/&gt;4HV*3LLC0-RT2B\N5^YH$)"'14I!:T.MBPN=2DA8C
M=0;*0.BNW&amp;7"_^,8Q)D.T"(@`&amp;#&amp;`+&lt;:1QAJ;A_B@Q[WAP7,A`_IG`U)K&amp;O&lt;
MUZR*H"56E.IE"QT/VV(]V)4)!Q0XV9:`"A@4J.7N6=',%JMZ_^,8Q)@/&gt;"H@
M`,#$`%,V++(F`(FZMD@D5O\.)/6K=7"+,TNJFHGM0Y]AB._8M03KV%+;F%02
M"0E`2#H4'AMC7.&amp;M(LY!46UW_^,8Q)40`$8D`,#&amp;``"[?2.N+,H)4L8U@K;J
M=)RIE?1&lt;E=*&gt;-CF-M9I0@54-Q"G;:"9\C1/%;VGCS'BCW5NYE+`&lt;G;BSVKH3
M_^,8Q)`.H"(@`'C``"W?94]2RY53WAQ*$'113[&amp;[:;(&gt;*1$-7E4I(+?&gt;YE`+
M%]73&gt;T&gt;.J&lt;X`&amp;D'R[F_8BRD9=8G',&amp;/'NG$P_^,8Q)`.("HD`'C``/3*I2]]
M!%R4-IL8979'NHT!A^RJEK;5R!^D6@&amp;9`S^T_:`0##(/F5")8,%@T]`Z8J"!
M*&lt;(3E:@),I%R_^,8Q)(.N!(@`'@``&amp;LZXC+[6&gt;?2FJXD7E6_'.$K\?U%44E6
M=-;[:LK5`&lt;46^;K@H@H@5:%0&amp;D")$QQ+%XM6Q^F+$8&amp;]Z:6&amp;_^,8Q)(-L!8D
M`'@``%"_*56L[EZ+=_;6]K!;0ZBS%M&amp;M!S*)NLR.Y6&amp;P=*)/)"@I,,'$VSFU
M[K';T[:S682@&gt;I(SJ&gt;@\O//&gt;_^,8Q)8/""(@`,B``%2B3&lt;ZWJJ0^O743OJ1T
M*@&lt;270-5EEG`A#'0H?R;Z*6Z48M:_1&lt;E4UIH.K*:SV*W,4+74R#6,N[PMV/T
M_^,8Q)0,&lt;!8D`'B&amp;`#G['^OWH34/%CN4U0XFXF`C'%*2#0/S&gt;IE4:FY_9K6F
MA(YET0.^Q2Y?2BC;M%OI:L\[[.U.G:GI%@2\_^,8Q)T-2#HD`'B&amp;`--D7)`D
M;&lt;M3UFL1&gt;.0EPQ&gt;TJ4KHOFTD%(=V-`I!VQ#GH3[$RQC9B_K0A&gt;FE.@]3J0.*
M%*N%1^3*#'GQ_^,8Q*(,F"XD`,"``%0D/I:P^3NY1D?]B0QJ75KCJQ6\TZI:
M7/&gt;ZP7&gt;NDLZY9AKA?Z+BHWUZE1*CQVK%!3@"+%JE--PU:.VR_^,8Q*H,0#XD
M`,C&amp;``=:Q:GAG8MMMRB#@HQAA&amp;&gt;EX47&amp;OJN*[FZ*W,4A!FB93`I&amp;TEZ]*@(@
MN",HQQ__JE(E`"I"@&gt;=`!&amp;F7_^,8Q+0,L!8D`'C``+D+Q'A_8Y`9:(Q4(/5*
MK(=EJ\H]R&amp;'11#BY(S'U)&amp;-1$U[5[6JT7V1?4BI,04U%,RXX."`H8F5T82FJ
M_^,8Q+P-$"HD`'C``*JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
MJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ_^,8Q,(-V"H@`$C&amp;`*JJJJJJ
MJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
MJJJJJJJJJJJJ_^,8Q,4/D#H@`4\``*JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
DJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
`
end
&lt;/code&gt;</field>
<field name="codedescription">
&lt;p&gt;Hear monks talk!  Listen to your favorite monks battle it out in the chatterbox, as streaming audio!  Make your co-workers think you're a basket case!&lt;/p&gt;
&lt;p&gt;This script delivers the chatterbox as MP3 encoded streaming audio.  It uses the festival/MBROLA text-to-speech (TTS) system to create the audio on the fly.&lt;/p&gt;
</field>
<field name="codecategory">
PerlMonks.org Related Scripts</field>
<field name="codeauthor">
J.C. Wren&lt;br&gt;
Twitching Monk Software Productions&lt;br&gt;
jcwren@jcwren.com</field>
</data>
</node>
