shroom 0.0.11 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -290,7 +290,7 @@
290
290
  <property name="visible">True</property>
291
291
  <property name="can_focus">True</property>
292
292
  <property name="invisible_char">&#x25CF;</property>
293
- <property name="adjustment">1 1 99 1 5 5</property>
293
+ <property name="adjustment">1 0 99 1 5 5</property>
294
294
  </widget>
295
295
  <packing>
296
296
  <property name="left_attach">2</property>
@@ -0,0 +1,2 @@
1
+ This file acts as a signpost to help find the Shroom resources directory, where
2
+ I live. Please do not delete me.
@@ -45,30 +45,29 @@ module Sh
45
45
  if queue
46
46
  queue << song
47
47
  else
48
- view.queue = [song]
49
- view.queue_pos = 0
48
+ view.set_queue([song])
50
49
  end
51
50
  end
52
51
  end
53
52
  action[:stock_image] = Stock::ADD
54
53
 
55
54
  action = Actions.new 'Reread tags', :song do |song|
56
- browse = Sh::Browse.instance
57
- browse.remove_song song if browse
58
- path = song.path
59
- song.delete
60
- song = Database.add_song path
61
- browse.insert_song song if browse
55
+ view = Sh::View.instance
56
+ proc = Proc.new do |s|
57
+ path = s.path
58
+ s.delete
59
+ Database.add_song path
60
+ end
61
+ view ? view.update_song(song, &proc) : proc.call(song)
62
62
  end
63
63
  action[:stock_image] = Stock::HARDDISK
64
64
 
65
65
  if try_require 'earworm' and try_require 'rbrainz'
66
66
  action = Actions.new 'Lookup metadata automatically', :song do |song|
67
67
  if song.lookup!
68
- browse = Sh::Browse.instance
69
- browse.remove_song song if browse
70
- song.save
71
- browse.insert_song song if browse
68
+ view = Sh::View.instance
69
+ proc = Proc.new {|s| s.save}
70
+ view ? view.update_song(song, &proc) : proc.call(song)
72
71
  end
73
72
  end
74
73
  action[:stock_image] = Stock::FIND
@@ -130,14 +129,13 @@ module Sh
130
129
  end
131
130
  btn_ok = glade['btn_ok']
132
131
  ok_handle = btn_ok.signal_connect('clicked') do
133
- browse = Browse.instance
134
132
  song.title = txt_title.text
135
133
  song.track_num = spn_track_num.value
136
134
  song.artist = cmb_artist.active_iter[1]
137
135
  song.album = cmb_album.active_iter[1]
138
- browse.remove_song song if browse
139
- song.save
140
- browse.insert_song song if browse
136
+ view = Sh::View.instance
137
+ proc = Proc.new {|s| s.save}
138
+ view ? view.update_song(song, &proc) : proc.call(song)
141
139
  dlg_song.hide
142
140
  end
143
141
  btn_cancel = glade['btn_cancel']
@@ -1,10 +1,6 @@
1
1
  module Sh
2
2
  class Browse
3
- @@instance = nil
4
-
5
3
  def initialize view
6
- Sh::Log.warning 'Instance of Browse already created!' if @@instance
7
- @@instance = self
8
4
  @tree = TreeView.new
9
5
  @tree.selection.mode = Gtk::SELECTION_MULTIPLE
10
6
 
@@ -13,7 +9,7 @@ module Sh
13
9
  col_artist.set_cell_data_func(ren_artist) do |tvc, cell, model, iter|
14
10
  cell.text = ''
15
11
  data = iter[0]
16
- if data.is_a? Sh::Song
12
+ if data.is_a? Song
17
13
  if data.title
18
14
  cell.text = "%.2d\t%s" % [data.track_num, data.title]
19
15
  else
@@ -26,9 +22,19 @@ module Sh
26
22
  @tree.append_column col_artist
27
23
 
28
24
  @model = TreeStore.new(Object, Array)
