feat(travel-proxy): indexer.py — 폴더 동기화 + 썸네일 일괄 생성
This commit is contained in:
125
travel-proxy/app/indexer.py
Normal file
125
travel-proxy/app/indexer.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Set
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from . import db
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
IMAGE_EXT = {".jpg", ".jpeg", ".png", ".webp"}
|
||||||
|
THUMB_SIZE = (480, 480)
|
||||||
|
|
||||||
|
|
||||||
|
def _scan_folder(folder: Path) -> List[Dict[str, Any]]:
|
||||||
|
"""폴더 내 이미지 파일 목록 수집 (os.scandir)."""
|
||||||
|
if not folder.exists():
|
||||||
|
return []
|
||||||
|
items = []
|
||||||
|
with os.scandir(folder) as entries:
|
||||||
|
for entry in entries:
|
||||||
|
if entry.is_file() and Path(entry.name).suffix.lower() in IMAGE_EXT:
|
||||||
|
items.append({
|
||||||
|
"filename": entry.name,
|
||||||
|
"mtime": entry.stat().st_mtime,
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_thumb(src: Path, dest: Path) -> bool:
|
||||||
|
"""원본에서 480x480 썸네일 생성. 성공 시 True."""
|
||||||
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
tmp = dest.with_name(dest.stem + ".tmp" + dest.suffix)
|
||||||
|
try:
|
||||||
|
with Image.open(src) as im:
|
||||||
|
im.thumbnail(THUMB_SIZE)
|
||||||
|
ext = dest.suffix.lower()
|
||||||
|
if ext in (".jpg", ".jpeg"):
|
||||||
|
fmt = "JPEG"
|
||||||
|
elif ext == ".png":
|
||||||
|
fmt = "PNG"
|
||||||
|
elif ext == ".webp":
|
||||||
|
fmt = "WEBP"
|
||||||
|
else:
|
||||||
|
fmt = (im.format or "").upper() or "JPEG"
|
||||||
|
im.save(tmp, format=fmt, quality=85, optimize=True)
|
||||||
|
tmp.replace(dest)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("Thumb generation failed: %s → %s", src, e)
|
||||||
|
try:
|
||||||
|
if tmp.exists():
|
||||||
|
tmp.unlink()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def sync(
|
||||||
|
travel_root: Path,
|
||||||
|
thumb_root: Path,
|
||||||
|
region_map_path: Path,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
폴더 스캔 → DB 동기화 + 썸네일 일괄 생성.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{"added": int, "removed": int, "thumbs_generated": int, "duration_sec": float}
|
||||||
|
"""
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
# 1. region_map.json에서 전체 앨범 폴더 수집
|
||||||
|
with open(region_map_path, "r", encoding="utf-8") as f:
|
||||||
|
region_map = json.load(f)
|
||||||
|
|
||||||
|
all_albums: Set[str] = set()
|
||||||
|
for v in region_map.values():
|
||||||
|
if isinstance(v, list):
|
||||||
|
all_albums.update(v)
|
||||||
|
elif isinstance(v, dict) and isinstance(v.get("albums"), list):
|
||||||
|
all_albums.update(v["albums"])
|
||||||
|
|
||||||
|
# 2. 각 앨범 폴더 스캔 → DB 동기화
|
||||||
|
added = 0
|
||||||
|
removed = 0
|
||||||
|
|
||||||
|
for album in sorted(all_albums):
|
||||||
|
folder = travel_root / album
|
||||||
|
items = _scan_folder(folder)
|
||||||
|
existing_filenames = set()
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
existing_filenames.add(item["filename"])
|
||||||
|
result = db.upsert_photo(album, item["filename"], item["mtime"])
|
||||||
|
if result == "added":
|
||||||
|
added += 1
|
||||||
|
|
||||||
|
removed += db.remove_missing_photos(album, existing_filenames)
|
||||||
|
|
||||||
|
# 3. 썸네일 미생성 분 일괄 생성
|
||||||
|
no_thumb = db.get_photos_without_thumb()
|
||||||
|
thumbs_generated = 0
|
||||||
|
|
||||||
|
for photo in no_thumb:
|
||||||
|
src = travel_root / photo["album"] / photo["filename"]
|
||||||
|
dest = thumb_root / photo["album"] / photo["filename"]
|
||||||
|
if _generate_thumb(src, dest):
|
||||||
|
db.mark_thumb_done(photo["album"], photo["filename"])
|
||||||
|
thumbs_generated += 1
|
||||||
|
|
||||||
|
duration = round(time.time() - start, 2)
|
||||||
|
logger.info(
|
||||||
|
"Sync complete: added=%d removed=%d thumbs=%d duration=%.2fs",
|
||||||
|
added, removed, thumbs_generated, duration,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"added": added,
|
||||||
|
"removed": removed,
|
||||||
|
"thumbs_generated": thumbs_generated,
|
||||||
|
"duration_sec": duration,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user