XQuery/XHTML + Voice

      Motivation

      You want to have your browser read Twitter updates using a text-to-speech conversion extension that is built into your browser.

      ↑Jump back a section

      Method

      XHTML + Voice is supported by the Opera Browser with the Voice extension installed. In this simple application it is used as a browser-based Text-to-Speech engine.

      Twitter Radio

      This script creates a simple text-to-speech version of Twitter Search.

      Obama

      ↑Jump back a section

      Limitations

      • Window has to be active for the T2S to play on refresh
      • The cleaned text to speak is held in a div which is rendered as white on white text. Initially it was output as a block in the header but it did not seen possible to apply styles. Applying a style display:none hid the text from the T2S engine as well!
      • Transforming the atom content to a a string suitable to speak needs more work.
      • Retweets and similar tweets could be removed using levenstein
      • Male and female voices are assigned randomly by tweet. I'd like to cache the voice assigned to a tweeter so that tweets are consistently spoken in the same voice
      • The initial load doesn't seem to trigger playing, hence the play button, but this also re-fetches the page.
      • This is an ideal situation to use AJAX instead of refresh
      • The T2S engine is quite good at rendering the text but it needs be helped in places, for example by replacing texting abbreviations with their expanded form.
      declare namespace atom = "http://www.w3.org/2005/Atom";
       
      declare variable $n := xs:integer( request:get-parameter("n",6));
      declare variable $search := request:get-parameter("search","");
      declare variable $timestamp := request:get-parameter("timestamp",());
      declare variable $seconds := $n * 12;
      declare variable $noise :=
      (  "<b>",
          "</b>",
          "&lt;.+?&gt;",
          "http://[^ ]+",
          "#\w+",
          "RT *@\w+",
          "@\w+",
          "[\[\]\\=«»:;()_?!~\|]",
          '"',
          "\.\.+"
      );
       
      declare function local:clean ($talk as xs:string, $noise as xs:string*) as xs:string {
          if (empty($noise))
          then $talk
          else
              local:clean(replace($talk,string($noise[1])," "),subsequence($noise,2))
      };
       
      declare function local:clean($talk as xs:string) as xs:string {
         local:clean($talk,$noise)
      };
       
      declare option exist:serialize  "method=xhtml media-type=application/xv+xml";
       
      let $entries := doc(concat("http://search.twitter.com/search.atom?lang=en&amp;q=",encode-for-uri($search)))//atom:entry
      let $entries := if (exists($timestamp))
                                then  $entries[atom:published>$timestamp]
                                else $entries
      let $entries := $entries[position() <= $n] 
      let $newtimestamp :=  if (exists($entries)) then string($entries[1]/atom:published) else $timestamp
      let $entries := reverse($entries)
      return
      <html xmlns="http://www.w3.org/1999/xhtml" 
            xmlns:vxml="http://www.w3.org/2001/vxml" 
            xmlns:xv="http://www.voicexml.org/2002/xhtml+voice"
            xmlns:ev="http://www.w3.org/2001/xml-events" 
      >
         <head>
             <meta http-equiv="refresh" content="{$seconds};url=?search={encode-for-uri($search)}&amp;timestamp={$newtimestamp}&amp;n={$n}"/>
             <title>Tweets matching {$search}</title>
             <vxml:form id="say">
                  <vxml:block>
                     <vxml:prompt src="#news"/>
               </vxml:block>
              </vxml:form>
            <link rel="stylesheet" type="text/css" href="voice2.css" title="Normal"/>
         </head>
         <body ev:event="load"  ev:handler="#say" >
             <h1>Twitter radio, listening to tweets matching {$search}</h1>
             <form method="get" >
                 Listen for <input type="text" name="search" value="{$search}" />
                 Max items <input type="text" name="n" value="{$n}" size="4" /> 
                 <input type="submit" value="Tune"/>
                 <button ev:event="click" ev:handler="#say">Play</button>
             </form>  
             {for $entry in $entries
                  return 
                      <div>                  
                           <a href="{$entry/atom:author/atom:uri}">{substring-before($entry/atom:author/atom:name, "(")}</a> &#160;
                           {util:parse(concat("<span xmlns='http://www.w3.org/1999/xhtml' >",$entry/atom:content/(text(),*),"</span>"))}
                       </div>
             }    
             <div id="news">
                    {for $entry in $entries
                      return 
                          <p class='{if (math:random ()< 0.5) then "male" else "female"}'> 
                              { local:clean($entry/atom:content/text())}         
                          </p>
                   }     
             </div>
         </body>
      </html>
      

      With the style sheet:

      .male {
          voice-family: male;
          pause-after:1.5s;
      }
      .female {
          voice-family:female;
           pause-after:1s
      }
      #news {
          color:white;
          background-color:white
      }
      
      ↑Jump back a section
      Last modified on 29 July 2009, at 15:27