1
0
mirror of https://github.com/spl0k/supysonic.git synced 2024-12-23 01:16:18 +00:00

Art extraction is now done as a method of Track; cover_art() only extracts cover art if given a track ID; Cache embeded art

This commit is contained in:
Taizo Simpson 2018-10-08 20:05:45 -04:00
parent 7106d95cee
commit 4bbcbd0acf
No known key found for this signature in database
GPG Key ID: D197B1197B2D4D68
3 changed files with 47 additions and 34 deletions

View File

@ -7,15 +7,12 @@
# #
# Distributed under terms of the GNU AGPLv3 license. # Distributed under terms of the GNU AGPLv3 license.
import base64
import codecs import codecs
import mimetypes import mimetypes
import mutagen
import os.path import os.path
import requests import requests
import shlex import shlex
import subprocess import subprocess
import tempfile
from flask import request, Response, send_file from flask import request, Response, send_file
from flask import current_app from flask import current_app
@ -134,27 +131,27 @@ def download_media():
@api.route('/getCoverArt.view', methods = [ 'GET', 'POST' ]) @api.route('/getCoverArt.view', methods = [ 'GET', 'POST' ])
def cover_art(): def cover_art():
res = get_entity(Folder) eid = request.values['id']
if not res.cover_art or not os.path.isfile(os.path.join(res.path, res.cover_art)): if Folder.exists(id=eid):
# Check for embeded metadata in songs res = get_entity(Folder)
temp_cover = tempfile.NamedTemporaryFile() if not res.cover_art or not os.path.isfile(os.path.join(res.path, res.cover_art)):
cover_path = temp_cover.name
for track in res.tracks:
song = mutagen.File(track.path)
if isinstance(song.tags, mutagen.id3.ID3Tags) and len(song.tags.getall('APIC')) > 0:
temp_cover.write(song.tags.getall('APIC')[0].data)
break
elif isinstance(song, mutagen.flac.FLAC) and len(song.pictures):
temp_cover.write(song.pictures[0].data)
break
elif isinstance(song.tags, mutagen._vorbis.VCommentDict) and 'METADATA_BLOCK_PICTURE' in song.tags and len(song.tags['METADATA_BLOCK_PICTURE']) > 0:
picture = mutagen.flac.Picture(base64.b64decode(song.tags['METADATA_BLOCK_PICTURE'][0]))
temp_cover.write(picture.data)
break
else:
raise NotFound('Cover art') raise NotFound('Cover art')
else:
cover_path = os.path.join(res.path, res.cover_art) cover_path = os.path.join(res.path, res.cover_art)
elif Track.exists(id=eid):
embed_cache = os.path.join(current_app.config['WEBAPP']['cache_dir'], 'embeded_art')
cover_path = os.path.join(embed_cache, eid)
if not os.path.exists(cover_path):
res = get_entity(Track)
art = res.extract_cover_art()
if not art:
raise NotFound('Cover art')
#Art found, save to cache
os.makedirs(embed_cache, exist_ok=True)
with open(cover_path, 'wb') as cover_file:
cover_file.write(art)
else:
raise NotFound('Entity')
size = request.values.get('size') size = request.values.get('size')
if size: if size:
size = int(size) size = int(size)
@ -234,4 +231,3 @@ def read_file_as_unicode(path):
# Fallback to ASCII # Fallback to ASCII
current_app.logger.debug('Reading file {} with ascii encoding'.format(path)) current_app.logger.debug('Reading file {} with ascii encoding'.format(path))
return unicode(open(path, 'r').read()) return unicode(open(path, 'r').read())

View File

