Home > develop > ruby Archive

ruby Archive

KeynoteをRubyから操作するkeynote-clientを作りました

  • 2015年8月10日 00:35
  • ruby

唐突の約1年ぶりのエントリーになりますが、itunes-clientの親戚みたいなかんじのkeynote-clientを作ってみました。

これは何?

itunes-clientと同様、高レベル(のはず)なAPIでKeynoteを操作できます。たとえばこんなかんじ。

require 'keynote-client'

# インストール済のテーマ一覧を取得
themes = Keynote::Theme.find_by(:all)

# 特定の名前のテーマを取得
theme = Keynote::Theme.find_by(name: 'cookpad').first

# テーマを指定して新しいドキュメントを作成
doc = Keynote::Document.new(theme: theme, file_path: '/path/to/slide_name.key')

# 現状のスライド一覧
doc.slides

# 利用可能なマスタースライド一覧
doc.master_slides

# マスタースライドを指定して、新規スライドを追加
doc.append_slide("タイトル & 箇条書き")

# 保存
doc.save

スライドを追加するあたりのAPIがかなり微妙なんですけど、Keynoteのマスタースライドを一意に指定する方法が名前しか無く、これも言語環境によって名前が同じドキュメントでも変わってしまうという、かなり辛い仕様なので対応方法が結構難しい感じです。頑張ればどうにかできそうか。。本当はシンボルを指定したい。

あとスライドはappendするとき、またはappendした後にタイトルや本文を更新できるようにすれば、シンプルなスライドであればひと通りの自動生成できそうかな。

背景

@k0kubunがLTのスライド作らずにスライドを作るツールを夜な夜な作ってるのを見てゲラゲラ笑ってたのですが、AppleScriptで疲弊してそうな印象が強かったので、itunes-clientの知見をどうにか活かせないかなーと思ったのと、JXAを一度使ってみたいなーと思ってたので、その練習がてらがっと書いてみました。

JXA、謎仕様が依然多いですが、少なくともAppleScriptよりだいぶ書きやすいので、感覚つかむまでの速度はだいぶ速くいけました。md2keyもJXAに切り替えちゃってもいい気もしますね。。

今後

スライドのタイトル、本文くらはいじれるようになると実用段階になると思うので、そうしたらmd2keyの下地をいい感じにしてあげられないかなーと企んでます。まだまだ実装雑なので、PRもお待ちしています!

middleman 3.2系から3.3系へupgrade時、wrap_layoutに気をつける

  • 2014年9月 6日 23:25
  • ruby

ALOHA FISHMANSのサイトで使ってるmiddlemanのバージョンは、長らく3.2.0を使っていたのですが、いい加減そろそろ最新のバージョンにあげておくか。。と思ったところ、結構ハマったのでメモ。

空白のページがrenderされる

バージョンの変更はGemfileを書き換えてbundle updateすると、基本的にはOK。Gemfileのdiff的にはこんなかんじ。

 gem "redcarpet", '~> 3.1.1'
-gem "middleman", "~> 3.2.0"
+gem "middleman", "~> 3.3.5"
 gem "middleman-blog", '~> 3.5.2'
 gem "middleman-minify-html", '~> 3.1.1'
 gem "middleman-deploy", '~> 0.2.3'

renderされるページの内容を確認していたところ、基本的に問題ないように見えつつ、blogページだけ何も出力されません。(bodyがカラ)

何か仕様が変わったんだろうと思いつつ、ChangeLog見てもよくわからないな。。。と途方に暮れて検索検索。

wrap_layout

の、ところ気になるIssueを発見。

見事これでした。テンプレートエンジンにhaml/slim を使っていて、wrap_layoutでレイアウトを上書きしているとき、

- wrap_layout :layout do
  ...

= wrap_layout :layout do
  ...

にしないとダメな模様。これで手元でも正常にbodyが出力されるようになりました。

で、よくよくChangeLog見直すと

Update Padrino to 0.12.1. Introduces BREAKING CHANGE for Haml. Helpers which take blocks used to require - instead of = to work correctly. Now, all helpers which output content should use =.

って、書いてるのが該当する話の模様。(気づかねぇ。。。)

TravisCIの結果を光で通知するtravis-blink1を作りました

  • 2014年9月 3日 01:12
  • ruby

これは何?

blink(1)という、USB接続で発光するガジェットをhmskさんからいただいたので、遊びでつくってみたgemです。

指定したgithubのプロジェクトについて、TravisCIの結果を発光して通知してくれるものです。CIが実行中のときは黄色で点滅、テストに失敗したときは赤色で点滅、テストが通ったときは緑で発光します。 動作の様子はこんなかんじ。

テスト失敗時

テスト成功時

blink(1)がおもしろいのは、プログラマブルに発光できること。色や点滅時間など、ことこまかく調整できるので、任意のイベントの通知として発光させることが可能になります。

いろんな言語からblink(1)のAPIを叩くクライアントがあるのですが、rubyだとrb-blink1がひとまずよさそう。travis-blink1でも内部で利用しています。

とりあえずチカチカさせるには、これだけでOK。

require 'blink1'

Blink1.open do |blink1|
  blink1.blink(255, 255, 0, 5)
end

使い方

お約束の

gem install travis-blink1

でインストール後、手元のディレクトリがプロジェクトをforkした場所でgit remoteの設定がされている場合は

travis-blink1

だけ実行すれば、git remoteの結果から、TravisCIの状態を監視。 また、任意のディレクトリから実行するときは

travis-blink1 katsuma/itunes-client

なんかのように「ユーザ名/プロジェクト名」を指定すればOKです。

雑感

blink(1)は、できることがシンプルなので、すぐに試せるのはいいですね。いっぱい天井からぶら下げるなどして、hueみたいに照明代わりにしみても楽しそうです。

Middleman + SinatraでALOHA FISHMANSのサイトをリニューアルしました

  • 2014年3月 9日 03:31
  • ruby

去年の秋頃、友人経由で、Fishmansファンのためのイベント「お彼岸ナイト」を開催しているALOHA FISHMANSの人たちを紹介してもらったのですが、何か僕で手伝えることがあれば、、というわけでサイトのフルリニューアルを手伝わせていただきました。

今回利用した技術としては

フロントエンド

バックエンド

インフラ

コード管理

  • github(のプライベートレポジトリ)

な、感じ。

今回、このリニューアルでいろいろハマったところや工夫できたことがあったので、もろもろまとめたいと思います。

CSSフレームワーク

以前のサイトは、PCでの閲覧のみ意識していたような作りになっていたので、スマホ(というかタッチデバイス)への対応が一番の要件でした。

さて、じゃぁレスポンシブ対応のCSSフレームワークを選ぶか、、と思って選定に入りました。検討したのはこんな感じ。

CSSフレームワークは似たものが乱立していてさてどうするか。。と思ったのですが、こんな基準で選びました。

  1. サイズが小さい
  2. スマートフォンに最適化されたUIが標準で用意
  3. フレームワークに依存したマークアップを強要されない

Bootstrapは1.が引っかかってやめました。あと、デフォルトの見た目が「いかにも」な感じになるのでもうちょっとシンプルで単純なスタイルが欲しかったこともあって利用はヤメ。

Foundationは全体的にバランスがいいのですが、メニューまわりのマークアップがちょっと複雑なので保留に。最終的には利用することにしたのですが、正直いまだにメニューまわりにクセがある印象なので、そんなに強くオススメできません。。

Skeltonはシンプルでいいんだけども2の観点で見ると、自分でスタイルいじるのは最低限に抑えたかったので、全然スマホサイトっぽい感じがしなかったのでヤメ。

HTML KickStartはすごく単純なマークアップで一番最初は利用していたのですが、メニューまわりのデフォルトの見た目がまったくスマホサイトっぽくない感じだったので途中でヤメました。

Pureはフォント周りのスタイルは一番好きなのですが、マークアップにpure-*とフレームワーク依存のclassやdata属性を強要されるのがどうにもシックリこなかったのでヤメ。

ベストなものは無い

これだけ乱立してるCSSフレームワークだから、さすがにグッとくるものがあるだろう。。と思い込んでたのですが、正直いまだにグッとくるものは見つかってません。。 Foundationを消去法的に利用しましたが、デフォルトのリストUIの見た目はあまりグっとこなかったので、結局いろいろベースとなるスタイルは自分で用意しました。

たとえばクックパッドで利用しているSaraフレームワークは、このへん見た目的にもマークアップ的にも、相当使いやすくなっていると実感できたので、SaraがOSSで公開されてれば。。(チラッチラッ)と何度も思った次第です。

そう考えると、これだけCSSフレームワークが乱立するのもやはり一周回って理解できるわけで、みんな痒いところに手を届かせるために「俺の考えた最強CSSフレームワーク」を作ろうとするんでしょうね。。

middleman

フロントエンドの基盤はmiddlemanにするのは最初から決めていました。

オリジナルのサイトはベースはPHPで実装されていて、チケット管理ページなど動的ページが一部あるものの、ユーザに見える部分はほぼ全てのページが静的な情報なため、PHPのメリットはレイアウトをDRYに書ける、というところに留まっていそうでした。

middlemanは最終的に静的なページを出力しつつ、

  • haml/slimなどのテンプレート言語
  • assetパイプライン
  • LiveReload

などのツールがすぐ使えるので、最近のRailsで開発するような感覚で開発を進めることができるのが大きなメリットです。

実際今回触った感想としてmiddlemanは大正解でした。今後も数ページだけのサイトだったらmiddlemanを利用しない理由は無いんじゃないかな、と言えそうです。1点を除いては。。

Sinatra

1点を除いては、と言ったのは動的なページが出てきたときの対応です。

middlemanはそもそも静的サイトジェネレータなので、動的なページなんて入れようとするほうが悪いのですが、いかんせん必要なものは必要です。今回もイベントのチケット予約に1ページだけ必要でした。

ロジックはシンプルなのでSinatraで完結できれば。。と安易に思っていたのですが、いかんせん事例がほとんどありません。milddeman公式サイトにももちろん載っていません。

で、おそらく唯一であろうヒントはmiddleman作者のThomasさんのコードの断片。要するにconfig.rbに特定のパスだけSinatra::Baseにリクエストを処理させる方法です。

「開発時はいいけどリリース時はproduction環境にどうやって載せるんや。。。」と悩んでたのですがnginxで特定のパスへのリクエストだけUnicorn(で動かしたSinatra)へリクエストを流すようにすればなんとかなりそうかも?と思ってトライ。このへんのインフラ全然触ったことなかったのでハマりましたが、なんとかかんとかできました。

ディレクトリ構造

開発時はこんな感じにしてます。

  • build (ビルドしてできる静的HTMLファイル)
  • app (Sinatraなんかの動的な処理をまとめ)
    • config.ru (production用、開発時は使わない)
  • source (静的ページの開発)
  • config.rb (Sinatraの設定)

middleman buildするとminifyやgzip化されたHTMLファイルがbuildディレクトリにできるので、リリース時はこの中身をまるっとrsync。nginxも基本このファイルを直接返します。

appはSinatraの実態。たとえばこんなかんじ。

app/ticket_reservation.rb

require 'sinatra'
require 'active_record'
require 'mysql2'
require_relative 'models/ticket'

config = YAML.load_file('./database.yml')
ActiveRecord::Base.establish_connection(config["db"]["development"])

class TicketReservation < Sinatra::Base
  post '/reserve' do
    ...
    redirect '/events/reserved'
  end
end

こいつをconfig.rbから呼び出しておく。

config.rb

require_relative 'app/ticket_reservation'

...

map '/tickets' do
  run TicketReservation
end

これで、middleman serverで手元でサーバを起動すると/ticketsのリクエストだけSinatraでハンドリングすることができます。

app/config.ru

開発時は利用しないのですが、productionではUnicornで利用するために必要です。プロセスの再起動はいい感じのタイミングにお願いしたいので、unicorn-worker-killerに任せてます。(設定はデフォルトのまま...)

require_relative 'ticket_reservation'

# Unicorn self-process killer
require 'unicorn/worker_killer'

# Max requests per worker
use Unicorn::WorkerKiller::MaxRequests, 3072, 4096

