isrc 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in isrc.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'guard-rspec'
8
+
9
+ if RUBY_PLATFORM.downcase.include? "darwin"
10
+ gem 'rb-fsevent'
11
+ gem 'growl'
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+
9
+ # Rails example
10
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
11
+ watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
12
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
13
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
14
+ watch('config/routes.rb') { "spec/routing" }
15
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
16
+ # Capybara request specs
17
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
18
+ end
19
+
20
+
21
+ guard 'rspec', :version => 2 do
22
+ watch(%r{^spec/.+_spec\.rb$})
23
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
24
+ watch('spec/spec_helper.rb') { "spec" }
25
+
26
+ # Rails example
27
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
28
+ watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
29
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
30
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
31
+ watch('config/routes.rb') { "spec/routing" }
32
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
33
+ # Capybara request specs
34
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
35
+ end
36
+
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2012 Michael Bianco
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name Spree nor the names of its contributors may be used to
13
+ endorse or promote products derived from this software without specific
14
+ prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,33 @@
1
+ # ISRC
2
+
3
+ Gem to pull isrc code data from [PPL UK's database](http://repsearch.ppluk.com/ARSWeb/appmanager/ARS/main).
4
+
5
+ Although this gem uses ppluk's DB, the goal is support more DBs as the need arises.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile or install manually.
10
+
11
+ ## Usage
12
+
13
+ class Song
14
+ def update_isrc
15
+ retriever = ISRC::PPLUK.new
16
+ retriever.retrieve artist: self.artist, title: self.title
17
+ isrc_match = retriever.match(time: self.song_length)
18
+
19
+ self.isrc = isrc_match[:isrc]
20
+ self.match_quality = isrc_match[:delta]
21
+ end
22
+ end
23
+
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create new Pull Request
32
+
33
+ Copyright (c) 2012 Michael Bianco, released under the New BSD License
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/isrc/version', __FILE__)
3
+
4
+ Gem::Specification.new do |g|
5
+ g.authors = ["Michael Bianco"]
6
+ g.email = ["info@cliffsidedev.com"]
7
+ g.description = "Pull ISRC codes from PPLK UK's database"
8
+ g.summary = "Gem to pull ISRC codes from PPL UK's database. " +
9
+ "Match by artist and title."
10
+ g.homepage = "http://github.com/iloveitaly/isrc"
11
+
12
+ g.files = `git ls-files`.split($\)
13
+ g.executables = g.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ g.test_files = g.files.grep(%r{^(test|spec|features)/})
15
+ g.name = "isrc"
16
+ g.require_paths = ["lib"]
17
+ g.version = ISRC::VERSION
18
+
19
+ g.add_dependency 'httparty'
20
+ g.add_dependency 'nokogiri'
21
+ g.add_dependency 'mechanize'
22
+
23
+ g.add_development_dependency 'rspec'
24
+ g.add_development_dependency 'guard'
25
+ end
@@ -0,0 +1,146 @@
1
+ require "isrc/version"
2
+
3
+ require 'nokogiri'
4
+ require 'mechanize'
5
+ require 'logger'
6
+
7
+ module ISRC
8
+ # NOTE the cont=A is crucial to getting the trick to work
9
+ PPLUK_SESSION_GRAB_URL = 'http://repsearch.ppluk.com/ARSWeb/appmanager/ARS/main'
10
+ PPLUK_AJAX_SEARCH_URL = 'http://repsearch.ppluk.com/ARSWeb/block/send-receive-updates'
11
+
12
+ class PPLUK
13
+ def retrieve(opts)
14
+ puts "INFO #{opts[:artist]}:#{opts[:title]}"
15
+ agent = Mechanize.new
16
+ agent.log = Logger.new "mech.log"
17
+ agent.user_agent_alias = 'Mac Safari'
18
+
19
+ isrc_session_init = agent.get PPLUK_SESSION_GRAB_URL
20
+ view_state = self.extract_view_state(isrc_session_init.body)
21
+ ice_session, ice_session_count = self.extract_ice_session(isrc_session_init.body)
22
+
23
+ # add the ice_sessions cookie
24
+ ice_cookie = Mechanize::Cookie.new('ice.sessions', "#{ice_session}##{ice_session_count}")
25
+ ice_cookie.path = "/"
26
+ ice_cookie.domain = "repsearch.ppluk.com"
27
+ agent.cookie_jar.add!(ice_cookie)
28
+
29
+ # NOTE the online search is a bit funky: adding more to the search make the results worse
30
+ # trying out a three word limit
31
+
32
+ shortened_title = opts[:title]
33
+
34
+ # TODO remove '(Club Mix)' from titles
35
+ # TODO remove anything in brackets
36
+
37
+ if shortened_title.count(' ') > 2
38
+ shortened_title = shortened_title.split(' ').slice(0, 3).join(' ')
39
+ end
40
+
41
+ begin
42
+ isrc_search = agent.post PPLUK_AJAX_SEARCH_URL, {
43
+ 'ice.submit.partial' => 'false',
44
+ # 'ice.event.target' => 'T400335881332330323192:ars_form:search_button',
45
+ # 'ice.event.captured' => 'T400335881332330323192:ars_form:search_button',
46
+ # 'ice.event.type' => 'onclick',
47
+ # 'ice.event.alt' => 'false',
48
+ # 'ice.event.ctrl' => 'false',
49
+ # 'ice.event.shift' => 'false',
50
+ # 'ice.event.meta' => 'false',
51
+ # 'ice.event.x' => '47',
52
+ # 'ice.event.y' => '65',
53
+ # 'ice.event.left' => 'false',
54
+ # 'ice.event.right' => 'false',
55
+ 'T400335881332330323192:ars_form:search_button' => 'Search',
56
+ 'T400335881332330323192:ars_form:isrc_code' => '',
57
+ 'T400335881332330323192:ars_form:rec_title_idx' => '',
58
+ 'T400335881332330323192:ars_form:rec_title' => shortened_title,
59
+ 'T400335881332330323192:ars_form:rec_band_artist_idx' => '',
60
+ 'T400335881332330323192:ars_form:rec_band_artist' => opts[:artist],
61
+ 'javax.faces.RenderKitId' => 'ICEfacesRenderKit',
62
+ 'javax.faces.ViewState' => view_state,
63
+ 'icefacesCssUpdates' => '',
64
+ 'T400335881332330323192:ars_form' => '',
65
+ 'ice.session' => ice_session,
66
+ 'ice.view' => view_state,
67
+ 'ice.focus' => '',
68
+
69
+ # the rand is 19 characters long in the browser's HTTP requests
70
+ 'rand' => sprintf('%1.17f', rand)
71
+ }
72
+ rescue Mechanize::ResponseCodeError => e
73
+ agent.log.error "The Stuff #{e.page.body}"
74
+ end
75
+
76
+ # creates an array representation of the table:
77
+ # artist, title, isrc, released, time
78
+ isrc_html = Nokogiri::HTML(isrc_search.body)
79
+ @matches = isrc_html.css("table[id='T400335881332330323192:ars_form:searchResultsTable'] tbody tr").map do |m|
80
+ columns = m.css('td')
81
+
82
+ # if there is no ISRC don't bother looking
83
+ next if columns[2] == 'Not Supplied'
84
+
85
+ # zero length music wont be used
86
+ next if columns[-1] == '0:00sec'
87
+
88
+ columns.map &:text
89
+ end
90
+ end
91
+
92
+ def match(opts = {})
93
+ return {
94
+ :isrc => 'No Match',
95
+ :delta => -1
96
+ } if @matches.count == 0
97
+
98
+ return {
99
+ :artist => @matches.first[0],
100
+ :title => @matches.first[1],
101
+ :isrc => @matches.first[2],
102
+ :delta => 0
103
+ } if @matches.count == 1
104
+
105
+ seconds = opts[:time]
106
+
107
+ if seconds
108
+ # if string, convert to integer. Format '5:08'
109
+ seconds = timestring_to_integer(seconds) if seconds.class == String
110
+ match_quality = []
111
+
112
+ @matches.each do |song_match|
113
+ song_seconds = timestring_to_integer(song_match[4].match(/([0-9]:[0-9]{2})/)[1])
114
+ match_quality << { :delta => (song_seconds - seconds).abs, :match => song_match }
115
+ end
116
+
117
+ best_match = match_quality.min_by { |m| m[:delta] }
118
+
119
+ return {
120
+ :artist => best_match[:match][0],
121
+ :title => best_match[:match][1],
122
+ :isrc => best_match[:match][2],
123
+ :delta => best_match[:delta]
124
+ }
125
+ end
126
+
127
+ nil
128
+ end
129
+
130
+ protected
131
+ def extract_view_state(body)
132
+ body.match(/javax\.faces\.ViewState" value="([0-9])"/)[1]
133
+ end
134
+
135
+ def extract_ice_session(body)
136
+ session_info = body.match(/history-frame:([^:]+):([0-9]+)/)
137
+ [session_info[1], session_info[2].to_i]
138
+ end
139
+
140
+ def timestring_to_integer(time_string)
141
+ minutes, seconds = time_string.split(':')
142
+ minutes.to_i * 60 + seconds.to_i
143
+ end
144
+
145
+ end
146
+ end
@@ -0,0 +1,3 @@
1
+ module ISRC
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+ require 'isrc'
3
+
4
+ describe ISRC do
5
+ before { @isrc = ISRC::PPLUK.new }
6
+ let(:isrc) { @isrc }
7
+
8
+ it "should correctly handle multiple search results" do
9
+ isrc.retrieve artist: 'Coldplay', title: 'Clocks'
10
+ isrc.match(time:'5:08')[:isrc].should == 'GBAYE0200771'
11
+ end
12
+
13
+ it "should correctly handle a single search result" do
14
+ isrc.retrieve artist: 'Coldplay', title: 'Glass'
15
+ isrc.match(time:'5:08')[:isrc].should == 'GBAYE0800410'
16
+ end
17
+
18
+ it "should handle songs with multiple results" do
19
+ isrc.retrieve artist:'Soul II Soul', title: 'Back To Life (However Do You Want Me) (Club Mix)'
20
+ isrc.match(time:'7:39')[:isrc].should == 'GBAAA8900153'
21
+
22
+ # the better match seems to be: GB1209500610
23
+ # however, the length delta is huge
24
+
25
+ end
26
+
27
+ it "should handle songs with no results" do
28
+ isrc.retrieve artist:'The Rurals', title: 'Take Your Time'
29
+ isrc.match(time:'7:27')[:isrc].should == 'No Match'
30
+
31
+ isrc = ISRC::PPLUK.new
32
+ isrc.retrieve artist:'Slut Puppies', title:'Funky Together'
33
+ isrc.match(time:'6:36')[:isrc].should == 'No Match'
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper.rb"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: isrc
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael Bianco
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: httparty
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: nokogiri
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: mechanize
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: guard
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Pull ISRC codes from PPLK UK's database
95
+ email:
96
+ - info@cliffsidedev.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - .gitignore
102
+ - .rspec
103
+ - Gemfile
104
+ - Guardfile
105
+ - LICENSE
106
+ - README.md
107
+ - Rakefile
108
+ - isrc.gemspec
109
+ - lib/isrc.rb
110
+ - lib/isrc/version.rb
111
+ - spec/isrc_spec.rb
112
+ - spec/spec_helper.rb
113
+ homepage: http://github.com/iloveitaly/isrc
114
+ licenses: []
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 1.8.24
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: Gem to pull ISRC codes from PPL UK's database. Match by artist and title.
137
+ test_files:
138
+ - spec/isrc_spec.rb
139
+ - spec/spec_helper.rb