Accept ZIP batches to preserve every photo shared by Shortcuts

This commit is contained in:
2026-09-19 15:36:16 +03:30
parent e42646e09e
commit a9401b6251
3 changed files with 70 additions and 11 deletions
+19 -1
View File
@@ -12,6 +12,7 @@ import shutil
import tempfile
from datetime import datetime, timezone
import warnings
import zipfile
from flask import Flask, abort, jsonify, request
from PIL import Image, ImageCms, ImageOps, UnidentifiedImageError
@@ -183,6 +184,23 @@ def create_app(data_dir=None, token=None):
def upload():
kind = "snippet" if request.path == "/api/snippets" else "photo"
photos = request.files.getlist("photos")
archives = request.files.getlist("archive")
if archives:
if photos or len(archives) != 1:
abort(400, "Send either photos or one ZIP archive, not both")
try:
with zipfile.ZipFile(archives[0].stream) as archive:
members = [item for item in archive.infolist() if not item.is_dir()
and not item.filename.startswith("__MACOSX/")
and Path(item.filename).name != ".DS_Store"]
if not 1 <= len(members) <= 20:
abort(400, "The ZIP must contain between 1 and 20 images")
if sum(item.file_size for item in members) > 100 * 1024 * 1024:
abort(413, "Uncompressed ZIP images must be at most 100 MiB")
# Read members only; never extract archive paths onto the filesystem.
photos = [archive.read(item) for item in members]
except (zipfile.BadZipFile, RuntimeError, NotImplementedError, OSError):
abort(400, "Send a valid, unencrypted ZIP archive of images")
if not (0 if kind == "snippet" else 1) <= len(photos) <= 20:
abort(400, "Send between 1 and 20 files in the photos form field")
text = request.form.get("text", "").strip() if kind == "snippet" else ""
@@ -205,7 +223,7 @@ def create_app(data_dir=None, token=None):
(temporary / "originals").mkdir()
(temporary / "images").mkdir()
for index, photo in enumerate(photos):
raw = photo.read()
raw = photo if isinstance(photo, bytes) else photo.read()
digest.update(hashlib.sha256(raw).digest())
(temporary / "originals" / str(index)).write_bytes(raw)
try: