2010/03/18

WebHookを溜めてRSSフィードを生成するWEBサービスを作ってみた

とある目的(については後日改めて)で使いたくて、GAE for Javaのお勉強がてら作ってみた。WebHooksの意義を考えると本末転倒な感じもするけど、深くは考えないことにする。。。

hook2feed - create feeds from web hooks

Googleアカウントでログインして「create new setting」すると、専用の2つのURL(「hook point URL」と「feed URL」)が作成される。hook2feedは、、、
  • 「hook point URL」の方に来たWebHook(HTTP POST)の履歴(最新20件分)を溜める
  • 「feed URL」の方で、WebHook履歴からRSSフィードを生成する
というように動く。
ちなみに、PubSubHubbubに一応対応していて、WebHookされたらhubにpublishするようになってる。PubSubHubbub対応のフィードリーダ(livedoorリーダなど)で見ると、リアルタイム性を体感できるはず。

さて、「とある目的」以外でどんな用途があるか、我ながらよく分からないけど、ブラウザで閲覧中のサイトタイトル&URLとダイアログボックスで入力したメモを「hook point URL」にWebHookしてフィード化するbookmarkletを作ってみた。以下のテキストボックスに「hook point URL」の末尾10桁のIDを入力してボタンを押すと、bookmarkletとして使えるリンクができる。
「hook point URL」のID(10桁) :
気付けば、まんま「あとで読む」なんだけど、役に立つシーンはあるかしら。他にも、HookHubを使って色んなWebHookをフィード化してみると、うれしいシーンが見つかる、かもしれない。

2010/03/06

GAE for Javaでquoted-printableなメールの受信

Google App Engine for Javaでメールを受信する処理を作ってたら、quoted-printableエンコーディングされたメール本文をデコードすると、デコード結果が途中で切れてしまう現象に遭遇した。調べてみると、quoted-printableエンコーディングでいうところの「Soft Line Breaks」がうまく認識されないことが原因だったので対策してみた。

まず、「Soft Line Breaks」については、RFCのquoted-printableエンコーディング規則の中で以下のように説明されている。要は、長すぎる行には適宜「=」+「改行」を入れて分割する、というエンコーディングが行われるとのこと。
Rule #5 (Soft Line Breaks):
The Quoted-Printable encoding REQUIRES that encoded lines be no more than 76 characters long. If longer lines are to be encoded with the Quoted-Printable encoding, 'soft' line breaks must be used. An equal sign as the last character on a encoded line indicates such a non-significant ('soft') line break in the encoded text.
RFC 1521: MIME Part One
RFCにはこう書いてあるのに、試してみると、JavaMail 1.4のMimeUtility.decode()でも、Commons Codec 1.4のQuotedPrintableCodec.decode()でも、Soft Line Breaksが正しく処理されず、最初のSoft Line Breakのところまででデコード結果が切れてしまう。で、なんでかなぁと思ってQuotedPrintableCodecのドキュメントをちゃんと読んでみると、きちんと明記されていた。
Note:
Rules #3, #4, and #5 of the quoted-printable spec are not implemented yet because the complete quoted-printable spec does not lend itself well into the byte[] oriented codec framework.
QuotedPrintableCodec (Commons Codec 1.4 API)
というわけで、自分でSoft Line Breaksを取り除いてやる必要がある。MimeMessageから取り出したMimeBodyPartをデコードする処理は、例えばこんな感じ。効率の悪いコードだけど、今のところ問題なく動いているようだ。
private String _decodeBody(MimeBodyPart bp)
{
  // parse header
  String contentType = null;
  String contentEncoding = null;
  String charset = null;
  try{
    contentType = bp.getContentType();
    contentEncoding = bp.getEncoding();
  }catch(MessagingException e){
  }
  String[] elems = contentType.split(";");
  for(String elem : elems){
    if(elem.trim().startsWith("charset=")){
      charset = elem.trim().substring("charset=".length());
    }
  }
  if(charset!=null){
    if(charset.startsWith("\"")) charset = charset.substring(1);
    if(charset.endsWith("\"")) charset = charset.substring(0, charset.length()-1);
  }
  // get inputstream
  InputStream in = null;
  try{
    in = bp.getRawInputStream();
  }catch(MessagingException e){
  }
  if(in==null) return "";
  // convert quoted-printable
  if(contentEncoding!=null && contentEncoding.equals("quoted-printable")){
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    int len;
    byte[] buffer = new byte[1024];
    try{
      while( (len=in.read(buffer, 0, buffer.length)) != -1 ){
        baos.write(buffer, 0, len);
      }
    }catch(IOException e){
    }
    byte[] b = baos.toByteArray();
    baos = new ByteArrayOutputStream();
    for(int j=0;j<b.length;j++){
      if(b[j]=='=' && j<b.length-1 && b[j+1]=='\n'){
        j++;
      }else{
        baos.write(b[j]);
      }
    }
    b = baos.toByteArray();
    in = new ByteArrayInputStream(b);
  }
  // decode
  if(in!=null && contentEncoding!=null){
    try{
      in = MimeUtility.decode(in, contentEncoding);
    }catch(MessagingException e){
    }
  }
  if(in==null) return "";
  // read body
  Reader r = null;
  if(charset!=null){
    try{
      r = new InputStreamReader(in, charset);
    }catch(UnsupportedEncodingException e){
    }
  }else{
    r = new InputStreamReader(in);
  }
  StringBuffer sb = new StringBuffer();
  BufferedReader br = null;
  try{
    br = new BufferedReader(r);
    String line = null;
    while( (line=br.readLine())!=null ){
      sb.append(line.trim());
      sb.append("\n");
    }
  }catch(IOException e){
  }finally{
    if(br!=null){
      try{
        br.close();
      }catch(IOException e){}
    }
  }
  return sb.toString();
}

