#!/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 def initialize(from, to) @from=from @to=to @artists_count={} end end class Artist attr_accessor :name, :mbid def initialize(name, mbid) @name=name @mbid=mbid end end class User attr_reader :weeklycharts, :artists, :user def initialize(user) @user=user @weeklycharts=Array.new @artists={} 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 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 weeklychartartist: "+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 artist (name TEXT, mbid TEXT, PRIMARY KEY (name))") end def array_for_artist(artist) @weeklycharts.collect{|wc| (wc.artists_count.has_key? artist) ? wc.artists_count[artist] : 0 } 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 def graficar(a,n_artists=25) g = Gruff::StackedBarLegendRight.new(1024) g.title="Weekly chart on last.fm for user #{a.user}" g.legend_font_size=8 g.legend_box_size=10 labels={} alabels=a.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 i=0 a.artists.sort{|p1,p2| -(p1[1]<=>p2[1])}.each do |ar| break if i>n_artists artist_name=ar[0] count=ar[1] g.data(artist_name,a.array_for_artist(artist_name)) i+=1 end g.write(a.user+".png") end if ARGV.size>0 a=Scrobbler::User.new(ARGV[0]) #p a.weeklycharts n=ARGV[1].nil? ? 25 : ARGV[1].to_i graficar(a, n) end