ちなみに

火曜日の空は僕を押しつぶした。

今日の日記

なんか不味そうに写ってる。

昨日は酔ってなかったはずなのに、いつどうやって寝たかまったく分からなかった。 3時くらいにふと目が覚めて寝ていたことに気付くという感じ。

BitBarに定時までのカウントダウン表示してたのだけれど、これのついでに分報チャンネルに毎時進捗確認のメッセージ送るようにした。 いつも思うけど Incoming Webhook べんりすぎる。 記録の意味でどんな作業しているときに何を聴いてたか分かると良さそうなので、聴いてる曲も流したいけど曲名だけで埋まりそうで送りかた迷ってる。

今週のお題「私がブログを書く理由」

記憶力がほんとうにカスみたいなので、書いておかないと忘れる。 あと、書くともやもやしたものを吐き出せる。 完全に自分のために書いてる。たまに承認されると嬉しい。

今日の日記

日曜日にホットケーキ焼いたけど綺麗に焼けなかったし、そんなに美味しくもなかった。もっと甘い食べ物だと思ってたのだけれど子供の頃の記憶は美化されていたのだろうか。

昨日はいろいろナーバスになってて変なテンションだった。いろいろやらかしてて完全になかったことにしたい。 承認はされたいけど注目はされたくなくて難しい。

同僚に祝ってもらう。感謝しかない。 めずらしく落ち着いた席だった。

一番気を使わなくていい友だちが30才になった。 めでたい。

いま聴いている曲の iTunes Store での trackId を取得する旅

f:id:Sixeight:20160419034911p:plain

2016年3月30日に京都で開催された 関西モバイルアプリ研究会 #12 で、iOS 9.3 に追加された、Apple Music 関連のAPIについて紹介しました。 Apple Music は月額料金を払えば、ストリーミングで音楽を聴きまくれる最高のサービスなので、使ってない人はいますぐ契約しましょう。

今回の更新で主に以下のようなことが出来るようになりました。

  • SKCloudServiceController
    • Apple Music へのアクセス権限の確認
    • Apple Music へのアクセス権限の取得
    • 再生、またはライブラリの追加が可能かどうかの確認
    • ユーザーがApple Musicを契約している国のStorefront IDの取得
  • MPMusicPlayerController
    • Apple Music の曲を再生する
  • MPMediaLibrary / MPMediaPlaylist
    • Apple Music の曲をライブラリに追加する
    • 権限の周りのAPIも追加されてるけど SKCloudServiceController と全く同じ動きでした

権限の取得や、Storefront ID を確認したあとは、おもむろに以下のようにして曲を再生できます。

let player = MPMusicPlayerController.systemMusicPlayer()
player.setQueueWithStoreIDs([ "1017194380", "771599829" ])

非常に簡単ですね。この StoreID は同じ曲でも国ごとに違うので Storefront ID を確認しておく必要があります

詳しくは僕の資料や、以下の記事が参考になるかと思います。 発表したあとブログに書こうと思って追加でもうちょっと調べてたら、先にこの記事が出たので書くのをやめたという負け惜しみをひとつ。

dev.classmethod.jp

ところで、この StoreID とやらはどうやって取得出来るのでしょうか。 残念ながら iOSAPI だけでは取得することが出来ません。悲しい。 なんか思っていた夢の世界とはちょっと違う。

iTunes Search API を使う

affiliate.itunes.apple.com

ところで iTunes Search API を使うと iTunes Store 上の曲などの情報を取得できます。

curl https://itunes.apple.com/search?term=ESNO+とか。&country=JP&media=music&entiry=song&isStreamable=true

たとえばこのようなクエリで Search API を叩くと以下のようなJSONが得られます。