# Max memory size (RSS) per worker
use Unicorn::WorkerKiller::Oom, (192*(1024**2)), (256*(1024**2))

# Run main app
run TicketReservation

production環境

こんなかんじのディレクトリツリーにしてます。

  • alohafishmans.com
    • app
    • public

リリース時は

  • build ⇛ public
  • app ⇛ app

へrsyncして、appのコードを変更したときのみUnicornを再起動させています。

nginx

config.rbでの設定と同じような感じで設定します。/ticketsへのリクエストだけをUnicornを利用するようにします。こんなかんじ。

alohafishmans.com.conf

upstream unicorn_alohafishmans {
  server unix:/path/to/alohafishmans.com/app/tmp/sockets/unicorn.sock
  fail_timeout=0;
}

server {
  server_name alohafishmans.com;

  location ^~ /tickets/ {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://unicorn_alohafishmans/;
  }

  location / {
    root /path/to/alohafishmans.com/public;
  }
}

その他の問題

基本的な構成は以上なのですが、その他にも細かいとこにもいろいろハマりました。。一応メモがてら残しておきます。

濁点問題

今回ブログのコンテンツのURLを、SEOを意識してタイトルをパス名に入れたURLにしました。例えばこんな感じ。

これも実際は「3/22「お彼岸ナイトvol.8 2014春」まであと1ヶ月!!」ディレクトリと、その直下にindex.htmlを作っているだけですが、buildしてrsyncするとアクセスできず404が。 どうもディレクトリ名がバグっておかしなものになっている模様。。

日本語が悪い??と思いきや、アクセスできるページもある。1文字づつ削って、問題の切り分けをするとどうやら「まであと1ヶ月」の「で」が悪い模様(!)

なんだこりゃ。。。。と思いきやUTF8-Macのエンコーディングに起因する話な模様。解決策としてはrsyncを3系にバージョンアップして(brew install rsyncでOK)iconvオプションをつければ良い。

rsync --iconv=UTF-8-MAC,UTF-8 -avz build/ foo@bar:/path/to/alohafishmans.com/public

FacebookのLikeをすると「You like 404 Not Found」問題

Likeボタンはそれなりに何度も設置しているつもりなのですが、今回はogpの設定をして、DeveloperページのDebuggerで見て情報が正確に設定されているにもかかわらず。Likeするとダイアログに「You like 404 Not Found」が出る、という謎現象に悩まされました。

類似の話としてSEM pdxの記事で全く同じものがあったのですが、向こうでは「IPv6を無効にしたらどうにかなったよ」と言ってる一方、僕の方では無効にしても全く変化なし。

で、やっとこさ気づいたのですが元々のボタンを

.fb-like{ data: { layout: 'button_count', width: '130', href: CGI.escape(page_url) }}

にしていたのですがdata-hrefの指定がどうも間違っていいた模様。正解は

.fb-like{ data: { layout: 'button_count', width: '130', href: page_url }}

コレ。

日本語がパスに含まれてるのを気にしてURLエンコードしていたのですが、まさかそれが足を引っ張っていたとは。。。 (ドキュメント見てもURLエンコードについて特に何も書いて無さそうなのですが。。)

まとめ

久々にゼロの状態からインフラ、DB、アプリケーションの実装まで全部実装したのですが、いい勉強になりました。特にインフラ面は現職ではセットアップ周りを自分ですることはここ数年ほとんどなかったので、仕組みを理解するいい機会になったと実感しています。

また、middlemanは相当使いやすく便利な一方、少しでも凝ったことをしようとするとハマるポイントも割りとあるので、改めて別途エントリをまとめるなり、プラグイン系へ修正PR投げるなりして貢献したいと思います。

最後に、Fishmans好きな人はお彼岸ナイトもぜひぜひ遊びにきてください :)

RubyMotion2.11がリリースされてspecの中でcontextが利用できるようになりました

  • 2013年10月16日 22:24
  • ruby

相変わらずの週末RubyMotion野郎な日々です。

前回の記事の中でMacBaconにPull Request送った話を書きましたが、本家のRubyMotion自体とは実装が分かれていることに気づいたので、改めてPull Requstを送ることにしました。(実質同じ内容)

実はこのPRなかなかマージされるどころか一切反応がないまま数週間放置されていました。つついていいのかどうなのか温度感もわからないままムーと唸っていたのですが、なんとなく唐突にtwitter経由で確認してもらうようにお願いしてみました。突然のPR確認依頼。

さて、どうなるかな、、と思ってたら3分後mentionが。 まじでー!ってくらい速攻でマージされました。驚愕。。 RubyMotionのメンテナにCocoaPodsメンテナのAlloyさんがJoinしてくれて(前回のMacBaconのPRを見ていただけていた)理解が早かったのかな、、と思いつつ、なかなか動きがないPRは個別でtwitterなり何なりでつついてみるのはアリなようですね。うーむ。

2.11リリース

