mr_eko 0.3.2 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -2
- data/lib/mr_eko/playlist.rb +10 -4
- data/lib/mr_eko/song.rb +69 -39
- data/lib/mr_eko/timed_playlist.rb +1 -1
- data/lib/mr_eko.rb +1 -1
- data/mr_eko.gemspec +3 -2
- data/test/playlist_test.rb +1 -6
- data/test/song_test.rb +10 -3
- data/test/timed_playlist_test.rb +1 -1
- metadata +18 -4
data/Rakefile
CHANGED
data/lib/mr_eko/playlist.rb
CHANGED
@@ -17,15 +17,14 @@ class MrEko::Playlist < Sequel::Model
|
|
17
17
|
# TODO: Is a name (or persisting) even necessary?
|
18
18
|
MrEko.connection.transaction do
|
19
19
|
pl = create(:name => options.delete(:name) || "Playlist #{rand(10000)}")
|
20
|
-
options = prepare_options(options)
|
21
20
|
|
21
|
+
filter = apply_filters prepare_options(options)
|
22
|
+
songs = filter.all
|
22
23
|
|
23
|
-
songs = MrEko::Song.where(options).all
|
24
24
|
if songs.size > 0
|
25
25
|
songs.each{ |song| pl.add_song(song) }
|
26
26
|
pl.save
|
27
27
|
else
|
28
|
-
# pl.delete # TODO: Look into not creating Playlist in the 1st place
|
29
28
|
raise NoSongsError.new("No songs match those criteria!")
|
30
29
|
end
|
31
30
|
end
|
@@ -44,7 +43,7 @@ class MrEko::Playlist < Sequel::Model
|
|
44
43
|
|
45
44
|
case key
|
46
45
|
|
47
|
-
when :danceability, :energy
|
46
|
+
when :danceability, :energy
|
48
47
|
new_options << transform(key, value, true)
|
49
48
|
|
50
49
|
when :mode
|
@@ -94,6 +93,13 @@ class MrEko::Playlist < Sequel::Model
|
|
94
93
|
end
|
95
94
|
|
96
95
|
|
96
|
+
# Recursively add Sequel where clauses
|
97
|
+
def self.apply_filters(options, filtered=nil)
|
98
|
+
filtered = (filtered || MrEko::Song).where(options.pop)
|
99
|
+
filtered = apply_filters(options, filtered) unless options.empty?
|
100
|
+
filtered
|
101
|
+
end
|
102
|
+
|
97
103
|
# Returns a text representation of the Playlist.
|
98
104
|
def create_text
|
99
105
|
songs.inject("") do |list, song|
|
data/lib/mr_eko/song.rb
CHANGED
@@ -36,42 +36,30 @@ class MrEko::Song < Sequel::Model
|
|
36
36
|
# @param [String] location of the audio file
|
37
37
|
# @param [Hash] opts
|
38
38
|
# @option opts [String] :md5 pre-calculated MD5 of file
|
39
|
-
# @return [MrEko::Song] the created Song
|
39
|
+
# @return [MrEko::Song, NilClass] the created Song
|
40
40
|
def self.catalog_via_enmfp(filename, opts={})
|
41
|
+
return if file_too_big? filename
|
42
|
+
|
41
43
|
md5 = opts[:md5] || MrEko.md5(filename)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
opts[:bucket] = 'audio_summary'
|
56
|
-
end
|
57
|
-
|
58
|
-
profile = MrEko.nest.song.identify(identify_options)
|
59
|
-
|
60
|
-
raise EnmfpError, "Nothing returned" if profile.songs.empty?
|
61
|
-
profile = profile.songs.first
|
62
|
-
|
63
|
-
# Get the extended audio data from the profile
|
64
|
-
analysis = MrEko.nest.song.profile(:id => profile.id, :bucket => 'audio_summary').songs.first.audio_summary
|
65
|
-
rescue Exception => e
|
66
|
-
log %Q{Issues using ENMFP data "(#{e})" #{e.backtrace.join("\n")}}
|
67
|
-
analysis, profile = get_datapoints_by_upload(filename)
|
68
|
-
end
|
44
|
+
log "Identifying with ENMFP code #{filename}"
|
45
|
+
|
46
|
+
begin
|
47
|
+
fingerprint_json = enmfp_data(filename, md5)
|
48
|
+
|
49
|
+
profile = identify_from_enmfp_data(fingerprint_json)
|
50
|
+
|
51
|
+
# Get the extended audio data from the profile
|
52
|
+
analysis = MrEko.nest.song.profile(:id => profile.id, :bucket => 'audio_summary').songs.first.audio_summary
|
53
|
+
|
54
|
+
rescue EnmfpError => e
|
55
|
+
log %Q{Issues using ENMFP data "(#{e})" #{e.backtrace.join("\n")}}
|
56
|
+
analysis, profile = get_datapoints_by_upload(filename)
|
69
57
|
end
|
70
58
|
|
71
59
|
create do |song|
|
72
60
|
song.filename = File.expand_path(filename)
|
73
61
|
song.md5 = md5
|
74
|
-
song.code = fingerprint_json.code
|
62
|
+
song.code = fingerprint_json ? fingerprint_json.code : nil
|
75
63
|
song.tempo = analysis.tempo
|
76
64
|
song.duration = analysis.duration
|
77
65
|
song.fade_in = analysis.end_of_fade_in
|
@@ -84,10 +72,10 @@ class MrEko::Song < Sequel::Model
|
|
84
72
|
song.bitrate = profile.bitrate
|
85
73
|
song.title = profile.title
|
86
74
|
song.artist = profile.artist || profile.artist_name
|
87
|
-
song.album = profile.release
|
75
|
+
song.album = fingerprint_json ? fingerprint_json.metadata.release : profile.release
|
88
76
|
song.danceability = profile.audio_summary? ? profile.audio_summary.danceability : analysis.danceability
|
89
77
|
song.energy = profile.audio_summary? ? profile.audio_summary.energy : analysis.energy
|
90
|
-
end
|
78
|
+
end if analysis && profile
|
91
79
|
end
|
92
80
|
|
93
81
|
# Parses the file's ID3 tags and converts and strange encoding.
|
@@ -110,10 +98,17 @@ class MrEko::Song < Sequel::Model
|
|
110
98
|
return unless has_required_tags? tags
|
111
99
|
|
112
100
|
md5 = opts[:md5] || MrEko.md5(filename)
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
101
|
+
begin
|
102
|
+
analysis = MrEko.nest.song.search(:artist => tags.artist,
|
103
|
+
:title => tags.title,
|
104
|
+
:bucket => 'audio_summary',
|
105
|
+
:limit => 1).songs.first
|
106
|
+
rescue => e
|
107
|
+
log "BAD TAGS? #{tags.artist} - #{tags.title} - #{filename}"
|
108
|
+
log e.message
|
109
|
+
log e.backtrace.join("\n")
|
110
|
+
return
|
111
|
+
end
|
117
112
|
|
118
113
|
create do |song|
|
119
114
|
song.filename = File.expand_path(filename)
|
@@ -155,13 +150,23 @@ class MrEko::Song < Sequel::Model
|
|
155
150
|
# @return [Hash] data from the ENMFP process
|
156
151
|
def self.enmfp_data(filename, md5)
|
157
152
|
unless File.exists?(fp_location(md5))
|
158
|
-
log '
|
153
|
+
log 'Waiting for ENMFP binary...'
|
159
154
|
`#{File.join(MrEko::HOME_DIR, 'ext', 'enmfp', MrEko.enmfp_binary)} "#{File.expand_path(filename)}" > #{fp_location(md5)}`
|
160
155
|
end
|
161
156
|
|
162
|
-
|
163
|
-
|
164
|
-
|
157
|
+
begin
|
158
|
+
raw_json = File.read fp_location(md5)
|
159
|
+
hash = Hashie::Mash.new(JSON.parse(raw_json).first)
|
160
|
+
hash.raw_data = raw_json
|
161
|
+
|
162
|
+
if hash.keys.include?('error')
|
163
|
+
raise EnmfpError, "Errors returned in the ENMFP fingerprint data: #{hash.error.inspect}"
|
164
|
+
end
|
165
|
+
|
166
|
+
rescue JSON::ParserError => e
|
167
|
+
raise EnmfpError, e.message
|
168
|
+
end
|
169
|
+
|
165
170
|
hash
|
166
171
|
end
|
167
172
|
|
@@ -188,6 +193,31 @@ class MrEko::Song < Sequel::Model
|
|
188
193
|
self.md5 ||= MrEko.md5(filename)
|
189
194
|
end
|
190
195
|
|
196
|
+
# Is the song over a sane limit for uploading and ENMFP?
|
197
|
+
#
|
198
|
+
# @param [String] filename
|
199
|
+
# @return [Boolean]
|
200
|
+
def self.file_too_big?(file)
|
201
|
+
File.size(file)/1024/1024 > 50
|
202
|
+
end
|
203
|
+
|
204
|
+
# @param [Hash]
|
205
|
+
# @return [Hashie, EnmfpError] EN song API result
|
206
|
+
def self.identify_from_enmfp_data(json)
|
207
|
+
identify_options = {}.tap do |opts|
|
208
|
+
opts[:code] = json.raw_data
|
209
|
+
opts[:artist] = json.metadata.artist
|
210
|
+
opts[:title] = json.metadata.title
|
211
|
+
opts[:release] = json.metadata.release
|
212
|
+
opts[:bucket] = 'audio_summary'
|
213
|
+
end
|
214
|
+
|
215
|
+
profile = MrEko.nest.song.identify(identify_options)
|
216
|
+
raise EnmfpError, "Nothing returned from song/identify API call" if profile.songs.empty?
|
217
|
+
|
218
|
+
profile.songs.first
|
219
|
+
end
|
220
|
+
|
191
221
|
# Return the file path of the EN fingerprint JSON file
|
192
222
|
#
|
193
223
|
# @param [String] MD5 hash of the file
|
@@ -102,7 +102,7 @@ class MrEko::TimedPlaylist
|
|
102
102
|
direction = step_count > 0 ? :asc : :desc
|
103
103
|
sorted_tempos = [attributes[:initial][:tempo], attributes[:final][:tempo]].sort
|
104
104
|
tempo_range = Range.new(*sorted_tempos)
|
105
|
-
all_songs = MrEko::Song.where(:tempo => tempo_range).order("tempo #{direction}".lit).all
|
105
|
+
all_songs = MrEko::Song.where({:tempo => tempo_range} & ~{:duration => nil}).order("tempo #{direction}".lit).all
|
106
106
|
|
107
107
|
songs_to_examine_per_step = step_count > all_songs.size ? 1 : all_songs.size / step_count
|
108
108
|
|
data/lib/mr_eko.rb
CHANGED
@@ -19,7 +19,7 @@ EKO_ENV = ENV['EKO_ENV'] || 'development'
|
|
19
19
|
Sequel.default_timezone = :utc
|
20
20
|
|
21
21
|
module MrEko
|
22
|
-
VERSION = '0.3.
|
22
|
+
VERSION = '0.3.3'
|
23
23
|
USER_DIR = File.join(ENV['HOME'], ".mreko")
|
24
24
|
FINGERPRINTS_DIR = File.join(USER_DIR, 'fingerprints')
|
25
25
|
LOG_DIR = File.join(USER_DIR, 'logs')
|
data/mr_eko.gemspec
CHANGED
@@ -13,8 +13,8 @@ Gem::Specification.new do |s|
|
|
13
13
|
## If your rubyforge_project name is different, then edit it and comment out
|
14
14
|
## the sub! line in the Rakefile
|
15
15
|
s.name = 'mr_eko'
|
16
|
-
s.version = '0.3.
|
17
|
-
s.date = '2011-
|
16
|
+
s.version = '0.3.3'
|
17
|
+
s.date = '2011-12-02'
|
18
18
|
s.rubyforge_project = 'mr_eko'
|
19
19
|
|
20
20
|
## Make sure your summary is short. The description may be as long
|
@@ -60,6 +60,7 @@ Gem::Specification.new do |s|
|
|
60
60
|
s.add_development_dependency('test-unit', "~> 2.1")
|
61
61
|
s.add_development_dependency("ruby-debug")
|
62
62
|
s.add_development_dependency("autotest")
|
63
|
+
s.add_development_dependency("pry")
|
63
64
|
|
64
65
|
## Leave this section as-is. It will be automatically generated from the
|
65
66
|
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
data/test/playlist_test.rb
CHANGED
@@ -31,11 +31,6 @@ class PlaylistTest < Test::Unit::TestCase
|
|
31
31
|
assert_equal @playlist_count + 1, MrEko::Playlist.count
|
32
32
|
end
|
33
33
|
|
34
|
-
should "filter out certain options before querying for songs" do
|
35
|
-
unfiltered_options = {:name => "Rock You in Your Face mix #{rand(1000)}", :time_signature => 4}
|
36
|
-
MrEko::Song.expects(:where).with(Not(has_key(:name))).once.returns(sequel_dataset_stub)
|
37
|
-
assert_raise(MrEko::Playlist::NoSongsError){ MrEko::Playlist.create_from_options(unfiltered_options) }
|
38
|
-
end
|
39
34
|
end
|
40
35
|
|
41
36
|
context 'output' do
|
@@ -96,7 +91,7 @@ class PlaylistTest < Test::Unit::TestCase
|
|
96
91
|
end
|
97
92
|
|
98
93
|
context "percentage values" do
|
99
|
-
[:
|
94
|
+
[:energy, :danceability].each do |attribute|
|
100
95
|
should "translate #{attribute} into decimal form" do
|
101
96
|
transformed = MrEko::Playlist.prepare_options({attribute => 32})
|
102
97
|
assert_equal( {attribute => 0.32}, transformed.last )
|
data/test/song_test.rb
CHANGED
@@ -65,9 +65,16 @@ class SongTest < Test::Unit::TestCase
|
|
65
65
|
|
66
66
|
context 'catalog_via_enmfp' do
|
67
67
|
|
68
|
-
should '
|
69
|
-
MrEko::Song.stubs(:enmfp_data).
|
70
|
-
|
68
|
+
should 'try uploading if the ENMFP fingerprint contains errors' do
|
69
|
+
MrEko::Song.stubs(:enmfp_data).raises(MrEko::Song::EnmfpError)
|
70
|
+
MrEko::Song.expects(:get_datapoints_by_upload).once.with(TEST_MP3).returns([])
|
71
|
+
MrEko::Song.catalog_via_enmfp(TEST_MP3)
|
72
|
+
end
|
73
|
+
|
74
|
+
should 'return nil when the file is too big' do
|
75
|
+
MrEko::Song.stubs(:file_too_big?).returns(true)
|
76
|
+
MrEko.nest.song.expects(:identify).never
|
77
|
+
assert_nil MrEko::Song.catalog_via_enmfp(TEST_MP3)
|
71
78
|
end
|
72
79
|
|
73
80
|
should 'try to upload when no songs are returned from the Song#identify call' do
|
data/test/timed_playlist_test.rb
CHANGED
@@ -57,7 +57,7 @@ class TimedPlaylistTest < Test::Unit::TestCase
|
|
57
57
|
assert !list.step_map.empty?
|
58
58
|
end
|
59
59
|
|
60
|
-
should 'increase the step length to 4.minutes if value is less
|
60
|
+
should 'increase the step length to 4.minutes if value is less than that' do
|
61
61
|
list = MrEko::TimedPlaylist.new(:length => 300) do |pl|
|
62
62
|
pl.initial(:tempo, 60)
|
63
63
|
pl.final(:tempo, 80)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mr_eko
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 3
|
9
|
-
-
|
10
|
-
version: 0.3.
|
9
|
+
- 3
|
10
|
+
version: 0.3.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Ed Hickey
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-12-02 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: sequel
|
@@ -194,6 +194,20 @@ dependencies:
|
|
194
194
|
version: "0"
|
195
195
|
type: :development
|
196
196
|
version_requirements: *id012
|
197
|
+
- !ruby/object:Gem::Dependency
|
198
|
+
name: pry
|
199
|
+
prerelease: false
|
200
|
+
requirement: &id013 !ruby/object:Gem::Requirement
|
201
|
+
none: false
|
202
|
+
requirements:
|
203
|
+
- - ">="
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
hash: 3
|
206
|
+
segments:
|
207
|
+
- 0
|
208
|
+
version: "0"
|
209
|
+
type: :development
|
210
|
+
version_requirements: *id013
|
197
211
|
description: Catalogs music file data and exposes a playlist interface
|
198
212
|
email: bassnode@gmail.com
|
199
213
|
executables:
|