diff --git a/supysonic/db.py b/supysonic/db.py index 25ea318..01076c4 100644 --- a/supysonic/db.py +++ b/supysonic/db.py @@ -31,7 +31,7 @@ try: except ImportError: from urlparse import urlparse, parse_qsl -SCHEMA_VERSION = '20180829' +SCHEMA_VERSION = '20181010' def now(): return datetime.now().replace(microsecond = 0) @@ -105,7 +105,7 @@ class Folder(PathMixin, db.Entity): info['coverArt'] = str(self.id) else: for track in self.tracks: - if track.extract_cover_art(): + if track.has_art: info['coverArt'] = str(track.id) break @@ -190,6 +190,10 @@ class Album(db.Entity): track_with_cover = self.tracks.select(lambda t: t.folder.cover_art is not None).first() if track_with_cover is not None: info['coverArt'] = str(track_with_cover.folder.id) + else: + track_with_cover = self.tracks.select(lambda t: t.has_art).first() + if track_with_cover is not None: + info['coverArt'] = str(track_with_cover.id) try: starred = StarredAlbum[user.id, self.id] @@ -216,6 +220,7 @@ class Track(PathMixin, db.Entity): year = Optional(int) genre = Optional(str, nullable = True) duration = Required(int) + has_art = Required(bool, default=False) album = Required(Album, column = 'album_id') artist = Required(Artist, column = 'artist_id') @@ -266,7 +271,7 @@ class Track(PathMixin, db.Entity): info['year'] = self.year if self.genre: info['genre'] = self.genre - if self.extract_cover_art(): + if self.has_art: info['coverArt'] = str(self.id) elif self.folder.cover_art: info['coverArt'] = str(self.folder.id) @@ -304,8 +309,12 @@ class Track(PathMixin, db.Entity): return (self.album.artist.name + self.album.name + ("%02i" % self.disc) + ("%02i" % self.number) + self.title).lower() def extract_cover_art(self): - if os.path.exists(self.path): - metadata = mutagen.File(self.path) + return Track._extract_cover_art(self.path) + + @staticmethod + def _extract_cover_art(path): + if os.path.exists(path): + metadata = mutagen.File(path) if metadata: if isinstance(metadata.tags, mutagen.id3.ID3Tags) and len(metadata.tags.getall('APIC')) > 0: return metadata.tags.getall('APIC')[0].data diff --git a/supysonic/scanner.py b/supysonic/scanner.py index 99b5527..ae5061b 100644 --- a/supysonic/scanner.py +++ b/supysonic/scanner.py @@ -140,6 +140,7 @@ class Scanner: trdict['year'] = self.__try_read_tag(tag, 'date', None, lambda x: int(x[0].split('-')[0])) trdict['genre'] = self.__try_read_tag(tag, 'genre') trdict['duration'] = int(tag.info.length) + trdict['has_art'] = bool(Track._extract_cover_art(path)) trdict['bitrate'] = (tag.info.bitrate if hasattr(tag.info, 'bitrate') else int(os.path.getsize(path) * 8 / tag.info.length)) // 1000 trdict['content_type'] = mimetypes.guess_type(path, False)[0] or 'application/octet-stream' diff --git a/supysonic/schema/migration/mysql/20181010.sql b/supysonic/schema/migration/mysql/20181010.sql new file mode 100644 index 0000000..df807de --- /dev/null +++ b/supysonic/schema/migration/mysql/20181010.sql @@ -0,0 +1,6 @@ +START TRANSACTION; + +ALTER TABLE track ADD has_art BOOLEAN DEFAULT false NOT NULL; + +COMMIT; + diff --git a/supysonic/schema/migration/postgres/20181010.sql b/supysonic/schema/migration/postgres/20181010.sql new file mode 100644 index 0000000..df807de --- /dev/null +++ b/supysonic/schema/migration/postgres/20181010.sql @@ -0,0 +1,6 @@ +START TRANSACTION; + +ALTER TABLE track ADD has_art BOOLEAN DEFAULT false NOT NULL; + +COMMIT; + diff --git a/supysonic/schema/migration/sqlite/20181010.sql b/supysonic/schema/migration/sqlite/20181010.sql new file mode 100644 index 0000000..f4013c9 --- /dev/null +++ b/supysonic/schema/migration/sqlite/20181010.sql @@ -0,0 +1,3 @@ +ALTER TABLE track ADD has_art BOOLEAN DEFAULT false NOT NULL; + +COMMIT; diff --git a/supysonic/schema/mysql.sql b/supysonic/schema/mysql.sql index 9787df2..5c48f03 100644 --- a/supysonic/schema/mysql.sql +++ b/supysonic/schema/mysql.sql @@ -29,6 +29,7 @@ CREATE TABLE IF NOT EXISTS track ( year INTEGER, genre VARCHAR(256), duration INTEGER NOT NULL, + has_art BOOLEAN NOT NULL DEFAULT false, album_id BINARY(16) NOT NULL REFERENCES album, artist_id BINARY(16) NOT NULL REFERENCES artist, bitrate INTEGER NOT NULL, diff --git a/supysonic/schema/postgres.sql b/supysonic/schema/postgres.sql index 9339f55..66cbb60 100644 --- a/supysonic/schema/postgres.sql +++ b/supysonic/schema/postgres.sql @@ -29,6 +29,7 @@ CREATE TABLE IF NOT EXISTS track ( year INTEGER, genre VARCHAR(256), duration INTEGER NOT NULL, + has_art BOOLEAN NOT NULL DEFAULT false, album_id UUID NOT NULL REFERENCES album, artist_id UUID NOT NULL REFERENCES artist, bitrate INTEGER NOT NULL, diff --git a/supysonic/schema/sqlite.sql b/supysonic/schema/sqlite.sql index bf8e567..169ab58 100644 --- a/supysonic/schema/sqlite.sql +++ b/supysonic/schema/sqlite.sql @@ -31,6 +31,7 @@ CREATE TABLE IF NOT EXISTS track ( year INTEGER, genre VARCHAR(256), duration INTEGER NOT NULL, + has_art BOOLEAN NOT NULL DEFAULT false, album_id CHAR(36) NOT NULL REFERENCES album, artist_id CHAR(36) NOT NULL REFERENCES artist, bitrate INTEGER NOT NULL, diff --git a/tests/base/test_db.py b/tests/base/test_db.py index 8365196..e99c7a5 100644 --- a/tests/base/test_db.py +++ b/tests/base/test_db.py @@ -71,6 +71,7 @@ class DbTestCase(unittest.TestCase): disc = 1, number = 1, duration = 3, + has_art = True, bitrate = 320, path = 'tests/assets/formats/silence.ogg', content_type = 'audio/ogg', @@ -96,9 +97,9 @@ class DbTestCase(unittest.TestCase): return track1, track2 - def create_track_in(self, folder, root): - artist = db.Artist(name = 'Snazzy Artist') - album = db.Album(artist = artist, name = 'Rockin\' Album') + def create_track_in(self, folder, root, artist = None, album = None, has_art = True): + artist = artist or db.Artist(name = 'Snazzy Artist') + album = album or db.Album(artist = artist, name = 'Rockin\' Album') return db.Track( title = 'Nifty Number', album = album, @@ -106,6 +107,7 @@ class DbTestCase(unittest.TestCase): disc = 1, number = 1, duration = 5, + has_art = has_art, bitrate = 96, path = 'tests/assets/formats/silence.flac', content_type = 'audio/flac', @@ -232,7 +234,8 @@ class DbTestCase(unittest.TestCase): # No tracks, shouldn't be stored under normal circumstances self.assertRaises(ValueError, album.as_subsonic_album, user) - self.create_some_tracks(artist, album) + root_folder, folder_art, folder_noart = self.create_some_folders() + track1 = self.create_track_in(root_folder, folder_noart, artist = artist, album = album) album_dict = album.as_subsonic_album(user) self.assertIsInstance(album_dict, dict) @@ -244,11 +247,13 @@ class DbTestCase(unittest.TestCase): self.assertIn('duration', album_dict) self.assertIn('created', album_dict) self.assertIn('starred', album_dict) + self.assertIn('coverArt', album_dict) self.assertEqual(album_dict['name'], album.name) self.assertEqual(album_dict['artist'], artist.name) self.assertEqual(album_dict['artistId'], str(artist.id)) - self.assertEqual(album_dict['songCount'], 2) - self.assertEqual(album_dict['duration'], 8) + self.assertEqual(album_dict['songCount'], 1) + self.assertEqual(album_dict['duration'], 5) + self.assertEqual(album_dict['coverArt'], str(track1.id)) self.assertRegex(album_dict['created'], date_regex) self.assertRegex(album_dict['starred'], date_regex)