|
|
|
@ -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,52 +29,82 @@ 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)
|
|
|
|
|
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:
|
|
|
|
|
webp_lossless = True
|
|
|
|
|
webp_quality = webp_quality - 101
|
|
|
|
|
else:
|
|
|
|
|
webp_lossless = False
|
|
|
|
|
|
|
|
|
|
data = thumbnail.read()
|
|
|
|
|
etag = str(crc32(data))
|
|
|
|
|
|
|
|
|
|
thumbnail_cache[filepath] = {
|
|
|
|
|
'data_raw': data,
|
|
|
|
|
'etag': etag,
|
|
|
|
|
'mimetype': 'image/webp'
|
|
|
|
|
self.webp_config = {
|
|
|
|
|
'quality': webp_quality,
|
|
|
|
|
'method': webp_method,
|
|
|
|
|
'lossless': webp_lossless,
|
|
|
|
|
'size': webp_size
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
thumbnail = thumbnail_cache[filepath]
|
|
|
|
|
thumbnail['data'] = BytesIO()
|
|
|
|
|
thumbnail['data'].write(thumbnail['data_raw'])
|
|
|
|
|
thumbnail['data'].seek(0)
|
|
|
|
|
def _generate_webp(self, fp, max_size=None):
|
|
|
|
|
if max_size is None:
|
|
|
|
|
max_size = tuple([self.webp_config['size']] * 2)
|
|
|
|
|
|
|
|
|
|
return thumbnail
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
class CalibreDB:
|
|
|
|
|
def __init__(self, path='metadata.db', enable_webp=True, webp_quality=80, webp_method=0, webp_size=2048):
|
|
|
|
|
self.database_path = f'file:{path}?mode=ro'
|
|
|
|
|
return image_buffer
|
|
|
|
|
|
|
|
|
|
self.webp = enable_webp
|
|
|
|
|
if self.webp is True:
|
|
|
|
|
# lossy: 0-100 (used as quality)
|
|
|
|
|
# lossless: 101-201 (101 subtracted and used as quality)
|
|
|
|
|
if webp_quality > 100:
|
|
|
|
|
webp_lossless = True
|
|
|
|
|
webp_quality = webp_quality - 101
|
|
|
|
|
else:
|
|
|
|
|
webp_lossless = False
|
|
|
|
|
def _generate_thumbnail(self, filepath):
|
|
|
|
|
if filepath not in thumbnail_cache:
|
|
|
|
|
thumbnail_buffer = self._generate_webp(filepath, max_size=(512, 512))
|
|
|
|
|
|
|
|
|
|
self.webp_config = {
|
|
|
|
|
'quality': webp_quality,
|
|
|
|
|
'method': webp_method,
|
|
|
|
|
'lossless': webp_lossless,
|
|
|
|
|
'size': webp_size
|
|
|
|
|
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
|
|
|
|
@ -122,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()
|
|
|
|
@ -174,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()
|
|
|
|
@ -231,38 +265,24 @@ class CalibreDB:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
page_filename = zip_info.filename
|
|
|
|
|
mimetype = mimetypes[os.path.splitext(page_filename)[1]]
|
|
|
|
|
|
|
|
|
|
if self.webp is True and mimetype != 'image/webp' and 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)
|
|
|
|
|
}
|
|
|
|
|