{
  "resultCount": 1,
  "results": [
    {
      "wrapperType": "track",
      "kind": "song",
      "artistId": 555290400,
      "collectionId": 1017193906,
      "trackId": 1017194380,
      "artistName": "ESNO",
      "collectionName": "Release",
      "trackName": "とか。 feat. きゃべこ - remix -",
      "collectionCensoredName": "Release",
      "trackCensoredName": "とか。 feat. きゃべこ - remix -",
      "artistViewUrl": "https://itunes.apple.com/jp/artist/esno/id555290400?uo=4",
      "collectionViewUrl": "https://itunes.apple.com/jp/album/toka-feat.-kyabeko-remix/id1017193906?i=1017194380&uo=4",
      "trackViewUrl": "https://itunes.apple.com/jp/album/toka-feat.-kyabeko-remix/id1017193906?i=1017194380&uo=4",
      "previewUrl": "http://a1896.phobos.apple.com/us/r20/Music5/v4/7c/06/ae/7c06aefa-3c4b-3916-20a3-660589df15dd/mzaf_2523222398560687944.plus.aac.p.m4a",
      "artworkUrl30": "http://is3.mzstatic.com/image/thumb/Music7/v4/8a/4e/42/8a4e42c8-0638-163e-bcb9-ec2a980999dc/source/30x30bb.jpg",
      "artworkUrl60": "http://is3.mzstatic.com/image/thumb/Music7/v4/8a/4e/42/8a4e42c8-0638-163e-bcb9-ec2a980999dc/source/60x60bb.jpg",
      "artworkUrl100": "http://is3.mzstatic.com/image/thumb/Music7/v4/8a/4e/42/8a4e42c8-0638-163e-bcb9-ec2a980999dc/source/100x100bb.jpg",
      "collectionPrice": 1800,
      "trackPrice": 200,
      "releaseDate": "2015-07-22T07:00:00Z",
      "collectionExplicitness": "notExplicit",
      "trackExplicitness": "notExplicit",
      "discCount": 1,
      "discNumber": 1,
      "trackCount": 12,
      "trackNumber": 9,
      "trackTimeMillis": 215507,
      "country": "JPN",
      "currency": "JPY",
      "primaryGenreName": "エレクトロニック",
      "isStreamable": true
    }
  ]
}

このJSONtrackIdcollectionId が今回求めている StoreID で、この ID さえあればAPIから再生やライブラリに追加することが出来ます。 呼び方がいろいろあってややこしいので、以下は trackId に統一します。 ちなみに isStreamabletrue の曲のみ Apple Music で有効です。

しかし、この Search API、意外と融通が効かない。このアーティストのこのアルバムなどのようなピンポイントの検索が出来ない。つまりいま聴いている曲から機械的に正しい trackId を取得することが困難です。変わった曲名や、変わったアルバム名なら項目指定で一発で引けそうですが、一般的な名前なら絶望的。

では、人間が検索して正しいのを選ぶとうのはどうでしょうか。良さそうですか? いやいや、我々はそういったことが出来る最高のアプリを知っているはずです。iTunes という最高のアプリがあるので使いましょう。 そう、つまりこの trackId が自動的に分からなければ特になんのメリットもないとうことです。

再生中の曲を得る

ひとまず現在、再生中の曲をのぞいてみましょう。

一番簡単な方法は MPMusicPlayerControllernowPlayingItem プロパティを見ることです。これは現在 Music アプリで再生中の曲情報を MPMediaItemインスタンスとして取得出来ます。 取得した MPMediaItemvalueForProperty(_:) メソッドを使って各種情報を取得できます。ドキュメントにそう書かれているけど、ヘッダーを眺めているとそれぞれプロパティが用意されているようなのでそれを使うと良さそうです。

let player = MPMusicPlayerController.systemMusicPlayer()
let currentTrack = player.nowPlayingItem
print(currentTrack.valueForProperty(MPMediaItemPropertyTitle))
// => とか。 feat. きゃべこ - remix -

