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
@@ -0,0 +1,27 @@
|
|
1
|
+
module Metamuse
|
2
|
+
module Services
|
3
|
+
module Lastfm
|
4
|
+
extend Service
|
5
|
+
include HTTParty
|
6
|
+
base_uri 'http://ws.audioscrobbler.com/2.0/'
|
7
|
+
has_api_key
|
8
|
+
|
9
|
+
def self.top_albums(name)
|
10
|
+
build_albums get('', :query => {:method => "artist.gettopalbums", :artist => name})
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.artist(name)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def self.build_albums(response)
|
19
|
+
album_data = response['lfm']['topalbums']['album']
|
20
|
+
album_data.map do |data|
|
21
|
+
images = data['image'].map {|location| Image.new location}
|
22
|
+
Album.new :name => data['name'], :mbid => data['mbid'], :rank => data['rank'], :images => images
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Metamuse::Services::Lastfm::Image
|
2
|
+
attr_reader :location, :size
|
3
|
+
|
4
|
+
DIMENSIONS = {
|
5
|
+
:small => '34x34',
|
6
|
+
:medium => '64x64',
|
7
|
+
:large => '126x126',
|
8
|
+
:extra_large => '300x300'
|
9
|
+
}
|
10
|
+
|
11
|
+
def initialize(location)
|
12
|
+
@location = location
|
13
|
+
set_size
|
14
|
+
end
|
15
|
+
|
16
|
+
def dimensions
|
17
|
+
DIMENSIONS[size]
|
18
|
+
end
|
19
|
+
|
20
|
+
def height
|
21
|
+
dimensions.split("x").last
|
22
|
+
end
|
23
|
+
|
24
|
+
def width
|
25
|
+
dimensions.split("x").first
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def set_size
|
31
|
+
@size = DIMENSIONS.index(dimension_value_from_location)
|
32
|
+
end
|
33
|
+
|
34
|
+
def dimension_value_from_location
|
35
|
+
size_regexp = %r(/serve/(\d+).*/)
|
36
|
+
match = location.match size_regexp
|
37
|
+
if match && width = match.captures.first
|
38
|
+
"#{width}x#{width}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Metamuse
|
2
|
+
module Services
|
3
|
+
class MusicBrainz
|
4
|
+
include HTTParty
|
5
|
+
base_uri 'http://musicbrainz.org/ws/1'
|
6
|
+
default_params :type => :xml
|
7
|
+
|
8
|
+
def self.album(id)
|
9
|
+
build_album get("/release/#{id}/", :query => {:inc => :tracks})
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def self.build_album(response)
|
15
|
+
release = response['metadata']['release']
|
16
|
+
album_data = {:name => release['title'], :mbid => release['id']}
|
17
|
+
|
18
|
+
track_list = release['track_list']['track']
|
19
|
+
tracks = track_list.map do |track|
|
20
|
+
Track.new :name => track['title'], :index => track_list.index(track), :mbid => track['id']
|
21
|
+
end
|
22
|
+
|
23
|
+
Album.new album_data.merge(:tracks => tracks)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/object_ext.rb
ADDED
data/metamuse.gemspec
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{metamuse}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Sandro Turriate"]
|
9
|
+
s.date = %q{2009-07-19}
|
10
|
+
s.email = %q{sandro.turriate@gmail.com}
|
11
|
+
s.extra_rdoc_files = [
|
12
|
+
"README.rdoc"
|
13
|
+
]
|
14
|
+
s.files = [
|
15
|
+
".document",
|
16
|
+
".gitignore",
|
17
|
+
"MIT_LICENSE",
|
18
|
+
"README.rdoc",
|
19
|
+
"Rakefile",
|
20
|
+
"VERSION",
|
21
|
+
"lib/array_ext.rb",
|
22
|
+
"lib/arrayish.rb",
|
23
|
+
"lib/httparty_ext.rb",
|
24
|
+
"lib/metamuse.rb",
|
25
|
+
"lib/metamuse/album.rb",
|
26
|
+
"lib/metamuse/artist.rb",
|
27
|
+
"lib/metamuse/association.rb",
|
28
|
+
"lib/metamuse/collection.rb",
|
29
|
+
"lib/metamuse/services.rb",
|
30
|
+
"lib/metamuse/services/echonest.rb",
|
31
|
+
"lib/metamuse/services/freebase.rb",
|
32
|
+
"lib/metamuse/services/lastfm.rb",
|
33
|
+
"lib/metamuse/services/lastfm/image.rb",
|
34
|
+
"lib/metamuse/services/music_brainz.rb",
|
35
|
+
"lib/metamuse/track.rb",
|
36
|
+
"lib/object_ext.rb",
|
37
|
+
"metamuse.gemspec",
|
38
|
+
"script/console",
|
39
|
+
"spec/fake_object.rb",
|
40
|
+
"spec/fake_object_spec.rb",
|
41
|
+
"spec/metamuse/album_spec.rb",
|
42
|
+
"spec/metamuse/artist_spec.rb",
|
43
|
+
"spec/metamuse/association_spec.rb",
|
44
|
+
"spec/metamuse/collection_spec.rb",
|
45
|
+
"spec/metamuse/services/echonest_spec.rb",
|
46
|
+
"spec/metamuse/services/freebase_spec.rb",
|
47
|
+
"spec/metamuse/services/lastfm/image_spec.rb",
|
48
|
+
"spec/metamuse/services/lastfm_spec.rb",
|
49
|
+
"spec/metamuse/services/music_brainz_spec.rb",
|
50
|
+
"spec/metamuse_spec.rb",
|
51
|
+
"spec/spec_helper.rb",
|
52
|
+
"spec/web_fixtures/GET_developer.echonest.com-api-search_artists_17a9da1.fixture",
|
53
|
+
"spec/web_fixtures/GET_developer.echonest.com-api-search_artists_350c352.fixture",
|
54
|
+
"spec/web_fixtures/GET_musicbrainz.org-ws-1-release-bb32aa1d-f37b-4134-8c0e-b43b7a6dab85_b976ba7.fixture",
|
55
|
+
"spec/web_fixtures/GET_www.freebase.com-api-service-mqlread_60ee2bd.fixture",
|
56
|
+
"spec/web_fixtures/GET_www.freebase.com-api-service-mqlread_b8b2565.fixture"
|
57
|
+
]
|
58
|
+
s.has_rdoc = true
|
59
|
+
s.homepage = %q{http://github.com/sandro/metamuse}
|
60
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
61
|
+
s.require_paths = ["lib"]
|
62
|
+
s.rubygems_version = %q{1.3.1}
|
63
|
+
s.summary = %q{Search for music metadata}
|
64
|
+
s.test_files = [
|
65
|
+
"spec/fake_object.rb",
|
66
|
+
"spec/fake_object_spec.rb",
|
67
|
+
"spec/metamuse/album_spec.rb",
|
68
|
+
"spec/metamuse/artist_spec.rb",
|
69
|
+
"spec/metamuse/association_spec.rb",
|
70
|
+
"spec/metamuse/collection_spec.rb",
|
71
|
+
"spec/metamuse/services/echonest_spec.rb",
|
72
|
+
"spec/metamuse/services/freebase_spec.rb",
|
73
|
+
"spec/metamuse/services/lastfm/image_spec.rb",
|
74
|
+
"spec/metamuse/services/lastfm_spec.rb",
|
75
|
+
"spec/metamuse/services/music_brainz_spec.rb",
|
76
|
+
"spec/metamuse_spec.rb",
|
77
|
+
"spec/spec_helper.rb"
|
78
|
+
]
|
79
|
+
|
80
|
+
if s.respond_to? :specification_version then
|
81
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
82
|
+
s.specification_version = 2
|
83
|
+
|
84
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
85
|
+
else
|
86
|
+
end
|
87
|
+
else
|
88
|
+
end
|
89
|
+
end
|
data/script/console
ADDED
data/spec/fake_object.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module FakeObject
|
2
|
+
def fake(*args)
|
3
|
+
Fake.new *args
|
4
|
+
end
|
5
|
+
|
6
|
+
class Fake
|
7
|
+
attr_reader :name, :strategy, :expectations
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
@strategy = extract_strategy(args.last) || :stub
|
11
|
+
setup args
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup(args)
|
15
|
+
case arg = args.shift
|
16
|
+
when Hash
|
17
|
+
set_expectations arg
|
18
|
+
when String
|
19
|
+
@name = arg
|
20
|
+
setup args
|
21
|
+
when NilClass
|
22
|
+
else
|
23
|
+
raise ArgumentError, "Hash excepted, got: #{args.inspect}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_expectations(expectations)
|
28
|
+
expectations.each do |method_name, return_value|
|
29
|
+
::RR.send(strategy, self).__send__(method_name).returns(return_value) # stub(object).method.returns(value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def extract_strategy(arg)
|
34
|
+
arg[:strategy] if Hash === arg
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
class FakeTestContext
|
4
|
+
include FakeObject
|
5
|
+
def stub(*args)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe FakeObject do
|
10
|
+
|
11
|
+
context "#fake" do
|
12
|
+
before do
|
13
|
+
@context = FakeTestContext.new
|
14
|
+
end
|
15
|
+
|
16
|
+
it "sets the context when setting expectations" do
|
17
|
+
mock(FakeObject::Fake).new(:greeting => 'hi')
|
18
|
+
@context.fake(:greeting => 'hi')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe Fake do
|
23
|
+
context "#new with no expectations" do
|
24
|
+
it "returns a fake object" do
|
25
|
+
FakeObject::Fake.new.should be_instance_of(Fake)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "has a name" do
|
29
|
+
f = FakeObject::Fake.new 'fake'
|
30
|
+
f.name.should == 'fake'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "#new with expectations" do
|
35
|
+
it "responds to fake method 'greeting'" do
|
36
|
+
f = FakeObject::Fake.new :greeting => 'hi'
|
37
|
+
f.greeting.should == 'hi'
|
38
|
+
end
|
39
|
+
|
40
|
+
it "responds has a name" do
|
41
|
+
f = FakeObject::Fake.new 'Greeter', :greeting => 'hi'
|
42
|
+
f.name.should == 'Greeter'
|
43
|
+
end
|
44
|
+
|
45
|
+
it "uses the mock build strategy" do
|
46
|
+
f = FakeObject::Fake.new({:greeting => 'hi'}, :strategy => :mock)
|
47
|
+
f.greeting
|
48
|
+
f.strategy.should == :mock
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "Hash not provided" do
|
53
|
+
it "raises ArgumentError for an array" do
|
54
|
+
expect {
|
55
|
+
FakeObject::Fake.new []
|
56
|
+
}.to raise_error(ArgumentError)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "raises ArgumentError for a name and array" do
|
60
|
+
expect {
|
61
|
+
FakeObject::Fake.new 'name', []
|
62
|
+
}.to raise_error(ArgumentError)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Metamuse::Artist do
|
4
|
+
subject { Metamuse::Artist.new :name => 'Mozart' }
|
5
|
+
|
6
|
+
describe ".build_artist" do
|
7
|
+
subject { Metamuse::Artist }
|
8
|
+
it "gets and enhances freebase artist information" do
|
9
|
+
mock.proxy(Metamuse::Services::Freebase).artist('Mozart') do |artist|
|
10
|
+
mock(artist).enhance_albums!
|
11
|
+
artist
|
12
|
+
end
|
13
|
+
subject.build('Mozart')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#tracks" do
|
18
|
+
it "collects all of the tracks from the every album" do
|
19
|
+
album1 = fake(:tracks => [1,2])
|
20
|
+
album2 = fake(:tracks => [3,4])
|
21
|
+
stub(subject).albums { [album1, album2] }
|
22
|
+
subject.tracks.should == [1,2,3,4]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#enhance_albums!" do
|
27
|
+
context "when albums exist" do
|
28
|
+
before do
|
29
|
+
@image = fake('image')
|
30
|
+
@lastfm_albums = [
|
31
|
+
Metamuse::Album.new(:name => 'Mild', :rank => 3, :mbid => 1),
|
32
|
+
Metamuse::Album.new(:name => 'Hot', :rank => 1, :mbid => 2, :images => [@image]),
|
33
|
+
Metamuse::Album.new(:name => 'Medium', :rank => 2, :mbid => 3)
|
34
|
+
]
|
35
|
+
stub(subject).lastfm_albums { @lastfm_albums }
|
36
|
+
subject.albums = [{:name => "Mild"}, {:name => 'Hot'}, {:name => 'Medium'}]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "uses albums from Last.fm" do
|
40
|
+
subject.enhance_albums!
|
41
|
+
subject.should have_received(:lastfm_albums).any_times
|
42
|
+
end
|
43
|
+
|
44
|
+
it "sets the album's mbid" do
|
45
|
+
subject.enhance_albums!.first.mbid.should == 2
|
46
|
+
end
|
47
|
+
|
48
|
+
it "sets the album's images collection" do
|
49
|
+
subject.enhance_albums!.first.images.should include(@image)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "sets the album's rank" do
|
53
|
+
subject.enhance_albums!.first.rank.should == 1
|
54
|
+
end
|
55
|
+
|
56
|
+
context "rankings" do
|
57
|
+
it "sets rank to nil when the lastfm album is unranked" do
|
58
|
+
ranked = Metamuse::Album.new
|
59
|
+
stub(subject).lastfm_albums { [ranked] }
|
60
|
+
subject.enhance_albums!.first.rank.should be_nil
|
61
|
+
end
|
62
|
+
|
63
|
+
it "sorts based on rank" do
|
64
|
+
ranked = [Metamuse::Album.new(:name => 'Mild', :rank => 3), Metamuse::Album.new(:name => 'Hot', :rank => 1), Metamuse::Album.new(:name => 'Medium', :rank => 2)]
|
65
|
+
stub(subject).ranked_albums { ranked }
|
66
|
+
subject.enhance_albums!
|
67
|
+
subject.albums.map{|a| a.name}.should == %w(Hot Medium Mild)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "artist has no albums" do
|
73
|
+
it "returns albums if the artist has no albums" do
|
74
|
+
albums = []
|
75
|
+
mock(subject).albums.any_times { albums }
|
76
|
+
subject.enhance_albums!.should == albums
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "#fetch_albums_and_tracks!" do
|
82
|
+
it "fetches artist information from Freebase" do
|
83
|
+
mock.proxy(Metamuse::Services::Freebase).artist("Mozart") do |freebase_artist|
|
84
|
+
mock(subject).albums=(freebase_artist.albums)
|
85
|
+
freebase_artist
|
86
|
+
end
|
87
|
+
subject.fetch_albums_and_tracks!
|
88
|
+
end
|
89
|
+
|
90
|
+
it "assigns the tracks that were retrieved from Freebase" do
|
91
|
+
freebase_artist = nil
|
92
|
+
stub.proxy(Metamuse::Services::Freebase).artist('Mozart') do |fba|
|
93
|
+
freebase_artist = fba
|
94
|
+
end
|
95
|
+
subject.fetch_albums_and_tracks!
|
96
|
+
subject.tracks.should == freebase_artist.tracks
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
class TestShoe
|
4
|
+
extend Metamuse::Association
|
5
|
+
attr_accessor :color
|
6
|
+
def initialize(attrs={})
|
7
|
+
attrs.each {|k,v| send(:"#{k}=", v)}
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class TestPerson
|
12
|
+
extend Metamuse::Association
|
13
|
+
has_many :shoes, TestShoe
|
14
|
+
end
|
15
|
+
TestShoe.class_eval { belongs_to :person, TestPerson }
|
16
|
+
|
17
|
+
describe Metamuse::Association do
|
18
|
+
it "creates a shoes accessor which returns an array" do
|
19
|
+
TestPerson.new.shoes.should be_kind_of(Array)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "adding shoes to the test person" do
|
23
|
+
before do
|
24
|
+
@person = TestPerson.new
|
25
|
+
end
|
26
|
+
|
27
|
+
it "adds a single red shoe" do
|
28
|
+
expect {
|
29
|
+
@person.shoes << {:color => "red"}
|
30
|
+
}.to change{@person.shoes.size}.by(1)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "creates a new shoe when not appending a TestShoe" do
|
34
|
+
mock.proxy(TestShoe).new({:color => 'red'})
|
35
|
+
@person.shoes << {:color => 'red'}
|
36
|
+
@person.shoes.last.color == 'red'
|
37
|
+
end
|
38
|
+
|
39
|
+
it "appends a TestShoe" do
|
40
|
+
@person.shoes << TestShoe.new(:color => "red")
|
41
|
+
@person.shoes.last.should be_instance_of(TestShoe)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "appends many shoes" do
|
45
|
+
@person.shoes << [TestShoe.new(:color => "red"), {:color => "blue"}]
|
46
|
+
@person.shoes.size.should == 2
|
47
|
+
@person.shoes.map {|s| s.class}.uniq.should == [TestShoe]
|
48
|
+
end
|
49
|
+
|
50
|
+
it "clears old shoes when setting the collection" do
|
51
|
+
@person.shoes << {:color => 'blue'}
|
52
|
+
expect {
|
53
|
+
@person.shoes = {:color => 'red'}
|
54
|
+
}.to change{@person.shoes.first.color}.from('blue').to('red')
|
55
|
+
@person.shoes.size.should == 1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "TestShoe belongs to a TestPerson" do
|
60
|
+
before do
|
61
|
+
@person = TestPerson.new
|
62
|
+
end
|
63
|
+
|
64
|
+
it "sets the shoe's person equal to the person that added the shoe" do
|
65
|
+
@person.shoes << {:color => 'red', :person => @person}
|
66
|
+
@person.shoes.last.person.should == @person
|
67
|
+
end
|
68
|
+
|
69
|
+
it "sets the shoe's person when not explicitly available in the hash" do
|
70
|
+
@person.shoes << {:color => 'red'}
|
71
|
+
@person.shoes.last.person.should == @person
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|