# based on something zenspider wrote. I hacked to add:
# 1. tracks that don't have ratings
# 2. take the number of tracks into account for the album's score (note to self: should instead take into account the length)
# 3. take an 'age' argument to only show stuff added in the last x weeks, f.e.: itunes-popular 12 52 shows the top twelve albums from the last fifty-two weeks

#!/usr/bin/env ruby
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 / size
end
def self.parse(file, age)
today = Time.now
d = {}
library = Hash.new { |h,k| h[k] = Album.new }

IO.foreach(File.expand_path(file)) do |line|
if line =~ /<dict/
d.clear
elsif line =~ /<key>(Name|Artist|Album|Date Added|Play Count)<\/key><.*?>(.*)<\/.*?>/ then
key = $1.downcase.split.first
val = $2
val = val.sub(/T.*/,'') if key == :date
d[key.intern] = val
elsif line =~ /<\/dict>/ && d.size == 5
# if we don't have all the keys, don't bother with this track or if it's some random /dict
key = "#{d[:album]} by #{d[:artist]}"
track_age = ((today - Time.parse(d[:date])) / 86400.0).to_i
next if age && ((track_age / 7) > age.to_i)
library[key] << Track.new(track_age, d[:play].to_i)
end
end
library
end
end

Track = Struct.new(:age, :count)
class Track
def score
# rating * count.to_f
count.to_f
end
end

max = (ARGV.shift || 10).to_i
age = ARGV.shift || nil # in weeks
file = ARGV.shift || "~/Music/iTunes/iTunes Music Library.xml"
library = Album.parse(file, age)

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