411 REFERENCE · Wikibooks
XQuery/XHTML + Voice
Reference material by Wikibooks contributors. This retains its source identity and is not 411 news reporting.
Motivation
You want to have your browser read Twitter updates using a text-to-speech conversion extension that is built into your browser.
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
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>",
"<.+?>",
"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&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)}×tamp={$newtimestamp}&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>
{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
}