isrc 1.0.0

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