Surface exiftool write failures so thermal stretch can't silently drop GPS
On a (near-)full disk, exiftool's temp-copy write partially fails, producing stretched thermal PNGs with most tags but no GPS. The stretch step was ignoring exiftool's exit code, so this happened silently. stretch_thermal._embed_exif now verifies GPS landed (spot-checks first/middle/ last frames), retries once on a transient miss, and otherwise raises a clear "free up disk space and re-run" error (the pixels are written; only metadata is missing). embed_metadata.embed_session's exit-code error gets the same hint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
9629a07399
commit
8dadd71f9e
|
|
@ -97,6 +97,10 @@ writable in stock exiftool). It uses this geometry — **not** the raw IMU.
|
||||||
`tools/exiftool.exe`, the OliverBetz user install (`%LOCALAPPDATA%\Programs\ExifTool`),
|
`tools/exiftool.exe`, the OliverBetz user install (`%LOCALAPPDATA%\Programs\ExifTool`),
|
||||||
else auto-installed via `winget install OliverBetz.ExifTool`. (SourceForge auto-download
|
else auto-installed via `winget install OliverBetz.ExifTool`. (SourceForge auto-download
|
||||||
is unreliable — GDPR/consent wall.)
|
is unreliable — GDPR/consent wall.)
|
||||||
|
- **Disk space:** exiftool writes metadata via a temp copy, so a (near-)full disk causes
|
||||||
|
partial/failed writes — symptom is an image with most tags but **no GPS**. The embed and
|
||||||
|
stretch steps now check exiftool's exit code, verify GPS landed, retry once, and raise a
|
||||||
|
clear "full disk" error instead of silently producing GPS-less files.
|
||||||
- **GPS source:** cam frames use the fused INS `position` block (falls back to raw `gps`);
|
- **GPS source:** cam frames use the fused INS `position` block (falls back to raw `gps`);
|
||||||
thermal has only `gps`. UTC time = GPS week/tow − 18 leap seconds.
|
thermal has only `gps`. UTC time = GPS week/tow − 18 leap seconds.
|
||||||
- **Thermal stretch window:** default = pooled 1–99th percentile across the whole session
|
- **Thermal stretch window:** default = pooled 1–99th percentile across the whole session
|
||||||
|
|
|
||||||
|
|
@ -280,7 +280,9 @@ def embed_session(session, out_root=None, gps_source="position", exiftool=None,
|
||||||
proc.wait()
|
proc.wait()
|
||||||
argfile.unlink(missing_ok=True)
|
argfile.unlink(missing_ok=True)
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
raise RuntimeError(f"exiftool exited with code {proc.returncode}")
|
raise RuntimeError(
|
||||||
|
f"exiftool exited with code {proc.returncode}. The usual cause is a FULL DISK "
|
||||||
|
"(metadata is written via a temp copy) - free up space and re-run.")
|
||||||
|
|
||||||
progress(1.0, "Done")
|
progress(1.0, "Done")
|
||||||
log(f"\nDone. {updated} files tagged.\nOutput: {out_root}")
|
log(f"\nDone. {updated} files tagged.\nOutput: {out_root}")
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ def _embed_exif(pngs, out_dir: Path, exiftool=None, log=None):
|
||||||
except BaseException as e: # noqa: BLE001 (ensure_exiftool may sys.exit)
|
except BaseException as e: # noqa: BLE001 (ensure_exiftool may sys.exit)
|
||||||
log(f" ! skipping EXIF embed - exiftool unavailable ({e})")
|
log(f" ! skipping EXIF embed - exiftool unavailable ({e})")
|
||||||
return
|
return
|
||||||
arg_lines = []
|
arg_lines, dsts = [], []
|
||||||
for p in pngs:
|
for p in pngs:
|
||||||
sidecar = p.with_suffix(".json")
|
sidecar = p.with_suffix(".json")
|
||||||
if not sidecar.exists():
|
if not sidecar.exists():
|
||||||
|
|
@ -174,17 +174,38 @@ def _embed_exif(pngs, out_dir: Path, exiftool=None, log=None):
|
||||||
except Exception: # noqa: BLE001
|
except Exception: # noqa: BLE001
|
||||||
continue
|
continue
|
||||||
dst = out_dir / (p.stem + ".png")
|
dst = out_dir / (p.stem + ".png")
|
||||||
|
dsts.append(dst)
|
||||||
arg_lines += ["-overwrite_original", *embed.build_tags(data, {}, None, "gps"),
|
arg_lines += ["-overwrite_original", *embed.build_tags(data, {}, None, "gps"),
|
||||||
str(dst), "-execute"]
|
str(dst), "-execute"]
|
||||||
if not arg_lines:
|
if not arg_lines:
|
||||||
return
|
return
|
||||||
argfile = out_dir / "_exif_args.txt"
|
argfile = out_dir / "_exif_args.txt"
|
||||||
argfile.write_text("\n".join(arg_lines) + "\n", encoding="utf-8")
|
argfile.write_text("\n".join(arg_lines) + "\n", encoding="utf-8")
|
||||||
|
cmd = [et, "-m", "-charset", "UTF8", "-charset", "filename=UTF8", "-@", str(argfile)]
|
||||||
|
samples = {dsts[0], dsts[len(dsts) // 2], dsts[-1]} # spot-check first/mid/last
|
||||||
|
|
||||||
|
def gps_written():
|
||||||
|
return all(subprocess.run([et, "-s", "-s", "-s", "-GPSLatitude", str(s)],
|
||||||
|
capture_output=True, text=True).stdout.strip() for s in samples)
|
||||||
|
|
||||||
log("Embedding GPS / orientation EXIF into the stretched frames ...")
|
log("Embedding GPS / orientation EXIF into the stretched frames ...")
|
||||||
subprocess.run([et, "-m", "-charset", "UTF8", "-charset", "filename=UTF8",
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
"-@", str(argfile)], capture_output=True, text=True)
|
if proc.returncode == 0 and not gps_written():
|
||||||
|
log(" ! GPS not present after first pass - retrying once ...")
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
argfile.unlink(missing_ok=True)
|
argfile.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
err = (proc.stderr or "").strip()
|
||||||
|
if err:
|
||||||
|
log(err)
|
||||||
|
if proc.returncode != 0 or not gps_written():
|
||||||
|
raise RuntimeError(
|
||||||
|
"Could not embed GPS/EXIF into the stretched PNGs (exiftool exit "
|
||||||
|
f"{proc.returncode}). The usual cause is a FULL DISK - free up space and re-run. "
|
||||||
|
"The stretched images were written; only the embedded metadata is missing."
|
||||||
|
+ (f"\nexiftool: {err}" if err else ""))
|
||||||
|
log(f"EXIF embedded into {len(dsts)} stretched frames.")
|
||||||
|
|
||||||
|
|
||||||
def stretch_session(session, out_dir=None, colormap="inferno", lo=None, hi=None,
|
def stretch_session(session, out_dir=None, colormap="inferno", lo=None, hi=None,
|
||||||
embed_exif=True, exiftool=None, progress=None, log=None) -> dict:
|
embed_exif=True, exiftool=None, progress=None, log=None) -> dict:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue