mr_eko 0.2.4.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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