rbrainz 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/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
|