sandro-metamuse 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|