rbrainz 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +25 -0
- data/README +11 -0
- data/Rakefile +110 -0
- data/TODO +24 -0
- data/doc/README.rdoc +68 -0
- data/examples/getartist.rb +54 -0
- data/lib/rbrainz.rb +11 -0
- data/lib/rbrainz/data/countrynames.rb +259 -0
- data/lib/rbrainz/data/languagenames.rb +404 -0
- data/lib/rbrainz/data/scriptnames.rb +64 -0
- data/lib/rbrainz/model.rb +48 -0
- data/lib/rbrainz/model/alias.rb +23 -0
- data/lib/rbrainz/model/artist.rb +54 -0
- data/lib/rbrainz/model/disc.rb +30 -0
- data/lib/rbrainz/model/entity.rb +72 -0
- data/lib/rbrainz/model/incomplete_date.rb +76 -0
- data/lib/rbrainz/model/label.rb +55 -0
- data/lib/rbrainz/model/mbid.rb +82 -0
- data/lib/rbrainz/model/release.rb +50 -0
- data/lib/rbrainz/model/release_event.rb +34 -0
- data/lib/rbrainz/model/track.rb +27 -0
- data/lib/rbrainz/webservice.rb +6 -0
- data/lib/rbrainz/webservice/filter.rb +132 -0
- data/lib/rbrainz/webservice/includes.rb +150 -0
- data/lib/rbrainz/webservice/mbxml.rb +404 -0
- data/lib/rbrainz/webservice/query.rb +61 -0
- data/lib/rbrainz/webservice/webservice.rb +69 -0
- data/test/lib/test_entity.rb +58 -0
- data/test/lib/testing_helper.rb +13 -0
- data/test/test-data/README +13 -0
- data/test/test-data/invalid/artist/basic_1.xml +12 -0
- data/test/test-data/invalid/artist/basic_2.xml +12 -0
- data/test/test-data/invalid/artist/empty_1.xml +0 -0
- data/test/test-data/invalid/artist/empty_2.xml +3 -0
- data/test/test-data/invalid/artist/empty_3.xml +7 -0
- data/test/test-data/invalid/artist/search_result_1.xml +11 -0
- data/test/test-data/valid/artist/Tchaikovsky-1.xml +90 -0
- data/test/test-data/valid/artist/Tori_Amos_1.xml +8 -0
- data/test/test-data/valid/artist/Tori_Amos_2.xml +53 -0
- data/test/test-data/valid/artist/Tori_Amos_3.xml +25 -0
- data/test/test-data/valid/artist/Tori_Amos_4.xml +14 -0
- data/test/test-data/valid/artist/Tori_Amos_5.xml +13 -0
- data/test/test-data/valid/artist/empty_1.xml +7 -0
- data/test/test-data/valid/artist/empty_2.xml +11 -0
- data/test/test-data/valid/artist/search_result_1.xml +19 -0
- data/test/test-data/valid/label/Atlantic_Records_1.xml +9 -0
- data/test/test-data/valid/label/Atlantic_Records_2.xml +11 -0
- data/test/test-data/valid/label/search_result_1.xml +14 -0
- data/test/test-data/valid/release/Highway_61_Revisited_1.xml +68 -0
- data/test/test-data/valid/release/Little_Earthquakes_1.xml +24 -0
- data/test/test-data/valid/release/Little_Earthquakes_2.xml +73 -0
- data/test/test-data/valid/release/Mission_Impossible_2.xml +155 -0
- data/test/test-data/valid/release/Under_the_Pink_1.xml +16 -0
- data/test/test-data/valid/release/Under_the_Pink_2.xml +14 -0
- data/test/test-data/valid/release/Under_the_Pink_3.xml +16 -0
- data/test/test-data/valid/release/search_result_1.xml +29 -0
- data/test/test-data/valid/track/Silent_All_These_Years_1.xml +7 -0
- data/test/test-data/valid/track/Silent_All_These_Years_2.xml +21 -0
- data/test/test-data/valid/track/Silent_All_These_Years_3.xml +16 -0
- data/test/test-data/valid/track/Silent_All_These_Years_4.xml +30 -0
- data/test/test-data/valid/track/Silent_All_These_Years_5.xml +20 -0
- data/test/test-data/valid/track/search_result_1.xml +45 -0
- data/test/test-data/valid/user/User_1.xml +15 -0
- data/test/test_alias.rb +50 -0
- data/test/test_artist.rb +132 -0
- data/test/test_artist_filter.rb +36 -0
- data/test/test_artist_includes.rb +63 -0
- data/test/test_disc.rb +38 -0
- data/test/test_incomplete_date.rb +60 -0
- data/test/test_label.rb +129 -0
- data/test/test_label_filter.rb +36 -0
- data/test/test_label_includes.rb +55 -0
- data/test/test_mbid.rb +99 -0
- data/test/test_mbxml.rb +368 -0
- data/test/test_query.rb +24 -0
- data/test/test_release.rb +161 -0
- data/test/test_release_event.rb +67 -0
- data/test/test_release_filter.rb +57 -0
- data/test/test_release_includes.rb +73 -0
- data/test/test_track.rb +102 -0
- data/test/test_track_filter.rb +57 -0
- data/test/test_track_includes.rb +61 -0
- data/test/test_webservice.rb +23 -0
- metadata +138 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
# $Id: release.rb 15 2007-05-23 14:31:36Z phw $
|
2
|
+
# Copyright (c) 2007, Philipp Wolfer
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE for permissions.
|
5
|
+
|
6
|
+
require 'rbrainz/model/entity'
|
7
|
+
require 'rbrainz/model/release_event'
|
8
|
+
require 'rbrainz/model/disc'
|
9
|
+
|
10
|
+
module MusicBrainz
|
11
|
+
module Model
|
12
|
+
|
13
|
+
# A release in the MusicBrainz DB.
|
14
|
+
#
|
15
|
+
# See http://musicbrainz.org/doc/Release.
|
16
|
+
class Release < Entity
|
17
|
+
|
18
|
+
TYPE_ALBUM = NS_MMD_1 + 'Album'
|
19
|
+
TYPE_AUDIOBOOK = NS_MMD_1 + 'Audiobook'
|
20
|
+
TYPE_BOOTLEG = NS_MMD_1 + 'Bootleg'
|
21
|
+
TYPE_COMPILATION = NS_MMD_1 + 'Compilation'
|
22
|
+
TYPE_EP = NS_MMD_1 + 'EP'
|
23
|
+
TYPE_INTERVIEW = NS_MMD_1 + 'Interview'
|
24
|
+
TYPE_LIVE = NS_MMD_1 + 'Live'
|
25
|
+
TYPE_NONE = NS_MMD_1 + 'None'
|
26
|
+
TYPE_OFFICIAL = NS_MMD_1 + 'Official'
|
27
|
+
TYPE_OTHER = NS_MMD_1 + 'Other'
|
28
|
+
TYPE_PROMOTION = NS_MMD_1 + 'Promotion'
|
29
|
+
TYPE_PSEUDO_RELEASE = NS_MMD_1 + 'Pseudo-Release'
|
30
|
+
TYPE_REMIX = NS_MMD_1 + 'Remix'
|
31
|
+
TYPE_SINGLE = NS_MMD_1 + 'Single'
|
32
|
+
TYPE_SOUNDTRACK = NS_MMD_1 + 'Soundtrack'
|
33
|
+
TYPE_SPOKENWORD = NS_MMD_1 + 'Spokenword'
|
34
|
+
|
35
|
+
attr_accessor :title, :types, :asin, :artist,
|
36
|
+
:text_language, :text_script
|
37
|
+
|
38
|
+
attr_accessor :tracks, :release_events, :discs
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@tracks = Array.new
|
42
|
+
@release_events = Array.new
|
43
|
+
@discs = Array.new
|
44
|
+
@types = Array.new
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# $Id: release_event.rb 9 2007-05-21 21:16:34Z phw $
|
2
|
+
# Copyright (c) 2007, Philipp Wolfer
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE for permissions.
|
5
|
+
|
6
|
+
require 'rbrainz/model/label'
|
7
|
+
|
8
|
+
module MusicBrainz
|
9
|
+
module Model
|
10
|
+
|
11
|
+
# A release event in the MusicBrainz DB.
|
12
|
+
#
|
13
|
+
# See http://musicbrainz.org/doc/ReleaseEvent.
|
14
|
+
class ReleaseEvent
|
15
|
+
|
16
|
+
attr_accessor :country, :catalog_number,
|
17
|
+
:barcode, :label
|
18
|
+
|
19
|
+
attr_reader :date
|
20
|
+
|
21
|
+
# Set the date the release took place.
|
22
|
+
#
|
23
|
+
# Should be an IncompleteDate object or
|
24
|
+
# a date string, which will get converted
|
25
|
+
# into an IncompleteDate.
|
26
|
+
def date=(date)
|
27
|
+
date = IncompleteDate.new date unless date.is_a? IncompleteDate
|
28
|
+
@date = date
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# $Id: track.rb 13 2007-05-22 22:59:57Z phw $
|
2
|
+
# Copyright (c) 2007, Philipp Wolfer
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE for permissions.
|
5
|
+
|
6
|
+
require 'rbrainz/model/entity'
|
7
|
+
|
8
|
+
module MusicBrainz
|
9
|
+
module Model
|
10
|
+
|
11
|
+
# A track in the MusicBrainz DB.
|
12
|
+
#
|
13
|
+
# See http://musicbrainz.org/doc/Track.
|
14
|
+
class Track < Entity
|
15
|
+
|
16
|
+
attr_accessor :title, :duration, :artist,
|
17
|
+
:puids, :releases
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@puids = Array.new
|
21
|
+
@releases = Array.new
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# $Id: filter.rb 4 2007-05-21 02:04:26Z phw $
|
2
|
+
# Copyright (c) 2007, Philipp Wolfer
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE for permissions.
|
5
|
+
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
module MusicBrainz
|
9
|
+
module Webservice
|
10
|
+
|
11
|
+
# Base class for all filter classes.
|
12
|
+
class AbstractFilter
|
13
|
+
|
14
|
+
# The parameter +filter+ is a hash with filter options.
|
15
|
+
# The the concrete classes for a description of those
|
16
|
+
# options.
|
17
|
+
def initialize(filter)
|
18
|
+
raise 'Tried to initialize abstract class.'
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the filter list as a query string
|
22
|
+
# (without leading +&+).
|
23
|
+
def to_s
|
24
|
+
@filter.to_a.map {|name, value|
|
25
|
+
'%s=%s' % [URI.escape(name.to_s), URI.escape(value.to_s)]
|
26
|
+
}.join('&')
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class ArtistFilter < AbstractFilter
|
32
|
+
|
33
|
+
# The parameter +filter+ is a hash with filter options:
|
34
|
+
# [:name] Fetch a list of artists with a matching name.
|
35
|
+
# [:limit] The maximum number of artists returned. Defaults
|
36
|
+
# to 25, the maximum allowed value is 100.
|
37
|
+
def initialize(filter)
|
38
|
+
@filter = Hash.new
|
39
|
+
@filter[:name] = filter[:name] if filter[:name]
|
40
|
+
@filter[:limit] = filter[:limit] if filter[:limit]
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
class ReleaseFilter < AbstractFilter
|
46
|
+
|
47
|
+
# The parameter +filter+ is a hash with filter options:
|
48
|
+
# [:title] Fetch a list of releases with a matching title.
|
49
|
+
# [:discid] Fetch all releases matching to the given DiscID.
|
50
|
+
# [:artist] The returned releases should match the given artist name.
|
51
|
+
# [:artistid] The returned releases should match the given artist ID
|
52
|
+
# (36 character ASCII representation). If this is given,
|
53
|
+
# the artist parameter is ignored.
|
54
|
+
# [:releasetypes] The returned releases must match all of the given
|
55
|
+
# release types. This is a list of space separated values
|
56
|
+
# like Official, Bootleg, Album, Compilation, etc.
|
57
|
+
# [:count] Number of tracks in the release.
|
58
|
+
# [:date] Earliest release date for the release.
|
59
|
+
# [:asin] The Amazon ASIN.
|
60
|
+
# [:lang] The language for this release.
|
61
|
+
# [:script] The script used in this release.
|
62
|
+
# [:limit] The maximum number of tracks returned. Defaults
|
63
|
+
# to 25, the maximum allowed value is 100.
|
64
|
+
def initialize(filter)
|
65
|
+
@filter = Hash.new
|
66
|
+
@filter[:title] = filter[:title] if filter[:title]
|
67
|
+
@filter[:discid] = filter[:discid] if filter[:discid]
|
68
|
+
@filter[:artist] = filter[:artist] if filter[:artist]
|
69
|
+
@filter[:artistid] = filter[:artistid] if filter[:artistid]
|
70
|
+
@filter[:releasetypes] = filter[:releasetypes] if filter[:releasetypes]
|
71
|
+
@filter[:count] = filter[:count] if filter[:count]
|
72
|
+
@filter[:date] = filter[:date] if filter[:date]
|
73
|
+
@filter[:asin] = filter[:asin] if filter[:asin]
|
74
|
+
@filter[:lang] = filter[:lang] if filter[:lang]
|
75
|
+
@filter[:script] = filter[:script] if filter[:script]
|
76
|
+
@filter[:limit] = filter[:limit] if filter[:limit]
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
class TrackFilter < AbstractFilter
|
82
|
+
|
83
|
+
# The parameter +filter+ is a hash with filter options:
|
84
|
+
# [:title] Fetch a list of tracks with a matching title.
|
85
|
+
# [:artist] The returned tracks have to match the given
|
86
|
+
# artist name.
|
87
|
+
# [:release] The returned tracks have to match the given
|
88
|
+
# release title.
|
89
|
+
# [:duration] The length of the track in milliseconds
|
90
|
+
# [:tracknum] The track number
|
91
|
+
# [:artistid] The artist's MBID. If this is given, the artist
|
92
|
+
# parameter is ignored.
|
93
|
+
# [:releaseid] The release's MBID. If this is given, the release
|
94
|
+
# parameter is ignored.
|
95
|
+
# [:puid] The returned tracks have to match the given PUID.
|
96
|
+
# [:count] Number of tracks on the release.
|
97
|
+
# [:releasetype] The type of the release this track appears on
|
98
|
+
# [:limit] The maximum number of tracks returned. Defaults
|
99
|
+
# to 25, the maximum allowed value is 100.
|
100
|
+
def initialize(filter)
|
101
|
+
@filter = Hash.new
|
102
|
+
@filter[:title] = filter[:title] if filter[:title]
|
103
|
+
@filter[:artist] = filter[:artist] if filter[:artist]
|
104
|
+
@filter[:release] = filter[:release] if filter[:release]
|
105
|
+
@filter[:duration] = filter[:duration] if filter[:duration]
|
106
|
+
@filter[:tracknum] = filter[:tracknum] if filter[:tracknum]
|
107
|
+
@filter[:artistid] = filter[:artistid] if filter[:artistid]
|
108
|
+
@filter[:releaseid] = filter[:releaseid] if filter[:releaseid]
|
109
|
+
@filter[:puid] = filter[:puid] if filter[:puid]
|
110
|
+
@filter[:count] = filter[:count] if filter[:count]
|
111
|
+
@filter[:releasetype] = filter[:releasetype] if filter[:releasetype]
|
112
|
+
@filter[:limit] = filter[:limit] if filter[:limit]
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
class LabelFilter < AbstractFilter
|
118
|
+
|
119
|
+
# The parameter +filter+ is a hash with filter options:
|
120
|
+
# [:name] Fetch a list of labels with a matching name.
|
121
|
+
# [:limit] The maximum number of labels returned. Defaults
|
122
|
+
# to 25, the maximum allowed value is 100.
|
123
|
+
def initialize(filter)
|
124
|
+
@filter = Hash.new
|
125
|
+
@filter[:name] = filter[:name] if filter[:name]
|
126
|
+
@filter[:limit] = filter[:limit] if filter[:limit]
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# $Id: includes.rb 4 2007-05-21 02:04:26Z phw $
|
2
|
+
# Copyright (c) 2007, Philipp Wolfer
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE for permissions.
|
5
|
+
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
module MusicBrainz
|
9
|
+
module Webservice
|
10
|
+
|
11
|
+
# Base class for all include classes.
|
12
|
+
class AbstractIncludes
|
13
|
+
|
14
|
+
def initialize(includes)
|
15
|
+
raise 'Tried to initialize abstract class.'
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
if @parameters.size > 0
|
20
|
+
return 'inc=' + URI.escape(@parameters.join(' '))
|
21
|
+
else
|
22
|
+
return ''
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
class ArtistIncludes < AbstractIncludes
|
29
|
+
|
30
|
+
# Includes is a hash with the following fields:
|
31
|
+
# [:aliases] Include aliases (boolean).
|
32
|
+
# [:releases] Array of release types that should be included
|
33
|
+
# in the result. All releases of the artist that match
|
34
|
+
# all of those types will be included. Use the constants
|
35
|
+
# defined in Model::Releases for the release types.
|
36
|
+
# [:va_releases] Array of release types. All various artist releases
|
37
|
+
# the artist appears on and that match all of those
|
38
|
+
# types will be included. Use the constants
|
39
|
+
# defined in Model::Releases for the release types.
|
40
|
+
# [:artist_rels] Include artist relationships (boolean).
|
41
|
+
# [:release_rels] Include release relationships (boolean).
|
42
|
+
# [:track_rels] Include track relationships (boolean).
|
43
|
+
# [:label_rels] Include label relationships (boolean).
|
44
|
+
# [:url_rels] Include url relationships (boolean).
|
45
|
+
#
|
46
|
+
# TODO: Check release types. It's possible that :releases
|
47
|
+
# and :va_releases can't be used in parallel.
|
48
|
+
def initialize(includes)
|
49
|
+
@parameters = Array.new
|
50
|
+
@parameters << 'aliases' if includes[:aliases]
|
51
|
+
@parameters << 'artist-rels' if includes[:artist_rels]
|
52
|
+
@parameters << 'release-rels' if includes[:release_rels]
|
53
|
+
@parameters << 'track-rels' if includes[:track_rels]
|
54
|
+
@parameters << 'label-rels' if includes[:label_rels]
|
55
|
+
@parameters << 'url-rels' if includes[:url_rels]
|
56
|
+
|
57
|
+
includes[:releases].each {|release_type|
|
58
|
+
@parameters << 'sa-' + release_type.to_s
|
59
|
+
} if includes[:releases]
|
60
|
+
|
61
|
+
includes[:va_releases].each {|release_type|
|
62
|
+
@parameters << 'va-' + release_type.to_s
|
63
|
+
} if includes[:va_releases]
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
class ReleaseIncludes < AbstractIncludes
|
69
|
+
|
70
|
+
# Includes is a hash with the following fields:
|
71
|
+
# [:artist] Include track artist (boolean).
|
72
|
+
# [:counts] Includes the track number (boolean).
|
73
|
+
# [:release_events] Includes the release events (boolean).
|
74
|
+
# [:discs] Include the disc IDs (boolean).
|
75
|
+
# [:tracks] Include the release tracks (boolean).
|
76
|
+
# [:labels] Include the labels under which the release
|
77
|
+
# was publlished (boolean).
|
78
|
+
# [:artist_rels] Include artist relationships (boolean).
|
79
|
+
# [:release_rels] Include release relationships (boolean).
|
80
|
+
# [:track_rels] Include track relationships (boolean).
|
81
|
+
# [:label_rels] Include label relationships (boolean).
|
82
|
+
# [:url_rels] Include url relationships (boolean).
|
83
|
+
# [:track_level_rels] Include the relationships for the
|
84
|
+
# single tracks as well (boolean).
|
85
|
+
def initialize(includes)
|
86
|
+
@parameters = Array.new
|
87
|
+
@parameters << 'artist' if includes[:artist]
|
88
|
+
@parameters << 'counts' if includes[:counts]
|
89
|
+
@parameters << 'release-events' if includes[:release_events]
|
90
|
+
@parameters << 'discs' if includes[:discs]
|
91
|
+
@parameters << 'tracks' if includes[:tracks]
|
92
|
+
@parameters << 'labels' if includes[:labels]
|
93
|
+
@parameters << 'artist-rels' if includes[:artist_rels]
|
94
|
+
@parameters << 'release-rels' if includes[:release_rels]
|
95
|
+
@parameters << 'track-rels' if includes[:track_rels]
|
96
|
+
@parameters << 'label-rels' if includes[:label_rels]
|
97
|
+
@parameters << 'url-rels' if includes[:url_rels]
|
98
|
+
@parameters << 'track-level-rels' if includes[:track_level_rels]
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
class TrackIncludes < AbstractIncludes
|
104
|
+
|
105
|
+
# Includes is a hash with the following fields:
|
106
|
+
# [:artist] Include track artist (boolean).
|
107
|
+
# [:releases] Includes releases the track appears on (boolean).
|
108
|
+
# [:puids] Include the track's PUIDs (boolean).
|
109
|
+
# [:artist_rels] Include artist relationships (boolean).
|
110
|
+
# [:release_rels] Include release relationships (boolean).
|
111
|
+
# [:track_rels] Include track relationships (boolean).
|
112
|
+
# [:label_rels] Include label relationships (boolean).
|
113
|
+
# [:url_rels] Include url relationships (boolean).
|
114
|
+
def initialize(includes)
|
115
|
+
@parameters = Array.new
|
116
|
+
@parameters << 'artist' if includes[:artist]
|
117
|
+
@parameters << 'releases' if includes[:releases]
|
118
|
+
@parameters << 'puids' if includes[:puids]
|
119
|
+
@parameters << 'artist-rels' if includes[:artist_rels]
|
120
|
+
@parameters << 'release-rels' if includes[:release_rels]
|
121
|
+
@parameters << 'track-rels' if includes[:track_rels]
|
122
|
+
@parameters << 'label-rels' if includes[:label_rels]
|
123
|
+
@parameters << 'url-rels' if includes[:url_rels]
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
class LabelIncludes < AbstractIncludes
|
129
|
+
|
130
|
+
# Includes is a hash with the following fields:
|
131
|
+
# [:aliases] Include aliases (boolean).
|
132
|
+
# [:artist_rels] Include artist relationships (boolean).
|
133
|
+
# [:release_rels] Include release relationships (boolean).
|
134
|
+
# [:track_rels] Include track relationships (boolean).
|
135
|
+
# [:label_rels] Include label relationships (boolean).
|
136
|
+
# [:url_rels] Include url relationships (boolean).
|
137
|
+
def initialize(includes)
|
138
|
+
@parameters = Array.new
|
139
|
+
@parameters << 'aliases' if includes[:aliases]
|
140
|
+
@parameters << 'artist-rels' if includes[:artist_rels]
|
141
|
+
@parameters << 'release-rels' if includes[:release_rels]
|
142
|
+
@parameters << 'track-rels' if includes[:track_rels]
|
143
|
+
@parameters << 'label-rels' if includes[:label_rels]
|
144
|
+
@parameters << 'url-rels' if includes[:url_rels]
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,404 @@
|
|
1
|
+
# $Id: mbxml.rb 17 2007-05-23 16:51:11Z phw $
|
2
|
+
# Copyright (c) 2007, Philipp Wolfer
|
3
|
+
# All rights reserved.
|
4
|
+
# See LICENSE for permissions.
|
5
|
+
|
6
|
+
require 'rbrainz/model'
|
7
|
+
require 'rexml/document'
|
8
|
+
include REXML
|
9
|
+
|
10
|
+
module MusicBrainz
|
11
|
+
module Webservice
|
12
|
+
|
13
|
+
# Class to read the XML data returned by the MusicBrainz
|
14
|
+
# webservice and create the corresponding model classes.
|
15
|
+
# The class understands the MusicBrainz XML Metadata Version 1.0
|
16
|
+
# schema.
|
17
|
+
# See http://musicbrainz.org/doc/MusicBrainzXMLMetaData for more
|
18
|
+
# information on the MusicBrainz XML Metadata schema.
|
19
|
+
class MBXML
|
20
|
+
|
21
|
+
def initialize(xml)
|
22
|
+
@document = Document.new(xml)
|
23
|
+
|
24
|
+
# Already loaded artists, releases, tracks
|
25
|
+
# and labels will get cached in these variables
|
26
|
+
# to link to them if they occure multiple times
|
27
|
+
# inside the same document.
|
28
|
+
@artists = Hash.new
|
29
|
+
@releases = Hash.new
|
30
|
+
@tracks = Hash.new
|
31
|
+
@labels = Hash.new
|
32
|
+
end
|
33
|
+
|
34
|
+
# Read the XML string and create an entity model
|
35
|
+
# for the given entity type if it is present in
|
36
|
+
# the document.
|
37
|
+
# Returns nil if no entity of the given type is present.
|
38
|
+
def get_entity(entity_type)
|
39
|
+
# Search for the first occuring node of type entity which is a child node
|
40
|
+
# of the metadata element.
|
41
|
+
entity = @document.elements["//[local-name()='metadata' and namespace-uri()='%s']/%s[1]" %
|
42
|
+
[Model::NS_MMD_1, entity_type]]
|
43
|
+
|
44
|
+
unless entity.nil? or entity.is_a? REXML::Text
|
45
|
+
case entity.name
|
46
|
+
when 'artist'
|
47
|
+
return create_artist(entity)
|
48
|
+
when 'release'
|
49
|
+
return create_release(entity)
|
50
|
+
when 'track'
|
51
|
+
return create_track(entity)
|
52
|
+
when 'label'
|
53
|
+
return create_label(entity)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
return nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Read the XML string and create a list of entity
|
61
|
+
# models for the given entity type. Ther must be
|
62
|
+
# an entity-list element as a child of the metadata
|
63
|
+
# element in the document.
|
64
|
+
# Returns nil if no entity list of the given type is present.
|
65
|
+
# Returns an empty array if the list is empty.
|
66
|
+
def get_entity_list(entity_type)
|
67
|
+
# Search for the first occuring node of type entity which is a child node
|
68
|
+
# of the metadata element.
|
69
|
+
entity_list = @document.elements["//[local-name()='metadata' and namespace-uri()='%s']/%s-list[1]" %
|
70
|
+
[Model::NS_MMD_1, entity_type]]
|
71
|
+
|
72
|
+
unless entity_list.nil? or entity_list.is_a? REXML::Text
|
73
|
+
list = Array.new
|
74
|
+
|
75
|
+
case entity_list.name
|
76
|
+
when 'artist-list'
|
77
|
+
read_artist_list(entity_list) {|model|
|
78
|
+
list << model
|
79
|
+
}
|
80
|
+
return list
|
81
|
+
when 'release-list'
|
82
|
+
read_release_list(entity_list) {|model|
|
83
|
+
list << model
|
84
|
+
}
|
85
|
+
return list
|
86
|
+
when 'track-list'
|
87
|
+
read_track_list(entity_list) {|model|
|
88
|
+
list << model
|
89
|
+
}
|
90
|
+
return list
|
91
|
+
when 'label-list'
|
92
|
+
read_label_list(entity_list) {|model|
|
93
|
+
list << model
|
94
|
+
}
|
95
|
+
return list
|
96
|
+
end
|
97
|
+
|
98
|
+
if iterator
|
99
|
+
list = Array.new
|
100
|
+
iterator.each {|model|
|
101
|
+
raise model.inspect
|
102
|
+
list << model
|
103
|
+
}
|
104
|
+
return list
|
105
|
+
end
|
106
|
+
end
|
107
|
+
return nil
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
# Iterate over a list of artists.
|
113
|
+
#
|
114
|
+
# The node must be of the type <em>artist-list</em>.
|
115
|
+
def read_artist_list(node)
|
116
|
+
node.elements.each('artist') {|child|
|
117
|
+
yield create_artist(child)
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
# Create an +Artist+ object from the given artist node.
|
122
|
+
#
|
123
|
+
# TODO: relation list
|
124
|
+
def create_artist(node)
|
125
|
+
if node.attributes['id'] and @artists[node.attributes['id']]
|
126
|
+
artist = @artists[node.attributes['id']]
|
127
|
+
else
|
128
|
+
artist = Model::Artist.new
|
129
|
+
@artists[node.attributes['id']] = artist
|
130
|
+
end
|
131
|
+
|
132
|
+
# Read all defined data fields
|
133
|
+
artist.id = node.attributes['id']
|
134
|
+
artist.type = MBXML.add_metadata_namespace(node.attributes['type']) if node.attributes['type']
|
135
|
+
|
136
|
+
artist.name = node.elements['name'].text if node.elements['name']
|
137
|
+
artist.sort_name = node.elements['sort-name'].text if node.elements['sort-name']
|
138
|
+
artist.disambiguation = node.elements['disambiguation'].text if node.elements['disambiguation']
|
139
|
+
|
140
|
+
if life_span = node.elements['life-span']
|
141
|
+
if life_span.attributes['begin']
|
142
|
+
artist.begin_date = Model::IncompleteDate.new life_span.attributes['begin']
|
143
|
+
end
|
144
|
+
if life_span.attributes['end']
|
145
|
+
artist.end_date = Model::IncompleteDate.new life_span.attributes['end']
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Read the alias list
|
150
|
+
if node.elements['alias-list']
|
151
|
+
read_alias_list(node.elements['alias-list']) {|artist_alias|
|
152
|
+
artist.aliases << artist_alias
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
# Read the release list
|
157
|
+
if node.elements['release-list']
|
158
|
+
read_release_list(node.elements['release-list']) {|release|
|
159
|
+
release.artist = artist unless release.artist
|
160
|
+
artist.releases << release
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
return artist
|
165
|
+
end
|
166
|
+
|
167
|
+
# Iterate over a list of releases.
|
168
|
+
#
|
169
|
+
# The node must be of the type <em>release-list</em>.
|
170
|
+
def read_release_list(node)
|
171
|
+
node.elements.each('release') {|child|
|
172
|
+
yield create_release(child)
|
173
|
+
}
|
174
|
+
end
|
175
|
+
|
176
|
+
# Create a +Release+ object from the given release node.
|
177
|
+
#
|
178
|
+
# TODO: PUID list
|
179
|
+
# TODO: relation list
|
180
|
+
def create_release(node)
|
181
|
+
if node.attributes['id'] and @releases[node.attributes['id']]
|
182
|
+
release = @releases[node.attributes['id']]
|
183
|
+
else
|
184
|
+
release = Model::Release.new
|
185
|
+
@releases[node.attributes['id']] = release
|
186
|
+
end
|
187
|
+
|
188
|
+
# Read all defined data fields
|
189
|
+
release.id = node.attributes['id']
|
190
|
+
release.title = node.elements['title'].text if node.elements['title']
|
191
|
+
release.asin = node.elements['asin'].text if node.elements['asin']
|
192
|
+
release.artist = create_artist(node.elements['artist']) if node.elements['artist']
|
193
|
+
|
194
|
+
# Read the types
|
195
|
+
node.attributes['type'].split(' ').each {|type|
|
196
|
+
release.types << MBXML.add_metadata_namespace(type)
|
197
|
+
} if node.attributes['type']
|
198
|
+
|
199
|
+
# Read the text representation information.
|
200
|
+
if text_representation = node.elements['text-representation']
|
201
|
+
release.text_language = text_representation.attributes['language']
|
202
|
+
release.text_script = text_representation.attributes['script']
|
203
|
+
end
|
204
|
+
|
205
|
+
# Read the track list
|
206
|
+
if node.elements['track-list']
|
207
|
+
read_track_list(node.elements['track-list']) {|track|
|
208
|
+
track.artist = release.artist unless track.artist
|
209
|
+
track.releases << release
|
210
|
+
release.tracks << track
|
211
|
+
}
|
212
|
+
end
|
213
|
+
|
214
|
+
# Read the release event list
|
215
|
+
if node.elements['release-event-list']
|
216
|
+
read_release_event_list(node.elements['release-event-list']) {|event|
|
217
|
+
release.release_events << event
|
218
|
+
}
|
219
|
+
end
|
220
|
+
|
221
|
+
# Read the disc list
|
222
|
+
if node.elements['disc-list']
|
223
|
+
read_disc_list(node.elements['disc-list']) {|disc|
|
224
|
+
release.discs << disc
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
228
|
+
return release
|
229
|
+
end
|
230
|
+
|
231
|
+
# Iterate over a list of tracks.
|
232
|
+
#
|
233
|
+
# The node must be of the type <em>track-list</em>.
|
234
|
+
def read_track_list(node)
|
235
|
+
node.elements.each('track') {|child|
|
236
|
+
yield create_track(child)
|
237
|
+
}
|
238
|
+
end
|
239
|
+
|
240
|
+
# Create a +Track+ object from the given track node.
|
241
|
+
#
|
242
|
+
# TODO: relation list
|
243
|
+
def create_track(node)
|
244
|
+
if node.attributes['id'] and @tracks[node.attributes['id']]
|
245
|
+
track = @tracks[node.attributes['id']]
|
246
|
+
else
|
247
|
+
track = Model::Track.new
|
248
|
+
@tracks[node.attributes['id']] = track
|
249
|
+
end
|
250
|
+
|
251
|
+
# Read all defined data fields
|
252
|
+
track.id = node.attributes['id']
|
253
|
+
track.title = node.elements['title'].text if node.elements['title']
|
254
|
+
track.duration = node.elements['duration'].text.to_i if node.elements['duration']
|
255
|
+
track.artist = create_artist(node.elements['artist']) if node.elements['artist']
|
256
|
+
|
257
|
+
# Read the release list
|
258
|
+
if node.elements['release-list']
|
259
|
+
read_release_list(node.elements['release-list']) {|release|
|
260
|
+
release.tracks << track
|
261
|
+
track.releases << release
|
262
|
+
}
|
263
|
+
end
|
264
|
+
|
265
|
+
# Read the PUID list
|
266
|
+
if node.elements['puid-list']
|
267
|
+
read_puid_list(node.elements['puid-list']) {|puid|
|
268
|
+
track.puids << puid
|
269
|
+
}
|
270
|
+
end
|
271
|
+
|
272
|
+
return track
|
273
|
+
end
|
274
|
+
|
275
|
+
# Iterate over a list of labels.
|
276
|
+
#
|
277
|
+
# The node must be of the type <em>label-list</em>.
|
278
|
+
def read_label_list(node)
|
279
|
+
node.elements.each('label') {|child|
|
280
|
+
yield create_label(child)
|
281
|
+
}
|
282
|
+
end
|
283
|
+
|
284
|
+
# Create a +Label+ object from the given label node.
|
285
|
+
#
|
286
|
+
# TODO: Relations
|
287
|
+
def create_label(node)
|
288
|
+
if node.attributes['id'] and @labels[node.attributes['id']]
|
289
|
+
label = @labels[node.attributes['id']]
|
290
|
+
else
|
291
|
+
label = Model::Label.new
|
292
|
+
@labels[node.attributes['id']] = label
|
293
|
+
end
|
294
|
+
|
295
|
+
# Read all defined data fields
|
296
|
+
label.id = node.attributes['id']
|
297
|
+
label.type = MBXML.add_metadata_namespace(node.attributes['type']) if node.attributes['type']
|
298
|
+
|
299
|
+
label.name = node.elements['name'].text if node.elements['name']
|
300
|
+
label.sort_name = node.elements['sort-name'].text if node.elements['sort-name']
|
301
|
+
label.code = node.elements['label-code'].text if node.elements['label-code']
|
302
|
+
label.disambiguation = node.elements['disambiguation'].text if node.elements['disambiguation']
|
303
|
+
label.country = node.elements['country'].text if node.elements['country']
|
304
|
+
|
305
|
+
if life_span = node.elements['life-span']
|
306
|
+
if life_span.attributes['begin']
|
307
|
+
label.founding_date = Model::IncompleteDate.new life_span.attributes['begin']
|
308
|
+
end
|
309
|
+
if life_span.attributes['end']
|
310
|
+
label.dissolving_date = Model::IncompleteDate.new life_span.attributes['end']
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# Read the alias list
|
315
|
+
if node.elements['release-list']
|
316
|
+
read_release_list(node.elements['release-list']) {|release|
|
317
|
+
label.releases << release
|
318
|
+
}
|
319
|
+
end
|
320
|
+
|
321
|
+
return label
|
322
|
+
end
|
323
|
+
|
324
|
+
# Iterate over a list of aliases.
|
325
|
+
#
|
326
|
+
# The node must be of the type <em>alias-list</em>.
|
327
|
+
def read_alias_list(node)
|
328
|
+
node.elements.each('alias') {|child|
|
329
|
+
yield create_alias(child)
|
330
|
+
}
|
331
|
+
end
|
332
|
+
|
333
|
+
# Create an +Alias+ object from the given alias node.
|
334
|
+
def create_alias(node)
|
335
|
+
alias_model = Model::Alias.new
|
336
|
+
alias_model.name = node.text
|
337
|
+
alias_model.type = node.attributes['type']
|
338
|
+
alias_model.script = node.attributes['script']
|
339
|
+
return alias_model
|
340
|
+
end
|
341
|
+
|
342
|
+
# Iterate over a list of release events.
|
343
|
+
#
|
344
|
+
# The node must be of the type <em>release-event-list</em>.
|
345
|
+
def read_release_event_list(node)
|
346
|
+
node.elements.each('event') {|child|
|
347
|
+
yield create_release_event(child)
|
348
|
+
}
|
349
|
+
end
|
350
|
+
|
351
|
+
# Create an +ReleaseEvent+ object from the given release event node.
|
352
|
+
def create_release_event(node)
|
353
|
+
event = Model::ReleaseEvent.new
|
354
|
+
|
355
|
+
# Read all defined data fields
|
356
|
+
if node.attributes['date']
|
357
|
+
event.date = Model::IncompleteDate.new node.attributes['date']
|
358
|
+
end
|
359
|
+
event.country = node.attributes['country']
|
360
|
+
event.catalog_number = node.attributes['catalog-number']
|
361
|
+
event.barcode = node.attributes['barcode']
|
362
|
+
event.label = create_label(node.elements['label']) if node.elements['label']
|
363
|
+
|
364
|
+
return event
|
365
|
+
end
|
366
|
+
|
367
|
+
# Iterate over a list of PUIDs.
|
368
|
+
#
|
369
|
+
# The node must be of the type <em>puid-list</em>.
|
370
|
+
def read_puid_list(node)
|
371
|
+
node.elements.each('puid') {|child|
|
372
|
+
yield child.attributes['id']
|
373
|
+
}
|
374
|
+
end
|
375
|
+
|
376
|
+
# Iterate over a list of discs.
|
377
|
+
#
|
378
|
+
# The node must be of the type <em>disc-list</em>.
|
379
|
+
def read_disc_list(node)
|
380
|
+
node.elements.each('disc') {|child|
|
381
|
+
disc = Model::Disc.new
|
382
|
+
disc.id = child.attributes['id']
|
383
|
+
disc.sectors = child.attributes['sectors'].to_i
|
384
|
+
yield disc
|
385
|
+
}
|
386
|
+
end
|
387
|
+
|
388
|
+
# Helper method which will return the given property
|
389
|
+
# extended by the metadata namespace. If the property
|
390
|
+
# already includes the namespace it will be returned
|
391
|
+
# unchanged.
|
392
|
+
def self.add_metadata_namespace(metadata_property)
|
393
|
+
regex = Regexp.new("/#{Model::NS_MMD_1}[a-z-]/i")
|
394
|
+
unless metadata_property =~ regex
|
395
|
+
return Model::NS_MMD_1 + metadata_property
|
396
|
+
else
|
397
|
+
return metadata_property
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
end
|
402
|
+
|
403
|
+
end
|
404
|
+
end
|