MPMusicPlayController に指定することで、再生状態が変わったとき (MPMusicPlayerControllerPlaybackStateDidChangeNotification) や、曲が変わったとき (MPMusicPlayerControllerNowPlayingItemDidChangeNotification) に通知が発行することが出来るので、これを監視することで常にいま聴いている曲の情報を取得し続ける事ができます。

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)

     // 通知を監視する
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(updateTrackTitle), name: MPMusicPlayerControllerPlaybackStateDidChangeNotification, object: self.player)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(updateTrackTitle), name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification, object: self.player)

    // 通知を送るようにする
    player.beginGeneratingPlaybackNotifications()
}

override func viewWillDisappear(animated: Bool) {
    // 通知を止める
    player.endGeneratingPlaybackNotifications()

    // 通知の監視を止める
    NSNotificationCenter.defaultCenter().removeObserver(self)

    super.viewWillDisappear(animated)
}

これで聴いてる曲を共有出来るかと思いきや、残念ながら iTunes Store に関する情報は皆無です。(もしかしてなんか取得方法あったら教えてください!) 曲名とかは共有出来るけど、いま欲しいのは trackId であって、曲名などどうでもいいのです。むしろ trackId さえあれば Lookup API で各種情報は取得できる。

Music アプリから iTunes Store の URL を共有出来るのだから取得出来てもおかしくないのに。

一方 Mac では

だんだんと iPhone で聴いている曲を自動で共有するということをあきらめつつあり、ひとまず trackId を集めて、API を試したいという気持ちになってきました。 IDを集めるだけだったらふだん仕事中に Mac で音楽を聴いているわけで、Mac で取得出来たら得だな、なにか方法はないのかなと調べ始めました。

再生中の曲を得る

Mac には Scripting Bridge という仕組みがあって、アプリのインターフェースを定義しておくと、別のアプリから操作することが出来るというものです。

sdef コマンドで定義されているインターフェースを取得することができ、さらに sdp コマンドでヘッダーファイルなどに変換できます。

sdef /Applications/iTunes.app | sdp -fh --basename iTunes

iTunes だとこういう感じ。

この生成されたヘッダーファイルを使うと使うといいのですが、Swift だとうまくいきません。

あきらめて適当なプロトコルを定義して、SBObject を拡張することで欲しい値を取得します。

@objc protocol iTunesTrack {
    optional var name: String { get }
    optional var album:  String { get }
    optional var artist: String { get }
}
extension SBObject: iTunesTrack {
}

let iTunesApplication = SBApplication(bundleIdentifier: "com.apple.iTunes")!
let currentTrack = iTunesApplication.valueForKey("currentTrack") as! iTunesTrack
print(currentTrack.name)

// => 夕暮れパラレリズム (feat. daoko)

しかし、残念ながら生成した iTunes.h を眺めても iTunes Store に関する情報は取得できそうにありませんでした。 そして、さらに Stream 再生している曲の場合は、currentTrack は取得出来るものに、name などが空文字列になってしまって、正常に取得出来ないように見えました。

通知を眺めていると

iTunes の曲情報を取得するのは若干面倒だったのですが、再生状況が変わったときに iTunes から通知が発行されていてこれを拾うことは簡単です。

