5c028d7952
包含 FastAPI 后端、React 前端、队列/OCR/标签/待办等完整功能。 Co-authored-by: Cursor <cursoragent@cursor.com>
49 lines
1.5 KiB
Python
49 lines
1.5 KiB
Python
"""生成并缓存缩略图。"""
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
from pathlib import Path
|
|
|
|
from PIL import Image
|
|
|
|
from app.core.config import settings
|
|
from app.core.path_utils import path_to_storage
|
|
|
|
|
|
SUPPORTED_EXTS = {".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tif", ".tiff"}
|
|
|
|
|
|
def is_supported(path: Path) -> bool:
|
|
"""是否为我们支持的图片格式。"""
|
|
return path.suffix.lower() in SUPPORTED_EXTS
|
|
|
|
|
|
def generate_thumbnail(image_path: Path, max_side: int | None = None) -> Path:
|
|
"""生成 webp 缩略图,落到缓存目录。返回缓存路径。"""
|
|
max_side = max_side or settings.thumb_size
|
|
# 用文件路径 + mtime 哈希作为缓存键,源文件变化会自动生成新缩略图
|
|
stat = image_path.stat()
|
|
key = hashlib.md5(
|
|
f"{path_to_storage(image_path)}|{stat.st_mtime_ns}|{max_side}".encode("utf-8")
|
|
).hexdigest()
|
|
out = settings.thumb_dir / f"{key}.webp"
|
|
if out.exists():
|
|
return out
|
|
with Image.open(image_path) as img:
|
|
img = img.convert("RGB")
|
|
img.thumbnail((max_side, max_side), Image.LANCZOS)
|
|
img.save(out, format="WEBP", quality=80)
|
|
return out
|
|
|
|
|
|
def file_hash(image_path: Path, chunk: int = 1024 * 1024) -> str:
|
|
"""计算文件 sha256,用作去重键。"""
|
|
h = hashlib.sha256()
|
|
with open(image_path, "rb") as f:
|
|
while True:
|
|
data = f.read(chunk)
|
|
if not data:
|
|
break
|
|
h.update(data)
|
|
return h.hexdigest()
|