shroom 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -4,9 +4,31 @@ Shroom
4
4
  Dependencies
5
5
  ------------
6
6
 
7
- ruby-gnome2
8
- ruby-gstreamer
9
- ruby-sqlite3
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
@@ -7,7 +7,7 @@ require 'rake/testtask'
7
7
 
8
8
  spec = Gem::Specification.new do |s|
9
9
  s.name = 'shroom'
10
- s.version = '0.0.9'
10
+ s.version = '0.0.10'
11
11
  s.has_rdoc = true
12
12
  s.extra_rdoc_files = ['README', 'LICENSE']
13
13
  s.summary = 'Shroom is a music player and organizer'
@@ -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
@@ -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
@@ -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
- pbtn_properties = ImageMenuItem.new Stock::PROPERTIES
90
- menu.append pbtn_properties
91
- pbtn_properties.signal_connect('activate') do |widget|
92
- show_song_properties_dialog(song)
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 show_song_properties_dialog song
134
- glade = Global::GLADE['dlg_song']
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
@@ -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 CoverArt.get_cover song
5
+ def self.get_cover song
8
6
  artist, album = song.artist.name, song.album.title
9
- path = "#{$cover_dir}/#{artist.to_md5}_#{album.to_md5}"
10
- if Ping.pingecho("audioscrobbler.com", 5) and not File.exists?(path)
11
- doc = lastfm("album.getInfo", {:artist => artist, :album => album})
12
- img_url = REXML::XPath.first(doc, '//image[@size="extralarge"]').text
13
- if img_url
14
- open(path, 'w') do |output|
15
- open(img_url) do |input|
16
- output << input.read
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