Archives

AITalk WebAPIを使ってwavを生成してs3に保存するaws lambda

LINE BOT AWARDSのハッカソンに参加した。ぷ◯きゅあがタスクの終了や寝る時間などを通知すると同時に音声合成でしゃべってくれる、という(いちおう)親子向けのBOTを作った。

LINE apiはeventに応じてwebhookが飛んでくる仕様になっているため、serverlessのアーキテクチャでシステムを作った。つまりlambda、step functionsをはじめとしたaws漬けだったのだが簡単に優れた設計のアプリが作れて僕スゴいじゃなくてawsマジすごい。

そのソースコードも公開する予定なのだが、スポンサー企業でもあったAITalkさんのAPIを呼んで音声合成したWAVEファイルをs3に保存するところは汎用性が高そうなのでgithubにrepositoryを作った。

https://github.com/Drunkar/aitalk_webapi_wav_generator_lambda

中身もいちおうここに貼っておく。めんどくさかったのは、jsのaws-sdkにはs3にファイルをアップロードする方法がなかった(rubyにはあるみたい)ので、jsのオブジェクトとしてメモリに保持した状態でputObject()することでI got Kotonaki. たぶんstreamを理解している人ならば一時ファイルとして/tmp以下に保存することなくs3にアップロードするコードが書けると思う。


var http = require("http");
var fs = require("fs");
var aws = require("aws-sdk");

var api_url = "http://webapi.aitalk.jp/webapi/v2/ttsget.php";

exports.handler = function(event, context) {
    var request_url = api_url +
        "?username=" + encodeURI(event.aitalk_username) +
        "&password=" + encodeURI(event.aitalk_password) +
        "&text=" + encodeURI(event.text) +
        "&speaker_name=" + encodeURI(event.aitalk_speaker_name) +
        "&volume=" + encodeURI(event.aitalk_volume) +
        "&speed=" + encodeURI(event.aitalk_speed) +
        "&pitch=" + encodeURI(event.aitalk_pitch) +
        "&range=" + encodeURI(event.aitalk_range) +
        "&ext=wav&use_wdic=1";

    var outFile = fs.createWriteStream("/tmp/" + event.filename);
    // start download
    http.get(request_url, function(res) {

        // output as a file
        res.pipe(outFile);

        // download end
        res.on("end", function() {
            outFile.close();

            // open the wav file as a js object and putObject to S3
            var s3 = new aws.S3();
            fs.readFile("/tmp/" + event.filename, function(err, data) {
                if (err) {
                    return console.log(err);
                }
                var params = {
                    Bucket: event.bucket,
                    Key: event.filename,
                    Body: data,
                    ContentType: "audio/wav",
                    ACL: "public-read"
                };
                s3.putObject(params, function(err, data) {
                    console.log(err, data);
                });
            });

        });
    }).on("error", function(e) {
        context.done("error", e);
    });
};

テストイベントを以下のように飛ばせばおk。


{
  "aitalk_username": "",
  "aitalk_password": "",
  "aitalk_speaker_name": "nozomi_emo",
  "aitalk_volume": "2.00",
  "aitalk_speed": "1.20",
  "aitalk_pitch": "0.90",
  "aitalk_range": "2.00",
  "text": "戦争に負けたから堕ちるのではないのだ。人間だから堕ちるのであり、生きているから堕ちるだけだ。",
  "filename": "voice.wav",
  "bucket": ""
}

Node.js+Socket.IO+MongoDB+expressでリアルタイム掲示板

全画面_13_04_27_15_08

 

(画像クリックで飛びます。githubはこっち。)

@ITの特集「Node.jsを使ってみよう(2): Node.js、MongoDBでデータの保存 (2/4)」を見て作って見ようと思いました。

expressしゅごい。超楽。

デプロイ先はherokuです。herokuについてはgihyo.jpのこの記事だったり橋本商会さんのこの記事が参考になりました。

あと今回ほぼ始めてCoffeeScript使ったけどなんか書いてて楽しい言語です。手放せません。JavaScriptがgmksすぎるからという可能性も惑星レベルで存在していますが。expressのコードをCoffeeScriptで書くのはこの記事が参考になります。

 

