mr_eko 0.2.4.1 → 0.3.0

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.
@@ -1,17 +1,17 @@
1
- class PlaylistTest < Test::Unit::TestCase
2
-
1
+ class PlaylistTest < Test::Unit::TestCase
2
+
3
3
  context "a new playlist" do
4
4
  setup do
5
5
  @playlist = MrEko::Playlist.new
6
6
  end
7
-
7
+
8
8
  should "have no songs" do
9
9
  assert_equal 0, @playlist.songs.size
10
10
  end
11
11
  end
12
-
12
+
13
13
  context "create_from_options" do
14
-
14
+
15
15
  setup do
16
16
  @options = {:tempo => 100..200}
17
17
  MrEko::Song.delete
@@ -23,113 +23,120 @@ class PlaylistTest < Test::Unit::TestCase
23
23
  assert_raise(MrEko::Playlist::NoSongsError){ MrEko::Playlist.create_from_options(@options) }
24
24
  assert_equal @playlist_count, MrEko::Playlist.count
25
25
  end
26
-
26
+
27
27
  should "create a playlist when there are songs found" do
28
- assert MrEko::Song.insert( :tempo => @options[:tempo].max,
29
- :filename => 'third_eye.mp3',
30
- :artist => 'Tool',
31
- :title => 'Third Eye',
32
- :md5 => Digest::MD5.hexdigest(Time.now.to_s),
33
- :created_on => Time.now,
34
- :duration => 567
35
- )
36
-
28
+ assert create_song(:tempo => @options[:tempo].max)
29
+
37
30
  assert MrEko::Playlist.create_from_options(@options)
38
31
  assert_equal @playlist_count + 1, MrEko::Playlist.count
39
32
  end
40
-
33
+
41
34
  should "filter out certain options before querying for songs" do
42
35
  unfiltered_options = {:name => "Rock You in Your Face mix #{rand(1000)}", :time_signature => 4}
43
36
  MrEko::Song.expects(:where).with(Not(has_key(:name))).once.returns(sequel_dataset_stub)
44
37
  assert_raise(MrEko::Playlist::NoSongsError){ MrEko::Playlist.create_from_options(unfiltered_options) }
45
38
  end
46
- end
47
-
48
- context "prepare_options!" do
49
-
39
+ end
40
+
41
+ context 'output' do
42
+ setup do
43
+ @playlist = MrEko::Playlist.create(:name => "Best Playlist#{rand(1000)}")
44
+ @song1 = create_song(:title => 'Song A')
45
+ @song2 = create_song(:title => 'Song B')
46
+ @playlist.songs << @song1
47
+ @playlist.songs << @song2
48
+ end
49
+
50
+ context 'default format' do
51
+
52
+ should 'be PLS' do
53
+ assert @playlist.output.match /^\[playlist\]/
54
+ assert @playlist.output.match /NumberOfEntries/
55
+ end
56
+ end
57
+
58
+ context 'text format' do
59
+
60
+ should 'contain a comma-sep list of the song name and file path' do
61
+ assert @playlist.output(:text).match /#{@song1.filename}\, #{@song1.title}/
62
+ assert @playlist.output(:text).match /#{@song2.filename}\, #{@song2.title}/
63
+ end
64
+ end
65
+ end
66
+
67
+ context "prepare_options" do
68
+
50
69
  context "when passed a preset option" do
51
-
70
+
52
71
  should "only use the presets' options, not the others passed" do
53
72
  opts = { :time_signature => 4, :preset => :gym }
54
- MrEko::Playlist.prepare_options!(opts)
55
- assert !opts.has_key?(:time_signature)
56
- assert_equal MrEko::Presets::FACTORY[:gym][:tempo], opts[:tempo]
73
+ transformed = MrEko::Playlist.prepare_options(opts)
74
+
75
+ assert_nil transformed.detect{ |opt| opt.has_key?(:time_signature) }
76
+ assert_equal MrEko::Presets::FACTORY[:gym].detect{ |opt| opt.has_key?(:tempo) }[:tempo],
77
+ transformed.detect{ |opt| opt.has_key?(:tempo) }[:tempo]
57
78
  end
