I like iPod
曲数の遷移
以下のグラフはiTunesのライブラリのxmlから生成した曲数の遷移です。
'03-11から始まっているのは、そのときにディスククラッシュがあったから。 過去半年の時速は 130曲/月らしい。
plistのplist.rbを使ってこんなスクリプトで抽出。 元のデータはiTunesからライブラリをXML出力したファイルです。5MBくらいのファイルを処理させたら iBookG4で6分かかった。
お。[RAA:xmlscan]を使うバージョンscan_plist.rbでは、1分くらいだ。速い。
require 'plist' def main plist = Plist.file_to_plist(ARGV.shift) bin = Hash.new(0) plist['Tracks'].each do |track, dict| key = dict['Date Added'].strftime('%Y-%m') bin[key] += 1 end bin.keys.sort.inject(0) do |sum, key| sum += bin[key] puts "#{key} #{sum}" sum end end main
ある期間でたくさん聴いたやつを調べたいなあ。
4/24 - 5/24
Play Time (song) (total: 111.3H)
1.0H The Right Time / Monday満ちる 0.8H Be Who You Are / Monday満ちる 0.8H Watertight (Napt Remix) / MJ Cole Feat. Laura Vane 0.7H Yikes! Peach Cut 5' 24"!! (by HANDSOMEBOY TECHNIQUE) / ピチカート・ファイヴ 0.7H Bodyswerve (Grant Nelson Remix) / M-Gee Feat. Mica Paris 0.7H Dig Deep / Monday満ちる 0.7H Jill / Paris Match 0.6H Saturday Night / Paris Match 0.6H Lovin' You More (That Big Track) / Steve Mac Vs. Mosquito Feat. Steve Smith 0.6H ボーイ フレンド / AYUSE KOZUE
Play Time (artist) (total: 111.3H)
7.6H Paris Match 7.1H Monday満ちる 3.0H Halfby 2.8H Pizzicato Five 2.6H スキマスイッチ 2.6H Infracom! 2.5H AYUSE KOZUE 2.4H [Re:Jazz] 2.4H Bonnie Pink 2.3H フジ子・ヘミング
Play Time (album) (total: 111.3H)
7.1H ROUTES 5.3H After Six 4.2H MOTIVATION3 compiled by TOWA TEI 3.1H Jazflora 3.0H Green Hours 2.6H [re:jazz] 2.4H [Re:Jazz] (Re:Mix) 2.2H Pizzicato Five CUT UP - Single 2.0H Modern Sounds From Italy 2 1.9H 憂愁のノクターン
集計スクリプト
何度も実行するとXMLを読み込む時間がバカにならないので、Marshalでキャッシュする。 iTunes Music Library.xmlを扱うクラスはどんなインターフェイスが良いんだろう。 もっとたくさんアプリを書かないと想像できないな。
以前(?)Marshalで保存した結果との再生回数の差分をとれるようにしてみた。(2005-06-04)
# itml.rb require 'scan_plist' class ITunesMusicLibrary def initialize setup_fname end def setup_fname @xml_fname = File.expand_path('~/Music/iTunes/iTunes Music Library.xml') @dump_fname = File.expand_path('~/iTML.dump') end def load_plist modified_xml = File.mtime(@xml_fname) modified_dump = File.mtime(@dump_fname) rescue nil if modified_dump && modified_xml < modified_dump return load_dump(@dump_fname) else if modified_dump suffix = modified_dump.strftime("-%y%m%d") File.rename(@dump_fname, @dump_fname + suffix) end plist = load_xml(@xml_fname) File.open(@dump_fname, 'w') { |fp| fp.write(Marshal.dump(plist)) } return plist end end def load_xml(fname) Plist.file_to_plist(fname) end def load_dump(fname) File.open(fname) { |fp| Marshal.load(fp) } end end class TopRateReporter def initialize(plist) @plist = plist end attr_reader :plist def subtract_playcount(old_plist) rev = {} old_plist['Tracks'].each do |k, v| pid = v['Persistent ID'] rev[pid] = v end @plist['Tracks'].each do |k, v| track = rev[v['Persistent ID']] next unless track count = v['Play Count'].to_i p(v) unless v['Location'] == track['Location'] old_count = track['Play Count'].to_i v['Play Count'] = count - old_count end end def report_play_time_by_song report_time(play_time(nil), 'song') end def report_play_count_by_song report_count(play_count(nil), 'song') end def report_play_time_by_artist report_time(play_time('Artist'), 'artist') end def report_play_time_by_album report_time(play_time('Album'), 'album') end def report_play_count_by_artist report_count(play_count('Artist'), 'artist') end def report_play_count_by_album report_count(play_count('Album'), 'album') end def play_time(key_name = 'Artist') make_bin(key_name) { |v| v['Total Time'].to_i * v['Play Count'].to_i } end def play_count(key_name = 'Artist') make_bin(key_name) { |v| v['Play Count'].to_i } end private class SongKey def initialize(track_id, value) @track_id = track_id @value = value end def hash @track_id.hash end def to_s [@value['Name'], @value['Artist']].join(' / ') end end def make_bin(key_name = 'Artist') @plist['Tracks'].inject({:all => 0}) { |bin, kv| v = kv[1] if key_name key = v[key_name] else key = SongKey.new(kv[0], kv[1]) end bin[key] ||= 0 value = yield(v) bin[key] += value bin[:all] += value bin }.sort_by { |kv| -1 * kv[1] } end def report_time(result, caption) printf("=== Play Time (%s) (total: %6.1fH)", caption, result[0][1] / 3600000.0) puts result[1, 20].each { |v| hour = v[1] / (3600000.0) printf("%6.1fH %s", hour, v[0]) puts } puts end def report_count(result, caption) printf("=== Play Count (%s) (total: %d)", caption, result[0][1]) puts result[1, 20].each { |v| printf(" %6d %s", v[1], v[0]) puts } puts end end def main(diff = nil) iml = ITunesMusicLibrary.new app = TopRateReporter.new(iml.load_plist) app.report_play_time_by_song app.report_play_time_by_artist app.report_play_time_by_album app.report_play_count_by_song app.report_play_count_by_artist app.report_play_count_by_album return unless diff a = iml.load_dump(File.expand_path(diff)) app.subtract_playcount(a) app.report_play_time_by_song app.report_play_time_by_artist app.report_play_time_by_album app.report_play_count_by_song app.report_play_count_by_artist app.report_play_count_by_album end main(ARGV.shift)