そうこうしていたら、RubyMotionの2.11が昨日リリースされました。リリースノート見ていたら、確かに僕のPRも入っていましたね!これでSpecの中でcontext使い放題です!!
= RubyMotion 2.11 =

  * Added the `rake clean:all' task which deletes all build object files
    (ex. those in ~/Library/RubyMotion/build). We recommend using that task
    before building an App Store submission.
  * Added support for Xcode asset catalogs. This can be used to manage all your
    image assets in a visual way, including your application's icons. You can
    create and edit a new catalog like so:
    $ mkdir resources/Images.xcassets && open -a Xcode resources/Image.xcassets
  * Fixed a long standing limitation in the compiler where overriding in Ruby
    an Objective-C method that accepts a C-level block was not possible.
  * Fixed a regression where `return' from a block would terminate the app.
  * Improve the build system to always copy embedded.mobileprovision. Thanks to
    Jan Brauer for the patch (pull request #121).
  * Fixed a bug where a boxed struct would incorrectly be interpreted as a
    object type, leading to the dispatcher not recognizing a signature.
  * Fixed a bug where compiled object files of a vendored project were not
    actually being cleaned when running `rake clean`.
  * Fixed a bug where defining a singleton method on an object inside a method
    with named parameters (Objective-C-style selector) would result in that
    method being defined in the runtime with a wrong selector.
  * Fixed a bug where Range objects created with non-literal begin/end points
    would never be released, and therefore leaking memory.
  * Added `context` method as `describe` alias in spec.  Thanks to Ryo Katsuma
    for the patch (pull request #134).
  * Fixed a small internal memory leak in the dispatcher when sending the
    #method_missing message.
  * Fixed a bug in the compiler where providing nil as the value of a C-level
    block argument would not actually pass NULL but an empty Block structure
    instead. Thanks to Ruben Fonseca for the detective work.
  * [iOS] Fixed a bug where device log is wrong filtered with `rake device' 
    when performed day is 1-9.
  * [iOS] Added support to launch the app as 64-bit in simulator.
  * [iOS] Fixed where non-retina iPad simulator does not launch as default.
    Thanks to Fabio Kuhn for the patch (pull request #133).
  * [iOS] Fixed a regression where certain GameKit class properties could not
    be used (ex. GKMatchRequest#minPlayers).
  * [iOS] Fixed a link error where "ld: framework not found IOKit" is caused
    with iOS 7 SDK when it will run `rake device'.
  * [OSX] Fixed the wrong default settings of short cut key in menu. Thanks to
    Kazuhiro NISHIYAMA for the detective work.

変更はほんの数行だけど、自分のちょこっとした貢献で世の中のソフトウェアがちょこっと良くなるのは嬉しいですね。

RubyMotionはじめました

  • 2013年9月19日 01:06
  • ruby

身の回りのiTunes関連で書きたいコードはtaifu, musical, itunes-clientあたりが落ち着いて一段落しました。 そろそろここらでiOSアプリを書けるようになりたいなぁ、でもObjective-Cをゼロから学ぶのもなかなか時間かかりそうだなぁ、、ともやもやしていたところ巷で噂のRubyMotionの存在を思い出したので、週末軽く手を出してみました。

RubyMotionについて細かな説明は省きますが、要するにRubyでネイティブのiOSアプリを作れるコンパイラ、テストスイートなどのツール群です。 Xcodeを(インストールはされていないとダメですが)起動しなくても、ターミナル+好きなエディタでサクサクとTDDでiOSアプリを書けるのが非常に良さそうです。ひとまずこの記事がいい感じにまとまってて良さそうです。

最初の一歩

ひとまず僕はこんな環境で始めてみました。

motion-modeはさくっとDash連携ができて、「このAPIどういう意味なんだ。。」とか疑問に思ってもすぐ調べられるのがとてもいいですね。補完は「ものすごく賢い!」とまでは正直思わなかったのですが、まぁまぁいいかな、な感じ。ここはさすがにXcodeの方が賢そうではあります。

チュートリアル

一番驚いたのは日本語情報が結構多いこと。 とくにRubyMotion.jpは、とんでもないほどの量の翻訳ドキュメントが揃っています。チュートリアルも文句ないレベルで翻訳されているので、ひとまずこれをなぞれば良さそう。僕もUITabBarController付けただけのありがちなサンプルアプリを作ってムフムフしていました。XcodeのDeveloperPreview版を利用したら、ちゃんとiOS7用でビルドできましたよ。

テスト

RubyMotionに興味をもったきっかけの1つがRSpecぽい形でテストが書けること。マジで??と最初はびっくりしたけど、確かにこんな感じで書けた。 これは、タップして状態が変わってるかどうかのテスト。

describe "button controller" do
  tests ButtonController

  it "changes instance variable when button is tapped" do
    tap 'Test me!'
    controller.instance_variable_get("@was_tapped").should == true
  end
end

よく見たらRSpecぽいけど、実はちょっと違う。

というのも中身はBaconというミニマムRSpecクローンをforkしてObjective-C用に拡張してできたMacBaconというものが利用されています。 ミニマムなクローンなので、当然のごとく本家RSpecには無いものも多くあって、subjectもletもcontextすら無い、、とRSpecに普段慣れている人にとってはむずむずしそうな内容。まぁテストをRubyで書けるだけでもかなりいいのですが。

ひとまずcontextはdescribeにalias張るだけで簡単にできるので、MacBaconにcontextを利用できるようにするPull Requestを送ってみたところ、早速取り込んでもらえました。

ただ、この変更がRubyMotion本家にどうやって反映されるのかは正直よくわかっていないので、もうちょっと掘りたいところ。MacBaconはまだまだ手が出せそうな雰囲気があるので、ここはいろいろ貢献していきたいと思っています。

まとめ

ひとまず身近な開発環境でiOSアプリを作ることができるようになりました。

iOSのAPIは正直まだまだ全く理解できていないので、作りたいものを作れるレベルには全く達していないのですが、少しづつ手を動かして学びながら怪しいアプリを作っていこうと思います。

musical 1.0.0をリリースしました

  • 2013年9月 8日 19:22
  • ruby

musical

2年くらい前に書いたmusicalが、全く動かなくなっていたので、ゼロから書きなおしてv1.0.0としてリリースしました。 musicalの詳しい説明はこちらのエントリーをご覧ください。

背景

地味なツールだというのに、Pull Requestも幾つかいただいていて、僕のつくったgemの中だと実は一番DL数が多いmusicalですが、いかんせん

  • classの分け方が微妙で見通しが悪い
  • そもそもテストが無いから正しく動作してるのかどうか分からない
  • iTunes10になってから、まともに動作しなくなっていた

などの問題がありました。

何度も書きなおし

前からゼロから書きなおそうとして開発を進めていたのですが、iTunes連携部分をitunes-clientのgemに切り出したり、周辺の開発に伴いTravisCIやCoverallとの連携のコツが掴めてきたりして、その都度ゼロから書き直し続けていました。おそらく、今回リリースするまでに3回くらい書きなおしたはず。。

ただ、その甲斐あってか、だいぶ直感的なコードで記述することができるようになりました。もちろんSpec付きでCoverage100%キープしてます。 mainのコードもこんなかんじになって、割とスッキリできたんじゃないでしょうか。(一部抜粋)

DVD.load(dvd_options) do |dvd|
  chapters = dvd.rip

  chapters.each do |chapter|
    wav = chapter.to_wav
    track = Itunes::Player.add(wav.expand_path)

    converted_track = track.convert
    converted_track.update_attributes(
      name: chapter.name,
      album: dvd.title,
      artist: dvd.artist,
      track_count: chapters.size,
      track_number: chapter.chapter_number,
    )

    wav.delete!
    track.delete!
  end
end

補足

当然ながら、本gemは著作権違反の手助けをするためのものではありません。 自身の責任の元、著作権を保持しているDVDでご利用ください。

LeapMotionとRubyでiTunesを制御できるLeapTunesを作りました

  • 2013年8月11日 22:57
  • ruby

@k_katsumi経由で話題のLeap Motionを購入したので、ひとまずiTunesを操作できるものをRubyで作ってみました。

デモムービーはこんなかんじです。

  • 5本指を差し出すことでトラックの再生/一時停止
  • 2本指を差し出すことで次のトラックを再生

が可能です。

WebSocket API

Leap MotionのSDKを利用することで、指の動きの情報をAPI経由で取得できる。。はずなのですが、いくらやっても情報が取得できませんでした。おそらく設定を何かミスってるのでしょうが。。たとえばこれらのgemが動作しませんでした。

この問題はひとまず置いておくことにして、方針を変えてWebSocet APIで取ることにします。今回はこのgemを利用しました。

ネタバレすると、LeapTunesの元ネタも上記gemの中のサンプルを元にして再実装したものです。

以下、実装するにあたって気づいた点です。

自然な動作

Leap Motionは非常におもしろいガジェットなのですが、やはりデスクからある程度の高さで円をかいたりジェスチャを行うのは若干無理があります。 (単純なマウスの代用には絶対できない)

なので、キーボードの入力を自然に補助できるような形を考えると、実は

  • 指をN本差し出す
  • 片手/両手で差し出す

くらいが自然なんじゃないのかなぁと思いました。

あとは「手をひねる」くらいはアリな気はしますが、スワイプは普段使いにはちょっとしんどそうな印象です。

ジェスチャのキャンセル

APIは常時

  • 指の数
  • 手の数

が送信されるので、これらがゼロになったとき(= Leap Motionのセンサから何も検知されなくなったとき)にいろいろ状態をリセットするような処理を入れるのは定石になりそうですね。

言われたら当然だろうという感じですが、最初このリセット方法が思いつかずに、無限に再生と一時停止を繰り返し続けるループにはまってました。。

CPU使用率

僕が普段使ってるiMacは24-inch Mid 2007のめちゃめちゃ古いものなのですが、WebSocketでデータを受信しつづけてるときはさすがにCPU使用率は結構上がります。

普段の操作もデータの受信にも支障は特にありませんが、それでもleapd(というデーモン)が常時50%くらい奪ってる感じなので、やはり「軽くはない」くらいの印象はあります。

まとめ

まだまだ手探り感強いLeap Motionですが、超シンプルなジェスチャでiTunesの操作を行えるものを作ってみました。 引き続き可能性を探っていこうと思います。

tweetで家電を操作する

  • 2013年7月21日 23:01
  • ruby

で、Siriの入力を赤外線に変換して家電を操作することを試みましたが、小ネタとしてTweetを入力にすることも試してみました。「エアコンをつけて」「エアコンを消して」のTweetで僕の部屋のエアコンを操作できます。

TwitterのStreamAPIってなんだかんだで実際に使ったことが無かったのでその使い方の勉強も兼ねて。

TweetStreamはめちゃめちゃ便利ですが、Ruby2.0でうまく動かないIssueに気づくまでかなり時間を無駄にしました。。

コードはめちゃめちゃシンプルに書いたので、現状は僕がFollowしている人が誰でもウチのエアコンを操作できちゃうんですけど、まー面白いからヨシとしてます。Timelineに出てきた文字列に「エアコン」でマッチさせてiRemoconの信号を出してるだけですね。

と、いうわけで家電操作系は一通り遊んだ感あるので、またitunes-client関連の開発に戻りたいと思います。

taifu-0.1.0をリリースしました

  • 2013年7月 7日 00:37
  • ruby

地道に少しづつ改良し続けてるitunes-clientですが、一通り使いたいAPIが整ってきました。

そこでtaifuのバックエンドを、生のAppleScriptベースのコードからitunes-clientを利用したものに書き換えました。

自分で書いておきながら、動作確認できてるgemに処理を任せられるのはいいですね。。このおかげでコードもだいぶ削れることができました。

と、いうわけで次は似たような処理が入ってるmusicalを書き換えようと思います。(実際は書き換えを行っていたのを再開しようと思います) musicalは全くSpec書かれていなかった上に、いろいろバグが見つかっていたので、そのへんも合わせて整理しなおしですね。。

rubygemからインストールできるtomdocは古い

  • 2013年6月16日 00:04
  • ruby

itunes-clientのドキュメントを勉強がてらtomdocのスタイルで書こうとしてたら、全く動かない。パースに失敗してる感じ。

$ tomdoc lib/itunes/track.rb                                                                                                                         
--------------------------------------------------------------------------------
Itunes

coding: utf-8

コメントが構文エラー起こしてるのかなぁ、、と思ってたら同僚の中村氏がさらっと気になる発言をしてた。

まさか、、と思って最新版をcloneしてそっちの実行ファイル経由で実行してみたら確かに通りました。

$ git clone https://github.com/defunkt/tomdoc.git
$ cd itunes-client
$ ../tomdoc/bin/tomdoc lib/itunes/track.rb 

Itunes::Track.find_by(arg)

Public: Find some tracks by conditions.

Examples

  Track.find_by(name: 'Hello, Goodbye')
  # => #[<Itunes::Track:0x007fd02213fc78 @album="1">, <Itunes::Track:0x007fd02213fc79 @album="Anthology2">]

  Track.find_by(name: 'Hello, Goodbye', album: '1')
  # => #[<Itunes::Track:0x007fd02213fc78 @album="1">]

Returns an array including found tracks.
--------------------------------------------------------------------------------
...

なぜこの最新版がgem化されてないのかは謎ですが、tomdocの利用時には注意しておいたほうが良さそうです。

iTunesの操作をラップするgem、itunes-clientをリリースしました

  • 2013年6月 9日 23:03
  • ruby

itunes-client って何?

itunes-clientはローカルのiTunesの操作を簡単に扱う高レベルなAPIを提供するgemです。たとえばトラックの操作はこんな感じで行えます。

背景

似たようなことを実現するものとして、AppleEventをラップして高レベルのAPIを提供するrb-appscriptや、それを利用したiTunes専用のライブラリrb-itunes などがあります。ところが、iTunes10.6からSandboxが加わることで、これ系のライブラリは全部動作しなくなりました

で、対応方法としてAppleScriptを介すことで回避はできるのですが、taifumusicalを書き直してる中で、何度も同じようなコードを書き続けているので、分離して管理したほうが実装しやすいなと思い、今回分離してgem化することにしました。

現状

まだまだやりたいことは全然実装できてません。 TrackのfindもAR的なAPIは実装していますが、本来なら配列を返すべきものを1情報しか返していないし(この後v0.0.2で実装しました)、情報の更新もまだできません。プレイリストは何も実装できていません。

とはいえ、AppleScriptを介す限りは、OSXやiTunesの仕様に沿って安定して稼働するものを提供できそうですし、時間さえかければAppleScript用のAPIは全部実装できそうです。

というわけで

これを読んでる方のpull-request待ってます!

Siriでスクリーンセーバー操作を行うSiriProxy-Screen

  • 2013年4月17日 02:39
  • ruby

SiriProxy-Screenって何?

またまたSiriProxyのプラグインの形で、Siriでスクリーンを操作できるものを作りました。と、いってもスクリーンセーバーを起動できるだけです。

画面を消して

とSiriに言う事で、スクリーンセーバーを起動することができます。 これでマウスやキーボードが無いマシンでも安心してスクリーンセーバーを起動して離席することができますね。

作った動機

社内ブログで「スクリーンをすぐロックするTips」というエントリが上がり、なぜかこのネタに限って他のエンジニアがこぞって「俺ならこうする」みたいなネタを連投する流れに。 「スクリーンをすぐロックするTips(2)」「スクリーンをすぐロックするTips(3)」... のように、次々と新しい方法のエントリが上がり、謎の祭り状態に。

そうしている間に、スクリーンロック専用Macアプリを作る人が出てきたので、ついカっとして今回の実装にいたりました。

とはいえ

iRemoconのproxyのときにも書きましたが、今回も実装コードほとんど無く、ちょう簡単です。実質これだけ。

class SiriProxy::Plugin::Screen < SiriProxy::Plugin
  listen_for /画面を?(消して|けして)/ do
    say 'スクリーンセーバーを起動します'
    `/System/Library/Frameworks/ScreenSaver.framework/Resources/ScreenSaverEngine.app/Contents/MacOS/ScreenSaverEngine`
    request_completed
  end
end

こんな感じで簡単に祭りに参加できるSiriProxyは便利なわけですね〜。

taifu-0.0.2.gemをリリースしました

  • 2013年3月 3日 13:39
  • ruby

taifu v0.0.2をリリースしました。

変更点は次の通りです。

  • Ruby 2.0.0 サポート
  • rb-appscriptの依存を排除
  • specを追加し、Travis CIで運用

Travis CI

Travis CIは初めて触りましたが、簡単に導入できるので非常に良いですね。

  1. githubアカウントでTravis CIにログインし、AccountページからTokenを取得
  2. githubのプロジェクトのページのService HooksのTravisの設定を開く
  3. 1.で取得したTokenを設定

これだけでOKです。

以降は、githubにpushするたびにCIが走ります。 CIの結果は画像でも取得できるので、プロジェクトページのReadmeに掲載もできます。

あと、サポートするRubyのバージョンを2.0.0とあわせて1.9.3もサポートしたかったので、.travis.ymlを以下のように設定しておくと自動的に両バージョンでテストしてくれます。便利!

language: ruby
rvm:
  - 2.0.0
  - 1.9.3

使い方

過去ログになりますが、こちらを参考ください。

SiriProxyのプラグインとしてSiriで家電を操作するSiriProxy-iRemocon

  • 2013年1月 5日 04:11
  • ruby

SiriProxy-iRemoconって何?

SiriProxyのプラグインの形で、Siriで家電を操作できるものを作りました。今のところ電気のON/OFFだけですが、こんな感じで

  • ライトを付けて
  • ライトを消して

とSiriに言う事で、部屋の電気を操作できます。

(2013.01.14追記) エアコンのON/OFFの様子も追加しました。

部屋の電気の操作ってどうやってるの??

このブログでは定番になってきましたが、やはりiRemoconを利用しています。詳しくはこちらをどうぞ。

SiriProxyって何?

SiriProxyはその名のごとくSiriのProxyサーバで、プラグイン形式で質問文に対して独自の処理を追加することができるものです。Readmeがしっかり書かれているとは言え、日本語のまともな導入事例の記事が無い+公式のReadmeが手順が間違ってる箇所があったのですこし手まどりましが、こんな感じで導入できます。

Ruby1.9.3の導入

RVMでインストール可能です。bundlerもあわせて導入。

rvm install 1.9.3
gem install bundler

DNSの設定 / dnsmasq

iOSデバイスからSiriへの通信は全てguzzoni.apple.com宛に送信されるのですが、この通信を全てSiriProxyが動作するサーバへ転送させる必要があります。

そこで、SiriProxyを起動するマシンで内部DNSを立てて、iOSデバイスのWiFiネットワークのDNSをこの内部DNSを向けさせてあげることで、iOSデバイスのSiriの通信はすべてSiriProxyを向くことになります。 公式ではdnsmasqの導入で実現を行っています。全体を通してここが一番厄介ですが、Homebrewを利用することで次のように導入可能です。

brew install dnsmasq
# confファイルを雛形から作成
cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf

ここで、dnsmasq.confには次のようにaddress情報を追記します。

# Add domains which you want to force to an IP address here.
# The example below send any host in double-click.net to a local
# web-server.
#address=/double-click.net/127.0.0.1
# 192.168.0.6はSiriProxyが可動するマシンのアドレス
address=/guzzoni.apple.com/192.168.0.6

保存後、

sudo /usr/local/sbin/dnsmasq

で、稼働します。

稼働後、iOSのネットワークの設定で、DNSのアドレスを先ほどdnsmasqを設置したマシンのアドレスに上書きします。

DNS setting

元々のDNSはアドレスは192.168.0.1でしたが、192.168.0.6で更新しています。

SiriProxyのセットアップ

ここまでくると後はそんなに詰まることが無いと思います。必要なライブラリや証明書を作成します。

git clone https://github.com/plamoni/SiriProxy.git
cd SiriProxy
mkdir ~/.siriproxy
cp ./config.example.yml ~/.siriproxy/config.yml
rake install
siriproxy gencerts

最後のgemcertsで~/.siriproxy/ca.pemな証明書が作成されているので、この証明書をメールで添付してiOSデバイス宛に送信します。iOSのメールアプリでメールを開くと、証明書のインストールが可能になります。

siriproxy.gemspecの編集

2013.1.5時点のgemspecにはバグがあって、最新のCFPropertyListを問答無用で利用しようとしてiOS6によるSiriの音声パケットの解析でコケる問題を持っています。Issueで散々盛り上がりながら、まだコードにマージされていないようなので、とりあえず手元で

s.add_runtime_dependency "CFPropertyList"

と、あるのを

s.add_runtime_dependency "CFPropertyList", '2.1.2'

と、修正します。

config.ymlの編集

このままSiriProxyを起動してもサンプルのプラグインは稼働しますが、SiriProxy-iRemoconのようにプラグインを追加するとなると、config.ymlを書き換える必要があります。~/.siriproxy/config.ymlのpluginsを次のような内容を追記します。この内容はSiriProxy-iRemoconの中のconfig-info.ymlと同一のものです。

- name: 'Iremocon'
  git: 'git@github.com:katsuma/SiriProxy-iRemocon.git'
  host: '192.168.0.9'

hostはiRemoconに割り当てられているIPアドレスになります。(これ書いてる途中で電気のON/OFFの赤外線IDが自宅の内容固定のまま公開してることに気づいた。。あとで外部から設定できるように変更します。。→ 変更しました

SiriProxyの起動

ここまでくるとようやくビルド+起動ができる状態になって、

bundle install
siriproxy update .
rvmsudo siriproxy server

で、SiriProxyが起動します。あとは冒頭の動画のようにSiriの内容を解析して動作するはずです。

SiriProxyのプラグインってどうなの?

びっくりするくらい簡単に書けます。基本的には

  • 期待する文章を正規表現でマッチさせる
  • そのブロック内で行いたい処理を書く

だけ。今回のプラグインもこんな感じのコードになっています。(以下、抜粋)

class SiriProxy::Plugin::Iremocon < SiriProxy::Plugin
  listen_for /ライト?を?(つけて|付けて)/i do
    say 'ライトをつけます'
    signal_to_iremocon(1000)
    request_completed
  end
end

listen_forメソッドの引数の正規表現が期待する文章です。ちなみに「ライト?」となってるのは、僕の滑舌が悪くて何度やっても「ライ」にしか聞き取ってくれなかったのでその補正です。。ひー。

あとは別途定義したsignal_to_iremoconで家電操作を行う信号を発信させるだけですね。また、最後のrequest_completedの呼び出しは、SiriProxyの作法的なものらしく、かならずブロックの最後で呼び出す必要があるそうです。

まとめ

こんな簡単にSiriに追加処理が書けるなんてSiriProxyかなり革命的です。。前回のエントリでRails4で家電操作サーバ作ろうとしてましたが、Siriの操作の方が楽しそうなのでこっちにシフトしようと思いますよ。 しかし、これは21世紀の「開けゴマ!」状態ですね。。家電の操作にとどまらず、何でもSiriに仕事を任せることができそうで夢広がりまくりです。うお〜

Ruby2.0 + Rails4 なアプリを作成する

  • 2013年1月 1日 03:00
  • ruby

明けましておめでとうございます。 年も明けたことだし、意識高めにRuby2.0+Rails4な(スケルトンの)アプリを作成しました。

我が家はiRemoconを使って家電を管理しているのですが、テンプレートを変更するのがあまりに面倒なので全部Webベースにしちゃえばいいや、と思ってせっかくなので最新のエッジな環境で作ってみよう!とおもった次第です。実際、Sinatraで十分なんだけど。。

微妙にハマる個所もありましたが、rspecでテストが動くところまで確認できました。 以下、そこまでのメモです。

Ruby2.0

RVMでpreview2版がインストールできます。 まずは、ビルドツールを最新にしておきます。Homebrewを利用している場合はこんなかんじ。

brew update
brew tap homebrew/dupes
brew install autoconf automake apple-gcc42

その後、RVMの情報を最新にしてopensslをRVMのパッケージでインストールしておきます。 また、Ruby2.0のインストール時にはARCHFLAGSをセットしておかないとmakeで失敗しました(すごいハマった)

rvm get head
rvm reload
rvm pkg install openssl
env ARCHFLAGS="-arch x86_64" rvm install ruby-2.0.0-preview2 
rvm use ruby-2.0.0-preview2 --default

すると、

$ ruby -v
ruby 2.0.0dev (2012-12-01 trunk 38126) [x86_64-darwin11.4.2]

と、なってRuby2.0が利用できていることが確認できます。

また、すぐに必要になるのでbundlerだけでもインストールしておきます。

gem install bundler

Rails4

Rails4はgem化されていないので、ひとまずコード一式を手元に持ってきます。

git clone git@github.com:rails/rails.git

落としてきたコードを元にアプリを作成します。

rails/railties/bin/rails new roomkeepr --skip-index-html

Gemfileを書き換えてbundle installします。最低限こんなセットでいけるかと。

source 'https://rubygems.org'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.0.0.beta', github: 'rails/rails'

gem 'activeresource', git: 'git://github.com/rails/activeresource', require: 'active_resource'
gem 'activerecord-deprecated_finders', git: 'git://github.com/rails/activerecord-deprecated_finders.git'
gem 'journey',   :git => 'git://github.com/rails/journey.git'
gem 'arel',      :git => 'git://github.com/rails/arel.git'

# Gems used only for assets and not required
# in production environments by default.
group :assets do
  gem 'sprockets-rails', '~> 2.0.0.rc1', github: 'rails/sprockets-rails'
  gem 'sass-rails',   '~> 4.0.0.beta', github: 'rails/sass-rails'
  gem 'coffee-rails', '~> 4.0.0.beta', github: 'rails/coffee-rails'

  # See https://github.com/sstephenson/execjs#readme for more supported runtimes
  # gem 'therubyracer', platforms: :ruby

  gem 'uglifier', '>= 1.0.3'
end

gem 'sqlite3'
gem 'jquery-rails'
gem 'haml-rails'

group :development, :test do
  gem 'rspec-rails'
  gem 'response_code_matchers'
end

# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks'

もしかすると、ここで依存関係の問題でbundle installに失敗するかもしれません。そのときは次のものを事前にインストールしておくと良さそうです。(この辺り試行錯誤しててメモが怪しい。。)

gem install thread_safe
gem install i18n
gem install thor

うまくいかなかった点

rspecの導入と合わせて、GuardとSporkも導入しようとしたのですが動作しませんでした。 具体的にはrb-fseventがファイル保存時にエラーをはいてうまくいかず。このへんはRuby2.0固有の深そうな話に思えたので、途中で断念。このあたりは後で掘ってみようと思います。

雑感

実際はRuby2.0固有のコードも無いし、Rails4っぽいこともbefore_filterじゃなくてbefore_actionを利用したくらいではありますが、思ったよりサクサク軽量に動いてる感じもあり、個人の開発は(Guard動かない問題が解決してないけど)もうこのエッジな環境でもさほど問題は無さそうです。

少なくともRails4はgem化されていないので導入こそ若干面倒なものの面白機能も増えいてるので、エッジなRailsを触ってみたい人は2013年スタートダッシュをキめて使い始めてみるといいと思います。

ちなみにRails3からのアップグレードを含めたoverviewを勉強するにはUpgrading to Rails4のドキュメントが良かったです。$15の有料ではありますが、コンパクトにまとまってる、かつめちゃめちゃ実用的な内容なので、エッジ野郎にはオススメのドキュメントです。

YouTubeの音声をiTunesに転送するgem - taifu

  • 2012年9月17日 23:13
  • ruby

この続きです。 iTunesが10.6.3になってからrb-appscriptがまともに動かなくなって、AppleScriptを経由しないとiTunesを操作できなくなったものに対応 + gemにまとめたものです。

バックエンドでffmpegとyoutube-dlを利用しているので(いつの間にかVLCでYouTubeを再生できなくなってたのでその対応。。)

brew install ffmpeg
brew install youtube-dl

で、インストールしておいて

gem install taifu

で、インストール可能です。使い方は今までと同じ。

GXEB#05「じわじわくるエンジニアリング」にスピーカーで参加しました

  • 2012年7月 8日 00:42
  • ruby

同僚、兼GXEBオーガナイザーの高本さんに誘われて、GXEB#05にスピーカーとして参加してきました。

当日は、最近つくったpop-zapの発表を。あわせて、今回のGXEBにあわせていくつか改良をしたので、その内容をふまえたものにしました。改良した内容として

  • チャンネル変更の精度の向上
  • チャンネル変更時の音声ガイダンス

などがあります。発表資料はこんなかんじ。

GXEBは、参加したことなかったものの、会のヤバそうな温度感だけは理解していたので、どう臨むかかなり悩んだのですが、まーなんとか最低限の仕事は果たせたかな、、と思います。どの発表も面白かったですが、中でもTokyo Interactive Spam & Phishing Awardはかなり面白すぎました。アレはまじでもう一度全部見直したい。。!

と、いうわけで誘っていただいた高本さん、発表を聞いてくださった皆さん、どうもありがとうございました!また次回もぜひ参加したいですね。

キーボードからTVのチャンネルとボリュームを簡単に変更する

  • 2012年7月 2日 00:16
  • ruby

pop-zapにキーボードリモコンモードを追加して、簡単にチャンネルとボリュームを変更できるようにしました。

使い方

--remoconか-rのオプションでリモコンモードで起動します。

bin/pop-zap --remocon

[Pop-zap] Remocon mode:

     Number            Change your channel
     KeyUp/KeyDown     Change your volume
     Enter             Exit

1~8の数字キーはチャンネル番号、上下キーはボリューム、Enterキー(実際は数字と上下以外のキーすべて)はプログラムの終了、に対応しています。単純なコマンドのラッパにすぎないのですが、Macで作業中にTVのリモコンを探すのはやっぱり面倒くさいので即席で作りました。便利!

あと、本当はAlfredなんかのランチャから1キーで起動させたいのですが、パスの問題かgemのrequireでコケる問題でハマってます。。なんとかせねば。とりあえず今は、.zshenvに

alias v='cd ~/repos/pop-zap && bin/pop-zap --remocon'

としておいて、ターミナルから「v」で起動できるようにしてます。

あわせて、TVに変更を与えたときにディスプレイ上にもなんらのフィードバックを表示しないとよくわかんないので、いい感じのビジュアルのフィードバックを返してあげることをTODOに考えています。

iTunes10.6.3からrb-appscriptが利用できない

  • 2012年6月20日 02:29
  • ruby

最近更新されたiTunes10.6.3からMountain Lion対応としてsandboxのコードが入っている様子。結果、なんとなく動いていたrb-appscriptを利用してrubyからiTunesを動かすことができなくなっています。(参考:iTunes 10.6.3 changes AppleScript interface?

結果、musicaltaifuも動かなくなってすごい不便。。。そもそもrb-appscriptはもう開発を停止したようだし、似たことをやるなら素のAppleScriptを書くか、Objective-Cで書きなおすかをしないとダメっぽいです。うーむ、残念。。というかなんとかせねば。

ネット上で話題になっている番組に自動的にTVのチャンネルを変えるpop-zap

  • 2012年6月13日 02:57
  • ruby

pop-zapというライブラリを作りました。

これは何?

2ちゃんねるの実況板で話題になっているチャンネルに、TVのチャンネルを定期的に自動で切り替えるrubyスクリプトです。

勝手に2ちゃんねる勢いグラフ」さんのデータを参照させていただいて、その中から一番「勢い」がある(盛り上がっている)チャンネルを取得し、 該当するTVのチャンネルを変更する信号を発信させています。チャンネル変更時はgrowlで再生する番組名とチャンネル名が表示され、内容がすぐに分かるようになっています。

作った動機としては、iRemocon(後述)という面白ガジェットを手に入れたので、何かおもしろいものを作ってみたいなぁと思い、アレコレ遊んでいるうちに出来上がったものです。

使い方

こんなかんじで利用します。手元に落として、必要なgemをインストールして実行ですね。

git clone git://github.com/katsuma/pop-zap.git
cd pop-zap
bundle install
bin/pop-zap

あと、こまかな設定もあるのですが、それについては最後の方で述べます。

iRecomonって何?

さて、上でいきなりiRemoconと言っていたものは、いわゆる「学習リモコン」のIP対応版のガジェットです。公式サイトからの説明文を引用すると、

iRemoconはスマホから家のリモコン家電をコントロールできるようにするネットワーク接続型学習リモコン機器です。

  • リモコン画面をWebから完全カスタマイズ可能
  • 外出先から自宅の家電をコントロール
  • 家電自動操作
  • 超協力広角赤外線LED内蔵

なんかの機能があります。

さらにスマートフォン向けアプリがあるので、iPhoneやAndoridを学習リモコン化させることができ、 家電のリモコンを全部携帯1台で集約させることができます。さらにリモコンのデザインも(頑張れば)自由に変更することができ、 たとえば僕は電気、TV、エアコンを扱えるようにカスタマイズしています。
iRemocon

使い方としては、ルータと接続し、通常の学習リモコンと同じように学習させたいボタンを順に押すだけです。 構成を説明すると、最初にルータと直接有線LANで接続することで、DHCPサーバからIPアドレスが自動的に割り振られ、51013番ポートで信号を待ち受けます。 その後、上記のスマホアプリなどからTCPで送りつけられた命令を受け取り、命令に該当する赤外線を発し、家電をコントロールする、という流れになります。

さらに、このiRemoconの面白い点として、開発者向けのAPIが公開されている点です。 APIといっても簡単な仕様で、基本的には

*ic;{channel_id}\r\n   # リモコンを学習させる
*is;{channel_id}\r\n   # 赤外線を発光させる

だけ覚えておくと十分に遊べます。たとえば僕は、channel_id=1201をNHK総合、1202をNHK教育、1204を日本テレビ...のように、「1200 + 普通のテレビのチャンネル」を iRemoconのIDに学習させています。なので、たとえばTVを利用しながらターミナルから

telnet 192.168.0.9 51013
Connected to 192.168.0.9.
Escape character is '^]'. 

と、接続したあと、

*is;1204

と入力すると

is;ok

と、返答がかえってきて、チャンネルを変更することができます。

pop-zapも基本的にこのAPIを利用してチャンネルを変更しているので、confディレクトリ以下の設定ファイルをご自宅の環境にあわせて書き換えれば、すぐに利用することが可能です。

1つ注意することとして、iRemoconは同時に1つの接続しか受け付けられないので、1つのターミナルでセッションを張りっぱなしにしていると、別のセッションを張ることができません。なので、命令を送るたびに接続を張り直すか、接続を適切に使い回す必要があります。

まとめ

TCPで命令を送ることで自由に家電を操作できるiRemoconを利用して、TVをよりインターネットとの親和性を高め、より受動的なメディアにさせるpop-zapというライブラリを作りました。 自分の意思を無視して勝手にどんどんチャンネルが変わる世界はなかなか新鮮です。

また、iRemoconはここ最近見たガジェット中でも抜群に自由度が高くて面白いガジェットだと思います。 家電がゼロアクションで自発的に動作する世界はなかなか夢があっておもしろいですよね。 ほかの技術と組み合わせることでもっともっといろんなことが可能になると思うので、しばらく可能性を掘ってみたいと思います。

githubにPushしたらwebhooksとSinatraを利用してサイトを自動的に更新する

  • 2012年3月 5日 02:28
  • ruby

githubにはwebhooks機能があり、これを利用することで、git pushすると同時に様々な処理を実行することができます。たとえば、サイトをgithubで丸ごと管理している場合、pushと同時にサイトを更新することも可能です。

僕は趣味のとんかつサイトTON.KATSUma.tvを遊びで作っていますが、このサイトは今はgithubで管理して、手元でgit pushするとサイトが更新される仕組みにしています。id:viverさんが素晴らしい記事を書いて下さっていますが、今回はその復習的備忘録です。

Post-Receive URLs

webhooks機能を利用するためには、githubから送信されるHTTP POST命令の処理するWebサーバが必要になります。 僕は前回紹介したようなSinatraでwebhooksのPOST命令だけを受け付けるCGIを動かすhook.katsuma.tvの環境をさくらインターネット上に用意して、こいつで更新作業を行なっています。

セキュリティ無視して必要部分だけ抜粋すると、こんなかんじのスクリプトをSinatraで動かして更新しています。指定されたアプリケーションをgit pullしてrsyncさせてるだけ。もろもろ必要な情報は変数payloadにデータが載ってくるので、それを処理するようにしておきます。

#!/home/katsumatv/.rvm/rubies/ruby-1.9.3-p125/bin/ruby
ENV['GEM_HOME'] = '/home/katsumatv/.rvm/gems/ruby-1.9.3-p125'
ENV['PATH'] = "#{ENV['PATH']}:/home/katsumatv/.rvm/gems/ruby-1.9.3-p125/bin:/home/katsumatv/bin"

require 'rubygems'
require 'sinatra'
require 'json'

post '/deploy' do
  user = 'user_name'
  server = 'host_name'
  workspace = "/path/to/cache"
  target_dir = "/path/to/www/"

  payload = params[:payload] ? JSON.parse(params[:payload]) : nil
  status 403 && return if payload.nil?

  app = payload["repository"]["name"]

  `cd #{workspace}/#{app} && git pull origin master`
  `rsync -avz #{workspace}/#{app}/public/ #{user}@#{server}:#{target_dir}`
  "done"
end

set :run => false
Rack::Handler::CGI.run Sinatra::Application
      

これで、http://hook.katsuma.tv/deploy で、POST命令を受け付ける準備ができたので、githubのPost-Receive URLsの設定を行います。

「アプリケーションのレポジトリ」>「Admin」>「Service Hooks」>「Post-Receive URLs」を辿るとURL設定フォームが表示されるので、上記URLを指定しておきます。

ちなみに、ここで「Tesh Hook」ボタンを押すと、実際にgit pushしたときと同じ情報がPost-Receive URLに発行されるので、開発時はこのボタンを利用すれば便利です。(最初これに気づかずに何度も空Pushしまくってました...)

今回はrsyncするだけの簡単な更新処理でしたが、Capistranoを利用してもう少し細かなデプロイ作業をしたり、デプロイだけじゃなくてCIの実行、メール送信、tweet。。など、 Hookから遊べそうなことは多いので、これを機にいろいろ試してみてはいかがでしょうか。

参考

さくらインターネットでrvm+Ruby1.9.3環境下でSinatraをCGIで動かす

  • 2012年2月26日 02:36
  • ruby

このご時世、VPSでもクラウドでもなく、さくらインターネットのレンタルサーバでCGIを動かす必要があったので、その備忘録。KENT-WEBみたいなかんじのPerlでもよかったのですが、せっかくなのでモダンな環境を用意してSinatraで動かしてみました。

rvm+Ruby1.9.3のインストール

さくらでRubyをインストールするときは、googleで調べるかぎりソースからコンパイルして導入しているケースが多いのですが、面倒くさいだけなのでrvmを利用します。特に変わった設定は不要で、いつも通りの感じでインストールできます。

bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)

で、rvmのインストール完了。パスの設定なんかが書かれた$HOME/.profileが作成されているので、

source ~/.profile

しておきます。

また、rubyは1.9.3の最新版をインストールしました。

rvm install ruby-1.9.3
# default設定
rvm alias create default ruby-1.9.3

rvm listの結果がこんなかんじになっていたらインストール成功です。

$ rvm list

rvm rubies

=* ruby-1.9.3-p125 [ i386 ]

# => - current
# =* - current && default
#  * - default

sinatraの導入

特に何も気にせず最新版を導入します。

gem install sinatra 

ちなみにインストールしたgemのバージョンはこんな感じです。

$ gem list

*** LOCAL GEMS ***

bundler (1.0.22)
rack (1.4.1)
rack-protection (1.2.0)
rake (0.9.2)
sinatra (1.3.2)
tilt (1.3.3)

さて、さくらインターネットの設定で、CGIを動かすファイルの拡張子は.cgiである必要があるので、sinatraのスクリプトも*.cgiの名前で作成します。rubyのパスはrvmでインストールされたrubyのパスを設定する必要があるので、以下のような内容になります。

#!/home/katsumatv/.rvm/rubies/ruby-1.9.3-p125/bin/ruby
ENV['GEM_HOME'] = '/home/katsumatv/.rvm/gems/ruby-1.9.3-p125'
require 'rubygems'
require 'sinatra'

get '/' do
  'Hello, World!!'
end

get '/foo' do
  'foo!'
end

set :run => false
Rack::Handler::CGI.run Sinatra::Application

ポイントは2つ。1つめは、GEM_HOMEのパスを明示的に指定してあげることです。これ、外から指定する方法がよくわからなかった上に、指定しないと動作しなかったので、シンプルにできる方法わかる方は教えていただきたいです。2つ目のポイントは最後の2行で、CGIで動作させる場合の設定として必要なことです。このへんの情報、Sinatraのドキュメント見てもよくわからなかったので、結構ハマりました。。

.htaccessの設置

さて、この状態で、start.cgiのパーミッションを755にすると、http://foo.com/start.cgiにアクセスすると「Hello, World!!」が表示されます。一方で、/fooにアクセスするためには「/start.cgi/foo」なURLでアクセスする必要があります。これはちょっとダサいので、mod_rewriteでいじることにします。httpd.confは編集できないので、.htaccessで次のような設定を記述します。

DirectoryIndex start.cgi

RewriteEngine    on
RewriteBase      /
RewriteCond      %{REQUEST_FILENAME} !-d
RewriteCond      %{REQUEST_FILENAME} !-f
RewriteRule      ^(.*)$ start.cgi/$1 [QSA,L]

これで、/fooにアクセスすると「foo!」と表示され、いつものSinatraな動作が可能になります。

まとめ

rvmを使うことで最新のRuby+Sinatra環境でcgiを動作させることができました。次はこの環境で、もう一歩踏み込んだ遊びをしてみたいと思います。

YouTubeの音声をiTunesに転送する

  • 2011年12月29日 21:16
  • ruby

年の瀬になんかちゃっちゃと作りたかったので、単機能musicalみたいなtaifuというスクリプトを書きました。

これは何?

YouTubeでかっこういい動画を見つけたときに、iTunesで音だけでも聴きたい!な時は割とあるかと思いますが、それを実現するスクリプトです。実行権限を追加して

taifu http://www.youtube.com/watch?v=KPWfBfFFrwsx

で、wavデータを標準のエンコーダ設定でエンコードして何事もなかったようにiTunesに追加します。(要VLC.app)

タグ情報はどうなってるの?

「TAIFU_NAME」「TAIFU_ARTIST」「TAIFU_ALBUM」の名前でタグ付けされているので、iTunes上から「TAIFU」で検索したら追加された曲が見つかるはずです。あとは自分の好みのタグ情報に更新ください。オプションで渡すことも考えたけど、iTunes上で編集したほうが楽だったのでやめました。

「あーこれいつでも聴いていたいな〜」なものを見つけたとき、ご利用ください!

rb-appscriptを利用してwavデータを自動的にiTunesにタグ情報付きでエンコード、ライブラリに追加

  • 2011年11月27日 00:17
  • ruby

musical 0.0.2リリース

musicalの0.0.2をリリースしました。(インストールなんかの細かい話はこちら)

リッピングしたwavデータを自動的にiTunesのエンコード設定(mp3/AAC/Appleロスレス...)に従ってエンコードし、アーティスト名とDVDタイトル名を与えることでタグ情報付きでライブラリに追加します。たとえばDVDをドライブに入れた状態で、次のように実行します。

musical --title "TOUR あいのわ" --artist ハナレグミ

そうすると、

  1. チャプタごとにvobでリッピング
  2. wavに変換
  3. iTunesライブラリにwavを追加
  4. エンコード設定に従ってタグ付きでエンコード

の順に処理がすすみ、最終的にはiTunesのライブラリにこのように取り込まれます。

各チャプタのタイトルは今は自動にタグ付けする仕組みはないので、そこは自前で編集を。ここもうまく自動化したいのですが、DVD自身にはトラック情報を持っていないので、amazonなりから情報をうまく吸い出すなりいい方法を模索してます。

--title, --artistを与えない場合は、それぞれ「LIVE」「ARTIST」の名前でタグ付けされます。また、半角スペースを挟む情報の場合(上の例だと、「TOUR あいのわ」)は、「"」と「"」で囲って渡してあげます。

rubyからiTunesを操作

iTunesを操作する処理については、rb-appscriptを利用しています。rb-appscriptは、AppleEventをブリッジしてrubyから扱えるようにしたライブラリで、AppleScriptに対応したアプリケーションは、全て制御することができます。

一見、すごく便利で万能すぎるように思えるんですけど、ドキュメントが全くないのが辛いとこ。(単純にファイルをライブラリに追加するだけでも相当苦労しました。) 基本的に、method_missingで実装されているので、APIを把握してないと何も開発できません。。最初は、AppleScriptのAPIをdeveloper.apple.comからAPIを探しまくってたのですが、全然見つからなくて途方に暮れてたら意外にも手元にすでに存在してました。

「アプリケーション」「ユーティリティ」から「Appleスクリプトエディタ」を起動し、「ウィンドウ」>「ライブラリ」から「iTunes」を選択すると、AppleScript用のAPIが表示されます。(ちなみに裏技的に、Appleスクリプトエディタのアイコンに、iTunesのアイコンをD&Dしてもこのライブラリウィンドウは起動します。すごい使いづらいですけど。。)

これを元に、上で述べたファイルを追加+エンコードの基本的な操作をまとめるとこんなかんじになります。

見てわかる通り、割と直感的な操作が可能になっていると思います。基本的にはgetで情報を取得、setで更新し、このときにAppleEventが発行されています。(なので、できるだけget/setの数は減らすほうがイベント発行の節約になって、パフォーマンスが上がる)

また、普段irbやpryなんかで簡単な操作をしているときに、APIを確認するまでもなく、ざっとプロパティを確認することくらいはもうちょっと手軽な方法で確認できます。ASDictionaryをインストールしておくと、各オブジェクトに対してhelpメソッドを利用できます。たとえば、現在iTunes上で選択中のトラック、current_trackには次のようなプロパティが存在することを確認できます。

$ pry
[1] pry(main)> require 'appscript'
=> true
[2] pry(main)> include Appscript
=> Object
[3] pry(main)> its = app("iTunes.app")
=> app("/Applications/iTunes.app")
[4] pry(main)> its.current_track
=> app("/Applications/iTunes.app").current_track
[5] pry(main)> its.current_track.help
==============================================================================
Help (-t)

Reference: app("/Applications/iTunes.app").current_track

------------------------------------------------------------------------------
Description of reference

Property: current_track (r/o) : track -- the current targeted track


Terminology for track class

Class: track -- playable audio source
  Plural:
    tracks
  Inherits from:
    item (in iTunes Suite)
  Properties:
    container (r/o) : reference -- the container of the item
    id_ (r/o) : integer -- the id of the item
    index (r/o) : integer -- The index of the item in internal application order.
    name : unicode_text -- the name of the item
    persistent_ID (r/o) : string -- the id of the item as a hexidecimal string. This id does not change over time.
    album : unicode_text -- the album name of the track
    album_artist : unicode_text -- the album artist of the track
    album_rating : integer -- the rating of the album for this track (0 to 100)
    album_rating_kind (r/o) : :user / :computed -- the rating kind of the album rating for this track
    artist : unicode_text -- the artist/source of the track
    bit_rate (r/o) : integer -- the bit rate of the track (in kbps)
    bookmark : short_float -- the bookmark time of the track in seconds
    bookmarkable : boolean -- is the playback position for this track remembered?
    bpm : integer -- the tempo of this track in beats per minute
    category : unicode_text -- the category of the track
    comment : unicode_text -- freeform notes about the track
    compilation : boolean -- is this track from a compilation album?
    composer : unicode_text -- the composer of the track
    database_ID (r/o) : integer -- the common, unique ID for this track. If two tracks in different playlists have the same database ID, they are sharing the same data.
    date_added (r/o) : date -- the date the track was added to the playlist
    description : unicode_text -- the description of the track
    disc_count : integer -- the total number of discs in the source album
    disc_number : integer -- the index of the disc containing this track on the source album
    duration (r/o) : short_float -- the length of the track in seconds
    enabled : boolean -- is this track checked for playback?
    episode_ID : unicode_text -- the episode ID of the track
    episode_number : integer -- the episode number of the track
    EQ : unicode_text -- the name of the EQ preset of the track
    finish : short_float -- the stop time of the track in seconds
    gapless : boolean -- is this track from a gapless album?
    genre : unicode_text -- the music/audio genre (category) of the track
    grouping : unicode_text -- the grouping (piece) of the track. Generally used to denote movements within a classical work.
    kind (r/o) : unicode_text -- a text description of the track
    long_description : unicode_text
    lyrics : unicode_text -- the lyrics of the track
    modification_date (r/o) : date -- the modification date of the content of this track
    played_count : integer -- number of times this track has been played
    played_date : date -- the date and time this track was last played
    podcast (r/o) : boolean -- is this track a podcast episode?
    rating : integer -- the rating of this track (0 to 100)
    rating_kind (r/o) : :user / :computed -- the rating kind of this track
    release_date (r/o) : date -- the release date of this track
    sample_rate (r/o) : integer -- the sample rate of the track (in Hz)
    season_number : integer -- the season number of the track
    shufflable : boolean -- is this track included when shuffling?
    skipped_count : integer -- number of times this track has been skipped
    skipped_date : date -- the date and time this track was last skipped
    show : unicode_text -- the show name of the track
    sort_album : unicode_text -- override string to use for the track when sorting by album
    sort_artist : unicode_text -- override string to use for the track when sorting by artist
    sort_album_artist : unicode_text -- override string to use for the track when sorting by album artist
    sort_name : unicode_text -- override string to use for the track when sorting by name
    sort_composer : unicode_text -- override string to use for the track when sorting by composer
    sort_show : unicode_text -- override string to use for the track when sorting by show name
    size (r/o) : integer -- the size of the track (in bytes)
    start : short_float -- the start time of the track in seconds
    time (r/o) : unicode_text -- the length of the track in MM:SS format
    track_count : integer -- the total number of tracks on the source album
    track_number : integer -- the index of the track on the source album
    unplayed : boolean -- is this track unplayed?
    video_kind : :none / :movie / :music_video / :TV_show -- kind of video track
    volume_adjustment : integer -- relative volume adjustment of the track (-100% to 100%)
    year : integer -- the year the track was recorded/released
  Elements:
    artworks -- by index



==============================================================================
=> app("/Applications/iTunes.app").current_track

実際は、さきほどのAppleスクリプトエディタのヘルプ情報から引っ張ってきて表示しているだけですが、都度手元で確認できるのは便利です。ざっとAPI全体を眺めて把握した上で、手元で動かしながら動作を確認するのが開発を進める方法として良さそうです。

今後の予定

当面、次の内容くらいはどうにかしたいです。

  • iTunesのライブラリに追加したとき、変換前のwavファイルをライブラリ上から削除
  • 副音声の扱いを制御
  • テストを追加
  • トラック名を自動追加

あとrb-appscriptめちゃめちゃ可能性を感じるので、こいつでもうちょっと遊んでみたいですね。API見ているだけでムフムフします。。!

DVDデータをチャプタ毎にリッピング/wav変換するLionに対応したgem 'musical'

  • 2011年11月 6日 23:57
  • ruby

musicalというgemを作りました。

これは何?

Mac OSXでライブDVDをmp3ファイルに変換」にも書いたのですが、僕はアーティストのライブDVDを買って思う存分鑑賞した後は、mp3/AACに変換してiTunes/iPhoneで聴くという楽しみ方をよくしています。 ところが、この変換の際に肝である0SExというソフトがMac OSX 10.7(Lion)になってから動かなくなりました。理由は明確で、0SExはPPC用にビルドされたバイナリなのでRosetta上では動作していたのですが、LionになってRosettaが取り除かれてしまったことで動作しなくなりました。Rosettaの代用品も存在せず、途方に暮れていたのですが、既存のソフトウェアの組み合わせでなんとかできることがわかったので、自分が使いやすい形にmusicalというgemの形でまとめてみました。

musicalができることが単純で、

  1. チャプタごとのリッピング
  2. チャプタごとのwavファイルの変換
  3. (オプションとして)タイトル/チャプタ情報の出力

だけです。個人的には2.のwavの変換を行ったあとに、iTunesにD&DでAACに変換を行っているので、ここまでの処理を行おうか迷ったのですが、利用する音声フォーマットは個々人に任せたほうがいいと思ったので、wav変換までで止めています。

(2011.11.27 追記) 0.0.2でiTunesの設定に従ってエンコード、ライブラリ追加まで行えるようにしました

インストール

必要ソフトウェアのインストール

musicalはバックエンドでdvdbackup, ffmpegを利用しているので、これらをインストールしておきます。homebrewでインストールできます(後述しますが、ちょっと罠があります)。

brew install dvdbackup
brew install ffmpeg

gemのインストール

毎度おなじみの

gem install musical

で、OKです。

利用方法

一番シンプルなのは、DVDドライブにDVDを入れた状態で

musical

だけで、カレントディレクトリにチャプタ毎にwavファイルができあがります。

wavはいらない!リッピングだけでいいんだけど

musical --ignore-convert-sound

で、リッピングだけ行い、wavへの変換は行いません。

保存する場所を変更したいんだけど

musical --output=save/to/path --title=DVD_title

--output, --titleオプションを利用できます。オプションについては--helpでご確認ください。

musical --infoが何も表示されないんだけど

DVDにプロテクトがかかっているので、Fairmountなどを使って、ディスクイメージとしてマウントすれば大丈夫です。

ライブラリのインストール時の注意

dvdbackup

brew installで簡単にいく。。と思いきや、依存パッケージのlibdvdreadのインストールでコケました。これは単純にURLが変更になっていたので、

brew edit libdvdread

して、

@@ -3,7 +3,7 @@ require 'formula'
 class Libdvdread < Formula
   homepage 'http://www.dtek.chalmers.se/groups/dvd/'
   # Official site is down; use a mirror.
-  url 'http://www.mplayerhq.hu/MPlayer/releases/dvdnav/libdvdread-4.1.3.tar.bz2'
+  url 'http://www.mplayerhq.hu/MPlayer/releases/dvdnav-old/libdvdread-4.1.3.tar.bz2'
   md5 '6dc068d442c85a3cdd5ad3da75f6c6e8'
   depends_on 'libdvdcss' => :optional

こんなかんじで編集した後、brew install libdvdreadしなおしたら大丈夫です。

ちなみにこの内容は(人生初の)pull requestを送ってみたので、もしかしたら今後は取り込まれるかもしれませんね。

(2011.11.11追記) pull requestは取り込まれたので、brew updateしたらformulaを編集しなくてもインストールできると思います。

ffmpeg

これもbrew installで簡単にいく。。と思いきや、最新のXcodeでgccが無くなった + pod2manの実行権限がなぜか無くなっていたことでかなりハマりました。あらかじめgccをインストールしなおした上で、こんなかんじでインストール可能です。

# 関連するものを含めて、一度アンインストール
brew uninstall --force `brew deps ffmpeg`

# なぜか実行権限無くなっていたので再設定
sudo chmod +x /usr/bin/pod2man

# gccの利用を指定
brew install --use-gcc ffmpeg

基本的にこれでいけるはずなんですけど、依存関係上一緒にインストールされるlibxvid, libmp3lameあたりでコケた場合(実際、別のLionのマシンで試したらコケた)は、最悪これらのインストールをスキップしても大丈夫です。brew edit ffmpegで

depends_on 'yasm' => :build
...
#depends_on 'lame' => :optional
...
#depends_on 'xvid' => :optional

...
#args << "--enable-libmp3lame" if Formula.factory('lame').installed?
...
#args << "--enable-libxvid" if Formula.factory('xvid').installed?

こんなかんじでコメントアウトたらビルドも通り、動作が確認できました。

最後に

当然ながら、本gemは著作権違反の手助けをするためのものではありません。自身の責任の元、ご利用ください。

MacRubyとLLVMを導入してRubyでネイティブGUIアプリを作る

  • 2011年1月23日 17:16
  • ruby

(2011/1/23 23:00追記) macrubycはLLVMから入れなくてもmacrubyをインストールするだけで一緒にインストールされます。下記内容は誤りを含んでいますのでご注意ください。ご指摘いただいたwatson1978さん、ありがとうございました。

最近Macアプリケーションが気になっていて、Cocoa周りの話を調べています。その一環でRubyでMacアプリを作る方法についての話です。

MacRuby

Mac上でRubyでアプリケーションを作る場合、最初からインストールされてあるRubyCocoaと、最近盛り上がっているMacRubyの2通りの手段があります。 どちらもCocoaを含むいろんなフレームワークをRubyから直接叩けるのですが、RubyCocoaはプロキシオブジェクトを介してCocoaフレームワークを叩くのに対して、MacRubyはプロキシを必要とせずに直接Objective-Cのメソッドにアクセスできるのが大きな特徴です。 実装方法として、MacRubyはRubyランタイムからObjective-Cのランタイム関数を直接呼び出すことで実現しているようです。 感覚的に考えてもRubyCocoaと比べて、MacRubyの方がパフォーマンスが大きく改善されていることが期待できるので、MacRubyで遊んでみたいと思います。

rvmの利用

MacRubyの本家サイトにはインストーラのpackageファイルがあるのですが、もっと手軽に試してみるにはrvmの利用がおすすめです。rvmは複数のRubyを共存させるツールで、いろんなバージョンのRubyを切り替えて使いたいときは必須ツールです。 同僚のmirakuiさんが詳しい記事を書いているので、rvm自身のインストールなど詳細な情報はそちらを参照ください。

ではMacRubyをrvmでインストールしてみます。MacRubyの最新バージョンは2011年1月22日現在で0.8なのですが、rvmのバージョンを最新にしないとバージョン指定でインストールできないようです。

$ rvm update --head
$ rvm reload
$ rvm install macruby-0.8
$ rvm use macruby-0.8

バージョンを確認してみて、次のような出力が得られるとインストール成功です。

$ ruby -v
MacRuby 0.8 (ruby 1.9.2) [universal-darwin10.0, x86_64]

あとGUI系のライブラリを利用するためにHotCocoaのgemもインストールしておきます。

$ gem install hotcocoa

Hello World!

まずは簡単なHello Worldアプリをつくってみます。「ruby hello_world.rb で」ウィンドウ上にボタンを表示し、ボタンクリックでHello Worldをputsさせます。

1st MacRuby App

MacRubyはKernelモジュールにframeworkメソッドを追加しているので、このメソッドでCocoa機能を呼び出します。 あとのコードも大体読めばわかる程度のレベルだとおもいます。Objective-Cだと抵抗あるRubyエンジニアもこれだとMacアプリも怖くないですね!

MacRubyコンパイラを利用してネイティブアプリ化する

このままだとただのRubyスクリプトなので、これをネイティブアプリ化します。 (2011/1/23 23:00追記、macrubycはmacrubyに梱包されているので、LLVMからインストールする必要はありません。一応、備忘録のために導入方法だけ残しておきます。) ネイティブアプリ化を行うためには、LLVMが提供するツールのmacrubycを利用します。macrubycはMacRubyをインストールするだけだと使えないので、LLVMのビルド、インストールが必要です。

LLVMはバージョンに依存するようで、僕の場合はrvmのサイトの内容を参考にして、次の手順で導入できました。

$ svn co -r 106781 https://llvm.org/svn/llvm-project/llvm/trunk llvm-trunk
$ cd llvm-trunk
$ env UNIVERSAL=1 UNIVERSAL_ARCH="i386 x86_64" CC=/usr/bin/gcc CXX=/usr/bin/g++ ./configure --enable-bindings=none --enable-optimized --with-llvmgccdir=/tmp
$ env UNIVERSAL=1 UNIVERSAL_ARCH="i386 x86_64" CC=/usr/bin/gcc CXX=/usr/bin/g++ make -j2
$ sudo env UNIVERSAL=1 UNIVERSAL_ARCH="i386 x86_64" CC=/usr/bin/gcc CXX=/usr/bin/g++ make install

makeのときに追加しているj2オプションの「2」の値はビルドを行うCPUのコア数に合わせて最適化を行ってみてください。なお、このビルドは1時間近くかかるので、時間があるときに試すのをおすすめします。

$ which macrubyc
/usr/local/bin/macrubyc

のようにエラーがなく結果が返ってくればインストール成功です。

では、早速先ほどつくったhello_world.rbをコンパイルしてみましょう。コンパイルは簡単で次のコマンドでコンパイルできます。

$ macrubyc hello_world.rb -o hello_world

これでhello_worldという名前の実行可能ファイルができるので、

$ ./hello_world

で、実行できます。無事、ウィンドウが表示されましたね!

まとめ

rvmでMacRubyは簡単に導入することができるので、Objective-Cを諦めていて人もRubyで手軽にMacアプリの作成を試すことが確認できました。 また、LLVMを導入することでネイティブアプリも作成することができるので、RubyでのMacアプリケーション開発の可能性の大きさも伺えますね!

redisでユーザをfollowしたときにTimeLineをsortして再構築

  • 2010年3月27日 06:10
  • kvs | ruby

前回のつづき。

課題の1つに挙げてた中で、「Followした瞬間に、そのユーザの過去のTweetを自分のTLに追加できていない」というのがありましたが、こんなかんじでいいのかな。自分のTLに他人のTLを混ぜて、sortしてstoreし直し。(redis-rbが0.2.0じゃないと動かないかも)

  def merge_timeline(user_id)
    my_id = @login_user[:id]
    return if @redis.type?("uid:#{user_id}:posts") == "none"

    user_statuses = @redis.lrange("uid:#{user_id}:posts", 0, 100)
    user_statuses.each do |status|
      @redis.lpush("uid:#{my_id}:home", status)
    end
    @redis.sort("uid:#{my_id}:home", :order => "desc alpha", :store => "uid:#{my_id}:home")
  end

でも、そうしたあとにremoveしたときにまた再構築し直さないと駄目なことに気づいた。。と、いうかremove面倒ですねぇ、どうするんだろう。泥臭く全statusを走査し直しなのか、それとも順序付Setとか使えば楽にremoveできるのかな。また考え直しです。

あと、このupdateについて、github上のRedTweetのコードもpushし直してますので、興味ある方はご確認ください。

redisとRailsでTwitterクローン「RedTweet」を作りました

  • 2010年3月25日 02:04
  • kvs | ruby

前回「Mac OSXにredisをインストール」で、redisを動かす環境まではできたので、せっかくなんでテスト的に何かサービスを作ってみよう、ということでTwitterクローンのRedTweetを作ってみました。

redisを使ったTwitterクローンは、PHP版のRetwisと、それをSinatraで書き直したRetwis-RBがあるのですが、サンプルコードはいくらっても世の中に少しは役立つだろうと思ってRails版で実装してみました。オンラインで動作できる環境はないので、git cloneしてscript/serverで手元の起動で確認ください、、と投げやり気味ですみません。とりあえず次の項目は一通り実装しています。

  1. ユーザID発行
  2. Login / Logout
  3. Follow / Remove
  4. 自分のTimeline, Public Timeline, 各ユーザのTimelineの閲覧

ちなみにRedTweetって名前はRedisとTweetを混ぜて直感でつけた名前で、git pushしたあとで同じ名前のサイトがあることが発覚したくらい直感でつけた名前です。

目的

さて、今回はまじめにTwitterクローンを作ることが目的ではなくて、実際は、次の項目を目的として実装してみました。

  1. Retwisのデザインを読んで、それに従って一通り実装してみる
  2. redisのAPIの仕様を学ぶ
  3. RDBを一切使わない、NoSQLでWebサービスを作るためのノウハウを身につける

結局全て似通った話になるのですが、上記のデザイン仕様書はTwitter的なサービスを作り上げることで、KVSをどのように利用すればいいのか、がかなり分かりやすく説明されてあるので、いい勉強になりました。また、ユーザ情報はString, 各TLはList, Following/FollowerをSetで管理することで、redisの主要なAPIを網羅できたことも、redisの学習に役立ちました。

と、同時に課題もすでに見えていて、

  1. スケーリングがどこまでできるかはまだ手元で理解できていない
  2. やっぱりActiveRecord的にラップしたライブラリは必須
  3. Followした瞬間に、そのユーザの過去のTweetを自分のTLに追加できていない

なんかが今の段階で挙げられます。1.はテストデータを作ることで解決するはず。2.はやっぱりmustだなぁ、と思えるところまでははっきりした理解で、ユーザ名をIDから引くときなど毎回決まったprefixがついたkeyから探索するのは冗長すぎてやってられません。OhmというHashとObjectのマッピングライブラリもあるので、このあたりも1度使ってみたほうが良さそうだな、というところ。3.はFollowした瞬間に一定数TweetをListにLPUSHして、そのlistの中でSORTすればいいのかな??正直、SORTまだよくわかってません。

まとめ

というわけで、課題は多いものの、redisとRailsで最低限の動作をするものは実装できました。NoSQLでもサービスを作り上げることは理解できたので、Ohmなど、他のライブラリも使ったりすることで、 redisの利点をもっと伸ばして理解を深めたいな、というところですね。

Mac OSXにredisをインストール

  • 2010年3月22日 02:43
  • kvs | ruby

Tokyo Cabinet / Tokyo Tyrantに代表されるKey-Value Storage,いわゆるKVSは多くのプロダクトが乱立してなかなか違いを理解するのも難しい状況になりつつあるのですが、ここ数日はredisに注目をしています。redisとは何ぞやというと、公式サイトには次のような特徴が挙げられています。

  • memcachedのようないわゆるKVS
  • valueにはStringだけではなく、List, Setも利用できる
  • データに対するpush/pop, add/removeのような操作はすべてatomicな操作
  • 通常はメモリ上で動作するが、定期的にデータをディスクに書き出す(常に書き出すような設定も可能)
  • Master-Slaveのレプしケーションをサポート
  • Ruby, Python, Perl, Java...などの各言語のバインディングを用意

と、かなり豪華なスペックで、なんでもかかってこい!なプロダクトです。国内の利用例はまだ聴いた事無いですが、海外ではgithubやCraigslistが採用したことでここ最近有名になってきました。

さて、そんなredisの開発環境を整えてみたいと思います。環境はMac OSX 10.5。

redisのインストール

ソースコードから入れてもいいのですが、Macの場合は、MacPortsでインストール可能です。

$sudo port -d sync

で、インストールリストを最新の状態にしておいて

$sudo port install redis

で、インストールされる、、はずなのですが、僕の環境だと途中で止まりました。

$ sudo port install redis
--->  Fetching redis
--->  Attempting to fetch redis-1.2.2.tar.gz from http://redis.googlecode.com/files/
--->  Verifying checksum(s) for redis
--->  Extracting redis
--->  Configuring redis
--->  Building redis
--->  Staging redis into destroot
--->  Creating launchd control script
###########################################################
# A startup item has been generated that will aid in
# starting redis with launchd. It is disabled
# by default. Execute the following command to start it,
# and to cause it to launch at startup:
#
# sudo launchctl load -w /Library/LaunchDaemons/org.macports.redis.plist
###########################################################
--->  Installing redis @1.2.2_0
--->  Activating redis @1.2.2_0
Error: Target org.macports.activate returned: couldn't open "/opt/local/var/log/redis.log": no such file or directory
Error: Status 1 encountered during processing.

なんかlogディレクトリがないみたいなので、ディレクトリ作成してから一度アンインストールしてやりなおしてみます。

$ sudo mkdir /opt/local/var/log
$ sudo port uninstall redis
$ sudo port install redis
==================================================================
* To start up a redis server instance use this command:
* 
*   redis-server /opt/local/etc/redis.conf
* 
==================================================================

--->  Cleaning redis

これで、インストールできました。サービスに登録したい場合は、途中のログに書いてあるとおり、

sudo launchctl load -w /Library/LaunchDaemons/org.macports.redis.plist

で、OKです。

Rubyのバインディングのインストール

僕は普段Rubyを使うので、Rubyのバインディングもあわせてインストールしてみることにします。gemでインストール可能です。

sudo gem install redis

redisの起動

redisを起動するときは、redis-serverを実行します。MacPortsでインストールした場合、/opt/local/bin にバイナリがインストールされてあるので

sudo /opt/local/bin/redis-server /opt/local/etc/redis.conf

で、起動します。confの中には、起動するときのポート番号や、ディスクに書き出すタイミングや条件などの設定項目があります。(ちなみに、起動時に設定ファイルのパスを与えていますが、これはビルド時?に設定しておけば、実行時に与える必要は無いようです。ただ、MacPorts利用時もそれが可能なのかどうか?は不明です)

では、実際に動作を確認してみたいとおもいます。確認するときは、redis-serverと同じ場所にあるredis-cli, またはtelnetで6379に対して接続してコマンドを入力します。(今回はtelnetで接続して生のコマンドを叩いて確認してみます)ValueをStringとして扱うときは、 set \r\nでデータをセット, get でデータを確認できます。

set foo 3
bar
+OK

get foo
$3
bar

listとして扱うときは、lpush \r\n, lrange などのコマンドを利用します。下記の例のlrangeの0は開始インデックス、-1は末尾の意味になります。

lpush artists 8
fishmans
+OK

lpush artists 9
clammbon
+OK

lpush artists 7
polaris
+OK

lrange artists 0 -1
*3
$7
polaris
$8
clammbon
$8
fishmans

Listの要素追加もStringの要素追加と同じでO(1)で実現できるのがredisのポイントですね。実際の細かなコマンドの仕様については、リファレンスを参照ください。

ここまでざっと環境構築まで確認できたので、次回はもうすこしredisを使い倒してその結果をまとめたいと思います。

CakePHPとRuby on Railsの違い

  • 2009年5月10日 03:48
  • php | ruby

最近、仕事でRailsを使い始めたので、今までよく使っていたCakePHPとどこが一緒でどこが違うのかをざっくりまとめてみました。まだRailsは勉強中なので、理解が不十分だったり間違っている箇所もあるかと思いますが、それらの点についてはコメントなどでご教授いただければ幸いです。

Controller

CakePHPの場合、任意のアクションにおいて、/users/show/katsuma のように、URLで「/」で区切られているものは、アクション以降の文字列も勝手に引数に分けてくれます。なので、アクション側の定義で

function show($id, $name)

のように引数を分けて定義しておいてあげれば、勝手に値が割り当てられることになります。

Railsの場合はconfig/routes.rbで振り分け方法を定義しておいてあげる必要があります。上の例だと

	map.connect 'users/show/:id/:name', :controller => 'users', :action => 'show'	

こんな感じになるでしょうか。

一方で、Railsの場合、routes.rbはものすごい強力で、map.connetのかわりに好きな名前のメソッドを指定するだけで「(名前)_url」で指定したcontroller, actionなどを含むURLを作成することなんかできたりします。たとえば

ActionController::Routing::Routes.draw do |map|
 map.berryz '', :controller => "berryz", :action=> "show"
end

こんな感じにroutes.rbで定義しておいた上で、

berryz_url(:id => 'miyabi')

で呼び出すと、

http://localhost:3000/berryz/show/miyabi

を意味することになります。Railsすごい。。。!

また、GETで引数指定で渡ってきたも、routes.rbで指定された引数も、すべて param[:hoge]のシンボル指定で取得できるのも特徴でしょうか。

ApplicationController

CakePHPの場合、任意のControllerクラスの継承元であるApplicationControllerは "app/" ディレクトリ直下に "application_controller.php" の名前で設置されます。

Railsの場合は、"app/controllers/" の中に "application.rb"の名前で設置され、その場所と名前が微妙に異なります。

この名前については、これ規約からも外れてるよなぁ。。と思ってたら、どうやらRails 2.3.0からは"application_controller.rb"に名前が変わるようですね。詳しい話はこちらに書いてました。(参照元:そういえば ApplicationController ってファイル名の規約を守ってなかったんだな

View(レイアウト)

CakePHPの場合、大枠のレイアウトはviews/layouts/default.ctp に、そのレイアウトのHTMLを記述します。

Railsの場合は、views/layouts/application.rhtmlに記述することになり、その名前は異なります。統一性の観点から言うと、Railsのこの名前の方が個人的には好きです。

部分テンプレート

CakePHPの場合、部分テンプレート(element)は、views/elements/ 以下に header.ctp の名前で保存しておきます。elementの呼び出す場合は、View側で

$this->element('header') 

の、ように呼び出します。

Railsの場合は、特定のcontroller内での共通テンプレート、全controllerでの共通テンプレートとそれぞれ別に分けることができます。 前者の場合は、 views/user/_header.rhtml のように、"views/controller名/_{部分テンプレート名}.rhtml"の形式になります。 逆に後者の場合、views/shared/_header.rhtmlのように、"views/shared/_{部分テンプレート名}.rhtml" の形式になります。

部分テンプレートの呼び出し方は、前者の場合は、<%=render :partial=>'header'/> のように、後者の場合は :partialで指定する値が"shared/header"のようになります。

この点については、Railsはここまで細かい指定がなくてもいいのにな、、と思います。Cakeの方が直感的な規約だし、呼び出し方も簡単かな、と。

Filter

Cake,Railsともにcontrollerにおいて、その前後にフィルタをかけることが可能です。いわゆるbeforeFilter, afterFilterですね。 Cakeでは特定のController、またはApplicationControllerにおいてbeforeFilter/afterFilterアクションを定義しておくことで、そのフィルタを通すことが可能です。

Railsの場合、フィルタにはメソッドはもちろんですが、クラス、ブロックの3つのレベルで指定可能です。また、複数のフィルタが定義されている場合は、その定義された順番にフィルタが適用されます。

このRailsの細かな指定は凄い、としか言いようがないかんじ。特にブロックで渡すことができる柔軟性は使いこなせばすごく便利そうな印象です。(まだ自分はそこまで使いこなせてません)

静的ファイル

CakePHPの場合、画像やCSSファイルなど、静的ファイル(や、routes.phpのルーティングに外れるもの)は、"webroot/" 以下に設置しておけばOKです。

逆に、Railsの場合は、"public" ディレクトリに設置することになり、そのディレクトリ名が異なっています。

これについては、Railsを意識しまくったCakeとしては、どうしてここの名前だけ変えたのかはよく分かりません。。。

まとめ

ざっと目につきやすい相違点をまとめてみました。Railsについてはまだまだ触り始めたばかりなので、相違点はまだまだあるでしょうし、注意して理解を深めて行きたいと思います。 また、今回こうやって相違点を考えていくことで、むしろお互いの理解が深まるんじゃないかな、とも思っています。

ちなみに、チュートリアルについて、book.cakephp.orgは日本語化されてるわけですが、guides.rubyonrails.orgは日本語化されてないんでしょうか?? すごくよくまとまってそうなので、できれば日本語で読んでみたいのですが。。

Rubyでブックマークカウンタの修正スクリプト書きました

  • 2008年12月11日 01:29
  • ruby

(追記 : 2009/02/12) ソースはgithubでホスティングすることにしました。

ここ最近、このBlogで使ってるソーシャルブックマークカウンタで、deliciousの数がぜんぜん動いていませんでした。どうもドメイン変わった時期の前後あたりから、APIでかえってくるJSONのパースにコケているようで、Perlのモジュールの手を出す方法がさっぱり分からなかったので途方に暮れてました。で、暮れてばっかりだとアレなのでRubyの勉強がてら修正スクリプト書いてみました。超素人コードですけど。

仕掛けは単純で、プラグインをインストールするとmt_bookmark_countってテーブルができるので、そこのbookmark_count_delicous_counter, bookmark_count_total_counterをがしがしupdateさせてるだけです。deliciousのAPIへはまとめて15個URLづつリクエスト投げてます。13, 32行目あたりのDB情報、URL情報を適当に直したら動くんじゃないかな、と思います。

ただ、このBlogのMTもバージョンは超古くて3.3とかだし最近のMTのバージョンでこのプラグインが動くかどうかも定かではないです。。あくまでオレオレパッチ。もしご利用されたい方がいらしたら使ってみてください。要MySQL/Rubyですが、次のサイトに従ってインストールすると楽にできました。

動かし方は上のリンクの圧縮ファイルを解凍し、ruby delicious.rb で動くと思います。あとはサイトを丸ごと再構築すればdeliciousのカウンタも反映されるはずです。

まともにRubyでコード書いたの初めてだったですけど、個人的にはPerlよりも書きやすかったかも。割とさくさく書けて楽しかったです。さらっとツール書くのは便利ですね。

Mac OSXで初めてのRubyを始めてみました

  • 2008年11月10日 19:34
  • ruby

Rubyの勉強を始めてみたくなったので、O'REILLYの「初めてのRuby」 を購入してみました。MacPortsで最新版のRubyを導入しつつ、1章から読み進めているのですが、早速ハマったところがあるのでそのメモを残しておきたいと思います。

Ruby1.9の導入

MacPortsでruby19の名前でインストールできます。

sudo port -d install ruby19

/usr/bin/ruby には、元からインストールされているrubyが入っているので、これをMacPortsでインストールしたものと入れ替えます。(シンボリックリンク張り替えちゃったけどこれでいいのかな?ruby_selectみたいのないのかな)

cd /usr/bin
sudo mv ruby ruby.org
sudo ln -s /opt/local/bin/ruby1.9 ruby

ruby --version

と、入力して

ruby 1.9.1 (2008-10-28 revision 19983) [i386-darwin9]

こんなかんじの出力になればOKだと思います。

日本語(マルチバイト)の利用

1.5.4制御式のあたりについて。通常だとマルチバイト文字の利用でエラーがでちゃいます。たとえば

p "こんにちわ!こんにちわ!><"

で、

odd.rb:8: invalid multibyte char (US-ASCII)
odd.rb:8: syntax error, unexpected $end, expecting keyword_end

などとエラー文が出力されてしまいます。 これは、次のように冒頭に利用するエンコード方式をコメントで書けばOK

#!/usr/bin/ruby

#!/usr/bin/ruby
# coding: UTF-8

に変更すればOK。

tkパッケージの利用

1.5.5のコールバックのあたりの話。実は冒頭のインストール方法だとtkパッケージがインストールされていないため、利用できません。(require文でコケる) なので、tkパッケージを有効にして入れ直します。variantsオプションで有効なインストールオプションを確認します。

$ port variants ruby19
ruby19 has the variants:
        universal
        c_api_docs: Generate documentation for Ruby C API
        tk: Build using MacPorts Tk
        mactk: Build using MacOS X Tk Framework

tk, mactkと似たようなものが2つあります。これ両方有効にしてインストールするとコケちゃいます。とりあえずmactkの方を有効にしてインストールしなおし。

sudo port install ruby19 +c_api_docs +mactk

ところがこれだとエラーでコケます。

Error: The following dependencies failed to build: doxygen graphviz pango urw-fonts

いろいろビルドに失敗しているみたいなので全部個別にインストール。

sudo port install urw-fonts
sudo port install pango
sudo port install graphviz
sudo port install doxygen

この上で、もう一度インストールしなおし。

sudo port install ruby19 +c_api_docs +mactk

これで次のrequire文でコケません。

require "tk"

まとめ

ちょっとづつRubyもわかってきました。これから毎日1章づつくらいのペースで進めていきたいとおもいますよ!

Index of all entries

Home > develop > ruby Archive

Search
Feeds

Return to page top