fix: v0.5.20 — migration safety, URL normalization, image rejection, atomic writes

- upload_tracker: replace executescript() in _migrate_schema_v2 with
  individual execute() calls inside a transaction so a crash between DROP
  and RENAME rolls back instead of permanently destroying tracked_assets
- frigate_api: _get_frigate_url now strips leading/trailing whitespace
  before rstrip('/') so whitespace-only FRIGATE_URL is treated as unset
- executor: upload_to_frigate now uses _get_frigate_url() eliminating
  double-slash upload paths when FRIGATE_URL has a trailing slash
- executor: corrupt thumbnail (resp.ok=True, Image.open fails) now calls
  mark_rejected() so permanently broken assets are not retried forever
- upload_tracker: reset_person now uses _get_frigate_url() instead of
  inline os.environ.get('FRIGATE_URL', '').strip()
- image_processing: _save_jpeg writes to a .tmp file and calls
  os.replace() so a disk-full error never leaves a truncated JPEG
- cli: _handle_duplicate_people falls back to local deduplication when
  all Immich merges fail, preventing two jobs from overwriting the same
  Frigate folder
- config: _getenv_optional_float now delegates to _getenv_num() like
  _getenv_optional_int, eliminating the inconsistent duplicate
- reconcile: _ts() uses rsplit('.', 1)[0] instead of .replace('.webp','')
  so FIFO mapping works with any Frigate training-file extension
This commit is contained in:
2026-06-15 03:26:51 +00:00
parent 728b84dc8c
commit 95fb39ed50
9 changed files with 82 additions and 35 deletions
+7 -2
View File
@@ -13,6 +13,7 @@ from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn
from .config import Config, get_headers
from .frigate_api import (
_get_frigate_url,
delete_frigate_person_files,
get_all_frigate_person_files,
get_frigate_person_files,
@@ -158,7 +159,11 @@ def execute_jobs(jobs: list[dict]) -> None:
try:
img = Image.open(BytesIO(resp.content))
except Exception:
logger.warning("Invalid image data for asset %s", asset["id"])
# resp.ok=True but content is unreadable — corrupt Immich
# thumbnail. Mark rejected so this asset isn't retried
# indefinitely on future runs.
logger.warning("Invalid image data for asset %s — marking rejected", asset["id"])
mark_rejected(asset["id"], person_name=name)
img = None
else:
img = None
@@ -216,7 +221,7 @@ def upload_to_frigate(jobs: list[dict]) -> None:
rprint("[dim]No jobs to upload.[/dim]")
return
frigate_url = os.environ.get("FRIGATE_URL", "")
frigate_url = _get_frigate_url()
if not frigate_url:
rprint("[yellow]⚠️ FRIGATE_URL not set, skipping upload.[/yellow]")
return