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.
@@ -1,207 +1,196 @@
1
1
  require 'rubygems'
2
2
  require 'sequel'
3
- require 'sh_song'
4
- require 'sh_album'
5
- require 'sh_artist'
3
+ require 'sh_tagreader'
4
+ require 'sh_fingerprint'
6
5
 
7
6
  module Sh
8
7
  class Database
9
- def initialize
10
- @db = Sequel.sqlite(Sh::Global::PATHS[:db_file])
11
-
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 unless @db.table_exists? :artists
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
- @db.create_table :albums do
21
- primary_key :id
22
- String :mbid
23
- String :title
24
- String :image_path
25
- String :info
26
- String :date
27
- end unless @db.table_exists? :albums
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
- @db.create_table :songs do
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 :track_num
44
+ String :info
45
+ Integer :track_num
37
46
  foreign_key :album_id, :albums
38
47
  foreign_key :artist_id, :artists
39
- end unless @db.table_exists? :songs
48
+ end unless @@db.table_exists? :songs
40
49
  end
41
50
 
42
- def save_song song
43
- begin
44
- # Get artist info
45
- artist = song.artist
46
- artist_hash = {
47
- :mbid => artist.mbid,
48
- :name => artist.name,
49
- :image_path => artist.image_path,
50
- :info => artist.info
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
- album_id = @db[:albums].insert(album_hash)
61
+ Artist.first(query) || Artist.create(query)
90
62
  end
91
- album.db_id = album_id
92
-
93
- song_hash = {
94
- :path => song.path,
95
- :mbid => song.mbid,
96
- :title => song.title,
97
- :lyrics => song.lyrics,
98
- :track_num => song.track_num,
99
- :album_id => album_id,
100
- :artist_id => artist_id
101
- }
102
-
103
- song_id = nil
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
- song_id = @db[:songs].insert(song_hash)
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 artists(params={})
126
- artists = []
127
- params ||= {}
128
- rows = @db[:artists]
129
- rows = rows.filter(params) unless params == {}
130
- rows.each do |hash|
131
- artists << hash_to_artist(hash)
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
- def albums(params={})
137
- albums = []
138
- params ||= {}
139
- rows = @db[:albums]
140
- rows = rows.filter(params) unless params == {}
141
- rows.each do |hash|
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
- return albums
101
+
102
+ return song
145
103
  end
104
+ end
146
105
 
147
- def songs(params={})
148
- songs = []
149
- params ||= {}
150
- rows = @db[:songs]
151
- rows = rows.filter(params) unless params == {}
152
- rows.each do |hash|
153
- songs << hash_to_song(hash)
154
- end
155
- return songs
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 contains? path
159
- @db[:songs][:path => path]
119
+ def to_s
120
+ return name
160
121
  end
161
122
 
162
- def clean
163
- @db[:songs].each do |hash|
164
- path = hash[:path]
165
- unless File.exists? path
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
- private
173
- def hash_to_song hash
174
- song = Sh::Song.new hash[:path]
175
- hash.each do |k, v|
176
- assign = (k.to_s + '=').to_sym
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 hash_to_album hash
186
- album = Sh::Album.new
187
- return album unless hash
188
- hash.each do |k, v|
189
- assign = (k.to_s + '=').to_sym
190
- album.send assign, v if album.respond_to? assign
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 hash_to_artist hash
197
- artist = Sh::Artist.new
198
- return artist unless hash
199
- hash.each do |k, v|
200
- assign = (k.to_s + '=').to_sym
201
- artist.send assign, v if artist.respond_to? assign
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
+ "&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
+ end
64
+ end
@@ -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
- Sh::Log.info 'Shroom started'
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
- Sh::View.new.show
26
+ View.new.show
25
27
  end
26
28
 
27
- Sh::Log.debug 'Saving preferences...'
28
- Sh::Global.save_prefs
29
- Sh::Log.debug 'Global preferences saved'
29
+ Log.debug 'Saving preferences...'
30
+ Global.save_prefs
31
+ Log.debug 'Global preferences saved'
30
32
  Plugin.save_prefs
31
- Sh::Log.debug 'Plugin preferences saved'
32
- Sh::Log.info 'Shroom quit'
33
+ Log.debug 'Plugin preferences saved'
34
+ Log.info 'Shroom quit'
33
35
  end
34
36
  end
35
37
  end