58
79
  end
59
-
60
- context "for tempo" do
61
-
62
- should "not transform when tempo is a Range" do
63
- opts = {:tempo => 160..180}
64
- MrEko::Playlist.prepare_options!(opts)
65
- assert_equal 160..180, opts[:tempo]
80
+
81
+ context "transformation" do
82
+
83
+ should "handle less-than sign" do
84
+ transformed = MrEko::Playlist.prepare_options({:duration => "<20"})
85
+ assert_equal "duration < 20".lit, transformed.last
66
86
  end
67
-
68
- should "transform even when there aren't any passed tempo opts" do
69
- opts = {:time_signature => 4}
70
- MrEko::Playlist.prepare_options!(opts)
71
- assert opts.has_key? :tempo
87
+
88
+ should "handle greater-than sign" do
89
+ transformed = MrEko::Playlist.prepare_options({:tempo => ">151"})
90
+ assert_equal "tempo > 151".lit, transformed.last
72
91
  end
73
92
 
74
- should "remove min and max keys" do
75
- opts = {:min_tempo => 100, :max_tempo => 200}
76
- MrEko::Playlist.prepare_options!(opts)
77
- assert !opts.has_key?(:min_tempo)
78
- assert !opts.has_key?(:max_tempo)
93
+ should "handle basic assignment" do
94
+ transformed = MrEko::Playlist.prepare_options({:artist => "Radiohead"})
95
+ assert_equal( {:artist => "Radiohead"}, transformed.last )
79
96
  end
80
-
81
- should "create a range with the passed min and max tempos" do
82
- opts = {:min_tempo => 100, :max_tempo => 200}
83
- MrEko::Playlist.prepare_options!(opts)
84
- assert_equal 100..200, opts[:tempo]
97
+
98
+ context "percentage values" do
99
+ [:loudness, :energy, :danceability].each do |attribute|
100
+ should "translate #{attribute} into decimal form" do
101
+ transformed = MrEko::Playlist.prepare_options({attribute => 32})
102
+ assert_equal( {attribute => 0.32}, transformed.last )
103
+ end
104
+ end
85
105
  end
86
106
  end
87
-
88
- context "for duration" do
89
-
90
- should "not transform when duration is a Range" do
91
- opts = {:duration => 200..2010}
92
- MrEko::Playlist.prepare_options!(opts)
93
- assert_equal 200..2010, opts[:duration]
94
- end
95
-
96
- should "transform even when there aren't any passed duration opts" do
97
- opts = {:time_signature => 4}
98
- MrEko::Playlist.prepare_options!(opts)
99
- assert opts.has_key? :duration
107
+
108
+ context "defaults" do
109
+ should "be overridable" do
110
+ transformed = MrEko::Playlist.prepare_options({:tempo => 180})
111
+ assert_equal 180, transformed.detect{ |opt| opt.has_key?(:tempo) }[:tempo]
100
112
  end
101
113
 
102
- should "remove min and max keys" do
103
- opts = {:min_duration => 100, :max_duration => 2000}
104
- MrEko::Playlist.prepare_options!(opts)
105
- assert !opts.has_key?(:min_duration)
106
- assert !opts.has_key?(:max_duration)
114
+ should "be set for tempo" do
115
+ transformed = MrEko::Playlist.prepare_options({})
116
+ assert_equal 0..500, transformed.detect{ |opt| opt.has_key?(:tempo) }[:tempo]
107
117
  end
108
118
 
109
- should "create a range with the passed min and max durations" do
110
- opts = {:min_duration => 100, :max_duration => 2000}
111
- MrEko::Playlist.prepare_options!(opts)
112
- assert_equal 100..2000, opts[:duration]
119
+ should "be set for duration" do
120
+ transformed = MrEko::Playlist.prepare_options({})
121
+ assert_equal 10..1200, transformed.detect{ |opt| opt.has_key?(:duration) }[:duration]
113
122
  end
