bassnode-ruby-echonest 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/ChangeLog +4 -0
- data/README.rdoc +45 -0
- data/Rakefile +36 -0
- data/bassnode-ruby-echonest.gemspec +29 -0
- data/lib/echonest/analysis.rb +106 -0
- data/lib/echonest/api.rb +336 -0
- data/lib/echonest/element/bar.rb +2 -0
- data/lib/echonest/element/beat.rb +2 -0
- data/lib/echonest/element/loudness.rb +8 -0
- data/lib/echonest/element/section.rb +9 -0
- data/lib/echonest/element/segment.rb +12 -0
- data/lib/echonest/element/tatum.rb +2 -0
- data/lib/echonest/response.rb +41 -0
- data/lib/echonest/traditional_api_methods.rb +14 -0
- data/lib/echonest/version.rb +3 -0
- data/lib/echonest.rb +16 -0
- data/spec/analysis_spec.rb +110 -0
- data/spec/api_spec.rb +144 -0
- data/spec/apimethods_base_spec.rb +207 -0
- data/spec/artist_spec.rb +184 -0
- data/spec/echonest_spec.rb +12 -0
- data/spec/fixtures/analysis.json +3 -0
- data/spec/fixtures/profile.json +1 -0
- data/spec/fixtures/profile_failure.json +1 -0
- data/spec/fixtures/profile_unknown.json +1 -0
- data/spec/fixtures/sample.mp3 +0 -0
- data/spec/playlist_spec.rb +56 -0
- data/spec/response_spec.rb +23 -0
- data/spec/song_spec.rb +91 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/track_spec.rb +105 -0
- metadata +167 -0
data/ChangeLog
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
= echonest
|
2
|
+
|
3
|
+
A Ruby interface for Echo Nest Developer API
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
=== Archive Installation
|
10
|
+
|
11
|
+
rake install
|
12
|
+
|
13
|
+
=== Gem Installation
|
14
|
+
|
15
|
+
gem install ruby-echonest
|
16
|
+
|
17
|
+
== Features/Problems
|
18
|
+
|
19
|
+
Only supports the API for Track http://developer.echonest.com/docs/v4/track.html
|
20
|
+
|
21
|
+
== Synopsis
|
22
|
+
|
23
|
+
require 'rubygems'
|
24
|
+
require 'echonest'
|
25
|
+
|
26
|
+
filename = 'foo.mp3'
|
27
|
+
echonest = Echonest('YOUR_API_KEY')
|
28
|
+
|
29
|
+
analysis = echonest.track.analysis(filename)
|
30
|
+
beats = analysis.beats
|
31
|
+
segments = analysis.segments
|
32
|
+
|
33
|
+
# traditional way
|
34
|
+
beats = echonest.get_beats(filename)
|
35
|
+
segments = echonest.get_segments(filename)
|
36
|
+
|
37
|
+
== Thanks
|
38
|
+
|
39
|
+
{koyachi}[http://github.com/koyachi] for original idea http://gist.github.com/87086
|
40
|
+
|
41
|
+
== Copyright
|
42
|
+
|
43
|
+
Author:: youpy <youpy@buycheapviagraonlinenow.com>
|
44
|
+
Copyright:: Copyright (c) 2009 youpy
|
45
|
+
License:: MIT
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/sshpublisher'
|
9
|
+
require 'spec/rake/spectask'
|
10
|
+
require 'fileutils'
|
11
|
+
include FileUtils
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift "lib"
|
14
|
+
require "echonest"
|
15
|
+
|
16
|
+
|
17
|
+
task :default => [:spec]
|
18
|
+
task :package => [:clean]
|
19
|
+
|
20
|
+
Spec::Rake::SpecTask.new do |t|
|
21
|
+
t.spec_opts = ['--options', "spec/spec.opts"]
|
22
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
23
|
+
t.rcov = true
|
24
|
+
end
|
25
|
+
|
26
|
+
spec = eval(File.read("bassnode-ruby-echonest.gemspec"))
|
27
|
+
Rake::GemPackageTask.new(spec) do |p|
|
28
|
+
p.need_tar = true
|
29
|
+
p.gem_spec = spec
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
desc "Show information about the gem"
|
34
|
+
task :debug_gem do
|
35
|
+
puts spec.to_ruby
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{bassnode-ruby-echonest}
|
5
|
+
s.version = "0.1.2"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["youpy", "bassnode"]
|
9
|
+
s.date = %q{2011-04-24}
|
10
|
+
s.description = %q{An Ruby interface for Echo Nest Developer API}
|
11
|
+
s.summary = %q{An Ruby interface for Echo Nest Developer API}
|
12
|
+
s.email = %q{youpy@buycheapviagraonlinenow.com}
|
13
|
+
s.homepage = %q{http://github.com/bassnode/ruby-echonest}
|
14
|
+
s.rubyforge_project = %q{bassnode-ruby-echonest}
|
15
|
+
s.rubygems_version = %q{1.3.6}
|
16
|
+
s.platform = Gem::Platform::RUBY
|
17
|
+
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
s.extra_rdoc_files = ["README.rdoc", "ChangeLog"]
|
20
|
+
s.rdoc_options = ["--title", "ruby-echonest documentation", "--charset", "utf-8", "--opname", "index.html", "--line-numbers", "--main", "README.rdoc", "--inline-source", "--exclude", "^(examples|extras)/"]
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
23
|
+
|
24
|
+
|
25
|
+
s.add_dependency(%q<libxml-ruby>, [">= 0"])
|
26
|
+
s.add_dependency(%q<httpclient>, [">= 0"])
|
27
|
+
s.add_dependency(%q<hashie>, [">= 0"])
|
28
|
+
s.add_development_dependency('rspec')
|
29
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
module Echonest
|
5
|
+
class Analysis
|
6
|
+
CHROMATIC = %w(C C# D D# E F F# G G# A A# B).freeze
|
7
|
+
|
8
|
+
def initialize(json)
|
9
|
+
@body = JSON.parse(json)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.new_from_url(url)
|
13
|
+
new(open(url).read)
|
14
|
+
end
|
15
|
+
|
16
|
+
def tempo
|
17
|
+
track_info['tempo']
|
18
|
+
end
|
19
|
+
|
20
|
+
def duration
|
21
|
+
track_info['duration']
|
22
|
+
end
|
23
|
+
|
24
|
+
def end_of_fade_in
|
25
|
+
track_info['end_of_fade_in']
|
26
|
+
end
|
27
|
+
|
28
|
+
def key
|
29
|
+
track_info['key']
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the corresponding letter for the key number value.
|
33
|
+
def key_letter
|
34
|
+
CHROMATIC[key]
|
35
|
+
end
|
36
|
+
|
37
|
+
def loudness
|
38
|
+
track_info['loudness']
|
39
|
+
end
|
40
|
+
|
41
|
+
def mode
|
42
|
+
track_info['mode']
|
43
|
+
end
|
44
|
+
|
45
|
+
def minor?
|
46
|
+
mode == 0
|
47
|
+
end
|
48
|
+
|
49
|
+
def major?
|
50
|
+
!minor?
|
51
|
+
end
|
52
|
+
|
53
|
+
def start_of_fade_out
|
54
|
+
track_info['start_of_fade_out']
|
55
|
+
end
|
56
|
+
|
57
|
+
def time_signature
|
58
|
+
track_info['time_signature']
|
59
|
+
end
|
60
|
+
|
61
|
+
def bars
|
62
|
+
@body['bars'].map do |bar|
|
63
|
+
Bar.new(bar['start'], bar['duration'], bar['confidence'])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def beats
|
68
|
+
@body['beats'].map do |beat|
|
69
|
+
Beat.new(beat['start'], beat['duration'], beat['confidence'])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def sections
|
74
|
+
@body['sections'].map do |section|
|
75
|
+
Section.new(section['start'], section['duration'], section['confidence'])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def tatums
|
80
|
+
@body['tatums'].map do |tatum|
|
81
|
+
Tatum.new(tatum['start'], tatum['duration'], tatum['confidence'])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def segments
|
86
|
+
@body['segments'].map do |segment|
|
87
|
+
loudness = Loudness.new(0.0, segment['loudness_start'])
|
88
|
+
max_loudness = Loudness.new(segment['loudness_max_time'], segment['loudness_max'])
|
89
|
+
|
90
|
+
Segment.new(
|
91
|
+
segment['start'],
|
92
|
+
segment['duration'],
|
93
|
+
segment['confidence'],
|
94
|
+
loudness,
|
95
|
+
max_loudness,
|
96
|
+
segment['pitches'],
|
97
|
+
segment['timbre']
|
98
|
+
)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def track_info
|
103
|
+
@body['track']
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/lib/echonest/api.rb
ADDED
@@ -0,0 +1,336 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'httpclient'
|
3
|
+
require 'json'
|
4
|
+
# For streaming output
|
5
|
+
STDOUT.sync = true
|
6
|
+
|
7
|
+
module Echonest
|
8
|
+
class Api
|
9
|
+
VERSION = '4.2'
|
10
|
+
BASE_URL = 'http://developer.echonest.com/api/v4/'
|
11
|
+
USER_AGENT = '%s/%s' % ['ruby-echonest', ::Echonest::VERSION]
|
12
|
+
|
13
|
+
include TraditionalApiMethods
|
14
|
+
|
15
|
+
class Error < StandardError; end
|
16
|
+
|
17
|
+
attr_reader :user_agent
|
18
|
+
|
19
|
+
def initialize(api_key)
|
20
|
+
@api_key = api_key
|
21
|
+
@user_agent = HTTPClient.new(:agent_name => USER_AGENT)
|
22
|
+
# for big files
|
23
|
+
@user_agent.send_timeout = 60 * 30
|
24
|
+
@user_agent.receive_timeout = 60 * 10
|
25
|
+
end
|
26
|
+
|
27
|
+
def track
|
28
|
+
ApiMethods::Track.new(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def artist(name=nil)
|
32
|
+
if name
|
33
|
+
ApiMethods::Artist.new_from_name(self, name)
|
34
|
+
else
|
35
|
+
ApiMethods::Artist.new(self)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def song
|
40
|
+
ApiMethods::Song.new(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
def playlist
|
44
|
+
ApiMethods::Playlist.new(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
def default_params
|
48
|
+
{
|
49
|
+
:format => 'json',
|
50
|
+
:api_key => @api_key
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_params(params)
|
55
|
+
params = params.
|
56
|
+
merge(default_params)
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_params_to_list(params)
|
60
|
+
result = []
|
61
|
+
hash_to_list = lambda{|kv| [kv[0].to_s, kv[1]]}
|
62
|
+
params.each do |param|
|
63
|
+
if param.instance_of? Array
|
64
|
+
param[1].map do |p1|
|
65
|
+
result << [param[0].to_s, p1]
|
66
|
+
end
|
67
|
+
else
|
68
|
+
result << hash_to_list.call(params)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
default_params.each do |kv|
|
72
|
+
result << hash_to_list.call(kv) unless params.include? kv[0]
|
73
|
+
end
|
74
|
+
result
|
75
|
+
end
|
76
|
+
|
77
|
+
def request(name, method, params, file = nil)
|
78
|
+
uri = URI.join(BASE_URL, name.to_s)
|
79
|
+
if file
|
80
|
+
query = build_params(params).sort_by do |param|
|
81
|
+
param[0].to_s
|
82
|
+
end.inject([]) do |m, param|
|
83
|
+
m << [URI.encode(param[0].to_s), URI.encode(param[1])].join('=')
|
84
|
+
end.join('&')
|
85
|
+
|
86
|
+
uri.query = query
|
87
|
+
file = file.read unless file.is_a?(String)
|
88
|
+
connection = @user_agent.__send__(
|
89
|
+
method.to_s + '_async',
|
90
|
+
uri,
|
91
|
+
file,
|
92
|
+
{
|
93
|
+
'Content-Type' => 'application/octet-stream'
|
94
|
+
})
|
95
|
+
|
96
|
+
# Show some feedback for big ole' POSTs
|
97
|
+
n=0
|
98
|
+
print "8"
|
99
|
+
begin
|
100
|
+
sleep 2
|
101
|
+
n+=2
|
102
|
+
print (n%6==0 ? "D 8" : "=")
|
103
|
+
end while !connection.finished?
|
104
|
+
|
105
|
+
res = connection.pop
|
106
|
+
response_body = res.content.read
|
107
|
+
else
|
108
|
+
response_body = @user_agent.__send__(
|
109
|
+
method.to_s + '_content',
|
110
|
+
uri,
|
111
|
+
build_params_to_list(params))
|
112
|
+
end
|
113
|
+
|
114
|
+
response = Response.new(response_body)
|
115
|
+
unless response.success?
|
116
|
+
raise Error.new(response.status.message)
|
117
|
+
end
|
118
|
+
|
119
|
+
response
|
120
|
+
rescue HTTPClient::BadResponseError => e
|
121
|
+
raise Error.new('%s: %s' % [name, e.message])
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
module ApiMethods
|
126
|
+
class Base
|
127
|
+
def initialize(api)
|
128
|
+
@api = api
|
129
|
+
end
|
130
|
+
|
131
|
+
def request(*args)
|
132
|
+
name, http_method, params = args
|
133
|
+
@api.request(name, http_method, params)
|
134
|
+
end
|
135
|
+
|
136
|
+
class << self
|
137
|
+
def method_with_required_any(category, method_id, http_method, required, required_any, option, proc, block=nil)
|
138
|
+
unless block
|
139
|
+
block = Proc.new {|response| response.body}
|
140
|
+
end
|
141
|
+
method = :request
|
142
|
+
required ||= %w[api_key]
|
143
|
+
define_method(method_id) do |*args|
|
144
|
+
name = "#{category.downcase}/#{method_id.to_s}"
|
145
|
+
if args.length > 0
|
146
|
+
param_required = {}
|
147
|
+
required.each do |k|
|
148
|
+
k = k.to_sym
|
149
|
+
param_required[k] = args[0].delete(k) if args[0][k]
|
150
|
+
end
|
151
|
+
param_option = args[0]
|
152
|
+
end
|
153
|
+
params = ApiMethods::Base.validator(required, required_any, option).call(
|
154
|
+
:required => param_required,
|
155
|
+
:required_any => proc.call(self),
|
156
|
+
:option => param_option)
|
157
|
+
block.call(send(method, name, http_method, params))
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def method_with_option(id, option, &block)
|
162
|
+
unless block
|
163
|
+
block = Proc.new {|response| response.body}
|
164
|
+
end
|
165
|
+
required = %w[]
|
166
|
+
required_any = %w[]
|
167
|
+
method = :request
|
168
|
+
http_method = :get
|
169
|
+
define_method(id) do |*args|
|
170
|
+
name = "#{self.class.to_s.split('::')[-1].downcase}/#{id.to_s}"
|
171
|
+
block.call(send(method, name, http_method, ApiMethods::Base.validator(required, required_any, option).call(
|
172
|
+
:option => args.length > 0 ? args[0] : {})))
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def validator(required, required_any, option)
|
178
|
+
Proc.new do |args|
|
179
|
+
ApiMethods::Base.build_params_with_validation(args, required, required_any, option)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def build_params_with_validation(args, required, required_any, option)
|
184
|
+
options = {}
|
185
|
+
# api_key is common parameter.
|
186
|
+
required -= %w[api_key]
|
187
|
+
required.each do |name|
|
188
|
+
name = name.to_sym
|
189
|
+
raise ArgumentError.new("%s is required" % name) unless args[:required][name]
|
190
|
+
options[name] = args[:required][name]
|
191
|
+
end
|
192
|
+
if required_any.length > 0
|
193
|
+
unless required_any.inject(false){|r,i| r || args[:required_any].include?(i.to_sym)}
|
194
|
+
raise ArgumentError.new("%s is required" % required_any.join(' or '))
|
195
|
+
end
|
196
|
+
key = required_any.find {|name| args[:required_any].include?(name.to_sym)}
|
197
|
+
options[key.to_sym] = args[:required_any][key.to_sym] if key
|
198
|
+
end
|
199
|
+
if args[:option] && !args[:option].empty?
|
200
|
+
option.each do |name|
|
201
|
+
name = name.to_sym
|
202
|
+
options[name] = args[:option][name] if args[:option][name]
|
203
|
+
end
|
204
|
+
end
|
205
|
+
options
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
class Track < Base
|
211
|
+
def profile(options)
|
212
|
+
@api.request('track/profile',
|
213
|
+
:get,
|
214
|
+
options.merge(:bucket => 'audio_summary'))
|
215
|
+
end
|
216
|
+
|
217
|
+
def analyze(options)
|
218
|
+
@api.request('track/analyze',
|
219
|
+
:post,
|
220
|
+
options.merge(:bucket => 'audio_summary'))
|
221
|
+
end
|
222
|
+
|
223
|
+
def upload(options)
|
224
|
+
options.update(:bucket => 'audio_summary')
|
225
|
+
|
226
|
+
if options.has_key?(:filename)
|
227
|
+
filename = options.delete(:filename)
|
228
|
+
filetype = filename.match(/\.(mp3|au|ogg)$/)[1]
|
229
|
+
|
230
|
+
open(filename) do |f|
|
231
|
+
@api.request('track/upload',
|
232
|
+
:post,
|
233
|
+
options.merge(:filetype => filetype),
|
234
|
+
f)
|
235
|
+
end
|
236
|
+
else
|
237
|
+
@api.request('track/upload', :post, options)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def analysis(filename)
|
242
|
+
analysis_url = analysis_url(filename)
|
243
|
+
Analysis.new_from_url(analysis_url)
|
244
|
+
end
|
245
|
+
|
246
|
+
def analysis_url(filename)
|
247
|
+
md5 = Digest::MD5.hexdigest(open(filename).read)
|
248
|
+
|
249
|
+
while true
|
250
|
+
begin
|
251
|
+
response = profile(:md5 => md5)
|
252
|
+
rescue Api::Error => e
|
253
|
+
if e.message =~ /^The Identifier specified does not exist/
|
254
|
+
response = upload(:filename => filename)
|
255
|
+
else
|
256
|
+
raise
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
case response.body.track.status
|
261
|
+
when 'unknown'
|
262
|
+
upload(:filename => filename)
|
263
|
+
when 'pending'
|
264
|
+
sleep 60
|
265
|
+
when 'complete'
|
266
|
+
return response.body.track.audio_summary.analysis_url
|
267
|
+
when 'error'
|
268
|
+
raise Error.new(response.body.track.status)
|
269
|
+
when 'unavailable'
|
270
|
+
analyze(:md5 => md5)
|
271
|
+
end
|
272
|
+
|
273
|
+
sleep 5
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
class Artist < Base
|
279
|
+
class << self
|
280
|
+
def new_from_name(echonest, artist_name)
|
281
|
+
instance = new(echonest)
|
282
|
+
instance.artist_name = artist_name
|
283
|
+
instance
|
284
|
+
end
|
285
|
+
|
286
|
+
def method_with_artist_id(method_id, option, &block)
|
287
|
+
required_any = %w[id name]
|
288
|
+
http_method = :get
|
289
|
+
proc = lambda {|s| s.artist_name ? {:name => s.artist_name} : {:id => s.artist_id} }
|
290
|
+
method_with_required_any('artist', method_id, http_method, [], required_any, option, proc, block)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
attr_accessor :artist_name, :artist_id
|
295
|
+
|
296
|
+
method_with_artist_id(:audio, %w[format results start])
|
297
|
+
method_with_artist_id(:biographies, %w[format results start license])
|
298
|
+
method_with_artist_id(:blogs, %w[format results start])
|
299
|
+
method_with_artist_id(:familiarity, %w[format results start])
|
300
|
+
method_with_artist_id(:hotttnesss, %w[format results start])
|
301
|
+
method_with_artist_id(:images, %w[format results start license])
|
302
|
+
method_with_artist_id(:news, %w[format results start])
|
303
|
+
method_with_artist_id(:profile, %w[format results start bucket])
|
304
|
+
method_with_artist_id(:reviews, %w[format results start])
|
305
|
+
method_with_option(:search, %w[format results bucket limit name description fuzzy_match max_familiarity min_familiarity max_hotttnesss min_hotttnesss sort])
|
306
|
+
method_with_artist_id(:songs, %w[format results bucket limit])
|
307
|
+
method_with_artist_id(:similar, %w[format results start bucket max_familiarity min_familiarity max_hotttnesss min_hotttnesss reverse limit])
|
308
|
+
method_with_artist_id(:terms, %w[format sort])
|
309
|
+
method_with_option(:top_hottt, %w[format results start bucket limit type])
|
310
|
+
method_with_option(:top_terms, %w[format results])
|
311
|
+
method_with_artist_id(:urls, %w[format])
|
312
|
+
method_with_artist_id(:video, %w[format results start])
|
313
|
+
end
|
314
|
+
|
315
|
+
class Song < Base
|
316
|
+
method_with_option(:search, %w[format title artist combined description artist_id results max_tempo min_tempo max_duration min_duration max_loudness min_loudness max_familiarity min_familiarity max_hotttnesss min_hotttnesss min_longitude max_longitude min_latitude max_latitude mode key bucket sort limitt])
|
317
|
+
method_with_required_any('song', :profile, :get, %w[api_key id], [], %w[format bucket limit], lambda{})
|
318
|
+
# method_with_option(:identify, %w[query code artist title release duration genre bucket])
|
319
|
+
def identify(opts)
|
320
|
+
file = opts.delete(:code)
|
321
|
+
@api.request('song/identify', :post, opts, file).body
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
class Playlist < Base
|
326
|
+
method_with_option(:static, %w[format type artist_pick variety artist_id artist song_id description results max_tempo min_tempo max_duration min_duration max_loudness min_loudness artist_max_familiarity artist_min_familiarity artist_max_hotttnesss artist_min_hotttnesss song_max_hotttnesss song_min_hotttnesss artist_min_longitude aritst_max_longitude artist_min_latitude arist_max_latitude mode key bucket sort limit audio])
|
327
|
+
method_with_option(:dynamic, %w[format type artist_pick variety artist_id artist song_id description results max_tempo min_tempo max_duration min_duration max_loudness min_loudness artist_max_familiarity artist_min_familiarity artist_max_hotttnesss artist_min_hotttnesss song_max_hotttnesss song_min_hotttnesss artist_min_longitude aritst_max_longitude artist_min_latitude arist_max_latitude mode key bucket sort limit audio session_id dmca rating chain_xspf])
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
class HTTPClient
|
333
|
+
def agent_name
|
334
|
+
@session_manager.agent_name
|
335
|
+
end
|
336
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Segment < Section
|
2
|
+
attr_reader :loudness, :max_loudness, :pitches, :timbre
|
3
|
+
|
4
|
+
def initialize(start, duration, confidence, loudness, max_loudness, pitches, timbre)
|
5
|
+
super(start, duration, confidence)
|
6
|
+
|
7
|
+
@loudness = loudness
|
8
|
+
@max_loudness = max_loudness
|
9
|
+
@pitches = pitches
|
10
|
+
@timbre = timbre
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'hashie'
|
3
|
+
|
4
|
+
module Echonest
|
5
|
+
class Response
|
6
|
+
attr_reader :json
|
7
|
+
|
8
|
+
def initialize(body)
|
9
|
+
@json = Hashie::Mash.new(JSON.parse(body))
|
10
|
+
end
|
11
|
+
|
12
|
+
def status
|
13
|
+
@status ||= Status.new(body)
|
14
|
+
end
|
15
|
+
|
16
|
+
def success?
|
17
|
+
status.code == Status::SUCCESS
|
18
|
+
end
|
19
|
+
|
20
|
+
def body
|
21
|
+
json.response
|
22
|
+
end
|
23
|
+
|
24
|
+
class Status
|
25
|
+
UNKNOWN_ERROR = -1
|
26
|
+
SUCCESS = 0
|
27
|
+
INVALID_API_KEY = 1
|
28
|
+
PERMISSION_DENIED = 2
|
29
|
+
RATE_LIMIT_EXCEEDED = 3
|
30
|
+
MISSING_PARAMETER = 4
|
31
|
+
INVALID_PARAMETER = 5
|
32
|
+
|
33
|
+
attr_reader :code, :message
|
34
|
+
|
35
|
+
def initialize(response_body)
|
36
|
+
@code = response_body.status.code
|
37
|
+
@message = response_body.status.message
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Echonest
|
2
|
+
module TraditionalApiMethods
|
3
|
+
def self.included(c)
|
4
|
+
%w/tempo duration end_of_fade_in key loudness mode start_of_fade_out time_signature bars beats sections tatums segments/.
|
5
|
+
each do |method|
|
6
|
+
c.module_eval %Q{
|
7
|
+
def get_%s(filename)
|
8
|
+
track.analysis(filename).%s
|
9
|
+
end
|
10
|
+
} % [method, method]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/echonest.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'echonest/version'
|
2
|
+
require 'echonest/traditional_api_methods'
|
3
|
+
require 'echonest/api'
|
4
|
+
require 'echonest/analysis'
|
5
|
+
require 'echonest/response'
|
6
|
+
require 'echonest/element/section'
|
7
|
+
require 'echonest/element/bar'
|
8
|
+
require 'echonest/element/beat'
|
9
|
+
require 'echonest/element/segment'
|
10
|
+
require 'echonest/element/loudness'
|
11
|
+
require 'echonest/element/tatum'
|
12
|
+
|
13
|
+
def Echonest(api_key) Echonest::Api.new(api_key) end
|
14
|
+
|
15
|
+
module Echonest
|
16
|
+
end
|