Skip to content

Safety model

media-ingest is built around one idea: renaming thousands of files should never be a single irreversible action you take on trust. The work is split into read-only steps you review, and the one destructive step is journaled and reversible.

Read-only until you say go

Four of the seven commands never touch your media files:

  • scan reads metadata and hashes.
  • plan computes the renames and builds the report.
  • align reads the manifest to show cross-device context.
  • report rewrites the HTML report from the plan.

Everything they produce lives in the .ingest/ directory. Your media does not move until you run apply. You can run scan and plan as many times as you want, look at the results, and throw them away by deleting .ingest/.

The plan is a reviewable artifact

plan is a pure function of the manifest and config. It writes plan.json, the exact list of moves, and report.html, a visual timeline. A wrong timezone offset, a misidentified device, or an unexpected exclusion is visible before anything happens, both in the printed summary and in the timeline report. Nothing about the plan depends on the state of the filesystem outside .ingest/, so re-running it is always safe.

The timezone cross-check

The configured trip timezone is checked against the cameras themselves. DJI video filenames embed local wall time while the metadata is UTC, so the true offset each camera's clock was set to is recoverable per camera per day. plan compares that against the timezone in ingest.toml and warns on any mismatch, for example camera clock implies UTC-5 but config says UTC-4. This catches the most damaging mistake, a wrong trip timezone, before it is baked into filenames. See the timezone model.

What apply guarantees

Each individual move is guarded:

  • The source is re-hashed immediately before it is moved. If the content no longer matches the hash recorded at scan time, that move aborts. The file changed since you scanned.
  • Nothing is ever overwritten. If the destination already exists with different content, that move is an error.
  • Renames happen only within the project volume. If the source is on a different volume than the project root, that move is an error, because a cross-volume "rename" would be a copy.
  • Every completed move is appended to apply-log.jsonl before the next move starts.

Errored files are left untouched and listed, and apply exits non-zero if any move errored. media-ingest never copies or rewrites file content; it only renames.

Files it will not guess about

Files that cannot be confidently placed are excluded with a stated reason rather than moved to a best guess:

  • empty or aborted recordings (under 4 KB),
  • duplicate content (confirmed with a full-file hash, not just the fast hash),
  • unrecognized devices or types,
  • files with no usable capture timestamp.

Excluded files stay exactly where they are and are listed in the plan and the report.

Idempotent apply versus undo

This is the most important operational property, so it is worth being precise about.

apply is idempotent. If a file has already been moved to its destination, apply recognizes that (the source is gone, the destination is present with the matching hash) and counts it as "already done" instead of repeating or erroring. That means an interrupted apply, from a full disk, an unplugged drive, or a Ctrl-C, can simply be re-run. You do not undo and start over; you run apply again and it finishes the remaining moves.

undo is for changing your mind. It replays the journal in reverse, restoring every file to its original path. undo also records its own reversals in the journal, so it is itself idempotent: running it twice does not error on files that are already back home, and an apply, undo, apply, undo sequence stays consistent.

In short:

  • Interrupted apply: run apply again.
  • Regret a completed apply: run undo.