114
123
  end
115
-
124
+
116
125
  context "for mode" do
117
-
126
+
118
127
  should "transform into numeric representation" do
119
- opts = {:mode => 'minor'}
120
- MrEko::Playlist.prepare_options!(opts)
121
- assert_equal 0, opts[:mode]
128
+ transformed = MrEko::Playlist.prepare_options(:mode => 'minor')
129
+ assert_equal 0, transformed.detect{ |opt| opt.key?(:mode) }[:mode]
122
130
  end
123
131
  end
124
132
 
125
133
  context "for key" do
126
-
134
+
127
135
  should "transform into numeric representation" do
128
- opts = {:key => 'C#'}
129
- MrEko::Playlist.prepare_options!(opts)
130
- assert_equal 1, opts[:key]
136
+ transformed = MrEko::Playlist.prepare_options(:key => 'C#')
137
+ assert_equal 1, transformed.detect{ |opt| opt.key?(:key) }[:key]
131
138
  end
132
139
  end
133
140
 
134
141
  end
135
- end
142
+ end
data/test/song_test.rb ADDED
@@ -0,0 +1,158 @@
1
+ class SongTest < Test::Unit::TestCase
2
+
3
+ TEST_MP3 = File.join(File.dirname(__FILE__), 'data', 'they_want_a_test.mp3')
4
+ TAGLESS_MP3 = File.join(File.dirname(__FILE__), 'data', 'tagless.mp3')
5
+
6
+ def enmfp_data_stub(overrides={})
7
+ opts = {
8
+ 'code' => '98ouhajsnd081oi2he0da8sdoihjasdi2y9e8aASD3e8yaushdjQWD',
9
+ 'tag' => 0,
10
+ 'raw_data' => "[]", # JSON returned from EN
11
+ 'metadata' => {
12
+ 'artist' => 'SebastiAn',
13
+ 'title' => 'Ross Ross Ross',
14
+ 'release' => 'Total',
15
+ 'genre' => 'Electronic',
16
+ 'filename' => '/Users/you/Music/sebastian-ross_ross_ross.mp3',
17
+ 'bitrate' => 320,
18
+ 'sample_rate' => 44100,
19
+ 'codegen_time' => 9.221,
20
+ 'duration' => 219,
21
+ }
22
+ }.merge(overrides)
23
+
24
+ Hashie::Mash.new(opts)
25
+ end
26
+
27
+ def setup
28
+ MrEko::Song.delete
29
+ end
30
+
31
+
32
+ context 'create_from_file!' do
33
+
34
+ should 'catalog from tags by default' do
35
+ MrEko::Song.expects(:catalog_via_tags).with(TEST_MP3, kind_of(Hash)).returns(MrEko::Song.new)
36
+ MrEko::Song.create_from_file!(TEST_MP3)
37
+ end
38
+
39
+ should 'try cataloging via ENMFP when tags dont work' do
40
+ MrEko::Song.expects(:catalog_via_tags).with(TEST_MP3, kind_of(Hash)).returns(nil)
41
+ MrEko::Song.expects(:catalog_via_enmfp).with(TEST_MP3, kind_of(Hash)).returns(MrEko::Song.new)
42
+ MrEko::Song.create_from_file!(TEST_MP3)
43
+ end
44
+
45
+ should 'not try cataloging if we have it stored already' do
46
+ md5 = MrEko.md5(TEST_MP3)
47
+ stub = MrEko::Song.new
48
+
49
+ MrEko::Song.expects(:where).with(:md5 => md5).returns( [stub] )
50
+ MrEko::Song.expects(:catalog_via_enmfp).never
51
+ MrEko::Song.expects(:catalog_via_tags).never
52
+
53
+ assert_equal stub, MrEko::Song.create_from_file!(TEST_MP3)
54
+ end
55
+
56
+ should 'not atempt to catalog via ENMFP if the tags_only option is passed in' do
57
+ MrEko::Song.expects(:catalog_via_tags).with(TEST_MP3, kind_of(Hash)).returns(nil)
58
+ MrEko::Song.expects(:catalog_via_enmfp).never
59
+
60
+ MrEko::Song.create_from_file!(TEST_MP3, :tags_only => true)
61
+
62
+ end
63
+ end
64
+
65
+
66
+ context 'catalog_via_enmfp' do
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) }
71
+ end
72
+
73
+ should 'try to upload when no songs are returned from the Song#identify call' do
74
+ stub_data = enmfp_data_stub
75
+ empty_profile_stub = stub(:songs => [])
76
+ id_opts = {
77
+ :code => stub_data.raw_data,
78
+ :artist => stub_data.metadata.artist,
79
+ :title => stub_data.metadata.title,
80
+ :release => stub_data.metadata.release,
81
+ :bucket => 'audio_summary'
82
+ }
83
+ MrEko::Song.stubs(:enmfp_data).returns(stub_data)
84
+ Echonest::ApiMethods::Song.any_instance.expects(:identify).with(id_opts).returns(empty_profile_stub)
85
+ MrEko::Song.expects(:get_datapoints_by_upload).returns([stub_everything, stub_everything(:id => 'whatever')])
86
+ MrEko::Song.catalog_via_enmfp(TEST_MP3)
87
+ end
88
+
89
+ should 'try to get the profile data when a song is returned from the Song#identify call' do
90
+ stub_data = enmfp_data_stub
91
+ profile_stub = stub(:songs => [stub_everything(:id => 'FJJ299KLOP')])
92
+ profile_details_stub = stub(:songs => [stub(:audio_summary => stub_everything)])
93
+
94
+ MrEko::Song.stubs(:enmfp_data).returns(stub_data)
95
+ Echonest::ApiMethods::Song.any_instance.expects(:identify).returns(profile_stub)
96
+ Echonest::ApiMethods::Song.any_instance.expects(:profile).with(:id => 'FJJ299KLOP', :bucket => 'audio_summary').returns(profile_details_stub)
97
+ MrEko::Song.expects(:get_datapoints_by_upload).never
98
+
99
+
100
+ assert_difference 'MrEko::Song.count' do
101
+ MrEko::Song.catalog_via_enmfp(TEST_MP3)
102
+ end
103
+ end
104
+ end
105
+
106
+ context 'catalog_via_tags' do
107
+
108
+ context 'for a mp3 with no useful tag information' do
109
+
110
+ setup do
111
+ @mp3 = MrEko::Song.parse_id3_tags(TAGLESS_MP3)
112
+ assert_nil @mp3.artist
113
+ assert_nil @mp3.title
114
+ end
115
+
116
+ should 'return nil' do
117
+ assert_nil MrEko::Song.catalog_via_tags(TAGLESS_MP3)
118
+ end
119
+ end
120
+
121
+ context 'for a mp3 with the required tag information' do
122
+
123
+ setup do
124
+ @mp3 = MrEko::Song.parse_id3_tags(TEST_MP3)
125
+ assert_not_nil @mp3.artist
126
+ assert_not_nil @mp3.title
127
+ end
128
+
129
+ should 'create a Song' do
130
+ songs_stub = [stub(:audio_summary => stub_everything, :artist => @mp3.artist, :title => @mp3.title, :id => 'xxx')]
131
+ Echonest::ApiMethods::Song.any_instance.expects(:search).returns(stub(:songs => songs_stub))
132
+
133
+ assert_difference 'MrEko::Song.count' do
134
+ MrEko::Song.catalog_via_tags(TEST_MP3)
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ context 'cleaning funky ID3 tags' do
141
+
142
+ should "decode iTunes' crazy tags" do
143
+ dm = Iconv.conv('UTF-16', 'LATIN1', 'Dead Meadow')
144
+ tag_stub = OpenStruct.new(:artist => dm, :title => 'Good Moaning')
145
+ ID3Lib::Tag.expects(:new).once.returns(tag_stub)
146
+ parsed_tags = MrEko::Song.parse_id3_tags(TEST_MP3)
147
+
148
+ assert_equal "Dead Meadow", parsed_tags.artist
149
+ end
150
+
151
+ should "not blow up when there isn't any crazy encoding" do
152
+ tag_stub = OpenStruct.new(:artist => 'Dead Meadow', :title => 'Good Moaning')
153
+ ID3Lib::Tag.expects(:new).once.returns(tag_stub)
154
+
155
+ assert_nothing_raised{ MrEko::Song.parse_id3_tags(TEST_MP3) }
156
+ end
157
+ end
158
+ end
data/test/test.rb CHANGED
@@ -6,12 +6,17 @@ require 'test/unit'
6
6
  require 'shoulda'
