Raw DV / HDV tape capture over FireWire for macOS.
tapecap pulls the bitstream a DV or HDV camcorder/deck sends over FireWire
(IEEE 1394) and writes it to disk exactly as it comes off the tape —
nothing demuxed, nothing re-encoded:
| Tape format | Bus protocol | What tapecap writes |
|---|---|---|
| DV (incl. DVCAM / Digital8) | IEC 61883-2 | raw DIF byte stream (.dv) |
| HDV | IEC 61883-4 | raw MPEG-2 Transport Stream, 188-byte TS packets (.m2t / .ts) — video and audio (MPEG-1 Layer II) and all PSI/metadata |
On macOS you can already capture SD DV through AVFoundation (e.g. with
ffmpeg's avfoundation input). But for HDV, AVFoundation deliberately
demuxes the incoming MPEG-2 Transport Stream and only hands back the MPEG-2
video elementary stream — so you lose the audio and the transport-stream
metadata. The leading DV archival tool, MediaArea/MIPoPS dvrescue, hits the
same wall: on macOS it captures via AVFoundation and is DV-oriented.
The data is all there on the wire, though — Adobe Premiere Pro captures full HDV
on the same machines. The trick is to bypass AVFoundation and read the raw
isochronous IEC 61883 stream straight from IOKit's FireWire stack. That is
exactly what Apple's own AVCVideoServices framework (from the now-retired
FireWire SDK) does, via its MPEG2Receiver (HDV) and DVReceiver (DV) classes.
tapecap is a small command-line front end over that framework, rebuilt as a
modern universal binary.
See docs/BACKGROUND.md for the gory details.
list/info/capture— enumerate FireWire AV/C devices, inspect a deck's capabilities and current mode/timecode, and capture the raw stream.- Positioning helpers —
cue/jog/winddrive the deck without capturing, report the final position, and support--jsonfor scripts. - DV / HDV auto-detect — picks the format from the deck's output-plug signal
format; override with
--format dv|hdv. - Automatic transport control — sends AV/C PLAY on start and STOP at
the end; also stops on
--duration, Ctrl-C, or end of tape (--no-controllets you drive the deck yourself). - Live status while capturing — tape SMPTE timecode, recording date/time, size and a real coded-frame count, parsed straight from the stream (DV VAUX/subcode packs and the Sony HDV MPEG-TS AUX stream), plus a running count of transport continuity errors (the tape/transport damage signal).
- Stream diagnostics (HDV) — confirms the elementary streams present (video, audio, timecode AUX — the audio and AUX are exactly what AVFoundation drops) and reports the video geometry, frame rate and bit rate from the stream.
- Auto-naming — with no output path, files are named from the recording's
own date/time (e.g.
20101029-140926.m2t); pass-to stream to stdout. - Universal binary — one arm64 + x86_64 build for macOS 11–15.
- macOS 11 – 15. macOS 15 Sequoia is the last release that ships the
IOFireWireFamilydriver and the FireWire SDK headers; FireWire was removed in macOS 26 Tahoe, sotapecapcannot work there. Build and run on macOS ≤ 15. - Apple Silicon and Intel are both supported (universal binary).
- Apple Silicon and recent Intel Macs have no FireWire port: use the Apple Thunderbolt-to-FireWire adapter (plus a Thunderbolt-3→2 adapter on USB-C/TB3/TB4 Macs). This is the same setup Premiere/dvrescue users rely on.
- A DV/HDV camcorder or deck with a FireWire (i.LINK / DV) port.
brew install xingrz/tap/tapecap(Installs the prebuilt universal binary; no quarantine step needed.)
Each push produces a universal binary via GitHub Actions — grab the
tapecap-macos-15 artifact from the Actions tab, or download a release
tarball. Then:
tar -xzf tapecap-macos-universal.tar.gz
xattr -dr com.apple.quarantine tapecap # clear the "downloaded" quarantine
./tapecap --helpNeeds the Xcode command-line tools on macOS ≤ 15:
git clone https://github.com/xingrz/tapecap
cd tapecap
make # -> build/tapecap (universal arm64 + x86_64)
make install # optional, installs to /usr/local/binRather have an AI coding assistant drive tapecap for you? Install the bundled skill into your agent (Claude Code, etc.):
npx skills add xingrz/tapecapIt teaches the agent to enumerate FireWire devices, inspect a deck, capture the untouched DV/HDV bitstream, and losslessly post-process the result.
tapecap list # list connected FireWire AV/C devices
tapecap info [--guid <hex>] [--json] # show device capabilities, mode, timecode
tapecap capture [options] [output] # capture raw stream (omit output to auto-name)
tapecap cue [--guid <hex>] [--overlap <sec>] [--json] <timecode> # fast-wind to a timecode
tapecap jog [--guid <hex>] [--json] <forward|back> <sec> # short timed fast-wind
tapecap wind [--guid <hex>] [--timeout <sec>] [--json] <start|end> # rewind to start / wind to end
| Option | Meaning |
|---|---|
--guid <hex> |
Pick a device by its 64-bit GUID (default: first DV/HDV device) |
--format auto|dv|hdv |
Force the stream format (default: auto-detect from the deck) |
--duration <sec> |
Stop after N seconds (default: until Ctrl-C / end of tape) |
--eot-timeout <ms> |
Auto-stop after this much silence; 0 disables (default: 5000; --seek uses at least 15000 for stream startup) |
--seek <timecode> |
Fast-wind to this tape timecode before capturing |
--until <timecode> |
Stop once the tape timecode passes this point |
--overlap <sec> |
Pre-/post-roll kept around --seek / --until (default: 4) |
--no-control |
Don't send AV/C PLAY/STOP — you press play on the deck yourself |
-v, --verbose |
Also print the framework's internal log on stderr |
[output] |
File to write. Omit to auto-name from the recording's date/time; use - for stdout. |
info --json prints a single JSON object on stdout, including
timecode_readable and timecode. cue, jog, and wind always print a final
human-readable position on stderr:
Position: 00:12:34
Position: --:--:-- (blank/no timecode)
With --json, positioning commands also print one machine-readable line on
stdout:
{"ok":true,"timecode_readable":true,"timecode":"00:12:34"}
{"ok":true,"timecode_readable":false,"timecode":null,"reason":"blank/no-timecode"}While capturing, a live status line (tape SMPTE timecode, recording date/time, size, coded-frame count and a continuity-error count) updates in place on stderr — no flag needed. The timecode and recording date/time are read straight from the stream (DV VAUX/subcode packs and the Sony HDV MPEG-TS AUX stream). For HDV, the tool also prints once which elementary streams are present (video, audio, timecode AUX) and the video geometry / frame rate / bit rate.
The continuity-error count is detect-and-report only — it never interrupts the capture, so a damaged tape still yields the most complete raw dump possible; the count just tells you whether the transport was clean.
# Auto-detect DV vs HDV, roll the tape, stop at end of tape, and save as
# e.g. 20101029-140926.m2t (from the recording's own date/time):
tapecap capture
# Force DV, capture a fixed 60 seconds to a named file:
tapecap capture --format dv --duration 60 clip.dv
# Don't drive the transport; pipe a live HDV stream straight into ffmpeg:
tapecap capture --no-control - | ffmpeg -i - -c copy out.mkv
# Re-capture just one damaged section by tape timecode (e.g. a tapeflow gap):
tapecap capture --seek 00:12:30 --until 00:14:00 gap.m2t
# Position only — fast-wind the tape to 30:00 and stop, without capturing:
tapecap cue 00:30:00
# Short fast-wind probe, useful when parked in blank/no-timecode tape:
tapecap jog forward 3
# Rewind to the very beginning, or fast-wind to the very end (blank, no timecode):
tapecap wind start
tapecap wind endFor tools like tapeflow — which capture a
worn tape several times and merge the clean frames, leaving a list of remaining
gaps labelled with timecode — tapecap can re-capture just one gap instead
of replaying the whole tape. --seek <tc> fast-winds (AV/C fast-forward/rewind)
to a tape timecode before capture; --until <tc> stops once the
captured stream's timecode passes a point.
A <timecode> is HH:MM:SS (also HH:MM:SS:FF with frames ignored, MM:SS, or
a bare number of seconds). These drive the transport, so they need AV/C control
(not --no-control). cue / --seek need a readable current tape timecode
as an anchor, not just a target timecode; they work from recorded footage, not
from the blank head/tail. If the deck is parked in a no-timecode region, use
jog forward <sec> / jog back <sec> and watch the final Position: (or
--json) until a timecode is readable, then cue/seek. capture --seek aborts
without capturing if it cannot establish that initial anchor.
Tape positioning is coarse — decks coast past a stop, and (the very reason
you're re-capturing) aged tapes drop their timecode mid-travel — so the landing
point is approximate. --overlap <sec> (default 4) deliberately keeps extra
footage on each side so the re-capture overlaps the neighbouring good
material, which is exactly what a frame-merge step wants. The seek reads the
deck's AV/C timecode opportunistically and dead-reckons across timecode dropouts.
tapecap cue <tc> exposes the positioning on its own, for an orchestrator that
prefers to cue the deck and then run capture --no-control.
By default tapecap issues an AV/C PLAY when capture starts and STOP
when it ends, so you can leave the deck alone. Use --no-control if you prefer
to drive playback yourself (or the deck ignores AV/C transport commands).
cue/--seek target a timecode, so they only reach recorded footage. The
blank head and tail of a tape have no timecode, so use wind for physical
ends: tapecap wind start rewinds to the very beginning, tapecap wind end
fast-winds to the very end. It drives the transport and watches the deck's
transport state, stopping when the deck auto-stops at the mechanical end; if a
deck doesn't report that state, stop it with Ctrl-C or bound it with
--timeout <sec> (default 900).
When archiving a whole tape: tapecap wind start first, then capture. Two
gotchas — the blank leader at the very start looks like end-of-tape and trips the
silence timeout, so pass --eot-timeout 0 for a from-the-top capture; and
after a full pass the deck is parked in the blank tail (no timecode), so
cue/--seek cannot lock on there. If you need another targeted capture, use
short tapecap jog back 3 probes until Position: reports a readable timecode,
then cue/seek from that anchor. If you need a full rewind first, run
tapecap wind start, then use short tapecap jog forward 3 probes before
cue/seek. Run tapecap wind start again at the end to leave the tape rewound.
macOS may gate FireWire AV/C access behind the camera/recording privacy permission (the device shows up as a muxed A/V capture device). If a capture fails to open the device:
- Run it once from Terminal and allow the prompt, or grant your terminal app access under System Settings → Privacy & Security → Camera.
- Make sure no other app (iMovie, Final Cut, Premiere, dvrescue, QuickTime) is holding the device open.
- DV → a raw DIF stream. Players/ffmpeg read it directly (
ffmpeg -i reel.dv …). - HDV → a raw MPEG-2 Transport Stream. Remux losslessly, e.g.
ffmpeg -i reel.m2t -c copy reel.mkv, or demux audio/video as needed. The audio that AVFoundation drops is present here.
- Verified on real hardware. HDV capture (full video + audio + metadata) confirmed against a Sony HDR-HC9 on both a MacBook Pro 2018 (Intel, macOS 12.6) and an M1 Max MacBook Pro (Apple Silicon, macOS 15) — the latter through an Apple Thunderbolt-to-FireWire adapter. CI additionally builds and smoke-tests the universal binary on macOS 14/15.
- This tool is built around Apple's proven AVCVideoServices code and relies on deprecated-but-present IOKit FireWire isochronous APIs. They work through macOS 15; there is no path on macOS 26+.
- Stream format (DV vs HDV) is auto-detected from the device's output-plug
signal format; if a particular deck misreports it, force it with
--format hdv/--format dv.tapecap infoshows what was detected. - Only the device's output plug 0 is used (the normal tape-playback plug).
tapecap's own code is MIT licensed — seeLICENSE.- Bundles Apple's AVCVideoServices sample code under
third_party/AVCVideoServices/(Apple sample-code license; provenance and details in itsNOTICE.md). - The DV/HDV recording-date and timecode parser (
src/dvmeta.*) is ported from xingrz/iina-dv-timecode. - Thanks to the IEEE 1394 / DV archival community — dvgrab, libiec61883, and MediaArea/MIPoPS dvrescue — for keeping this knowledge alive.