jimmy_jukebox 0.2.6

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.
Files changed (40) hide show
  1. data/LICENSE.txt +340 -0
  2. data/bin/jload_jukebox +29 -0
  3. data/bin/jplay_jukebox +49 -0
  4. data/bin/load_jukebox +29 -0
  5. data/bin/play_jukebox +49 -0
  6. data/lib/jimmy_jukebox/artists.rb +45 -0
  7. data/lib/jimmy_jukebox/song_loader.rb +119 -0
  8. data/lib/jimmy_jukebox/songs/ArtTatum.yml +169 -0
  9. data/lib/jimmy_jukebox/songs/ArtieShaw.yml +581 -0
  10. data/lib/jimmy_jukebox/songs/BennieMoten.yml +108 -0
  11. data/lib/jimmy_jukebox/songs/BennyGoodman.yml +402 -0
  12. data/lib/jimmy_jukebox/songs/BillieHoliday.yml +64 -0
  13. data/lib/jimmy_jukebox/songs/BixBeiderbecke.yml +96 -0
  14. data/lib/jimmy_jukebox/songs/CharlieChristian.yml +9 -0
  15. data/lib/jimmy_jukebox/songs/CharlieParker.yml +33 -0
  16. data/lib/jimmy_jukebox/songs/ColemanHawkins.yml +17 -0
  17. data/lib/jimmy_jukebox/songs/CountBasie.yml +45 -0
  18. data/lib/jimmy_jukebox/songs/DizzyGillespie.yml +4 -0
  19. data/lib/jimmy_jukebox/songs/DjangoReinhardt.yml +76 -0
  20. data/lib/jimmy_jukebox/songs/DukeEllington.yml +159 -0
  21. data/lib/jimmy_jukebox/songs/EarlHines.yml +99 -0
  22. data/lib/jimmy_jukebox/songs/FletcherHenderson.yml +159 -0
  23. data/lib/jimmy_jukebox/songs/JamesPJohnson.yml +9 -0
  24. data/lib/jimmy_jukebox/songs/JellyRollMorton.yml +90 -0
  25. data/lib/jimmy_jukebox/songs/KingOliver.yml +61 -0
  26. data/lib/jimmy_jukebox/songs/LionelHampton.yml +149 -0
  27. data/lib/jimmy_jukebox/songs/LouisArmstrong.yml +151 -0
  28. data/lib/jimmy_jukebox/songs/MilesDavis.yml +9 -0
  29. data/lib/jimmy_jukebox/songs/OriginalDixielandJazzBand.yml +46 -0
  30. data/lib/jimmy_jukebox/songs/RedNorvo.yml +40 -0
  31. data/lib/jimmy_jukebox/songs/SidneyBechet.yml +26 -0
  32. data/lib/jimmy_jukebox/user_config.rb +202 -0
  33. data/lib/jimmy_jukebox/version.rb +3 -0
  34. data/lib/jimmy_jukebox.rb +120 -0
  35. data/roadmap.txt +20 -0
  36. data/spec/jimmy_jukebox_spec.rb +161 -0
  37. data/spec/song_loader_spec.rb +190 -0
  38. data/spec/spec_helper.rb +5 -0
  39. data/spec/user_config_spec.rb +148 -0
  40. metadata +192 -0