29
- @model.set_default_sort_func do |a, b|
30
- oa, ob = a[0], b[0]
31
- oa.to_s <=> ob.to_s
25
+ @model.set_default_sort_func do |oa, ob|
26
+ a1, b1 = oa[0].to_s, ob[0].to_s
27
+ a2, b2 = a1.gsub(/[^\w ]/, ""), b1.gsub(/[^\w ]/, "")
28
+ cmp = 0
29
+
30
+ unless a2.empty? or b2.empty?
31
+ cmp = a2.casecmp b2 if cmp == 0
32
+ cmp = a2 <=> b2 if cmp == 0
33
+ end
34
+
35
+ cmp = a1.casecmp b1 if cmp == 0
36
+ cmp = a1 <=> b1 if cmp == 0
37
+ cmp
32
38
  end
33
39
  @model.set_sort_column_id(TreeSortable::DEFAULT_SORT_COLUMN_ID,
34
40
  Gtk::SORT_ASCENDING)
@@ -113,12 +119,11 @@ module Sh
113
119
  iter.next!
114
120
  end
115
121
  queue.insert 0, *prev
116
- view.queue = queue
117
- view.queue_pos = prev.size
122
+ view.set_queue queue, prev.size
118
123
  view.play
119
124
  elsif not iter[1].empty?
120
125
  view.stop
121
- view.queue = iter[1].dup
126
+ view.set_queue iter[1].dup
122
127
  view.play
123
128
  end
124
129
  end
@@ -127,16 +132,21 @@ module Sh
127
132
  @scroll.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
128
133
  @scroll.add @tree
129
134
  end
130
-
131
- def self.instance
132
- return @@instance
133
- end
134
135
 
135
136
  def widget
136
137
  return @scroll
137
138
  end
138
139
 
140
+ def update_song song
141
+ # TODO: Improve this
142
+ remove_song song
143
+ new_song = yield song
144
+ song = new_song if new_song.is_a? Song
145
+ insert_song song
146
+ end
147
+
139
148
  def remove_song song
149
+ # TODO: Improve this
140
150
  song_node = nil
141
151
  if song.is_a? TreeIter
142
152
  song_node = song
@@ -153,9 +163,11 @@ module Sh
153
163
  artist_node = album_node.parent
154
164
  @model.remove song_node
155
165
  album_node[1].delete song
156
- @model.remove album_node unless album_node.has_child?
166
+ # Removal of these nodes are commented out because they can result
167
+ # in parentless song nodes
168
+ #@model.remove album_node unless album_node.has_child?
157
169
  artist_node[1].delete song
158
- @model.remove artist_node unless artist_node.has_child?
170
+ #@model.remove artist_node unless artist_node.has_child?
159
171
  end
160
172
  end
161
173
 
@@ -22,7 +22,7 @@ module Sh
22
22
  album = iter[COL_ALBUM]
23
23
  if album
24
24
  view.stop
25
- view.queue = album.songs.sort_by {|s| (s.to_s || '')}
25
+ view.set_queue(album.songs.sort_by {|s| s.to_s || ''})
26
26
  view.play
27
27
  end
28
28
  end
@@ -97,6 +97,15 @@ module Sh
97
97
  s.album = album
98
98
  s.title = tags[:title]
99
99
  s.track_num = tags[:track_num]
100
+
101
+ image = tags[:cover_art]
102
+ if image
103
+ image_path = File.join(Global::PATHS[:cover_dir], image.to_md5)
104
+ open(image_path, 'w') do |file|
105
+ file.write image
106
+ end
107
+ s.image_path = image_path
108
+ end
100
109
  end
101
110
 
102
111
  return song
@@ -158,12 +167,13 @@ module Sh
158
167
  many_to_one :album
159
168
 
160
169
  def to_s
161
- return "%s, %s, %.2d - %s" % [artist, album, track_num, title]
170
+ metadata = [artist, album, track_num, title || File.basename(path)]
171
+ return "%s, %s, %.2d - %s" % metadata
162
172
  end
