KLab若手エンジニアの これなぁに?

KLab株式会社の若手エンジニアによる技術ブログです。phpやRubyなどの汎用LLやJavaScript/actionScript等のクライアントサイドの内容、MySQL等のデータベース、その他フレームワークまで幅広く面白い情報を発信します。

2011年01月

とある日本の楽曲目録(JASRAC)

umjammer です、 お久しぶりです、すっかり寒い季節になってしまっています。 私は音楽を聞くのが趣味の一つです。iPod と iTunes を使っています。自分の持っているすべての音楽を一元管理できるってのは世界観が変わるよと言われてやってみました。たしかに世界観は変わったのですが、デジタル潔癖症 [1] を罹患している私は思わぬところでハマってしまいました。楽曲の情報をすべて埋めてやらないと気が済まないんです。 楽曲のタイトルとアルバムはたいてい iTunes が勝手にデータベースから取ってきてくれるので問題ありません。まずハマったのはすべてのアルバムのジャケットをそろえることです。600x600 pixel のジャケットを揃えるのにものすごい労力を費やしたものです。
ジャケ写取得参考ソフト iGCover なければアマゾンのユーザーがアップロードしたジャケ写 それでもなければ discogs.com
iTunes Artworks


ジャケットをすべて揃えたあとリストを眺めてると、入ってるのと入ってないものがある項目として作曲者が目についてしまいました。うまっていない項目をうめないとデジタル潔癖症の私の心は癒えません。 ググッてみたのですがジャケットみたいにフリーなソフトは見つかりません。 自作するしかありません。 アーティスト、曲名から作曲者を検索するプログラム さて作曲者情報はどこから取ってきましょうか? 実はいいデータベースがあるんですよ。それも国内に。そう JASRAC楽曲データベースです。ネットでは何かと言われています JASRAC ですが、さすが音楽の世界を牛耳っているだけあって持っているデータベースはメジャーどころでは洋邦問わずほとんど網羅されています。そしてそのデータベースはネットで公開されています。
http://www2.jasrac.or.jp/eJwid/
技術者としては文句をいう暇があるのならその代わりにとことん利用させていただきます。 普通の Web Form インターフェースで API などなさそうなのでスクレイピングすることにします。iTunes から作曲者情報の無い楽曲のアーティストとタイトルを取ってきてそれを引数にしてJASRAC データベースを検索します。 JASRAC のページは最初のページのボタンを押さないとフォーム検索してくれないので Web コントロールを行うソフトとして htmlunit を使います。 iTunes データベースは XML ファイルとして存在するので XPath で読み込もうとしたのですが、容量がでかいため(2万曲近くあるので) JDK 付属の DOM で扱うとメモリが溢れてしまうので Saxon を使います。 流れは以下のようになります。
iTunes Music Library.xml -> xpath (artist, title) -> htmlunit -> JASRAC DB -> xpath (composer) -> TSV file
JASRAC のデータベース内の人名は変な規則で正規化されてるので普通に読みやすくしてやる必要があります。あと alias も同時に登録されているのでダブリを除かないといけないのですが面倒なのでそのままです。 今までの流れを記述した実際のコードは以下になります。
iTunes.java


実行結果の TSV は以下の様になります。
JASRAC Database Scraping
見つかった場合は先頭が RESULT、見つからなかった場合は NONE、もしかしての候補がある場合は MAYBE になっています。 さすがに JASRAC データベースも 100% 網羅しているわけではないので、見つからなかった場合は、
discogs.com allmusic.com
等のサイトを利用してください。 allmusic.com はとても面倒なのでスクレイピングしたいところですが力尽きました...orz iTunes に作曲者を登録するプログラム 先のプログラムは作曲者情報を TSV ファイルに落とすだけですので、実際に iTunes に登録する必要があります。Mac では Apple Script で iTunes をコントロールすることができますので、こんなプログラムを書いてやります。

$ cat composer.scpt
on run argv
  tell application "iTunes"
    set results to (every file track of library playlist 1 whose artist equals item 1 of argv and name equals item 2 of argv)
    -- display dialog item 1 of argv & ", " & item 2 of argv & ", " & count of results
    repeat with t in results
      try
        tell t to set composer to item 3 of argv
      end try
    end repeat
  end tell
end run

TSV ファイルから先頭が RESULT なものだけを抽出して上のプログラムに食わせます。

$ cat tsv2sh
#!/bin/bash

OIFS=$IFS

IFS=' '

exec < "composers.tsv"
while read LINE
do
  if [[ "$LINE" =~ ^RESULT* ]]; then
    # 注意! -F"<tab>"
    echo $LINE | awk -F"    " '{ printf "osascript composer.scpt \"%s\" \"%s\" \"%s\"\n", $2, $3, $4 }'
  fi
done

IFS=$OIFS

$ sh tsv2sh > tmp.sh ; sh -v tmp.sh
:

作曲者の項目のほとんど (17914/17979=99.6%) を埋めることができました!ぱちぱち〜
iTunes Composers


