sandro-metamuse 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +6 -0
- data/MIT_LICENSE +20 -0
- data/README.rdoc +7 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/lib/array_ext.rb +5 -0
- data/lib/arrayish.rb +23 -0
- data/lib/httparty_ext.rb +16 -0
- data/lib/metamuse.rb +71 -0
- data/lib/metamuse/album.rb +19 -0
- data/lib/metamuse/artist.rb +43 -0
- data/lib/metamuse/association.rb +48 -0
- data/lib/metamuse/collection.rb +25 -0
- data/lib/metamuse/services.rb +16 -0
- data/lib/metamuse/services/echonest.rb +26 -0
- data/lib/metamuse/services/freebase.rb +43 -0
- data/lib/metamuse/services/lastfm.rb +27 -0
- data/lib/metamuse/services/lastfm/image.rb +41 -0
- data/lib/metamuse/services/music_brainz.rb +27 -0
- data/lib/metamuse/track.rb +13 -0
- data/lib/object_ext.rb +11 -0
- data/metamuse.gemspec +89 -0
- data/script/console +8 -0
- data/spec/fake_object.rb +38 -0
- data/spec/fake_object_spec.rb +66 -0
- data/spec/metamuse/album_spec.rb +4 -0
- data/spec/metamuse/artist_spec.rb +99 -0
- data/spec/metamuse/association_spec.rb +74 -0
- data/spec/metamuse/collection_spec.rb +4 -0
- data/spec/metamuse/services/echonest_spec.rb +28 -0
- data/spec/metamuse/services/freebase_spec.rb +24 -0
- data/spec/metamuse/services/lastfm/image_spec.rb +71 -0
- data/spec/metamuse/services/lastfm_spec.rb +30 -0
- data/spec/metamuse/services/music_brainz_spec.rb +12 -0
- data/spec/metamuse_spec.rb +21 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/web_fixtures/GET_developer.echonest.com-api-search_artists_17a9da1.fixture +36 -0
- data/spec/web_fixtures/GET_developer.echonest.com-api-search_artists_350c352.fixture +36 -0
- data/spec/web_fixtures/GET_musicbrainz.org-ws-1-release-bb32aa1d-f37b-4134-8c0e-b43b7a6dab85_b976ba7.fixture +26 -0
- data/spec/web_fixtures/GET_www.freebase.com-api-service-mqlread_60ee2bd.fixture +938 -0
- data/spec/web_fixtures/GET_www.freebase.com-api-service-mqlread_b8b2565.fixture +107 -0
- metadata +106 -0
data/.document
ADDED
data/.gitignore
ADDED
data/MIT_LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Sandro Turriate
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "metamuse"
|
8
|
+
gem.summary = %Q{Search for music metadata}
|
9
|
+
gem.email = "sandro.turriate@gmail.com"
|
10
|
+
gem.homepage = "http://github.com/sandro/metamuse"
|
11
|
+
gem.authors = ["Sandro Turriate"]
|
12
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
13
|
+
end
|
14
|
+
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'spec/rake/spectask'
|
20
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
21
|
+
spec.libs << 'lib' << 'spec'
|
22
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
23
|
+
end
|
24
|
+
|
25
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
26
|
+
spec.libs << 'lib' << 'spec'
|
27
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
28
|
+
spec.rcov = true
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
task :default => :spec
|
33
|
+
|
34
|
+
require 'rake/rdoctask'
|
35
|
+
Rake::RDocTask.new do |rdoc|
|
36
|
+
if File.exist?('VERSION.yml')
|
37
|
+
config = YAML.load(File.read('VERSION.yml'))
|
38
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
39
|
+
else
|
40
|
+
version = ""
|
41
|
+
end
|
42
|
+
|
43
|
+
rdoc.rdoc_dir = 'rdoc'
|
44
|
+
rdoc.title = "metamuse #{version}"
|
45
|
+
rdoc.rdoc_files.include('README*')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
48
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/array_ext.rb
ADDED
data/lib/arrayish.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Arrayish
|
2
|
+
# List of array methods (that are not in +Object+) that need to be
|
3
|
+
# delegated to +collection+.
|
4
|
+
ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map { |n| n.to_s }
|
5
|
+
|
6
|
+
# List of additional methods that must be delegated to +collection+.
|
7
|
+
MUST_DEFINE = %w[to_a to_ary inspect]
|
8
|
+
|
9
|
+
(ARRAY_METHODS + MUST_DEFINE).uniq.each do |method|
|
10
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
11
|
+
def #{method}(*args, &block) # def each(*args, &block)
|
12
|
+
collection.send(:#{method}, *args, &block) # collection.send(:each, *args, &block)
|
13
|
+
end # end
|
14
|
+
RUBY
|
15
|
+
end
|
16
|
+
|
17
|
+
# Lie about our class. Borrowed from Rake::FileList
|
18
|
+
# Note: Does not work for case equality (<tt>===</tt>)
|
19
|
+
def is_a?(klass)
|
20
|
+
klass == Array || super(klass)
|
21
|
+
end
|
22
|
+
alias kind_of? is_a?
|
23
|
+
end
|
data/lib/httparty_ext.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
HTTParty::Request.class_eval do
|
2
|
+
class << self
|
3
|
+
attr_accessor :debug
|
4
|
+
end
|
5
|
+
|
6
|
+
def perform_with_debug
|
7
|
+
if self.class.debug
|
8
|
+
puts "HTTParty making #{http_method::METHOD} request to:"
|
9
|
+
puts uri
|
10
|
+
end
|
11
|
+
perform_without_debug
|
12
|
+
end
|
13
|
+
|
14
|
+
alias_method :perform_without_debug, :perform
|
15
|
+
alias_method :perform, :perform_with_debug
|
16
|
+
end
|
data/lib/metamuse.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
begin; require 'rubygems'; rescue LoadError; end
|
4
|
+
require 'cgi'
|
5
|
+
require 'httparty'
|
6
|
+
|
7
|
+
require 'httparty_ext'
|
8
|
+
require 'object_ext'
|
9
|
+
require 'array_ext'
|
10
|
+
require 'arrayish'
|
11
|
+
|
12
|
+
require 'metamuse/collection'
|
13
|
+
require 'metamuse/association'
|
14
|
+
require 'metamuse/services'
|
15
|
+
require 'metamuse/services/echonest'
|
16
|
+
require 'metamuse/services/freebase'
|
17
|
+
require 'metamuse/services/music_brainz'
|
18
|
+
require 'metamuse/services/lastfm'
|
19
|
+
require 'metamuse/services/lastfm/image'
|
20
|
+
|
21
|
+
module Metamuse
|
22
|
+
autoload :Artist, 'metamuse/artist'
|
23
|
+
autoload :Album, 'metamuse/album'
|
24
|
+
autoload :Track, 'metamuse/track'
|
25
|
+
|
26
|
+
class << self
|
27
|
+
attr_reader :echonest_api_key, :lastfm_api_key
|
28
|
+
|
29
|
+
def echonest_api_key=(key)
|
30
|
+
@echonest_api_key = key
|
31
|
+
Services::Echonest.api_key = echonest_api_key
|
32
|
+
end
|
33
|
+
|
34
|
+
def lastfm_api_key=(key)
|
35
|
+
@lastfm_api_key = key
|
36
|
+
Services::Lastfm.api_key = lastfm_api_key
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_artist(name)
|
40
|
+
Services::Echonest.artist(name)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def api_keys
|
46
|
+
if File.exists?(name=".api_keys.yml")
|
47
|
+
YAML.load_file name
|
48
|
+
else
|
49
|
+
{}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def register_api_keys
|
54
|
+
api_keys.each do |service, value|
|
55
|
+
send("#{service}_api_key=", value)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module InstanceInitialize
|
61
|
+
def initialize(attrs={})
|
62
|
+
attrs.each do |k,v|
|
63
|
+
if self.class.has_many_set.values.include?(k)
|
64
|
+
send "#{k}=", v
|
65
|
+
else
|
66
|
+
instance_variable_set "@#{k}", v
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Metamuse
|
2
|
+
class Album
|
3
|
+
include InstanceInitialize
|
4
|
+
extend Association
|
5
|
+
has_many :tracks, Track
|
6
|
+
|
7
|
+
attr_accessor :name, :release_date, :freebase_id, :mbid, :rank, :images
|
8
|
+
|
9
|
+
def <=>(other)
|
10
|
+
if rank
|
11
|
+
rank <=> other.rank
|
12
|
+
else
|
13
|
+
to_s <=> other.to_s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Metamuse
|
2
|
+
class Artist
|
3
|
+
include InstanceInitialize
|
4
|
+
extend Association
|
5
|
+
has_many :albums, Album
|
6
|
+
|
7
|
+
attr_accessor :name, :echonest_id, :freebase_guid, :mbid
|
8
|
+
|
9
|
+
def self.build(name)
|
10
|
+
artist = Services::Freebase.artist(name)
|
11
|
+
artist.enhance_albums!
|
12
|
+
artist
|
13
|
+
end
|
14
|
+
|
15
|
+
def tracks
|
16
|
+
@tracks ||= albums.map{|a| a.tracks}.flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
def enhance_albums!
|
20
|
+
return albums if albums.empty?
|
21
|
+
self.albums = albums.map do |album|
|
22
|
+
lastfm_album = lastfm_albums.detect{|a| a.name == album.name}
|
23
|
+
if lastfm_album
|
24
|
+
album.rank = lastfm_album.rank
|
25
|
+
album.mbid = lastfm_album.mbid
|
26
|
+
album.images = lastfm_album.images
|
27
|
+
end
|
28
|
+
album
|
29
|
+
end.sort
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch_albums_and_tracks!
|
33
|
+
artist = Services::Freebase.artist(name)
|
34
|
+
self.albums = artist.albums
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def lastfm_albums
|
40
|
+
@lastfm_albums ||= Services::Lastfm.top_albums(name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Metamuse
|
2
|
+
module Association
|
3
|
+
def self.extended(base)
|
4
|
+
class << base
|
5
|
+
attr_accessor :has_many_set, :belongs_to_set
|
6
|
+
end
|
7
|
+
base.belongs_to_set = base.has_many_set = {}
|
8
|
+
base.send :include, InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
def has_many(accessor, klass)
|
12
|
+
has_many_set.merge! klass => accessor
|
13
|
+
|
14
|
+
define_method(accessor) do
|
15
|
+
ivar = "@#{accessor}"
|
16
|
+
if val = instance_variable_get(ivar)
|
17
|
+
val
|
18
|
+
else
|
19
|
+
instance_variable_set(ivar, Collection.new(self, klass))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
define_method("#{accessor}=") do |items|
|
24
|
+
send(accessor).clear
|
25
|
+
send(accessor) << items
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def belongs_to(accessor, klass)
|
30
|
+
belongs_to_set.merge! klass => accessor
|
31
|
+
|
32
|
+
define_method(accessor) do
|
33
|
+
instance_variable_get(:"@#{accessor}")
|
34
|
+
end
|
35
|
+
|
36
|
+
define_method(:"#{accessor}=") do |parent|
|
37
|
+
instance_variable_set(:"@#{accessor}", parent)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module InstanceMethods
|
42
|
+
def belongs(owner)
|
43
|
+
accessor = self.class.belongs_to_set[owner.class]
|
44
|
+
send("#{accessor}=", owner) if accessor
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Metamuse
|
2
|
+
class Collection
|
3
|
+
include ::Arrayish
|
4
|
+
attr_reader :collection, :collection_class, :owner
|
5
|
+
|
6
|
+
def initialize(owner, collection_class)
|
7
|
+
@owner = owner
|
8
|
+
@collection_class = collection_class
|
9
|
+
@collection = []
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method :append, :<<
|
13
|
+
alias_method :push, :<<
|
14
|
+
alias_method :concat, :<<
|
15
|
+
|
16
|
+
def <<(items)
|
17
|
+
Array.insist(items).each do |item|
|
18
|
+
item = collection_class.new(item) unless collection_class === item
|
19
|
+
append item
|
20
|
+
item.belongs owner
|
21
|
+
end
|
22
|
+
self
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Metamuse
|
2
|
+
module Services
|
3
|
+
class Echonest
|
4
|
+
extend Service
|
5
|
+
include HTTParty
|
6
|
+
base_uri 'http://developer.echonest.com/api'
|
7
|
+
default_params :version => 3
|
8
|
+
format :xml
|
9
|
+
has_api_key
|
10
|
+
|
11
|
+
def self.artist(name)
|
12
|
+
build_artist get('/search_artists', :query => {:query => name})
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def self.build_artist(response)
|
18
|
+
if artists = response['response']['artists']
|
19
|
+
artist = artists['artist']
|
20
|
+
artist = artist.first if artist.is_a?(Array)
|
21
|
+
Metamuse::Artist.new :name => artist['name'], :echonest_id => artist['id']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Metamuse
|
2
|
+
module Services
|
3
|
+
class Freebase
|
4
|
+
include HTTParty
|
5
|
+
base_uri 'http://www.freebase.com'
|
6
|
+
format :json
|
7
|
+
|
8
|
+
def self.artist(name)
|
9
|
+
envelop = %({"query": #{album_and_track_mql(name)}})
|
10
|
+
response = get("/api/service/mqlread", :query => {:query => envelop})
|
11
|
+
build_artist response
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.artist_search(name)
|
15
|
+
response = get("/api/service/search", :query => album_and_track_search_query(name))
|
16
|
+
build_artist response
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def self.album_and_track_mql(name)
|
22
|
+
%({"album": [{ "id": null, "name": null, "track": [{ "name": null, "index": null, "sort": "index" }], "release_date": null, "release_type": null, "sort": "-release_date", "/common/topic/image": [{ "id": null, "optional": true, "limit": 3 }] }], "id": null, "name": "#{name}", "type": "/music/artist"})
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.album_and_track_search_query(name)
|
26
|
+
{:query => name, :type => "/music/artist", :limit => 1, :mql_output => album_and_track_mql(name)}
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.build_artist(response)
|
30
|
+
result = response['result']
|
31
|
+
artist = Metamuse::Artist.new :name => CGI.unescapeHTML(result['name']), :freebase_guid => result['guid']
|
32
|
+
albums = Array.insist result['album']
|
33
|
+
albums.each do |album|
|
34
|
+
data = {:name => CGI.unescapeHTML(album['name']), :release_date => album['release_date'], :freebase_id => album['freebase_id']}
|
35
|
+
tracks = album['track']
|
36
|
+
data[:tracks] = tracks.map {|t| t.update 'name' => CGI.unescapeHTML(t['name'])}
|
37
|
+
artist.albums << data
|
38
|
+
end
|
39
|
+
artist
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|