shroom 0.0.10 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -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