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 CHANGED
@@ -155,8 +155,7 @@ end
155
155
 
156
156
  desc 'Launch an IRB console'
157
157
  task :console do
158
- libs = "-rlib/mr_eko -rirb/completion -rruby-debug"
159
- exec "irb #{libs}"
158
+ exec "bundle console"
160
159
  end
161
160
 
162
161
  namespace :db do
@@ -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, :loudness
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
- fingerprint_json = enmfp_data(filename, md5)
43
-
44
- if fingerprint_json.keys.include?('error')
45
- raise EnmfpError, "Errors returned in the ENMFP fingerprint data: #{fingerprint_json.error.inspect}"
46
- else
47
- begin
48
- log "Identifying with ENMFP code"
49
-
50
- identify_options = {}.tap do |opts|
51
- opts[:code] = fingerprint_json.raw_data
52
- opts[:artist] = fingerprint_json.metadata.artist
53
- opts[:title] = fingerprint_json.metadata.title
54
- opts[:release] = fingerprint_json.metadata.release
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
- analysis = MrEko.nest.song.search(:artist => tags.artist,
114
- :title => tags.title,
115
- :bucket => 'audio_summary',
116
- :limit => 1).songs.first
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 'Running ENMFP'
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
- raw_json = File.read fp_location(md5)
163
- hash = Hashie::Mash.new(JSON.parse(raw_json).first)
164
- hash.raw_data = raw_json
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.2'
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.2'
17
- s.date = '2011-11-30'
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
@@ -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
- [:loudness, :energy, :danceability].each do |attribute|
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 'raise an error if the ENMFP fingerprint contains errors' do
69
- MrEko::Song.stubs(:enmfp_data).returns(enmfp_data_stub('error' => 'BOOM'))
70
- assert_raise(MrEko::Song::EnmfpError){ MrEko::Song.catalog_via_enmfp(TEST_MP3) }
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
@@ -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 that that' do
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: 23
4
+ hash: 21
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
- - 2
10
- version: 0.3.2
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-11-30 00:00:00 Z
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: