├── lib
├── mb.rb
├── musicbrainz
│ ├── version.rb
│ ├── models
│ │ ├── medium.rb
│ │ ├── track.rb
│ │ ├── recording.rb
│ │ ├── artist.rb
│ │ ├── release_group.rb
│ │ ├── release.rb
│ │ └── base_model.rb
│ ├── bindings
│ │ ├── discid_releases.rb
│ │ ├── release_mediums.rb
│ │ ├── release_group_releases.rb
│ │ ├── release_tracks.rb
│ │ ├── artist_release_groups.rb
│ │ ├── medium.rb
│ │ ├── track.rb
│ │ ├── release_group.rb
│ │ ├── release_group_search.rb
│ │ ├── artist.rb
│ │ ├── recording_search.rb
│ │ ├── recording.rb
│ │ ├── relations.rb
│ │ ├── artist_search.rb
│ │ └── release.rb
│ ├── client_modules
│ │ ├── transparent_proxy.rb
│ │ ├── failsafe_proxy.rb
│ │ └── caching_proxy.rb
│ ├── middleware.rb
│ ├── deprecated.rb
│ ├── configuration.rb
│ └── client.rb
└── musicbrainz.rb
├── .gitignore
├── spec
├── models
│ ├── medium_spec.rb
│ ├── track_spec.rb
│ ├── recording_spec.rb
│ ├── base_model_spec.rb
│ ├── artist_spec.rb
│ ├── release_group_spec.rb
│ └── release_spec.rb
├── support
│ └── mock_helpers.rb
├── fixtures
│ ├── recording
│ │ ├── find_b3015bab-1540-4d4e-9f30-14872a1525f7.xml
│ │ └── search_bound_for_the_floor_local_h.xml
│ ├── release_group
│ │ ├── find_6f33e0f0-cde2-38f9-9aee-2c60af8d1a61.xml
│ │ ├── search_kasabian_empire.xml
│ │ └── search_kasabian_empire_album.xml
│ ├── release
│ │ ├── find_2225dd4c-ae9a-403b-8ea0-9e05014c778f.xml
│ │ ├── find_b94cb547-cf7a-4357-894c-53c3bf33b093.xml
│ │ ├── find_by_discid_pmzhT6ZlFiwSRCdVwV0eqire5_Y-.xml
│ │ ├── find_2225dd4c-ae9a-403b-8ea0-9e05014c778f_tracks.xml
│ │ └── list_6f33e0f0-cde2-38f9-9aee-2c60af8d1a61.xml
│ └── artist
│ │ ├── search_kasabian.xml
│ │ ├── find_69b39eab-6577-46a4-a9f5-817839092033.xml
│ │ └── release_groups_69b39eab-6577-46a4-a9f5-817839092033.xml
├── spec_helper.rb
├── bindings
│ ├── recording_spec.rb
│ ├── track_spec.rb
│ ├── release_group_search_spec.rb
│ ├── medium_spec.rb
│ ├── recording_search_spec.rb
│ ├── relations_spec.rb
│ └── release_spec.rb
├── deprecated
│ ├── cache_config_spec.rb
│ └── proxy_config_spec.rb
└── client_modules
│ └── cache_spec.rb
├── Rakefile
├── Gemfile
├── Contributors
├── CHANGELOG.md
├── .github
└── workflows
│ └── test.yml
├── musicbrainz.gemspec
├── LICENSE
├── Gemfile.lock
└── README.md
/lib/mb.rb:
--------------------------------------------------------------------------------
1 | require "musicbrainz"
2 | MB = MusicBrainz
3 |
--------------------------------------------------------------------------------
/lib/musicbrainz/version.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | VERSION = "0.8.0"
3 | end
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .ruby-version
2 | coverage
3 | .bundle
4 | tmp
5 | .gem
6 | pkg/*
7 | /vendor
8 |
--------------------------------------------------------------------------------
/spec/models/medium_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Medium
4 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | begin
3 | require 'bundler/setup'
4 | rescue LoadError
5 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6 | end
7 |
8 | Bundler::GemHelper.install_tasks
9 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gemspec
4 |
5 | group :test do
6 | gem 'rspec'
7 | end
8 |
9 | group :development, :test do
10 | gem 'rake', '~> 13.1.0'
11 | gem 'pry'
12 | gem 'awesome_print'
13 | end
14 |
--------------------------------------------------------------------------------
/Contributors:
--------------------------------------------------------------------------------
1 | Huge thanks to that people for making this gem better!
2 |
3 | Applicat (https://github.com/Applicat)
4 | Jens Fahnenbruck (https://github.com/jigfox)
5 | Sander Nieuwenhuizen (https://github.com/munkius)
6 | Savater Sebastien (https://github.com/blakink)
7 | Thomas Wolfe (https://github.com/tomwolfe)
8 |
--------------------------------------------------------------------------------
/lib/musicbrainz/models/medium.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | class Medium < BaseModel
3 | field :position, Integer
4 | field :format, String
5 | field :tracks, Array
6 |
7 | def tracks=(tracks_data)
8 | @tracks = tracks_data.map { |track_data| Track.new(track_data) }
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/discid_releases.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module DiscidReleases
4 | def parse(xml)
5 | xml.xpath('./disc/release-list/release').map do |xml|
6 | MusicBrainz::Bindings::Release.parse(xml)
7 | end
8 | end
9 |
10 | extend self
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/release_mediums.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module ReleaseMediums
4 | def parse(xml)
5 | xml.xpath('./release/medium-list/medium').map do |xml|
6 | MusicBrainz::Bindings::Medium.parse(xml)
7 | end
8 | end
9 |
10 | extend self
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/release_group_releases.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module ReleaseGroupReleases
4 | def parse(xml)
5 | xml.xpath('./release-list/release').map do |xml|
6 | MusicBrainz::Bindings::Release.parse(xml)
7 | end
8 | end
9 |
10 | extend self
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/release_tracks.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module ReleaseTracks
4 | def parse(xml)
5 | xml.xpath('./release/medium-list/medium/track-list/track').map do |xml|
6 | MusicBrainz::Bindings::Track.parse(xml)
7 | end
8 | end
9 |
10 | extend self
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/artist_release_groups.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module ArtistReleaseGroups
4 | def parse(xml)
5 | xml.xpath('./release-group-list/release-group').map do |xml|
6 | MusicBrainz::Bindings::ReleaseGroup.parse(xml)
7 | end
8 | end
9 |
10 | extend self
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/musicbrainz/client_modules/transparent_proxy.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module ClientModules
3 | module TransparentProxy
4 | def get_contents(url)
5 | response = http.get(url)
6 | { body: response.body, status: response.status }
7 | rescue Faraday::Error::ClientError
8 | { body: nil, status: 500 }
9 | end
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/spec/support/mock_helpers.rb:
--------------------------------------------------------------------------------
1 | module MockHelpers
2 | def mock(url:, fixture:)
3 | allow_any_instance_of(MusicBrainz::Client).to receive(:get_contents)
4 | .with(url)
5 | .and_return({ status: 200, body: read_fixture(fixture)})
6 | end
7 |
8 | def read_fixture(name)
9 | spec_path = File.join(File.dirname(__FILE__), "..")
10 | file_path = File.join(spec_path, "fixtures", name)
11 | File.open(file_path).read
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/fixtures/recording/find_b3015bab-1540-4d4e-9f30-14872a1525f7.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Empire
5 | 233013
6 | 2006-08-25
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lib/musicbrainz/models/track.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | class Track < BaseModel
3 | field :id, String
4 | field :position, Integer
5 | field :recording_id, String
6 | field :title, String
7 | field :length, Integer
8 |
9 | class << self
10 | def find(id)
11 | client.load(:recording, { id: id }, {
12 | binding: :track,
13 | create_model: :track
14 | })
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require "rubygems"
2 | require "bundler/setup"
3 | require "musicbrainz"
4 | require "pry"
5 | require "awesome_print"
6 | require_relative 'support/mock_helpers'
7 |
8 | RSpec.configure do |c|
9 | include MockHelpers
10 |
11 | c.order = 'random'
12 | c.color = true
13 | end
14 |
15 | MusicBrainz.configure do |c|
16 | c.app_name = "MusicBrainzGemTestSuite"
17 | c.app_version = MusicBrainz::VERSION
18 | c.contact = "root@localhost"
19 | end
20 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/medium.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module Medium
4 | def parse(xml)
5 | {
6 | position: (xml.xpath('./position').text rescue nil),
7 | format: (xml.xpath('./format').text rescue nil),
8 | tracks: xml.xpath('./track-list/track').map do |track_xml|
9 | MusicBrainz::Bindings::Track.parse(track_xml)
10 | end
11 | }
12 | end
13 |
14 | extend self
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/musicbrainz/models/recording.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | class Recording < BaseModel
3 | field :id, String
4 | field :mbid, String
5 | field :title, String
6 | field :artist, String
7 | field :releases, String
8 | field :score, Integer
9 | field :isrcs, Array
10 |
11 | class << self
12 | def find(id)
13 | super({ id: id, inc: [:isrcs] })
14 | end
15 |
16 | def search(track_name, artist_name)
17 | super({recording: track_name, artist: artist_name})
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/track.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module Track
4 | def parse(xml)
5 | {
6 | id: (xml.attribute('id').value rescue nil),
7 | position: (xml.xpath('./position').text rescue nil),
8 | recording_id: (xml.xpath('./recording').attribute('id').value rescue nil),
9 | title: (xml.xpath('./recording/title').text rescue nil),
10 | length: (xml.xpath('./recording/length').text rescue nil)
11 | }
12 | end
13 |
14 | extend self
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## musicbrainz (unreleased) ##
2 |
3 | # 0.7.7 (Dezember 1, 2014) ##
4 |
5 | * Handle nil values returned from Base model search at Artist#find_by_name and ReleaseGroup#find_by_artist_and_title [#24]
6 |
7 | # 0.7.6 (June 14, 2013) ##
8 |
9 | * Improves urls attribute to return an array if there are multiple urls for a relation type. [#19]
10 |
11 | # 0.7.5 (May 6, 2013) ##
12 |
13 | * Created new track_search binding to allow searching for tracks. [#18]
14 | * Adds release group urls. [#17]
15 |
16 | # 0.1.0 (July 18, 2011) ##
17 |
18 | * Initial version.
--------------------------------------------------------------------------------
/lib/musicbrainz/middleware.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | class Middleware < Faraday::Middleware
3 | def call(env)
4 | env[:request_headers].merge!(
5 | "User-Agent" => user_agent_string,
6 | "Via" => via_string
7 | )
8 | @app.call(env)
9 | end
10 |
11 | def user_agent_string
12 | "#{config.app_name}/#{config.app_version} ( #{config.contact} )"
13 | end
14 |
15 | def via_string
16 | "gem musicbrainz/#{VERSION} (#{GH_PAGE_URL})"
17 | end
18 |
19 | def config
20 | MusicBrainz.config
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/release_group.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module ReleaseGroup
4 | def parse(xml)
5 | xml = xml.xpath('./release-group') unless xml.xpath('./release-group').empty?
6 | {
7 | id: (xml.attribute('id').value rescue nil),
8 | type: (xml.attribute('type').value rescue nil),
9 | title: (xml.xpath('./title').text rescue nil),
10 | desc: (xml.xpath('./disambiguation').text rescue nil),
11 | first_release_date: (xml.xpath('./first-release-date').text rescue nil)
12 | }.merge(Relations.parse(xml))
13 | end
14 |
15 | extend self
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/release_group_search.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module ReleaseGroupSearch
4 | def parse(xml)
5 | xml.xpath('./release-group-list/release-group').map do |xml|
6 | {
7 | id: (xml.attribute('id').value rescue nil),
8 | mbid: (xml.attribute('id').value rescue nil), # Old shit
9 | title: (xml.xpath('./title').text.gsub(/[`’]/, "'") rescue nil),
10 | type: (xml.attribute('type').value rescue nil),
11 | score: (xml.attribute('score').value.to_i rescue nil)
12 | } rescue nil
13 | end.delete_if{ |item| item.nil? }
14 | end
15 |
16 | extend self
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | # Adopted from
2 | # https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-ruby
3 | name: Ruby CI
4 |
5 | on:
6 | push:
7 | branches: [ master ]
8 | pull_request:
9 | branches: [ master ]
10 |
11 | jobs:
12 | test:
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | ruby-version: ['3.3', '3.4']
17 | steps:
18 | - uses: actions/checkout@v4
19 | - name: Set up Ruby ${{ matrix.ruby-version }}
20 | uses: ruby/setup-ruby@v1.207.0
21 | with:
22 | ruby-version: ${{ matrix.ruby-version }}
23 | - name: Install dependencies
24 | run: bundle install
25 | - name: Run tests
26 | run: bundle exec rspec
27 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/artist.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module Artist
4 | def parse(xml)
5 | xml = xml.xpath('./artist')
6 |
7 | return {} if xml.empty?
8 |
9 | {
10 | id: (xml.attribute('id').value rescue nil),
11 | type: (xml.attribute('type').value rescue nil),
12 | name: (xml.xpath('./name').text.gsub(/[`’]/, "'") rescue nil),
13 | country: (xml.xpath('./country').text rescue nil),
14 | date_begin: (xml.xpath('./life-span/begin').text rescue nil),
15 | date_end: (xml.xpath('./life-span/end').text rescue nil)
16 | }.merge(Relations.parse(xml))
17 | end
18 |
19 | extend self
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/musicbrainz.gemspec:
--------------------------------------------------------------------------------
1 | require File.expand_path('../lib/musicbrainz/version', __FILE__)
2 |
3 | Gem::Specification.new do |gem|
4 | gem.authors = ["Gregory Eremin"]
5 | gem.email = ["magnolia_fan@me.com"]
6 | gem.summary = %q{ MusicBrainz Web Service wrapper with ActiveRecord-style models }
7 | gem.homepage = "http://github.com/localhots/musicbrainz"
8 |
9 | gem.files = %x{ git ls-files }.split($\)
10 | gem.executables = []
11 | gem.test_files = gem.files.grep(%r{^spec/})
12 | gem.name = "musicbrainz"
13 | gem.require_paths = %w[ lib ]
14 | gem.version = MusicBrainz::VERSION
15 | gem.license = "MIT"
16 |
17 | gem.add_runtime_dependency('faraday')
18 | gem.add_runtime_dependency('nokogiri')
19 | end
20 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/recording_search.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module RecordingSearch
4 | def parse(xml)
5 | xml.xpath('./recording-list/recording').map do |xml|
6 | {
7 | id: (xml.attribute('id').value rescue nil),
8 | mbid: (xml.attribute('id').value rescue nil), # Old shit
9 | title: (xml.xpath('./title').text.gsub(/[`’]/, "'") rescue nil),
10 | artist: (xml.xpath('./artist-credit/name-credit/artist/name').text rescue nil),
11 | releases: (xml.xpath('./release-list/release/title').map{ |xml| xml.text } rescue []),
12 | score: (xml.attribute('score').value.to_i rescue nil)
13 | } rescue nil
14 | end.delete_if{ |item| item.nil? }
15 | end
16 |
17 | extend self
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/musicbrainz/deprecated.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Deprecated
3 | module ProxyConfig
4 | def query_interval
5 | MusicBrainz.config.query_interval
6 | end
7 |
8 | def query_interval=(value)
9 | MusicBrainz.config.query_interval = value
10 | end
11 | end
12 |
13 | module CacheConfig
14 | def cache_path
15 | MusicBrainz.config.cache_path
16 | end
17 |
18 | def cache_path=(value)
19 | MusicBrainz.config.cache_path = value
20 | end
21 | end
22 | end
23 |
24 | module Tools
25 | module Proxy
26 | extend Deprecated::ProxyConfig
27 | end
28 |
29 | module Cache
30 | extend Deprecated::CacheConfig
31 | end
32 | end
33 |
34 | extend Deprecated::ProxyConfig
35 | extend Deprecated::CacheConfig
36 | end
37 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/recording.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module Recording
4 | def parse(xml)
5 | xml = xml.xpath('./recording') unless xml.xpath('./recording').empty?
6 | {
7 | id: (xml.attribute('id').value rescue nil),
8 | mbid: (xml.attribute('id').value rescue nil), # Old shit
9 | title: (xml.xpath('./title').text.gsub(/[`’]/, "'") rescue nil),
10 | artist: (xml.xpath('./artist-credit/name-credit/artist/name').text rescue nil),
11 | releases: (xml.xpath('./release-list/release/title').map{ |xml| xml.text } rescue []),
12 | score: (xml.attribute('score').value.to_i rescue nil),
13 | isrcs: (xml.xpath('./isrc-list/isrc/@id').map(&:text) rescue [])
14 | }
15 | end
16 |
17 | extend self
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/relations.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module Relations
4 | def parse(xml)
5 | hash = { urls: {} }
6 |
7 | xml.xpath('./relation-list[@target-type="url"]/relation').map do |xml|
8 | next unless type = xml.attribute('type')
9 |
10 | type = type.value.downcase.split(" ").join("_").to_sym
11 | target = xml.xpath('./target').text
12 |
13 | if hash[:urls][type].nil? then hash[:urls][type] = target
14 | elsif hash[:urls][type].is_a?(Array) then hash[:urls][type] << target
15 | else hash[:urls][type] = [hash[:urls][type]]; hash[:urls][type] << target
16 | end
17 | end
18 |
19 | hash
20 | end
21 |
22 | extend self
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/bindings/recording_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Bindings::Recording do
4 | describe '.parse' do
5 | subject(:parse) { described_class.parse(xml) }
6 |
7 | let(:xml) { Nokogiri::XML(<<~XML).at_xpath('recording') }
8 |
9 | Empire
10 | 233013
11 | 2006-08-25
12 |
13 |
14 |
15 |
16 | XML
17 |
18 | it 'contains recording attributes' do
19 | expect(parse).to include(
20 | id: 'b3015bab-1540-4d4e-9f30-14872a1525f7',
21 | mbid: 'b3015bab-1540-4d4e-9f30-14872a1525f7', title: 'Empire',
22 | isrcs: ['JPB600760301']
23 | )
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/spec/deprecated/cache_config_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Deprecated::CacheConfig do
4 | before(:all) {
5 | @old_cache_path = MusicBrainz.config.cache_path
6 | }
7 |
8 | before(:each) {
9 | MusicBrainz.config.cache_path = nil
10 | }
11 |
12 | after(:all) {
13 | MusicBrainz.config.cache_path = @old_cache_path
14 | }
15 |
16 | it "allows deprecated use of cache_path" do
17 | MusicBrainz.config.cache_path = "test1"
18 |
19 | expect(MusicBrainz::Tools::Cache.cache_path).to eq "test1"
20 | expect(MusicBrainz.cache_path).to eq "test1"
21 | end
22 |
23 | it "allows deprecated use of cache_path=" do
24 | MusicBrainz::Tools::Cache.cache_path = "test2"
25 | expect(MusicBrainz.config.cache_path).to eq "test2"
26 |
27 | MusicBrainz.cache_path = "test3"
28 | expect(MusicBrainz.config.cache_path).to eq "test3"
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/spec/deprecated/proxy_config_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Deprecated::ProxyConfig do
4 | before(:all) {
5 | @old_query_interval = MusicBrainz.config.query_interval
6 | }
7 |
8 | before(:each) {
9 | MusicBrainz.config.query_interval = nil
10 | }
11 |
12 | after(:all) {
13 | MusicBrainz.config.query_interval = @old_query_interval
14 | }
15 |
16 | it "allows deprecated use of query_interval" do
17 | MusicBrainz.config.query_interval = 2
18 |
19 | expect(MusicBrainz::Tools::Proxy.query_interval).to eq 2
20 | expect(MusicBrainz.query_interval).to eq 2
21 | end
22 |
23 | it "allows deprecated use of query_interval=" do
24 | MusicBrainz::Tools::Proxy.query_interval = 3
25 | expect(MusicBrainz.config.query_interval).to eq 3
26 |
27 | MusicBrainz.query_interval = 4
28 | expect(MusicBrainz.config.query_interval).to eq 4
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/spec/models/track_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Track do
4 | describe '.find' do
5 | before(:each) {
6 | mock url: 'http://musicbrainz.org/ws/2/recording/b3015bab-1540-4d4e-9f30-14872a1525f7?',
7 | fixture: 'recording/find_b3015bab-1540-4d4e-9f30-14872a1525f7.xml'
8 | }
9 | let(:track) {
10 | MusicBrainz::Track.find("b3015bab-1540-4d4e-9f30-14872a1525f7")
11 | }
12 |
13 | it "gets no exception while loading release info" do
14 | expect { track }.to_not raise_error(Exception)
15 | end
16 |
17 | it "gets correct instance" do
18 | expect(track).to be_an_instance_of(MusicBrainz::Track)
19 | end
20 |
21 | it "gets correct track data" do
22 | expect(track.recording_id).to eq "b3015bab-1540-4d4e-9f30-14872a1525f7"
23 | expect(track.title).to eq "Empire"
24 | expect(track.length).to eq 233013
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/artist_search.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module ArtistSearch
4 | def parse(xml)
5 | xml.xpath('./artist-list/artist').map do |xml|
6 | {
7 | id: (xml.attribute('id').value rescue nil),
8 | mbid: (xml.attribute('id').value rescue nil), # Old shit
9 | name: (xml.xpath('./name').text.gsub(/[`’]/, "'") rescue nil),
10 | sort_name: (xml.xpath('./sort-name').gsub(/[`’]/, "'") rescue nil),
11 | type: (xml.attribute('type').value rescue nil),
12 | score: (xml.attribute('score').value.to_i rescue nil),
13 | desc: (xml.xpath('./disambiguation').value rescue nil),
14 | aliases: (xml.xpath('./alias-list/alias').map{ |xml| xml.text } rescue [])
15 | } rescue nil
16 | end.delete_if{ |item| item.nil? }
17 | end
18 |
19 | extend self
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/spec/bindings/track_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Bindings::Track do
4 | describe '.parse' do
5 | subject(:parse) { described_class.parse(xml) }
6 |
7 | let(:xml) { Nokogiri::XML(<<~XML).at_xpath('track') }
8 |
18 | XML
19 |
20 | it 'contains track attributes' do
21 | expect(parse).to include(
22 | id: '0501cf38-48b7-3026-80d2-717d574b3d6a', position: '1', length: '233013',
23 | recording_id: 'b3015bab-1540-4d4e-9f30-14872a1525f7', title: 'Empire'
24 | )
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/musicbrainz/client_modules/failsafe_proxy.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module ClientModules
3 | module FailsafeProxy
4 | def get_contents(url)
5 | return super unless failsafe?
6 |
7 | response = { body: nil, status: 500 }
8 | MusicBrainz.config.tries_limit.times do
9 | wait_util_ready!
10 | response = super
11 | break if response[:status] == 200
12 | end
13 |
14 | response
15 | end
16 |
17 | def time_passed
18 | Time.now.to_f - @last_query_time ||= 0.0
19 | end
20 |
21 | def time_to_wait
22 | MusicBrainz.config.query_interval - time_passed
23 | end
24 |
25 | def ready?
26 | time_passed > MusicBrainz.config.query_interval
27 | end
28 |
29 | def wait_util_ready!
30 | sleep(time_to_wait) unless ready?
31 | @last_query_time = Time.now.to_f
32 | end
33 |
34 | def failsafe?
35 | MusicBrainz.config.tries_limit > 1 && MusicBrainz.config.query_interval.to_f > 0
36 | end
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/bindings/release_group_search_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Bindings::ReleaseGroupSearch do
4 | describe '.parse' do
5 | let(:response) {
6 | <<-XML
7 |
8 |
9 |
10 | Empire
11 |
12 |
13 |
14 | XML
15 | }
16 | let(:xml) {
17 | Nokogiri::XML.parse(response)
18 | }
19 | let(:metadata) {
20 | described_class.parse(xml.remove_namespaces!.xpath('/metadata'))
21 | }
22 |
23 | it "gets correct release group data" do
24 | expect(metadata).to eq [
25 | {
26 | id: '246bc928-2dc8-35ba-80ee-7a0079de1632',
27 | mbid: '246bc928-2dc8-35ba-80ee-7a0079de1632',
28 | title: 'Empire',
29 | type: 'Single',
30 | score: 100,
31 | }
32 | ]
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2024 Gregory Eremin
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/lib/musicbrainz/bindings/release.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module Bindings
3 | module Release
4 | def parse(xml)
5 | xml = xml.xpath('./release') unless xml.xpath('./release').empty?
6 |
7 | hash = {
8 | id: (xml.attribute('id').value rescue nil),
9 | type: (xml.xpath('./release-group').attribute('type').value rescue nil),
10 | title: (xml.xpath('./title').text rescue nil),
11 | status: (xml.xpath('./status').text rescue nil),
12 | country: (xml.xpath('./country').text rescue nil),
13 | date: (xml.xpath('./date').text rescue nil),
14 | asin: (xml.xpath('./asin').text rescue nil),
15 | barcode: (xml.xpath('./barcode').text rescue nil),
16 | quality: (xml.xpath('./quality').text rescue nil)
17 | }
18 |
19 | formats = (xml.xpath('./medium-list/medium/format') rescue []).map(&:text)
20 |
21 | hash[:format] = formats.uniq.map do |format|
22 | format_count = formats.select{|f| f == format}.length
23 | format_count == 1 ? format : "#{format_count}x#{format}"
24 | end.join(' + ')
25 |
26 | hash
27 | end
28 |
29 | extend self
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/musicbrainz/models/artist.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | class Artist < BaseModel
3 | field :id, String
4 | field :type, String
5 | field :name, String
6 | field :country, String
7 | field :date_begin, Date
8 | field :date_end, Date
9 | field :urls, Hash
10 |
11 | def release_groups
12 | @release_groups ||= client.load(:release_group, { artist: id, inc: [:url_rels] }, {
13 | binding: :artist_release_groups,
14 | create_models: :release_group,
15 | sort: :first_release_date
16 | }) unless @id.nil?
17 | end
18 |
19 | class << self
20 | def find(id)
21 | client.load(:artist, { id: id, inc: [:url_rels] }, {
22 | binding: :artist,
23 | create_model: :artist
24 | })
25 | end
26 |
27 | def search(name)
28 | super({artist: name})
29 | end
30 |
31 | def discography(mbid)
32 | artist = find(mbid)
33 | artist.release_groups.each { |rg| rg.releases.each { |r| r.tracks } }
34 | artist
35 | end
36 |
37 | def find_by_name(name)
38 | matches = search(name)
39 | if matches and not matches.empty?
40 | find(matches.first[:id])
41 | end
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | musicbrainz (0.8.0)
5 | faraday
6 | nokogiri
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | awesome_print (1.9.2)
12 | coderay (1.1.3)
13 | diff-lcs (1.5.1)
14 | faraday (2.9.0)
15 | faraday-net_http (>= 2.0, < 3.2)
16 | faraday-net_http (3.1.0)
17 | net-http
18 | method_source (1.0.0)
19 | net-http (0.4.1)
20 | uri
21 | nokogiri (1.16.3-x86_64-linux)
22 | racc (~> 1.4)
23 | pry (0.14.2)
24 | coderay (~> 1.1)
25 | method_source (~> 1.0)
26 | racc (1.7.3)
27 | rake (13.1.0)
28 | rspec (3.13.0)
29 | rspec-core (~> 3.13.0)
30 | rspec-expectations (~> 3.13.0)
31 | rspec-mocks (~> 3.13.0)
32 | rspec-core (3.13.0)
33 | rspec-support (~> 3.13.0)
34 | rspec-expectations (3.13.0)
35 | diff-lcs (>= 1.2.0, < 2.0)
36 | rspec-support (~> 3.13.0)
37 | rspec-mocks (3.13.0)
38 | diff-lcs (>= 1.2.0, < 2.0)
39 | rspec-support (~> 3.13.0)
40 | rspec-support (3.13.1)
41 | uri (0.13.0)
42 |
43 | PLATFORMS
44 | x86_64-linux
45 |
46 | DEPENDENCIES
47 | awesome_print
48 | musicbrainz!
49 | pry
50 | rake (~> 13.1.0)
51 | rspec
52 |
53 | BUNDLED WITH
54 | 2.3.26
55 |
--------------------------------------------------------------------------------
/spec/bindings/medium_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Bindings::Medium do
4 | describe '.parse' do
5 | subject(:parse) { described_class.parse(xml) }
6 |
7 | let(:xml) { Nokogiri::XML(<<~XML).at_xpath('medium') }
8 |
9 | 1
10 | CD
11 |
12 |
22 |
23 |
24 | XML
25 |
26 | it 'contains medium attributes' do
27 | expect(parse).to include(position: '1', format: 'CD')
28 | end
29 |
30 | it 'contains track attributes' do
31 | expect(parse[:tracks].first).to include(
32 | id: '0501cf38-48b7-3026-80d2-717d574b3d6a', position: '1', length: '233013',
33 | recording_id: 'b3015bab-1540-4d4e-9f30-14872a1525f7', title: 'Empire'
34 | )
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/spec/fixtures/release_group/find_6f33e0f0-cde2-38f9-9aee-2c60af8d1a61.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Empire
5 | 2006-08-28
6 | Album
7 |
8 |
9 | http://en.wikipedia.org/wiki/Empire_(Kasabian_album)
10 |
11 |
12 | http://lyrics.wikia.com/Kasabian:Empire_(2006)
13 |
14 |
15 | http://www.bbc.co.uk/music/reviews/389x
16 |
17 |
18 | http://www.discogs.com/master/89980
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/lib/musicbrainz/models/release_group.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | class ReleaseGroup < BaseModel
3 | field :id, String
4 | field :type, String
5 | field :title, String
6 | field :desc, String
7 | field :first_release_date, Date
8 | field :urls, Hash
9 |
10 | alias_method :disambiguation, :desc
11 |
12 | def releases
13 | @releases ||= client.load(:release, { release_group: id, inc: [:media, :release_groups], limit: 100 }, {
14 | binding: :release_group_releases,
15 | create_models: :release,
16 | sort: :date
17 | }) unless @id.nil?
18 | end
19 |
20 | class << self
21 | def find(id)
22 | client.load(:release_group, { id: id, inc: [:url_rels] }, {
23 | binding: :release_group,
24 | create_model: :release_group
25 | })
26 | end
27 |
28 | def search(artist_name, title, type = nil)
29 | if type
30 | super({artist: artist_name, releasegroup: title, type: type})
31 | else
32 | super({artist: artist_name, releasegroup: title})
33 | end
34 | end
35 |
36 | def find_by_artist_and_title(artist_name, title, type = nil )
37 | matches = search(artist_name, title, type)
38 | matches.nil? || matches.empty? ? nil : find(matches.first[:id])
39 | end
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/musicbrainz/models/release.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | class Release < BaseModel
3 | field :id, String
4 | field :type, String
5 | field :title, String
6 | field :status, String
7 | field :format, String
8 | field :date, Date
9 | field :country, String
10 | field :asin, String
11 | field :barcode, String
12 | field :quality, String
13 |
14 | def mediums
15 | @mediums ||= client.load(:release, { id: id, inc: [:recordings, :media], limit: 100 }, {
16 | binding: :release_mediums,
17 | create_models: :medium,
18 | sort: :position
19 | }) unless @id.nil?
20 | end
21 |
22 | def tracks
23 | @tracks ||= client.load(:release, { id: id, inc: [:recordings, :media], limit: 100 }, {
24 | binding: :release_tracks,
25 | create_models: :track,
26 | sort: :position
27 | }) unless @id.nil?
28 | end
29 |
30 | class << self
31 | def find(id)
32 | client.load(:release, { id: id, inc: [:media, :release_groups] }, {
33 | binding: :release,
34 | create_model: :release
35 | })
36 | end
37 |
38 | def find_by_discid(id)
39 | client.load(:discid, { id: id }, {
40 | binding: :discid_releases,
41 | create_models: :release
42 | })
43 | end
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/musicbrainz.rb:
--------------------------------------------------------------------------------
1 | require "digest/sha1"
2 | require "fileutils"
3 | require "date"
4 |
5 | require "faraday"
6 | require "nokogiri"
7 |
8 | require "musicbrainz/version"
9 | require "musicbrainz/deprecated"
10 | require "musicbrainz/middleware"
11 | require "musicbrainz/configuration"
12 |
13 | require "musicbrainz/client_modules/transparent_proxy"
14 | require "musicbrainz/client_modules/failsafe_proxy"
15 | require "musicbrainz/client_modules/caching_proxy"
16 | require "musicbrainz/client"
17 |
18 | require "musicbrainz/models/base_model"
19 | require "musicbrainz/models/artist"
20 | require "musicbrainz/models/medium"
21 | require "musicbrainz/models/release_group"
22 | require "musicbrainz/models/release"
23 | require "musicbrainz/models/track"
24 | require "musicbrainz/models/recording"
25 |
26 | require "musicbrainz/bindings/artist"
27 | require "musicbrainz/bindings/artist_search"
28 | require "musicbrainz/bindings/artist_release_groups"
29 | require "musicbrainz/bindings/discid_releases"
30 | require "musicbrainz/bindings/medium"
31 | require "musicbrainz/bindings/relations"
32 | require "musicbrainz/bindings/release_group"
33 | require "musicbrainz/bindings/release_group_search"
34 | require "musicbrainz/bindings/release_group_releases"
35 | require "musicbrainz/bindings/release"
36 | require "musicbrainz/bindings/release_mediums"
37 | require "musicbrainz/bindings/release_tracks"
38 | require "musicbrainz/bindings/track"
39 | require "musicbrainz/bindings/recording"
40 | require "musicbrainz/bindings/recording_search"
41 |
42 | module MusicBrainz
43 | GH_PAGE_URL = "http://git.io/brainz"
44 | end
45 |
--------------------------------------------------------------------------------
/spec/models/recording_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Recording do
4 | describe '.find' do
5 | before(:each) {
6 | mock url: 'http://musicbrainz.org/ws/2/recording/b3015bab-1540-4d4e-9f30-14872a1525f7?inc=isrcs',
7 | fixture: 'recording/find_b3015bab-1540-4d4e-9f30-14872a1525f7.xml'
8 | }
9 | let(:recording) {
10 | MusicBrainz::Recording.find("b3015bab-1540-4d4e-9f30-14872a1525f7")
11 | }
12 |
13 | it "gets no exception while loading release info" do
14 | expect{ recording }.to_not raise_error(Exception)
15 | end
16 |
17 | it "gets correct instance" do
18 | expect(recording).to be_an_instance_of(MusicBrainz::Recording)
19 | end
20 |
21 | it "gets correct track data" do
22 | expect(recording.title).to eq "Empire"
23 | expect(recording.isrcs).to eq ["JPB600760301"]
24 | end
25 | end
26 |
27 | describe '.search' do
28 | before(:each) {
29 | mock url: 'http://musicbrainz.org/ws/2/recording?query=recording:"Bound+for+the+floor" AND artist:"Local+H"&limit=10',
30 | fixture: 'recording/search_bound_for_the_floor_local_h.xml'
31 | }
32 | let(:matches) {
33 | MusicBrainz::Recording.search('Bound for the floor', 'Local H')
34 | }
35 |
36 | it "searches tracks (aka recordings) by artist name and title" do
37 | expect(matches).to_not be_empty
38 | expect(matches.first[:id]).to eq 'bb940e26-4265-4909-b128-4906d8b1079e'
39 | expect(matches.first[:title]).to eq "Bound for the Floor"
40 | expect(matches.first[:artist]).to eq "Local H"
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/musicbrainz/client_modules/caching_proxy.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | module ClientModules
3 | module CachingProxy
4 | def cache_path
5 | MusicBrainz.config.cache_path
6 | end
7 |
8 | def clear_cache
9 | FileUtils.rm_r(cache_path) if cache_path && File.exist?(cache_path)
10 | end
11 |
12 | def get_contents(url)
13 | return super unless caching?
14 |
15 | hash = Digest::SHA256.hexdigest(url)
16 | dir_path = [cache_path, *(0..2).map{ |i| hash.slice(2*i, 2) }].join(?/)
17 | file_path = [dir_path, '/', hash.slice(6, 58), '.xml'].join
18 |
19 | response = { body: nil, status: 500 }
20 |
21 | if File.exist?(file_path)
22 | get_cached_contents(file_path)
23 | else
24 | response = get_live_contents(file_path, url)
25 | if response[:status] == 200
26 | cache_contents!(file_path, response[:body])
27 | end
28 | response
29 | end
30 | end
31 |
32 | def caching?
33 | MusicBrainz.config.perform_caching
34 | end
35 |
36 | private
37 |
38 | def get_cached_contents(file_path)
39 | {
40 | body: File.open(file_path, 'rb').gets,
41 | status: 200
42 | }
43 | end
44 |
45 | def get_live_contents(file_path, url)
46 | http.get(url)
47 | end
48 |
49 | def cache_contents!(file_path, body)
50 | dir_path = File.dirname(file_path)
51 | FileUtils.mkpath(dir_path)
52 | File.open(file_path, 'wb') do |f|
53 | f.puts(body)
54 | f.chmod(0755)
55 | f.close
56 | end
57 | end
58 | end
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/spec/bindings/recording_search_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Bindings::RecordingSearch do
4 | describe '.parse' do
5 | let(:response) {
6 | <<-XML
7 |
8 |
9 |
10 | King Fred
11 |
12 |
13 |
14 | Too Much Joy
15 |
16 |
17 |
18 |
19 |
20 | Green Eggs and Crack
21 |
22 |
23 |
24 |
25 |
26 | XML
27 | }
28 | let(:xml) {
29 | Nokogiri::XML.parse(response).remove_namespaces!
30 | }
31 | let(:metadata) {
32 | described_class.parse(xml.xpath('/metadata'))
33 | }
34 |
35 | it "gets correct Recording data" do
36 | expect(metadata).to eq [
37 | {
38 | id: '0b382a13-32f0-4743-9248-ba5536a6115e',
39 | mbid: '0b382a13-32f0-4743-9248-ba5536a6115e',
40 | title: 'King Fred',
41 | artist: 'Too Much Joy',
42 | releases: ['Green Eggs and Crack'],
43 | score: 100,
44 | }
45 | ]
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/spec/fixtures/release/find_2225dd4c-ae9a-403b-8ea0-9e05014c778f.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Empire
5 | Official
6 | normal
7 |
8 | eng
9 |
10 |
11 |
12 | Empire
13 | 2006-08-25
14 | Album
15 |
16 | 2006-08-28
17 | GB
18 |
19 |
20 | 2006-08-28
21 |
22 | United Kingdom
23 | United Kingdom
24 |
25 | GB
26 |
27 |
28 |
29 |
30 | B000H49P28
31 |
32 | true
33 | 1
34 | true
35 | false
36 |
37 |
38 |
39 | 1
40 | CD
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/spec/fixtures/release/find_b94cb547-cf7a-4357-894c-53c3bf33b093.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Humanoid
5 | Official
6 | normal
7 |
8 | eng
9 |
10 |
11 |
12 | Humanoid
13 | English version
14 | 2009-10-02
15 | Album
16 |
17 | 2009-10-06
18 | US
19 |
20 |
21 | 2009-10-06
22 |
23 | United States
24 | United States
25 |
26 | US
27 |
28 |
29 |
30 |
31 | 602527197692
32 | B002NOYX6I
33 |
34 | false
35 | 0
36 | false
37 | false
38 |
39 |
40 |
41 | 1
42 | CD
43 |
44 |
45 |
46 | 2
47 | CD
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/spec/models/base_model_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::BaseModel do
4 | describe '#validate_type' do
5 | describe 'Date' do
6 | let(:xml) {
7 | Nokogiri::XML.parse(response)
8 | }
9 | let(:bindings) {
10 | MusicBrainz::Bindings::ReleaseGroup.parse(xml)
11 | }
12 | let(:release_group) {
13 | MusicBrainz::ReleaseGroup.new(bindings)
14 | }
15 |
16 | context 'nil value' do
17 | let(:response) {
18 | <<-XML
19 |
20 |
21 |
22 | XML
23 | }
24 |
25 | it 'returns 2030-12-31' do
26 | expect(release_group.first_release_date).to eq Date.new(2030, 12, 31)
27 | end
28 | end
29 |
30 | context 'year only' do
31 | let(:response) {
32 | <<-XML
33 |
34 | 1995
35 |
36 | XML
37 | }
38 |
39 | it 'returns 1995-12-31' do
40 | expect(release_group.first_release_date).to eq Date.new(1995, 12, 31)
41 | end
42 | end
43 |
44 | context 'year and month only' do
45 | let(:response) {
46 | <<-XML
47 |
48 | 1995-04
49 |
50 | XML
51 | }
52 |
53 | it 'returns 1995-04-30' do
54 | expect(release_group.first_release_date).to eq Date.new(1995, 4, 30)
55 | end
56 | end
57 |
58 | context 'year, month and day' do
59 | let(:response) {
60 | <<-XML
61 |
62 | 1995-04-30
63 |
64 | XML
65 | }
66 |
67 | it 'returns 1995-04-30' do
68 | expect(release_group.first_release_date).to eq Date.new(1995, 4, 30)
69 | end
70 | end
71 | end
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/lib/musicbrainz/configuration.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | class Configuration
3 | attr_accessor :app_name, :app_version, :contact,
4 | :web_service_url,
5 | :query_interval, :tries_limit,
6 | :cache_path, :perform_caching
7 |
8 | DEFAULT_WEB_SERVICE_URL = "http://musicbrainz.org/ws/2/"
9 | DEFAULT_QUERY_INTERVAL = 1.5
10 | DEFAULT_TRIES_LIMIT = 5
11 | DEFAULT_CACHE_PATH = File.join(File.dirname(__FILE__), "..", "tmp", "cache")
12 | DEFAULT_PERFORM_CACHING = false
13 |
14 | def initialize
15 | @web_service_url = DEFAULT_WEB_SERVICE_URL
16 | @query_interval = DEFAULT_QUERY_INTERVAL
17 | @tries_limit = DEFAULT_TRIES_LIMIT
18 | @cache_path = DEFAULT_CACHE_PATH
19 | @perform_caching = DEFAULT_PERFORM_CACHING
20 | end
21 |
22 | def valid?
23 | %w[ app_name app_version contact ].each do |param|
24 | unless instance_variable_defined?(:"@#{param}")
25 | raise Exception.new("Application identity parameter '#{param}' missing")
26 | end
27 | end
28 | unless tries_limit.nil? && query_interval.nil?
29 | raise Exception.new("'tries_limit' parameter must be 1 or greater") if tries_limit.to_i < 1
30 | raise Exception.new("'query_interval' parameter must be greater than zero") if query_interval.to_f < 0
31 | end
32 | if perform_caching
33 | raise Exception.new("'cache_path' parameter must be set") if cache_path.nil?
34 | end
35 | true
36 | end
37 | end
38 |
39 | module Configurable
40 | def configure
41 | raise Exception.new("Configuration block missing") unless block_given?
42 | yield @config ||= MusicBrainz::Configuration.new
43 | config.valid?
44 | end
45 |
46 | def config
47 | raise Exception.new("Configuration missing") unless instance_variable_defined?(:@config)
48 | @config
49 | end
50 |
51 | def apply_test_configuration!
52 | configure do |c|
53 | c.app_name = "gem musicbrainz (development mode)"
54 | c.app_version = MusicBrainz::VERSION
55 | c.contact = `git config user.email`.chomp
56 | end
57 | end
58 | end
59 | extend Configurable
60 | end
61 |
--------------------------------------------------------------------------------
/lib/musicbrainz/client.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | class Client
3 | include ClientModules::TransparentProxy
4 | include ClientModules::FailsafeProxy
5 | include ClientModules::CachingProxy
6 |
7 | def http
8 | @faraday ||= Faraday.new do |f|
9 | f.request :url_encoded # form-encode POST params
10 | f.adapter Faraday.default_adapter # make requests with Net::HTTP
11 | f.use MusicBrainz::Middleware # run requests with correct headers
12 | end
13 | end
14 |
15 | def load(resource, query, params)
16 | raise Exception.new("You need to run MusicBrainz.configure before querying") if MusicBrainz.config.nil?
17 |
18 | url = build_url(resource, query)
19 | response = get_contents(url)
20 |
21 | return nil if response[:status] != 200
22 |
23 | xml = Nokogiri::XML.parse(response[:body]).remove_namespaces!.xpath('/metadata')
24 | data = binding_class_for(params[:binding]).parse(xml)
25 |
26 | if params[:create_model]
27 | model_class_for(params[:create_model]).new(data)
28 | elsif params[:create_models]
29 | models = data.map{ |item| model_class_for(params[:create_models]).new(item) }
30 | models.sort!{ |a, b| a.send(params[:sort]) <=> b.send(params[:sort]) } if params[:sort]
31 | models
32 | else
33 | data
34 | end
35 | end
36 |
37 | private
38 |
39 | def build_url(resource, params)
40 | "#{MusicBrainz.config.web_service_url}#{resource.to_s.gsub('_', '-')}" <<
41 | ((id = params.delete(:id)) ? "/#{id}?" : "?") <<
42 | params.map do |key, value|
43 | key = key.to_s.gsub('_', '-')
44 | value = if value.is_a?(Array)
45 | value.map{ |el| el.to_s.gsub('_', '-') }.join(?+)
46 | else
47 | value.to_s
48 | end
49 | [key, value].join(?=)
50 | end.join(?&)
51 | end
52 |
53 | def binding_class_for(key)
54 | MusicBrainz::Bindings.const_get(constantized(key))
55 | end
56 |
57 | def model_class_for(key)
58 | MusicBrainz.const_get(constantized(key))
59 | end
60 |
61 | def constantized(key)
62 | key.to_s.split(?_).map(&:capitalize).join.to_sym
63 | end
64 | end
65 |
66 | module ClientHelper
67 | def client
68 | @client ||= Client.new
69 | end
70 | end
71 | extend ClientHelper
72 | end
73 |
--------------------------------------------------------------------------------
/spec/bindings/relations_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Bindings::Relations do
4 | describe '.parse' do
5 | describe 'attributes' do
6 | describe 'urls' do
7 | context '1 url for relation type' do
8 | let(:response) {
9 | <<-XML
10 |
11 |
12 |
13 | https://plus.google.com/+Madonna
14 |
15 |
16 |
17 | XML
18 | }
19 | let(:xml) {
20 | Nokogiri::XML.parse(response)
21 | }
22 | let(:artist) {
23 | described_class.parse(xml.xpath('./artist'))
24 | }
25 |
26 | it 'returns a string' do
27 | expect(artist[:urls][:social_network]).to eq 'https://plus.google.com/+Madonna'
28 | end
29 | end
30 |
31 | context 'multiple urls for relation types' do
32 | let(:response) {
33 | <<-XML
34 |
35 |
36 |
37 | https://plus.google.com/+Madonna
38 |
39 |
40 | https://www.facebook.com/madonna
41 |
42 |
43 |
44 | XML
45 | }
46 | let(:xml) {
47 | Nokogiri::XML.parse(response)
48 | }
49 | let(:artist) {
50 | described_class.parse(xml.xpath('./artist'))
51 | }
52 |
53 | it 'returns an array' do
54 | expect(artist[:urls][:social_network]).to eq [
55 | 'https://plus.google.com/+Madonna',
56 | 'https://www.facebook.com/madonna',
57 | ]
58 | end
59 | end
60 | end
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/spec/client_modules/cache_spec.rb:
--------------------------------------------------------------------------------
1 | require "ostruct"
2 | require "spec_helper"
3 |
4 | describe MusicBrainz::ClientModules::CachingProxy do
5 | let(:test_mbid){ "69b39eab-6577-46a4-a9f5-817839092033" }
6 | let(:tmp_cache_path){ File.join(File.dirname(__FILE__), '..', '..', 'tmp', 'cache_module_spec_cache') }
7 | let(:test_cache_file){ "#{tmp_cache_path}/03/48/ec/6c2bee685d9a96f95ed46378f624714e7a4650b0d44c1a8eee5bac2480.xml" }
8 | let(:test_response){ read_fixture('artist/find_69b39eab-6577-46a4-a9f5-817839092033.xml') }
9 |
10 | before(:all) do
11 | MusicBrainz.config.cache_path = File.join(File.dirname(__FILE__), '..', '..', 'tmp', 'cache_module_spec_cache')
12 | end
13 |
14 | before do
15 | File.delete(test_cache_file) if File.exist?(test_cache_file)
16 | end
17 |
18 | after(:all) do
19 | MusicBrainz.config.cache_path = File.join(File.dirname(__FILE__), '..', '..', 'tmp', 'spec_cache')
20 | MusicBrainz.config.perform_caching = true
21 | MusicBrainz.config.query_interval = 1.5
22 | end
23 |
24 | context "with cache enabled" do
25 | it "calls http only once when requesting the resource twice" do
26 | MusicBrainz.config.perform_caching = true
27 | expect(File).to_not exist(test_cache_file)
28 |
29 | # Stubbing
30 | allow(MusicBrainz.client).to receive(:get_live_contents)
31 | .and_return({status: 200, body: test_response})
32 | expect(MusicBrainz.client).to receive(:get_live_contents).once
33 |
34 | 2.times do
35 | artist = MusicBrainz::Artist.find(test_mbid)
36 | expect(artist).to be_kind_of(MusicBrainz::Artist)
37 | expect(File).to exist(test_cache_file)
38 | end
39 |
40 | MusicBrainz.client.clear_cache
41 | end
42 | end
43 |
44 | context "with cache disabled" do
45 | it "calls http twice when requesting the resource twice" do
46 | MusicBrainz.config.perform_caching = false
47 | expect(File).to_not exist(test_cache_file)
48 |
49 | # Hacking for test performance purposes
50 | MusicBrainz.config.query_interval = 0.0
51 |
52 | # Stubbing
53 | allow(MusicBrainz.client.http).to receive(:get).and_return(OpenStruct.new(status: 200, body: test_response))
54 | expect(MusicBrainz.client.http).to receive(:get).twice
55 |
56 | 2.times do
57 | artist = MusicBrainz::Artist.find(test_mbid)
58 | expect(artist).to be_kind_of(MusicBrainz::Artist)
59 | expect(File).to_not exist(test_cache_file)
60 | end
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/spec/fixtures/release_group/search_kasabian_empire.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Empire
6 | Album
7 |
8 |
9 |
10 | Kasabian
11 | Kasabian
12 |
13 |
14 |
15 |
16 |
17 | Empire
18 | Official
19 |
20 |
21 | Empire
22 | Official
23 |
24 |
25 | Empire
26 | Official
27 |
28 |
29 | Empire
30 | Official
31 |
32 |
33 | Empire
34 | Official
35 |
36 |
37 | Empire
38 | Official
39 |
40 |
41 | Empire
42 | Official
43 |
44 |
45 |
46 |
47 | rock
48 |
49 |
50 | alternative rock
51 |
52 |
53 | indie
54 |
55 |
56 | rock and indie
57 |
58 |
59 |
60 |
61 | Empire
62 | Single
63 |
64 |
65 |
66 | Kasabian
67 | Kasabian
68 |
69 |
70 |
71 |
72 |
73 | Empire
74 | Official
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/lib/musicbrainz/models/base_model.rb:
--------------------------------------------------------------------------------
1 | module MusicBrainz
2 | class BaseModel
3 | def self.inherited(klass)
4 | klass.send(:include, InstanceMethods)
5 | klass.send(:extend, ClassMethods)
6 | end
7 |
8 | module ClassMethods
9 | def field(name, type)
10 | fields[name] = type
11 | attr_reader name
12 |
13 | define_method("#{name}=") do |val|
14 | instance_variable_set("@#{name}", validate_type(val, type))
15 | end
16 | end
17 |
18 | def fields
19 | instance_variable_set(:@fields, {}) unless instance_variable_defined?(:@fields)
20 | instance_variable_get(:@fields)
21 | end
22 |
23 | def client
24 | MusicBrainz.client
25 | end
26 |
27 | def find(hash)
28 | underscored_name = underscore_name.to_sym
29 | client.load(underscored_name, hash, { binding: underscored_name, create_model: underscored_name })
30 | end
31 |
32 | def search(hash)
33 | hash = escape_strings(hash)
34 | query_val = build_query(hash)
35 | underscored_name = underscore_name
36 | client.load(underscored_name.to_sym, { query: query_val, limit: 10 }, { binding: underscored_name.insert(-1,"_search").to_sym })
37 | end
38 |
39 | class ::String
40 | def underscore
41 | self.gsub(/::/, '/').
42 | gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
43 | gsub(/([a-z\d])([A-Z])/,'\1_\2').
44 | tr("-", "_").
45 | downcase
46 | end
47 | end
48 |
49 | def build_query(hash)
50 | return ["#{hash.first[0].to_s}:\"#{hash.first[1]}\""] if hash.size ==1
51 | arr ||= []
52 | hash.each { |k, v| arr << "#{k.to_s}:\"#{hash[k]}\"" }
53 | arr.join(' AND ')
54 | end
55 |
56 | def escape_strings(hash)
57 | hash.each { |k, v| hash[k] = CGI.escape(v).gsub(/\!/, '\!') }
58 | hash
59 | end
60 |
61 | def underscore_name
62 | # self.name[13..-1] => removes MusicBrainz::
63 | self.name[13..-1].underscore
64 | end
65 |
66 | # these probably should be private... but I'm not sure how to get it to work in a module...
67 | # private_class_method :build_query, :escape_strings
68 | end
69 |
70 | module InstanceMethods
71 | def initialize(params = {})
72 | params.each do |field, value|
73 | self.send(:"#{field}=", value)
74 | end
75 | end
76 |
77 | def client
78 | MusicBrainz.client
79 | end
80 |
81 | private
82 |
83 | def validate_type(val, type)
84 | if type == Integer
85 | val.to_i
86 | elsif type == Float
87 | val.to_f
88 | elsif type == String
89 | val.to_s
90 | elsif type == Date
91 | val = if val.nil? or val == ""
92 | [2030, 12, 31]
93 | elsif val.split("-").length == 1
94 | [val.split("-").first.to_i, 12, 31]
95 | elsif val.split("-").length == 2
96 | val = val.split("-").map(&:to_i)
97 | [val.first, val.last, -1]
98 | else
99 | val.split("-").map(&:to_i)
100 | end
101 |
102 | Date.new(*val)
103 | elsif type == Array
104 | Array(val)
105 | else
106 | val
107 | end
108 | end
109 | end
110 | end
111 | end
112 |
--------------------------------------------------------------------------------
/spec/models/artist_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Artist do
4 | describe '.search' do
5 | before(:each) {
6 | mock url: 'http://musicbrainz.org/ws/2/artist?query=artist:"Kasabian"&limit=10',
7 | fixture: 'artist/search_kasabian.xml'
8 | }
9 | let(:matches) {
10 | MusicBrainz::Artist.search('Kasabian')
11 | }
12 |
13 | it "searches artist by name" do
14 | expect(matches).to_not be_empty
15 | expect(matches.first[:name]).to eq("Kasabian")
16 | end
17 |
18 | it "should return search results in the right order and pass back the correct score" do
19 | expect(matches.length).to be >= 2
20 | expect(matches[0][:score]).to eq 100
21 | expect(matches[0][:id]).to eq "69b39eab-6577-46a4-a9f5-817839092033"
22 | expect(matches[1][:score]).to eq 73
23 | expect(matches[1][:id]).to eq "d76aed40-1332-44db-9b19-aee7ec17464b"
24 | end
25 | end
26 |
27 | describe '.find' do
28 | before(:each) {
29 | mock url: 'http://musicbrainz.org/ws/2/artist/69b39eab-6577-46a4-a9f5-817839092033?inc=url-rels',
30 | fixture: 'artist/find_69b39eab-6577-46a4-a9f5-817839092033.xml'
31 | }
32 | let(:artist) {
33 | MusicBrainz::Artist.find('69b39eab-6577-46a4-a9f5-817839092033')
34 | }
35 |
36 | it "gets no exception while loading artist info" do
37 | expect{ artist }.to_not raise_error(Exception)
38 | expect(artist.id).to eq '69b39eab-6577-46a4-a9f5-817839092033'
39 | expect(artist.name).to eq 'Kasabian'
40 | end
41 | end
42 |
43 | describe '.find_by_name' do
44 | before(:each) {
45 | mock url: 'http://musicbrainz.org/ws/2/artist?query=artist:"Kasabian"&limit=10',
46 | fixture: 'artist/search_kasabian.xml'
47 | mock url: 'http://musicbrainz.org/ws/2/artist/69b39eab-6577-46a4-a9f5-817839092033?inc=url-rels',
48 | fixture: 'artist/find_69b39eab-6577-46a4-a9f5-817839092033.xml'
49 | }
50 | let(:artist) {
51 | MusicBrainz::Artist.find_by_name('Kasabian')
52 | }
53 |
54 | it "gets correct instance" do
55 | expect(artist).to be_instance_of(MusicBrainz::Artist)
56 | end
57 |
58 | it "gets correct result by name" do
59 | expect(artist.id).to eq "69b39eab-6577-46a4-a9f5-817839092033"
60 | end
61 |
62 | it "gets correct artist data" do
63 | expect(artist.id).to eq "69b39eab-6577-46a4-a9f5-817839092033"
64 | expect(artist.type).to eq "Group"
65 | expect(artist.name).to eq "Kasabian"
66 | expect(artist.country).to eq "GB"
67 | expect(artist.date_begin.year).to eq 1997
68 | end
69 |
70 | describe '#release_groups' do
71 | before(:each) {
72 | mock url: 'http://musicbrainz.org/ws/2/release-group?artist=69b39eab-6577-46a4-a9f5-817839092033&inc=url-rels',
73 | fixture: 'artist/release_groups_69b39eab-6577-46a4-a9f5-817839092033.xml'
74 | }
75 | let(:release_groups) {
76 | artist.release_groups
77 | }
78 |
79 | it "gets correct artist's release groups" do
80 | release_groups = MusicBrainz::Artist.find_by_name('Kasabian').release_groups
81 | expect(release_groups.length).to be >= 16
82 | expect(release_groups.first.id).to eq "5547285f-578f-3236-85aa-b65cc0923b58"
83 | expect(release_groups.first.type).to eq "Single"
84 | expect(release_groups.first.title).to eq "Reason Is Treason"
85 | expect(release_groups.first.first_release_date).to eq Date.new(2004, 2, 23)
86 | expect(release_groups.first.urls[:discogs]).to eq 'https://www.discogs.com/master/125172'
87 | end
88 | end
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/spec/bindings/release_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Bindings::Release do
4 | describe '.parse' do
5 | describe 'attributes' do
6 | describe 'format' do
7 | context 'single cd' do
8 | let(:response) {
9 | <<-XML
10 |
11 |
12 |
13 | CD
14 |
15 |
16 |
17 | XML
18 | }
19 | let(:xml) {
20 | Nokogiri::XML.parse(response)
21 | }
22 | let(:release) {
23 | described_class.parse(xml)
24 | }
25 |
26 | it 'returns CD' do
27 | expect(release[:format]).to eq 'CD'
28 | end
29 | end
30 |
31 | context 'multiple cds' do
32 | let(:response) {
33 | <<-XML
34 |
35 |
36 |
37 | CD
38 |
39 |
40 |
41 | bonus disc
42 | CD
43 |
44 |
45 |
46 | XML
47 | }
48 | let(:xml) {
49 | Nokogiri::XML.parse(response)
50 | }
51 | let(:release) {
52 | described_class.parse(xml)
53 | }
54 |
55 | it 'returns 2xCD' do
56 | expect(release[:format]).to eq '2xCD'
57 | end
58 | end
59 |
60 | context 'different formats' do
61 | let(:response) {
62 | <<-XML
63 |
64 |
65 |
66 | DVD
67 |
68 |
69 | CD
70 |
71 |
72 |
73 | XML
74 | }
75 | let(:xml) {
76 | Nokogiri::XML.parse(response)
77 | }
78 | let(:release) {
79 | described_class.parse(xml)
80 | }
81 |
82 | it 'returns DVD + CD' do
83 | expect(release[:format]).to eq 'DVD + CD'
84 | end
85 | end
86 |
87 | context 'different formats plus multiple mediums with same format' do
88 | let(:response) {
89 | <<-XML
90 |
91 |
92 |
93 | CD
94 |
95 |
96 | CD
97 |
98 |
99 | DVD
100 |
101 |
102 |
103 | XML
104 | }
105 | let(:xml) {
106 | Nokogiri::XML.parse(response)
107 | }
108 | let(:release) {
109 | described_class.parse(xml)
110 | }
111 |
112 | it 'returns 2xCD + DVD' do
113 | expect(release[:format]).to eq '2xCD + DVD'
114 | end
115 | end
116 | end
117 | end
118 | end
119 | end
120 |
--------------------------------------------------------------------------------
/spec/models/release_group_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::ReleaseGroup do
4 | before(:each) {
5 | mock url: 'http://musicbrainz.org/ws/2/release-group/6f33e0f0-cde2-38f9-9aee-2c60af8d1a61?inc=url-rels',
6 | fixture: 'release_group/find_6f33e0f0-cde2-38f9-9aee-2c60af8d1a61.xml'
7 |
8 | mock url: 'http://musicbrainz.org/ws/2/release-group?query=artist:"Kasabian" AND releasegroup:"Empire"&limit=10',
9 | fixture: 'release_group/search_kasabian_empire.xml'
10 |
11 | mock url: 'http://musicbrainz.org/ws/2/release-group?query=artist:"Kasabian" AND releasegroup:"Empire" AND type:"Album"&limit=10',
12 | fixture: 'release_group/search_kasabian_empire_album.xml'
13 |
14 | mock url: 'http://musicbrainz.org/ws/2/release?release-group=6f33e0f0-cde2-38f9-9aee-2c60af8d1a61&inc=media+release-groups&limit=100',
15 | fixture: 'release/list_6f33e0f0-cde2-38f9-9aee-2c60af8d1a61.xml'
16 | }
17 |
18 | describe '.find' do
19 | let(:release_group) {
20 | MusicBrainz::ReleaseGroup.find("6f33e0f0-cde2-38f9-9aee-2c60af8d1a61")
21 | }
22 |
23 | it "gets no exception while loading release group info" do
24 | expect { release_group }.to_not raise_error(Exception)
25 | end
26 |
27 | it "gets correct instance" do
28 | expect(release_group).to be_an_instance_of(MusicBrainz::ReleaseGroup)
29 | end
30 |
31 | it "gets correct release group data" do
32 | expect(release_group.id).to eq "6f33e0f0-cde2-38f9-9aee-2c60af8d1a61"
33 | expect(release_group.type).to eq "Album"
34 | expect(release_group.title).to eq "Empire"
35 | expect(release_group.first_release_date).to eq Date.new(2006, 8, 28)
36 | expect(release_group.urls[:wikipedia]).to eq 'http://en.wikipedia.org/wiki/Empire_(Kasabian_album)'
37 | end
38 | end
39 |
40 | describe '.search' do
41 | context 'without type filter' do
42 | let(:matches) {
43 | MusicBrainz::ReleaseGroup.search('Kasabian', 'Empire')
44 | }
45 |
46 | it "searches release group by artist name and title" do
47 | expect(matches.length).to be > 0
48 | expect(matches.first[:title]).to eq 'Empire'
49 | expect(matches.first[:type]).to eq 'Album'
50 | end
51 | end
52 |
53 | context 'with type filter' do
54 | let(:matches) {
55 | MusicBrainz::ReleaseGroup.search('Kasabian', 'Empire', 'Album')
56 | }
57 |
58 | it "searches release group by artist name and title" do
59 | expect(matches.length).to be > 0
60 | expect(matches.first[:title]).to eq 'Empire'
61 | expect(matches.first[:type]).to eq 'Album'
62 | end
63 | end
64 | end
65 |
66 | describe '.find_by_artist_and_title' do
67 | let(:release_group) {
68 | MusicBrainz::ReleaseGroup.find_by_artist_and_title('Kasabian', 'Empire')
69 | }
70 |
71 | it "gets first release group by artist name and title" do
72 | expect(release_group.id).to eq '6f33e0f0-cde2-38f9-9aee-2c60af8d1a61'
73 | end
74 | end
75 |
76 | describe '#releases' do
77 | let(:releases) {
78 | MusicBrainz::ReleaseGroup.find("6f33e0f0-cde2-38f9-9aee-2c60af8d1a61").releases
79 | }
80 |
81 | it "gets correct release group's releases" do
82 | expect(releases.length).to be >= 5
83 | expect(releases.first.id).to eq "2225dd4c-ae9a-403b-8ea0-9e05014c778f"
84 | expect(releases.first.status).to eq "Official"
85 | expect(releases.first.title).to eq "Empire"
86 | expect(releases.first.date).to eq Date.new(2006, 8, 28)
87 | expect(releases.first.country).to eq "GB"
88 | expect(releases.first.type).to eq "Album"
89 | end
90 | end
91 | end
92 |
--------------------------------------------------------------------------------
/spec/models/release_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe MusicBrainz::Release do
4 | before(:each) {
5 | mock url: 'http://musicbrainz.org/ws/2/release/2225dd4c-ae9a-403b-8ea0-9e05014c778f?inc=media+release-groups',
6 | fixture: 'release/find_2225dd4c-ae9a-403b-8ea0-9e05014c778f.xml'
7 |
8 | mock url: 'http://musicbrainz.org/ws/2/release/2225dd4c-ae9a-403b-8ea0-9e05014c778f?inc=recordings+media&limit=100',
9 | fixture: 'release/find_2225dd4c-ae9a-403b-8ea0-9e05014c778f_tracks.xml'
10 |
11 | mock url: 'http://musicbrainz.org/ws/2/release/b94cb547-cf7a-4357-894c-53c3bf33b093?inc=media+release-groups',
12 | fixture: 'release/find_b94cb547-cf7a-4357-894c-53c3bf33b093.xml'
13 |
14 | mock url: 'http://musicbrainz.org/ws/2/discid/pmzhT6ZlFiwSRCdVwV0eqire5_Y-?',
15 | fixture: 'release/find_by_discid_pmzhT6ZlFiwSRCdVwV0eqire5_Y-.xml'
16 | }
17 |
18 | describe '.find' do
19 | let(:release) {
20 | MusicBrainz::Release.find("b94cb547-cf7a-4357-894c-53c3bf33b093")
21 | }
22 |
23 | it "gets no exception while loading release info" do
24 | expect { release }.to_not raise_error(Exception)
25 | end
26 |
27 | it "gets correct instance" do
28 | expect(release).to be_an_instance_of(MusicBrainz::Release)
29 | end
30 |
31 | it "gets correct release data" do
32 | expect(release.id).to eq "b94cb547-cf7a-4357-894c-53c3bf33b093"
33 | expect(release.title).to eq "Humanoid"
34 | expect(release.status).to eq "Official"
35 | expect(release.date).to eq Date.new(2009, 10, 6)
36 | expect(release.country).to eq "US"
37 | expect(release.asin).to eq 'B002NOYX6I'
38 | expect(release.barcode).to eq '602527197692'
39 | expect(release.quality).to eq 'normal'
40 | expect(release.type).to eq 'Album'
41 | end
42 | end
43 |
44 | describe '#mediums' do
45 | subject(:mediums) {
46 | release.mediums
47 | }
48 |
49 | let(:release) {
50 | MusicBrainz::Release.find("2225dd4c-ae9a-403b-8ea0-9e05014c778f")
51 | }
52 |
53 | it "gets correct release mediums" do
54 | expect(mediums.length).to eq 1
55 | expect(mediums.first.position).to eq 1
56 | expect(mediums.first.format).to eq "CD"
57 | end
58 |
59 | it "gets correct mediums tracks" do
60 | expect(mediums.first.tracks.length).to eq 11
61 | expect(mediums.first.tracks.first.position).to eq 1
62 | expect(mediums.first.tracks.first.recording_id).to eq "b3015bab-1540-4d4e-9f30-14872a1525f7"
63 | expect(mediums.first.tracks.first.title).to eq "Empire"
64 | expect(mediums.first.tracks.first.length).to eq 233013
65 | end
66 | end
67 |
68 | describe '#tracks' do
69 | let(:release) {
70 | MusicBrainz::Release.find("2225dd4c-ae9a-403b-8ea0-9e05014c778f")
71 | }
72 | let(:tracks) {
73 | release.tracks
74 | }
75 |
76 | it "gets correct release tracks" do
77 | expect(tracks.length).to eq 11
78 | expect(tracks.first.position).to eq 1
79 | expect(tracks.first.recording_id).to eq "b3015bab-1540-4d4e-9f30-14872a1525f7"
80 | expect(tracks.first.title).to eq "Empire"
81 | expect(tracks.first.length).to eq 233013
82 | end
83 | end
84 |
85 | describe '.find_by_discid' do
86 | let(:releases) {
87 | MusicBrainz::Release.find_by_discid("pmzhT6ZlFiwSRCdVwV0eqire5_Y-")
88 | }
89 |
90 | it "gets a list of matching releases for a discid" do
91 | expect(releases.length).to eq 2
92 | expect(releases.first.id).to eq "7a31cd5f-6a57-4fca-a731-c521df1d3b78"
93 | expect(releases.first.title).to eq "Kveikur"
94 | expect(releases.first.asin).to eq "B00C1GBOU6"
95 | end
96 | end
97 | end
98 |
--------------------------------------------------------------------------------
/spec/fixtures/artist/search_kasabian.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Kasabian
6 | Kasabian
7 | GB
8 |
9 | United Kingdom
10 | United Kingdom
11 |
12 | false
13 |
14 |
15 |
16 | Leicester
17 | Leicester
18 |
19 | false
20 |
21 |
22 |
23 | 0000000106652390
24 |
25 |
26 | 1997
27 | false
28 |
29 |
30 |
31 | rock
32 |
33 |
34 | alternative rock
35 |
36 |
37 | british
38 |
39 |
40 | indie rock
41 |
42 |
43 | indie
44 |
45 |
46 | alternative
47 |
48 |
49 | neo-psychedelia
50 |
51 |
52 | alternative dance
53 |
54 |
55 | dance-punk
56 |
57 |
58 | indietronica
59 |
60 |
61 | rock and indie
62 |
63 |
64 | post-baggy
65 |
66 |
67 |
68 |
69 | Mother Kasabian
70 | Mother Kasabian
71 |
72 | Stockholm
73 | Stockholm
74 |
75 | false
76 |
77 |
78 |
79 | 2011
80 | false
81 |
82 |
83 |
84 | psychedelic
85 |
86 |
87 | experimental
88 |
89 |
90 | metal
91 |
92 |
93 | doom
94 |
95 |
96 | stoner
97 |
98 |
99 | sludge
100 |
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## MusicBrainz Web Service wrapper
2 |
3 | ```ruby
4 | gem 'musicbrainz', '0.5.2'
5 | ```
6 |
7 | ### Installation
8 | ```
9 | gem install musicbrainz
10 | ```
11 | or add this line to your Gemfile
12 | ```ruby
13 | gem 'musicbrainz'
14 | ```
15 |
16 | ### Configuration
17 | ```ruby
18 | MusicBrainz.configure do |c|
19 | # Application identity (required)
20 | c.app_name = "My Music App"
21 | c.app_version = "1.0"
22 | c.contact = "support@mymusicapp.com"
23 |
24 | # Cache config (optional)
25 | c.cache_path = "/tmp/musicbrainz-cache"
26 | c.perform_caching = true
27 |
28 | # Querying config (optional)
29 | c.query_interval = 1.2 # seconds
30 | c.tries_limit = 2
31 | end
32 | ```
33 |
34 | ### Usage
35 | ```ruby
36 | require 'musicbrainz'
37 |
38 | # Search for artists
39 | @suggestions = MusicBrainz::Artist.search("Jet")
40 |
41 | # Find artist by name or mbid
42 | @foo_fighters = MusicBrainz::Artist.find_by_name("Foo Fighters")
43 | @kasabian = MusicBrainz::Artist.find("69b39eab-6577-46a4-a9f5-817839092033")
44 |
45 | # Use them like ActiveRecord models
46 | @empire_tracks = @kasabian.release_groups[8].releases.first.tracks
47 | ```
48 |
49 | ### Models
50 |
51 | MusicBrainz::Artist
52 | ```ruby
53 | # Class Methods:
54 | MusicBrainz::Artist.find(id)
55 | MusicBrainz::Artist.find_by_name(name)
56 | MusicBrainz::Artist.search(name)
57 | MusicBrainz::Artist.discography(id)
58 |
59 | # Instance Methods:
60 | @artist.release_groups
61 |
62 | # Fields
63 | {
64 | :id => String,
65 | :type => String,
66 | :name => String,
67 | :country => String,
68 | :date_begin => Date,
69 | :date_end => Date,
70 | :urls => Hash
71 | }
72 | ```
73 |
74 | MusicBrainz::ReleaseGroup
75 | ```ruby
76 | # Class Methods
77 | MusicBrainz::ReleaseGroup.find(id)
78 | MusicBrainz::ReleaseGroup.find_by_artist_and_title(artist_name, title, 'Album') # 3rd arg optional
79 | MusicBrainz::ReleaseGroup.search(artist_name, title)
80 | MusicBrainz::ReleaseGroup.search(artist_name, title, 'Album') # 3rd arg optional
81 |
82 | # Instance Methods
83 | @release_group.releases
84 |
85 | # Fields
86 | {
87 | :id => String,
88 | :type => String,
89 | :title => String,
90 | :desc => String,
91 | :first_release_date => Date,
92 | :urls => Hash
93 | }
94 | ```
95 |
96 | MusicBrainz::Release
97 | ```ruby
98 | # Class Methods
99 | MusicBrainz::Release.find(id)
100 |
101 | # Instance Methods
102 | @release.tracks
103 |
104 | # Fields
105 | {
106 | :id => String,
107 | :type => String,
108 | :title => String,
109 | :status => String,
110 | :format => String,
111 | :date => Date,
112 | :country => String,
113 | :asin => String,
114 | :barcode => String,
115 | :quality => String
116 | }
117 | ```
118 |
119 | MusicBrainz::Track (depreciated, now called Recording)
120 | ```ruby
121 | # Class Methods
122 | MusicBrainz::Track.find(id)
123 |
124 | # Fields
125 | {
126 | :position => Integer,
127 | :recording_id => String,
128 | :title => String,
129 | :length => Integer
130 | }
131 | ```
132 |
133 | MusicBrainz::Recording
134 | ```ruby
135 | # Class Methods
136 | MusicBrainz::Recording.find(id)
137 | MusicBrainz::Recording.search(track_name, artist_name)
138 |
139 | # Fields
140 | {
141 | :id => Integer,
142 | :mbid => Integer,
143 | :title => String,
144 | :artist => String,
145 | :releases => String,
146 | :score => Integer
147 | }
148 | ```
149 |
150 | ### Testing
151 | ```
152 | bundle exec rspec
153 | ```
154 |
155 | ### Debug console
156 | ```
157 | bundle exec irb -r musicbrainz
158 | ```
159 |
160 | ### Contributing
161 |
162 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
163 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
164 | * Fork the project
165 | * **Start a feature/bugfix branch (IMPORTANT)**
166 | * Commit and push until you are happy with your contribution
167 | * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
168 | * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
169 |
170 | ### License
171 |
172 | [MIT](https://raw.github.com/localhots/musicbrainz/master/LICENSE)
173 |
--------------------------------------------------------------------------------
/spec/fixtures/release_group/search_kasabian_empire_album.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Empire
6 | 2006-08-25
7 | Album
8 |
9 |
10 | Kasabian
11 |
12 | Kasabian
13 | Kasabian
14 |
15 |
16 |
17 |
18 |
19 | Empire
20 | Official
21 |
22 |
23 | Empire
24 | Official
25 |
26 |
27 | Empire
28 | Official
29 |
30 |
31 | Empire
32 | Official
33 |
34 |
35 | Empire
36 | Official
37 |
38 |
39 | Empire
40 | Official
41 |
42 |
43 | Empire
44 | Official
45 |
46 |
47 | Empire
48 | Official
49 |
50 |
51 | Empire
52 | Official
53 |
54 |
55 | Empire
56 | Official
57 |
58 |
59 | Empire
60 | Official
61 |
62 |
63 | Empire
64 | Official
65 |
66 |
67 | Empire
68 | Official
69 |
70 |
71 | Empire
72 | Official
73 |
74 |
75 |
76 |
77 | rock
78 |
79 |
80 | alternative rock
81 |
82 |
83 | indie rock
84 |
85 |
86 | indie
87 |
88 |
89 | rock and indie
90 |
91 |
92 |
93 |
94 | Empire / West Ryder Pauper Lunatic Asylum
95 | 2011
96 | Album
97 |
98 | Compilation
99 |
100 |
101 |
102 | Kasabian
103 |
104 | Kasabian
105 | Kasabian
106 |
107 |
108 |
109 |
110 |
111 | Empire / West Ryder Pauper Lunatic Asylum
112 | Official
113 |
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/spec/fixtures/release/find_by_discid_pmzhT6ZlFiwSRCdVwV0eqire5_Y-.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 217171
5 |
6 | 150
7 | 35000
8 | 63711
9 | 86440
10 | 105891
11 | 128017
12 | 154668
13 | 177007
14 | 200379
15 |
16 |
17 |
18 | Kveikur
19 | Official
20 | normal
21 | Cardboard/Paper Sleeve
22 |
23 | isl
24 |
25 |
26 | 2013-06-17
27 | XE
28 |
29 |
30 | 2013-06-17
31 |
32 | Europe
33 | Europe
34 |
35 | XE
36 |
37 |
38 |
39 |
40 | 2013-06-18
41 |
42 | United States
43 | United States
44 |
45 | US
46 |
47 |
48 |
49 |
50 | 634904060626
51 | B00C1GBOU6
52 |
53 | true
54 | 14
55 | true
56 | true
57 |
58 |
59 |
60 | 1
61 | CD
62 |
63 |
64 | 217171
65 |
66 | 150
67 | 35000
68 | 63711
69 | 86440
70 | 105891
71 | 128017
72 | 154668
73 | 177007
74 | 200379
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | Kveikur
84 | Official
85 | normal
86 | Cardboard/Paper Sleeve
87 |
88 | eng
89 |
90 |
91 | 2013-06-14
92 | TW
93 |
94 |
95 | 2013-06-14
96 |
97 | Taiwan
98 | Taiwan
99 |
100 | TW
101 |
102 |
103 | CN-71
104 |
105 |
106 |
107 |
108 | 4712765169088
109 |
110 | true
111 | 1
112 | true
113 | false
114 |
115 |
116 |
117 | 1
118 | CD
119 |
120 |
121 | 217171
122 |
123 | 150
124 | 35000
125 | 63711
126 | 86440
127 | 105891
128 | 128017
129 | 154668
130 | 177007
131 | 200379
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/spec/fixtures/release/find_2225dd4c-ae9a-403b-8ea0-9e05014c778f_tracks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Empire
5 | Official
6 | normal
7 |
8 | eng
9 |
10 |
11 | 2006-08-28
12 | GB
13 |
14 |
15 | 2006-08-28
16 |
17 | United Kingdom
18 | United Kingdom
19 |
20 | GB
21 |
22 |
23 |
24 |
25 | B000H49P28
26 |
27 | true
28 | 1
29 | true
30 | false
31 |
32 |
33 |
34 | 1
35 | CD
36 |
37 |
47 |
58 |
68 |
78 |
88 |
98 |
108 |
118 |
128 |
138 |
148 |
149 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/spec/fixtures/artist/find_69b39eab-6577-46a4-a9f5-817839092033.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Kasabian
5 | Kasabian
6 |
7 | 0000000106652390
8 |
9 | GB
10 |
11 | United Kingdom
12 | United Kingdom
13 |
14 | GB
15 |
16 |
17 |
18 | Leicester
19 | Leicester
20 |
21 | GB-LCE
22 |
23 |
24 |
25 | 1997
26 |
27 |
28 |
29 | https://www.allmusic.com/artist/mn0000361541
30 | forward
31 |
32 |
33 | https://www.bbc.co.uk/music/artists/69b39eab-6577-46a4-a9f5-817839092033
34 | forward
35 | 2020-11-19
36 | true
37 |
38 |
39 | https://www.discogs.com/artist/235133
40 | forward
41 |
42 |
43 | https://open.spotify.com/artist/11wRdbnoYqRddKBrpHt4Ue
44 | forward
45 |
46 |
47 | https://www.deezer.com/artist/1247
48 | forward
49 |
50 |
51 | https://commons.wikimedia.org/wiki/File:Kasabian_at_Brixton_Academy_2009.jpg
52 | forward
53 |
54 |
55 | https://www.imdb.com/name/nm1868442/
56 | forward
57 |
58 |
59 | https://www.last.fm/music/Kasabian
60 | forward
61 |
62 |
63 | https://genius.com/artists/Kasabian
64 | forward
65 |
66 |
67 | https://muzikum.eu/en/kasabian/lyrics
68 | forward
69 |
70 |
71 | https://myspace.com/kasabian
72 | forward
73 |
74 |
75 | https://www.kasabian.co.uk/
76 | forward
77 |
78 |
79 | http://id.loc.gov/authorities/names/n2005068609
80 | forward
81 |
82 |
83 | https://catalogue.bnf.fr/ark:/12148/cb145796883
84 | forward
85 |
86 |
87 | https://d-nb.info/gnd/10336310-5
88 | forward
89 |
90 |
91 | https://rateyourmusic.com/artist/kasabian
92 | forward
93 |
94 |
95 | http://www.worldcat.org/identities/lccn-n2005068609/
96 | forward
97 | true
98 |
99 |
100 | https://play.google.com/store/music/artist?id=A2chuocuj6o6rswcqvmb6h2fzn4
101 | forward
102 | 2020-10-12
103 | true
104 |
105 |
106 | https://itunes.apple.com/us/artist/id20478838
107 | forward
108 |
109 |
110 | https://secondhandsongs.com/artist/25440
111 | forward
112 |
113 |
114 | https://twitter.com/kasabianhq
115 | forward
116 |
117 |
118 | https://www.facebook.com/kasabian
119 | forward
120 |
121 |
122 | https://www.instagram.com/kasabianofficial/
123 | forward
124 |
125 |
126 | https://www.songkick.com/artists/175029
127 | forward
128 |
129 |
130 | https://soundcloud.com/kasabian
131 | forward
132 |
133 |
134 | https://music.apple.com/gb/artist/20478838
135 | forward
136 |
137 |
138 | https://music.youtube.com/channel/UCmJDzMXYTdyjtp5VNONQRKg
139 | forward
140 |
141 |
142 | https://tidal.com/artist/1556
143 | forward
144 |
145 |
146 | http://viaf.org/viaf/138707200
147 | forward
148 |
149 |
150 | https://www.wikidata.org/wiki/Q272517
151 | forward
152 |
153 |
154 | https://www.youtube.com/channel/UC_niYTRmUdRvVNdGYIA1vcg
155 | forward
156 |
157 |
158 | https://www.youtube.com/user/KasabianVEVO
159 | forward
160 |
161 |
162 | https://www.youtube.com/user/KasabianTour
163 | forward
164 | true
165 |
166 |
167 |
168 |
169 |
--------------------------------------------------------------------------------
/spec/fixtures/release/list_6f33e0f0-cde2-38f9-9aee-2c60af8d1a61.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Empire
6 | Official
7 | normal
8 | Jewel Case
9 |
10 | eng
11 |
12 |
13 |
14 | Empire
15 | 2006-08-28
16 | Album
17 |
18 | 2006
19 | XE
20 |
21 |
22 | 2006
23 |
24 | Europe
25 | Europe
26 |
27 | XE
28 |
29 |
30 |
31 |
32 | 828768934227
33 |
34 | false
35 | 0
36 | false
37 | false
38 |
39 |
40 |
41 | 1
42 | CD
43 |
44 |
45 |
46 |
47 |
48 | Empire
49 | Official
50 | normal
51 |
52 | eng
53 |
54 |
55 |
56 | Empire
57 | 2006-08-28
58 | Album
59 |
60 | 2006-08-28
61 | GB
62 |
63 |
64 | 2006-08-28
65 |
66 | United Kingdom
67 | United Kingdom
68 |
69 | GB
70 |
71 |
72 |
73 |
74 | B000H49P28
75 |
76 | false
77 | 0
78 | false
79 | false
80 |
81 |
82 |
83 | 1
84 |
85 |
86 |
87 |
88 |
89 | Empire
90 | Official
91 | normal
92 |
93 | eng
94 |
95 |
96 |
97 | Empire
98 | 2006-08-28
99 | Album
100 |
101 | 2006-09-19
102 | US
103 |
104 |
105 | 2006-09-19
106 |
107 | United States
108 | United States
109 |
110 | US
111 |
112 |
113 |
114 |
115 | 828768832325
116 | B000HEVYQS
117 |
118 | false
119 | 0
120 | false
121 | false
122 |
123 |
124 |
125 | 1
126 | CD
127 |
128 |
129 |
130 |
131 |
132 | Empire
133 | Official
134 | normal
135 |
136 | eng
137 |
138 |
139 |
140 | Empire
141 | 2006-08-28
142 | Album
143 |
144 | 2006-09-22
145 | XW
146 |
147 |
148 | 2006-09-22
149 |
150 | [Worldwide]
151 | [Worldwide]
152 |
153 | XW
154 |
155 |
156 |
157 |
158 |
159 | false
160 | 0
161 | false
162 | false
163 |
164 |
165 |
166 | 1
167 | Digital Media
168 |
169 |
170 |
171 |
172 |
173 | Empire
174 | Official
175 | normal
176 |
177 | eng
178 |
179 |
180 |
181 | Empire
182 | 2006-08-28
183 | Album
184 |
185 | 2006-08-28
186 | GB
187 |
188 |
189 | 2006-08-28
190 |
191 | United Kingdom
192 | United Kingdom
193 |
194 | GB
195 |
196 |
197 |
198 |
199 | 828768849927
200 | B000H49P28
201 |
202 | false
203 | 0
204 | false
205 | false
206 |
207 |
208 |
209 | 1
210 | CD
211 |
212 |
213 |
214 |
215 |
216 | Empire
217 | Official
218 | normal
219 | Jewel Case
220 |
221 | eng
222 |
223 |
224 |
225 | Empire
226 | 2006-08-28
227 | Album
228 |
229 | 2006
230 | GB
231 |
232 |
233 | 2006
234 |
235 | United Kingdom
236 | United Kingdom
237 |
238 | GB
239 |
240 |
241 |
242 |
243 | 828768934227
244 |
245 | false
246 | 0
247 | false
248 | false
249 |
250 |
251 |
252 | 1
253 | CD
254 |
255 |
256 |
257 |
258 |
259 | Empire
260 | Official
261 | normal
262 |
263 | eng
264 |
265 |
266 |
267 | Empire
268 | 2006-08-28
269 | Album
270 |
271 | 2006-12-20
272 | JP
273 |
274 |
275 | 2006-12-20
276 |
277 | Japan
278 | Japan
279 |
280 | JP
281 |
282 |
283 |
284 |
285 | 4988017645901
286 | B000K2QL9W
287 |
288 | false
289 | 0
290 | false
291 | false
292 |
293 |
294 |
295 | 1
296 | CD
297 |
298 |
299 |
300 |
301 |
302 | Empire
303 | Official
304 | normal
305 | Jewel Case
306 |
307 | eng
308 |
309 |
310 |
311 | Empire
312 | 2006-08-28
313 | Album
314 |
315 | 2006-12-18
316 | GB
317 |
318 |
319 | 2006-12-18
320 |
321 | United Kingdom
322 | United Kingdom
323 |
324 | GB
325 |
326 |
327 |
328 |
329 |
330 | true
331 | 1
332 | true
333 | false
334 |
335 |
336 |
337 | 1
338 | Digital Media
339 |
340 |
341 |
342 |
343 |
344 |
345 |
--------------------------------------------------------------------------------
/spec/fixtures/recording/search_bound_for_the_floor_local_h.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Bound for the Floor
6 |
7 |
8 | Local H
9 |
10 | Local H
11 | Local H
12 |
13 | Local H.
14 |
15 |
16 |
17 |
18 | 1997-04-28
19 |
20 |
21 | Blackrock
22 | Official
23 |
24 |
25 | Various Artists
26 |
27 | Various Artists
28 | Various Artists
29 | add compilations to this artist
30 |
31 |
32 |
33 |
34 | Blackrock
35 | Album
36 |
37 | Soundtrack
38 |
39 |
40 | 1997-04-28
41 | AU
42 |
43 |
44 | 1997-04-28
45 |
46 | Australia
47 | Australia
48 |
49 | AU
50 |
51 |
52 |
53 |
54 |
55 | 15
56 |
57 | 1
58 |
59 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | Bound for the Floor
71 | 224000
72 |
73 |
74 | Local H
75 |
76 | Local H
77 | Local H
78 |
79 | Local H.
80 |
81 |
82 |
83 |
84 | 2001
85 |
86 |
87 | Killer Buzz
88 | Official
89 |
90 |
91 | Various Artists
92 |
93 | Various Artists
94 | Various Artists
95 | add compilations to this artist
96 |
97 |
98 |
99 |
100 | Killer Buzz
101 | Album
102 |
103 | Compilation
104 |
105 |
106 | 2001
107 | US
108 |
109 |
110 | 2001
111 |
112 | United States
113 | United States
114 |
115 | US
116 |
117 |
118 |
119 |
120 |
121 | 32
122 |
123 | 1
124 | CD
125 |
126 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | Bound for the Floor
139 | 230773
140 |
141 |
142 | Local H
143 |
144 | Local H
145 | Local H
146 |
147 | Local H.
148 |
149 |
150 |
151 |
152 |
153 |
154 | Unplugged & Burnt Out
155 | Official
156 |
157 |
158 | Various Artists
159 |
160 | Various Artists
161 | Various Artists
162 | add compilations to this artist
163 |
164 |
165 |
166 |
167 | Unplugged & Burnt Out
168 | Album
169 |
170 | Compilation
171 |
172 |
173 |
174 | 12
175 |
176 | 1
177 |
178 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 | Bound for the Floor
191 | 231880
192 |
193 |
194 | Local H
195 |
196 | Local H
197 | Local H
198 |
199 | Local H.
200 |
201 |
202 |
203 |
204 | 1997-11-25
205 |
206 |
207 | WAAF 107.3 FM: Royal Flush: Live On‐air
208 | Official
209 |
210 |
211 | Various Artists
212 |
213 | Various Artists
214 | Various Artists
215 | add compilations to this artist
216 |
217 |
218 |
219 |
220 | WAAF 107.3 FM: Royal Flush: Live On‐air
221 | Album
222 |
223 | Live
224 |
225 |
226 | 1997-11-25
227 | US
228 |
229 |
230 | 1997-11-25
231 |
232 | United States
233 | United States
234 |
235 | US
236 |
237 |
238 |
239 |
240 |
241 | 26
242 |
243 | 1
244 | CD
245 |
246 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 | Bound for the Floor
259 | 218360
260 | live
261 |
262 |
263 | Local H
264 |
265 | Local H
266 | Local H
267 |
268 | Local H.
269 |
270 |
271 |
272 |
273 | 1998
274 |
275 |
276 | The Point Platinum Version 1.0
277 | Official
278 |
279 |
280 | Various Artists
281 |
282 | Various Artists
283 | Various Artists
284 | add compilations to this artist
285 |
286 |
287 |
288 |
289 | The Point Platinum Version 1.0
290 | Album
291 |
292 | Compilation
293 |
294 |
295 | 1998
296 | US
297 |
298 |
299 | 1998
300 |
301 | United States
302 | United States
303 |
304 | US
305 |
306 |
307 |
308 |
309 |
310 | 32
311 |
312 | 2
313 | CD
314 |
315 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 | Bound for the Floor
328 | 223866
329 |
330 |
331 | Local H
332 |
333 | Local H
334 | Local H
335 |
336 | Local H.
337 |
338 |
339 |
340 |
341 |
342 |
343 | Radio 104 WMRQ Back to School
344 |
345 |
346 | Various Artists
347 |
348 | Various Artists
349 | Various Artists
350 | add compilations to this artist
351 |
352 |
353 |
354 |
355 | Radio 104 WMRQ Back to School
356 | Album
357 |
358 | Compilation
359 |
360 |
361 |
362 | 15
363 |
364 | 1
365 |
366 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 | Bound For the Floor
379 | 225000
380 |
381 |
382 | Local H
383 |
384 | Local H
385 | Local H
386 |
387 | Local H.
388 |
389 |
390 |
391 |
392 | 2018-02-06
393 |
394 |
395 | Live In Europe
396 | Official
397 |
398 |
399 | Local H
400 |
401 | Local H
402 | Local H
403 |
404 |
405 |
406 |
407 | Live In Europe
408 | Album
409 |
410 | Live
411 |
412 |
413 | 2018-02-06
414 | US
415 |
416 |
417 | 2018-02-06
418 |
419 | United States
420 | United States
421 |
422 | US
423 |
424 |
425 |
426 |
427 |
428 | 18
429 |
430 | 1
431 | CD
432 |
433 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 | Bound for the Floor
446 | 223947
447 |
448 |
449 | Local H
450 |
451 | Local H
452 | Local H
453 |
454 | Local H.
455 |
456 |
457 |
458 |
459 | 1996
460 |
461 |
462 | Promo Only: Modern Rock Radio, July 1996
463 | Promotion
464 |
465 |
466 | Various Artists
467 |
468 | Various Artists
469 | Various Artists
470 | add compilations to this artist
471 |
472 |
473 |
474 |
475 | Promo Only: Modern Rock Radio, July 1996
476 | Album
477 |
478 | Compilation
479 |
480 |
481 | 1996
482 | US
483 |
484 |
485 | 1996
486 |
487 | United States
488 | United States
489 |
490 | US
491 |
492 |
493 |
494 |
495 |
496 | 19
497 |
498 | 1
499 | CD
500 |
501 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 | Bound For The Floor
514 | 213000
515 |
516 |
517 | Local H
518 |
519 | Local H
520 | Local H
521 |
522 | Local H.
523 |
524 |
525 |
526 |
527 | 1999-06-26
528 |
529 |
530 | Live at The Metro / Smart Bar
531 | Official
532 |
533 |
534 | Local H
535 |
536 | Local H
537 | Local H
538 |
539 |
540 |
541 |
542 | Live at The Metro / Smart Bar
543 | Album
544 |
545 | Live
546 |
547 |
548 | 1999-06-26
549 | US
550 |
551 |
552 | 1999-06-26
553 |
554 | United States
555 | United States
556 |
557 | US
558 |
559 |
560 |
561 |
562 |
563 | 19
564 |
565 | 1
566 | CD
567 |
568 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 | Bound For The Floor
581 | 381000
582 |
583 |
584 | Local H
585 |
586 | Local H
587 | Local H
588 |
589 | Local H.
590 |
591 |
592 |
593 |
594 | 2006-02-11
595 |
596 |
597 | Live at Durty Nellies
598 | Official
599 |
600 |
601 | Local H
602 |
603 | Local H
604 | Local H
605 |
606 |
607 |
608 |
609 | Live at Durty Nellies
610 | Album
611 |
612 | Live
613 |
614 |
615 | 2006-02-11
616 | US
617 |
618 |
619 | 2006-02-11
620 |
621 | United States
622 | United States
623 |
624 | US
625 |
626 |
627 |
628 |
629 |
630 | 17
631 |
632 | 2
633 | CD
634 |
635 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
--------------------------------------------------------------------------------
/spec/fixtures/artist/release_groups_69b39eab-6577-46a4-a9f5-817839092033.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Kasabian
6 | 2004-07-23
7 | Album
8 |
9 |
10 | https://www.allmusic.com/album/mw0000261419
11 | forward
12 |
13 |
14 | https://www.discogs.com/master/89974
15 | forward
16 |
17 |
18 | https://genius.com/albums/Kasabian/Kasabian
19 | forward
20 |
21 |
22 | https://rateyourmusic.com/release/album/kasabian/kasabian_f2/
23 | forward
24 |
25 |
26 | https://www.bbc.co.uk/music/reviews/64w6
27 | forward
28 |
29 |
30 | https://www.wikidata.org/wiki/Q1535408
31 | forward
32 |
33 |
34 |
35 |
36 | Empire
37 | 2006-08-25
38 | Album
39 |
40 |
41 | https://www.allmusic.com/album/mw0000445384
42 | forward
43 |
44 |
45 | https://www.discogs.com/master/89980
46 | forward
47 |
48 |
49 | https://www.bbc.co.uk/music/reviews/389x
50 | forward
51 |
52 |
53 | https://www.wikidata.org/wiki/Q748870
54 | forward
55 |
56 |
57 |
58 |
59 | West Ryder Pauper Lunatic Asylum
60 | 2009-04-06
61 | Album
62 |
63 |
64 | https://www.allmusic.com/album/mw0000818188
65 | forward
66 |
67 |
68 | https://www.discogs.com/master/125155
69 | forward
70 |
71 |
72 | https://rateyourmusic.com/release/album/kasabian/west_ryder_pauper_lunatic_asylum/
73 | forward
74 |
75 |
76 | https://www.bbc.co.uk/music/reviews/bn3n
77 | forward
78 |
79 |
80 | https://www.wikidata.org/wiki/Q1932087
81 | forward
82 |
83 |
84 |
85 |
86 | Velociraptor!
87 | 2011-09-16
88 | Album
89 |
90 |
91 | https://www.allmusic.com/album/mw0002169520
92 | forward
93 |
94 |
95 | https://www.discogs.com/master/368845
96 | forward
97 |
98 |
99 | https://www.bbc.co.uk/music/reviews/vcm5
100 | forward
101 |
102 |
103 | https://www.wikidata.org/wiki/Q1951148
104 | forward
105 |
106 |
107 |
108 |
109 | 48:13
110 | 2014-06-09
111 | Album
112 |
113 |
114 | https://www.allmusic.com/album/mw0002668450
115 | forward
116 |
117 |
118 | https://www.discogs.com/master/696552
119 | forward
120 |
121 |
122 | https://www.wikidata.org/wiki/Q16735842
123 | forward
124 |
125 |
126 |
127 |
128 | For Crying Out Loud
129 | 2017-05-05
130 | Album
131 |
132 |
133 | https://www.discogs.com/master/1174013
134 | forward
135 |
136 |
137 | https://www.wikidata.org/wiki/Q28951744
138 | forward
139 |
140 |
141 |
142 |
143 | The Alchemist’s Euphoria
144 | 2022-08-02
145 | Album
146 |
147 |
148 | https://www.discogs.com/master/2743487
149 | forward
150 |
151 |
152 | https://www.offiziellecharts.de/album-details-497860
153 | forward
154 |
155 |
156 | https://www.plattentests.de/rezi.php?show=18727
157 | forward
158 |
159 |
160 | https://www.wikidata.org/wiki/Q113456602
161 | forward
162 |
163 |
164 |
165 |
166 | Happenings
167 | 2024-07-05
168 | Album
169 |
170 |
171 | Empire / West Ryder Pauper Lunatic Asylum
172 | 2011
173 | Album
174 |
175 | Compilation
176 |
177 |
178 |
179 | Live at Brixton Academy
180 | 2005-07-04
181 | Album
182 |
183 | Live
184 |
185 |
186 |
187 | https://www.wikidata.org/wiki/Q1949413
188 | forward
189 |
190 |
191 | https://en.wikipedia.org/wiki/Live_from_Brixton_Academy
192 | forward
193 |
194 |
195 |
196 |
197 | iTunes Festival: London 2007
198 | 2007-08-03
199 | Album
200 |
201 | Live
202 |
203 |
204 |
205 | https://www.wikidata.org/wiki/Q3789916
206 | forward
207 |
208 |
209 |
210 |
211 | Live in London
212 | 2011
213 | Album
214 |
215 | Live
216 |
217 |
218 |
219 | Reason Is Treason
220 | 2004-02-23
221 | Single
222 |
223 |
224 | https://www.discogs.com/master/125172
225 | forward
226 |
227 |
228 |
229 |
230 | Club Foot
231 | 2004-05-10
232 | Single
233 |
234 |
235 | https://www.discogs.com/master/125150
236 | forward
237 |
238 |
239 | https://www.wikidata.org/wiki/Q2327037
240 | forward
241 |
242 |
243 | https://en.wikipedia.org/wiki/Club_Foot_(song)
244 | forward
245 |
246 |
247 |
248 |
249 | L.S.F.
250 | 2004-08-17
251 | Single
252 |
253 |
254 | https://www.discogs.com/master/125163
255 | forward
256 |
257 |
258 | https://www.wikidata.org/wiki/Q607352
259 | forward
260 |
261 |
262 | https://en.wikipedia.org/wiki/L.S.F._(song)
263 | forward
264 |
265 |
266 |
267 |
268 | Processed Beats
269 | 2004-10-13
270 | Single
271 |
272 |
273 | https://www.wikidata.org/wiki/Q1463837
274 | forward
275 |
276 |
277 |
278 |
279 | Cutt Off
280 | 2005-01-03
281 | Single
282 |
283 |
284 | https://www.wikidata.org/wiki/Q3699798
285 | forward
286 |
287 |
288 | https://en.wikipedia.org/wiki/Cutt_Off
289 | forward
290 |
291 |
292 |
293 |
294 | Empire
295 | 2006-07-24
296 | Single
297 |
298 |
299 | https://www.discogs.com/master/125174
300 | forward
301 |
302 |
303 | https://www.wikidata.org/wiki/Q1463830
304 | forward
305 |
306 |
307 | https://en.wikipedia.org/wiki/Empire_(Kasabian_song)
308 | forward
309 |
310 |
311 |
312 |
313 | Shoot the Runner
314 | 2006-11-06
315 | Single
316 |
317 |
318 | https://www.discogs.com/master/125179
319 | forward
320 |
321 |
322 | https://www.wikidata.org/wiki/Q1463795
323 | forward
324 |
325 |
326 |
327 |
328 | Me Plus One
329 | 2007-01-29
330 | Single
331 |
332 |
333 | https://www.discogs.com/master/125175
334 | forward
335 |
336 |
337 | https://www.wikidata.org/wiki/Q1120372
338 | forward
339 |
340 |
341 | https://en.wikipedia.org/wiki/Me_Plus_One
342 | forward
343 |
344 |
345 |
346 |
347 | Fire
348 | 2009-05-28
349 | Single
350 |
351 |
352 | https://www.wikidata.org/wiki/Q783320
353 | forward
354 |
355 |
356 | https://en.wikipedia.org/wiki/Fire_(Kasabian_song)
357 | forward
358 |
359 |
360 |
361 |
362 | Where Did All the Love Go?
363 | 2009-08-10
364 | Single
365 |
366 |
367 | https://www.wikidata.org/wiki/Q4019433
368 | forward
369 |
370 |
371 | https://en.wikipedia.org/wiki/Where_Did_All_the_Love_Go%3F
372 | forward
373 |
374 |
375 |
376 |
377 | Underdog
378 | 2009-10-22
379 | Single
380 |
381 |
382 | https://www.wikidata.org/wiki/Q4004596
383 | forward
384 |
385 |
386 |
387 |
388 | Days Are Forgotten
389 | 2011-08
390 | Single
391 |
392 |
393 | https://www.discogs.com/master/381246
394 | forward
395 |
396 |
397 | https://www.wikidata.org/wiki/Q3703828
398 | forward
399 |
400 |
401 | https://en.wikipedia.org/wiki/Days_Are_Forgotten
402 | forward
403 |
404 |
405 |
406 |
407 | Re‐Wired
408 | 2011-11-21
409 | Single
410 |
411 |
412 |
413 |
--------------------------------------------------------------------------------