96 lines
2.8 KiB
Python
96 lines
2.8 KiB
Python
"""Immich API client for fetching people and assets."""
|
|
|
|
import logging
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
import requests
|
|
|
|
from .config import Config, get_headers
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_people() -> list[dict]:
|
|
"""Fetch all people from Immich."""
|
|
try:
|
|
resp = requests.get(
|
|
f"{Config.IMMICH_URL}/api/people",
|
|
headers=get_headers(),
|
|
timeout=10,
|
|
)
|
|
resp.raise_for_status()
|
|
return resp.json().get("people", [])
|
|
except requests.RequestException as e:
|
|
logger.error(f"Failed to fetch people: {e}")
|
|
return []
|
|
|
|
|
|
def fetch_all_assets(person: dict) -> list[dict]:
|
|
"""Fetch all assets for a person with pagination."""
|
|
name = person.get("name", "Unknown")
|
|
person_id = person["id"]
|
|
url = f"{Config.IMMICH_URL}/api/search/metadata"
|
|
page_size = 1000
|
|
|
|
logger.info(f"Fetching assets for {name}...")
|
|
|
|
assets = []
|
|
for page in range(1, 1000): # Safety limit
|
|
try:
|
|
resp = requests.post(
|
|
url,
|
|
json={"personIds": [person_id], "size": page_size, "page": page},
|
|
headers=get_headers(),
|
|
timeout=30,
|
|
)
|
|
|
|
if not resp.ok:
|
|
logger.error(f"Error fetching assets for {name} (page {page}): {resp.status_code}")
|
|
break
|
|
|
|
page_assets = resp.json().get("assets", [])
|
|
if isinstance(page_assets, dict):
|
|
page_assets = page_assets.get("items", [])
|
|
|
|
if not page_assets:
|
|
break
|
|
|
|
assets.extend(page_assets)
|
|
logger.debug(f"Fetched page {page}, total: {len(assets)}")
|
|
|
|
if len(page_assets) < page_size:
|
|
break
|
|
|
|
except requests.RequestException as e:
|
|
logger.error(f"Exception fetching assets for {name}: {e}")
|
|
break
|
|
|
|
return assets
|
|
|
|
|
|
def filter_recent_assets(assets: list[dict], years: int | None = None) -> list[dict]:
|
|
"""Filter assets to keep only those from the last N years."""
|
|
years = years or Config.YEARS_FILTER
|
|
cutoff = datetime.now(timezone.utc) - timedelta(days=365 * years)
|
|
|
|
logger.debug(f"Filtering assets older than {years} years ({cutoff})")
|
|
|
|
recent, skipped = [], 0
|
|
for asset in assets:
|
|
created_at_str = asset.get("fileCreatedAt")
|
|
if not created_at_str:
|
|
continue
|
|
|
|
try:
|
|
# Handle ISO8601 with 'Z' suffix
|
|
created_at = datetime.fromisoformat(created_at_str.replace("Z", "+00:00"))
|
|
if created_at > cutoff:
|
|
recent.append(asset)
|
|
else:
|
|
skipped += 1
|
|
except ValueError:
|
|
continue
|
|
|
|
logger.info(f"Retained {len(recent)} assets (filtered {skipped} old assets).")
|
|
return recent
|