Skip to content

Getting started

This page walks the full media-ingest workflow on a real project, from an unorganized dump of trip footage to renamed, per-day folders you can drop into DaVinci Resolve.

Requirements

  • Python 3.12 or newer.
  • uv to install and run the tool.
  • exiftool on your PATH. The scan step shells out to it to read metadata.

exiftool is the only external dependency, and it is only needed at scan time. Everything after scan works from the manifest that scan writes.

Install

Clone the repository and sync its dependencies with uv:

git clone https://github.com/aaronwolen/media-ingest.git
cd media-ingest
uv sync

You can then invoke the CLI with uv run media-ingest from anywhere, or install it onto your PATH with uv tool install . and call it as media-ingest. The examples below use the bare media-ingest form; prefix them with uv run if you have not installed the tool globally.

A real project

Say your trip footage has been copied off the cameras into a folder on an external drive, with each device dumped into its own subfolder:

/Volumes/T7/PROJECTS/2026-03_my-trip/
  DCIM/                # DJI Osmo cards
  iphone-export/       # AirDrop / Photos export
  mic/                 # DJI Mic internal recordings

1. Initialize

Change into the project directory and write a config file. Pass the trip timezone and the trip date range:

cd "/Volumes/T7/PROJECTS/2026-03_my-trip"
media-ingest init --timezone -04:00 --start 2026-03-14 --end 2026-03-22

This writes ingest.toml at the project root. init also pre-fills the [scan] include list with the media-looking subdirectories it found, but you should open the file and confirm it lists only the folders that hold raw, unrenamed media. See init and the timezone model for details on the timezone option.

2. Scan

Read metadata and content hashes for every file in the included directories:

media-ingest scan

Scan is read-only. It runs exiftool once over all the files, classifies each into a device profile, computes a fast content hash, and writes everything to .ingest/manifest.json. It prints a per-device count so you can sanity-check that every camera is recognized. If any files come back as unknown, the next step will list them.

3. Plan

Compute the renames, run the quality checks, and build the HTML timeline report:

media-ingest plan

Plan is also read-only. It writes .ingest/plan.json (the exact list of moves) and .ingest/report.html (the visual review), and prints a summary of planned renames, exclusions, and warnings. Nothing has moved yet.

4. Review the report

Open the timeline report and look it over:

open .ingest/report.html

The report lays out each day as a set of device swimlanes. A wrong timezone offset shows up immediately as mic bars that do not line up with the camera clips they were recorded with. Use the day filter chips and drag-to-zoom to inspect a single day closely. See the timeline report page for a full tour.

5. Spot-check with align

If a DJI clip looks like it is in the wrong place, or a timezone warning fired, cross-check it against iPhone media, whose clock is authoritative:

media-ingest align DJI_20260316184422_0105_D.MP4

This lists everything captured around that clip's corrected time, with iPhone rows highlighted. Open a neighboring iPhone photo and confirm the scenes agree. See align.

6. Apply

When the plan looks right, execute it:

media-ingest apply

Apply asks for confirmation, then renames each file. It re-hashes every file immediately before moving it, refuses to overwrite anything, only renames within the project volume, and appends each completed move to .ingest/apply-log.jsonl. Pass --yes to skip the prompt.

The result is per-day folders with names that sort chronologically across all devices:

media/source/action_cameras/2026-03-16/
  2026-03-16T184422 - OsmoPocket3 - 0105.mp4
media/source/iphones/2026-03-16/
  2026-03-16T184510 - iPhone 17 Pro - IMG_4821.mov

7. Undo if needed

Every move is journaled, so the whole operation reverses cleanly:

media-ingest undo

Because apply is idempotent, an interrupted run can simply be re-run instead of undone. See the safety model for how this works.

The state directory

Everything media-ingest produces lives in a .ingest/ directory at the project root:

  • manifest.json is the output of scan: metadata, device detection, and hashes.
  • plan.json is the output of plan: the exact moves, exclusions, and warnings.
  • report.html is the self-contained timeline report.
  • apply-log.jsonl is the journal that apply writes and undo reads.

You can safely delete .ingest/ to start over from scan.