Twitterの検索読み上げつき

日本語音声合成Web APIが面白いなと思ってて、よくみたらcrossdomain.xmlもちゃんと設定してあったので、びっくりついでに作ってみた。

Twitterの検索読み上げつき – wonderfl build flash online

[sourcecode language=”as3″]
package {
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.events.SecurityErrorEvent;
import flash.geom.PerspectiveProjection;
import flash.geom.Point;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.text.TextField;
import flash.text.TextFieldType;
import org.libspark.betweenas3.BetweenAS3;
import org.libspark.betweenas3.easing.*;
import org.libspark.betweenas3.tweens.ITween;
/**
* Twitterの検索読み上げz軸つき
* …
* @author umhr
*
* 読み上げapi
* http://gimite.net/speech
*
* Twitter Search API Method: search
* http://apiwiki.twitter.com/Twitter-Search-API-Method%3A-search
*
* Twitter API 仕様書 (勝手に日本語訳シリーズ)
* http://watcher.moe-nifty.com/memo/2007/04/twitter_api.html
*
* 正規表現
* http://www.tom.sfc.keio.ac.jp/~fjedi/wiki/index.php?%C7%DB%CE%F3%A1%A2%CA%B8%BB%FA%CE%F3%A1%A2%C0%B5%B5%AC%C9%BD%B8%BD(ActionScript3)
*/
[SWF(backgroundColor="0xFFFFFF",width=465,height=465)]
public class Main2 extends Sprite {
private var _resultStage:Sprite = new Sprite();
private var _speech:Speech = new Speech();
public function Main2():void {
//ヘッダーのサーチエリア
//背景
var titleArea:Shape = new Shape();
titleArea.graphics.beginFill(0x39CCFF);
titleArea.graphics.drawRect(0, 0, 465, 23);
addChild(titleArea);
//Serchの文字
var serchWord:TextField = new TextField();
serchWord.text = "Serch:";
serchWord.width = 33;
serchWord.height = 18;
serchWord.x = 2;
serchWord.y = 2;
serchWord.textColor = 0xFFFFFF;
addChild(serchWord);
//テキスト入力欄。キーボードのキーを押し上げた際にonKeyUpが実行される
var title:TextField = new TextField();
title.text = "水玉";
title.width = 426;
title.height = 18;
title.x = 36;
title.y = 2;
title.border = true;
title.borderColor = 0xFFFFFF;
title.background = true;
title.backgroundColor = 0x99DDFF;
title.type = TextFieldType.INPUT;
title.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
addChild(title);

//検索結果を配置するSpriteをaddChild
_resultStage.y = 25;
//_resultStage.z = 0;
addChild(_resultStage);

//初期状態で検索結果画面が出るように、検索を実行する。
serch(title.text);
}

private function onKeyUp(e:KeyboardEvent):void {
//Enterキーのキーコード13だったら、serchメソッドを実行。引数には適すと入力欄の文字列
if (e.keyCode == 13) {
serch(e.currentTarget.text)
}
}

public function serch(serchWord:String):void {
//検索APIにリクエストを投げる
//http://search.twitter.com/crossdomain.xml上で許可をしているので
//特に指定しなくても自動的に確認して、許可を得られる
var myURLLoader:URLLoader = new URLLoader();
myURLLoader.addEventListener(Event.COMPLETE, onCompleteXML);
myURLLoader.addEventListener(IOErrorEvent.IO_ERROR, onIoError);
myURLLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onIoError);
var xmlURL:String = "http://search.twitter.com/search.atom?q=";
//encodeURIComponent()を使うことによって、#や日本語にも対応
xmlURL += encodeURIComponent(serchWord); //*1
myURLLoader.load(new URLRequest(xmlURL));
}

public function onIoError(e:IOErrorEvent):void {
trace(e.type);
}

private function onCompleteXML(e:Event):void {
//eventListenerを削除
e.currentTarget.removeEventListener(Event.COMPLETE, onCompleteXML);
//_resultStage上のオブジェクトを全て削除
while (_resultStage.numChildren) {
_resultStage.removeChildAt(0);
_resultStage.z = 0;
}

//XML型にキャスト
var myXML:XML = new XML(e.currentTarget.data);
//namespaceを設定
default xml namespace = new Namespace("http://www.w3.org/2005/Atom"); //*2

//Security.allowDomain()

//要素を取り出して、textFieldを作りならべる
var itemLength:int = myXML.entry.length();
for (var j:int = 0; j < itemLength; j++) {
var i:int = itemLength – j – 1;

//検索結果のテキストフィールド
var card:Card = new Card();
card.setHtmlText(myXML.entry[i]);
card.z = i * 240 – 80;
_resultStage.addChild(card);
}

_speech.load(card.title, card.authorName);

_resultStage.addEventListener(MouseEvent.CLICK, onClick);
this.transform.perspectiveProjection = new PerspectiveProjection();
this.transform.perspectiveProjection.projectionCenter = new Point(stage.stageWidth / 2, stage.stageHeight / 2.5);
}
private var _t:ITween;
private function onClick(event:MouseEvent):void {
if (_t != null) {
_t.stop();
}
var card:Card = event.target as Card;
_t = BetweenAS3.tween(_resultStage, { z: -card.z }, null, 1, Quint.easeOut);
_t.onUpdate = onUpdate;
_t.onComplete = onComplete;
_t.onCompleteParams = [card.title, card.authorName]
_t.play();
}
private function onUpdate():void {
var n:int = _resultStage.numChildren;
for (var i:int = 0; i < n; i++) {
_resultStage.getChildAt(i).visible = (_resultStage.getChildAt(i).z + _resultStage.z > -250);
}
}
private function onComplete(… rest):void {
_speech.load(rest[0], rest[1]);
}

}
}