163
173
 
164
174
  def to_html
165
175
  t = title
166
- t = "Unknown" if not t or t.strip == ""
176
+ t = File.basename(path) if not t or t.strip == ""
167
177
  t = CGI.escapeHTML t
168
178
  ar = artist.name
169
179
  ar = "Unknown" if not ar or ar.strip == ""
@@ -7,8 +7,8 @@ module Sh
7
7
  Log.debug "Earworming #{song.path}"
8
8
  ew = Earworm::Client.new(KEYS[:music_dns])
9
9
  info = ew.identify :file => song.path
10
- title = unescape_html info.title
11
- artist_name = unescape_html info.artist_name
10
+ title = info.title.unescape_html
11
+ artist_name = info.artist_name.unescape_html
12
12
  puids = info.puid_list || []
13
13
  return false unless title and artist_name
14
14
 
@@ -47,18 +47,5 @@ module Sh
47
47
 
48
48
  return true
49
49
  end
50
-
51
- def self.unescape_html str
52
- entities = {
53
- "&quot;" => "\"",
54
- "&apos;" => "'",
55
- "&amp;" => "&",
56
- "&lt;" => "<",
57
- "&gt;" => ">",
58
- "&nbsp;" => " "
59
- }
60
-
61
- return CGI.unescapeHTML(str.gsub(/&\w*;/) {|e| entities[e] || e})
62
- end
63
50
  end
64
51
  end
@@ -1,4 +1,5 @@
1
1
  require 'fileutils'
2
+ require 'pathname'
2
3
  require 'yaml'
3
4
  require 'libglade2'
4
5
  require 'ping'
@@ -12,7 +13,7 @@ module Sh
12
13
  }
13
14
 
14
15
  class Global
15
- SUPPORTED_EXTENSIONS = ['.mp3', '.m4a', '.ogg', '.flac', '.wma', '.wav']
16
+ SUPPORTED_EXTENSIONS = %w{.mp3 .m4a .ogg .oga .flac .wma .wav}
16
17
  GLADE = {}
17
18
  PATHS = {}
18
19
 
@@ -56,7 +57,8 @@ module Sh
56
57
  end
57
58
 
58
59
  def self.locate(file)
59
- find_in_load_path("shroom-res/#{file}") || "shroom-res/#{file}"
60
+ signpost = find_in_load_path(File.join("res", "shroom_resources_signpost"))
61
+ return Pathname.new(signpost).dirname.join(file).expand_path.to_s if signpost
60
62
  end
61
63
 
62
64
  def self.prefs