あとがき 最近ネット配信の音源が増えてきました。海外からの直ダウンロード等ですと JASRAC が 管理していないので作曲者がわからない曲がでてきました。困ったものです。TAG はちゃんと埋めてくださいね。 スクレイピングの際に利用サイトに負荷をかけないようにご注意願います。また岡崎事件みたいなことになる可能性もあるということを十分ご理解いただいた上で実行してください。 そういえば歌詞の項目がすべてうまっていない気がするのですが、さらに無駄な時間を浪費しそうなので気づかなかったことにしています(笑)

[1] リアルの部屋は汚いくせにPCの中のデータやフォルダはきれいに整理されてなければ許されない性格

iPadで動く電子書籍アプリを作ってみる(4)

初めに

あけましておめでとうございます。ponpoko1968です。昨年中はご愛読ありがとうございました。2011年も若手ブログをよろしくお願いいたします。 さて、今回はズーム機能の説明です。

通常のズーム機能

これまでの記事で何度かご説明したように、今回のアプリでは、文書のページイメージを表示するビューをUIScrollViewのサブビューとするようビューの階層構造を作っています。 これは、UIScrollViewのズーム機能を活用することが目的の一つでした。 早速、ページ表示画面にズーム機能を追加してみましょう。 UIScrollVeiwは、UIScrollViewDelegateというプロトコルをサポートしており、UIScrollViewに対してユーザが操作を行うタイミングの節目節目でこのプロトコルで定義されているメソッドをデリゲートに対して送ります。 デリゲートとなるオブジェクトは必要なデータを返したり、アプリの状態をデリゲートメソッドが呼ばれたタイミングに適したものにする処理を行うことが出来ます。 iOSプログラミングでは、クラスの継承によってクラスをカスタマイズさせるのではなく、デリゲートパターンを用いてクラスの一部処理を別のクラスに委譲(delegation)させる手法がよく用いられています。 このアプリの場合、ページ表示画面の動作を全体的に制御しているPageViewContollerをUIScrollViewのデリゲートに設定します。 PageViewContollerクラスの定義で、UIScrollViewDelegateをサポートすることを宣言します。

  PageViewContoller.h
  @interface PageViewController : UIViewController {
次に、InterfaceBuilderで、UIScrollViewのdelegateプロパティにPageViewContollerのインスタンスを指定します。これで、UIScrollViewはUIScrollViewDelegateのメソッドをPageViewContollerに送るようになります。 スクロール動作を始めたときにUIScrollViewがズーム表示する対象となるビューをUIScrollViewに教える処理を実装します。

  - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
    return contentView_;
  }

拡大してスクロール操作をしようとしているのに、タップと見なされてページ遷移してしまうのを防ぐため、拡大中はタップの認識を抑制するようにします。 拡大操作の開始タイミングは先ほどのviewForZoomingInScrollView:が送られることで知ることが出来ます。また、拡大操作が終了するタイミングは、scrollViewDidEndZooming:withView:atScale:がというデリゲートメソッドが送られることで取れます。

部分拡大機能

ビューア系のアプリでよくある、2本指のピンチイン、ピンチアウトで拡大・縮小する機能は前節のように比較的簡単に実装できるのですが、部分的に拡大したい、テンポ良くページを閲覧したい場合には使いづらく感じるときもあります。 そこで、画面をタップした後、そのまま長押ししたらタップした場所の周辺部が拡大された画面が表示され、タップした指を離すと消える、という動作にすれば、ページ遷移の動作の邪魔にならず、必要な部分だけを拡大表示出来るのではないかと、実装してみました。 今回、iOS3.2つまりiPadのリリースに合わせてサポートされたUIPopoverControllerを使って、部分拡大の表示を試してみます。 ポップオーバーとはどんなUI要素かというと、百聞は一見にしかず、下記のスクリーンショットをご覧ください。


このようにマンガの吹き出しのような画面を任意の場所に表示させることができます。指定した大きさのポップオーバーが画面に収まるよう表示位置を自動で調整してくれるのでお手軽です。 UIPopoverControllerを使うには、ポップオーバーの中身として表示するためのビューコントローラを用意する必要があります。 ビューア画面本体と同様、UIScrollViewのズーム機能を使います。 このビューコントローラはInterfaceBuilderを使うほど複雑な階層関係を持たないので、nibは作成せず、loadViewメソッドの呼び出しの中で、プログラマブルにビュー構造を生成します。 ビューの階層関係は、ビューア本体の表示画面同様、UIScrollViewの下位ビューにイメージを表示するビューを配置する形となります。