最初の読み込みは結構時間かかります。あとたまにウインドウがドラッグ移動できなくなったりサイズがおかしくなったりといったバグもあります。本当はresizableにしようとしてたけどいろいろめんどかったので中途。まあ7割動けばいいって誰かが言ってたし。

スレッドタイトルウインドウの右上の「答える」をクリックするとレスできます。デフォルトでは5分5分ぐらいで「ちょ」か「エビフライぶつけんぞ」のAAになります。ごくたまに英語でEbi furaiをぶつけます。「返信する」機能は未実装。

TorToiというアプリタイトルは、これから作ろうとしてる感じのやつで、それの基礎としてリアルタイム掲示板を作りました、という感じです。とりあえず次実装したいのは検索やらなんやら。

Three.jsでobjファイルをアップロードして操作

できました!(画像クリックで飛びます。ロードは長い。)

「ファイルを選択」で.objか.mtlをアップロードしていきます。.mtlのみはムリです。

最初は某住宅が表示されますのでいじってみてください。

ソースコードはこちら。php使ってるのでサーバ必要です。

******

アップロードするってことはobjファイルが私の手に渡りますので、それが嫌な方はソースコードを落としていじってください。

******

なんに使えるかっつーとビミョーだなー。細かい話はまた追加します。

Three.jsのOBJMTLLoaderで影を描画

Three.jsはとても便利な関数が用意されていて、OBJMTLLoader = three.js-master/examples/js/loaders/OBJMTLLoader.js もそのひとつです。

<script src="three.js-master/examples/js/loaders/MTLLoader.js"></script>
<script src="three.js-master/examples/js/loaders/OBJMTLLoader.js"></script>

でインクルード(って言うの?)。

んで、とりあえずobj(3DCGファイルの方)を表示させてみたんだが、影が描画できない。

影の描画のやり方はググれば出てくるが、端的に言えば

  • レンダラの設定:renderer.shadowMapEnabled = true;
  • オブジェクトの設定:cube.castShadow = true; cube.receiveShadow = true;
  • ライトの設定:light.castShadow = true; light.shadowCameraVisible = true;

が必要になる。

lightはTHREE.SpotLightじゃないとダメとかいろいろややこしいが、なんとかできた。

だけど、cubeとかプリミティブには影が付いてるけど、OBJMTLLoaderで読み込んだオブジェクトはできてない。あとPlaneGeometryもなぜか影が計算されない。

 

まず、planeに影が描画されない方は公式に解答がありました。

要は

renderer.shadowMapCullFrontFaces = false;

を加えなさいと。

で、肝心のOBJMTLLoaderの方。読み込みはexampleのwebgl_loader_obj_mtl.htmlに従って

var objPath = "files/Sanple.obj";
var mtlPath = "files/Sample.mtl";

var loader = new THREE.OBJMTLLoader();
loader.addEventListener( 'load', function ( event ) {
    var object = event.content;
    object.position.x = -1000;
    object.position.z = 1000;
    object.castShadow = true;
    object.receiveShadow = true;
    scene.add( object );
});
loader.load( objPath, mtlPath );

みたいな感じにしていたのですが、これではダメなようで、OBJMTLLoader.jsを見てみるとloaderが返す値はどうやらオブジェクトの配列?のようなもののようでした。

そこで、OBJMTLLoader.jsの207行目以降の関数finalize_mesh( group, mesh_info )に次のように追加しました。

function finalize_mesh( group, mesh_info ) {
    mesh_info.geometry.computeCentroids();
    mesh_info.geometry.computeFaceNormals();
    mesh_info.geometry.computeBoundingSphere();
    var tmpMesh = new THREE.Mesh( mesh_info.geometry, mesh_info.material );
    // 以下2行を加える
    tmpMesh.castShadow = true;
    tmpMesh.receiveShadow = true;
    group.add( tmpMesh );
}

できた!

でもよく見るとなんかおかしい。影になってるはずのところがなんか光ってる。

しばらく考えて、最初のコレ

renderer.shadowMapCullFrontFaces = false;

のせいだと気づき、コメントアウトすると、ちゃんと表示されました。

影の計算で、最初の面を無視しますよってのがデフォルトでtrueなんだけど、それをfalseにしちゃうと面で構成されたモノがちょっとおかしくなるかもよってことだろう。

デモスクリプトはもうちょっとだけ追加するもん追加してからにします。