Make image crop target size configurable
This commit is contained in:
@@ -20,6 +20,24 @@ def create_app():
|
||||
app.config.setdefault("SQLALCHEMY_TRACK_MODIFICATIONS", False)
|
||||
app.config.setdefault("UPLOAD_FOLDER", os.path.join(app.root_path, "static", "uploads"))
|
||||
|
||||
# Target output resolution for cropped images.
|
||||
# This is used by the client-side cropper (to generate an upload) and by the server-side
|
||||
# image processing (to cap the resulting WEBP size).
|
||||
#
|
||||
# Defaults to Full HD landscape (1920x1080). Portrait is derived by swapping.
|
||||
# Override via env vars, e.g.:
|
||||
# IMAGE_CROP_TARGET_W=1920
|
||||
# IMAGE_CROP_TARGET_H=1080
|
||||
def _env_int(name: str, default: int) -> int:
|
||||
try:
|
||||
v = int(os.environ.get(name, "") or default)
|
||||
except (TypeError, ValueError):
|
||||
v = default
|
||||
return max(1, v)
|
||||
|
||||
app.config.setdefault("IMAGE_CROP_TARGET_W", _env_int("IMAGE_CROP_TARGET_W", 1920))
|
||||
app.config.setdefault("IMAGE_CROP_TARGET_H", _env_int("IMAGE_CROP_TARGET_H", 1080))
|
||||
|
||||
# NOTE: Videos should be max 250MB.
|
||||
# Flask's MAX_CONTENT_LENGTH applies to the full request payload (multipart includes overhead).
|
||||
# We set this slightly above 250MB to allow for multipart/form fields overhead, while still
|
||||
|
||||
@@ -147,15 +147,22 @@ def _save_compressed_image(
|
||||
img = img.convert("RGB")
|
||||
|
||||
# Optional crop
|
||||
# NOTE: The front-end may already upload a cropped image (canvas export), but we still
|
||||
# enforce aspect + maximum output size here for consistency.
|
||||
target_w = int(current_app.config.get("IMAGE_CROP_TARGET_W", 1920) or 1920)
|
||||
target_h = int(current_app.config.get("IMAGE_CROP_TARGET_H", 1080) or 1080)
|
||||
target_w = max(1, target_w)
|
||||
target_h = max(1, target_h)
|
||||
|
||||
if cm == "16:9":
|
||||
img = _center_crop_to_aspect(img, 16, 9)
|
||||
max_box = (1920, 1080)
|
||||
max_box = (target_w, target_h)
|
||||
elif cm == "9:16":
|
||||
img = _center_crop_to_aspect(img, 9, 16)
|
||||
max_box = (1080, 1920)
|
||||
max_box = (target_h, target_w)
|
||||
else:
|
||||
# No crop: allow both portrait and landscape up to 1920px on the longest side.
|
||||
max_box = (1920, 1920)
|
||||
# No crop: allow both portrait and landscape up to target_w/target_h on the longest side.
|
||||
max_box = (max(target_w, target_h),) * 2
|
||||
|
||||
# Resize down if very large (keeps aspect ratio)
|
||||
img.thumbnail(max_box)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<h1 class="page-title">Welcome{% if current_user and current_user.email %}, {{ current_user.email }}{% endif %}!</h1>
|
||||
<h1 class="page-title">Dashboard</h1>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{# Expose server-side crop target sizes to the JS without embedding Jinja inside JS #}
|
||||
<div
|
||||
id="page-config"
|
||||
class="d-none"
|
||||
data-image-crop-target-w="{{ config.get('IMAGE_CROP_TARGET_W', 1920) }}"
|
||||
data-image-crop-target-h="{{ config.get('IMAGE_CROP_TARGET_H', 1080) }}"
|
||||
></div>
|
||||
{# Cropper.js (used for image cropping) #}
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/cropperjs@1.6.2/dist/cropper.min.css" />
|
||||
<style>
|
||||
@@ -912,9 +919,15 @@
|
||||
|
||||
cropStatus.textContent = 'Preparing cropped image…';
|
||||
const isPortrait = cm === '9:16';
|
||||
// Export at Full HD by default (or whatever the server config says).
|
||||
// We still enforce a server-side max output size in _save_compressed_image.
|
||||
const cfg = document.getElementById('page-config');
|
||||
const TARGET_W = parseInt(cfg?.dataset?.imageCropTargetW || '1920', 10) || 1920;
|
||||
const TARGET_H = parseInt(cfg?.dataset?.imageCropTargetH || '1080', 10) || 1080;
|
||||
|
||||
const canvas = cropper.getCroppedCanvas({
|
||||
width: isPortrait ? 720 : 1280,
|
||||
height: isPortrait ? 1280 : 720,
|
||||
width: isPortrait ? TARGET_H : TARGET_W,
|
||||
height: isPortrait ? TARGET_W : TARGET_H,
|
||||
imageSmoothingQuality: 'high',
|
||||
});
|
||||
const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/png'));
|
||||
|
||||
Reference in New Issue
Block a user