add thumbnails in backend
This commit is contained in:
parent
b992303d41
commit
8260cf5ae3
147
app.py
147
app.py
@ -1,6 +1,6 @@
|
|||||||
from flask import Flask, render_template, send_file, url_for, jsonify, request, session, send_from_directory, abort
|
from flask import Flask, render_template, send_file, url_for, jsonify, request, session, send_from_directory, abort
|
||||||
import os
|
import os
|
||||||
from PIL import Image
|
from PIL import Image, ImageOps
|
||||||
import io
|
import io
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import mimetypes
|
import mimetypes
|
||||||
@ -249,10 +249,9 @@ def serve_file(subpath):
|
|||||||
# 2) Prep request info
|
# 2) Prep request info
|
||||||
mime, _ = mimetypes.guess_type(full_path)
|
mime, _ = mimetypes.guess_type(full_path)
|
||||||
mime = mime or 'application/octet-stream'
|
mime = mime or 'application/octet-stream'
|
||||||
|
|
||||||
is_cache_request = request.headers.get('X-Cache-Request') == 'true'
|
is_cache_request = request.headers.get('X-Cache-Request') == 'true'
|
||||||
ip_address = request.remote_addr
|
ip_address = request.remote_addr
|
||||||
user_agent = request.headers.get('User-Agent')
|
user_agent = request.headers.get('User-Agent')
|
||||||
|
|
||||||
# skip logging on cache hits or on audio GETs (per your rules)
|
# skip logging on cache hits or on audio GETs (per your rules)
|
||||||
do_log = not is_cache_request
|
do_log = not is_cache_request
|
||||||
@ -260,77 +259,119 @@ def serve_file(subpath):
|
|||||||
do_log = False
|
do_log = False
|
||||||
|
|
||||||
# 3) Pick cache
|
# 3) Pick cache
|
||||||
if mime.startswith('audio/'): cache = cache_audio
|
if mime.startswith('audio/'):
|
||||||
elif mime.startswith('image/'): cache = cache_image
|
cache = cache_audio
|
||||||
elif mime.startswith('video/'): cache = cache_video
|
elif mime.startswith('image/'):
|
||||||
else: cache = cache_other
|
cache = cache_image
|
||||||
|
elif mime.startswith('video/'):
|
||||||
|
cache = cache_video
|
||||||
|
else:
|
||||||
|
cache = cache_other
|
||||||
|
|
||||||
# 4) Ensure cached on‑disk file and get its path
|
# 4) Image and thumbnail handling first
|
||||||
|
if mime.startswith('image/'):
|
||||||
|
small = request.args.get('thumbnail') == 'true'
|
||||||
|
name, ext = os.path.splitext(subpath)
|
||||||
|
orig_key = subpath
|
||||||
|
small_key = f"{name}_small{ext}"
|
||||||
|
cache_key = small_key if small else orig_key
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Try to read the requested variant
|
||||||
|
with cache.read(cache_key) as reader:
|
||||||
|
file_path = reader.name
|
||||||
|
except KeyError:
|
||||||
|
# On miss: generate both full-size and small thumb, then cache
|
||||||
|
with Image.open(full_path) as orig:
|
||||||
|
exif_bytes = orig.info.get('exif')
|
||||||
|
img = ImageOps.exif_transpose(orig)
|
||||||
|
variants = {
|
||||||
|
orig_key: (1920, 1920),
|
||||||
|
small_key: ( 192, 192),
|
||||||
|
}
|
||||||
|
for key, size in variants.items():
|
||||||
|
thumb = img.copy()
|
||||||
|
thumb.thumbnail(size, Image.LANCZOS)
|
||||||
|
if thumb.mode in ("RGBA", "P"):
|
||||||
|
thumb = thumb.convert("RGB")
|
||||||
|
bio = io.BytesIO()
|
||||||
|
save_kwargs = {'format': 'JPEG', 'quality': 85}
|
||||||
|
if exif_bytes:
|
||||||
|
save_kwargs['exif'] = exif_bytes
|
||||||
|
thumb.save(bio, **save_kwargs)
|
||||||
|
bio.seek(0)
|
||||||
|
cache.set(key, bio, read=True)
|
||||||
|
# Read back the variant we need
|
||||||
|
with cache.read(cache_key) as reader:
|
||||||
|
file_path = reader.name
|
||||||
|
|
||||||
|
# Serve the image variant
|
||||||
|
response = send_file(
|
||||||
|
file_path,
|
||||||
|
mimetype=mime,
|
||||||
|
conditional=True,
|
||||||
|
as_attachment=(request.args.get('download') == 'true'),
|
||||||
|
download_name=os.path.basename(orig_key)
|
||||||
|
)
|
||||||
|
response.headers['Content-Disposition'] = 'inline'
|
||||||
|
response.headers['Cache-Control'] = 'public, max-age=86400'
|
||||||
|
|
||||||
|
if do_log:
|
||||||
|
a.log_file_access(
|
||||||
|
cache_key,
|
||||||
|
os.path.getsize(file_path),
|
||||||
|
mime,
|
||||||
|
ip_address,
|
||||||
|
user_agent,
|
||||||
|
session['device_id'],
|
||||||
|
True
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
# 5) Non-image branch: ensure original is cached
|
||||||
try:
|
try:
|
||||||
with cache.read(subpath) as reader:
|
with cache.read(subpath) as reader:
|
||||||
file_path = reader.name
|
file_path = reader.name
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# cache miss
|
try:
|
||||||
if mime.startswith('image/'):
|
cache.set(subpath, open(full_path, 'rb'), read=True)
|
||||||
# ─── 4a) Image branch: thumbnail & cache the JPEG ───
|
with cache.read(subpath) as reader:
|
||||||
try:
|
file_path = reader.name
|
||||||
with Image.open(full_path) as img:
|
except Exception as e:
|
||||||
img.thumbnail((1920, 1920))
|
app.logger.error(f"Failed to cache file {subpath}: {e}")
|
||||||
if img.mode in ("RGBA", "P"):
|
abort(500)
|
||||||
img = img.convert("RGB")
|
|
||||||
thumb_io = io.BytesIO()
|
|
||||||
img.save(thumb_io, format='JPEG', quality=85)
|
|
||||||
thumb_io.seek(0)
|
|
||||||
# write thumbnail into diskcache as a real file
|
|
||||||
cache.set(subpath, thumb_io, read=True)
|
|
||||||
# now re-open from cache to get the on-disk path
|
|
||||||
with cache.read(subpath) as reader:
|
|
||||||
file_path = reader.name
|
|
||||||
except Exception as e:
|
|
||||||
app.logger.error(f"Image processing failed for {subpath}: {e}")
|
|
||||||
abort(500)
|
|
||||||
else:
|
|
||||||
# ─── 4b) Non-image branch: cache original file ───
|
|
||||||
try:
|
|
||||||
# store the real file on diskcache
|
|
||||||
cache.set(subpath, open(full_path, 'rb'), read=True)
|
|
||||||
# read back to get its path
|
|
||||||
with cache.read(subpath) as reader:
|
|
||||||
file_path = reader.name
|
|
||||||
except Exception as e:
|
|
||||||
app.logger.error(f"Failed to cache file {subpath}: {e}")
|
|
||||||
abort(500)
|
|
||||||
|
|
||||||
|
# 6) Build response for non-image
|
||||||
filesize = os.path.getsize(file_path)
|
filesize = os.path.getsize(file_path)
|
||||||
cached_hit = True # or False if you want to pass that into your logger
|
cached_hit = True
|
||||||
|
|
||||||
# 5) Figure out download flag and filename
|
# Figure out download flag and filename
|
||||||
ask_download = request.args.get('download') == 'true'
|
ask_download = request.args.get('download') == 'true'
|
||||||
filename = os.path.basename(full_path)
|
filename = os.path.basename(full_path)
|
||||||
|
|
||||||
# 6) Single send_file call with proper attachment handling
|
# Single send_file call with proper attachment handling
|
||||||
response = send_file(
|
response = send_file(
|
||||||
file_path,
|
file_path,
|
||||||
mimetype = mime,
|
mimetype=mime,
|
||||||
conditional = True,
|
conditional=True,
|
||||||
as_attachment = ask_download,
|
as_attachment=ask_download,
|
||||||
download_name = filename if ask_download else None
|
download_name=filename if ask_download else None
|
||||||
)
|
)
|
||||||
|
|
||||||
# Explicitly force inline if not downloading
|
|
||||||
if not ask_download:
|
if not ask_download:
|
||||||
response.headers['Content-Disposition'] = 'inline'
|
response.headers['Content-Disposition'] = 'inline'
|
||||||
|
|
||||||
response.headers['Cache-Control'] = 'public, max-age=86400'
|
response.headers['Cache-Control'] = 'public, max-age=86400'
|
||||||
|
|
||||||
# 7) Logging
|
# 7) Logging
|
||||||
if do_log:
|
if do_log:
|
||||||
a.log_file_access(
|
a.log_file_access(
|
||||||
subpath, filesize, mime,
|
subpath,
|
||||||
ip_address, user_agent,
|
filesize,
|
||||||
session['device_id'], cached_hit
|
mime,
|
||||||
|
ip_address,
|
||||||
|
user_agent,
|
||||||
|
session['device_id'],
|
||||||
|
cached_hit
|
||||||
)
|
)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user