import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.filters.DropShadowFilter;
import flash.media.Sound;
import flash.net.URLRequest;
import flash.text.TextField;
class Card extends TextField {
public var title:String;
public var authorName:String;
public function Card() {
super();
width = 300;
x = 2+80;
wordWrap = true;
background = true;
backgroundColor = 0xFFFFFF;
border = true;
selectable = false;
borderColor = 0xCCCCCC;
textColor = 0x000000;
//htmlText = htmldata;
autoSize = "left";
//テキストフィールドのy座標はtfYに加算していくことにより、決定
y = 280;
//myTextField.z = 3000 – i * 250 + 20;
//z = i * 250 + 20;
//name = String(i * 250 + 20);
//myTextField.name = String(3000 – i * 250);
filters = [new DropShadowFilter(2, 90, 0xCCCCCC, 1, 8, 8)];
}
public function setHtmlText(xml:XML):void {
var link:String = xml.link[0].@href;
authorName = xml.author.name;
title = xml.title;
//アイコン画像のURLを取得
var imgURL:String = xml.link[1].@href;
//bmpフォーマットのアイコン画像を使っている人も少なくないため、Flashで使えるファイルの拡張子かどうかを判別
var extention:String = imgURL.substr( -4);
extention = extention.toLowerCase();
if (extention != ".jpg" && extention != ".gif" && extention != ".png") {
//Flashで使える画像の拡張子ではなかった場合にはデフォルトアイコンを指定
imgURL = "http://a3.twimg.com/sticky/default_profile_images/default_profile_4_mini.png";
}
//画像のファイル名が日本語などの規定外の文字である場合を考慮しエンコードする
imgURL = encodeURI(imgURL); //*3
//小さい画像で十分なので、名前を差し替える
imgURL = imgURL.replace(/_normal\./, "_mini\.");

title = title.replace(/\n/, "");

//得られた各値をhtmlTextに入るように整形
var htmldata:String = "";
//htmldata = "<a href=’" + link + "’ target=’_blank’>";
htmldata += "<img src=’" + imgURL + "’ width=’48’ height=’48’ checkPolicyFile=’true’ />";
htmldata += "<b>" + authorName + "</b> " + title + "";

//trace(htmldata);
htmlText = htmldata;
}
}

class Speech {
private const voiceList:Array/*String*/ = ["male01", "female01"];
public function Speech() {
}
public function load(text:String, name:String):void {

//@hogehogeを排除
text = text.replace(/@\w+/g, "");
//URLを排除
text = text.replace(/http:\/\/[a-zA-Z0-9_:\/.]+/g, "");

var url:String = "http://gimite.net/speech?format=mp3&speaker="+voiceList[name.length%voiceList.length]+"&text="
var sound:Sound = new Sound(new URLRequest(url + encodeURIComponent(text)));
sound.addEventListener(IOErrorEvent.IO_ERROR, sound_ioError);
sound.play();
}

private function sound_ioError(e:IOErrorEvent):void
{
trace(e.type);
}
}
[/sourcecode]