- git 仓只进压缩版(构建用);原图落 ~/docker_apps/blog/photos-originals/
(迁移器/ingest 双入口写入,幂等),build.sh 独立 rsync 到
staging/photos-originals/(同一静态根,零 nginx 改动)
- lightbox 增「原图」(新标签全尺寸)+「下载」(download 属性带文件名)
- 原图持久保障:store + Phase 0 备份 tar 双留档;存量 21 张已回填(196M)
验证: astro build 11 页 exit 0;dist/gallery 含 lb-original×2 与
photos-originals 引用;staging/photos-originals 21 张实测
Spec: none (用户功能补req:原图查看/下载,跨 Phase 独立小特性)
Co-authored-by: GLM-5.3
53 lines
2.1 KiB
Python
53 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
||
"""照片入库处理器:把 content/photos/ 下超限图片压缩到站内规范。
|
||
|
||
为什么存在:gitea web/本地上传的是原图(曾发生 196M/21 张入仓事故,
|
||
见 git log aea21c8 的重写说明);sharp 只派生 dist 缩略图不管仓内体积。
|
||
工作流:上传照片到 content/photos/(顺带跑一次 sidecar 生成或手写)后、
|
||
git commit 前执行本脚本;幂等,已在规范内的文件原样跳过(零 diff)。
|
||
|
||
规范与迁移器一致:长边 ≤2400px、JPEG q86(scripts/migrate/photos_migrate.py)。
|
||
"""
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
PHOTOS = Path("content/photos")
|
||
MAX_EDGE = 2400
|
||
QUALITY = 86
|
||
# 跳过阈值:小于此字节数的 jpg 视为已处理过(2400px q86 成片通常 200KB-1MB)
|
||
SKIP_BELOW_BYTES = 1_500_000
|
||
|
||
|
||
def main() -> int:
|
||
from PIL import Image
|
||
|
||
changed, skipped = [], 0
|
||
for jpg in sorted(PHOTOS.glob("*.jpg")):
|
||
size = jpg.stat().st_size
|
||
if size < SKIP_BELOW_BYTES:
|
||
skipped += 1
|
||
continue
|
||
STORE = Path.home() / "docker_apps/blog/photos-originals"
|
||
STORE.mkdir(parents=True, exist_ok=True)
|
||
# 压缩覆写仓内文件前原件先进 store(幂等:store 已有同名则跳过)
|
||
if not (STORE / jpg.name).exists():
|
||
(STORE / jpg.name).write_bytes(jpg.read_bytes())
|
||
img = Image.open(jpg)
|
||
# EXIF 显式保留:重存默认剥离会抽走图库参数条的构建期数据源
|
||
exif = img.info.get("exif")
|
||
if img.mode not in ("RGB", "L"):
|
||
img = img.convert("RGB")
|
||
img.thumbnail((MAX_EDGE, MAX_EDGE))
|
||
kwargs = {"exif": exif} if exif else {}
|
||
img.save(jpg, "JPEG", quality=QUALITY, optimize=True, **kwargs)
|
||
changed.append((jpg.name, size, jpg.stat().st_size))
|
||
|
||
for name, before, after in changed:
|
||
print(f" compressed: {name} {before/1e6:.1f}M -> {after/1e6:.2f}M")
|
||
print(f"photos ingest: {len(changed)} compressed, {skipped} skipped (within spec)")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|