@ -7,8 +7,10 @@
# #
# Distributed under terms of the GNU AGPLv3 license. # Distributed under terms of the GNU AGPLv3 license.
import base64
import importlib import importlib
import mimetypes import mimetypes
import mutagen
import os.path import os.path
import pkg_resources import pkg_resources
import time import time
@ -101,6 +103,11 @@ class Folder(PathMixin, db.Entity):
info['artist'] = self.parent.name info['artist'] = self.parent.name
if self.cover_art: if self.cover_art:
info['coverArt'] = str(self.id) info['coverArt'] = str(self.id)
else:
for track in self.tracks:
if track.extract_cover_art():
info['coverArt'] = str(track.id)
break
try: try:
starred = StarredFolder[user.id, self.id] starred = StarredFolder[user.id, self.id]
@ -259,7 +266,9 @@ class Track(PathMixin, db.Entity):
info['year'] = self.year info['year'] = self.year
if self.genre: if self.genre:
info['genre'] = self.genre info['genre'] = self.genre
if self.folder.cover_art: if self.extract_cover_art():
info['coverArt'] = str(self.id)
elif self.folder.cover_art:
info['coverArt'] = str(self.folder.id) info['coverArt'] = str(self.folder.id)
try: try:
@ -294,6 +303,20 @@ class Track(PathMixin, db.Entity):
def sort_key(self): def sort_key(self):
return (self.album.artist.name + self.album.name + ("%02i" % self.disc) + ("%02i" % self.number) + self.title).lower() 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)
data = None
if metadata:
if isinstance(metadata.tags, mutagen.id3.ID3Tags) and len(metadata.tags.getall('APIC')) > 0:
return metadata.tags.getall('APIC')[0].data
elif isinstance(metadata, mutagen.flac.FLAC) and len(metadata.pictures):
return metadata.pictures[0].data
elif isinstance(metadata.tags, mutagen._vorbis.VCommentDict) and 'METADATA_BLOCK_PICTURE' in metadata.tags and len(metadata.tags['METADATA_BLOCK_PICTURE']) > 0:
picture = mutagen.flac.Picture(base64.b64decode(metadata.tags['METADATA_BLOCK_PICTURE'][0]))
return picture.data
return None
class User(db.Entity): class User(db.Entity):
_table_ = 'user' _table_ = 'user'

View File

@ -32,13 +32,6 @@ class MediaTestCase(ApiTestBase):
) )
self.folderid = folder.id self.folderid = folder.id
folder_embeded_art = Folder(
name = 'Root',
path = os.path.abspath('tests/assets/folder'),
root = True,
)
self.folderid_embeded = folder_embeded_art.id
artist = Artist(name = 'Artist') artist = Artist(name = 'Artist')
album = Album(artist = artist, name = 'Album') album = Album(artist = artist, name = 'Album')
@ -64,13 +57,14 @@ class MediaTestCase(ApiTestBase):
artist = artist, artist = artist,
album = album, album = album,
path = os.path.abspath('tests/assets/folder/silence.mp3'), path = os.path.abspath('tests/assets/folder/silence.mp3'),
root_folder = folder_embeded_art, root_folder = folder,
folder = folder_embeded_art, folder = folder,
duration = 2, duration = 2,
bitrate = 320, bitrate = 320,
content_type = 'audio/mpeg', content_type = 'audio/mpeg',
last_modification = 0 last_modification = 0
) )
self.trackid_embeded_art = track_embeded_art.id
def test_stream(self): def test_stream(self):
self._make_request('stream', error = 10) self._make_request('stream', error = 10)
@ -143,7 +137,7 @@ class MediaTestCase(ApiTestBase):
# TODO test non square covers # TODO test non square covers
# Test extracting cover art from embeded media # Test extracting cover art from embeded media
args['id'] = str(self.folderid_embeded) args['id'] = str(self.trackid_embeded_art)
rv = self.client.get('/rest/getCoverArt.view', query_string = args) rv = self.client.get('/rest/getCoverArt.view', query_string = args)
self.assertEqual(rv.status_code, 200) self.assertEqual(rv.status_code, 200)
self.assertEqual(rv.mimetype, 'image/png') self.assertEqual(rv.mimetype, 'image/png')