A checksum-verified ingest script for offloading Sony FX3 MP4 clips from an SD card into a date-organized folder structure, ready for import into DaVinci Resolve.
One bash script, no dependencies beyond exiftool. It reads the card once,
files each clip under its true local shooting date, verifies every copy with
SHA-256, and refuses to overwrite or silently skip a file it hasn't proven is
identical — so that when it says the ingest is clean, the card really is safe
to format.
$ ./fx3_ingest.sh /Volumes/SDCARD/PRIVATE/M4ROOT/CLIP ~/Footage
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FX3 Ingest
Source: /Volumes/SDCARD/PRIVATE/M4ROOT/CLIP
Destination: /Users/you/Footage
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ Verified: 2026-07-20/C0001.MP4
✓ Verified: 2026-07-20/C0001M01.XML
✓ Verified: 2026-07-20/C0002.MP4
! NO GYRO: 2026-07-20/C0003.MP4 (no rtmd track — not stabilizable in Gyroflow)
[##############----------------] 47% (7/15 files, 4.2GB/9.8GB)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Copied & verified: 15
Skipped (existing): 0
Without gyro data: 1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Resulting layout:
Footage/
└── 2026-07-20/
├── C0001.MP4
├── C0001.MP4.sha256
├── C0001M01.XML
└── C0001M01.XML.sha256
Offshoot no longer runs on current
macOS. DaVinci Resolve's Clone tool does verified copying but no
metadata-based folder templating. Existing open-source options
(phockup,
Elodie) either lack folder templating
or rename files in ways that conflict with keeping native FX3 filenames
(C0001.MP4, etc.) intact. fx3_ingest.sh is a small bash script wrapping
exiftool and shasum that does exactly what's needed and nothing more.
- Recursively finds
.MP4files in a source directory (the SD card's clip folder). - Derives each clip's shooting date and files it as
<destination>/<YYYY-MM-DD>/<filename>. See Date handling — this is the part most likely to go wrong, and it's worth understanding. - Copies each clip's Sony XML metadata sidecar (e.g.
C0001M01.XML) into the same date folder, so the camera metadata survives card formatting. - Copies each file, preserving original filenames and timestamps. The
source is read only once: it's hashed while being copied to a
.partstaging file, which is renamed into place only after verification — the destination never holds an unverified file. - Verifies every copy with a SHA-256 checksum (source vs. destination) and
writes a
.sha256sidecar next to each copied file, so the archive can be re-verified later without the original SD card. - Is idempotent — re-running skips files already ingested, and refuses to silently overwrite or skip a different file that happens to share a name, whether that file is already in the archive or is another clip in the same run. See Name collisions.
- Flags clips with no gyro/IMU track, so you learn a clip can't be stabilized while the card is still in the reader. See Gyroflow.
- Discards the staged copy and reports failure if a checksum mismatch or a write error occurs.
- Checks the destination has enough free space before starting, counting only the files that actually need copying.
- Shows a live, byte-weighted progress bar while it works — and drops the colours and the bar automatically when output is piped to a file.
macOS only — the script uses BSD stat and df syntax, and refuses to run
elsewhere. shasum ships with macOS; exiftool does not:
brew install exiftoolThen grab the script — it's a single file, so there's nothing to install:
curl -O https://raw.githubusercontent.com/jpmontez/fx3-ingest/main/fx3_ingest.sh
chmod +x fx3_ingest.shOr clone the repo if you'd rather track updates with git pull:
git clone https://github.com/jpmontez/fx3-ingest.git
cd fx3-ingest./fx3_ingest.sh <source_dir> <destination_dir>
# Example:
./fx3_ingest.sh /Volumes/SDCARD/PRIVATE/M4ROOT/CLIP /Volumes/MyDrive/Projects/Shoot_Name/Footage--help lists every flag; --version prints the version, which is worth
including in a bug report.
--dry-run prints exactly what would be copied, where, and why, and writes
nothing. Start here on your first run — and on any ingest you plan to
format the card after:
./fx3_ingest.sh --dry-run /Volumes/SDCARD/PRIVATE/M4ROOT/CLIP ~/FootageCOPY C0001.MP4 → 2026-07-20/ 512.1MB
COPY C0001M01.XML → 2026-07-20/ 2144B
COPY C0002.MP4 → 2026-07-20/ 318.4MB (no gyro)
SKIP C0003.MP4 → 2026-07-20/ (already ingested)
--verify re-hashes against the .sha256 sidecars and reports corruption,
missing files, and files that never got a sidecar. It exits non-zero if
anything is wrong, so it's cron-friendly:
./fx3_ingest.sh --verify /Volumes/MyDrive/Projects/Shoot_Name/FootageIt also takes a single date folder, when you want to spot check one day rather than re-hash the whole archive:
./fx3_ingest.sh --verify ~/Footage/2026-07-20 # one dayFor a single clip, use shasum directly — the sidecars are in its standard
format. Note that shasum -c resolves the filename in the sidecar relative to
the current directory, not to the sidecar's location, so you have to be in
the file's folder:
cd ~/Footage/2026-07-20 && shasum -a 256 -c C0001.MP4.sha256An MP4's QuickTime CreateDate tag is stored in UTC. Using it directly
files anything shot after roughly 19:00 local into the next day's folder,
which splits a single evening shoot across two folders. The script resolves
the true local date in this order:
CreationDateValue— Sony's NRT tag, which carries the camera's own UTC offset (e.g.2026-07-20T19:01:58-05:00). This is the wall-clock time the operator actually saw, and is the preferred source.CreateDatewith-api QuickTimeUTC=1— tells exiftool to treat the tag as UTC and convert to this Mac's local time. Correct as long as the Mac and the camera share a timezone.- Filesystem mtime.
Unknown-Dateas a last resort.
All clips on the card are read in a single batched exiftool call, since
exiftool costs roughly half a second of interpreter startup per invocation.
FX3 cards restart numbering at C0001.MP4 after a format, so two cards from
the same shoot day will contain different clips with identical names. The
script guards both directions of this.
Against the archive. Whether a file is already ingested is decided by
comparing the source against the destination (size + mtime). If a
file of that name already exists but is a different file, that's reported
as a collision, the existing file is left untouched, the new one is not
copied, and the script exits non-zero with Do not format the card.
Against the rest of the same run. Two different clips in one source tree can also target the same destination path — two card dumps under one parent, or clips spread across subfolders. Those are grouped and reported together, and neither is copied:
COLLISION C0001.MP4 → 2026-07-20/
two or more source files map to this path:
/cards/cardA/C0001.MP4 (512099328B)
/cards/cardB/C0001.MP4 (498012160B)
Ingest each card into its own destination folder.
Either way: ingest the second card into a different destination folder, or rename the clips, then re-run.
If two source files are byte-identical (same name, size, and mtime — a card
and a backup of it under one parent), that isn't a collision. The first is
copied and the rest are reported as DUPLICATE and skipped, since nothing is
lost.
Size + mtime is a deliberate fast path: ingest preserves mtime via touch -r,
so a genuine re-run skips without reading a byte. The trade-off is that
routine ingest won't detect bit rot in files already in the archive — that's
what --verify is for.
The FX3 records gyro/IMU data into an rtmd timed-metadata track inside
the MP4. Gyroflow reads that track out of the original file, so:
- Ingest preserves it. The copy is byte-exact and checksum-verified, so
the gyro data survives untouched. Point Gyroflow at the ingested
.MP4. - The
.XMLsidecar is not a substitute. That's Sony'snrtm(Non-Real Time Metadata) — a summary of the clip, with no gyro in it. - Re-encoding or trimming outside Gyroflow destroys it. Stabilize from the ingested original, not from a transcode or a Resolve render.
Sony doesn't write the IMU track in every recording mode, and proxy clips
never carry usable gyro. So ingest checks each clip and flags the ones that
have no rtmd track:
! NO GYRO: 2026-07-20/C0003.MP4 (no rtmd track — not stabilizable in Gyroflow)
The point is to tell you while the card is still in the reader. This is informational — a clip shot in a mode without IMU data is not an ingest failure, so it never changes the exit code.
The check is free: it reads MetaFormat from the file's moov header in the
same batched exiftool call that resolves dates. Confirming the samples
themselves are populated would need exiftool -ee, which walks the whole
mdat — a second full read of every clip, which is exactly what the
single-read copy exists to avoid.
Import into DaVinci Resolve and use Media Pool → right-click → Auto-Bin by Metadata (or drag the date folders in directly) to mirror the folder structure into bins.
- macOS only — uses BSD
statanddfsyntax. - Handles
.MP4clips and their Sony XML metadata sidecars. RAW and other Sony formats aren't currently supported. - Sony proxy clips (
SUB/C0001S01.MP4) are detected and skipped, not ingested — they carry no usable gyro. The count is reported. - macOS AppleDouble stubs (
._C0001.MP4), which appear on exFAT cards, are ignored. - No multi-destination (simultaneous backup) cloning — single destination only, by design.
- No reel/card-ID folder level; the FX3 writes no usable card identifier
(
serialNois4294967295, andumidRefis per-clip). Collisions are caught rather than structurally prevented. - Paths containing tabs or newlines are rejected rather than mishandled.
Issues and PRs are welcome. See CONTRIBUTING.md for the
manual test checklist and a heads-up about the two areas of this script that
have previously caused silent data loss. CLAUDE.md documents
the architecture and the reasoning behind the trickier decisions.
MIT.