@@ -0,0 +1,3 @@
1
+ module JimmyJukebox
2
+ VERSION = '0.2.6'
3
+ end
@@ -0,0 +1,120 @@
1
+ module JimmyJukebox
2
+
3
+ # make system call and get pid so you can terminate process
4
+ def system_yield_pid(*cmd)
5
+ # would like to use Process.respond_to?(:fork) but JRuby mistakenly returns true
6
+ begin
7
+ pid = fork do # creates and runs block in subprocess (which will terminate with status 0), capture subprocess pid
8
+ exec(*cmd) # replaces current process with system call
9
+ exit! 127 # exit process and return exit status 127; should never be reached
10
+ end
11
+ rescue NotImplementedError
12
+ require 'rubygems'
13
+ require 'spoon'
14
+ pid = Spoon.spawnp(*cmd)
15
+ #raise "*** fork() not supported ***" unless Process.respond_to?(:fork)
16
+ end
17
+ yield pid if block_given? # call block, passing in the subprocess pid
18
+ Process.waitpid(pid) # Waits for a child process to exit, returns its process id, and sets $? to a Process::Status object
19
+ $? # return Process::Status object with instance methods .stopped?, .exited?, .exitstatus; see: http://www.ruby-doc.org/core/classes/Process/Status.html
20
+ end
21
+
22
+ class Jukebox
23
+
24
+ require 'jimmy_jukebox/user_config'
25
+
26
+ attr_reader :loop, :current_song_paused, :playing_pid
27
+
28
+ def initialize
29
+ @user_config = UserConfig.new
30
+ end
31
+
32
+ def play_loop
33
+ @loop = true
34
+ while @loop do
35
+ play
36
+ end
37
+ end
38
+
39
+ def play
40
+ begin
41
+ play_random_song(@user_config.songs)
42
+ rescue SystemExit, Interrupt => e
43
+ terminate_current_song
44
+ puts "\nMusic terminated by user"
45
+ exit
46
+ end
47
+ end
48
+
49
+ def quit
50
+ stop_looping
51
+ terminate_current_song
52
+ end
53
+
54
+ def skip_song
55
+ terminate_current_song
56
+ end
57
+
58
+ def pause_current_song
59
+ @current_song_paused = true
60
+ # jruby doesn't seem to handle system() correctly
61
+ # trying backticks
62
+ # system("kill -s STOP #{@playing_pid}") if @playing_pid
63
+ `kill -s STOP #{@playing_pid}` if @playing_pid
64
+ end
65
+
66
+ def unpause_current_song
67
+ @current_song_paused = false
68
+ # jruby doesn't seem to handle system() correctly
69
+ # trying backticks
70
+ #system("kill -s CONT #{@playing_pid}") if @playing_pid
71
+ `kill -s CONT #{@playing_pid}` if @playing_pid
72
+ end
73
+
74
+ private
75
+
76
+ def stop_looping
77
+ @loop = false
78
+ end
79
+
80
+ def play_random_song(songs)
81
+ terminate_current_song
82
+ raise "JimmyJukebox has no songs to play!" if songs.length == 0
83
+ music_file = songs[rand(songs.length)]
84
+ play_file(music_file)
85
+ end
86
+
87
+ def terminate_current_song
88
+ if @playing_pid
89
+ @current_song_paused = false
90
+ # killing processes seems problematic in JRuby
91
+ # I've tried several approaches, and nothing seems reliable
92
+ Process.kill("SIGKILL",@playing_pid)
93
+ #Process.kill("SIGTERM",@playing_pid)
94
+ #`kill #{@playing_pid}` if @playing_pid
95
+ @playing_pid = nil
96
+ end
97
+ end
98
+
99
+ def play_file(music_file)
100
+ # TODO: refactor the duplicate code below into a method
101
+ if music_file =~ /\.mp3$/i && @user_config.mp3_player
102
+ process_status = play_file_with(music_file, @user_config.mp3_player)
103
+ elsif music_file =~ /\.ogg$/i && @user_config.ogg_player
104
+ process_status = play_file_with(music_file, @user_config.ogg_player)
105
+ else
106
+ raise "Attempted to play a file format this program cannot play"
107
+ end
108
+ process_status.exitstatus.to_i == 0 ? (@playing_pid = nil) : (raise "Experienced a problem playing a song")
109
+ end
110
+
111
+ def play_file_with(music_file,player)
112
+ puts "Press Ctrl-C to stop the music and exit this program"
113
+ system_yield_pid(player, File.expand_path(music_file)) do |pid|
114
+ @playing_pid = pid
115
+ end
116
+ end
117
+
118
+ end
119
+
120
+ end
data/roadmap.txt ADDED
@@ -0,0 +1,20 @@
1
+ TODO:
2
+ figure out why Macs running Ruby 1.8 won't skip song
3
+ add file downloading error handling
4
+ make playable on Windows
5
+ call strip on music directory names
6
+ chmod ug+rx downloaded music files?
7
+
8
+ Buddy Bolden
9
+ Bunk Johnson
10
+ Cab Calloway
11
+ Glenn Miller
12
+ Fats Waller
13
+ Ben Webster
14
+ Paul Whiteman
15
+ http://www.archive.org/search.php?query=paul%20whiteman%20AND%20mediatype%3Aaudio
16
+ Thelonious Monk
17
+ http://www.archive.org/details/1920s-redNicholsHisFivePennies-01-10
18
+ http://www.archive.org/details/1920sJazz-01-90
19
+ http://www.archive.org/details/LilArmstrong-01-10
20
+
@@ -0,0 +1,161 @@
1
+ require 'spec_helper'
2
+ require 'fakefs/safe'
3
+ require 'jimmy_jukebox'
4
+ include JimmyJukebox
5
+
6
+ # Override exec() to prevent songs from actually playing
7
+ # Instead, start a brief sleep process
8
+ module Kernel
9
+ alias :real_exec :exec
10
+
11
+ def exec(*cmd)
12
+ real_exec("sleep 0.2")
13
+ end
14
+ end
15
+
16
+ describe Jukebox do
17
+
18
+ before(:all) do
19
+ #ARGV.clear
20
+ ARGV.pop
21
+ end
22
+
23
+ context "with no command line parameter" do
24
+
25
+ it "can be instantiated" do
26
+ jj = Jukebox.new
27
+ jj.should_not be_nil
28
+ jj.quit
29
+ end
30
+
31
+ #it "raises exception when no songs available"
32
+ # lambda do
33
+ # jj = Jukebox.new
34
+ # end.should raise_error
35
+ #end
36
+
37
+ it "generates a non-empty song list" do
38
+ jj = Jukebox.new
39
+ jj.instance_variable_get(:@user_config).songs.should_not be_nil
40
+ jj.instance_variable_get(:@user_config).songs.should_not be_empty
41
+ jj.instance_variable_get(:@user_config).songs.length.should be > 0
42
+ end
43
+
44
+ it "generates a non-empty song list with only mp3 & ogg files" do
45
+ jj = Jukebox.new
46
+ jj.instance_variable_get(:@user_config).songs.each do |song|
47
+ song.should match(/.*\.mp3|.*\.ogg/i)
48
+ end
49
+ end
50
+
51
+ it "can play" do
52
+ jj = Jukebox.new
53
+ thread = Thread.new do
54
+ jj.play
55
+ end
56
+ sleep 0.2
57
+ jj.instance_variable_get(:@playing_pid).should_not be_nil
58
+ jj.should_receive(:terminate_current_song)
59
+ jj.quit
60
+ end
61
+
62
+ it "can play_loop" do
63
+ jj = Jukebox.new
64
+ thread = Thread.new do
65
+ jj.play_loop
66
+ end
67
+ sleep 0.1
68
+ song1 = jj.playing_pid
69
+ song1.should_not be_nil
70
+ jj.loop.should be_true
71
+ sleep 0.2
72
+ song2 = jj.playing_pid
73
+ song2.should_not be_nil
74
+ song2.should_not == song1
75
+ jj.quit
76
+ end
77
+
78
+ it "can skip a song" do
79
+ jj = Jukebox.new
80
+ thread = Thread.new do
81
+ jj.play_loop
82
+ end
83
+ sleep 0.2
84
+ song_1 = jj.playing_pid
85
+ jj.skip_song
86
+ sleep 0.2
87
+ song_2 = jj.playing_pid
88
+ jj.skip_song
89
+ sleep 0.2
90
+ song_3 = jj.playing_pid
91
+ song_1.should_not == song_2 || song_2.should_not == song_3
92
+ jj.quit
93
+ end
94
+
95
+ it "can pause the current song" do
96
+ jj = Jukebox.new
97
+ thread = Thread.new do
98
+ jj.play
99
+ end
100
+ sleep 0.1
101
+ song_1 = jj.playing_pid
102
+ jj.pause_current_song
103
+ song_2 = jj.playing_pid
104
+ song_1.should == song_2
105
+ jj.current_song_paused.should be_true
106
+ jj.quit
107
+ end
108
+
109
+ it "can unpause a paused song" do
110
+ jj = Jukebox.new
111
+ thread = Thread.new do
112
+ jj.play
113
+ end
114
+ sleep 0.05
115
+ song_1 = jj.playing_pid
116
+ jj.current_song_paused.should be_false
117
+ jj.pause_current_song
118
+ song_2 = jj.playing_pid
119
+ jj.current_song_paused.should be_true
120
+ song_2.should == song_1
121
+ jj.unpause_current_song
122
+ jj.current_song_paused.should be_false
123
+ song_3 = jj.playing_pid
124
+ jj.current_song_paused.should be_false
125
+ jj.pause_current_song
126
+ song_4 = jj.playing_pid
127
+ jj.current_song_paused.should be_true
128
+ song_4.should == song_3
129
+ jj.unpause_current_song
130
+ jj.current_song_paused.should be_false
131
+ jj.quit
132
+ end
133
+ end
134
+
135
+ context "with valid music directory as command line parameter" do
136
+
137
+ before(:each) do
138
+ ARGV.delete_if { |val| true }
139
+ ARGV << File.expand_path("~/Music")
140
+ end
141
+
142
+ it "can skip a song" do
143
+ jj = Jukebox.new
144
+ thread = Thread.new do
145
+ jj.play_loop
146
+ end
147
+ sleep 0.2
148
+ song_1 = jj.playing_pid
149
+ jj.skip_song
150
+ sleep 0.2
151
+ song_2 = jj.playing_pid
152
+ jj.skip_song
153
+ sleep 0.2
154
+ song_3 = jj.playing_pid
155
+ song_1.should_not == song_2 || song_2.should_not == song_3
156
+ jj.quit
157
+ end
158
+
159
+ end
160
+
161
+ end
@@ -0,0 +1,190 @@
1
+ require 'spec_helper'
2
+ require 'rubygems'
3
+ require 'rspec/mocks'
4
+ require 'fakeweb' # apparently must be required before fakefs
5
+ FakeWeb.allow_net_connect = false
6
+ require 'fakefs/safe'
7
+ require 'jimmy_jukebox/song_loader'
8
+
9
+ describe JimmyJukebox::SongLoader do
10
+
11
+ #before(:all) do
12
+ # If using fakefs without safe, make sure we're using FakeFS gem,
13
+ # not the real file system!
14
+ # File.directory?("/home").should be_false
15
+ #end
16
+
17
+ before(:all) do
18
+ #ARGV.delete_if { |val| true }
19
+ #ARGV.clear
20
+ ARGV.pop
21
+ end
22
+
23
+ before(:each) do
24
+ @sl = JimmyJukebox::SongLoader
25
+ end
26
+
27
+ describe "#create_save_dir" do
28
+
29
+ it "should create a directory" do
30
+ FakeFS do
31
+ topdir = File.join("/home","user_name4","Music")
32
+ subdir = File.join(topdir, "rock", "Beatles")
33
+ File.directory?(subdir).should be_false
34
+ @sl.create_save_dir(subdir)
35
+ File.directory?(subdir).should be_true
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ describe "#version_of_song_in_any_dir?" do
42
+
43
+ it "should return true if song in top of directory tree" do
44
+ FakeFS do
45
+ topdir = "/home/user_name1/Music"
46
+ songname = "Paperback_Writer.mp3"
47
+ File.directory?(topdir).should be_false
48
+ FileUtils.mkdir_p(topdir)
49
+ File.directory?(topdir).should be_true
50
+ Dir.chdir(topdir)
51
+ File.exists?(songname).should be_false
52
+ FileUtils.touch(songname)
53
+ File.exists?(songname).should be_true
54
+ @sl.version_of_song_in_any_dir?(songname,topdir).should be_true
55
+ end
56
+ end
57
+
58
+ it "should return true if song in subdirectory" do
59
+ FakeFS do
60
+ topdir = "/home/user_name2/Music"
61
+ subdir = File.join(topdir, "rock", "Beatles")
62
+ songname = "Paperback_Writer.mp3"
63
+ File.directory?(subdir).should be_false
64
+ FileUtils.mkdir_p(subdir)
65
+ FileUtils.touch(File.join(subdir, songname))
66
+ File.exists?(File.join(subdir, songname)).should be_true
67
+ @sl.version_of_song_in_any_dir?(songname,subdir).should be_true
68
+ end
69
+ end
70
+
71
+ it "should return false if song not in directory tree" do
72
+ FakeFS do
73
+ topdir = "/home/user_name3/Music"
74
+ subdir = File.join(topdir, "rock", "Beatles")
75
+ songname = "Paperback_Writer.mp3"
76
+ FileUtils.mkdir_p(subdir)
77
+ File.exists?(File.join(subdir, songname)).should be_false
78
+ @sl.version_of_song_in_any_dir?(songname,subdir).should be_false
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ describe "test defaults" do
85
+
86
+ it "should have a user_config" do
87
+ @sl.instance_variable_get(:@user_config).should_not be_nil
88
+ end
89
+
90
+ it "should have a user_config with a non-nil default_music_dir" do
91
+ @sl.instance_variable_get(:@user_config).default_music_dir.should == File.expand_path("~/Music")
92
+ end
93
+
94
+ it "should have a SUPPORTED_MUSIC_TYPES of '/\.mp3$|\.ogg$/i'" do
95
+ @sl::SUPPORTED_MUSIC_TYPES.should == /\.mp3$|\.ogg$/i
96
+ end
97
+
98
+ end
99
+
100
+ describe "#original_dixieland_jazz_band without dirname" do
101
+
102
+ context "no songs in directory" do
103
+
104
+ before(:each) do
105
+ @dirname = File.expand_path(@sl.instance_variable_get(:@user_config).default_music_dir + '/JAZZ/Original_Dixieland_Jazz_Band')
106
+ @sl.stub!(:version_of_song_in_any_dir?).and_return(false)
107
+ end
108
+
109
+ it "should try to download many songs" do
110
+ @sl.should_receive(:open).at_least(25).times
111
+ @sl.send(:original_dixieland_jazz_band)
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+
118
+ describe "#charlie_christian without dirname" do
119
+
120
+ context "no songs yet downloaded" do
121
+
122
+ it "should try to download many songs" do
123
+ dirname = File.expand_path(@sl.instance_variable_get(:@user_config).default_music_dir + '/JAZZ/Charlie_Christian')
124
+ @sl.stub!(:version_of_song_in_any_dir?).and_return(false)
125
+ @sl.should_receive(:open).exactly(8).times
126
+ @sl.charlie_christian
127
+ File.exists?(dirname).should be_true
128
+ end
129
+
130
+ end
131
+
132
+ context "all songs already downloaded" do
133
+
134
+ it "should not download any songs" do
135
+ dirname = File.expand_path(@sl.instance_variable_get(:@user_config).default_music_dir + '/JAZZ/Charlie_Christian')
136
+ @sl.stub!(:version_of_song_in_any_dir?).and_return(true)
137
+ @sl.should_not_receive(:open)
138
+ @sl.charlie_christian
139
+ end
140
+
141
+ end
142
+
143
+ end
144
+
145
+ describe "#lionel_hampton without dirname" do
146
+
147
+ it "should try to download many songs" do
148
+ dirname = File.expand_path(@sl.instance_variable_get(:@user_config).default_music_dir + '/JAZZ/Lionel_Hampton')
149
+ @sl.stub!(:version_of_song_in_any_dir?).and_return(false)
150
+ @sl.should_receive(:open).at_least(50).times
151
+ @sl.lionel_hampton
152
+ File.exists?(dirname).should be_true
153
+ end
154
+
155
+ end
156
+
157
+ describe "#dizzy_gillespie with dirname" do
158
+
159
+ it "should try to download three songs" do
160
+ dirname = File.expand_path(@sl.instance_variable_get(:@user_config).default_music_dir + '/JAZZ/Dizzy_Gillespie')
161
+ @sl.stub!(:version_of_song_in_any_dir?).and_return(false)
162
+ @sl.should_receive(:open).exactly(3).times
163
+ @sl.dizzy_gillespie(dirname)
164
+ end
165
+
166
+ it "should successfully download three songs" do
167
+ pending("use FakeWeb")
168
+ FakeWeb.register_uri(:any, "http://www.archive.org/download/DizzyGillespie-GroovinHigh/02.GroovinHigh.mp3", :response => "/home/james/Music/JAZZ/Dizzy_Gillespie/Groovin' High 1945.mp3")
169
+ FakeWeb.register_uri(:any, "http://www.archive.org/download/DizzyGillespie-Manteca/01Manteca.ogg", :response => "/home/james/Music/JAZZ/Dizzy_Gillespie/01Manteca.ogg")
170
+ FakeWeb.register_uri(:any, "http://www.archive.org/download/DizzyGillespieLouisArmstrong-UmbrellaMan/DizzyGillespieLouisArmstrong-UmbrellaMan.mp3", :response => "/home/james/Music/JAZZ/Dizzy_Gillespie/DizzyGillespieLouisArmstrong-UmbrellaMan.mp3")
171
+ dirname = File.expand_path("/home/user_name6/non-existent-dir")
172
+ File.exists?(dirname).should be_false
173
+ @sl.dizzy_gillespie(dirname)
174
+ #File.exists?(dirname + "/02.GroovinHigh.mp3").should be_true
175
+ #File.exists?(dirname + "/01Manteca.ogg").should be_true
176
+ #File.exists?(dirname + "/DizzyGillespieLouisArmstrong-UmbrellaMan.mp3").should be_true
177
+ FakeWeb.clean_registry
178
+ end
179
+
180
+ end
181
+
182
+ describe "call to non-existent method" do
183
+
184
+ it "should raise NoMethodError" do
185
+ lambda { @sl.non_existent_method }.should raise_error(NoMethodError)
186
+ end
187
+
188
+ end
189
+
190
+ end