import re from pathlib import Path from flask import Blueprint, current_app, redirect, render_template, request from app.immich import ImmichClient from app.state import TripState, Photo, load_state, save_state bp = Blueprint("albums", __name__) def _sanitise_slug(s: str) -> str: s = s.strip().lower() s = re.sub(r'[^a-z0-9-]+', '-', s) return s.strip('-') def _client(): return ImmichClient(current_app.config["IMMICH_URL"], current_app.config["IMMICH_API_KEY"]) @bp.get("/") def index(): try: albums = _client().list_albums() error = None except ConnectionError as e: albums = [] error = str(e) state_dir = Path(current_app.config["STATE_DIR"]) for album in albums: album["has_state"] = (state_dir / f"{album['id']}.json").exists() return render_template("phase1.html", albums=albums, error=error, current_phase="", album_id=None, phase_stale=[], notes_content="") @bp.post("/select") def select(): album_ids = request.form.getlist("album_ids[]") grav_trip_slug = _sanitise_slug(request.form["grav_trip_slug"]) start_over = request.form.get("start_over") == "1" if len(album_ids) == 1: primary_id = album_ids[0] else: primary_id = "__merged__" + "_".join(sorted(album_ids)) existing = load_state(primary_id, current_app) if existing and not start_over: return redirect(f"/{existing.phase}?album_id={primary_id}") # Fetch and merge assets, deduplicating by asset ID all_assets = {} album_name_parts = [] for aid in album_ids: album = _client().get_album(aid) album_name_parts.append(album["albumName"]) for asset in album["assets"]: if asset["id"] not in all_assets: all_assets[asset["id"]] = asset photos = [ Photo(id=a["id"], original_filename=a["originalFileName"], local_datetime=a["localDateTime"]) for a in sorted(all_assets.values(), key=lambda x: x["localDateTime"]) ] for i, p in enumerate(photos): p.order = i state = TripState( album_id=primary_id, album_name=", ".join(album_name_parts), grav_trip_slug=grav_trip_slug, photos=photos, ) save_state(state, current_app) return redirect(f"/triage?album_id={primary_id}")