Files
intotheeast-com/services/travel-memories/app/routes/export.py
T
2026-06-21 17:12:37 +02:00

226 lines
7.3 KiB
Python

import re
import shutil
from pathlib import Path
from flask import Blueprint, current_app, jsonify, render_template, request
from app.immich import ImmichClient
from app.state import load_state, save_state
bp = Blueprint("export", __name__)
def slugify(text: str) -> str:
text = text.lower().strip()
text = re.sub(r"[^\w\s-]", "", text)
return re.sub(r"[\s_-]+", "-", text).strip("-")
def _client():
return ImmichClient(
current_app.config["IMMICH_URL"],
current_app.config["IMMICH_API_KEY"],
)
@bp.get("/export")
def export_view():
album_id = request.args["album_id"]
state = load_state(album_id, current_app)
to_export = [g for g in state.groups if g.status == "written"]
skipped = [g for g in state.groups if g.status == "skipped"]
return render_template(
"phase6.html",
state=state,
to_export=to_export,
skipped=skipped,
current_phase="export",
album_id=album_id,
phase_stale=state.phase_stale,
notes_content=state.notes,
)
@bp.post("/export/run")
def run_export():
body = request.get_json()
album_id = body["album_id"]
state = load_state(album_id, current_app)
pages_dir = Path(current_app.config["PAGES_DIR"])
client = _client()
photo_map = {p.id: p for p in state.photos}
exported = 0
all_failed = []
for group in state.groups:
if group.status != "written":
continue
title_slug = slugify(group.title or group.date or "entry")
if group.entry_type == "journal":
folder_name = f"{group.date}-{title_slug}.entry"
dest = pages_dir / "01.trips" / state.grav_trip_slug / "01.dailies" / folder_name
md_file = "entry.md"
template = "entry"
else:
folder_name = f"{title_slug}.story"
dest = pages_dir / "01.trips" / state.grav_trip_slug / "04.stories" / folder_name
md_file = "story.md"
template = "story"
if dest.exists():
save_state(state, current_app)
return jsonify({"conflict": True, "path": str(dest)})
dest.mkdir(parents=True, exist_ok=True)
# Download photos
failed = []
hero_filename = None
photo_num = 1
for pid in group.photo_ids:
photo = photo_map.get(pid)
if not photo:
continue
filename = f"photo-{photo_num}.jpg"
try:
data = client.get_original(pid)
(dest / filename).write_bytes(data)
if pid == group.hero_photo_id or photo_num == 1:
hero_filename = filename
photo_num += 1
except Exception as e:
current_app.logger.warning("Failed to download asset %s: %s", pid, e)
failed.append(pid)
# Build frontmatter
date_str = (group.date + " 12:00") if group.date else ""
if group.entry_type == "journal":
frontmatter = (
f"---\n"
f"title: '{group.title}'\n"
f"date: '{date_str}'\n"
f"template: {template}\n"
f"published: true\n"
f"location_city: '{group.location_city}'\n"
f"location_country: '{group.location_country}'\n"
f"hero_image: {hero_filename or ''}\n"
f"---\n"
)
else:
frontmatter = (
f"---\n"
f"title: '{group.title}'\n"
f"date: '{date_str}'\n"
f"template: {template}\n"
f"published: true\n"
f"hero_image: {hero_filename or ''}\n"
f"---\n"
)
body_text = group.body or ""
if group.shortcode_hints:
body_text += f"\n<!-- shortcode hints:\n{group.shortcode_hints}\n-->"
(dest / md_file).write_text(frontmatter + "\n" + body_text)
group.status = "exported"
exported += 1
all_failed.extend(failed)
save_state(state, current_app)
return jsonify({"ok": True, "exported": exported, "failed": all_failed})
@bp.post("/export/overwrite")
def overwrite_export():
body = request.get_json()
album_id = body["album_id"]
conflict_path = Path(body["path"])
state = load_state(album_id, current_app)
pages_dir = Path(current_app.config["PAGES_DIR"])
client = _client()
photo_map = {p.id: p for p in state.photos}
# Remove the conflicting folder so the run loop can proceed past it
if conflict_path.exists():
shutil.rmtree(conflict_path)
exported = 0
all_failed = []
for group in state.groups:
if group.status != "written":
continue
title_slug = slugify(group.title or group.date or "entry")
if group.entry_type == "journal":
folder_name = f"{group.date}-{title_slug}.entry"
dest = pages_dir / "01.trips" / state.grav_trip_slug / "01.dailies" / folder_name
md_file = "entry.md"
template = "entry"
else:
folder_name = f"{title_slug}.story"
dest = pages_dir / "01.trips" / state.grav_trip_slug / "04.stories" / folder_name
md_file = "story.md"
template = "story"
if dest.exists():
save_state(state, current_app)
return jsonify({"conflict": True, "path": str(dest)})
dest.mkdir(parents=True, exist_ok=True)
failed = []
hero_filename = None
photo_num = 1
for pid in group.photo_ids:
photo = photo_map.get(pid)
if not photo:
continue
filename = f"photo-{photo_num}.jpg"
try:
data = client.get_original(pid)
(dest / filename).write_bytes(data)
if pid == group.hero_photo_id or photo_num == 1:
hero_filename = filename
photo_num += 1
except Exception as e:
current_app.logger.warning("Failed to download asset %s: %s", pid, e)
failed.append(pid)
date_str = (group.date + " 12:00") if group.date else ""
if group.entry_type == "journal":
frontmatter = (
f"---\n"
f"title: '{group.title}'\n"
f"date: '{date_str}'\n"
f"template: {template}\n"
f"published: true\n"
f"location_city: '{group.location_city}'\n"
f"location_country: '{group.location_country}'\n"
f"hero_image: {hero_filename or ''}\n"
f"---\n"
)
else:
frontmatter = (
f"---\n"
f"title: '{group.title}'\n"
f"date: '{date_str}'\n"
f"template: {template}\n"
f"published: true\n"
f"hero_image: {hero_filename or ''}\n"
f"---\n"
)
body_text = group.body or ""
if group.shortcode_hints:
body_text += f"\n<!-- shortcode hints:\n{group.shortcode_hints}\n-->"
(dest / md_file).write_text(frontmatter + "\n" + body_text)
group.status = "exported"
exported += 1
all_failed.extend(failed)
save_state(state, current_app)
return jsonify({"ok": True, "exported": exported, "failed": all_failed})