@@ -0,0 +1,64 @@
1
+ require 'hpricot'
2
+
3
+ module Sh
4
+ class LastFM
5
+ @album_getInfo = Rest::Get.new("http://ws.audioscrobbler.com/2.0",
6
+ :api_key => KEYS[:lastfm], :method => "album.getInfo")
7
+ @artist_getInfo = Rest::Get.new("http://ws.audioscrobbler.com/2.0",
8
+ :api_key => KEYS[:lastfm], :method => "artist.getInfo")
9
+
10
+ def self.get_album_info album
11
+ artist = album.artist.name
12
+ title = album.title
13
+
14
+ if artist and title
15
+ image_path = File.join(Global::PATHS[:cover_dir], "#{artist.to_md5}_#{title.to_md5}")
16
+ doc = Hpricot(@album_getInfo[:artist => artist, :album => title])
17
+
18
+ if File.exists? image_path and File.size?(image_path) and File.size?(image_path) > 0
19
+ album.image_path = image_path
20
+ else
21
+ begin
22
+ img_url = doc.at('/lfm/album/image[@size="extralarge"]').inner_html.strip
23
+ input = open(img_url).read
24
+ if input and not input.empty?
25
+ open(image_path, 'w') {|output| output << input}
26
+ album.image_path = image_path
27
+ end
28
+ rescue
29
+ album.image_path = nil
30
+ end
31
+ end
32
+
33
+ (album.info ||= (doc/'/lfm/album/wiki/content').inner_text.strip.unescape_html) rescue Exception
34
+ (album.mbid ||= (doc/'/lfm/album/mbid').inner_html.strip) rescue Exception
35
+ end
36
+ album.save
37
+ end
38
+
39
+ def self.get_artist_info artist
40
+ name = artist.name
41
+
42
+ if name
43
+ image_path = File.join(Global::PATHS[:cover_dir], name.to_md5)
44
+ doc = Hpricot(@artist_getInfo[:artist => name])
45
+
46
+ size = File.size?(image_path)
47
+ if File.exists? image_path and size and size > 0
48
+ artist.image_path = image_path
49
+ else
50
+ begin
51
+ img_url = doc.at('/lfm/artist/image[@size="extralarge"]').inner_html.strip
52
+ open(image_path, 'w') {|output| output << open(img_url).read}
53
+ artist.image_path = image_path
54
+ rescue
55
+ artist.image_path = nil
56
+ end
57
+ end
58
+ end
59
+
60
+ (artist.info ||= (doc/'/lfm/artist/bio/content').inner_text.strip.unescape_html) rescue Exception
61
+ (artist.mbid ||= (doc/'/lfm/artist/mbid').inner_html.strip) rescue Exception
62
+ end
63
+ end
64
+ end
@@ -6,22 +6,24 @@ module Sh
6
6
  end
7
7
 
8
8
  def write str
9
- @buffer ||= ''
9
+ @buffer ||= ""
10
10
  @buffer << str
11
- if @buffer.end_with? "\n"
12
- case @type
13
- when :stderr
14
- Log.error '> ' + @buffer
15
- when :stdout
16
- Log.info '> ' + @buffer
17
- end
18
- @buffer = nil
19
- end
11
+ flush if @buffer.end_with? "\n"
20
12
  end
21
13
 
22
14
  def puts str
23
- write str + "\n"
15
+ write "#{str}\n"
24
16
  end
17
+
18
+ def flush
19
+ case @type
20
+ when :stderr
21
+ Log.error "> #{@buffer}"
22
+ when :stdout
23
+ Log.info "> #{@buffer}"
24
+ end
25
+ @buffer = ""
26
+ end
25
27
  end
26
28
 
27
29
  LEVELS = [:debug, :info, :warning, :error]
@@ -1,4 +1,4 @@
1
- #!/usr/bin/ruby
1
+ #!/usr/bin/env ruby
2
2
 
3
3
  # Problems occur when 'socket' is not required first
4
4
  require 'socket'
@@ -20,7 +20,7 @@ module Sh
20
20
  class Main
21
21
  def initialize
22
22
  Log.info 'Shroom started'
23
-
23
+
24
24
  # See sh_player.rb for why this Wx stuff is necessary
25
25
  Wx::App.run do
26
26
  View.new.show
@@ -14,8 +14,14 @@ module Sh
14
14
  line.strip!
15
15
  unless line[0] == ?# or line.empty?
16
16
  begin
17
- # TODO: make cross-platform
18
- ref = File.expand_path(line.gsub('\\', '/'), File.dirname(path))
17
+ ref = line
18
+ begin
19
+ ref = GLib.filename_from_uri(ref)
20
+ rescue
21
+ # TODO: make cross-platform
22
+ ref = File.expand_path(ref.gsub('\\', '/'), File.dirname(path))
23
+ end
24
+
19
25
  if File.exists? ref
20
26
  if Global::SUPPORTED_EXTENSIONS.include? File.extname(ref)
21
27
  list << (Song.first(:path => ref) || Database.add_song(ref))
@@ -30,7 +30,7 @@ module Sh
30
30
  iter = lst_tabs.model.get_iter(path)
31
31
  song = iter[1]
