Make backend more modular
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Simon Bruder 2019-12-31 15:17:37 +00:00
parent cfe77a82c1
commit 52debb8b89
No known key found for this signature in database
GPG key ID: 6F03E0000CC5B62F
2 changed files with 79 additions and 57 deletions

View file

@ -8,6 +8,10 @@ import os.path
import sqlite3 import sqlite3
import werkzeug.exceptions as exceptions import werkzeug.exceptions as exceptions
DEFAULT_WEBP_QUALITY = 80
DEFAULT_WEBP_METHOD = 0
DEFAULT_WEBP_SIZE = 1906 # width of FullHD monitor without scroll bar
mimetypes['.webp'] = 'image/webp' mimetypes['.webp'] = 'image/webp'
if os.environ.get('DISK_CACHE', '1') == '0': if os.environ.get('DISK_CACHE', '1') == '0':
@ -25,35 +29,8 @@ def dict_factory(cursor, row):
return d return d
def generate_thumbnail(filepath): class BaseDB:
if filepath not in thumbnail_cache: def __init__(self, webp_quality, webp_method, webp_size):
image = Image.open(filepath)
image.thumbnail((512, 512))
thumbnail = BytesIO()
image.save(thumbnail, 'webp')
thumbnail.seek(0)
data = thumbnail.read()
etag = str(crc32(data))
thumbnail_cache[filepath] = {
'data_raw': data,
'etag': etag,
'mimetype': 'image/webp'
}
thumbnail = thumbnail_cache[filepath]
thumbnail['data'] = BytesIO()
thumbnail['data'].write(thumbnail['data_raw'])
thumbnail['data'].seek(0)
return thumbnail
class CalibreDB:
def __init__(self, path='metadata.db', webp_quality=80, webp_method=0, webp_size=2048):
self.database_path = f'file:{path}?mode=ro'
# lossy: 0-100 (used as quality) # lossy: 0-100 (used as quality)
# lossless: 101-201 (101 subtracted and used as quality) # lossless: 101-201 (101 subtracted and used as quality)
if webp_quality > 100: if webp_quality > 100:
@ -69,6 +46,65 @@ class CalibreDB:
'size': webp_size 'size': webp_size
} }
def _generate_webp(self, fp, max_size=None):
if max_size is None:
max_size = tuple([self.webp_config['size']] * 2)
image = Image.open(fp)
image.thumbnail(max_size)
image_buffer = BytesIO()
image.save(
image_buffer,
format='webp',
save_all=True, #
append_images=[image], # https://github.com/python-pillow/Pillow/issues/4042
quality=self.webp_config['quality'],
method=self.webp_config['method'],
lossless=self.webp_config['lossless']
)
image_buffer.seek(0)
return image_buffer
def _generate_thumbnail(self, filepath):
if filepath not in thumbnail_cache:
thumbnail_buffer = self._generate_webp(filepath, max_size=(512, 512))
data = thumbnail_buffer.read()
etag = str(crc32(data))
thumbnail_cache[filepath] = {
'data_raw': data,
'etag': etag,
'mimetype': 'image/webp'
}
thumbnail = thumbnail_cache[filepath]
thumbnail['buffer'] = BytesIO()
thumbnail['buffer'].write(thumbnail['data_raw'])
thumbnail['buffer'].seek(0)
return thumbnail
def _generate_page(self, page_buffer, volume, page):
page_buffer = self._generate_webp(page_buffer)
disk_cache.set(f'{volume}-{page}', page_buffer)
page_buffer.seek(0)
return page_buffer
class CalibreDB(BaseDB):
def __init__(self, path='metadata.db', webp_quality=DEFAULT_WEBP_QUALITY, webp_method=DEFAULT_WEBP_METHOD, webp_size=DEFAULT_WEBP_SIZE):
super().__init__(
webp_quality=webp_quality,
webp_method=webp_method,
webp_size=webp_size
)
self.database_path = f'file:{path}?mode=ro'
def create_cursor(self): def create_cursor(self):
conn = sqlite3.connect(self.database_path, uri=True) conn = sqlite3.connect(self.database_path, uri=True)
conn.row_factory = dict_factory conn.row_factory = dict_factory
@ -120,7 +156,7 @@ class CalibreDB:
return self.get_volume_cover(first_volume['id']) return self.get_volume_cover(first_volume['id'])
def get_series_cover_thumbnail(self, series_id): def get_series_cover_thumbnail(self, series_id):
return generate_thumbnail(self.get_series_cover(series_id)) return self._generate_thumbnail(self.get_series_cover(series_id))
def get_series_volumes(self, series_id): def get_series_volumes(self, series_id):
cursor = self.create_cursor() cursor = self.create_cursor()
@ -172,7 +208,7 @@ class CalibreDB:
raise exceptions.NotFound() raise exceptions.NotFound()
def get_volume_cover_thumbnail(self, volume_id): def get_volume_cover_thumbnail(self, volume_id):
return generate_thumbnail(self.get_volume_cover(volume_id)) return self._generate_thumbnail(self.get_volume_cover(volume_id))
def get_volume_filepath(self, volume_id): def get_volume_filepath(self, volume_id):
cursor = self.create_cursor() cursor = self.create_cursor()
@ -229,38 +265,24 @@ class CalibreDB:
return None return None
page_filename = zip_info.filename page_filename = zip_info.filename
mimetype = mimetypes[os.path.splitext(page_filename)[1]]
if original is False: if original is True:
mimetype = mimetypes[os.path.splitext(page_filename)[1]]
page_buffer = BytesIO()
page_buffer.write(volume.read(page_filename))
page_buffer.seek(0)
else:
mimetype = 'image/webp' mimetype = 'image/webp'
try: try:
page_data = disk_cache.get(f'{volume_id}-{page_number}') page_buffer = disk_cache.get(f'{volume_id}-{page_number}')
except FileNotFoundError: except FileNotFoundError:
with volume.open(page_filename) as orig_page_data: with volume.open(page_filename) as orig_page_buffer:
image = Image.open(orig_page_data) page_buffer = self._generate_page(orig_page_buffer, volume_id, page_number)
image.thumbnail(tuple([self.webp_config['size']] * 2))
page_data = BytesIO()
image.save(
page_data,
format='webp',
save_all=True, #
append_images=[image], # https://github.com/python-pillow/Pillow/issues/4042
quality=self.webp_config['quality'],
method=self.webp_config['method'],
lossless=self.webp_config['lossless']
)
page_data.seek(0)
disk_cache.set(f'{volume_id}-{page_number}', page_data)
page_data.seek(0)
else:
page_data = BytesIO()
page_data.write(volume.read(page_filename))
page_data.seek(0)
return { return {
'data': page_data, 'buffer': page_buffer,
'mimetype': mimetype, 'mimetype': mimetype,
'etag': str(zip_info.CRC) 'etag': str(zip_info.CRC)
} }

View file

@ -21,7 +21,7 @@ def send_file_with_etag(fp, etag, **kwargs):
def send_image(image): def send_image(image):
return send_file_with_etag(image['data'], image['etag'], mimetype=image['mimetype']) return send_file_with_etag(image['buffer'], image['etag'], mimetype=image['mimetype'])
app = Flask(__name__, static_folder='frontend/dist/static', template_folder='frontend/dist') app = Flask(__name__, static_folder='frontend/dist/static', template_folder='frontend/dist')