shroom 0.0.10 → 0.0.11
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 +626 -629
- data/README +25 -32
- data/Rakefile +2 -4
- data/lib/sh_actions.rb +25 -19
- data/lib/sh_browse.rb +84 -54
- data/lib/sh_cover_art.rb +3 -6
- data/lib/sh_cover_browse.rb +10 -6
- data/lib/sh_database.rb +149 -160
- data/lib/sh_fingerprint.rb +64 -0
- data/lib/sh_main.rb +13 -11
- data/lib/sh_playlist.rb +15 -11
- data/lib/sh_util.rb +0 -17
- data/lib/sh_view.rb +45 -33
- data/lib/shroom-res/cover_unavailable.png +0 -0
- data/lib/shroom-res/icon_128x128.png +0 -0
- data/lib/shroom-res/plugins/lastfm_scrobbler.rb +32 -30
- data/lib/shroom-res/shroom.glade +13 -1
- metadata +4 -19
- data/lib/sh_album.rb +0 -14
- data/lib/sh_artist.rb +0 -14
- data/lib/sh_song.rb +0 -135
- data/lib/shroom-res/cover_unavailable.svg +0 -152
- data/lib/shroom-res/icon.svg +0 -206
data/lib/sh_database.rb
CHANGED
@@ -1,207 +1,196 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'sequel'
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require 'sh_artist'
|
3
|
+
require 'sh_tagreader'
|
4
|
+
require 'sh_fingerprint'
|
6
5
|
|
7
6
|
module Sh
|
8
7
|
class Database
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
8
|
+
def self.load
|
9
|
+
@@db = Sequel.sqlite(Global::PATHS[:db_file])
|
10
|
+
|
11
|
+
unless @@db.table_exists? :artists
|
12
|
+
@@db.create_table :artists do
|
13
|
+
primary_key :id
|
14
|
+
String :mbid
|
15
|
+
String :name
|
16
|
+
String :image_path
|
17
|
+
String :info
|
18
|
+
end
|
19
|
+
@@db[:artists].insert(:id => 1, :name => "Unknown Artist")
|
20
|
+
@@db[:artists].insert(:id => 2, :name => "Various Artists")
|
21
|
+
end
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
unless @@db.table_exists? :albums
|
24
|
+
@@db.create_table :albums do
|
25
|
+
primary_key :id
|
26
|
+
String :mbid
|
27
|
+
String :title
|
28
|
+
String :image_path
|
29
|
+
String :info
|
30
|
+
Integer :year
|
31
|
+
foreign_key :artist_id, :artists
|
32
|
+
end
|
33
|
+
@@db[:albums].insert(:id => 1, :title => "Unknown Album")
|
34
|
+
@@db[:albums].insert(:id => 2, :title => "Non-Album Tracks")
|
35
|
+
end
|
28
36
|
|
29
|
-
|
37
|
+
@@db.create_table :songs do
|
30
38
|
primary_key :id
|
31
39
|
String :mbid
|
32
40
|
String :path
|
33
41
|
String :title
|
34
42
|
String :lyrics
|
35
43
|
String :image_path
|
36
|
-
String :
|
44
|
+
String :info
|
45
|
+
Integer :track_num
|
37
46
|
foreign_key :album_id, :albums
|
38
47
|
foreign_key :artist_id, :artists
|
39
|
-
end unless
|
48
|
+
end unless @@db.table_exists? :songs
|
40
49
|
end
|
41
50
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
}
|
52
|
-
|
53
|
-
# Insert/update artist in database
|
54
|
-
artist_id = nil
|
55
|
-
row_artist = @db[:artists][:name => artist.name]
|
56
|
-
if row_artist
|
57
|
-
artist_hash.each do |k, v|
|
58
|
-
if row_artist[k] != v
|
59
|
-
@db[:artists].filter(:name => artist.name).update(k => v)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
artist_id = row_artist[:id]
|
63
|
-
else
|
64
|
-
artist_id = @db[:artists].insert(artist_hash)
|
65
|
-
end
|
66
|
-
artist.db_id = artist_id
|
67
|
-
|
68
|
-
# Get album info
|
69
|
-
album = song.album
|
70
|
-
album_hash = {
|
71
|
-
:mbid => album.mbid,
|
72
|
-
:title => album.title,
|
73
|
-
:image_path => album.image_path,
|
74
|
-
:date => album.date,
|
75
|
-
:info => album.info,
|
76
|
-
}
|
77
|
-
|
78
|
-
# Insert/update artist in database
|
79
|
-
album_id = nil
|
80
|
-
row_album = @db[:albums][:title => album.title]
|
81
|
-
if row_album
|
82
|
-
album_hash.each do |k, v|
|
83
|
-
if row_album[k] != v
|
84
|
-
@db[:albums].filter(:title => album.title).update(k => v)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
album_id = row_album[:id]
|
51
|
+
def self.get_or_insert_artist query
|
52
|
+
name = query[:name]
|
53
|
+
artist = Artist[Artist::UNKNOWN]
|
54
|
+
unless name.nil?
|
55
|
+
artist = case name.downcase
|
56
|
+
when "", "unknown", "unknown artist"
|
57
|
+
Artist[Artist::UNKNOWN]
|
58
|
+
when "various", "various artists"
|
59
|
+
Artist[Artist::VARIOUS]
|
88
60
|
else
|
89
|
-
|
61
|
+
Artist.first(query) || Artist.create(query)
|
90
62
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
row_song = @db[:songs][:path => song.path]
|
105
|
-
if row_song
|
106
|
-
song_hash.each do |k, v|
|
107
|
-
if row_song[k] != v
|
108
|
-
@db[:songs].filter(:path => song.path).update(k => v)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
song_id = row_song[:id]
|
63
|
+
end
|
64
|
+
return artist
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.get_or_insert_album query
|
68
|
+
title = query[:title]
|
69
|
+
album = Album[Album::UNKNOWN]
|
70
|
+
unless title.nil?
|
71
|
+
album = case title.downcase
|
72
|
+
when "", "unknown", "unknown album"
|
73
|
+
Album[Artist::UNKNOWN]
|
74
|
+
when "[non-album tracks]", "non-album", "non-album tracks"
|
75
|
+
Album[Album::NON_ALBUM]
|
112
76
|
else
|
113
|
-
|
114
|
-
row_song = @db[:songs][:id => song_id]
|
77
|
+
Album.first(query) || Album.create(query)
|
115
78
|
end
|
116
|
-
song.db_id = song_id
|
117
|
-
|
118
|
-
return row_song
|
119
|
-
rescue
|
120
|
-
Sh::Log.warning "Couldn't save #{song.path} to database", $!
|
121
|
-
return
|
122
79
|
end
|
80
|
+
return album
|
123
81
|
end
|
124
82
|
|
125
|
-
def
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
83
|
+
def self.add_song path
|
84
|
+
tags = TagReader.read path
|
85
|
+
|
86
|
+
artist = get_or_insert_artist(:name => tags[:artist])
|
87
|
+
album = get_or_insert_album(:title => tags[:album], :artist_id => artist.id)
|
88
|
+
|
89
|
+
unless album.special_case?
|
90
|
+
album.year = tags[:year]
|
91
|
+
album.save
|
132
92
|
end
|
133
|
-
return artists
|
134
|
-
end
|
135
93
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
albums << hash_to_album(hash)
|
94
|
+
song = Song.create do |s|
|
95
|
+
s.path = path
|
96
|
+
s.artist = artist
|
97
|
+
s.album = album
|
98
|
+
s.title = tags[:title]
|
99
|
+
s.track_num = tags[:track_num]
|
143
100
|
end
|
144
|
-
|
101
|
+
|
102
|
+
return song
|
145
103
|
end
|
104
|
+
end
|
146
105
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
106
|
+
Database.load
|
107
|
+
|
108
|
+
class Artist < Sequel::Model
|
109
|
+
UNKNOWN = 1
|
110
|
+
VARIOUS = 2
|
111
|
+
|
112
|
+
one_to_many :albums
|
113
|
+
one_to_many :songs
|
114
|
+
|
115
|
+
def special_case?
|
116
|
+
return [UNKNOWN, VARIOUS].include?(id)
|
156
117
|
end
|
157
118
|
|
158
|
-
def
|
159
|
-
|
119
|
+
def to_s
|
120
|
+
return name
|
160
121
|
end
|
161
122
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
Sh::Log.debug "Removing #{path} from database"
|
167
|
-
@db[:songs].filter(:path => path).delete
|
123
|
+
def_dataset_method :clean_empty_artists do
|
124
|
+
Artist.each do |a|
|
125
|
+
unless a.special_case?
|
126
|
+
a.delete if a.albums.empty? and a.songs.empty?
|
168
127
|
end
|
169
128
|
end
|
170
129
|
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Album < Sequel::Model
|
133
|
+
UNKNOWN = 1
|
134
|
+
NON_ALBUM = 2
|
135
|
+
|
136
|
+
many_to_one :artist
|
137
|
+
one_to_many :songs
|
138
|
+
|
139
|
+
def special_case?
|
140
|
+
return [UNKNOWN, NON_ALBUM].include?(id)
|
141
|
+
end
|
142
|
+
|
143
|
+
def to_s
|
144
|
+
return title
|
145
|
+
end
|
171
146
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
song.send assign, v if song.respond_to? assign
|
147
|
+
def_dataset_method :clean_empty_albums do
|
148
|
+
Album.each do |a|
|
149
|
+
unless a.special_case?
|
150
|
+
a.delete if a.songs.empty?
|
151
|
+
end
|
178
152
|
end
|
179
|
-
song.db_id = hash[:id]
|
180
|
-
song.album = hash_to_album(@db[:albums][:id => hash[:album_id]])
|
181
|
-
song.artist = hash_to_artist(@db[:artists][:id => hash[:artist_id]])
|
182
|
-
return song
|
183
153
|
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class Song < Sequel::Model
|
157
|
+
many_to_one :artist
|
158
|
+
many_to_one :album
|
184
159
|
|
185
|
-
def
|
186
|
-
album
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
160
|
+
def to_s
|
161
|
+
return "%s, %s, %.2d - %s" % [artist, album, track_num, title]
|
162
|
+
end
|
163
|
+
|
164
|
+
def to_html
|
165
|
+
t = title
|
166
|
+
t = "Unknown" if not t or t.strip == ""
|
167
|
+
t = CGI.escapeHTML t
|
168
|
+
ar = artist.name
|
169
|
+
ar = "Unknown" if not ar or ar.strip == ""
|
170
|
+
ar = CGI.escapeHTML ar
|
171
|
+
al = album.title
|
172
|
+
al = "Unknown" if not al or al.strip == ""
|
173
|
+
al = CGI.escapeHTML al
|
174
|
+
return "<b>#{t}</b> by <i>#{ar}</i> from <i>#{al}</i>"
|
175
|
+
end
|
176
|
+
|
177
|
+
def lookup!
|
178
|
+
begin
|
179
|
+
return Fingerprint.identify! self
|
180
|
+
rescue Exception
|
181
|
+
Log.debug "Couldn't identify song from fingerprint", $!
|
182
|
+
return false
|
191
183
|
end
|
192
|
-
album.db_id = hash[:id]
|
193
|
-
return album
|
194
184
|
end
|
195
185
|
|
196
|
-
def
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
186
|
+
def validate
|
187
|
+
errors[:path] << "must exist" unless path and File.exists?(path)
|
188
|
+
end
|
189
|
+
|
190
|
+
def_dataset_method :clean_missing_songs do
|
191
|
+
Song.each do |s|
|
192
|
+
s.delete unless File.exists?(s.path)
|
202
193
|
end
|
203
|
-
artist.db_id = hash[:id]
|
204
|
-
return artist
|
205
194
|
end
|
206
195
|
end
|
207
196
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
try_require 'earworm'
|
2
|
+
try_require 'rbrainz'
|
3
|
+
|
4
|
+
module Sh
|
5
|
+
class Fingerprint
|
6
|
+
def self.identify! song
|
7
|
+
Log.debug "Earworming #{song.path}"
|
8
|
+
ew = Earworm::Client.new(KEYS[:music_dns])
|
9
|
+
info = ew.identify :file => song.path
|
10
|
+
title = unescape_html info.title
|
11
|
+
artist_name = unescape_html info.artist_name
|
12
|
+
puids = info.puid_list || []
|
13
|
+
return false unless title and artist_name
|
14
|
+
|
15
|
+
query = MusicBrainz::Webservice::Query.new
|
16
|
+
tracks = query.get_tracks :title => title, :artist => artist_name, :puid => puids.first
|
17
|
+
track = tracks.first.entity
|
18
|
+
# Sort track matches with same score to find most appropriate one
|
19
|
+
all_matches = tracks.reject {|t| t.score < tracks.first.score}
|
20
|
+
all_matches.each do |entry|
|
21
|
+
t = entry.entity
|
22
|
+
rel = t.releases.first
|
23
|
+
if Album.first(:title => rel.title)
|
24
|
+
track = t
|
25
|
+
break
|
26
|
+
elsif rel.types.include? MusicBrainz::Model::Release::TYPE_ALBUM
|
27
|
+
track = t
|
28
|
+
end
|
29
|
+
end
|
30
|
+
artist = track.artist
|
31
|
+
release = track.releases.first
|
32
|
+
album_artist = query.get_release_by_id(release.id.uuid, :artist => true).artist
|
33
|
+
track_num = release.tracks.offset + 1
|
34
|
+
|
35
|
+
song.title = title
|
36
|
+
song.track_num = track_num
|
37
|
+
song.album = Database.get_or_insert_album(:title => release.title)
|
38
|
+
song.album.mbid = release.id.uuid
|
39
|
+
song.album.artist = Database.get_or_insert_artist(:name => album_artist.name)
|
40
|
+
song.album.artist.mbid = album_artist.id.uuid
|
41
|
+
song.album.artist.save
|
42
|
+
song.album.save
|
43
|
+
song.artist = Database.get_or_insert_artist(:name => artist.name)
|
44
|
+
song.artist.mbid = artist.id.uuid
|
45
|
+
song.artist.save
|
46
|
+
song.save
|
47
|
+
|
48
|
+
return true
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.unescape_html str
|
52
|
+
entities = {
|
53
|
+
""" => "\"",
|
54
|
+
"'" => "'",
|
55
|
+
"&" => "&",
|
56
|
+
"<" => "<",
|
57
|
+
">" => ">",
|
58
|
+
" " => " "
|
59
|
+
}
|
60
|
+
|
61
|
+
return CGI.unescapeHTML(str.gsub(/&\w*;/) {|e| entities[e] || e})
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/sh_main.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
|
+
# Problems occur when 'socket' is not required first
|
3
4
|
require 'socket'
|
5
|
+
require 'resolv-replace'
|
4
6
|
require 'rubygems'
|
5
7
|
|
6
8
|
require 'sh_util'
|
9
|
+
|
10
|
+
# Try to require 'breakpoint' (for debugging purposes)
|
11
|
+
try_require 'breakpoint'
|
12
|
+
|
7
13
|
require 'sh_log'
|
8
14
|
require 'sh_global'
|
9
|
-
require 'sh_song'
|
10
15
|
require 'sh_database'
|
11
16
|
require 'sh_player'
|
12
17
|
require 'sh_view'
|
@@ -14,22 +19,19 @@ require 'sh_view'
|
|
14
19
|
module Sh
|
15
20
|
class Main
|
16
21
|
def initialize
|
17
|
-
|
18
|
-
|
19
|
-
# Load the database
|
20
|
-
$db = Sh::Database.new
|
22
|
+
Log.info 'Shroom started'
|
21
23
|
|
22
24
|
# See sh_player.rb for why this Wx stuff is necessary
|
23
25
|
Wx::App.run do
|
24
|
-
|
26
|
+
View.new.show
|
25
27
|
end
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
|
29
|
+
Log.debug 'Saving preferences...'
|
30
|
+
Global.save_prefs
|
31
|
+
Log.debug 'Global preferences saved'
|
30
32
|
Plugin.save_prefs
|
31
|
-
|
32
|
-
|
33
|
+
Log.debug 'Plugin preferences saved'
|
34
|
+
Log.info 'Shroom quit'
|
33
35
|
end
|
34
36
|
end
|
35
37
|
end
|