2012年3月17日土曜日

DropboxにRESTfulアクセス

少しDropboxにデータを送り込みたいとなって
エンジンをGoogleAppEngineで送り込む事になったので少し調べました。

SDKはiOS,Android(Java),Ruby,Pythonがあります。
今回はJavaを選択したので見ましたがHttp「s」URLConnectionを使用しており、
これがAppEngineでサポートしてないので実行環境でエラーが起こります。

ってことでRESTfulアクセスで行なってみました。

準備
まずここからアプリを登録してクライアントキーを作成します。
作成するとこんな感じ。
AccessTypeは後から変えられません。 「App folder」は専用のディレクトリのみアクセス可能なもの。 「Full Dropbox」はすべてのファイルアクセスです。 専用のディレクトリのみでOKだったんですけど、Fullにしました。
リクエストトークンの取得
https://api.dropbox.com/1/oauth/request_tokenにアクセスを行います。 で戻り値(line)は「oauth_token_secret=biv7oeyp3nvkb1g&oauth_token=ql0l5qpk8h10rjz」みたいな感じです。 buildOAuthHeader()はSDKのコードにもありますが、
    private static String buildOAuthHeader(boolean accessToken) {
        Map<String,String> map = new HashMap<String,String>();
        map.put("oauth_version","1.0");
        map.put("oauth_signature_method","PLAINTEXT");
        map.put("oauth_consumer_key",APP_KEY);

        String sig = null;
        if ( accessToken ) {
            map.put("oauth_token",TOKEN_KEY);
            sig = encode(APP_SECRET) + "&" + encode(TOKEN_SECRET);
        } else {
            sig = encode(APP_SECRET) + "&";
        }
        map.put("oauth_signature",sig);

        StringBuilder buf = new StringBuilder();
        buf.append("OAuth ");

        Iterator<Entry<String, String>> itr = map.entrySet().iterator();
        while ( itr.hasNext() ) {
            Entry<String, String> entry = itr.next();
            buf.append(entry.getKey() + "=" + entry.getValue() + ",");
        }
        String data = buf.toString();
        return data.substring(0,data.length()-1);
    }
といった感じでヘッダに設定してあげてます。
認可用のURL作成
lineにある「oauth_token」の値を使います。
        System.out.println("https://www.dropbox.com/1/oauth/authorize?oauth_token=" + argsMap.get("oauth_token"));
        System.in.read();
read()はブラウザからの入力待ちです。 ブラウザを立ち上げるとDropboxに認証してその後許可のアクションがあります。
このアクセスの際に「oauth_callback」を指定しておくとコールバックしてくれます。 操作後の挙動としては ・許可(あり):コールバックURLに戻る ・許可(なし):許可画面に遷移 ・拒否:Dropboxのhomeに遷移 って感じです。 ここで良くOAuthでの認可コードを取るようなコードを書いてましたが、 コールバックを指定したとしてもそれはDropboxにはありません。 ※ただしコールバックにはuidとoauth_tokenを付けてくれるのでユーザ特定は可能です。 ようは「認可」を行った時点でアクセストークンは取れる状態になるってわけです。
アクセストークンの取得
認可されていれば以下ができます。
        TOKEN_KEY    = argsMap.get("oauth_token");
        TOKEN_SECRET = argsMap.get("oauth_token_secret");
        url = new URL("https://api.dropbox.com/1/oauth/access_token");
        connection = (HttpURLConnection)url.openConnection();
        connection.setDoInput(true);
        connection.addRequestProperty("Authorization", buildOAuthHeader(true));
        connection.setRequestMethod("GET");

        inStream = connection.getInputStream();
        input = new BufferedReader(new InputStreamReader(inStream));

        line = input.readLine();
リクエストトークンで取ってきていた値を設定してbuildOAuthHeader()の値をtrueの方で動かします。 アクセストークンもリクエスト時と同じように 「oauth_token_secret=biv7oeyp3nvkb1g&oauth_token=ql0l5qpk8h10rjz」 という値で入ってきます。 これでアクセス可能になります。
ファイルのアップロード
使用するのはファイルのアップロードだけだったので PUTでbodyに設定してあげてアップします。
    public static void put(String name,byte[] data) throws Exception {

        URL url = new URL("https://api-content.dropbox.com/1/files_put/dropbox/" + name);
        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.addRequestProperty("Authorization", buildOAuthHeader(true));
        connection.setRequestMethod("PUT");

        OutputStream outputStream = connection.getOutputStream();
        outputStream.write(data);
        outputStream.close();

        InputStream inStream = connection.getInputStream();
        BufferedReader input = new BufferedReader(new InputStreamReader(inStream));
        String line = "";
        while ((line = input.readLine()) != null) {
            System.out.println(line);
        }
    }
