shroom 0.0.1
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.
- data/LICENSE +674 -0
- data/README +16 -0
- data/Rakefile +51 -0
- data/bin/shroom +4 -0
- data/lib/sh_browse.rb +122 -0
- data/lib/sh_cover_art.rb +34 -0
- data/lib/sh_database.rb +86 -0
- data/lib/sh_global.rb +54 -0
- data/lib/sh_lyrics.rb +24 -0
- data/lib/sh_main.rb +27 -0
- data/lib/sh_player.rb +164 -0
- data/lib/sh_song.rb +164 -0
- data/lib/sh_util.rb +21 -0
- data/lib/sh_view.rb +430 -0
- data/lib/shroom-res/icon_16x16.png +0 -0
- data/lib/shroom-res/shroom.glade +193 -0
- metadata +123 -0
data/lib/sh_song.rb
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'mp3info'
|
3
|
+
require 'flacinfo'
|
4
|
+
require 'ogginfo'
|
5
|
+
require 'earworm'
|
6
|
+
require 'rbrainz'
|
7
|
+
include MusicBrainz
|
8
|
+
|
9
|
+
module Sh
|
10
|
+
class Song
|
11
|
+
attr_reader :path, :mime, :matches
|
12
|
+
attr_accessor :title, :artist, :album, :image, :lyrics, :track_num, :year
|
13
|
+
|
14
|
+
def initialize path
|
15
|
+
@path = File.expand_path path
|
16
|
+
ext = File.extname path
|
17
|
+
@mime = {
|
18
|
+
'.mp3' => 'audio/mpeg',
|
19
|
+
'.flac' => 'audio/flac',
|
20
|
+
'.ogg' => 'audio/ogg'
|
21
|
+
}[ext]
|
22
|
+
end
|
23
|
+
|
24
|
+
def duration
|
25
|
+
if not @duration
|
26
|
+
case
|
27
|
+
when 'audio/mpeg'
|
28
|
+
Mp3Info.open(@path) {|mp3| @duration = mp3.length}
|
29
|
+
when 'audio/flac'
|
30
|
+
when 'audio/ogg'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
return @duration
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def lookup!
|
39
|
+
track = lookup_multiple.first
|
40
|
+
if track
|
41
|
+
# Don't distinguish between bands with the same name by adding to the name
|
42
|
+
track.artist.disambiguation = false
|
43
|
+
# Pull data from response
|
44
|
+
self.title = track.title
|
45
|
+
self.artist = track.artist.to_s
|
46
|
+
artistid = track.artist.id.to_mbid.uuid
|
47
|
+
rel = track.releases.to_a.first
|
48
|
+
self.album = rel.title
|
49
|
+
releaseid = rel.id.to_mbid.uuid
|
50
|
+
# Determine track number
|
51
|
+
query = Webservice::Query.new
|
52
|
+
filter = Webservice::TrackFilter.new(:artistid => artistid, :releaseid => releaseid)
|
53
|
+
tracks = query.get_tracks(filter).entities
|
54
|
+
tracks.to_a.each_with_index do |t, i|
|
55
|
+
self.track_num = i + 1 if t.title == track.title and t.duration == track.duration
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def lookup_multiple
|
62
|
+
# Generate fingerprint and send to MusicDNS
|
63
|
+
ew = Earworm::Client.new(Sh::KEYS[:music_dns])
|
64
|
+
begin
|
65
|
+
info = ew.identify(:file => path)
|
66
|
+
rescue
|
67
|
+
printf "Couldn't generate fingerprint for %s\n> %s\n\n", path, $!
|
68
|
+
return
|
69
|
+
end
|
70
|
+
puids = info.puid_list
|
71
|
+
self.title = info.title
|
72
|
+
self.artist = info.artist_name
|
73
|
+
# Return if no matches are found
|
74
|
+
return if not title or not artist
|
75
|
+
# Get more information from MusicBrainz
|
76
|
+
query = Webservice::Query.new
|
77
|
+
filter = Webservice::TrackFilter.new(:artist => artist, :title => title, :puid => puids.first)
|
78
|
+
tracks = query.get_tracks(filter).entities
|
79
|
+
@matches = tracks || []
|
80
|
+
end
|
81
|
+
|
82
|
+
public
|
83
|
+
def read_tags!
|
84
|
+
case @mime
|
85
|
+
when 'audio/mpeg'
|
86
|
+
read_tags_mp3!
|
87
|
+
when 'audio/flac'
|
88
|
+
read_tags_flac!
|
89
|
+
when 'audio/ogg'
|
90
|
+
read_tags_ogg!
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
def read_tags_mp3!
|
96
|
+
Mp3Info.open path do |mp3|
|
97
|
+
t = mp3.tag
|
98
|
+
@title = t.title unless t.title and t.title.is_binary_data?
|
99
|
+
@artist = t.artist unless t.artist and t.artist.is_binary_data?
|
100
|
+
@album = t.album unless t.album and t.album.is_binary_data?
|
101
|
+
@year = t.year
|
102
|
+
@track_num = t.tracknum
|
103
|
+
@duration = mp3.length
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def read_tags_ogg!
|
108
|
+
OggInfo.open path do |ogg|
|
109
|
+
t = ogg.tag
|
110
|
+
@title = t.title unless t.title and t.title.is_binary_data?
|
111
|
+
@artist = t.artist unless t.artist and t.artist.is_binary_data?
|
112
|
+
@album = t.album unless t.album and t.album.is_binary_data?
|
113
|
+
#TODO: handle alternative date formats, like yyyy/mm/dd
|
114
|
+
@year = t.date.split('-').first.to_i
|
115
|
+
@track_num = t.tracknumber.to_i
|
116
|
+
@duration = ogg.length
|
117
|
+
end rescue Exception
|
118
|
+
self
|
119
|
+
end
|
120
|
+
|
121
|
+
def read_tags_flac!
|
122
|
+
flac = FlacInfo.new(path)
|
123
|
+
comments = flac.comment
|
124
|
+
t = {}
|
125
|
+
comments.each do |comment|
|
126
|
+
split = comment.split('=')
|
127
|
+
key = split.first.downcase
|
128
|
+
value = (split[1..-1]).join
|
129
|
+
value = nil if value == '' or value.is_binary_data?
|
130
|
+
case key
|
131
|
+
when 'title'
|
132
|
+
@title = value
|
133
|
+
when 'artist'
|
134
|
+
@artist = value
|
135
|
+
when 'album'
|
136
|
+
@album = value
|
137
|
+
when 'year'
|
138
|
+
#TODO: handle alternative date formats, like yyyy/mm/dd
|
139
|
+
@year = value.split('-').first.to_i
|
140
|
+
when 'tracknumber'
|
141
|
+
@track_num = value.to_i
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
public
|
147
|
+
def to_s
|
148
|
+
sprintf("%s, %s, %.2d - %s", artist, album, track_num, title)
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_html
|
152
|
+
t = title
|
153
|
+
t = "Unknown" if not t or t.is_binary_data?
|
154
|
+
t.gsub!("<", "<")
|
155
|
+
ar = artist
|
156
|
+
ar = "Unknown" if not ar or ar.is_binary_data?
|
157
|
+
ar.gsub!("<", "<")
|
158
|
+
al = album
|
159
|
+
al = "Unknown" if not al or al.is_binary_data?
|
160
|
+
al.gsub!("<", "<")
|
161
|
+
return "<b>#{t}</b> by <i>#{ar}</i> from <i>#{al}</i>".gsub("&", "&")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
data/lib/sh_util.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
class Object
|
2
|
+
def try_require lib
|
3
|
+
name = lib
|
4
|
+
name << '.rb' unless File.extname(name) == '.rb'
|
5
|
+
found = false
|
6
|
+
$:.each do |d|
|
7
|
+
found = !Dir["#{d}/#{name}"].empty?
|
8
|
+
break if found
|
9
|
+
end
|
10
|
+
require lib if found
|
11
|
+
found
|
12
|
+
end
|
13
|
+
|
14
|
+
def find_in_load_path(file)
|
15
|
+
$:.each do |path|
|
16
|
+
abs_file = "#{path}/#{file}"
|
17
|
+
return abs_file if File.exists? abs_file
|
18
|
+
end
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
end
|
data/lib/sh_view.rb
ADDED
@@ -0,0 +1,430 @@
|
|
1
|
+
require 'sh_browse'
|
2
|
+
require 'sh_lyrics'
|
3
|
+
require 'sh_cover_art'
|
4
|
+
require 'libglade2'
|
5
|
+
require 'gtk2'
|
6
|
+
include Gtk
|
7
|
+
|
8
|
+
module Sh
|
9
|
+
class View
|
10
|
+
TAB_QUEUE, TAB_BROWSE, TAB_LYRICS, NUM_TABS = *(0..3).to_a
|
11
|
+
TAB_NAMES = {
|
12
|
+
TAB_QUEUE => 'Queue',
|
13
|
+
TAB_BROWSE => 'Browse',
|
14
|
+
TAB_LYRICS => 'Lyrics'
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
Gtk.init
|
19
|
+
|
20
|
+
@rnotify = try_require 'RNotify'
|
21
|
+
|
22
|
+
Notify.init 'Shroom' if @rnotify
|
23
|
+
|
24
|
+
@glade= GladeXML.new(Global.locate('shroom.glade'))
|
25
|
+
|
26
|
+
cancelled = false
|
27
|
+
dialog = Dialog.new("Adding songs to database",
|
28
|
+
nil,
|
29
|
+
Dialog::MODAL,
|
30
|
+
[Stock::CANCEL, Dialog::RESPONSE_NONE])
|
31
|
+
dialog.signal_connect('response') { cancelled = true; dialog.destroy }
|
32
|
+
progress = ProgressBar.new
|
33
|
+
dialog.vbox.pack_start Label.new('Adding songs to database'), false, false, 8
|
34
|
+
lbl_path = Label.new
|
35
|
+
dialog.vbox.pack_start lbl_path, false, false, 0
|
36
|
+
dialog.vbox.pack_start progress, false, false, 8
|
37
|
+
dialog.width_request = 300
|
38
|
+
|
39
|
+
new_songs = []
|
40
|
+
Dir[$prefs[:library_dir]+'/**/*'].each do |path|
|
41
|
+
ext = File.extname path
|
42
|
+
if [".mp3", ".ogg", ".flac"].include? ext
|
43
|
+
new_songs << Song.new(path) unless $db.contains? path
|
44
|
+
end
|
45
|
+
end
|
46
|
+
if new_songs.length > 0
|
47
|
+
dialog.show_all
|
48
|
+
while (Gtk.events_pending?)
|
49
|
+
Gtk.main_iteration
|
50
|
+
end
|
51
|
+
end
|
52
|
+
inc = 1 / new_songs.length.to_f
|
53
|
+
new_songs.each do |song|
|
54
|
+
lbl_path.text = "..." + song.path[-30..-1]
|
55
|
+
puts "Adding: #{song.path}"
|
56
|
+
song.read_tags!
|
57
|
+
$db.save_song song
|
58
|
+
progress.fraction += inc
|
59
|
+
while (Gtk.events_pending?)
|
60
|
+
Gtk.main_iteration
|
61
|
+
end
|
62
|
+
break if cancelled
|
63
|
+
end
|
64
|
+
progress.pulse unless cancelled
|
65
|
+
$db.clean
|
66
|
+
dialog.destroy unless cancelled
|
67
|
+
|
68
|
+
# Clean up
|
69
|
+
new_songs = nil
|
70
|
+
dialog = nil
|
71
|
+
progress = nil
|
72
|
+
lbl_path = nil
|
73
|
+
|
74
|
+
icon = Gdk::Pixbuf.new Global.locate('icon_16x16.png')
|
75
|
+
|
76
|
+
@window = Window.new "Shroom"
|
77
|
+
@window.icon = icon
|
78
|
+
@window.resize 800, 600
|
79
|
+
@window.signal_connect('destroy') do
|
80
|
+
quit
|
81
|
+
end
|
82
|
+
|
83
|
+
@status_icon = StatusIcon.new
|
84
|
+
@status_icon.pixbuf = icon
|
85
|
+
@status_icon.visible = true
|
86
|
+
@status_icon.signal_connect('activate') do |widget|
|
87
|
+
@window.visible = !@window.visible?
|
88
|
+
end
|
89
|
+
|
90
|
+
content_pane = VBox.new(false, 0)
|
91
|
+
@window.add content_pane
|
92
|
+
|
93
|
+
content_pane.pack_start(create_menu, false, true, 0)
|
94
|
+
|
95
|
+
vpaned = VBox.new
|
96
|
+
content_pane.add vpaned
|
97
|
+
|
98
|
+
# Controls
|
99
|
+
vbox = VBox.new false, 0
|
100
|
+
vpaned.pack_start vbox, false, true, 8
|
101
|
+
hbox = HBox.new
|
102
|
+
lbl_song = Label.new
|
103
|
+
hbox.add lbl_song
|
104
|
+
lbl_time = Label.new(time_to_s(0) + "/" + time_to_s(0))
|
105
|
+
hbox.pack_start lbl_time, false, false, 8
|
106
|
+
vbox.pack_start hbox, false, false, 8
|
107
|
+
box_controls = HBox.new
|
108
|
+
vbox.add box_controls
|
109
|
+
@btn_play = ToggleButton.new
|
110
|
+
@btn_play.add Image.new Stock::MEDIA_PLAY, IconSize::SMALL_TOOLBAR
|
111
|
+
box_controls.pack_start @btn_play, false, true, 0
|
112
|
+
btn_prev = Button.new
|
113
|
+
btn_prev.add Image.new Stock::MEDIA_PREVIOUS, IconSize::SMALL_TOOLBAR
|
114
|
+
box_controls.pack_start btn_prev, false, true, 0
|
115
|
+
seeker = HScale.new 0, 1, 0.01
|
116
|
+
box_controls.add seeker
|
117
|
+
seeker.draw_value = false
|
118
|
+
btn_next = Button.new
|
119
|
+
btn_next.add Image.new Stock::MEDIA_NEXT, IconSize::SMALL_TOOLBAR
|
120
|
+
box_controls.pack_start btn_next, false, true, 0
|
121
|
+
|
122
|
+
seeker.signal_connect('change_value') do |range, scroll, value|
|
123
|
+
if @player
|
124
|
+
seeker.value = value if value < 0.999
|
125
|
+
pos = seeker.value * @player.duration
|
126
|
+
@player.seek pos
|
127
|
+
else
|
128
|
+
seeker.value = 0
|
129
|
+
end
|
130
|
+
end
|
131
|
+
GLib::Timeout.add(100) do
|
132
|
+
if @player
|
133
|
+
pos = @player.position
|
134
|
+
seeker.value = pos / @player.duration
|
135
|
+
lbl_time.text = time_to_s(pos) + "/" + time_to_s(@player.duration)
|
136
|
+
lbl_song.set_markup @player.song.to_html
|
137
|
+
else
|
138
|
+
seeker.value = 0
|
139
|
+
lbl_time.text = time_to_s(0) + "/" + time_to_s(0)
|
140
|
+
lbl_song.set_markup "<b>Not playing</b>"
|
141
|
+
end
|
142
|
+
true
|
143
|
+
end
|
144
|
+
@btn_play.signal_connect('clicked') do |toggle|
|
145
|
+
if toggle.active?
|
146
|
+
@player.play
|
147
|
+
else
|
148
|
+
@player.pause
|
149
|
+
end if @player
|
150
|
+
end
|
151
|
+
btn_next.signal_connect('clicked') do |btn|
|
152
|
+
if @queue
|
153
|
+
playing = @player.playing?
|
154
|
+
@player.stop
|
155
|
+
@queue_pos += 1
|
156
|
+
if @queue_pos < @queue.size
|
157
|
+
prepare_song
|
158
|
+
play if playing
|
159
|
+
else
|
160
|
+
@queue_pos = 0
|
161
|
+
prepare_song
|
162
|
+
@btn_play.active = false
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
btn_prev.signal_connect('clicked') do |btn|
|
167
|
+
if @queue
|
168
|
+
playing = @player.playing?
|
169
|
+
@player.stop
|
170
|
+
@queue_pos -= 1
|
171
|
+
if @queue_pos >= 0
|
172
|
+
prepare_song
|
173
|
+
play if playing
|
174
|
+
else
|
175
|
+
@queue_pos = 0
|
176
|
+
prepare_song
|
177
|
+
@btn_play.active = false
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Horizontally split area
|
183
|
+
hpaned = HPaned.new
|
184
|
+
vpaned.add hpaned
|
185
|
+
|
186
|
+
# Content
|
187
|
+
frm_content = Frame.new
|
188
|
+
hpaned.add2 frm_content
|
189
|
+
|
190
|
+
# Sidebar
|
191
|
+
sw = ScrolledWindow.new(nil, nil)
|
192
|
+
hpaned.add1 sw
|
193
|
+
sw.set_policy(POLICY_AUTOMATIC, POLICY_NEVER)
|
194
|
+
sw.width_request = 150
|
195
|
+
sidebar = VBox.new(false, 0)
|
196
|
+
sw.add_with_viewport sidebar
|
197
|
+
sto_tabs = ListStore.new(String)
|
198
|
+
NUM_TABS.times do |tab|
|
199
|
+
iter = sto_tabs.append
|
200
|
+
iter[0] = TAB_NAMES[tab]
|
201
|
+
end
|
202
|
+
lst_tabs = TreeView.new sto_tabs
|
203
|
+
lst_tabs.headers_visible = false
|
204
|
+
renderer = CellRendererText.new
|
205
|
+
column = TreeViewColumn.new('Invisible header',
|
206
|
+
renderer,
|
207
|
+
'text' => 0)
|
208
|
+
lst_tabs.append_column column
|
209
|
+
sidebar.pack_start lst_tabs, true, true, 0
|
210
|
+
# Cover image widget
|
211
|
+
@img_cover = Image.new
|
212
|
+
@img_cover.height_request = 150
|
213
|
+
sidebar.pack_start @img_cover, false, false, 0
|
214
|
+
# Lyrics text view
|
215
|
+
@txt_lyrics = TextView.new
|
216
|
+
@txt_lyrics.editable = false
|
217
|
+
scr_lyrics = ScrolledWindow.new(nil, nil)
|
218
|
+
scr_lyrics.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
|
219
|
+
scr_lyrics.add @txt_lyrics
|
220
|
+
# Browse area
|
221
|
+
browse = Sh::Browse.new self
|
222
|
+
lst_tabs.selection.signal_connect('changed') do |selection|
|
223
|
+
frm_content.remove frm_content.child if frm_content.child
|
224
|
+
case selection.selected.path.indices[0]
|
225
|
+
when TAB_QUEUE
|
226
|
+
frm_content.child = Label.new 'Queue'
|
227
|
+
when TAB_BROWSE
|
228
|
+
frm_content.child = browse.widget
|
229
|
+
when TAB_LYRICS
|
230
|
+
frm_content.child = scr_lyrics
|
231
|
+
end
|
232
|
+
frm_content.show_all
|
233
|
+
end
|
234
|
+
# Select 'Browse' tab
|
235
|
+
i = 0
|
236
|
+
lst_tabs.model.each do |row|
|
237
|
+
lst_tabs.selection.select_path row[1] if i == TAB_BROWSE
|
238
|
+
i += 1
|
239
|
+
end
|
240
|
+
|
241
|
+
# Show everything
|
242
|
+
@window.show_all
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
def create_menu
|
247
|
+
menu_items = [
|
248
|
+
['/_File'],
|
249
|
+
['/File/sep1', '<Separator>', nil, nil, lambda {}],
|
250
|
+
['/File/Quit', '<StockItem>', '<control>Q', Stock::QUIT, lambda {Gtk.main_quit}],
|
251
|
+
['/_Edit'],
|
252
|
+
['/Edit/Preferences...', '<StockItem>', nil, Stock::PREFERENCES, lambda {show_preferences}],
|
253
|
+
['/_Help'],
|
254
|
+
['/Help/About', '<StockItem>', nil, Stock::ABOUT, lambda {show_about}]
|
255
|
+
]
|
256
|
+
accel_group = AccelGroup.new
|
257
|
+
item_factory = ItemFactory.new(ItemFactory::TYPE_MENU_BAR, '<main>', accel_group)
|
258
|
+
@window.add_accel_group(accel_group)
|
259
|
+
item_factory.create_items(menu_items)
|
260
|
+
item_factory.get_widget('<main>')
|
261
|
+
end
|
262
|
+
|
263
|
+
def show_preferences
|
264
|
+
#FIXME: disable close button on dialog
|
265
|
+
dlg_prefs = @glade.get_widget("dlg_preferences")
|
266
|
+
dlg_prefs.signal_connect('delete-event') do
|
267
|
+
dlg_prefs.hide
|
268
|
+
end
|
269
|
+
fbtn_library = @glade.get_widget("fbtn_library")
|
270
|
+
fbtn_library.current_folder = $prefs[:library_dir]
|
271
|
+
chk_lastfm = @glade.get_widget("chk_lastfm")
|
272
|
+
chk_lastfm.active = $prefs[:lastfm]
|
273
|
+
txt_lastfm_user = @glade.get_widget("txt_lastfm_user")
|
274
|
+
txt_lastfm_user.text = $prefs[:lastfm_user]
|
275
|
+
txt_lastfm_pass = @glade.get_widget("txt_lastfm_pass")
|
276
|
+
txt_lastfm_pass.text = $prefs[:lastfm_password]
|
277
|
+
btn_ok = @glade.get_widget("btn_ok")
|
278
|
+
ok_handle = btn_ok.signal_connect('clicked') do
|
279
|
+
$prefs[:library_dir] = fbtn_library.filename
|
280
|
+
$prefs[:lastfm] = chk_lastfm.active?
|
281
|
+
$prefs[:lastfm_user] = txt_lastfm_user.text
|
282
|
+
$prefs[:lastfm_password] = txt_lastfm_pass.text
|
283
|
+
Sh::Global.save_prefs
|
284
|
+
dlg_prefs.hide
|
285
|
+
end
|
286
|
+
btn_close = @glade.get_widget("btn_close")
|
287
|
+
close_handle = btn_close.signal_connect('clicked') do
|
288
|
+
dlg_prefs.hide
|
289
|
+
end
|
290
|
+
dlg_prefs.show
|
291
|
+
while dlg_prefs.visible?
|
292
|
+
Gtk.main_iteration
|
293
|
+
end
|
294
|
+
btn_ok.signal_handler_disconnect ok_handle
|
295
|
+
btn_close.signal_handler_disconnect close_handle
|
296
|
+
end
|
297
|
+
|
298
|
+
def show_about
|
299
|
+
version = '0.0.0'
|
300
|
+
Gem::SourceIndex.from_installed_gems.each do |n, spec|
|
301
|
+
version = spec.version.to_s if spec.name == 'shroom'
|
302
|
+
end
|
303
|
+
dlg_about = Gnome::About.new('Shroom', version,
|
304
|
+
"Copyright (C) 2009 Aiden Nibali",
|
305
|
+
"Shroom - A music player and organizer in Ruby",
|
306
|
+
["Aiden Nibali"], ["Aiden Nibali"], nil)
|
307
|
+
dlg_about.logo = Gdk::Pixbuf.new(Global.locate('icon_16x16.png'))
|
308
|
+
dlg_about.show
|
309
|
+
end
|
310
|
+
|
311
|
+
def prepare_song
|
312
|
+
@player = Sh::Player.new @queue[@queue_pos]
|
313
|
+
@player.on_finished do |player|
|
314
|
+
if player == @player
|
315
|
+
@player.stop
|
316
|
+
@queue_pos += 1
|
317
|
+
if @queue_pos < @queue.size
|
318
|
+
prepare_song
|
319
|
+
play
|
320
|
+
else
|
321
|
+
@queue_pos = 0
|
322
|
+
prepare_song
|
323
|
+
@btn_play.active = false
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
on_song_changed
|
328
|
+
end
|
329
|
+
|
330
|
+
# Format time (in seconds) as mins:secs
|
331
|
+
def time_to_s(time)
|
332
|
+
mins_float = time / 60
|
333
|
+
mins = mins_float.floor
|
334
|
+
secs = ((mins_float - mins) * 60).round
|
335
|
+
if secs == 60
|
336
|
+
mins += 1
|
337
|
+
secs = 0
|
338
|
+
end
|
339
|
+
"#{mins}:#{sprintf('%.2d', secs)}"
|
340
|
+
end
|
341
|
+
|
342
|
+
def on_song_changed
|
343
|
+
if @player
|
344
|
+
song = @player.song
|
345
|
+
@note.close if @note
|
346
|
+
@note = nil
|
347
|
+
if @rnotify
|
348
|
+
# Prepare notification
|
349
|
+
msg = "by <i>#{song.artist}</i> from <i>#{song.album}</i>"
|
350
|
+
@note = Notify::Notification.new(song.title || 'Unknown track', msg, nil, @status_icon)
|
351
|
+
end
|
352
|
+
# Cover art
|
353
|
+
if $prefs[:cover_art]
|
354
|
+
@pixbuf = nil
|
355
|
+
(@pixbuf = Gdk::Pixbuf.new(song.image)) rescue Exception
|
356
|
+
if @pixbuf
|
357
|
+
@note.pixbuf_icon = @pixbuf.scale(48, 48) if @rnotify
|
358
|
+
@img_cover.pixbuf = @pixbuf.scale(132, 132)
|
359
|
+
else
|
360
|
+
@img_cover.pixbuf = nil
|
361
|
+
Thread.new do
|
362
|
+
song.image = Sh::CoverArt.get_cover(song)
|
363
|
+
$db.save_song song
|
364
|
+
# Show cover unless requests have been shuffled
|
365
|
+
if song == @player.song
|
366
|
+
@pixbuf = nil
|
367
|
+
(@pixbuf = Gdk::Pixbuf.new(song.image)) rescue Exception
|
368
|
+
if @pixbuf
|
369
|
+
if @rnotify
|
370
|
+
@note.close
|
371
|
+
@note.pixbuf_icon = @pixbuf.scale(48, 48)
|
372
|
+
@note.show
|
373
|
+
end
|
374
|
+
@img_cover.pixbuf = @pixbuf.scale(132, 132)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
# Lyrics
|
381
|
+
if $prefs[:lyrics]
|
382
|
+
@txt_lyrics.buffer.text = "Loading..."
|
383
|
+
Thread.new do
|
384
|
+
if not song.lyrics
|
385
|
+
song.lyrics = Sh::Lyrics.get_lyrics(song)
|
386
|
+
$db.save_song song
|
387
|
+
end
|
388
|
+
# Show lyrics unless requests have been shuffled
|
389
|
+
@txt_lyrics.buffer.text = song.lyrics if @player.song == song
|
390
|
+
end
|
391
|
+
end
|
392
|
+
if @rnotify
|
393
|
+
#Show notification
|
394
|
+
@note.timeout = 5000
|
395
|
+
@note.show
|
396
|
+
end
|
397
|
+
# Tooltip
|
398
|
+
@status_icon.tooltip = @player.song.to_s
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
public
|
403
|
+
def queue=(queue)
|
404
|
+
@queue_pos = 0
|
405
|
+
@queue = queue
|
406
|
+
prepare_song
|
407
|
+
end
|
408
|
+
|
409
|
+
def play
|
410
|
+
stop
|
411
|
+
if @player
|
412
|
+
@btn_play.active = true
|
413
|
+
@player.play
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
def stop
|
418
|
+
@btn_play.active = false
|
419
|
+
@player.stop if @player
|
420
|
+
end
|
421
|
+
|
422
|
+
def show
|
423
|
+
Gtk.main
|
424
|
+
end
|
425
|
+
|
426
|
+
def quit
|
427
|
+
Gtk.main_quit
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
Binary file
|