mr_eko 0.3.2 → 0.3.3
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/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:
|