ここでURLにある「dropbox」です。 「Full Dropbox」を選択した場合はこれでOKなのですが、 「App folder」の場合は「sandbox」を指定してあげます。 前者はフルなのでユーザのディレクトリ通りに設定していったりするのですが、 sandboxの場合、「apps/[アプリID]」が使用されて、そこのアクセスのみが可能です。 フルアクセスのViewerアプリを作らない限り、後者を選択した方が良いでしょうね。 ※ブログ書き始めてアプリを変更しました。
感想
DropboxのAPIって使うとは思ってなかったのでアクセスするとは思いませんでした。 まぁ本家が作っているクライアントが同期とかもかなり優秀なので あまりアクセスする人はいないのかもしれませんね。 ただファイルアクセスはかなり簡単にできたので、 クラウドから一時的に持ってくるツールは作りやすいかな?と感じました。 何点か気になるのは ・POSTアクセスのAPIがGETで取得できる ・PLAINTEXTを使用している ・一度認可されていると許可とかが出ない って感じですかね? ちなみに自分以外に公開するには 「My Apps」のところで「Additional users」をONにする必要があります。 製品バージョンと特に制限はないみたいに見えます。 ※開発中は5人だけっぽい感じには書いてありますけど。

2012年3月10日土曜日

Xtionで画像を作ってみる

XtionでDepthGeneratorで深度を取得できますが、
カメラ的に使うにはImageGeneratorを使用します。

今回はJavaで取得してみます。


設定値
実はここではまりました。。。
<Node type="Image" stopOnError="false" >
 <Configuration>
  <MapOutputMode xRes="640" yRes="480" FPS="30"/> 
 </Configuration>
</Node>
とConfigに記述しておきます。 画面を指定してない場合、320x240で取得できます。 なんでハマったかというと 描画する予定の画面(640x480)に対して、 320x240のデータを埋め込んでしまった為、うまく描画できないという 現象に引っかかったからです。 なので作成する画像が320x240の場合は指定しなくてもOKです。
画像データの作成
imageGen = ImageGenerator.create(context);
でインスタンスを取得。 描画処理等の永久ループの箇所に
  ImageMetaData imageMD = imageGen.getMetaData();
  ImageMap imageMap = imageMD.getData();
  ByteBuffer image = imageMap.createByteBuffer();
  while ( image.remaining() > 0 ) {
   int pos = image.position();
   byte pixel = image.get();
      imgbytes[pos] = pixel;
  }
という風にbyte[]を作成します。 imageMapにある値はwidth*height*3になります。 *3の値はRGBを表しています。 表示する際には深度の時と同じように
        DataBufferByte dataBuffer = new DataBufferByte(imgbytes, width*height*3);
        WritableRaster raster = Raster.createInterleavedRaster(dataBuffer, width, height, width * 3, 3, new int[]{0, 1, 2}, null); 
        
        ColorModel colorModel = new ComponentColorModel(
          ColorSpace.getInstance(ColorSpace.CS_sRGB), 
          new int[]{8, 8, 8}, 
          false, 
          false, 
          ComponentColorModel.OPAQUE, 
          DataBuffer.TYPE_BYTE);

        bimg = new BufferedImage(colorModel, raster, false, null);
        g.drawImage(bimg, 0, 0, null);
となります。 (gは画面上にあるpaint用のGraphicsオブジェクトです) これでカメラのように画像を出力する事が可能です。
( ー`дー´)キリッ。。。もちろん動きますよ。
例えば
私のアプリはWebSocketと連携させているので この画像データをそのままWebSocketで送ってみようと思っています。 しかしそれはXtionとはあまり関係ないので 例えばですが1枚無人の背景データを用意してみて、 人間として認識された部分を背景データと重ねて塗りつぶす。 というプレデターごっこ(おそらくある程度マージンがあるので微妙に人の形が浮かび上がる)等が できるかなぁ。。。と思っています。 Depthとかの話とか書いてないから何か微妙な説明になるかなぁ、、、 もう少し、Generator周りとかがかっこよくなったら、記述したいと思います。