="prettyprint"> 1 - (void)loadView { 2 3 // ポップオーバー表示時に表示するサイズを指定 4 self.contentSizeForViewInPopover = CGSizeMake( kLupeViewWidth, kLupeViewHeight ); 5 // トップレベルのビューを作る 6 self.view = [[UIView alloc] initWithFrame:CGRectMake(0,0,kLupeViewWidth, kLupeViewHeight)]; 7 8 // スクロールビューを作る 9 lupeScrollView_ = [[UIScrollView alloc] initWithFrame:CGRectMake(0,0,kLupeViewWidth, kLupeViewHeight)]; 10 [self.view addSubview:lupeScrollView_]; 11 12 // イメージ表示ビューを作る 13 lupeImageView_ = [[UIImageView alloc] initWithFrame:contentFrame_]; 14 15 // イメージの縦横比率を保持して、長辺がビューに収まるよう表示する指定 16 lupeImageView_.contentMode=UIViewContentModeScaleAspectFit; 17 18 [lupeScrollView_ addSubview:lupeImageView_]; 19 lupeImageView_.backgroundColor = [UIColor blackColor]; 20 21 22 lupeScrollView_.delegate =self; 23 24 // ズーム可能にする(デフォルト値が1.0なので注意) 25 lupeScrollView_.maximumZoomScale = kLupeZoomRatio; 26 [lupeScrollView_ setZoomScale:kLupeZoomRatio animated:NO]; 27 28 }
上記コードで重要な点としては、このビューコントローラ自身のcontentSizeForViewInPopoverプロパティに適切な値を設定すること(4行目)、あらかじめ設定しておいたcontentFrame_をイメージビューの大きさとして設定する(13行目)でしょう。 実際にポップオーバーで表示させるには、ビューコントローラのviewDidLoadメソッドでUIPopoverControllerを作成し、長押しのハンドラで表示させます。

     1	- (void)viewDidLoad {
     2	  (略)
     3	  // 長押しrecognizerの登録
     4	  UILongPressGestureRecognizer* longTapRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongTapFrom:)];
     5	  [self.view addGestureRecognizer:longTapRecognizer];
     6	  [longTapRecognizer release];
     7
     8	  lupeViewController_ = [[LupeViewController alloc] initWithContentFrame:self.view.frame];
     9	  popover_ = [[UIPopoverController alloc]
    10		       initWithContentViewController:lupeViewController_];
    11
    12	}
UILongPressGestureRecognizerを生成してセットし、長押しが認識されたらhandleLongTapFrom:が呼び出されるように設定します(4-5行目)。次いで、今回作成したLupeViewController をinitWithContentFrame:で初期化することで、ズーム対象となるページ表示画面のサイズをLupeViewControllerに設定しておきます。 UIPopoverControllerにLupeViewControllerのインスタンスを指定して生成すれば準備完了です。

     1	- (void)handleLongTapFrom:(UIGestureRecognizer *)recognizer {
     2	  CGPoint location = [recognizer locationInView:self.view];
     3	  switch (recognizer.state) {
     4	  case UIGestureRecognizerStatePossible:
     5	    break;
     6	  case UIGestureRecognizerStateBegan:
     7	      lupeViewController_.image = [UIImage imageWithCGImage:  (CGImageRef)[[contentView_ layer] contents]];
     8	  case UIGestureRecognizerStateChanged:
     9	    {
    10	      [popover_.contentViewController zoomAt:location];
    11	      [popover_ presentPopoverFromRect:CGRectMake(location.x, location.y, 0,0)
    12			inView:self.view
    13			permittedArrowDirections:UIPopoverArrowDirectionAny  animated:YES];
    14
    15	    }
    16	    break;
    17	  case UIGestureRecognizerStateEnded:
    18	  case UIGestureRecognizerStateCancelled:
    19	    [popover_ dismissPopoverAnimated:YES];
    20	    break;
    21	  }
    22	}
長押しタップのハンドラも、基本的には通常タップのハンドラと同様です。長押しが開始されたタイミングでそのとき表示しているページのイメージをLupeViewControllerのインスタンスに渡し、UIPopoverControllerのpresentPopoverFromRectメソッドを呼び出せばポップオーバーが表示されます。presentPopoverFromRectにはタップされたビュー上の位置を渡します。permittedArrowDirections:にはUIPopoverArrowDirectionAnyを指定することで、ポップオーバーの吹き出しの部分が、ポップオーバーのウインドウ位置とタップされた座標の位置関係にあった形で表示されます。(下図参照)


まとめ

今回ご紹介したUIScrollViewは、UITableViewとならんでiPhoneのスムースな使い勝手に貢献している大きな要素ではないでしょうか。UIScrollViewがサポートする、ハードウェアによるスクロール/拡大機能を活用しない手はありません。本連載では今後も応用例をご紹介していくと思います。 今回のソースは、下記bzrコマンドで取得することができます。

bzr branch -r article-4-release lp:~klabrd/jisuireader/trunk article-4-release

次回予告

しおり機能など、これからやりたいことを実現しようとすると、書籍の内容に加えて、付加的なデータを保存する機能を追加する必要が出てきました。そこで、次回は、Core Dataを用いてデータの永続化を実装してみたいと思います。

前回までの目次





iPadで動く電子書籍アプリを作ってみる(3)
 KLab若手エンジニアブログのフッター