32
32
  view.stop
33
- view.queue_pos = path.indices.first
33
+ view.set_queue_pos path.indices.first
34
34
  view.play
35
35
  end
36
36
  end
@@ -1,9 +1,10 @@
1
1
  require 'sh_util'
2
2
  require 'sh_log'
3
+ require 'base64'
3
4
 
4
5
  module Sh
5
6
  class TagReader
6
- def self.read path, &block
7
+ def self.read path
7
8
  # Ensure that the path is absolute
8
9
  @path = File.expand_path path
9
10
 
@@ -15,7 +16,7 @@ module Sh
15
16
  else
16
17
  # Attempt to use the `file` command
17
18
  begin
18
- type = `file --mime-type -b #{@path}`
19
+ type = `file --mime-type -b "#{@path.gsub('"', '\\"')}"`.strip
19
20
  @mime = type if type[0..5] == 'audio/'
20
21
  rescue Exception
21
22
  @mime = nil
@@ -30,7 +31,7 @@ module Sh
30
31
  '.ogg' => 'audio/ogg',
31
32
  '.wav' => 'audio/x-wav',
32
33
  '.wma' => 'audio/x-ms-wma'
33
- }[File.extname @path]
34
+ }[File.extname(@path)]
34
35
  end
35
36
  end
36
37
 
@@ -57,12 +58,12 @@ module Sh
57
58
  end
58
59
 
59
60
  # Report on our metadata findings
60
- yield metadata if block
61
+ yield metadata if block_given?
61
62
  return metadata
62
63
  end
63
64
 
64
65
  # Read metadata from MP3 file
65
- def self.read_mp3 path, &block
66
+ def self.read_mp3 path
66
67
  metadata = {}
67
68
  if try_require 'mp3info'
68
69
  Mp3Info.open path do |mp3|
@@ -73,16 +74,25 @@ module Sh
73
74
  metadata[:year] = t.year.to_i
74
75
  metadata[:track_num] = t.tracknum.to_i
75
76
  metadata[:duration] = mp3.length
77
+
78
+ begin
79
+ if mp3.tag2 and mp3.tag2['APIC']
80
+ a = mp3.tag2['APIC'].last
81
+ a = a[1..-1].sub(/[^\0]*\0/, "")[1..-1].sub(/[^\0]*\0/, "")
82
+ metadata[:cover_art] = a
83
+ end
84
+ rescue
85
+ end
76
86
  end
77
87
  else
78
- Log.info 'Please install the "mp3info" gem in order to read MP3 info'
88
+ Log.info 'Please install the "ruby-mp3info" gem in order to read MP3 info'
79
89
  end
80
- yield metadata if block
90
+ yield metadata if block_given?
81
91
  return metadata
82
92
  end
83
93
 
84
94
  # Read metadata from M4A file
85
- def self.read_m4a path, &block
95
+ def self.read_m4a path
86
96
  metadata = {}
87
97
  if try_require 'mp4info'
88
98
  mp4 = MP4Info.open path
@@ -92,35 +102,55 @@ module Sh
92
102
  metadata[:year] = mp4.DAY.to_i
93
103
  metadata[:track_num] = mp4.TRKN.first.to_i
94
104
  metadata[:duration] = mp4.SECS
105
+ metadata[:cover_art] = mp4.COVR if mp4.COVR
95
106
  else
96
- Log.info 'Please install the "mp4info" gem in order to read M4A info'
107
+ Log.info 'Please install the "MP4Info" gem in order to read M4A info'
97
108
  end
98
109
  yield metadata if block
99
110
  return metadata
100
111
  end
101
112
 
102
- # Read metadata from OGG Vorbis file
103
- def self.read_ogg path, &block
113
+ # Read metadata from Ogg Vorbis file
114
+ def self.read_ogg path
104
115
  metadata = {}
