Files
blog/scripts/migrate/photos_migrate.py
T
wait4xandGLM-5.3 aea21c8694 feat(migrate): Halo 全量内容迁移(压缩版)——6文/21图/1瞬间 URL 1:1
- 正文源=线上渲染页(Halo 快照层 patch 残迹:泥潭/赎罪仅 2 字符),
  pandoc html→gfm + tags API 显示名换算后分类:5 书评(五档情绪色)+1 技术
- 图库 21 张:长边 2400/q86 压缩入仓(原图 196M→9.5M——公开仓历史膨胀
  与 nginx 100M 推送上限双杀;原图在 Phase 0 备份 tar 中永久留档)
- 瞬间 1 条;URL 1:1(6 slug 全等,零 301)
- 本提交由 7 个本地提交重写而成(清除已进历史的 196M blob,未推送过);
  因 reset --soft 暂存竞态,一并包含 spec 归档文件
  (.spec/specs/archive/2026-10-24_migrate-blog-content/)

验证: check_urls.py 6==6;astro build 11 页 exit 0(8.67s);
      EXIF 管线跑通;staging 更新;halo healthy+200 全程未动

Co-authored-by: GLM-5.3
2026-10-25 00:39:08 +08:00

79 lines
2.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""图库迁移(Phase 2 T3):Halo photos → content/photos/{jpg,json sidecar}
EXIF 不在迁移时提——构建期 photos loader(src/lib/exif.ts)会补全;
sidecar 里 exif 留空对象。分组:Halo displayName 含山/雪/land/snow →
landscape,否则 street(当前仅一组 snow mountain → landscape)。
"""
import json
import re
import urllib.request
from pathlib import Path
from urllib.parse import unquote
BASE = "http://127.0.0.1:18090"
OUT = Path("content/photos")
def slugify(name: str) -> str:
stem = re.sub(r"\.(jpg|jpeg|png|webp)$", "", name, flags=re.I)
stem = re.sub(r"[^0-9A-Za-z\u4e00-\u9fff-]+", "-", stem).strip("-")
return stem or "photo"
def group_of(display: str) -> str:
low = display.lower()
return "landscape" if any(k in low for k in ("山", "雪", "snow", "land", "mountain")) else "street"
def compress(data: bytes) -> bytes:
from io import BytesIO
from PIL import Image
img = Image.open(BytesIO(data))
if img.mode not in ("RGB", "L"):
img = img.convert("RGB")
img.thumbnail((2400, 2400))
buf = BytesIO()
img.save(buf, "JPEG", quality=86, optimize=True)
return buf.getvalue()
def main() -> int:
# 真实照片取代样例:先清样例(jpg/json 成对;仅清本脚本管辖的产物)
for f in OUT.glob("*"):
if f.suffix in (".jpg", ".json"):
f.unlink()
photos = json.load(open(".migration/photos.json", encoding="utf-8"))
n = 0
for p in photos:
spec = p["spec"]
url = spec.get("url") or spec.get("cover") or ""
if not url.startswith("/upload/"):
print(f" skip 非本站图: {url[:60]}")
continue
fname = unquote(url.rsplit("/", 1)[1])
slug = slugify(spec.get("displayName") or fname)
data = urllib.request.urlopen(BASE + url, timeout=60).read()
# 入仓前压到长边 2400/q86:原图总计 196M(单张最大 28M)会永久膨胀
# 公开仓历史并撞死 nginx 100M 推送上限;原图已随 Phase 0 备份归档,
# 站内展示由 sharp 出 srcset,2400px 足够 lightbox 全屏
(OUT / f"{slug}.jpg").write_bytes(compress(data))
(OUT / f"{slug}.json").write_text(json.dumps({
"title": spec.get("displayName", fname).rsplit(".", 1)[0],
"image": f"./{slug}.jpg",
"group": group_of(spec.get("groupName", "")),
"alt": spec.get("description", ""),
"exif": {},
}, ensure_ascii=False, indent=1))
n += 1
print(f"migrated {n} photos")
return 0 if n else 1
if __name__ == "__main__":
raise SystemExit(main())