diff --git a/backend.py b/backend.py index 1a979f1..7dfa675 100644 --- a/backend.py +++ b/backend.py @@ -8,6 +8,10 @@ import os.path import sqlite3 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' if os.environ.get('DISK_CACHE', '1') == '0': @@ -25,35 +29,8 @@ def dict_factory(cursor, row): return d -def generate_thumbnail(filepath): - if filepath not in thumbnail_cache: - 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' - +class BaseDB: + def __init__(self, webp_quality, webp_method, webp_size): # lossy: 0-100 (used as quality) # lossless: 101-201 (101 subtracted and used as quality) if webp_quality > 100: @@ -69,6 +46,65 @@ class CalibreDB: '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): conn = sqlite3.connect(self.database_path, uri=True) conn.row_factory = dict_factory @@ -120,7 +156,7 @@ class CalibreDB: return self.get_volume_cover(first_volume['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): cursor = self.create_cursor() @@ -172,7 +208,7 @@ class CalibreDB: raise exceptions.NotFound() 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): cursor = self.create_cursor() @@ -229,38 +265,24 @@ class CalibreDB: return None 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' try: - page_data = disk_cache.get(f'{volume_id}-{page_number}') + page_buffer = disk_cache.get(f'{volume_id}-{page_number}') except FileNotFoundError: - with volume.open(page_filename) as orig_page_data: - image = Image.open(orig_page_data) - 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) + with volume.open(page_filename) as orig_page_buffer: + page_buffer = self._generate_page(orig_page_buffer, volume_id, page_number) return { - 'data': page_data, + 'buffer': page_buffer, 'mimetype': mimetype, 'etag': str(zip_info.CRC) } diff --git a/mangareader.py b/mangareader.py index aa412d5..b1ff1ac 100755 --- a/mangareader.py +++ b/mangareader.py @@ -21,7 +21,7 @@ def send_file_with_etag(fp, etag, **kwargs): 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')