shroom 0.0.9 → 0.0.10
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/README +25 -3
- data/Rakefile +1 -1
- data/lib/sh_actions.rb +146 -0
- data/lib/sh_album.rb +1 -7
- data/lib/sh_browse.rb +41 -95
- data/lib/sh_cover_art.rb +16 -23
- data/lib/sh_cover_browse.rb +54 -0
- data/lib/sh_database.rb +4 -4
- data/lib/sh_global.rb +39 -22
- data/lib/sh_log.rb +78 -0
- data/lib/sh_lyrics.rb +2 -55
- data/lib/sh_main.rb +10 -6
- data/lib/sh_player.rb +2 -4
- data/lib/sh_playlist.rb +91 -6
- data/lib/sh_plugin.rb +31 -7
- data/lib/sh_song.rb +13 -10
- data/lib/sh_tagreader.rb +59 -32
- data/lib/sh_util.rb +33 -68
- data/lib/sh_view.rb +106 -76
- data/lib/shroom-res/plugins/lastfm_scrobbler.rb +65 -21
- data/lib/shroom-res/shroom.glade +1 -95
- metadata +5 -2
data/README
CHANGED
@@ -4,9 +4,31 @@ Shroom
|
|
4
4
|
Dependencies
|
5
5
|
------------
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
ruby
|
7
|
+
'rake'
|
8
|
+
'libwxgtk2.8-0'
|
9
|
+
'libgnome2-ruby'
|
10
|
+
'libglade2-ruby'
|
11
|
+
'libgtkhtml2-ruby'
|
12
|
+
'libsqlite3-ruby'
|
13
|
+
'ruby-dev'
|
14
|
+
'libopenssl-ruby'
|
15
|
+
'libmp3lame-dev'
|
16
|
+
'libvorbis-dev'
|
17
|
+
'build-essential'
|
18
|
+
|
19
|
+
Gem dependencies
|
20
|
+
----------------
|
21
|
+
|
22
|
+
('ruby-mp3info', '>= 0.6.13')
|
23
|
+
('MP4Info', '>= 0.3.3')
|
24
|
+
('ruby-ogginfo', '>= 0.3.2')
|
25
|
+
('flacinfo-rb', '>= 0.4')
|
26
|
+
('earworm', '>= 0.0.2')
|
27
|
+
('sequel', '>= 3.2.0')
|
28
|
+
('wxruby', '>= 2.0.0')
|
29
|
+
('rbrainz', '>= 0.5.0')
|
30
|
+
('scrobbler', '>= 0.2.3')
|
31
|
+
('shared-mime-info', '>= 0.1')
|
10
32
|
|
11
33
|
Repository
|
12
34
|
----------
|
data/Rakefile
CHANGED
data/lib/sh_actions.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
module Sh
|
2
|
+
class Actions
|
3
|
+
@@actions = []
|
4
|
+
attr_reader :name, :tags, :user_data
|
5
|
+
|
6
|
+
def initialize name, *tags, &block
|
7
|
+
@@actions << self
|
8
|
+
@name = name
|
9
|
+
@tags = tags
|
10
|
+
@block = block
|
11
|
+
@user_data = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def []=(key, value)
|
15
|
+
@user_data[key] = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
return @user_data[key]
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute *args
|
23
|
+
@block.call *args
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.get *tags
|
27
|
+
matches = []
|
28
|
+
@@actions.each do |action|
|
29
|
+
subset = true
|
30
|
+
tags.each do |tag|
|
31
|
+
subset = false unless action.tags.include? tag
|
32
|
+
end
|
33
|
+
matches << action if subset
|
34
|
+
end
|
35
|
+
return matches
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ShroomActions
|
40
|
+
def self.init
|
41
|
+
action = Actions.new 'Add to queue', :song do |song|
|
42
|
+
view = Sh::View.instance
|
43
|
+
if view
|
44
|
+
queue = view.queue
|
45
|
+
if queue
|
46
|
+
queue << song
|
47
|
+
else
|
48
|
+
view.queue = [song]
|
49
|
+
view.queue_pos = 0
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
action[:stock_image] = Stock::ADD
|
54
|
+
|
55
|
+
action = Actions.new 'Reread tags', :song do |song|
|
56
|
+
browse = Sh::Browse.instance
|
57
|
+
browse.remove_song song if browse
|
58
|
+
song.read_tags!(true)
|
59
|
+
$db.save_song song
|
60
|
+
browse.insert_song song if browse
|
61
|
+
end
|
62
|
+
action[:stock_image] = Stock::HARDDISK
|
63
|
+
|
64
|
+
action = Actions.new 'Lookup metadata automatically', :song do |song|
|
65
|
+
if song.lookup!
|
66
|
+
browse = Sh::Browse.instance
|
67
|
+
browse.remove_song song if browse
|
68
|
+
$db.save_song song
|
69
|
+
browse.insert_song song if browse
|
70
|
+
end
|
71
|
+
end
|
72
|
+
action[:stock_image] = Stock::FIND
|
73
|
+
|
74
|
+
action = Actions.new 'Add to playlist', :song, :playlist do |song, playlist|
|
75
|
+
playlist << song
|
76
|
+
playlist.save!
|
77
|
+
end
|
78
|
+
action[:stock_image] = Stock::ADD
|
79
|
+
|
80
|
+
action = Actions.new 'Properties', :song do |song|
|
81
|
+
show_song_properties_dialog(song)
|
82
|
+
end
|
83
|
+
action[:stock_image] = Stock::PROPERTIES
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.show_song_properties_dialog song
|
87
|
+
glade = Global::GLADE['dlg_song']
|
88
|
+
dlg_song = glade['dlg_song']
|
89
|
+
txt_title = glade['txt_title']
|
90
|
+
txt_title.text = song.title
|
91
|
+
|
92
|
+
# Artists combo box
|
93
|
+
sto_artists = ListStore.new(String, Artist)
|
94
|
+
cmb_artist = glade['cmb_artist']
|
95
|
+
sel = 0
|
96
|
+
$db.artists.sort_by{|a| (a.name || '')}.each_with_index do |artist, i|
|
97
|
+
iter = sto_artists.append
|
98
|
+
iter[0] = artist.name
|
99
|
+
iter[1] = artist
|
100
|
+
sel = i if song.artist && artist.db_id == song.artist.db_id
|
101
|
+
end
|
102
|
+
cmb_artist.model = sto_artists
|
103
|
+
cmb_artist.active = sel
|
104
|
+
|
105
|
+
# Albums combo box
|
106
|
+
sto_albums = ListStore.new(String, Album)
|
107
|
+
cmb_album = glade['cmb_album']
|
108
|
+
sel = 0
|
109
|
+
$db.albums.sort_by{|a| (a.title || '')}.each_with_index do |album, i|
|
110
|
+
iter = sto_albums.append
|
111
|
+
iter[0] = album.title
|
112
|
+
iter[1] = album
|
113
|
+
sel = i if song.album && album.db_id == song.album.db_id
|
114
|
+
end
|
115
|
+
cmb_album.model = sto_albums
|
116
|
+
cmb_album.active = sel
|
117
|
+
|
118
|
+
glade['chk_image'].sensitive = false
|
119
|
+
glade['fbtn_image'].sensitive = false
|
120
|
+
glade['btn_add_artist'].sensitive = false
|
121
|
+
glade['btn_add_album'].sensitive = false
|
122
|
+
|
123
|
+
dlg_song.signal_connect('delete-event') do
|
124
|
+
dlg_song.hide
|
125
|
+
end
|
126
|
+
btn_ok = glade['btn_ok']
|
127
|
+
ok_handle = btn_ok.signal_connect('clicked') do
|
128
|
+
browse = Sh::Browse.instance
|
129
|
+
song.title = txt_title.text
|
130
|
+
song.artist = cmb_artist.active_iter[1]
|
131
|
+
song.album = cmb_album.active_iter[1]
|
132
|
+
browse.remove_song song if browse
|
133
|
+
$db.save_song song
|
134
|
+
browse.insert_song song if browse
|
135
|
+
dlg_song.hide
|
136
|
+
end
|
137
|
+
btn_cancel = glade['btn_cancel']
|
138
|
+
close_handle = btn_cancel.signal_connect('clicked') do
|
139
|
+
dlg_song.hide
|
140
|
+
end
|
141
|
+
dlg_song.run
|
142
|
+
btn_ok.signal_handler_disconnect ok_handle
|
143
|
+
btn_cancel.signal_handler_disconnect close_handle
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/sh_album.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module Sh
|
2
2
|
class Album
|
3
|
-
attr_writer :artist
|
4
3
|
attr_accessor :title, :mbid, :image_path, :info, :date
|
5
4
|
attr_accessor :db_id
|
6
5
|
|
@@ -8,13 +7,8 @@ module Sh
|
|
8
7
|
@songs = []
|
9
8
|
end
|
10
9
|
|
11
|
-
def artist
|
12
|
-
#@artist = $db.artists(:id => @artist.db_id).first if @artist and @artist.db_id
|
13
|
-
return @artist
|
14
|
-
end
|
15
|
-
|
16
10
|
def to_s
|
17
11
|
return self.title
|
18
12
|
end
|
19
13
|
end
|
20
|
-
end
|
14
|
+
end
|
data/lib/sh_browse.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
module Sh
|
2
2
|
class Browse
|
3
|
+
@@instance = nil
|
4
|
+
|
3
5
|
def initialize view
|
6
|
+
Sh::Log.warning 'Instance of Browse already created!' if @@instance
|
7
|
+
@@instance = self
|
4
8
|
@tree = TreeView.new
|
5
9
|
@tree.selection.mode = Gtk::SELECTION_MULTIPLE
|
6
10
|
|
@@ -22,6 +26,13 @@ module Sh
|
|
22
26
|
@tree.append_column col_artist
|
23
27
|
|
24
28
|
@model = TreeStore.new(Object, Array)
|
29
|
+
# TODO: use this
|
30
|
+
# @model.set_default_sort_func do |a, b|
|
31
|
+
# oa, ob = a[0], b[0]
|
32
|
+
# oa.to_s <=> ob.to_s
|
33
|
+
# end
|
34
|
+
# @model.set_sort_column_id(TreeSortable::DEFAULT_SORT_COLUMN_ID,
|
35
|
+
# Gtk::SORT_ASCENDING)
|
25
36
|
@tree.model = @model
|
26
37
|
|
27
38
|
GLib::Timeout.add(1000) do
|
@@ -54,42 +65,31 @@ module Sh
|
|
54
65
|
if data.is_a? Sh::Song
|
55
66
|
song = data
|
56
67
|
menu = Menu.new
|
57
|
-
|
58
|
-
pbtn_add_to_queue = ImageMenuItem.new Stock::ADD
|
59
|
-
menu.append pbtn_add_to_queue
|
60
|
-
pbtn_add_to_queue.signal_connect('activate') do |widget|
|
61
|
-
queue = view.queue
|
62
|
-
if queue
|
63
|
-
queue << song
|
64
|
-
else
|
65
|
-
view.queue = [song]
|
66
|
-
view.queue_pos = 0
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
pbtn_lookup = MenuItem.new 'Lookup metadata automatically'
|
71
|
-
menu.append pbtn_lookup
|
72
|
-
pbtn_lookup.signal_connect('activate') do |widget|
|
73
|
-
if song.lookup!
|
74
|
-
remove_song @model.get_iter(path)
|
75
|
-
$db.save_song song
|
76
|
-
insert_song song
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
pbtn_reread_tags = MenuItem.new 'Reread tags'
|
81
|
-
menu.append pbtn_reread_tags
|
82
|
-
pbtn_reread_tags.signal_connect('activate') do |widget|
|
83
|
-
remove_song @model.get_iter(path)
|
84
|
-
song.read_tags!(true)
|
85
|
-
$db.save_song song
|
86
|
-
insert_song song
|
87
|
-
end
|
88
68
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
69
|
+
Actions.get(:song).each do |action|
|
70
|
+
pbtn = nil
|
71
|
+
if action[:stock_image]
|
72
|
+
pbtn = ImageMenuItem.new(action.name)
|
73
|
+
pbtn.image = Image.new(action[:stock_image], IconSize::MENU)
|
74
|
+
else
|
75
|
+
pbtn = MenuItem.new(action.name)
|
76
|
+
end
|
77
|
+
menu.append pbtn
|
78
|
+
if action.tags.include? :playlist
|
79
|
+
pmnu = Gtk::Menu.new
|
80
|
+
Sh::Playlists.load_playlists.each do |playlist|
|
81
|
+
pbtn_playlist = Gtk::MenuItem.new playlist.name
|
82
|
+
pbtn_playlist.signal_connect('activate') do |widget|
|
83
|
+
action.execute song, playlist
|
84
|
+
end
|
85
|
+
pmnu.append pbtn_playlist
|
86
|
+
end
|
87
|
+
pbtn.submenu = pmnu
|
88
|
+
else
|
89
|
+
pbtn.signal_connect('activate') do |widget|
|
90
|
+
action.execute song
|
91
|
+
end
|
92
|
+
end
|
93
93
|
end
|
94
94
|
|
95
95
|
menu.show_all
|
@@ -129,64 +129,9 @@ module Sh
|
|
129
129
|
@scroll.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
|
130
130
|
@scroll.add @tree
|
131
131
|
end
|
132
|
-
|
133
|
-
def
|
134
|
-
|
135
|
-
dlg_song = glade['dlg_song']
|
136
|
-
txt_title = glade['txt_title']
|
137
|
-
txt_title.text = song.title
|
138
|
-
|
139
|
-
# Artists combo box
|
140
|
-
sto_artists = ListStore.new(String, Artist)
|
141
|
-
cmb_artist = glade['cmb_artist']
|
142
|
-
sel = 0
|
143
|
-
$db.artists.sort_by{|a| (a.name || '')}.each_with_index do |artist, i|
|
144
|
-
iter = sto_artists.append
|
145
|
-
iter[0] = artist.name
|
146
|
-
iter[1] = artist
|
147
|
-
sel = i if song.artist && artist.db_id == song.artist.db_id
|
148
|
-
end
|
149
|
-
cmb_artist.model = sto_artists
|
150
|
-
cmb_artist.active = sel
|
151
|
-
|
152
|
-
# Albums combo box
|
153
|
-
sto_albums = ListStore.new(String, Album)
|
154
|
-
cmb_album = glade['cmb_album']
|
155
|
-
sel = 0
|
156
|
-
$db.albums.sort_by{|a| (a.title || '')}.each_with_index do |album, i|
|
157
|
-
iter = sto_albums.append
|
158
|
-
iter[0] = album.title
|
159
|
-
iter[1] = album
|
160
|
-
sel = i if song.album && album.db_id == song.album.db_id
|
161
|
-
end
|
162
|
-
cmb_album.model = sto_albums
|
163
|
-
cmb_album.active = sel
|
164
|
-
|
165
|
-
glade['chk_image'].sensitive = false
|
166
|
-
glade['fbtn_image'].sensitive = false
|
167
|
-
glade['btn_add_artist'].sensitive = false
|
168
|
-
glade['btn_add_album'].sensitive = false
|
169
|
-
|
170
|
-
dlg_song.signal_connect('delete-event') do
|
171
|
-
dlg_song.hide
|
172
|
-
end
|
173
|
-
btn_ok = glade['btn_ok']
|
174
|
-
ok_handle = btn_ok.signal_connect('clicked') do
|
175
|
-
song.title = txt_title.text
|
176
|
-
song.artist = cmb_artist.active_iter[1]
|
177
|
-
song.album = cmb_album.active_iter[1]
|
178
|
-
remove_song song
|
179
|
-
$db.save_song song
|
180
|
-
insert_song song
|
181
|
-
dlg_song.hide
|
182
|
-
end
|
183
|
-
btn_cancel = glade['btn_cancel']
|
184
|
-
close_handle = btn_cancel.signal_connect('clicked') do
|
185
|
-
dlg_song.hide
|
186
|
-
end
|
187
|
-
dlg_song.run
|
188
|
-
btn_ok.signal_handler_disconnect ok_handle
|
189
|
-
btn_cancel.signal_handler_disconnect close_handle
|
132
|
+
|
133
|
+
def self.instance
|
134
|
+
return @@instance
|
190
135
|
end
|
191
136
|
|
192
137
|
def remove_song song
|
@@ -250,6 +195,7 @@ module Sh
|
|
250
195
|
end
|
251
196
|
|
252
197
|
def fill_model
|
198
|
+
@model.clear
|
253
199
|
artist_node = album_node = track_node = nil
|
254
200
|
$db.songs.sort_by {|a| (a.to_s || '')}.each do |song|
|
255
201
|
artist = song.artist
|
@@ -279,7 +225,7 @@ module Sh
|
|
279
225
|
end
|
280
226
|
|
281
227
|
def widget
|
282
|
-
@scroll
|
228
|
+
return @scroll
|
283
229
|
end
|
284
230
|
end
|
285
|
-
end
|
231
|
+
end
|
data/lib/sh_cover_art.rb
CHANGED
@@ -1,35 +1,28 @@
|
|
1
|
-
require 'open-uri'
|
2
1
|
require 'rexml/document'
|
3
|
-
require 'cgi'
|
4
2
|
|
5
3
|
module Sh
|
6
4
|
class CoverArt
|
7
|
-
def
|
5
|
+
def self.get_cover song
|
8
6
|
artist, album = song.artist.name, song.album.title
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
if
|
14
|
-
|
15
|
-
|
16
|
-
|
7
|
+
if artist and album
|
8
|
+
@lastfm_rest ||= Rest::Get.new("http://ws.audioscrobbler.com/2.0",
|
9
|
+
:api_key => Sh::KEYS[:lastfm], :method => "album.getInfo")
|
10
|
+
path = "#{$cover_dir}/#{artist.to_md5}_#{album.to_md5}"
|
11
|
+
if File.exists? path and File.size?(path) > 0
|
12
|
+
return path
|
13
|
+
elsif Ping.pingecho("audioscrobbler.com", 5)
|
14
|
+
doc = REXML::Document.new(@lastfm_rest[:artist => artist, :album => album])
|
15
|
+
img_url = REXML::XPath.first(doc, '//image[@size="extralarge"]').text
|
16
|
+
if img_url
|
17
|
+
open(path, 'w') do |output|
|
18
|
+
open(img_url) do |input|
|
19
|
+
output << input.read
|
20
|
+
end
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|
24
|
+
return path if File.exists? path
|
20
25
|
end
|
21
|
-
if File.exists? path
|
22
|
-
return path
|
23
|
-
else
|
24
|
-
return nil
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
def CoverArt.lastfm(method, arg_map = {}.freeze)
|
30
|
-
args = arg_map.collect { |k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v)}"}.join('&')
|
31
|
-
url = "http://ws.audioscrobbler.com/2.0/?method=#{method}&api_key=#{Sh::KEYS[:lastfm]}&#{args}"
|
32
|
-
return REXML::Document.new(open(url).read)
|
33
26
|
end
|
34
27
|
end
|
35
28
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Sh
|
2
|
+
class CoverBrowse
|
3
|
+
COL_PIXBUF, COL_ALBUM = (0..1).to_a
|
4
|
+
|
5
|
+
def initialize view
|
6
|
+
@model = ListStore.new(Gdk::Pixbuf, Object)
|
7
|
+
@model.set_default_sort_func do |a, b|
|
8
|
+
a[COL_ALBUM].title <=> b[COL_ALBUM].title
|
9
|
+
end
|
10
|
+
@model.set_sort_column_id(TreeSortable::DEFAULT_SORT_COLUMN_ID,
|
11
|
+
Gtk::SORT_ASCENDING)
|
12
|
+
|
13
|
+
@iconview = IconView.new @model
|
14
|
+
@iconview.pixbuf_column = COL_PIXBUF
|
15
|
+
@iconview.signal_connect 'item_activated' do |iconview, path|
|
16
|
+
iter = @model.get_iter path
|
17
|
+
album = iter[COL_ALBUM]
|
18
|
+
if album
|
19
|
+
view.stop
|
20
|
+
songs = $db.songs(:album_id => album.db_id)
|
21
|
+
view.queue = songs.sort_by {|s| (s.to_s || '')}
|
22
|
+
view.play
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
GLib::Timeout.add(1000) do
|
27
|
+
fill_model
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
@scroll = ScrolledWindow.new(nil, nil)
|
32
|
+
@scroll.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
|
33
|
+
@scroll.add @iconview
|
34
|
+
end
|
35
|
+
|
36
|
+
def fill_model
|
37
|
+
@model.clear
|
38
|
+
$db.albums.each do |album|
|
39
|
+
begin
|
40
|
+
pixbuf = Gdk::Pixbuf.new(album.image_path).scale(128, 128)
|
41
|
+
iter = @model.append
|
42
|
+
iter[COL_ALBUM] = album
|
43
|
+
iter[COL_PIXBUF] = pixbuf
|
44
|
+
rescue Exception
|
45
|
+
# Insufficient data about album
|
46
|
+
end if album
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def widget
|
51
|
+
return @scroll
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|