#!/bin/env ruby # # Graficador de artistas escuchados, registrados por last.fm # Autor: Claudio Bustos # # Uso: # ruby lastfm.rb USUARIO [NUMERO DE ARTISTAS] # # Ejemplo # ruby JR 25 # require 'sqlite3' require 'net/http' require 'uri' require 'rexml/document' require 'gruff' module Scrobbler URL="http://ws.audioscrobbler.com/1.0" CONFIG_DIR=File.expand_path("~/.config/php.apsique.com/lastfm") CONFIG_FILE=CONFIG_DIR+"/lastfm.db" class WeeklyChart attr_accessor :from, :to, :artists_count, :albums_count, :tracks_count def initialize(from, to) @from=from @to=to @artists_count={} @albums_count={} @tracks_count={} end def set_track_count (name, value) @tracks_count[name]=value end def set_album_count (name, value) @albums_count[name]=value end end class Artist attr_accessor :name, :mbid def initialize(name, mbid) @name=name @mbid=mbid end end class User attr_reader :weeklycharts, :artists, :tracks, :albums, :user def initialize(user) @user=user @weeklycharts=Array.new @artists={} @tracks={} @albums={} install_db if !File.exists? Scrobbler::CONFIG_FILE @db = SQLite3::Database.new(Scrobbler::CONFIG_FILE) @db.results_as_hash=true if(@db.get_first_value("select count(*) from user WHERE id=?" ,@user)=="0") @db.execute("INSERT INTO user (id,last_access) VALUES (?,?)",@user,Time.now().to_i) end set_weeklycharts set_weeklycharts_artists set_weeklycharts_tracks set_weeklycharts_albums end def set_track (name, value) @tracks[name]=value end def set_album (name, value) @albums[name]=value end def set_common(el) plural=el+"s" @weeklycharts.each do |weeklychart| puts "Revisando...:" if(@db.get_first_value("select count(*) from weeklychart#{el} WHERE user_id=? and date_from=? and date_to=?" ,@user,weeklychart.from, weeklychart.to)=="0") @db.transaction do |db| puts "download weekly #{el} chart: "+Time.at(weeklychart.from.to_i).to_s xml = Net::HTTP.get( URI.parse( Scrobbler::URL+"/user/#{@user}/weekly#{el}chart.xml?from=#{weeklychart.from}&to=#{weeklychart.to}")) doc=::REXML::Document.new(xml).root doc.each_element {|row| artist=row.elements['artist'].text artist_mbid=row.elements['artist'].attributes['mbid'] name=row.elements['name'].text chartposition=row.elements['chartposition'].text playcount=row.elements['playcount'].text url=row.elements['url'].text #puts sprintf("Insertando %s,%s,%s,%s,%s,%s,%s,%s,%s",@user, weeklychart.from, weeklychart.to, artist, artist_mbid, name, chartposition, playcount, url) db.execute("INSERT INTO weeklychart#{el} (user_id, date_from, date_to, artist_name, artist_mbid, name, chartposition, playcount, url) VALUES (?,?,?,?,?,?,?,?,?)", @user, weeklychart.from, weeklychart.to, artist, artist_mbid, name, chartposition, playcount, url) } end sleep 1 end func_count="#{plural}_count" @db.execute("select * from weeklychart#{el} WHERE user_id=? and date_from=? and date_to=?" ,@user,weeklychart.from, weeklychart.to) do |row| name=row['artist_name']+" - "+row['name'] weeklychart.send("set_"+el+"_count", name, row['playcount'].to_i) end end # define tracks @db.execute("select artist_name as artist, name, SUM(playcount) as n from weeklychart#{el} WHERE user_id=? GROUP BY artist_name, name" ,@user) do |row| name = row['artist']+' - '+row['name'] self.send("set_"+el,name,row['n'].to_i) end end def set_weeklycharts_albums set_common("album") end def set_weeklycharts_tracks set_common("track") end def set_weeklycharts_artists @weeklycharts.each do |weeklychart| if(@db.get_first_value("select count(*) from weeklychartartist WHERE user_id=? and date_from=? and date_to=?" ,@user,weeklychart.from, weeklychart.to)=="0") @db.transaction do |db| puts "download weekly artist chart artist: "+Time.at(weeklychart.from.to_i).to_s xml = Net::HTTP.get( URI.parse( Scrobbler::URL+"/user/#{@user}/weeklyartistchart.xml?from=#{weeklychart.from}&to=#{weeklychart.to}")) doc=::REXML::Document.new(xml).root doc.each_element {|artist| #p artist name=artist.elements['name'].text mbid=artist.elements['mbid'].text chartposition=artist.elements['chartposition'].text playcount=artist.elements['playcount'].text url=artist.elements['url'].text db.execute("INSERT INTO weeklychartartist (user_id, date_from, date_to, artist_name, artist_mbid, chartposition, playcount, url) VALUES (?,?,?,?,?,?,?,?)", @user, weeklychart.from, weeklychart.to, name, mbid, chartposition, playcount, url) } end sleep 1 end @db.execute("select * from weeklychartartist WHERE user_id=? and date_from=? and date_to=?" ,@user,weeklychart.from, weeklychart.to) do |row| weeklychart.artists_count[row['artist_name']]=row['playcount'].to_i end end # define artists @db.execute("select artist_name as artist, SUM(playcount) as n from weeklychartartist WHERE user_id=? GROUP BY artist_name" ,@user) do |row| @artists[row['artist']]=row['n'].to_i end end def set_weeklycharts last_retrieve=@db.get_first_value("select MAX(date_to) from weeklychart WHERE user_id=?" ,@user).to_i # p last_retrieve if((Time.now()-last_retrieve).to_i>604800) #descargar puts "download last weeklychart" puts Scrobbler::URL+"/user/#{@user}/weeklychartlist.xml" xml = Net::HTTP.get( URI.parse( Scrobbler::URL+"/user/#{@user}/weeklychartlist.xml")) #xml = File.new("/home/cdx/weeklychartlist.xml").read doc=::REXML::Document.new(xml).root doc.each_element {|chart| from=chart.attributes['from'] to=chart.attributes['to'] if(@db.get_first_value("select count(*) from weeklychart WHERE user_id=? AND date_from=? and date_to=?" ,@user,from,to)=="0") @db.execute("INSERT INTO weeklychart (user_id, date_from, date_to) VALUES (?,?,?)",@user,from,to) end } end @db.execute( "select * from weeklychart WHERE user_id=? ORDER BY date_from", @user) do |row| @weeklycharts.push(WeeklyChart.new(row['date_from'], row['date_to'])) end end def install_db require 'ftools' File.mkpath Scrobbler::CONFIG_DIR db = SQLite3::Database.new(Scrobbler::CONFIG_FILE) db.execute("CREATE TABLE user (id TEXT, last_access INTEGER, PRIMARY KEY(id))") db.execute("CREATE TABLE weeklychart (user_id TEXT, date_from INTEGER, date_to INTEGER, PRIMARY KEY (user_id,date_from,date_to))") db.execute("CREATE TABLE weeklychartartist (user_id TEXT, date_from INTEGER, date_to INTEGER, artist_name TEXT, artist_mbid TEXT, chartposition INTEGER, playcount INTEGER, url TEXT, PRIMARY KEY (user_id,date_from,date_to, artist_name))") db.execute("CREATE TABLE weeklycharttrack (user_id TEXT, date_from INTEGER, date_to INTEGER, artist_name TEXT, artist_mbid TEXT, name TEXT, chartposition INTEGER, playcount INTEGER, url TEXT, PRIMARY KEY (user_id,date_from,date_to, artist_name,name))") db.execute("CREATE TABLE weeklychartalbum (user_id TEXT, date_from INTEGER, date_to INTEGER, artist_name TEXT, artist_mbid TEXT, name TEXT, chartposition INTEGER, playcount INTEGER, url TEXT, PRIMARY KEY (user_id,date_from,date_to, artist_name,name))") db.execute("CREATE TABLE artist (name TEXT, mbid TEXT, PRIMARY KEY (name))") end def array_for(func, k) @weeklycharts.collect{|wc| (wc.send(func).has_key? k) ? wc.send(func)[k] : 0 } end def array_for_artist(artist) self.array_for(:artists_count,artist) end def array_for_track(track) self.array_for(:tracks_count,track) end def array_for_album(album) self.array_for(:albums_count, album) end end end module Gruff class StackedBarLegendRight < StackedBar def initialize_ivars super @legend_box_size=10 @right_margin=@columns*0.25 end def draw_legend return if @hide_legend @legend_labels = @data.collect {|item| item[DATA_LABEL_INDEX] } legend_square_width = @legend_box_size # small square with color of this item # May fix legend drawing problem at small sizes @d.font = @font if @font @d.pointsize = @legend_font_size max_text=0 @legend_labels.each{|l| max_text=l if l.size>max_text.size } metrics = @d.get_type_metrics(@base_image, max_text) legend_text_width = metrics.width legend_width = legend_text_width + legend_square_width * 2.7 current_x_offset = @raw_columns - @right_margin +legend_square_width *2.7 current_y_offset = @hide_title ? @top_margin + LEGEND_MARGIN : @top_margin + TITLE_MARGIN + @title_caps_height + LEGEND_MARGIN debug { @d.line 0.0, current_y_offset, @raw_columns, current_y_offset } @legend_labels.each_with_index do |legend_label, index| # Draw label @d.fill = @font_color @d.font = @font if @font metrics = @d.get_type_metrics(@base_image, legend_label.to_s) if(metrics.width*@scale<@right_margin) @d.pointsize = scale_fontsize(@legend_font_size) else @d.pointsize = scale_fontsize(@legend_font_size/1.5) end @d.stroke('transparent') @d.font_weight = NormalWeight @d.gravity = WestGravity @d = @d.annotate_scaled( @base_image, @raw_columns, 1.0, current_x_offset + (legend_square_width * 1.7), current_y_offset, legend_label.to_s, @scale) # Now draw box with color of this dataset @d = @d.stroke('transparent') @d = @d.fill @data[index][DATA_COLOR_INDEX] @d = @d.rectangle(current_x_offset, current_y_offset - legend_square_width / 2.0, current_x_offset + legend_square_width, current_y_offset + legend_square_width / 2.0) @d.pointsize = @legend_font_size current_string_offset = metrics.height current_y_offset += current_string_offset end @color_index = 0 end end end class Graficador def initialize(user,num) @user=user @num=num end def prepare g = Gruff::StackedBarLegendRight.new(1024) g.legend_font_size=8 g.legend_box_size=10 labels={} alabels=@user.weeklycharts.collect {|wc| Time.at(wc.from.to_i).strftime("%Y") } ant='' i=0 alabels.each {|l| labels[i]=l if l!=ant i+=1 ant=l } g.labels=labels colors=%w(red blue green) 10.downto(3) {|i| colors.push(sprintf("#%x",i*25*65536+i*25*256+i*25)) } 10.downto(1) {|i| colors.push(sprintf("#00%x",i*25*256+i*25)) } 10.downto(1) {|i| colors.push(sprintf("#%x",i*25*65536+0+i*25)) } 10.downto(1) {|i| colors.push(sprintf("#%x",i*25*65536+i*25*256+0)) } 10.downto(1) {|i| colors.push(sprintf("#%x",250*65536+((24-i)*25*256)+((24-i)*25))) } g.colors=colors @g=g end def graph_artists prepare @g.title="Artists on last.fm for user #{@user.user}" i=0 @user.artists.sort{|p1,p2| -(p1[1]<=>p2[1])}.each do |ar| break if i>@num artist_name=ar[0] count=ar[1] @g.data(artist_name, @user.array_for_artist(artist_name)) i+=1 end @g.write(@user.user+"_artists.png") end def graph_tracks prepare @g.title="Tracks on last.fm for user #{@user.user}" i=0 @user.tracks.sort{|p1,p2| -(p1[1]<=>p2[1])}.each do |ar| break if i>@num artist_name=ar[0] count=ar[1] @g.data(artist_name, @user.array_for_track(artist_name)) i+=1 end @g.write(@user.user+"_tracks.png") end def graph_albums prepare @g.title="Albums on last.fm for user #{@user.user}" i=0 @user.albums.sort{|p1,p2| -(p1[1]<=>p2[1])}.each do |ar| break if i>@num artist_name=ar[0] count=ar[1] @g.data(artist_name, @user.array_for_album(artist_name)) i+=1 end @g.write(@user.user+"_albums.png") end end if ARGV.size>0 a=Scrobbler::User.new(ARGV[0]) #p a.weeklycharts n=ARGV[1].nil? ? 25 : ARGV[1].to_i g=Graficador.new(a,n) g.graph_albums g.graph_tracks g.graph_artists end