That Stupid Thing I Wrote the Other Day, part 2
Published 2007-03-16 @ 00:57
Tagged ruby, toys
This script sifts through your iTunes DB and tells you what you really like to listen to. It told me that my mental list of my top N and my actual listening habits disagree. It also illustrates that you don’t need to parse XML if it is well formed and it can be way faster than rexml and the other xml-overkill libraries.
- (2306 tot, 327.33 adj): Everything! (Disc 2) by Tones On Tail
- (2295 tot, 316.93 adj): Mezzanine by Massive Attack
- (2026 tot, 293.42 adj): Earth Sun Moon by Love & Rockets
- (1789 tot, 259.74 adj): Kakusei by DJ Krush
- (1700 tot, 241.82 adj): Staring At The Sea: The Singles 1979-1985 by The Cure
- (1753 tot, 240.61 adj): 100th Window by Massive Attack
- (1307 tot, 224.80 adj): Nouvelle Vague by Nouvelle Vague
- (1458 tot, 207.87 adj): Substance 1987 (Disc 1) by New Order
- (1342 tot, 194.84 adj): Meiso by DJ Krush
- (1351 tot, 192.62 adj): Express by Love & Rockets
See the code after the cut. require ‘time’
class Album < Array
def age
map { |track| track.age }.max
end
def score
total / Math.log(age)
end
def total
inject(0.0) do |total_score, track|
total_score + track.score
end
end
def self.parse(file)
today = Time.now
d = {}
library = Hash.new { |h,k| h[k] = Album.new }
IO.foreach(File.expand_path(file)) do |line|
if line =~ /<key>(Name|Artist|Album|Date Added|Play Count|Rating)<\/key><.*?>(.*)<\/.*?>/ then
key = $1.downcase.split.first
val = $2
d[key.intern] = val
if d.size == 6 then
date = d[:date].sub(/T.*/, '')
key = "#{d[:album]} by #{d[:artist]}"
age = ((today - Time.parse(date)) / 86400.0).to_i
library[key] << Track.new(age, d[:play].to_i, d[:rating].to_i / 20)
d.clear
end
end
end
library
end
end
Track = Struct.new(:age, :count, :rating)
class Track
def score
rating * count.to_f
end
end
max = (ARGV.shift || 10).to_i
file = ARGV.shift || "~/Music/iTunes/iTunes Music Library.xml"
library = Album.parse(file)
top = library.sort_by { |h,k| -k.score }[0...max]
top.each_with_index do |(artist_album, album), c|
puts "%-3d = (%4d tot, %5.2f adj): %s" % [c+1, album.total, album.score, artist_album,]
album.each do |t|
puts " #{t.age} days old, #{t.count} count, #{t.rating} rating = #{t.score}"
end if $DEBUG
end