the_little_streamer 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +1 -1
- data/bin/the_little_streamer +12 -346
- data/lib/the_little_streamer/finder.rb +72 -0
- data/lib/the_little_streamer/html.rb +219 -0
- data/lib/the_little_streamer/music.rb +83 -0
- data/lib/the_little_streamer/player.rb +127 -0
- data/lib/the_little_streamer/searcher.rb +21 -0
- data/lib/the_little_streamer/song.rb +4 -0
- data/lib/the_little_streamer.rb +7 -0
- metadata +53 -67
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ba04105936f88f2cea71bf565934e892590cbf6f
|
4
|
+
data.tar.gz: 7da1b197bfc2e8ddac3bf5f3cee5faad14a09b8a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b8ba19974db05fc9e901b76a06945e6f01eac9954bff693baf50060a80593375b8456084967e585cee38c21e8defb4a93d522702f9bced89847ba7f9a87f359f
|
7
|
+
data.tar.gz: e249d40b2a7ab750448890d33c3226e5c16d038c574a9abb59f9aa8d973f6552e4b5d780212d3e1c4546a3af47123b54d3be0d20dc9d02219c6f83a28b629e64
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@ Browse to [http://localhost:4567](http://localhost:4567)
|
|
15
15
|
## Dependencies
|
16
16
|
|
17
17
|
* [Sinatra](http://www.sinatrarb.com/)
|
18
|
-
* [ruby
|
18
|
+
* [taglib-ruby](http://robinst.github.io/taglib-ruby/)
|
19
19
|
* A browser that supports HTML5, like [Chrome](http://www.google.com/chrome/)
|
20
20
|
|
21
21
|
You are strongly encouraged to use [Mongrel](https://github.com/fauna/mongrel) or [Thin](http://code.macournoyer.com/thin/) (installable as gems) instead of WEBrick, as they will be way faster. Just install one and Sinatra will prefer it over WEBrick.
|
data/bin/the_little_streamer
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
#This is a simple Sinatra application to play mp3/ogg files through a browser
|
3
3
|
#using the HTML5 audio tag
|
4
|
+
$LOAD_PATH.unshift "./lib"
|
4
5
|
|
5
6
|
require 'rubygems'
|
6
|
-
require 'taglib2'
|
7
7
|
require 'sinatra'
|
8
|
-
require 'cgi'
|
9
|
-
require 'uri'
|
10
8
|
require 'pathname'
|
9
|
+
require 'the_little_streamer'
|
11
10
|
|
12
11
|
#Supply a directory with all the music in it
|
13
12
|
abort "Point me to the music!" unless ARGV[0]
|
@@ -17,280 +16,6 @@ trap "INT" do
|
|
17
16
|
exit!
|
18
17
|
end
|
19
18
|
|
20
|
-
#Wrap up in HTML
|
21
|
-
def html body
|
22
|
-
<<-HTML
|
23
|
-
<html>
|
24
|
-
<head>
|
25
|
-
<style type="text/css">
|
26
|
-
#{css}
|
27
|
-
</style>
|
28
|
-
<title>The Little Streamer</title>
|
29
|
-
</head>
|
30
|
-
<body>
|
31
|
-
#{body}
|
32
|
-
</body>
|
33
|
-
<script>
|
34
|
-
if(document.getElementById('artist')) {
|
35
|
-
window.onload = function() {
|
36
|
-
var artist = document.getElementById('artist').firstChild.innerText;
|
37
|
-
var title = document.getElementById('title').innerHTML;
|
38
|
-
document.title = title + " by " + artist;
|
39
|
-
}
|
40
|
-
}
|
41
|
-
</script>
|
42
|
-
</html>
|
43
|
-
HTML
|
44
|
-
end
|
45
|
-
|
46
|
-
def css
|
47
|
-
<<-CSS
|
48
|
-
body {
|
49
|
-
font-family: Helvetica,Verdana,Arial,sans-serif;
|
50
|
-
font-size: 13pt;
|
51
|
-
}
|
52
|
-
|
53
|
-
span#title {
|
54
|
-
font-size: 14pt;
|
55
|
-
font-weight: bold;
|
56
|
-
}
|
57
|
-
|
58
|
-
span#artist {
|
59
|
-
font-size: 14pt;
|
60
|
-
font-weight: bold;
|
61
|
-
}
|
62
|
-
|
63
|
-
span#artist a {
|
64
|
-
text-decoration: none;
|
65
|
-
color: black;
|
66
|
-
}
|
67
|
-
|
68
|
-
span#artist a:hover {
|
69
|
-
text-decoration: underline;
|
70
|
-
}
|
71
|
-
|
72
|
-
span#album {
|
73
|
-
font-style: italic;
|
74
|
-
}
|
75
|
-
|
76
|
-
span#album a {
|
77
|
-
text-decoration: none;
|
78
|
-
color: black;
|
79
|
-
}
|
80
|
-
|
81
|
-
span#album a:hover {
|
82
|
-
text-decoration: underline;
|
83
|
-
}
|
84
|
-
|
85
|
-
div#playlist {
|
86
|
-
float: left;
|
87
|
-
margin-top: 15px;
|
88
|
-
height: 80%;
|
89
|
-
overflow: auto;
|
90
|
-
text-align: left;
|
91
|
-
}
|
92
|
-
|
93
|
-
div#playerbox {
|
94
|
-
float: left;
|
95
|
-
text-align: center;
|
96
|
-
}
|
97
|
-
CSS
|
98
|
-
end
|
99
|
-
|
100
|
-
#Create a link
|
101
|
-
def link root, path, text = path, params = {}
|
102
|
-
full_path = root.split('/').map! {|r| URI.escape r }.join('/') << "/" << URI.escape(path)
|
103
|
-
params = params.map { |k, v| "#{k}=#{v}" }.join "&"
|
104
|
-
"<a href=\"#{full_path}?#{params}\">#{text}</a>"
|
105
|
-
end
|
106
|
-
|
107
|
-
#Some navigation
|
108
|
-
def title artist, album = nil
|
109
|
-
if album
|
110
|
-
"<h3>#{link "/", "", "Music"} : #{link "/artist/", artist, artist} : #{album}</h3>"
|
111
|
-
else
|
112
|
-
"<h3>#{link "/", "", "Music"} : #{artist}</h3>"
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
#Audio tag
|
117
|
-
def audio path
|
118
|
-
<<-HTML
|
119
|
-
<audio id='player' onEnded='javascript:play_next()' src=#{("/" << URI.escape(path)).inspect} autobuffer controls autoplay >
|
120
|
-
You need the power of HTML5!
|
121
|
-
</audio>
|
122
|
-
<script type="text/javascript">
|
123
|
-
document.getElementById('player').volume = 0.3;
|
124
|
-
</script>
|
125
|
-
HTML
|
126
|
-
end
|
127
|
-
|
128
|
-
#HTML for displaying playlist
|
129
|
-
def playlist_html songs
|
130
|
-
list = []
|
131
|
-
|
132
|
-
songs.each_with_index do |song, i|
|
133
|
-
list << "<li><a href=\"javascript:play_index(#{i})\">#{song.artist} - #{song.title}</a></li>"
|
134
|
-
end
|
135
|
-
|
136
|
-
<<-HTML
|
137
|
-
<div style="clear:both"> </div>
|
138
|
-
<div id="playlist">
|
139
|
-
<ol>
|
140
|
-
#{list.join}
|
141
|
-
</ol>
|
142
|
-
</div>
|
143
|
-
HTML
|
144
|
-
end
|
145
|
-
|
146
|
-
#Javascript for playlist
|
147
|
-
def playlist_js songs
|
148
|
-
<<-JAVASCRIPT
|
149
|
-
<script type="text/javascript">
|
150
|
-
var player = document.getElementById('player');
|
151
|
-
var artist = document.getElementById('artist');
|
152
|
-
var album = document.getElementById('album');
|
153
|
-
var title = document.getElementById('title');
|
154
|
-
var playlist = [#{songs.map { |s| song_to_js s }.join "," }]
|
155
|
-
var current_song = 0;
|
156
|
-
|
157
|
-
play_next = function(reverse) {
|
158
|
-
if(reverse && current_song > 0)
|
159
|
-
current_song--;
|
160
|
-
else if(!reverse && current_song < playlist.length)
|
161
|
-
current_song++;
|
162
|
-
else
|
163
|
-
return;
|
164
|
-
|
165
|
-
play_current();
|
166
|
-
}
|
167
|
-
|
168
|
-
play_current = function() {
|
169
|
-
var song = playlist[current_song];
|
170
|
-
player.src = song.path;
|
171
|
-
artist.innerHTML = song.artist
|
172
|
-
album.innerHTML = song.album
|
173
|
-
title.innerHTML = song.title
|
174
|
-
document.title = song.title + " by " + artist.firstChild.innerText;
|
175
|
-
player.play();
|
176
|
-
}
|
177
|
-
|
178
|
-
play_index = function(index) {
|
179
|
-
current_song = index;
|
180
|
-
play_current();
|
181
|
-
}
|
182
|
-
</script>
|
183
|
-
JAVASCRIPT
|
184
|
-
end
|
185
|
-
|
186
|
-
#Javascript for a song
|
187
|
-
def song_to_js song
|
188
|
-
<<-JAVASCRIPT
|
189
|
-
{ artist: #{link("/artist", song.artist, song.artist).inspect},
|
190
|
-
album: #{link("/artist/#{song.artist}/album/", song.album, song.album).inspect},
|
191
|
-
title: #{song.title.inspect},
|
192
|
-
path: #{(CGI.escapeHTML "/" << CGI.escape(song.path)).inspect} }
|
193
|
-
JAVASCRIPT
|
194
|
-
end
|
195
|
-
|
196
|
-
#Back/forward links
|
197
|
-
def prev_and_next
|
198
|
-
"<a href='javascript:play_next(true)'>Prev</a> <a href='javascript:play_next()'>Next</a>"
|
199
|
-
end
|
200
|
-
|
201
|
-
#Output HTML for player
|
202
|
-
def player_html songs
|
203
|
-
html <<-HTML
|
204
|
-
<div id="playerbox">
|
205
|
-
#{song_info songs.first}<br/>
|
206
|
-
#{audio songs.first.path}<br/>
|
207
|
-
#{prev_and_next if songs.length > 1}
|
208
|
-
#{playlist_js songs}
|
209
|
-
#{playlist_html songs if songs.length > 1}
|
210
|
-
<div style="clear:both">#{link "/", "", "Back to All Music"}</div>
|
211
|
-
</div>
|
212
|
-
HTML
|
213
|
-
end
|
214
|
-
|
215
|
-
def play_all limit = 100, order = "normal"
|
216
|
-
songs = []
|
217
|
-
|
218
|
-
case order
|
219
|
-
when "random"
|
220
|
-
Music.values.each do |artist|
|
221
|
-
artist.values.each do |album|
|
222
|
-
album.values.each do |song|
|
223
|
-
songs << song
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
songs.shuffle!
|
229
|
-
else
|
230
|
-
Music.values.each do |artist|
|
231
|
-
artist.values.each do |album|
|
232
|
-
album.values.sort_by { |s| s.track }.each do |song|
|
233
|
-
songs << song
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
player_html songs[0..limit.to_i]
|
240
|
-
end
|
241
|
-
|
242
|
-
#HTML for playing all songs by artist
|
243
|
-
def play_artist artist, order = "normal"
|
244
|
-
songs = []
|
245
|
-
|
246
|
-
case order
|
247
|
-
when "random"
|
248
|
-
Music[artist].each_value do |album|
|
249
|
-
album.each_value do |song|
|
250
|
-
songs << song
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
songs.shuffle!
|
255
|
-
else
|
256
|
-
Music[artist].each_value do |album|
|
257
|
-
album.values.sort_by { |s| s.track }.each do |song|
|
258
|
-
songs << song
|
259
|
-
end
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
player_html songs
|
264
|
-
end
|
265
|
-
|
266
|
-
#HTML for playing all songs on an album
|
267
|
-
def play_album artist, album, order = "normal"
|
268
|
-
|
269
|
-
case order
|
270
|
-
when "random"
|
271
|
-
songs = Music[artist][album].values.shuffle!
|
272
|
-
else
|
273
|
-
songs = Music[artist][album].values.sort_by { |s| s.track }
|
274
|
-
end
|
275
|
-
|
276
|
-
player_html songs
|
277
|
-
end
|
278
|
-
|
279
|
-
#HTML for song information header
|
280
|
-
def song_info song
|
281
|
-
"<span id='title'>#{song.title}</span> by <span id='artist'>#{link "/artist/", song.artist}</span><br/><span id='album'>#{link "/artist/#{song.artist}/album", song.album}</span>"
|
282
|
-
end
|
283
|
-
|
284
|
-
#Storing all music information in here
|
285
|
-
Music = Hash.new do |h,k|
|
286
|
-
h[k] = Hash.new do |hash, key|
|
287
|
-
hash[key] = Hash.new
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
#Song information
|
292
|
-
Song = Struct.new :artist, :album, :title, :track, :path
|
293
|
-
|
294
19
|
#Used to figure out path to music files
|
295
20
|
root = Pathname.new ARGV[0]
|
296
21
|
|
@@ -302,101 +27,42 @@ set :run, true
|
|
302
27
|
|
303
28
|
$stderr.puts "Scanning music..."
|
304
29
|
|
305
|
-
|
30
|
+
music = TLS::Music.new(TLS::Finder.new(root, ARGV[0]))
|
306
31
|
|
307
|
-
|
308
|
-
|
309
|
-
#Grab information from all the song files
|
310
|
-
Dir.glob "#{ARGV[0]}/**/*.{mp3,ogg}", File::FNM_CASEFOLD do |file|
|
311
|
-
begin
|
312
|
-
info = TagLib2::File.new(file)
|
313
|
-
|
314
|
-
artist, album, title, track = info.artist, info.album, info.title, info.track
|
315
|
-
artist = "Unknown" if artist.nil? or artist.empty?
|
316
|
-
album = "Unknown" if album.nil? or album.empty?
|
317
|
-
|
318
|
-
info = nil
|
319
|
-
|
320
|
-
if title
|
321
|
-
[artist, album, title].each { |i| i.tr!('/', '-') }
|
322
|
-
Music[artist][album][title] = Song.new(artist, album, title, track, Pathname.new(file).relative_path_from(root).to_s)
|
323
|
-
end
|
324
|
-
rescue Exception => e
|
325
|
-
$stderr.puts e if $DEBUG
|
326
|
-
end
|
32
|
+
before do
|
33
|
+
@player = TLS::Player.new(music)
|
327
34
|
end
|
328
35
|
|
329
|
-
$stderr = old_stderr
|
330
|
-
|
331
|
-
GC.start #Clean up after TagLib2
|
332
|
-
|
333
36
|
#List artists
|
334
37
|
get '/' do
|
335
|
-
|
336
|
-
html <<-HTML
|
337
|
-
(Random
|
338
|
-
#{link("/", "all", "10", :limit => 10, :order => :random)}
|
339
|
-
#{link("/", "all", "50", :limit => 50, :order => :random) }
|
340
|
-
#{link("/", "all", "100", :limit => 100, :order => :random) })<br/><hr/>
|
341
|
-
|
342
|
-
#{Music.keys.sort.map { |a| link path, a }.join "<br/>"}<br/><br/>
|
343
|
-
HTML
|
38
|
+
@player.list_artists
|
344
39
|
end
|
345
40
|
|
346
41
|
get '/all' do
|
347
|
-
play_all params["limit"], params["order"]
|
42
|
+
@player.play_all params["limit"], params["order"]
|
348
43
|
end
|
349
44
|
|
350
45
|
#List albums by given artist
|
351
46
|
get '/artist/:artist/?' do |artist|
|
352
|
-
|
353
|
-
path = "/artist/#{artist}/album/"
|
354
|
-
html title(artist) <<
|
355
|
-
Music[artist].keys.sort.map { |a| link path, a }.join("<br/>") << "<br/><br/>" <<
|
356
|
-
link("/artist/#{artist}/", "play", "Play All") << " - " <<
|
357
|
-
link("/artist/#{artist}/", "play", "Randomly", :order => :random)
|
358
|
-
|
359
|
-
else
|
360
|
-
html "Could not find <b>#{artist}</b>."
|
361
|
-
end
|
47
|
+
@player.list_albums_by artist
|
362
48
|
end
|
363
49
|
|
364
50
|
#Play songs by given artist
|
365
51
|
get '/artist/:artist/play/?' do |artist|
|
366
|
-
|
367
|
-
play_artist artist, params["order"]
|
368
|
-
else
|
369
|
-
html "Could not find <b>#{artist}</b>."
|
370
|
-
end
|
52
|
+
@player.play_artist artist, params["order"]
|
371
53
|
end
|
372
54
|
|
373
55
|
#List songs on given album
|
374
56
|
get '/artist/:artist/album/:album/?' do |artist, album|
|
375
|
-
|
376
|
-
path = "/artist/#{artist}/album/#{album}/song/"
|
377
|
-
html title(artist, album) <<
|
378
|
-
Music[artist][album].values.sort_by { |s| s.track || 0}.map { |s| link path, s.title }.join("<br/>") << "<br/><br/>" <<
|
379
|
-
link("/artist/#{artist}/album/#{album}/", "play", "Play All") << " - " <<
|
380
|
-
link("/artist/#{artist}/album/#{album}/", "play", "Randomly", :order => :random)
|
381
|
-
else
|
382
|
-
html "Could not find <b>#{album}</b> for <b>#{artist}</b>."
|
383
|
-
end
|
57
|
+
@player.list_songs_on artist, album
|
384
58
|
end
|
385
59
|
|
386
60
|
#Play all songs from a given album
|
387
61
|
get '/artist/:artist/album/:album/play/?' do |artist, album|
|
388
|
-
|
389
|
-
play_album artist, album, params["order"]
|
390
|
-
else
|
391
|
-
html "Could not find <b>#{album}</b> by <b>#{artist}</b>."
|
392
|
-
end
|
62
|
+
@player.play_album artist, album, params["order"]
|
393
63
|
end
|
394
64
|
|
395
65
|
#Play song!
|
396
66
|
get '/artist/:artist/album/:album/song/:song/?' do |artist, album, title|
|
397
|
-
|
398
|
-
player_html [song]
|
399
|
-
else
|
400
|
-
html "Could not find <b>#{title}</b> on <b>#{album}</b> by <b>#{artist}</b>."
|
401
|
-
end
|
67
|
+
@player.play_song artist, album, title
|
402
68
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'taglib'
|
2
|
+
require 'the_little_streamer/song'
|
3
|
+
|
4
|
+
module TLS
|
5
|
+
class Finder
|
6
|
+
def initialize root, path
|
7
|
+
@root = root
|
8
|
+
@paths = [path] #maybe support multiple paths in future
|
9
|
+
@songs = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def each_song &block
|
13
|
+
if @songs
|
14
|
+
@songs.each(&block)
|
15
|
+
else
|
16
|
+
get_songs(&block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def each_file
|
21
|
+
@paths.each do |path|
|
22
|
+
Dir.glob "#{path}/**/*.{mp3,mP3,Mp3,MP3,ogg,Ogg,OGg,OGG,oGg,oGG,ogG,OgG}" do |file|
|
23
|
+
yield file
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_songs
|
29
|
+
puts "Getting songs..."
|
30
|
+
@songs = []
|
31
|
+
|
32
|
+
each_file do |file|
|
33
|
+
song = get_song file
|
34
|
+
|
35
|
+
if song
|
36
|
+
@songs << song
|
37
|
+
yield song
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
GC.start #Clean up after TagLib2
|
42
|
+
@songs
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_song file
|
46
|
+
song = nil
|
47
|
+
|
48
|
+
begin
|
49
|
+
TagLib::FileRef.open(file) do |fileref|
|
50
|
+
info = fileref.tag
|
51
|
+
|
52
|
+
artist, album, title, track = info.artist, info.album, info.title, info.track
|
53
|
+
|
54
|
+
artist = "Unknown" if artist.nil? or artist.empty?
|
55
|
+
album = "Unknown" if album.nil? or album.empty?
|
56
|
+
title = File.basename(file).split('.').first if title.nil? or title.empty?
|
57
|
+
|
58
|
+
if title
|
59
|
+
[artist, album, title].each { |i| i.tr!('/', '-') }
|
60
|
+
|
61
|
+
song = Song.new(artist, album, title, track, Pathname.new(file).relative_path_from(@root).to_s)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
rescue Exception => e
|
65
|
+
p e
|
66
|
+
#Should probably do something here?
|
67
|
+
end
|
68
|
+
|
69
|
+
song
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
module TLS::HTML
|
2
|
+
#Wrap up in HTML
|
3
|
+
def page body = nil
|
4
|
+
if block_given?
|
5
|
+
body = yield
|
6
|
+
end
|
7
|
+
|
8
|
+
<<-HTML
|
9
|
+
<html>
|
10
|
+
<head>
|
11
|
+
<style type="text/css">
|
12
|
+
#{css}
|
13
|
+
</style>
|
14
|
+
<title>The Little Streamer</title>
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
#{body}
|
18
|
+
</body>
|
19
|
+
<script>
|
20
|
+
if(document.getElementById('artist')) {
|
21
|
+
window.onload = function() {
|
22
|
+
var artist = document.getElementById('artist').firstChild.innerText;
|
23
|
+
var title = document.getElementById('title').innerHTML;
|
24
|
+
document.title = title + " by " + artist;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
</script>
|
28
|
+
</html>
|
29
|
+
HTML
|
30
|
+
end
|
31
|
+
|
32
|
+
def css
|
33
|
+
@css ||= <<-CSS
|
34
|
+
body {
|
35
|
+
font-family: Helvetica,Verdana,Arial,sans-serif;
|
36
|
+
font-size: 13pt;
|
37
|
+
}
|
38
|
+
|
39
|
+
span#title {
|
40
|
+
font-size: 14pt;
|
41
|
+
font-weight: bold;
|
42
|
+
}
|
43
|
+
|
44
|
+
span#artist {
|
45
|
+
font-size: 14pt;
|
46
|
+
font-weight: bold;
|
47
|
+
}
|
48
|
+
|
49
|
+
span#artist a {
|
50
|
+
text-decoration: none;
|
51
|
+
color: black;
|
52
|
+
}
|
53
|
+
|
54
|
+
span#artist a:hover {
|
55
|
+
text-decoration: underline;
|
56
|
+
}
|
57
|
+
|
58
|
+
span#album {
|
59
|
+
font-style: italic;
|
60
|
+
}
|
61
|
+
|
62
|
+
span#album a {
|
63
|
+
text-decoration: none;
|
64
|
+
color: black;
|
65
|
+
}
|
66
|
+
|
67
|
+
span#album a:hover {
|
68
|
+
text-decoration: underline;
|
69
|
+
}
|
70
|
+
|
71
|
+
div#playlist {
|
72
|
+
float: left;
|
73
|
+
margin-top: 15px;
|
74
|
+
height: 80%;
|
75
|
+
overflow: auto;
|
76
|
+
text-align: left;
|
77
|
+
}
|
78
|
+
|
79
|
+
div#playerbox {
|
80
|
+
float: left;
|
81
|
+
text-align: center;
|
82
|
+
}
|
83
|
+
CSS
|
84
|
+
|
85
|
+
@css
|
86
|
+
end
|
87
|
+
|
88
|
+
#Create a link
|
89
|
+
def link root, path, text = path, params = {}
|
90
|
+
full_path = root.split('/').map! {|r| URI.escape r }.join('/') << "/" << URI.escape(path)
|
91
|
+
params = params.map { |k, v| "#{k}=#{v}" }.join "&"
|
92
|
+
"<a href=\"#{full_path}?#{params}\">#{text}</a>"
|
93
|
+
end
|
94
|
+
|
95
|
+
#Some navigation
|
96
|
+
def title artist, album = nil
|
97
|
+
if album
|
98
|
+
"<h3>#{link "/", "", "Music"} : #{link "/artist/", artist, artist} : #{album}</h3>"
|
99
|
+
else
|
100
|
+
"<h3>#{link "/", "", "Music"} : #{artist}</h3>"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
#Audio tag
|
105
|
+
def audio path
|
106
|
+
<<-HTML
|
107
|
+
<audio id='player' onEnded='javascript:play_next()' src=#{("/" << URI.escape(path)).inspect} autobuffer controls autoplay >
|
108
|
+
You need the power of HTML5!
|
109
|
+
</audio>
|
110
|
+
<script type="text/javascript">
|
111
|
+
document.getElementById('player').volume = 0.3;
|
112
|
+
</script>
|
113
|
+
HTML
|
114
|
+
end
|
115
|
+
|
116
|
+
#HTML for displaying playlist
|
117
|
+
def playlist_html songs
|
118
|
+
list = []
|
119
|
+
|
120
|
+
songs.each_with_index do |song, i|
|
121
|
+
list << "<li><a href=\"javascript:play_index(#{i})\">#{song.artist} - #{song.title}</a></li>"
|
122
|
+
end
|
123
|
+
|
124
|
+
<<-HTML
|
125
|
+
<div style="clear:both"> </div>
|
126
|
+
<div id="playlist">
|
127
|
+
<ol>
|
128
|
+
#{list.join}
|
129
|
+
</ol>
|
130
|
+
</div>
|
131
|
+
HTML
|
132
|
+
end
|
133
|
+
|
134
|
+
#Javascript for playlist
|
135
|
+
def playlist_js songs
|
136
|
+
<<-JAVASCRIPT
|
137
|
+
<script type="text/javascript">
|
138
|
+
var player = document.getElementById('player');
|
139
|
+
var artist = document.getElementById('artist');
|
140
|
+
var album = document.getElementById('album');
|
141
|
+
var title = document.getElementById('title');
|
142
|
+
var playlist = [#{songs.map { |s| song_to_js s }.join "," }]
|
143
|
+
var current_song = 0;
|
144
|
+
|
145
|
+
play_next = function(reverse) {
|
146
|
+
if(reverse && current_song > 0)
|
147
|
+
current_song--;
|
148
|
+
else if(!reverse && current_song < playlist.length)
|
149
|
+
current_song++;
|
150
|
+
else
|
151
|
+
return;
|
152
|
+
|
153
|
+
play_current();
|
154
|
+
}
|
155
|
+
|
156
|
+
play_current = function() {
|
157
|
+
var song = playlist[current_song];
|
158
|
+
player.src = song.path;
|
159
|
+
artist.innerHTML = song.artist
|
160
|
+
album.innerHTML = song.album
|
161
|
+
title.innerHTML = song.title
|
162
|
+
document.title = song.title + " by " + artist.firstChild.innerText;
|
163
|
+
player.play();
|
164
|
+
}
|
165
|
+
|
166
|
+
play_index = function(index) {
|
167
|
+
current_song = index;
|
168
|
+
play_current();
|
169
|
+
}
|
170
|
+
</script>
|
171
|
+
JAVASCRIPT
|
172
|
+
end
|
173
|
+
|
174
|
+
#Javascript for a song
|
175
|
+
def song_to_js song
|
176
|
+
<<-JAVASCRIPT
|
177
|
+
{ artist: #{link("/artist", song.artist, song.artist).inspect},
|
178
|
+
album: #{link("/artist/#{song.artist}/album/", song.album, song.album).inspect},
|
179
|
+
title: #{song.title.inspect},
|
180
|
+
path: #{(CGI.escapeHTML "/" << CGI.escape(song.path)).inspect} }
|
181
|
+
JAVASCRIPT
|
182
|
+
end
|
183
|
+
|
184
|
+
#HTML for song information header
|
185
|
+
def song_info song
|
186
|
+
"<span id='title'>#{song.title}</span> by <span id='artist'>#{link "/artist/", song.artist}</span><br/><span id='album'>#{link "/artist/#{song.artist}/album", song.album}</span>"
|
187
|
+
end
|
188
|
+
|
189
|
+
#Back/forward links
|
190
|
+
def prev_and_next
|
191
|
+
@prev_and_next ||= "<a href='javascript:play_next(true)'>Prev</a> <a href='javascript:play_next()'>Next</a>"
|
192
|
+
@prev_and_next
|
193
|
+
end
|
194
|
+
|
195
|
+
#Output HTML for player
|
196
|
+
def player_html songs
|
197
|
+
page <<-HTML
|
198
|
+
<div id="playerbox">
|
199
|
+
#{song_info songs.first}<br/>
|
200
|
+
#{audio songs.first.path}<br/>
|
201
|
+
#{prev_and_next if songs.length > 1}
|
202
|
+
#{playlist_js songs}
|
203
|
+
#{playlist_html songs if songs.length > 1}
|
204
|
+
<div style="clear:both">#{link "/", "", "Back to All Music"}</div>
|
205
|
+
</div>
|
206
|
+
HTML
|
207
|
+
end
|
208
|
+
|
209
|
+
def search_box
|
210
|
+
<<-HTML
|
211
|
+
<div id="searchbox">
|
212
|
+
<form action="/search">
|
213
|
+
<label for="search">Search:</label>
|
214
|
+
<input type="text" />
|
215
|
+
</form>
|
216
|
+
</div>
|
217
|
+
HTML
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
class TLS::Music
|
2
|
+
def initialize finder
|
3
|
+
#Hash three levels deep for artist, album, and song
|
4
|
+
@music = Hash.new do |h,k|
|
5
|
+
h[k] = Hash.new do |hash, key|
|
6
|
+
hash[key] = Hash.new
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
@songs = []
|
11
|
+
|
12
|
+
finder.each_song do |song|
|
13
|
+
add_song song
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_song song
|
18
|
+
@music[song.artist][song.album][song.title] = song
|
19
|
+
@songs << song
|
20
|
+
end
|
21
|
+
|
22
|
+
def [] artist, album = nil, title = nil
|
23
|
+
if title
|
24
|
+
get_song artist, album, title
|
25
|
+
elsif album
|
26
|
+
get_album artist, album
|
27
|
+
elsif artist
|
28
|
+
get_artist artist
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def all_songs
|
33
|
+
@songs
|
34
|
+
end
|
35
|
+
|
36
|
+
def has? artist, album = nil, title = nil
|
37
|
+
not self[artist, album, title].empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_artist artist
|
41
|
+
@music[artist]
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_album artist, album
|
45
|
+
@music[artist][album]
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_song artist, album, title
|
49
|
+
@music[artist][album][title]
|
50
|
+
end
|
51
|
+
|
52
|
+
def artist_names
|
53
|
+
@music.keys.sort!
|
54
|
+
end
|
55
|
+
|
56
|
+
def album_names_by artist
|
57
|
+
get_artist(artist).keys.sort!
|
58
|
+
end
|
59
|
+
|
60
|
+
def song_names_on artist, album
|
61
|
+
get_album(artist, album).keys.sort!
|
62
|
+
end
|
63
|
+
|
64
|
+
def songs_on artist, album
|
65
|
+
get_album(artist, album).values
|
66
|
+
end
|
67
|
+
|
68
|
+
def songs_by artist
|
69
|
+
songs = []
|
70
|
+
|
71
|
+
get_artist(artist).each_value do |album|
|
72
|
+
album.each_value do |song|
|
73
|
+
songs << song
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
songs
|
78
|
+
end
|
79
|
+
|
80
|
+
def albums_by artist
|
81
|
+
get_artist(artist).values
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
class TLS::Player
|
2
|
+
include TLS::HTML
|
3
|
+
|
4
|
+
def initialize music
|
5
|
+
@music = music
|
6
|
+
end
|
7
|
+
|
8
|
+
def play_all limit = 100, order = "normal"
|
9
|
+
case order
|
10
|
+
when "random"
|
11
|
+
songs = @music.all_songs.shuffle
|
12
|
+
else
|
13
|
+
songs = @music.all_songs
|
14
|
+
end
|
15
|
+
|
16
|
+
player_html songs[0..limit.to_i]
|
17
|
+
end
|
18
|
+
|
19
|
+
#HTML for playing all songs by artist
|
20
|
+
def play_artist artist, order = "normal"
|
21
|
+
if @music.has? artist
|
22
|
+
songs = []
|
23
|
+
|
24
|
+
case order
|
25
|
+
when "random"
|
26
|
+
songs = @music.songs_by(artist).shuffle!
|
27
|
+
else
|
28
|
+
@music.album_names_by(artist).each do |album|
|
29
|
+
@music.get_album(artist, album).values.sort_by { |s| s.track }.each do |song|
|
30
|
+
songs << song
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
player_html songs
|
36
|
+
else
|
37
|
+
artist_not_found artist
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#HTML for playing all songs on an album
|
42
|
+
def play_album artist, album, order = "normal"
|
43
|
+
if @music.has? artist, album
|
44
|
+
if order == "random"
|
45
|
+
songs = @music.songs_on(artist, album).shuffle!
|
46
|
+
else
|
47
|
+
songs = @music.songs_on(artist, album).sort_by { |s| s.track }
|
48
|
+
end
|
49
|
+
|
50
|
+
player_html songs
|
51
|
+
else
|
52
|
+
album_not_found artist, album
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def play_song artist, album, title
|
57
|
+
song = @music.get_song(artist, album, title)
|
58
|
+
|
59
|
+
if song
|
60
|
+
player_html [song]
|
61
|
+
else
|
62
|
+
song_not_found
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def artist_not_found artist
|
67
|
+
page "Could not find <b>#{artist}</b>."
|
68
|
+
end
|
69
|
+
|
70
|
+
def song_not_found artist, album, title
|
71
|
+
page "Could not find <b>#{title}</b> on <b>#{album}</b> by <b>#{artist}</b>."
|
72
|
+
end
|
73
|
+
|
74
|
+
def album_not_found artist, album
|
75
|
+
page "Could not find <b>#{album}</b> by <b>#{artist}</b>."
|
76
|
+
end
|
77
|
+
|
78
|
+
def list_artists
|
79
|
+
path = '/artist/'
|
80
|
+
page <<-HTML
|
81
|
+
(Random
|
82
|
+
#{link("/", "all", "10", :limit => 10, :order => :random)}
|
83
|
+
#{link("/", "all", "50", :limit => 50, :order => :random) }
|
84
|
+
#{link("/", "all", "100", :limit => 100, :order => :random) })
|
85
|
+
#{search_box}
|
86
|
+
<br/><hr/>
|
87
|
+
#{@music.artist_names.map { |a| link path, a }.join "<br/>"}
|
88
|
+
<br/><br/>
|
89
|
+
HTML
|
90
|
+
end
|
91
|
+
|
92
|
+
def list_albums_by artist
|
93
|
+
if @music.has? artist
|
94
|
+
path = "/artist/#{artist}/album/"
|
95
|
+
|
96
|
+
page <<-HTML
|
97
|
+
#{title(artist)}
|
98
|
+
#{@music.album_names_by(artist).map { |a| link path, a }.join("<br/>")}
|
99
|
+
<br/><br/>
|
100
|
+
#{link("/artist/#{artist}/", "play", "Play All")} -
|
101
|
+
#{link("/artist/#{artist}/", "play", "Randomly", :order => :random)}
|
102
|
+
HTML
|
103
|
+
else
|
104
|
+
artist_not_found artist
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def list_songs_on artist, album
|
109
|
+
if @music.has? artist, album
|
110
|
+
path = "/artist/#{artist}/album/#{album}/song/"
|
111
|
+
|
112
|
+
page <<-HTML
|
113
|
+
#{title(artist, album)}
|
114
|
+
#{@music.songs_on(artist, album).sort_by { |s| s.track || 0}.map { |s| link path, s.title }.join("<br/>")}
|
115
|
+
<br/><br/>
|
116
|
+
#{link "/artist/#{artist}/album/#{album}/", "play", "Play All" } -
|
117
|
+
#{link "/artist/#{artist}/album/#{album}/", "play", "Randomly", :order => :random }
|
118
|
+
HTML
|
119
|
+
else
|
120
|
+
album_not_found artist, album
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def list_search search
|
125
|
+
songs = @music.find search
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module TLS
|
2
|
+
class Searcher
|
3
|
+
def initialize music
|
4
|
+
@music = music
|
5
|
+
end
|
6
|
+
|
7
|
+
def find_all search
|
8
|
+
search = normalize search
|
9
|
+
|
10
|
+
@music.all_songs.select do |song|
|
11
|
+
song.title.downcase.include? search or
|
12
|
+
song.album.downcase.include? search or
|
13
|
+
song.artist.downcase.include? search
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def normalize search
|
18
|
+
search.strip.gsub(/\s{2,}/, ' ').downcase
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
metadata
CHANGED
@@ -1,95 +1,81 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: the_little_streamer
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 4
|
9
|
-
- 2
|
10
|
-
version: 0.4.2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.3
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
6
|
+
authors:
|
13
7
|
- Justin Collins
|
14
8
|
autorequire:
|
15
9
|
bindir: bin
|
16
10
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
- !ruby/object:Gem::Dependency
|
11
|
+
date: 2013-08-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
21
14
|
name: sinatra
|
22
|
-
|
23
|
-
|
24
|
-
none: false
|
25
|
-
requirements:
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
26
17
|
- - ~>
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
|
29
|
-
segments:
|
30
|
-
- 1
|
31
|
-
- 3
|
32
|
-
version: "1.3"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
33
20
|
type: :runtime
|
34
|
-
version_requirements: *id001
|
35
|
-
- !ruby/object:Gem::Dependency
|
36
|
-
name: ruby-taglib2
|
37
21
|
prerelease: false
|
38
|
-
|
39
|
-
|
40
|
-
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: taglib-ruby
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
41
31
|
- - ~>
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
|
44
|
-
segments:
|
45
|
-
- 1
|
46
|
-
- 2
|
47
|
-
version: "1.02"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.6'
|
48
34
|
type: :runtime
|
49
|
-
|
50
|
-
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.6'
|
41
|
+
description: Point the Little Streamer to your music directory and it will serve up
|
42
|
+
the tunes using the HTML5 audio tag.
|
51
43
|
email:
|
52
|
-
executables:
|
44
|
+
executables:
|
53
45
|
- the_little_streamer
|
54
46
|
extensions: []
|
55
|
-
|
56
47
|
extra_rdoc_files: []
|
57
|
-
|
58
|
-
files:
|
48
|
+
files:
|
59
49
|
- bin/the_little_streamer
|
60
50
|
- README.md
|
51
|
+
- lib/the_little_streamer.rb
|
52
|
+
- lib/the_little_streamer/finder.rb
|
53
|
+
- lib/the_little_streamer/html.rb
|
54
|
+
- lib/the_little_streamer/searcher.rb
|
55
|
+
- lib/the_little_streamer/player.rb
|
56
|
+
- lib/the_little_streamer/music.rb
|
57
|
+
- lib/the_little_streamer/song.rb
|
61
58
|
homepage: http://github.com/presidentbeef/the_little_streamer
|
62
59
|
licenses: []
|
63
|
-
|
60
|
+
metadata: {}
|
64
61
|
post_install_message:
|
65
62
|
rdoc_options: []
|
66
|
-
|
67
|
-
require_paths:
|
63
|
+
require_paths:
|
68
64
|
- lib
|
69
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
none: false
|
80
|
-
requirements:
|
81
|
-
- - ">="
|
82
|
-
- !ruby/object:Gem::Version
|
83
|
-
hash: 3
|
84
|
-
segments:
|
85
|
-
- 0
|
86
|
-
version: "0"
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
87
75
|
requirements: []
|
88
|
-
|
89
76
|
rubyforge_project:
|
90
|
-
rubygems_version:
|
77
|
+
rubygems_version: 2.0.7
|
91
78
|
signing_key:
|
92
|
-
specification_version:
|
79
|
+
specification_version: 4
|
93
80
|
summary: Simple Sinatra application for streaming music.
|
94
81
|
test_files: []
|
95
|
-
|