# どこかで監視を追加
NSDistributedNotificationCenter.defaultCenter().addObserver(self, selector: #selector(trackChanged(_:)), name: iTunesNotificationName, object: nil)

// ...

func trackChanged(notification: NSNotification) {
  print(notification)
}

iOSとは違って通知は1種類なのですが、NSNotification に各種情報が格納されています 再生中かどうか、現在選択されている曲の情報などが含まれます。 こいつを眺めてみると以下のようになっています。

__CFNotification 0x608000040360 {name = com.apple.iTunes.playerInfo; object = com.apple.iTunes.player; userInfo = {
    Album = "#501";
    "Album Artist" = MIDICRONICA;
    "Album Rating" = 0;
    "Album Rating Computed" = 1;
    Artist = MIDICRONICA;
    "Artwork Count" = 1;
    "Back Button State" = Prev;
    "Disc Count" = 1;
    "Disc Number" = 1;
    "Display Line 0" = "San Francisco";
    "Display Line 1" = "MIDICRONICA \U2014 #501";
    Genre = "Hip Hop/Rap";
    "Library PersistentID" = 4494643586137931510;
    "Like Status" = None;
    Name = "San Francisco";
    PersistentID = "-1983237849806433000";
    "Play Count" = 12;
    "Play Date" = "2016-03-18 02:06:26 +0000";
    "Player State" = Playing;
    "Playlist PersistentID" = 5800905515721368577;
    "Rating Computed" = 1;
    "Skip Count" = 0;
    "Store URL" = "itms://itunes.com/album?p=500044680&i=500044854";
    "Total Time" = 206733;
    "Track Count" = 15;
    "Track Number" = 9;
    Year = 2005;
    elapsedStr = "-3:27";
}}

( ゚д゚)

(つд⊂)ゴシゴシ

(;゚д゚)

Store URL !!!!!!!!!!!!!!

ということで、iTunes Store での URL が取得出来ることが分かります。 よく見ると i=500044854 というパラメータがついており、これが求めていた trackId です。

お疲れ様でした。

安心はできない

ここで Apple Music で再生出来るのは isStreamable な曲だけだったことを思い出してください。 今回取得出来た情報だけだと、iTunes Store で販売されているということは分かりますが、Apple Music で配信されているかまでは分かりません。

そこでふたたび登場するのが iTunes Search API です。このAPIには Lookup というのもあって、これは id を指定して商品(ここでは曲!)の情報を得られます。

以下のようにします。ドキュメントを読んでいると country パラメータは不要に見えるのですが、指定しないと日本のストアで検索できないのがハマりポイントです。

curl https://itunes.apple.com/lookup?country=JP&id=500044854

すると以下のような曲情報が取得できて、isStreamabletrue なので Apple Music で再生出来ることが分かりました。

{
  "resultCount": 1,
  "results": [
    {
      "trackId": 500044854,
      # (略) ...
      "isStreamable": true
    }
  ]
}

結局

本来の目的は iOS 9.3 の Apple Music 関連APIを活用することだったのですが、trackId が分からないと何も出来ないし、Search API を検索するくらいなら iTunes 使うでしょということで、まずは trackId を集まることに注目して調べてみました。結果、Mac アプリから取得できる iTunes の通知からとれる Store URL。そこから分かる id と lookup API を使って、自動的に trackId と正確な曲情報を取得出来ることが分かりました。

trackId さえ集まれば再生やライブラリへの追加はできますし、プレイリストを Web から作れて、それを iOS で誰でも再生出来るとかのサービスは作れそうですが、どっちみち Web サービス前提となってしまって気軽には出来ません。今回の API 追加は夢だけは広がりましたが、どちらかというと既存の音楽系サービス向けのもので、一般ユーザー向けには iOS 上で再生中の曲の trackId を取得する手段が公開されるまでは使いどころが難しいのではないでしょうか。

おまけ

調べる過程で以下のようなのが出来ました。

聴いてる

サイトはどうでもよくてここで trackId を収集しつつ、Slack に Webhook を投げたりしていてちょっとだけべんり。

iOSAPIをちょっと試してみたい人向けに ここApple Music で再生出来る僕(か知り合い)が聴いた曲の直近50件の trackId を返すようにしています。(予告なく消す可能性あります)

Twitter にも一部流しています。Twitter にはアフィリエイトリンク張っていないので安心してクリックください。

twitter.com

30才の日記

僕の人生を模した動画です。

30才という字面がやばい。30才になることに対して感情はないと言っていたけどめちゃくちゃナーバスになって、一日そわそわイライラしていた。なんてことだ。

29才のふりかえり

sixeight.hatenablog.com

以下の抱負を掲げていた。

* [やめる] ネガティブなことを言わない。(最近ひどかった。言霊はあるよ。)
* [やめる] 断ることをやめる。(食わず嫌いは損。)
* [やる] 自分を知る。(何が出来るの、何がしたいの、何が好きなの、何が嫌いなの。)
* [やめる] 場当たり的な生き方をやめる。(べつにゴールを決めるわけじゃないけど発散しないように)
* [やる] 人と関わる。(これは一昨年初めてからちょっとずつ進めてる。)
  • [やめる] ネガティブなことを言わない。(最近ひどかった。言霊はあるよ。)
    • とてもひどかった。人生で二番目くらいにひどかった。ネガティブなことしか言ってない。
    • 精神的にも参ってしまってて夏は厳しかった。
  • [やめる] 断ることをやめる。(食わず嫌いは損。)
    • これはけっこう出来た気がする。
    • 逆に親しい友だちの誘いを断りがちだった気がする。
    • あとめんどくさい気持ちが勝って誘われないと何も出来なかった
  • [やる] 自分を知る。(何が出来るの、何がしたいの、何が好きなの、何が嫌いなの。)
    • いろいろ分かった。
    • 記憶力が最悪。文字を認識する能力が低い。とくにめちゃくちゃ集中してないとアルファベットが混ざって見えることに気付いた。厳しい。
    • 視力かと思って眼科行って眼鏡新調したけどまったく意味なかった
    • フィクションが好き。とくに短編が好き。漫画めちゃくちゃ読んだ気がする。小説も好きなのだけれど文字を読むのがめちゃくちゃ遅いので漫画が効率的。とにかく物語を消費したい。ただし消費した物語をほとんど覚えてない。これは脳の欠陥という気がする。
    • においフェチだと分かったけど自分はずっと汗臭いし泣きたい
    • 根拠のない自身が嫌い、たぶん自分がずっと自身がないので羨ましいのだと思う
    • 何がしたいのかは分からなかった、楽しくありたい。
  • [やめる] 場当たり的な生き方をやめる。(べつにゴールを決めるわけじゃないけど発散しないように)
    • これまでと変わらなかった。
  • [やる] 人と関わる。(これは一昨年初めてからちょっとずつ進めてる。)
    • 最悪。ほとんど何も出来なかった。なんで。
    • 人と話すのに異常に緊張するようになってて、昔からの知り合いとしか話せない感じになってる
    • 最高な人たちと働いているはずなのに。最高な人たちだから緊張するのかもしれない。

抱負

  • なんかちゃんとしてるのが良いことという強迫観念に取り憑かれすぎてちょっとでもそこからずれると全部駄目に感じる。だんだん疲れてきてちゃんと出来なくなってきてるのに、ちゃんとしてるフリだけしようとしてて最悪だったので、あきらめて自然体を目指す
  • なんかこうやって真面目ぶって抱負とか書いたりするの止める

みんなも早く三十路になろうよ、目指すは不惑だよ

これおもしろいと思ったけどおやじギャクだった。こうやっておっさんになっていく。

今年はがんばらない。

今日の日記

だんだんずれ込んできて昼に書くようになっている。是正が必要。

さいきん「なんとしたい…」とか「XXXにしたい」とか言いまくってる気がする。 僕はエンジニアなのでやれば出来るはずなので「したい」っていっている間にやってないといけない気がする。 口だけで何もやらないの許せない気がするけど、自分には甘すぎる。 この文章すら「したい」に該当するものであって救いはない。

いろいろ懸念事項はあるのだけれどだいたい後まわしにされていて、いまはとにかく免許の更新が懸念。

週末の日記

最近会ってないなーとつぶやいたら土曜日に飲みに行くことになった。 他大学の友人の研究室の先輩という変な関係なのだけれど、いまやその友人よりも会っている気がする。

いつもは京都に来てもらっているので、久しぶりに僕が行くことにしたら、どうぶつ園に行こうということになって行ってきた。

www.kobe-oukoku.com

起きたら連絡するということにしていたのだけれど、結局13時くらいまで寝ていて、向こうについたのが15時半を回っていた。 どうぶつ園についたころには16時を回っていて、閉園が17時半だったのであんまり時間がなかった。

神戸どうぶつ王国、動物が放し飼いになっていてふつうの動物園では味わえない楽しさがある。 本当にふつうにその辺に動物がいるという感じ。 人馴れしているし近づいても逃げない。

相手が動物なので事故が起きないとも限らないし、家族連れも多いので子供が多くてよりリスクは高い。 ちゃんとしつけして、かつ常に監視しないといけなくて、運用は相当大変そう。

スタッフの人、すごい気軽に話しかけてくれて、いろいろ教えてくれる。 動物が好きじゃないとこんなに大変なことできないのでかっこいい。

最初からめちゃくちゃテンション高くて、おかしい感じだった。 これだけ楽しいの久しぶりかもしれない。

閉園時間になったので名残惜しみつつもにこにこしながらどうぶつ王国をあとにした。

しばらく三宮のジュンク堂で時間をつぶしつつ、お店に移動して酒を飲む。 みかん酒がおいしくてかぱかぱいってたら酔っ払ってしまったので、なんの会話したかあんまり覚えていないのだけれど、おっさんになると食べる量が減るという話をした気がする。

食べ終わったころにとつぜんサプライズでお祝いをされておっさんながら感動してしまう。 誕生日もうちょっと先なので最速。30才になるのがネタとしてのおもしろさしかなくて、年齢が上がることに対しては感情はない。 感情はないことはないけど、ネガティブな話はそろそろやめたい。

気がついたら京都駅にいて、バスに乗って帰宅した。 ひさしぶりに Kindle を持ち歩いて使ってみたけど良かったので、新型出るなら買いたい。

帰りの電車で寝てしまって眠れなかったので、朝まで漫画を読んでいた。地震などあったのもあるかも。

姑獲鳥の夏 1 (怪COMIC)

姑獲鳥の夏 1 (怪COMIC)

分厚すぎて読む気元気がでなくて京極堂シリーズ読んだことないのだけれど、友達からはずっとおすすめされていて、あきらめて志水アキ先生のコミカライズを読んでいる。 あきらめてという言い方はちょっと乱暴で、原作読んでないので比較できないだけで、僕はとても好き。 姑獲鳥の夏と、百器徒然袋の最後のほう読んでなかったのでこれらを読んだ。 ミステリーは一気読みできないと続きが気になって仕方ない。

日曜日は案の定崩壊してて、何回か目覚めては本を読んで自然と寝たりを繰り返しながら夜になっていた。 灰と幻想のグリムガルついに1巻読み終わってしまって、2巻読み始めた。ラノベでも読むのに時間がかかるの脳の欠陥という気がする。 洗濯をしてあきらめて寝る。

今日の日記

だいぶ前の治安崩壊。

この2日くらい凹みまくっていた。去年の4月と同じで、4月は自分の駄目さを痛感する。 一年経ったけどそんなに進歩してない。それを言うならまもなく30才になるけど年齢に見合ったもろもろがない。 年齢にみあったとは何か。わからん。

総じて寝不足なのだと思う。

カクヨム、ちまちま読んでいる。

kakuyomu.jp

1部もおもしろいと思ったけど、2部は本格的におもしろくなっってくる。 1部はニャクザが大立ち回り、2部はニャフィアがスマートに成り上がったり、苦悩したりする。 にゃんこかわいいという気持ちが、だんだんかっこよさが際立ってきた。 意外とマフィアもの好きな気がしてきた。

kakuyomu.jp

もともとなろうの作品っぽい。騙されると聞いていたけど、ふつうに騙されて読みなおしてしまった。 ビールが飲みたくなる作品で、家まで歩いて帰りながら読んだけど、読み終わってすぐに24時間のスーパーが目に入って、そのあとは記憶にない。 自分のペースでビールを飲もう。

僕たちがやりました(4) (ヤングマガジンコミックス)

僕たちがやりました(4) (ヤングマガジンコミックス)

4巻、被害者側に集点が当てられていて、これまでとちょっと印象が変わった。 なんかだんだん主人公側に腹立ってくる。 多分、僕が主人公側だったらこういう感じになる気もする。 いや、逃げる勇気もなくて自首するか。