shroom 0.0.1 → 0.0.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.
- data/Rakefile +12 -2
- data/bin/shroom +1 -0
- data/lib/sh_browse.rb +18 -5
- data/lib/sh_main.rb +3 -1
- data/lib/sh_player.rb +162 -111
- data/lib/sh_song.rb +18 -78
- data/lib/sh_tagreader.rb +161 -0
- data/lib/sh_util.rb +73 -9
- data/lib/sh_view.rb +45 -48
- data/lib/shroom-res/icon.svg +206 -0
- data/lib/shroom-res/icon_128x128.png +0 -0
- metadata +63 -7
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.
|
10
|
+
s.version = '0.0.3'
|
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'
|
@@ -17,14 +17,24 @@ spec = Gem::Specification.new do |s|
|
|
17
17
|
s.rubyforge_project = 'shroom'
|
18
18
|
s.homepage = 'http://shroom.rubyforge.org'
|
19
19
|
s.add_dependency('ruby-mp3info', '>= 0.6.13')
|
20
|
+
s.add_dependency('MP4Info', '>= 0.3.3')
|
20
21
|
s.add_dependency('ruby-ogginfo', '>= 0.3.2')
|
21
22
|
s.add_dependency('flacinfo-rb', '>= 0.4')
|
22
23
|
s.add_dependency('earworm', '>= 0.0.2')
|
23
24
|
s.add_dependency('sequel', '>= 3.2.0')
|
24
|
-
s.
|
25
|
+
s.add_dependency('wxruby', '>= 2.0.0')
|
26
|
+
s.add_dependency('rbrainz', '>= 0.5.0')
|
27
|
+
s.add_dependency('scrobbler', '>= 0.2.3')
|
28
|
+
s.add_dependency('shared-mime-info', '>= 0.1')
|
25
29
|
s.requirements << 'libgnome2-ruby'
|
26
30
|
s.requirements << 'libsqlite3-ruby'
|
27
31
|
s.requirements << 'libglade2-ruby'
|
32
|
+
# Requirements for icanhasaudio
|
33
|
+
s.requirements << 'ruby-dev'
|
34
|
+
s.requirements << 'libopenssl-ruby'
|
35
|
+
s.requirements << 'libmp3lame-dev'
|
36
|
+
s.requirements << 'libvorbis-dev'
|
37
|
+
s.requirements << 'make'
|
28
38
|
s.executables = ['shroom']
|
29
39
|
s.files = %w(LICENSE README Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
|
30
40
|
s.require_path = "lib"
|
data/bin/shroom
CHANGED
data/lib/sh_browse.rb
CHANGED
@@ -20,7 +20,7 @@ module Sh
|
|
20
20
|
end
|
21
21
|
@tree.append_column col_artist
|
22
22
|
|
23
|
-
@model = TreeStore.new(Object)
|
23
|
+
@model = TreeStore.new(Object, Array)
|
24
24
|
@tree.model = @model
|
25
25
|
|
26
26
|
GLib::Timeout.add(1000) do
|
@@ -79,7 +79,19 @@ module Sh
|
|
79
79
|
queue << iter[0]
|
80
80
|
iter.next!
|
81
81
|
end
|
82
|
+
prev = []
|
83
|
+
iter = parent.first_child
|
84
|
+
until iter.path.indices == path.indices
|
85
|
+
prev << iter[0]
|
86
|
+
iter.next!
|
87
|
+
end
|
88
|
+
queue.insert 0, *prev
|
82
89
|
view.queue = queue
|
90
|
+
view.queue_pos = prev.size
|
91
|
+
view.play
|
92
|
+
else
|
93
|
+
view.stop
|
94
|
+
view.queue = iter[1].dup
|
83
95
|
view.play
|
84
96
|
end
|
85
97
|
end
|
@@ -98,20 +110,21 @@ module Sh
|
|
98
110
|
artist_node[0] = artist
|
99
111
|
# Required for proper handling of multiple artists with on album
|
100
112
|
album_node = nil
|
101
|
-
#artist_node[1] = []
|
102
113
|
end
|
103
|
-
|
114
|
+
artist_node[1] ||= []
|
115
|
+
artist_node[1] << song
|
104
116
|
|
105
117
|
album = song.album || '!Unknown Album!'
|
106
118
|
if not album_node or album_node[0] != album
|
107
119
|
album_node = @model.append artist_node
|
108
120
|
album_node[0] = album
|
109
|
-
#album_node[1] = []
|
110
121
|
end
|
111
|
-
|
122
|
+
album_node[1] ||= []
|
123
|
+
album_node[1] << song
|
112
124
|
|
113
125
|
track_node = @model.append album_node
|
114
126
|
track_node[0] = song
|
127
|
+
track_node[1] = [song]
|
115
128
|
end
|
116
129
|
end
|
117
130
|
|
data/lib/sh_main.rb
CHANGED
data/lib/sh_player.rb
CHANGED
@@ -1,164 +1,215 @@
|
|
1
|
-
require '
|
1
|
+
require 'wx'
|
2
2
|
require 'rubygems'
|
3
3
|
require 'scrobbler'
|
4
4
|
|
5
5
|
module Sh
|
6
|
-
class Player
|
6
|
+
class Player < Wx::Frame
|
7
7
|
@@scrobble_auth = nil
|
8
|
-
|
9
|
-
Gst.init
|
10
|
-
|
11
8
|
attr_reader :song
|
12
9
|
|
13
10
|
def initialize song
|
14
11
|
@song = song
|
15
|
-
@
|
12
|
+
@destroyed = false
|
16
13
|
@play_time = 0
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
# setup the playbin
|
24
|
-
@playbin = Gst::ElementFactory.make('playbin')
|
25
|
-
@playbin.uri = "file://#{@song.path}"
|
26
|
-
@playbin.ready
|
27
|
-
|
28
|
-
# Get duration
|
29
|
-
# FIXME: This duration-getting code results in nasty error messages
|
30
|
-
@playbin.audio_sink = Gst::ElementFactory.make('fakesink')
|
31
|
-
@playbin.play
|
32
|
-
pos = -1
|
33
|
-
while true
|
34
|
-
while (Gtk.events_pending?)
|
35
|
-
Gtk.main_iteration
|
36
|
-
end
|
37
|
-
query = Gst::QueryPosition.new(Gst::Format::TIME)
|
38
|
-
@playbin.query(query)
|
39
|
-
new_pos = query.parse.last / 1000000000.to_f
|
40
|
-
break if pos > 0 and new_pos == pos
|
41
|
-
pos = new_pos
|
42
|
-
end
|
43
|
-
query = Gst::QueryDuration.new(Gst::Format::TIME)
|
44
|
-
@playbin.query(query)
|
45
|
-
@duration = query.parse.last / 1000000000.to_f
|
46
|
-
@playbin.stop
|
47
|
-
|
48
|
-
# setup the sink
|
49
|
-
@playbin.audio_sink = Gst::ElementFactory.make('autoaudiosink')
|
50
|
-
|
51
|
-
@playbin.bus.add_watch do |a, message|
|
52
|
-
case message.type
|
53
|
-
when Gst::Message::Type::ERROR
|
54
|
-
puts "An error occured: #{message.parse_error[0]}"
|
55
|
-
when Gst::Message::Type::EOS
|
56
|
-
pause
|
57
|
-
seek 0
|
58
|
-
@finished_cb.call self if @finished_cb
|
59
|
-
end
|
60
|
-
true
|
61
|
-
end
|
62
|
-
|
63
|
-
#@duration = @song.duration if @duration <= 0
|
64
|
-
|
65
|
-
# Make sure pipeline is stopped when program is exited
|
66
|
-
Signal.trap('EXIT') do
|
67
|
-
stop
|
14
|
+
super(nil,:title=>"",:size=>[700,700])
|
15
|
+
@mp = MediaPanel.new(self)
|
16
|
+
@mp.do_load_file song.path
|
17
|
+
self.hide
|
18
|
+
trap('EXIT') do
|
19
|
+
self.stop
|
68
20
|
end
|
69
|
-
|
70
21
|
GLib::Timeout.add(1000) do
|
71
22
|
@play_time += 1 if playing?
|
72
|
-
|
23
|
+
!@destroyed
|
73
24
|
end
|
74
25
|
end
|
75
26
|
|
76
|
-
|
27
|
+
def demolish
|
28
|
+
@destroyed = true
|
29
|
+
self.destroy
|
30
|
+
end
|
31
|
+
|
77
32
|
def play
|
78
|
-
@
|
79
|
-
@
|
33
|
+
@paused = false
|
34
|
+
@playing = true
|
35
|
+
@mp.play
|
80
36
|
end
|
81
37
|
|
82
38
|
def pause
|
83
|
-
@
|
39
|
+
@paused = true
|
40
|
+
@playing = false
|
41
|
+
@mp.pause
|
42
|
+
end
|
43
|
+
|
44
|
+
def playing?
|
45
|
+
@playing
|
46
|
+
end
|
47
|
+
|
48
|
+
def paused?
|
49
|
+
@paused
|
84
50
|
end
|
85
51
|
|
86
52
|
def stop
|
87
|
-
@
|
88
|
-
|
89
|
-
|
53
|
+
@mp.stop
|
54
|
+
scrobble_lastfm! if $prefs[:lastfm]
|
55
|
+
end
|
56
|
+
|
57
|
+
def duration
|
58
|
+
@mp.duration
|
90
59
|
end
|
91
60
|
|
92
|
-
def
|
61
|
+
def position
|
62
|
+
@mp.position
|
63
|
+
end
|
64
|
+
|
65
|
+
# Set the position (in seconds) to start playback from
|
66
|
+
def seek pos
|
67
|
+
@mp.seek pos
|
68
|
+
end
|
69
|
+
|
70
|
+
def on_finished &block
|
71
|
+
@mp.on_finished {block.call(self)}
|
72
|
+
end
|
73
|
+
|
74
|
+
#TODO: move scrobble support elsewhere
|
75
|
+
def scrobble_lastfm!
|
76
|
+
@@scrobble_queue ||= []
|
93
77
|
play_time = @play_time
|
94
78
|
if play_time > 240 or (play_time > duration / 2 and duration > 30)
|
95
79
|
auth = @@scrobble_auth
|
96
|
-
if not auth
|
80
|
+
if not auth or auth.status.strip.upcase != 'OK'
|
97
81
|
user, pass = $prefs[:lastfm_user], $prefs[:lastfm_password]
|
98
|
-
|
99
|
-
|
100
|
-
|
82
|
+
begin
|
83
|
+
auth = @@scrobble_auth = Scrobbler::SimpleAuth.new(:user => user, :password => pass)
|
84
|
+
auth.handshake!
|
85
|
+
puts "Last.fm authentication status: #{auth.status}"
|
86
|
+
rescue
|
87
|
+
end
|
101
88
|
end
|
102
89
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
:track_number => song.track_num)
|
90
|
+
@@scrobble_queue << Scrobbler::Scrobble.new(
|
91
|
+
:session_id => auth.session_id,
|
92
|
+
:submission_url => auth.submission_url,
|
93
|
+
:artist => song.artist,
|
94
|
+
:track => song.title,
|
95
|
+
:album => song.album,
|
96
|
+
:time => Time.new,
|
97
|
+
:length => duration,
|
98
|
+
:track_number => song.track_num)
|
113
99
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
100
|
+
# No point attempting to scrobble without authentication
|
101
|
+
return if auth.status.strip.upcase != 'OK'
|
102
|
+
|
103
|
+
Thread.new do
|
104
|
+
failed_scrobbles = []
|
105
|
+
until @@scrobble_queue.empty?
|
106
|
+
scrobble = @@scrobble_queue.pop
|
107
|
+
scrobble.submit! rescue Exception
|
108
|
+
|
109
|
+
if scrobble.status.strip.upcase == 'OK'
|
110
|
+
puts "Scrobble for '#{scrobble.track}' succeeded"
|
111
|
+
else
|
112
|
+
puts "Warning: Scrobble for '#{scrobble.track}' failed"
|
113
|
+
failed_scrobbles << scrobble
|
114
|
+
end
|
121
115
|
end
|
116
|
+
@@scrobble_queue.insert(0, *failed_scrobbles)
|
122
117
|
end
|
123
118
|
end
|
124
119
|
end
|
120
|
+
end
|
125
121
|
|
126
|
-
|
127
|
-
|
122
|
+
private
|
123
|
+
class MediaPanel < Wx::Panel
|
124
|
+
def initialize(parent)
|
125
|
+
super(parent)
|
126
|
+
|
127
|
+
# The actual media player control
|
128
|
+
@mc = Wx::MediaCtrl.new(self)
|
129
|
+
|
130
|
+
evt_media_loaded @mc, :on_media_loaded
|
131
|
+
evt_media_finished @mc, :on_media_finished
|
128
132
|
end
|
129
133
|
|
130
|
-
|
131
|
-
|
134
|
+
# Update the position indicator in seconds
|
135
|
+
def position
|
136
|
+
offset = @mc.tell
|
137
|
+
# Will be -1 if nothing is loaded
|
138
|
+
if offset >= 0
|
139
|
+
offset_secs = offset / 1000
|
140
|
+
return offset_secs
|
141
|
+
#lbl_pos = "%i:%02d" % [ offset_secs / 60, offset_secs % 60 ]
|
142
|
+
end
|
143
|
+
return nil
|
132
144
|
end
|
133
145
|
|
134
|
-
|
135
|
-
|
146
|
+
# Actually load a file into the mediactrl
|
147
|
+
def do_load_file(path)
|
148
|
+
unless @mc.load(path)
|
149
|
+
Wx::message_box("Unable to load file", "ERROR",
|
150
|
+
Wx::ICON_ERROR|Wx::OK)
|
151
|
+
on_media_finished nil
|
152
|
+
end
|
136
153
|
end
|
137
154
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
return duration
|
148
|
-
else
|
149
|
-
return pos / 1000000000.to_f
|
150
|
-
end
|
155
|
+
MEDIA_FILE_WILDCARD = "Audio Files|*.mp3;*.flac;*.ogg;*.m4a"
|
156
|
+
# Load a media file from disk
|
157
|
+
def show_open_dialog
|
158
|
+
dlg = Wx::FileDialog.new( self,
|
159
|
+
:message => "Choose a media file",
|
160
|
+
:wildcard => MEDIA_FILE_WILDCARD,
|
161
|
+
:style => Wx::OPEN)
|
162
|
+
if dlg.show_modal == Wx::ID_OK
|
163
|
+
do_load_file(dlg.path)
|
151
164
|
end
|
152
165
|
end
|
153
166
|
|
154
|
-
#
|
155
|
-
def seek
|
156
|
-
@
|
157
|
-
|
167
|
+
# Move the media to a position in the file, using the slider
|
168
|
+
def seek(secs)
|
169
|
+
@mc.seek((secs*1000).to_i)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Set up the initial size for the move
|
173
|
+
def on_media_loaded(evt)
|
174
|
+
@loaded = true
|
175
|
+
|
176
|
+
# Update the length and position text indicators with the media's
|
177
|
+
# time length, shown as minutes and seconds
|
178
|
+
len_secs = duration
|
179
|
+
lbl_len = "%i:%02d" % [ len_secs / 60, len_secs % 60 ]
|
158
180
|
end
|
159
181
|
|
160
182
|
def on_finished &block
|
161
183
|
@finished_cb = block
|
162
184
|
end
|
185
|
+
|
186
|
+
private
|
187
|
+
def on_media_finished(evt)
|
188
|
+
@finished_cb.call if @finished_cb
|
189
|
+
end
|
190
|
+
|
191
|
+
public
|
192
|
+
def duration
|
193
|
+
return @mc.length / 1000
|
194
|
+
end
|
195
|
+
|
196
|
+
# Stop the playback
|
197
|
+
def stop
|
198
|
+
@mc.stop
|
199
|
+
end
|
200
|
+
|
201
|
+
# Pause the playback
|
202
|
+
def pause
|
203
|
+
@mc.pause
|
204
|
+
end
|
205
|
+
|
206
|
+
# Start the playback
|
207
|
+
def play
|
208
|
+
if not @mc.play
|
209
|
+
Wx::MessageBox("unable to play media","ERROR",Wx::ICON_ERROR|Wx::OK)
|
210
|
+
on_media_finished nil
|
211
|
+
else
|
212
|
+
end
|
213
|
+
end
|
163
214
|
end
|
164
215
|
end
|
data/lib/sh_song.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require '
|
3
|
-
require 'flacinfo'
|
4
|
-
require 'ogginfo'
|
2
|
+
require 'sh_tagreader'
|
5
3
|
require 'earworm'
|
6
4
|
require 'rbrainz'
|
7
5
|
include MusicBrainz
|
@@ -13,25 +11,10 @@ module Sh
|
|
13
11
|
|
14
12
|
def initialize path
|
15
13
|
@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
14
|
end
|
23
15
|
|
24
16
|
def duration
|
25
|
-
|
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
|
17
|
+
@duration ||= Sh::TagReader.read(path)[:duration]
|
35
18
|
end
|
36
19
|
|
37
20
|
|
@@ -80,65 +63,22 @@ module Sh
|
|
80
63
|
end
|
81
64
|
|
82
65
|
public
|
83
|
-
def read_tags!
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
66
|
+
def read_tags! overwrite=false
|
67
|
+
Sh::TagReader.read(path) do |data|
|
68
|
+
if overwrite
|
69
|
+
@title = data[:title]
|
70
|
+
@artist = data[:artist]
|
71
|
+
@album = data[:album]
|
72
|
+
@year = data[:year]
|
73
|
+
@track_num = data[:track_num]
|
74
|
+
@duration = data[:duration]
|
75
|
+
else
|
76
|
+
@title ||= data[:title]
|
77
|
+
@artist ||= data[:artist]
|
78
|
+
@album ||= data[:album]
|
79
|
+
@year ||= data[:year]
|
80
|
+
@track_num ||= data[:track_num]
|
81
|
+
@duration ||= data[:duration]
|
142
82
|
end
|
143
83
|
end
|
144
84
|
end
|
data/lib/sh_tagreader.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
module Sh
|
2
|
+
class TagReader
|
3
|
+
def TagReader.read path, &block
|
4
|
+
@path = File.expand_path path
|
5
|
+
metadata = {}
|
6
|
+
|
7
|
+
if try_require 'shared-mime-info'
|
8
|
+
type = MIME.check(path)
|
9
|
+
@mime = type.to_s if type.media == 'audio'
|
10
|
+
end
|
11
|
+
@mime ||= {
|
12
|
+
'.mp3' => 'audio/mpeg',
|
13
|
+
'.m4a' => 'audio/mp4',
|
14
|
+
'.flac' => 'audio/x-flac',
|
15
|
+
'.ogg' => 'audio/ogg',
|
16
|
+
'.wav' => 'audio/x-wav',
|
17
|
+
'.wma' => 'audio/x-ms-wma'
|
18
|
+
}[File.extname path]
|
19
|
+
|
20
|
+
case @mime
|
21
|
+
when 'audio/mpeg'
|
22
|
+
metadata = TagReader.read_mp3 path
|
23
|
+
when 'audio/mp4'
|
24
|
+
metadata = TagReader.read_m4a path
|
25
|
+
when 'audio/ogg'
|
26
|
+
metadata = TagReader.read_ogg path
|
27
|
+
when 'audio/x-flac'
|
28
|
+
metadata = TagReader.read_flac path
|
29
|
+
when 'audio/x-ms-wma'
|
30
|
+
metadata = TagReader.read_wma path
|
31
|
+
when 'audio/x-wav'
|
32
|
+
metadata = TagReader.read_wave path
|
33
|
+
end
|
34
|
+
yield metadata if block
|
35
|
+
return metadata
|
36
|
+
end
|
37
|
+
|
38
|
+
def TagReader.read_mp3 path, &block
|
39
|
+
metadata = {}
|
40
|
+
if try_require 'mp3info'
|
41
|
+
Mp3Info.open path do |mp3|
|
42
|
+
t = mp3.tag
|
43
|
+
metadata[:title] = t.title unless t.title and t.title.is_binary_data?
|
44
|
+
metadata[:artist] = t.artist unless t.artist and t.artist.is_binary_data?
|
45
|
+
metadata[:album] = t.album unless t.album and t.album.is_binary_data?
|
46
|
+
metadata[:year] = t.year.to_i
|
47
|
+
metadata[:track_num] = t.tracknum.to_i
|
48
|
+
metadata[:duration] = mp3.length
|
49
|
+
end
|
50
|
+
else
|
51
|
+
puts 'Please install the "mp3info" gem in order to read MP3 info'
|
52
|
+
end
|
53
|
+
yield metadata if block
|
54
|
+
return metadata
|
55
|
+
end
|
56
|
+
|
57
|
+
def TagReader.read_m4a path, &block
|
58
|
+
metadata = {}
|
59
|
+
if try_require 'mp4info'
|
60
|
+
mp4 = MP4Info.open path
|
61
|
+
metadata[:title] = mp4.NAM unless mp4.NAM and mp4.NAM.is_binary_data?
|
62
|
+
metadata[:artist] = mp4.ART unless mp4.ART and mp4.ART.is_binary_data?
|
63
|
+
metadata[:album] = mp4.ALB unless mp4.ALB and mp4.ALB.is_binary_data?
|
64
|
+
metadata[:year] = mp4.DAY.to_i
|
65
|
+
metadata[:track_num] = mp4.TRKN.first.to_i
|
66
|
+
metadata[:duration] = mp4.SECS
|
67
|
+
else
|
68
|
+
puts 'Please install the "mp4info" gem in order to read M4A info'
|
69
|
+
end
|
70
|
+
yield metadata if block
|
71
|
+
return metadata
|
72
|
+
end
|
73
|
+
|
74
|
+
def TagReader.read_ogg path, &block
|
75
|
+
metadata = {}
|
76
|
+
if try_require 'ogginfo'
|
77
|
+
OggInfo.open path do |ogg|
|
78
|
+
t = ogg.tag
|
79
|
+
metadata[:title] = t.title unless t.title and t.title.is_binary_data?
|
80
|
+
metadata[:artist] = t.artist unless t.artist and t.artist.is_binary_data?
|
81
|
+
metadata[:album] = t.album unless t.album and t.album.is_binary_data?
|
82
|
+
metadata[:year] = t.date.to_i
|
83
|
+
metadata[:track_num] = t.tracknumber.to_i
|
84
|
+
metadata[:duration] = ogg.length
|
85
|
+
end rescue Exception
|
86
|
+
else
|
87
|
+
puts 'Please install the "ogginfo" gem in order to read OGG info'
|
88
|
+
end
|
89
|
+
yield metadata if block
|
90
|
+
return metadata
|
91
|
+
end
|
92
|
+
|
93
|
+
def TagReader.read_flac path, &block
|
94
|
+
metadata = {}
|
95
|
+
if try_require 'flacinfo'
|
96
|
+
flac = FlacInfo.new(path)
|
97
|
+
flac.comment.each do |comment|
|
98
|
+
split = comment.split('=')
|
99
|
+
key = split.first.downcase
|
100
|
+
value = (split[1..-1]).join
|
101
|
+
value = nil if value == '' or value.is_binary_data?
|
102
|
+
case key
|
103
|
+
when 'title'
|
104
|
+
metadata[:title] = value
|
105
|
+
when 'artist'
|
106
|
+
metadata[:artist] = value
|
107
|
+
when 'album'
|
108
|
+
metadata[:album] = value
|
109
|
+
when 'year'
|
110
|
+
metadata[:year] = value.to_i
|
111
|
+
when 'tracknumber'
|
112
|
+
metadata[:track_num] = value.to_i
|
113
|
+
end
|
114
|
+
end
|
115
|
+
else
|
116
|
+
puts 'Please install the "flacinfo" gem in order to read FLAC info'
|
117
|
+
end
|
118
|
+
yield metadata if block
|
119
|
+
return metadata
|
120
|
+
end
|
121
|
+
|
122
|
+
def TagReader.read_wma path, &block
|
123
|
+
metadata = {}
|
124
|
+
if try_require 'wmainfo'
|
125
|
+
wma = WmaInfo.new(path)
|
126
|
+
wma.tags.each do |key, value|
|
127
|
+
key = key.downcase
|
128
|
+
case key
|
129
|
+
when 'title'
|
130
|
+
metadata[:title] = value
|
131
|
+
when 'author'
|
132
|
+
metadata[:artist] = value
|
133
|
+
when 'albumtitle'
|
134
|
+
metadata[:album] = value
|
135
|
+
when 'year'
|
136
|
+
metadata[:year] = value.to_i
|
137
|
+
when 'tracknumber'
|
138
|
+
metadata[:track_num] = value.to_i
|
139
|
+
end
|
140
|
+
end
|
141
|
+
metadata[:duration] = wma.info["playtime_seconds"]
|
142
|
+
else
|
143
|
+
puts 'Please install the "wmainfo" gem in order to read WMA info'
|
144
|
+
end
|
145
|
+
yield metadata if block
|
146
|
+
return metadata
|
147
|
+
end
|
148
|
+
|
149
|
+
def TagReader.read_wave path, &block
|
150
|
+
metadata = {}
|
151
|
+
if try_require 'waveinfo'
|
152
|
+
wave = WaveInfo.new path
|
153
|
+
metadata[:duration] = wave.duration
|
154
|
+
else
|
155
|
+
puts 'Please install the "waveinfo" gem in order to read WAV info'
|
156
|
+
end
|
157
|
+
yield metadata if block
|
158
|
+
return metadata
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
data/lib/sh_util.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
+
require 'gtk2'
|
2
|
+
|
1
3
|
class Object
|
2
4
|
def try_require lib
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
found
|
5
|
+
found = nil
|
6
|
+
begin
|
7
|
+
require lib
|
8
|
+
found = true
|
9
|
+
rescue LoadError
|
10
|
+
found = false
|
11
|
+
end
|
12
|
+
return found
|
12
13
|
end
|
13
14
|
|
14
15
|
def find_in_load_path(file)
|
@@ -19,3 +20,66 @@ class Object
|
|
19
20
|
return nil
|
20
21
|
end
|
21
22
|
end
|
23
|
+
|
24
|
+
module Kelp
|
25
|
+
class ProgressDialog < Gtk::Dialog
|
26
|
+
def initialize(title)
|
27
|
+
super(title,
|
28
|
+
nil,
|
29
|
+
Dialog::MODAL,
|
30
|
+
[Stock::CANCEL, Dialog::RESPONSE_NONE])
|
31
|
+
@lbl_message = Label.new("Please wait")
|
32
|
+
vbox.pack_start @lbl_message, false, false, 8
|
33
|
+
@progress = ProgressBar.new
|
34
|
+
vbox.pack_start @progress, false, false, 8
|
35
|
+
self.width_request = 300
|
36
|
+
vbox.show_all
|
37
|
+
end
|
38
|
+
|
39
|
+
def message=(msg)
|
40
|
+
@lbl_message.set_markup(msg)
|
41
|
+
end
|
42
|
+
|
43
|
+
def message
|
44
|
+
@lbl_message.text
|
45
|
+
end
|
46
|
+
|
47
|
+
def fraction=(frac)
|
48
|
+
@progress.fraction = frac
|
49
|
+
end
|
50
|
+
|
51
|
+
def fraction
|
52
|
+
@progress.fraction
|
53
|
+
end
|
54
|
+
|
55
|
+
def pulse
|
56
|
+
@progress.pulse
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class ImageDialog < Gtk::Dialog
|
61
|
+
def initialize(pixbuf, title="")
|
62
|
+
super(title,
|
63
|
+
nil,
|
64
|
+
nil,
|
65
|
+
[Stock::CANCEL, Dialog::RESPONSE_NONE])
|
66
|
+
@image = Image.new(pixbuf)
|
67
|
+
vbox.pack_start @image, true, true, 8
|
68
|
+
if pixbuf
|
69
|
+
self.width_request = pixbuf.width + 16
|
70
|
+
self.height_request = size[1] + pixbuf.height + 16
|
71
|
+
end
|
72
|
+
vbox.show_all
|
73
|
+
signal_connect('response') { destroy }
|
74
|
+
end
|
75
|
+
|
76
|
+
def pixbuf=(pixbuf)
|
77
|
+
@image.pixbuf = pixbuf
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def Kelp.process_events
|
82
|
+
Gtk.main_iteration while Gtk.events_pending?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
data/lib/sh_view.rb
CHANGED
@@ -23,53 +23,39 @@ module Sh
|
|
23
23
|
|
24
24
|
@glade= GladeXML.new(Global.locate('shroom.glade'))
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
[
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
26
|
+
if File.exists? $prefs[:library_dir]
|
27
|
+
cancelled = false
|
28
|
+
dialog = Kelp::ProgressDialog.new("Updating database...")
|
29
|
+
dialog.signal_connect('response') { cancelled = true}
|
30
|
+
new_songs = []
|
31
|
+
Dir[$prefs[:library_dir]+'/**/*'].each do |path|
|
32
|
+
ext = File.extname path
|
33
|
+
if ['.mp3', '.m4a', '.ogg', '.flac'].include? ext
|
34
|
+
new_songs << Song.new(path) unless $db.contains? path
|
35
|
+
end
|
44
36
|
end
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
while (Gtk.events_pending?)
|
49
|
-
Gtk.main_iteration
|
37
|
+
if new_songs.length > 0
|
38
|
+
dialog.show_all
|
39
|
+
Kelp.process_events
|
50
40
|
end
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
Gtk.main_iteration
|
41
|
+
inc = 1 / new_songs.length.to_f
|
42
|
+
new_songs.each do |song|
|
43
|
+
dialog.message = "..." + song.path[-30..-1]
|
44
|
+
puts "Adding: #{song.path}"
|
45
|
+
song.read_tags!
|
46
|
+
$db.save_song song
|
47
|
+
dialog.fraction += inc
|
48
|
+
Kelp.process_events
|
49
|
+
break if cancelled
|
61
50
|
end
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
dialog.destroy unless cancelled
|
51
|
+
dialog.message = "Cleaning out old entries..."
|
52
|
+
dialog.pulse
|
53
|
+
$db.clean
|
54
|
+
dialog.destroy
|
67
55
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
progress = nil
|
72
|
-
lbl_path = nil
|
56
|
+
# Clean up
|
57
|
+
new_songs = dialog = nil
|
58
|
+
end
|
73
59
|
|
74
60
|
icon = Gdk::Pixbuf.new Global.locate('icon_16x16.png')
|
75
61
|
|
@@ -130,7 +116,7 @@ module Sh
|
|
130
116
|
end
|
131
117
|
GLib::Timeout.add(100) do
|
132
118
|
if @player
|
133
|
-
pos = @player.position
|
119
|
+
pos = @player.position.to_f
|
134
120
|
seeker.value = pos / @player.duration
|
135
121
|
lbl_time.text = time_to_s(pos) + "/" + time_to_s(@player.duration)
|
136
122
|
lbl_song.set_markup @player.song.to_html
|
@@ -209,8 +195,13 @@ module Sh
|
|
209
195
|
sidebar.pack_start lst_tabs, true, true, 0
|
210
196
|
# Cover image widget
|
211
197
|
@img_cover = Image.new
|
198
|
+
event_box = EventBox.new
|
199
|
+
event_box.add @img_cover
|
200
|
+
event_box.signal_connect("button-press-event") do |w, event|
|
201
|
+
Kelp::ImageDialog.new(@pixbuf, @player.song.album).show if @pixbuf and @player
|
202
|
+
end
|
212
203
|
@img_cover.height_request = 150
|
213
|
-
sidebar.pack_start
|
204
|
+
sidebar.pack_start event_box, false, false, 0
|
214
205
|
# Lyrics text view
|
215
206
|
@txt_lyrics = TextView.new
|
216
207
|
@txt_lyrics.editable = false
|
@@ -261,7 +252,6 @@ module Sh
|
|
261
252
|
end
|
262
253
|
|
263
254
|
def show_preferences
|
264
|
-
#FIXME: disable close button on dialog
|
265
255
|
dlg_prefs = @glade.get_widget("dlg_preferences")
|
266
256
|
dlg_prefs.signal_connect('delete-event') do
|
267
257
|
dlg_prefs.hide
|
@@ -304,11 +294,12 @@ module Sh
|
|
304
294
|
"Copyright (C) 2009 Aiden Nibali",
|
305
295
|
"Shroom - A music player and organizer in Ruby",
|
306
296
|
["Aiden Nibali"], ["Aiden Nibali"], nil)
|
307
|
-
dlg_about.logo = Gdk::Pixbuf.new(Global.locate('
|
297
|
+
dlg_about.logo = Gdk::Pixbuf.new(Global.locate('icon_128x128.png'))
|
308
298
|
dlg_about.show
|
309
299
|
end
|
310
300
|
|
311
301
|
def prepare_song
|
302
|
+
@player.demolish if @player
|
312
303
|
@player = Sh::Player.new @queue[@queue_pos]
|
313
304
|
@player.on_finished do |player|
|
314
305
|
if player == @player
|
@@ -329,7 +320,7 @@ module Sh
|
|
329
320
|
|
330
321
|
# Format time (in seconds) as mins:secs
|
331
322
|
def time_to_s(time)
|
332
|
-
mins_float = time / 60
|
323
|
+
mins_float = time.to_f / 60
|
333
324
|
mins = mins_float.floor
|
334
325
|
secs = ((mins_float - mins) * 60).round
|
335
326
|
if secs == 60
|
@@ -406,6 +397,12 @@ module Sh
|
|
406
397
|
prepare_song
|
407
398
|
end
|
408
399
|
|
400
|
+
def queue_pos=(pos)
|
401
|
+
puts pos
|
402
|
+
@queue_pos = pos
|
403
|
+
prepare_song
|
404
|
+
end
|
405
|
+
|
409
406
|
def play
|
410
407
|
stop
|
411
408
|
if @player
|
@@ -0,0 +1,206 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
3
|
+
<svg
|
4
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
5
|
+
xmlns:cc="http://creativecommons.org/ns#"
|
6
|
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
7
|
+
xmlns:svg="http://www.w3.org/2000/svg"
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
9
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
10
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
11
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
12
|
+
width="128"
|
13
|
+
height="128"
|
14
|
+
id="svg2"
|
15
|
+
sodipodi:version="0.32"
|
16
|
+
inkscape:version="0.46"
|
17
|
+
version="1.0"
|
18
|
+
sodipodi:docname="icon.svg"
|
19
|
+
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
20
|
+
inkscape:export-filename="/home/aiden/data/Projects/Ruby/shroom/lib/shroom-res/icon.png"
|
21
|
+
inkscape:export-xdpi="90"
|
22
|
+
inkscape:export-ydpi="90">
|
23
|
+
<defs
|
24
|
+
id="defs4">
|
25
|
+
<linearGradient
|
26
|
+
id="linearGradient3185">
|
27
|
+
<stop
|
28
|
+
style="stop-color:#d81010;stop-opacity:1;"
|
29
|
+
offset="0"
|
30
|
+
id="stop3187" />
|
31
|
+
<stop
|
32
|
+
style="stop-color:#f79393;stop-opacity:1;"
|
33
|
+
offset="1"
|
34
|
+
id="stop3189" />
|
35
|
+
</linearGradient>
|
36
|
+
<linearGradient
|
37
|
+
id="linearGradient3173">
|
38
|
+
<stop
|
39
|
+
style="stop-color:#ddd990;stop-opacity:1;"
|
40
|
+
offset="0"
|
41
|
+
id="stop3175" />
|
42
|
+
<stop
|
43
|
+
id="stop3183"
|
44
|
+
offset="1"
|
45
|
+
style="stop-color:#fafaef;stop-opacity:1;" />
|
46
|
+
</linearGradient>
|
47
|
+
<inkscape:perspective
|
48
|
+
sodipodi:type="inkscape:persp3d"
|
49
|
+
inkscape:vp_x="0 : 526.18109 : 1"
|
50
|
+
inkscape:vp_y="0 : 1000 : 0"
|
51
|
+
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
52
|
+
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
53
|
+
id="perspective10" />
|
54
|
+
<inkscape:perspective
|
55
|
+
id="perspective2390"
|
56
|
+
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
57
|
+
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
58
|
+
inkscape:vp_y="0 : 1000 : 0"
|
59
|
+
inkscape:vp_x="0 : 526.18109 : 1"
|
60
|
+
sodipodi:type="inkscape:persp3d" />
|
61
|
+
<linearGradient
|
62
|
+
inkscape:collect="always"
|
63
|
+
xlink:href="#linearGradient3173"
|
64
|
+
id="linearGradient3181"
|
65
|
+
x1="67.09375"
|
66
|
+
y1="71.425606"
|
67
|
+
x2="72.102676"
|
68
|
+
y2="70.175606"
|
69
|
+
gradientUnits="userSpaceOnUse" />
|
70
|
+
<linearGradient
|
71
|
+
inkscape:collect="always"
|
72
|
+
xlink:href="#linearGradient3185"
|
73
|
+
id="linearGradient3191"
|
74
|
+
x1="66.030533"
|
75
|
+
y1="64.673965"
|
76
|
+
x2="71.879753"
|
77
|
+
y2="59.227531"
|
78
|
+
gradientUnits="userSpaceOnUse" />
|
79
|
+
<linearGradient
|
80
|
+
inkscape:collect="always"
|
81
|
+
xlink:href="#linearGradient3185"
|
82
|
+
id="linearGradient3206"
|
83
|
+
gradientUnits="userSpaceOnUse"
|
84
|
+
x1="66.030533"
|
85
|
+
y1="64.673965"
|
86
|
+
x2="71.879753"
|
87
|
+
y2="59.227531" />
|
88
|
+
<linearGradient
|
89
|
+
inkscape:collect="always"
|
90
|
+
xlink:href="#linearGradient3173"
|
91
|
+
id="linearGradient3208"
|
92
|
+
gradientUnits="userSpaceOnUse"
|
93
|
+
x1="67.09375"
|
94
|
+
y1="71.425606"
|
95
|
+
x2="72.102676"
|
96
|
+
y2="70.175606" />
|
97
|
+
<linearGradient
|
98
|
+
inkscape:collect="always"
|
99
|
+
xlink:href="#linearGradient3173"
|
100
|
+
id="linearGradient3214"
|
101
|
+
gradientUnits="userSpaceOnUse"
|
102
|
+
x1="67.09375"
|
103
|
+
y1="71.425606"
|
104
|
+
x2="72.102676"
|
105
|
+
y2="70.175606"
|
106
|
+
gradientTransform="matrix(7.6863997,0,0,7.6863997,-464.30109,-449.66077)" />
|
107
|
+
<linearGradient
|
108
|
+
inkscape:collect="always"
|
109
|
+
xlink:href="#linearGradient3185"
|
110
|
+
id="linearGradient3217"
|
111
|
+
gradientUnits="userSpaceOnUse"
|
112
|
+
x1="66.030533"
|
113
|
+
y1="64.673965"
|
114
|
+
x2="71.879753"
|
115
|
+
y2="59.227531"
|
116
|
+
gradientTransform="matrix(7.6863997,0,0,7.6863997,-464.30109,-449.66077)" />
|
117
|
+
<linearGradient
|
118
|
+
inkscape:collect="always"
|
119
|
+
xlink:href="#linearGradient3185"
|
120
|
+
id="linearGradient3221"
|
121
|
+
gradientUnits="userSpaceOnUse"
|
122
|
+
gradientTransform="matrix(7.6863997,0,0,7.6863997,-464.30109,-449.66077)"
|
123
|
+
x1="66.030533"
|
124
|
+
y1="64.673965"
|
125
|
+
x2="71.879753"
|
126
|
+
y2="59.227531" />
|
127
|
+
<linearGradient
|
128
|
+
inkscape:collect="always"
|
129
|
+
xlink:href="#linearGradient3185"
|
130
|
+
id="linearGradient3226"
|
131
|
+
gradientUnits="userSpaceOnUse"
|
132
|
+
gradientTransform="matrix(7.6863997,0,0,7.6863997,-464.30109,-449.66077)"
|
133
|
+
x1="66.030533"
|
134
|
+
y1="64.673965"
|
135
|
+
x2="71.879753"
|
136
|
+
y2="59.227531" />
|
137
|
+
<linearGradient
|
138
|
+
inkscape:collect="always"
|
139
|
+
xlink:href="#linearGradient3185"
|
140
|
+
id="linearGradient3231"
|
141
|
+
gradientUnits="userSpaceOnUse"
|
142
|
+
gradientTransform="matrix(7.6863997,0,0,7.6863997,-464.30109,-449.66077)"
|
143
|
+
x1="66.030533"
|
144
|
+
y1="64.673965"
|
145
|
+
x2="71.879753"
|
146
|
+
y2="59.227531" />
|
147
|
+
</defs>
|
148
|
+
<sodipodi:namedview
|
149
|
+
id="base"
|
150
|
+
pagecolor="#ffffff"
|
151
|
+
bordercolor="#666666"
|
152
|
+
borderopacity="1.0"
|
153
|
+
gridtolerance="10000"
|
154
|
+
guidetolerance="10"
|
155
|
+
objecttolerance="10"
|
156
|
+
inkscape:pageopacity="0.0"
|
157
|
+
inkscape:pageshadow="2"
|
158
|
+
inkscape:zoom="0.98994949"
|
159
|
+
inkscape:cx="-129.05856"
|
160
|
+
inkscape:cy="95.506274"
|
161
|
+
inkscape:document-units="px"
|
162
|
+
inkscape:current-layer="layer1"
|
163
|
+
showgrid="false"
|
164
|
+
inkscape:window-width="1280"
|
165
|
+
inkscape:window-height="952"
|
166
|
+
inkscape:window-x="0"
|
167
|
+
inkscape:window-y="25" />
|
168
|
+
<metadata
|
169
|
+
id="metadata7">
|
170
|
+
<rdf:RDF>
|
171
|
+
<cc:Work
|
172
|
+
rdf:about="">
|
173
|
+
<dc:format>image/svg+xml</dc:format>
|
174
|
+
<dc:type
|
175
|
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
176
|
+
</cc:Work>
|
177
|
+
</rdf:RDF>
|
178
|
+
</metadata>
|
179
|
+
<g
|
180
|
+
inkscape:label="Layer 1"
|
181
|
+
inkscape:groupmode="layer"
|
182
|
+
id="layer1">
|
183
|
+
<path
|
184
|
+
style="fill:url(#linearGradient3217);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1.0;stroke-miterlimit:4;stroke-dasharray:none"
|
185
|
+
d="M 6.4908916,57.916122 C 24.999535,58.794024 105.77402,59.044478 121.78689,59.288698 C 131.51788,59.437115 111.17279,6.7059531 62.766321,7.1309803 C 14.358045,7.5560228 -3.430298,57.445546 6.4908916,57.916122 z"
|
186
|
+
id="path2396"
|
187
|
+
sodipodi:nodetypes="cszs" />
|
188
|
+
<path
|
189
|
+
style="fill:url(#linearGradient3214);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1.0;stroke-miterlimit:4;stroke-dasharray:none"
|
190
|
+
d="M 43.24149,58.60241 L 43.48169,116.01021 C 43.542721,130.59666 82.73358,126.90661 82.634289,115.28961 L 82.153889,59.08281 C 68.137693,58.983433 57.427602,58.761526 43.24149,58.60241 z"
|
191
|
+
id="path2398"
|
192
|
+
sodipodi:nodetypes="csscc" />
|
193
|
+
<path
|
194
|
+
style="fill:#eeeeec;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1.0;stroke-miterlimit:4;stroke-dasharray:none"
|
195
|
+
d="M 103.71875 43.1875 C 102.26956 43.207151 100.91692 43.48529 99.90625 44.125 C 96.197739 46.47232 90.335985 54.343794 94.59375 57.75 C 95.129767 58.178813 95.873561 58.630912 96.75 59.0625 C 96.759442 59.062566 96.771811 59.062434 96.78125 59.0625 C 106.76936 59.132599 114.2276 59.191346 119 59.25 C 119.07587 59.134452 119.1521 59.029367 119.21875 58.90625 C 121.57537 54.552957 114.73828 47.432683 110.5 44.875 C 108.79319 43.844986 106.13406 43.154748 103.71875 43.1875 z "
|
196
|
+
id="path3224" />
|
197
|
+
<path
|
198
|
+
style="fill:#eeeeec;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1.0;stroke-miterlimit:4;stroke-dasharray:none"
|
199
|
+
d="M 34.75 13.8125 C 24.029081 19.257975 16.113724 27.276003 11 35 C 14.127645 39.028665 19.127459 42.369797 23.09375 42.65625 C 31.784744 43.28393 46.237628 33.649945 46.09375 24.9375 C 46.017229 20.303889 40.3162 15.899514 34.75 13.8125 z "
|
200
|
+
id="path3229" />
|
201
|
+
<path
|
202
|
+
style="fill:#eeeeec;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1.0;stroke-miterlimit:4;stroke-dasharray:none"
|
203
|
+
d="M 80.65625 19.03125 C 77.997773 19.118855 75.15174 19.743968 72.34375 21 C 69.295722 22.363411 63.410697 24.286122 63.375 27.625 C 63.305604 34.116491 74.008211 40.874321 80.5 40.84375 C 85.404748 40.82066 92.327441 35.517104 93.34375 30.71875 C 94.89759 23.382506 88.631682 18.768434 80.65625 19.03125 z "
|
204
|
+
id="path3219" />
|
205
|
+
</g>
|
206
|
+
</svg>
|
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shroom
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aiden Nibali
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-07-
|
12
|
+
date: 2009-07-23 00:00:00 +10:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -22,6 +22,16 @@ dependencies:
|
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: 0.6.13
|
24
24
|
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: MP4Info
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.3.3
|
34
|
+
version:
|
25
35
|
- !ruby/object:Gem::Dependency
|
26
36
|
name: ruby-ogginfo
|
27
37
|
type: :runtime
|
@@ -62,6 +72,46 @@ dependencies:
|
|
62
72
|
- !ruby/object:Gem::Version
|
63
73
|
version: 3.2.0
|
64
74
|
version:
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: wxruby
|
77
|
+
type: :runtime
|
78
|
+
version_requirement:
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 2.0.0
|
84
|
+
version:
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: rbrainz
|
87
|
+
type: :runtime
|
88
|
+
version_requirement:
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 0.5.0
|
94
|
+
version:
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: scrobbler
|
97
|
+
type: :runtime
|
98
|
+
version_requirement:
|
99
|
+
version_requirements: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.2.3
|
104
|
+
version:
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
name: shared-mime-info
|
107
|
+
type: :runtime
|
108
|
+
version_requirement:
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: "0.1"
|
114
|
+
version:
|
65
115
|
description: Shroom is a music player and organizer
|
66
116
|
email: dismal.denizen@gmail.com
|
67
117
|
executables:
|
@@ -81,17 +131,19 @@ files:
|
|
81
131
|
- lib/sh_main.rb
|
82
132
|
- lib/sh_song.rb
|
83
133
|
- lib/sh_cover_art.rb
|
134
|
+
- lib/sh_tagreader.rb
|
84
135
|
- lib/sh_lyrics.rb
|
85
136
|
- lib/sh_database.rb
|
86
137
|
- lib/sh_util.rb
|
138
|
+
- lib/shroom-res
|
87
139
|
- lib/shroom-res/icon_16x16.png
|
140
|
+
- lib/shroom-res/icon.svg
|
141
|
+
- lib/shroom-res/icon_128x128.png
|
88
142
|
- lib/shroom-res/shroom.glade
|
89
143
|
- lib/sh_player.rb
|
90
144
|
- lib/sh_view.rb
|
91
145
|
has_rdoc: true
|
92
146
|
homepage: http://shroom.rubyforge.org
|
93
|
-
licenses: []
|
94
|
-
|
95
147
|
post_install_message:
|
96
148
|
rdoc_options: []
|
97
149
|
|
@@ -110,14 +162,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
110
162
|
version: "0"
|
111
163
|
version:
|
112
164
|
requirements:
|
113
|
-
- libgstreamer0.10-ruby
|
114
165
|
- libgnome2-ruby
|
115
166
|
- libsqlite3-ruby
|
116
167
|
- libglade2-ruby
|
168
|
+
- ruby-dev
|
169
|
+
- libopenssl-ruby
|
170
|
+
- libmp3lame-dev
|
171
|
+
- libvorbis-dev
|
172
|
+
- make
|
117
173
|
rubyforge_project: shroom
|
118
|
-
rubygems_version: 1.3.
|
174
|
+
rubygems_version: 1.3.1
|
119
175
|
signing_key:
|
120
|
-
specification_version:
|
176
|
+
specification_version: 2
|
121
177
|
summary: Shroom is a music player and organizer
|
122
178
|
test_files: []
|
123
179
|
|