the_little_streamer 0.4.2 → 0.4.3
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.
- 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
|
-
|