require 'rubygems' require 'scrobbler' require 'active_support' $DEBUG = 1 ## ## HACK IT UP! ## module Scrobbler class Base class << self # last.fm requires us to wait for at least one second between requests, so let's do that. def wait sleep( 1 ) end end end class Artist class << self alias :old_new_from_xml :new_from_xml def new_from_xml( xml, doc = nil ) a = old_new_from_xml( xml, doc = nil ) a.playcount = a.playcount.to_i a.chartposition = a.chartposition.to_i a end end end class User alias :old_weekly_artist_chart :weekly_artist_chart def weekly_chart_list self.class.wait doc = self.class.fetch_and_parse( "#{api_path}/weeklychartlist.xml" ) (doc/:chart).collect{ |el| Week.new( el[ :from ], el[ :to ], self ) } end def weekly_artist_chart( from = nil, to = nil ) self.class.wait old_weekly_artist_chart( from ? from.to_i : nil, to ? to.to_i : nil ) end end end ## ## DE-FINE ## class Week attr_reader :from, :to, :artists def initialize( from, to, user ) @from = from @to = to get_artist_data( user ) end def get_artist_data( user ) filename_for_data = "data.#{user.username}.#{from.to_i}.dmp" begin if not File.exists?( filename_for_data ) File.open( filename_for_data, "w" ) do |f| puts "Saving data from #{filename_for_data}" if $DEBUG f << Marshal.dump( @artists = user.weekly_artist_chart( @from, @to ) ) end else File.open( filename_for_data, "r" ) do |f| puts "Loading data from #{filename_for_data}" if $DEBUG @artists = Marshal.load( f ) end end rescue EOFError => e # Catch data files which we interrupted the creation of. File.delete( filename_for_data ) retry end end def total_plays @total_plays ||= @artists.inject( 0 ) { |sum, artist| sum += artist.playcount } end end ## ## GET THE DATA! ## user = Scrobbler::User.new( "carldr" ) data = user.weekly_chart_list puts data.size # data.delete_if{ |w| w.total_plays < 10 } puts data.size unique_artists_and_total_plays = [] data.sort{ |a,b| a.from <=> b.from }.each do |week| week.artists.sort{ |a,b| a.chartposition <=> b.chartposition }.each do |artist| if not unique_artists_and_total_plays.select{ |a| a[ :name ] == artist.name }.size > 0 unique_artists_and_total_plays << { :name => artist.name, :plays => artist.playcount } else unique_artists_and_total_plays.select{ |a| a[ :name ] == artist.name }.first[ :plays ] += artist.playcount end end end unique_artists_and_total_plays.delete_if{ |a| a[ :plays ] < 10 } puts "Number of unique artists : #{unique_artists_and_total_plays.size}" puts unique_artists_and_total_plays.inspect unique_artist_names = unique_artists_and_total_plays.collect{ |ua| ua[ :name ] } sums = Hash.new( 0 ) unique_artists_and_total_plays.each do |ua| data.each do |week| sums[ week ] += week.artists.select{ |a| a.name == ua[ :name ] }.inject( 0 ) { |sum, a| sum += a.playcount } end end most_plays_in_a_week = sums.sort{ |a,b| b[1] <=> a[1] }.first[1] puts "Most plays in a week of good artists : #{most_plays_in_a_week}" puts puts puts christmas_tree = [] ua = unique_artists_and_total_plays.dup while ua.size > 0 christmas_tree.push( ua.shift ) christmas_tree.unshift( ua.shift ) if ua.size > 0 end puts christmas_tree.inspect ## ## GRAPH IT UP! ## WIDTH = 10000.0 HEIGHT = 1600.0 File.open( "#{user.username}.svg", "w" ) do |f| f.write <<-EOS EOS text_data = [] unique_artists_and_total_plays.reverse.each do |ua| artistno = unique_artists_and_total_plays.index( ua ) colour_off = artistno * ( 255.0 / unique_artist_names.size ) week_data = [] max_week = nil max_week_size = 0 max_week_index = 0 data.each_with_index do |week, weekno| amount_under = 0 this_size = 0 unique_artists_and_total_plays.each do |all_ua| artist = week.artists.select{ |a| a.name == all_ua[ :name ] }.first if all_ua[ :name ] == ua[ :name ] this_size = artist ? artist.playcount : 0 break else amount_under += artist ? artist.playcount : 0 end end week_data << { :total_plays => week.total_plays, :under => amount_under, :size => this_size, :total_size => week.artists.select{ |a| unique_artist_names.include?( a.name ) }.inject( 0 ) { |sum, a| sum += a.playcount } } if this_size > max_week_size max_week_size = this_size max_week = week_data.last max_week_index = weekno end end puts ua[ :name ] puts week_data.inspect text_data << { :ua => ua, :week_data => max_week.dup, :weekno => max_week_index } colour = "#" + ( 255 - colour_off ).to_i.to_s(16).rjust(2, "0") + ( ( 255 - colour_off ) / 3 ).to_i.to_s(16).rjust(2, "0") + colour_off.to_i.to_s(16).rjust(2, "0") f << "\n" end text_data.each do |td| next if td[ :weekno ] == data.size - 1 or td[ :weekno ] == 0 # Lets not display band names in the first or last weeks. weekly_data = td[ :week_data ] x = ( WIDTH / data.size * td[ :weekno ] ).to_i y = ( HEIGHT / 2 / most_plays_in_a_week * ( weekly_data[ :under ] + weekly_data[ :size ] / 2 ) ).to_i yoff = ( HEIGHT / 2 ) + ( ( HEIGHT / 2 ) * weekly_data[ :total_plays ].to_f / most_plays_in_a_week ) yoff = 0 f << "#{td[ :ua ][ :name ]}" end f.write <<-EOS EOS end