backend: use webp for thumbnails and cache them
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
d1da8cd9e2
commit
9800befe37
|
@ -27,11 +27,13 @@ COPY --from=requirements /usr/src/app/requirements.txt .
|
||||||
RUN apk add --no-cache --virtual .deps \
|
RUN apk add --no-cache --virtual .deps \
|
||||||
build-base \
|
build-base \
|
||||||
libjpeg-turbo-dev \
|
libjpeg-turbo-dev \
|
||||||
|
libwebp-dev \
|
||||||
zlib-dev \
|
zlib-dev \
|
||||||
&& pip install -r requirements.txt \
|
&& pip install -r requirements.txt \
|
||||||
&& apk del .deps \
|
&& apk del .deps \
|
||||||
&& apk add --no-cache \
|
&& apk add --no-cache \
|
||||||
libjpeg-turbo
|
libjpeg-turbo \
|
||||||
|
libwebp
|
||||||
|
|
||||||
COPY --from=frontend /usr/src/app/frontend/dist/ frontend/dist
|
COPY --from=frontend /usr/src/app/frontend/dist/ frontend/dist
|
||||||
|
|
||||||
|
|
1
Pipfile
1
Pipfile
|
@ -10,6 +10,7 @@ Flask = "*"
|
||||||
pillow = "*"
|
pillow = "*"
|
||||||
flask-cors = "*"
|
flask-cors = "*"
|
||||||
gunicorn = "*"
|
gunicorn = "*"
|
||||||
|
flask-caching = "*"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.7"
|
python_version = "3.7"
|
||||||
|
|
22
Pipfile.lock
generated
22
Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "6bf198e4fcc5777632eb0d839a8259ef2fc737c6df23cbe2923d0d4593ab06e4"
|
"sha256": "92afeeaf4d754a31e04e294f6974bd9616eb0f388ce3d977ae235f3d49fa2eae"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -25,11 +25,19 @@
|
||||||
},
|
},
|
||||||
"flask": {
|
"flask": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a31adc27de06034c657a8dc091cc5fcb0227f2474798409bff0e9674de31a026",
|
"sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52",
|
||||||
"sha256:b5ae63812021cb04174fcff05d560a98387a44d9cccd4652a2bfa131ba4e4c9b"
|
"sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.1.0"
|
"version": "==1.1.1"
|
||||||
|
},
|
||||||
|
"flask-caching": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:52e236cbc836c41a5ced0c0a67b48ad180c9e2b5cb69e881089bba766db5569e",
|
||||||
|
"sha256:b0daabd5249bebfbae3da4c22987bac22047fc8b18ea2716c4fc63d57d218946"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.7.2"
|
||||||
},
|
},
|
||||||
"flask-cors": {
|
"flask-cors": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -135,10 +143,10 @@
|
||||||
},
|
},
|
||||||
"werkzeug": {
|
"werkzeug": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:865856ebb55c4dcd0630cdd8f3331a1847a819dda7e8c750d3db6f2aa6c0209c",
|
"sha256:87ae4e5b5366da2347eb3116c0e6c681a0e939a33b2805e2c0cbd282664932c4",
|
||||||
"sha256:a0b915f0815982fb2a09161cb8f31708052d0951c3ba433ccc5e1aa276507ca6"
|
"sha256:a13b74dd3c45f758d4ebdb224be8f1ab8ef58b3c0ffc1783a8c7d9f4f50227e6"
|
||||||
],
|
],
|
||||||
"version": "==0.15.4"
|
"version": "==0.15.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {}
|
"develop": {}
|
||||||
|
|
23
backend.py
23
backend.py
|
@ -8,6 +8,8 @@ import werkzeug.exceptions as exceptions
|
||||||
|
|
||||||
mimetypes['.webp'] = 'image/webp'
|
mimetypes['.webp'] = 'image/webp'
|
||||||
|
|
||||||
|
thumbnail_cache = {}
|
||||||
|
|
||||||
|
|
||||||
# https://docs.python.org/3.7/library/sqlite3.html#sqlite3.Connection.row_factory
|
# https://docs.python.org/3.7/library/sqlite3.html#sqlite3.Connection.row_factory
|
||||||
def dict_factory(cursor, row):
|
def dict_factory(cursor, row):
|
||||||
|
@ -18,12 +20,23 @@ def dict_factory(cursor, row):
|
||||||
|
|
||||||
|
|
||||||
def generate_thumbnail(filepath):
|
def generate_thumbnail(filepath):
|
||||||
image = Image.open(filepath)
|
if filepath in thumbnail_cache:
|
||||||
image.thumbnail((512, 512))
|
thumbnail = BytesIO()
|
||||||
thumbnail = BytesIO()
|
thumbnail.write(thumbnail_cache[filepath])
|
||||||
image.save(thumbnail, format='jpeg')
|
else:
|
||||||
|
image = Image.open(filepath)
|
||||||
|
image.thumbnail((512, 512))
|
||||||
|
thumbnail = BytesIO()
|
||||||
|
image.save(thumbnail, 'webp')
|
||||||
|
thumbnail.seek(0)
|
||||||
|
thumbnail_cache[filepath] = thumbnail.read()
|
||||||
|
|
||||||
thumbnail.seek(0)
|
thumbnail.seek(0)
|
||||||
return thumbnail
|
|
||||||
|
return {
|
||||||
|
'data': thumbnail,
|
||||||
|
'mimetype': 'image/webp'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class CalibreDB:
|
class CalibreDB:
|
||||||
|
|
|
@ -51,7 +51,7 @@ def get_series_cover(series_id):
|
||||||
@app.route('/api/series/<int:series_id>/cover/thumbnail')
|
@app.route('/api/series/<int:series_id>/cover/thumbnail')
|
||||||
def get_series_cover_thumbnail(series_id):
|
def get_series_cover_thumbnail(series_id):
|
||||||
thumbnail = db.get_series_cover_thumbnail(series_id)
|
thumbnail = db.get_series_cover_thumbnail(series_id)
|
||||||
return send_file_with_etag(thumbnail, mimetype='image/jpeg')
|
return send_file_with_etag(thumbnail['data'], mimetype=thumbnail['mimetype'])
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/series/<int:series_id>')
|
@app.route('/api/series/<int:series_id>')
|
||||||
|
@ -67,7 +67,7 @@ def get_volume_cover(volume_id):
|
||||||
@app.route('/api/volume/<int:volume_id>/cover/thumbnail')
|
@app.route('/api/volume/<int:volume_id>/cover/thumbnail')
|
||||||
def get_volume_cover_thumbnail(volume_id):
|
def get_volume_cover_thumbnail(volume_id):
|
||||||
thumbnail = db.get_volume_cover_thumbnail(volume_id)
|
thumbnail = db.get_volume_cover_thumbnail(volume_id)
|
||||||
return send_file_with_etag(thumbnail, mimetype='image/jpeg')
|
return send_file_with_etag(thumbnail['data'], mimetype=thumbnail['mimetype'])
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/volume/<int:volume_id>')
|
@app.route('/api/volume/<int:volume_id>')
|
||||||
|
|
Reference in a new issue