105
- if try_require 'ogginfo'
106
- OggInfo.open path do |ogg|
107
- t = ogg.tag
108
- metadata[:title] = t.title.to_u if t.title
109
- metadata[:artist] = t.artist.to_u if t.artist
110
- metadata[:album] = t.album.to_u if t.album
111
- metadata[:year] = t.date.to_i
112
- metadata[:track_num] = t.tracknumber.to_i
113
- metadata[:duration] = ogg.length
116
+ if try_require 'vorbis'
117
+ Vorbis::Info.open path do |info|
118
+ dur = info.duration
119
+ metadata[:duration] = dur if dur and dur > 0
120
+
121
+ info.comments.each do |k, vs|
122
+ if vs and vs.any?
123
+ v = vs.first
124
+
125
+ case k
126
+ when 'TITLE'
127
+ metadata[:title] = v.to_u
128
+ when 'ARTIST'
129
+ metadata[:artist] = v.to_u
130
+ when 'ALBUM'
131
+ metadata[:album] = v.to_u
132
+ when 'YEAR'
133
+ metadata[:year] = v.to_i
134
+ when 'TRACKNUMBER'
135
+ metadata[:track_num] = v.to_i
136
+ when 'COVERART'
137
+ metadata[:cover_art] = Base64.decode64(v)
138
+ when 'METADATA_BLOCK_PICTURE'
139
+ # TODO: Support this:
140
+ # http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
141
+ end
142
+ end rescue Exception
143
+ end
114
144
  end rescue Exception
115
145
  else
116
- Log.info 'Please install the "ogginfo" gem in order to read OGG info'
146
+ Log.info 'Please install the "ruby-ogg" gem in order to read Vorbis info'
117
147
  end
118
- yield metadata if block
148
+ yield metadata if block_given?
119
149
  return metadata
120
150
  end
121
151
 
122
- # Read metadata from FLAC file
123
- def self.read_flac path, &block
152
+ # Read metadata from FLAC file
153
+ def self.read_flac path
124
154
  metadata = {}
125
155
  if try_require 'flacinfo'
126
156
  flac = FlacInfo.new(path)
@@ -142,15 +172,17 @@ module Sh
142
172
  metadata[:track_num] = value.to_i
143
173
  end
144
174
  end
175
+
176
+ #TODO: Support METADATA_BLOCK_PICTURE
145
177
  else
146
- Log.info 'Please install the "flacinfo" gem in order to read FLAC info'
178
+ Log.info 'Please install the "flacinfo-rb" gem in order to read FLAC info'
147
179
  end
148
- yield metadata if block
180
+ yield metadata if block_given?
149
181
  return metadata
150
182
  end
151
183
 
152
- # Read metadata from WMA file
153
- def self.read_wma path, &block
184
+ # Read metadata from WMA file
185
+ def self.read_wma path
154
186
  metadata = {}
155
187
  if try_require 'wmainfo'
156
188
  wma = WmaInfo.new(path)
@@ -172,14 +204,14 @@ module Sh
172
204
  end
173
205
  metadata[:duration] = wma.info["playtime_seconds"]
174
206
  else
175
- Log.info 'Please install the "wmainfo" gem in order to read WMA info'
207
+ Log.info 'Please install the "wmainfo-rb" gem in order to read WMA info'
176
208
  end
177
- yield metadata if block
209
+ yield metadata if block_given?
178
210
  return metadata
179
211
  end
180
212
 
181
- # Read metadata from Wave file
182
- def self.read_wave path, &block
213
+ # Read metadata from Wave file
214
+ def self.read_wave path
183
215
  metadata = {}
184
216
  if try_require 'waveinfo'
185
217
  wave = WaveInfo.new path
@@ -187,7 +219,7 @@ module Sh
187
219
  else
188
220
  Log.info 'Please install the "waveinfo" gem in order to read WAV info'
189
221
  end
190
- yield metadata if block
222
+ yield metadata if block_given?
191
223
  return metadata
192
224
  end
193
225
  end