2009/10/13

Everioの動画をMPEG4にお手軽変換できるワークフロー

動画撮影にはHDDビデオカメラのビクターEverio(GZ-MG77)を使っているのだが、これで撮れるMPEG2形式の動画ファイル、実はMacではそのまま扱えない。QuickTimeで再生できないし、当然、iMovieで使う事もできない。Apple純正のQuickTime用「MPEG2再生コンポーネント」なるものはあるが、EverioのMPEG2ファイルに関しては再生ができても音が出ないそうだ。Everioの音声コーデックに非対応ならしい。

というわけで、WindowsからMacに乗り換えて数ヶ月、動画を撮り貯めるばかりで、編集する事はもちろん、再生する事すらもできていなかったのだが、ようやく最近になって、このEverioの動画ファイルを扱える環境をMac上に整えた。と言っても、再生のためにVLCを、iMovieで読み込めるMPEG4への変換のためにffmpegXを、それぞれインストールしただけ。こんなに簡単ならもっと早く使い始めておけばよかった。

これらのツール、対応する動画/音声のフォーマットが豊富で、UIはシンプルで使いやすく、とにかく素晴らしく便利。そこで今回は、便利ついでにAutomatorでワークフローを作ってみた。
Automator画面
このワークフローでは、ffmpegXのアプリパッケージに含まれているffmpegというコマンドを利用している(っていうか、そもそもffmpegXはffmpegのGUIラッパーなので、ffmpegこそが本体なんだけど)。「選択されたFinder項目を取得」の後ろに、ffmpegを実行するための「シェルスクリプトを実行」をつないで、Finder用のプラグインとして保存。Finderで選択した動画ファイルをMPEG4に自動変換してくれる。
ちなみに、シェルスクリプトの中身を書き下すと以下のような感じ。
for f in "$@"
do
/Applications/ffmpegX.app/Contents/Resources/ffmpeg -i "$f" \
-vcodec mpeg4 -s 640x480 -aspect 4:3 -r 29.97 -b 4000k -qmin 2 -qmax 9 \
-acodec aac -ab 96k "$f".mp4
done
ffmpegにはかなり豊富なコマンドオプションが用意されているので、特に出力の各種パラメータについては色々とカスタマイズの余地がありそうだが、これで変換がお手軽にできるようになった。

ワークフローを作るのは初めてだったが、ちょー便利だな、これ。


[2010/03/22追記]
ちなみに、ワイド(16:9)で撮った動画を変換するために、
for f in "$@"
do
/Applications/ffmpegX.app/Contents/Resources/ffmpeg -i "$f" \
-vcodec mpeg4 -s 720x405 -aspect 16:9 -r 29.97 -b 4000k -qmin 2 -qmax 9 \
-acodec aac -ab 96k "$f".mp4
done
と、スクリプトの縦横比関連の指定("-s"と"-aspect")を変えたものも別途用意して使っている。

2009/10/05

Wiiリモコンでラジコン制御

Wiiリモコンを使ってArduinoを載せたラジコンを無線制御できるようにしてみた。
ラジコンとWiiリモコン
基板部分のアップ
と言っても直接つないでいる訳ではなく、MacBookを介して、Wiiリモコン⇔MacBookはBluetoothで、MacBook⇔ラジコン上のArduinoはXBee(Zigbee)で、それぞれつないでいる。プログラム上の問題がまだ色々とあるものの、実際に走行させてみる事もできた。Wiiハンドルを握って操作してると、『リアルマリオカート』な気分になれるかも。;-)

※走行させているシーンではないが、こんな感じで操作できる。


ちなみに、写真にある通り、GPS受信機や各種センサ(加速度、ジャイロ、デジタルコンパス)など、『操作』には必要ないものも色々と載せてあるが、これらのデータはMacBookで記録できるようにしてある。今後、走行させた時のデータを色々と取ってみる予定。目指せ『自動制御』!

2009/10/03

Arduino 0017で、Megaのアナログ入力ピン8〜15が使えないバグ

Arduino Megaでは16本のアナログ入力が使えるはずなのだが、自分の環境では何故か0番から7番のピンしか正常に使えなかった。8番から15番のピンを読み取った結果を見てみると、ただのノイズ、という感じ。
Arduino Forumを調べてみたら、ばっちり同じ問題に関する書き込みがあった。結局、Arduino IDE 0017のバグだったようだ。
Sorry guys, this will be fixed in Arduino 0018. In the meantime, you can edit hardware/cores/arduino/wiring_analog.c and change:

ADMUX = (analog_reference << 6) | (pin & 0x0f);

to:

ADMUX = (analog_reference << 6) | (pin & 0x07);

Arduino Forum - Trouble with Mega Anolog in port.
上記の通り修正したら問題なく使えるようになった。