7
7
  require 'mocha'
8
8
  require "mr_eko"
9
+ require "ruby-debug"
9
10
 
10
11
  require 'sequel/extensions/migration'
11
12
  Sequel::Migrator.apply(MrEko.connection, File.join(File.dirname(__FILE__), "..", "db", "migrate"))
13
+ # Clear the tables out
14
+ (MrEko.connection.tables - [:schema_info]).each do |table|
15
+ MrEko.connection.run "DELETE FROM #{table}"
16
+ end
12
17
 
13
18
  class Test::Unit::TestCase
14
-
19
+
15
20
  # Could be fleshed out some more.
16
21
  def sequel_dataset_stub
17
22
  data = mock()
@@ -19,4 +24,33 @@ class Test::Unit::TestCase
19
24
  data
20
25
  end
21
26
 
22
- end
27
+ def create_song(opts={})
28
+ defaults = {
29
+ :filename => 'third_eye.mp3',
30
+ :artist => 'Tool',
31
+ :title => 'Third Eye',
32
+ :md5 => Digest::MD5.hexdigest(Time.now.to_s + rand(10000000).to_s),
33
+ :created_on => Time.now,
34
+ :duration => 567,
35
+ :tempo => 143
36
+ }.merge(opts)
37
+
38
+ MrEko::Song.create(defaults)
39
+ end
40
+
41
+ def assert_difference(expression, difference = 1, message = nil, &block)
42
+ b = block.send(:binding)
43
+ exps = expression.is_a?(Array) ? expression : [expression]
44
+ before = exps.map { |e| eval(e, b) }
45
+
46
+ yield
47
+
48
+ exps.each_with_index do |e, i|
49
+ error = "#{e.inspect} didn't change by #{difference}"
50
+ error = "#{message}.\n#{error}" if message
51
+ assert_equal(before[i] + difference, eval(e, b), error)
52
+ end
53
+ end
54
+
55
+
56
+ end
@@ -0,0 +1,130 @@
1
+ class TimedPlaylistTest < Test::Unit::TestCase
2
+
3
+ context 'a new TimedPlaylist' do
4
+
5
+ should 'accept a hash of options' do
6
+ assert MrEko::TimedPlaylist.new(:length => 600, :name => 'whatever')
7
+ end
8
+
9
+ should 'set those options as expected' do
10
+ pl = MrEko::TimedPlaylist.new(:length => 600, :name => 'Awesome')
11
+ assert_equal 600, pl.length
12
+ assert_equal 'Awesome', pl.name
13
+ end
14
+
15
+ end
16
+
17
+ context 'initial' do
18
+
19
+ should 'add the attribute to the list of initial attributes' do
20
+ MrEko::TimedPlaylist.new(:length => 600, :name => 'sad shit') do |pl|
21
+ assert pl.initial(:mode, :minor)
22
+ assert_equal :minor, pl.attributes[:initial][:mode]
23
+ end
24
+ end
25
+ end
26
+
27
+ context 'final' do
28
+
29
+ should 'add the attribute to the list of final attributes' do
30
+ MrEko::TimedPlaylist.new(:length => 200, :name => 'Rock') do |pl|
31
+ assert pl.final(:tempo, 120)
32
+ assert_equal 120, pl.attributes[:final][:tempo]
33
+ end
34
+ end
35
+ end
36
+
37
+ context 'static' do
38
+
39
+ should 'add the attribute to the list of static attributes' do
40
+ MrEko::TimedPlaylist.new(:length => 1000, :name => 'Bump') do |pl|
41
+ assert pl.static(:genre, 'HipHop')
42
+ assert_equal 'HipHop', pl.attributes[:static][:genre]
43
+ end
44
+ end
45
+ end
46
+
47
+ context 'save' do
48
+
49
+ should 'populate the step_map' do
50
+ list = MrEko::TimedPlaylist.new(:length => 360) do |pl|
51
+ pl.initial(:tempo, 100)
52
+ pl.final(:tempo, 106)
53
+ end
54
+
55
+ assert list.step_map.empty?
56
+ assert list.save
57
+ assert !list.step_map.empty?
58
+ end
59
+
60
+ should 'increase the step length to 4.minutes if value is less that that' do
61
+ list = MrEko::TimedPlaylist.new(:length => 300) do |pl|
62
+ pl.initial(:tempo, 60)
63
+ pl.final(:tempo, 80)
64
+ end
65
+
66
+ assert list.save
67
+ assert_equal [20, 240], list.step_map[:tempo]
68
+ end
69
+
70
+ should 'populate the step_map with the proper mode step data' do
71
+ list = MrEko::TimedPlaylist.new(:length => 3060) do |pl|
72
+ pl.initial(:mode, :major)
73
+ pl.final(:mode, :minor)
74
+ end
75
+
76
+ assert list.save
77
+ assert_equal [2, 3060.to_f/2], list.step_map[:mode]
78
+ end
79
+
80
+ should 'populate the step_map with the proper tempo and loudness step data' do
81
+ list = MrEko::TimedPlaylist.new(:length => 3600) do |pl|
82
+ pl.initial(:tempo, 60)
83
+ pl.final(:tempo, 70)
84
+
85
+ pl.initial(:loudness, -13)
86
+ pl.final(:loudness, -9)
87
+ end
88
+
89
+ assert list.save
90
+ assert_equal [10, 3600.to_f/10], list.step_map[:tempo]
91
+ assert_equal [4, 3600.to_f/4], list.step_map[:loudness]
92
+ end
93
+
94
+ should 'populate the step_map with the proper energy and danceability fractional step data' do
95
+ list = MrEko::TimedPlaylist.new(:length => 3600) do |pl|
96
+ pl.initial(:energy, 0.622)
97
+ pl.final(:energy, 0.888)
98
+
99
+ pl.initial(:danceability, 0.22)
100
+ pl.final(:danceability, 0.88)
101
+ end
102
+
103
+ assert list.save
104
+ assert_equal [3, (3600.to_f/3).round], list.step_map[:energy]
105
+ assert_equal [7, (3600.to_f/7).round], list.step_map[:danceability]
106
+ end
107
+
108
+ should 'populate the step_map with the proper key step data' do
109
+ list = MrEko::TimedPlaylist.new(:length => 3060) do |pl|
110
+ pl.initial(:key, 'C#')
111
+ pl.final(:key, 'A#')
112
+ end
113
+
114
+ step = MrEko.key_lookup('A#') - MrEko.key_lookup('C#')
115
+ assert list.save
116
+ assert_equal [step, 3060.to_f/step], list.step_map[:key]
117
+ end
118
+
119
+
120
+ context 'validation' do
121
+ should "raise an exception when initial and final attribute keys don't match" do
122
+ pl = MrEko::TimedPlaylist.new(:length => 1000) do |pl|
123
+ assert pl.initial(:tempo, 66)
124
+ end
125
+
126
+ assert_raise(MrEko::TimedPlaylist::InvalidAttributes){ pl.save }
127
+ end
128
+ end
129
+ end
130
+ end