🕷 zenspider.com

by ryan davis



sitemap
Looking for the Ruby Quickref?

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.

  1. (2306 tot, 327.33 adj): Everything! (Disc 2) by Tones On Tail
  2. (2295 tot, 316.93 adj): Mezzanine by Massive Attack
  3. (2026 tot, 293.42 adj): Earth Sun Moon by Love & Rockets
  4. (1789 tot, 259.74 adj): Kakusei by DJ Krush
  5. (1700 tot, 241.82 adj): Staring At The Sea: The Singles 1979-1985 by The Cure
  6. (1753 tot, 240.61 adj): 100th Window by Massive Attack
  7. (1307 tot, 224.80 adj): Nouvelle Vague by Nouvelle Vague
  8. (1458 tot, 207.87 adj): Substance 1987 (Disc 1) by New Order
  9. (1342 tot, 194.84 adj): Meiso by DJ Krush
  10. (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