TL;DR: capture (digitize) MiniDV video from a camcorder over USB on a modern Mac — Apple Silicon or Intel, no FireWire, lossless DV — right from the tape.
brew tap boriswinner/tap && brew trust boriswinner/tap && brew install uvcdv-captureA userspace implementation of the one video format Apple left out of macOS: the UVC DV payload. Comes as a scriptable CLI and a native macOS GUI.
If you have a box of MiniDV cassettes and a camcorder, you have a problem in 2026:
- The "proper" way to capture MiniDV is FireWire — a port that modern Macs don't have, can't get through adapters on Apple Silicon, and whose OS support Apple removed entirely.
- Analog capture (composite → USB grabber) works but re-digitizes the video: you lose quality that is sitting right there on the tape as digital data.
There is a third, almost forgotten path. Some mid-2000s camcorders — notably
Panasonic's "Motion DV" models — can send the complete raw DV stream over
regular USB. It's the same bit-exact data you would get over FireWire, and
it uses a standard (if obscure) part of the USB Video Class specification:
the stream-based DV payload format (VS_FORMAT_DV).
Windows supports that format out of the box (which is why tools like
Scenalyzer "just work" there). Linux supports it (dvgrab -V). macOS does
not: Apple's UVC driver implements only frame-based formats
(uncompressed/MJPEG/H.264), so the camera enumerates but no capture device
ever appears.
uvcdv-capture fills that gap from userspace. It talks the UVC protocol
directly through libusb: finds the VideoStreaming interface, claims it (macOS
allows this precisely because no Apple driver binds to a format it doesn't
know), negotiates the DV format, and reads the isochronous stream. No kernel
extensions, no SIP changes, no drivers.
What you get: bit-exact .dv files identical to a FireWire capture —
720×576 PAL (or 720×480 NTSC) with embedded PCM audio, timecode, and
recording dates. Verified with dvrescue: 0 damaged frames, 0 bus drops on
the reference hardware.
- Lossless capture to raw
.dv— the archival master format every DV tool understands - Tape transport control over USB — play/pause/stop/rewind/fast-forward the camcorder from your Mac, read the tape timecode without capturing
- Hands-free digitizing — start playback, capture until the tape ends, stop the tape: one command
- Scene splitting — one file per recorded clip, detected from timecode and recording-date jumps in the DV metadata
- On-the-fly H.264/AAC encoding — get a watchable
.mp4(6–8× smaller) instead of, or alongside, the raw master; optional 50i → 50p deinterlacing - Live preview — half-block graphics in the terminal, or real video in the GUI, with the live tape timecode
- Data-loss accounting — every lost packet is counted and shown; a clean
capture provably has
drop=0 ovf=0 - Native macOS GUI — everything above in one window, English/Russian
Any USB device that exposes the UVC VS_FORMAT_DV descriptor. The tool is
vendor-agnostic and auto-configures from the device's own descriptors.
In practice this class of devices is small: Panasonic MiniDV camcorders
with the USB FUNCTION → Motion DV menu item — the NV-GS series
(GS75/120/150/180/230/250/280/300/320/330/500 and siblings) and their
North-American PV-GS twins. Tested end-to-end on a Panasonic NV-GS330.
Explicitly not supported (the hardware doesn't speak UVC-DV):
- Sony "USB Streaming" — proprietary low-resolution MPEG, not DV
- JVC / Canon MiniDV models — their USB is for stills/webcam only; DV is FireWire-only
- Any camera in "Web Camera" USB mode — that's a 320×240 webcam, useless for archiving; switch the camera to its DV/Motion DV mode
Have a different camera with a DV-over-USB mode? See Trying an untested camera — checking takes a minute, and reports are welcome.
brew tap boriswinner/tap
brew trust boriswinner/tap # newer Homebrew requires trusting third-party taps
brew install uvcdv-captureThis builds from source on your machine, so there are no Gatekeeper warnings, and it works on both Apple Silicon and Intel Macs (the prebuilt release binaries are arm64-only; on Intel use Homebrew or build from source — the code has nothing architecture-specific, though Intel is untested so far). For encoding, preview and GUI snapshots also install ffmpeg:
brew install ffmpegThe releases page
has arm64 builds of both the CLI (uvcdv-capture-*.zip) and the GUI app
(UVCDV-Capture-*.app.zip, CLI included inside). They are not code-signed,
so after unzipping either right-click → Open on first launch, or:
xattr -d com.apple.quarantine ./uvcdv-capture # CLI
xattr -dr com.apple.quarantine "UVCDV Capture.app" # GUIDependencies: libusb and pkg-config (Homebrew), plus the Xcode Command Line
Tools (xcode-select --install — provides clang; swiftc for the GUI).
git clone https://github.com/boriswinner/uvcdv-capture.git
cd uvcdv-capture
brew install libusb pkg-config
make # CLI -> ./uvcdv-capture
make gui # GUI -> ./uvcdv-gui (run from this directory)
make app # both -> "UVCDV Capture.app" (double-clickable bundle)
sudo make install # optional: CLI to /usr/local/bin (PREFIX=... to change)The CLI also builds on Linux (apt install libusb-1.0-0-dev pkg-config),
where the kernel driver is auto-detached — though on Linux you can just use
dvgrab -V, which is exactly why this tool doesn't need to exist there.
- Power the camcorder from the AC adapter (not battery).
- Menu: USB FUNCTION → Motion DV (or your model's DV-over-USB mode).
- Switch the camera to VCR/playback mode.
- Connect the USB cable to the Mac — directly, no hubs if possible.
You do not need to press play on the camera: the tool can start the tape
itself (--play, or the checkbox in the GUI).
uvcdv-capture --play --stop --idle 15 tape01.dvStarts tape playback, captures until 15 seconds pass with no data (= end of
tape), stops the tape. Wrap it in caffeinate -i to keep the Mac awake for
hour-long tapes:
caffeinate -i uvcdv-capture --play --stop --idle 15 tape01.dvThe output extension decides the mode:
uvcdv-capture tape01.dv # raw DV only (lossless master, ~13 GB/hour)
uvcdv-capture tape01.mp4 # H.264 only (~1.5-2 GB/hour, no raw file)
uvcdv-capture --encode tape01.mp4 tape01.dv # both at once
uvcdv-capture --deint --crf 20 tape01.mp4 # deinterlaced 50p, smaller fileEncoding runs in a separate ffmpeg process and can never slow down or
corrupt the capture — if the encoder ever fell behind, frames would be
skipped in the MP4 only, counted and reported (the raw stream is untouched).
Without --deint the MP4 keeps the original interlacing (correctly flagged
BFF); with --deint you get smooth progressive 50 fps, nicer for direct
viewing on modern screens.
For irreplaceable tapes keep the raw master: an interrupted MP4 may be
unplayable (unfinalized container), while a raw .dv is valid up to the last
byte written. Storage math: one 60-minute tape ≈ 13 GB raw, ≈ 1.7 GB as MP4.
uvcdv-capture --split --play --stop --idle 15 tape01.dv
# -> tape01-001.dv, tape01-002.dv, ... one file per recorded clipCuts are detected from jumps in the tape timecode or in the embedded recording date/time (consumer camcorders keep timecode continuous across in-camera cuts, so both signals are checked — the same approach as dvgrab and Scenalyzer). Works for both raw and MP4 outputs, consistently.
uvcdv-capture --tape status # transport state + tape timecode + supported modes
uvcdv-capture --tape play # also: pause, stop, ff, rew, rew-fast, eject--tape status reads the current tape position (hh:mm:ss:ff) without
touching the stream — handy for scripting batch digitization. Note: on the
NV-GS330 the transport control stalls after each command until the device is
reset; the tool detects this and recovers automatically (you'll see a
one-line resetting device and retrying notice — it's expected).
uvcdv-capture --preview --play --stop --idle 15 tape01.dvRenders the actual video with Unicode half-blocks (24-bit color where the terminal supports it), with capture stats and the live tape timecode below. Needs ffmpeg. End-to-end preview latency is ~0.3 s.
- Put the camera in its DV-over-USB mode (look for "Motion DV" or "DV stream via USB" in the manual) and connect it.
uvcdv-capture --list— you want your device listed withDV payload: YES.uvcdv-capture --probe— runs the full UVC negotiation without capturing. Success looks likedwMaxVideoFrameSize=144000 (PAL DV frame)(or120000for NTSC).uvcdv-capture --max-sec 30 test.dv, then check the file withmediainfo test.dvanddvrescue test.dv.
Please open an issue with your results (working or not, with --list and
--probe --verbose output) — the tested-hardware list is currently one
camera long.
Device selection:
| option | default | meaning |
|---|---|---|
--list |
— | list UVC video devices and whether each exposes a DV format |
--vid 0xXXXX, --pid 0xXXXX |
any | pick a camera when several are connected |
Outputs and encoding:
| option | default | meaning |
|---|---|---|
positional out.dv |
— | raw DV output (lossless master) |
positional out.mp4/.mov/.mkv/.m4v |
— | encoded output only, no raw file |
--encode FILE |
off | encoded copy in addition to the raw output |
--deint |
off | deinterlace: 50i → 50p (bwdif); smoother on modern screens, off keeps the original interlacing |
--crf N |
18 | quality mode (used when --vbitrate is 0): constant quality, size varies; 18 ≈ visually lossless for DV, 20 good, 23 noticeably smaller |
--vbitrate KBPS |
0 (off) | size mode instead of CRF: predictable file size; 3000–4000 kbps is transparent for SD DV |
--preset NAME |
veryfast | x264 speed/size trade-off (ultrafast…veryslow); slower ≈ 15–20% smaller at equal quality, all faster than real time for DV |
--scale WxH |
source size | output resolution (e.g. 640x480); keep the source 720×576/720×480 for archives; auto-enables --deint |
--abitrate KBPS |
192 | AAC audio bitrate; 192 is transparent, 128 acceptable |
--split |
off | one file per recorded scene (out-001.dv, out-002.dv, …) |
Session control:
| option | default | meaning |
|---|---|---|
--play |
off | start tape playback just before capture begins |
--stop |
off | stop the tape when capture ends |
--idle SEC |
off | stop after SEC seconds without data (end of tape) |
--max-sec SEC |
off | stop after SEC seconds (test runs) |
--preview |
off | live preview in the terminal |
Tape transport (standalone, no capture):
| option | meaning |
|---|---|
--tape play|pause|stop|ff|rew|rew-fast|eject |
send a transport command |
--tape status |
transport state, tape timecode, supported modes |
Tuning and quirk overrides (defaults are validated on real hardware — only
reach for these if --probe fails or the status line shows losses):
| option | default | meaning |
|---|---|---|
--transfers N |
32 | isochronous transfers in flight |
--pkts N |
128 | packets per transfer (32×128 ≈ 512 ms of queue) |
--ring-mb MB |
64 | RAM buffer between USB and disk |
--iface / --format-index / --frame-index / --alt / --ep |
auto | force UVC parameters for cameras with quirky descriptors |
--probe |
— | negotiate and print stream parameters, then exit |
--drop-err |
off | discard device-flagged packets — not recommended, they normally carry valid DV |
Frontend integration (what the GUI uses; available to any script):
| option | meaning |
|---|---|
--status-json |
one JSON status line per second + a final done event |
--snapshot FILE |
keep FILE updated with a JPEG of the current frame (~12/s) |
123.4 MB | 3.46 MB/s | ok=73135 drop=0 uvc_err=7351 (0/s) hdrbad=0 ovf=0
drop— real bus losses. Must stay 0. If it grows: connect the camera directly (no hubs), unplug other USB devices, try--transfers 48.ovf— buffer overflow (disk too slow). Must stay 0; increase--ring-mb.uvc_err— packets the camera flagged. A burst in the first ~1.5 s is normal (the camera marks packets while its tape tract synchronizes; the data inside is valid and is kept). The(N/s)rate should be 0 afterwards; a persistent rate suggests dirty video heads or a damaged tape section.
brew tap amiaopensource/amiaos && brew install dvrescue mediainfo
mediainfo tape01.dv # DV, 720x576 25fps (PAL), interlaced BFF, PCM 48kHz, timecode
dvrescue tape01.dv # frame-level error report; no error records = clean captureThe raw .dv is understood by every editor and archival tool:
ffmpeg -i tape01.dv -c copy tape01.mov # rewrap for editing, no re-encode
dvrescue pass1.dv pass2.dv -m merged.dv # two-pass capture merge for precious tapesLaunch UVCDV Capture.app (or ./uvcdv-gui next to the CLI binary). The GUI
is a frontend over the same CLI engine — every capture it runs is identical
to the equivalent terminal command, which it prints into the log pane.
Workflow:
- Camera — click Find camera. You should see ✅ with the device id and "(tape control available)". If it says the camera is in Web Camera mode, switch it to Motion DV and reconnect.
- Tape buttons — Rew / Play / Pause / Stop / FF / Status, with the tape timecode displayed large. Use them to find the right spot on the tape. They are disabled while a capture runs (the capture process owns the device) — use the Play on start / Stop at end checkboxes instead.
- Where to write — check raw DV, MP4, or both. Deinterlace and CRF live under the MP4 entry because they affect only the MP4 — the raw DV is always a bit-exact copy of the tape. Filenames default to a timestamp and auto-increment instead of overwriting. Optionally enable Split by scene.
- Session — Play on start and Stop at end give you hands-free digitizing; the idle timeout (default 15 s) detects the end of the tape.
- Press Start capture. You get: live video preview, a big green "Stream clean — no data loss" banner (which turns red with details if a single packet is ever lost), per-second stats with the tape timecode, and scene events in the log. The Mac is kept awake automatically for as long as the capture runs.
- Press Stop capture (or just let the idle timeout fire at the end of the tape). Stopping is a clean shutdown — identical to Ctrl-C in the terminal.
The interface is English by default; switch to Russian with the language popup in the top-left (applies instantly, remembered across launches).
no UVC-DV device found— redo the camera setup ritual in order (AC power → Motion DV → VCR → USB). Runuvcdv-capture --listto see what the Mac actually enumerates.- Camera listed with
DV payload: no— it's in webcam/photo USB mode, or the model doesn't do DV-over-USB at all (see Supported hardware). claim_interface failed— another process holds the interface; close other capture apps (or another copy of this tool).drop> 0 — starved USB bus: direct port instead of a hub, remove other USB devices, try--transfers 48.ovf> 0 — slow disk (usually a nearly-full or network volume); increase--ring-mb.- Encoder/preview doesn't start — install ffmpeg:
brew install ffmpeg. - First ~1.5 s of a capture look corrupted in
uvc_err— that's the camera's startup sync burst; the frames are actually valid (see the status line section above).
- Scans USB descriptors for a VideoStreaming interface whose class-specific
descriptors contain
VS_FORMAT_DV, and claims it — possible on macOS exactly because Apple's driver refuses to bind to a format it doesn't implement. - Negotiates UVC PROBE/COMMIT for the DV format and selects a matching isochronous alt-setting. The stream has only ~4% bus headroom, so the capture keeps ~0.5 s of transfers queued and does disk I/O on a separate thread behind a RAM ring buffer — a lost isochronous packet is unrecoverable, and the measured loss rate is zero.
- Writes everything, including packets the camera marks with its error bit: DV's 80-byte DIF blocks carry their own error flags and concealment, so judging data validity is the DV layer's job, not the transport's (the same philosophy as FireWire capture). This design took the measured frame-damage rate from 16.7% to 0.
- Tape control uses the UVC Media Transport Terminal — the same standard
mechanism Windows maps to DirectShow's
IAMExtTransport, plus automatic recovery from an NV-GS330 firmware quirk. - Preview, encoding and GUI snapshots are separate ffmpeg subprocesses fed through independent drop-on-overflow buffers: by construction, none of them can stall the capture path.
Developer/agent documentation — architecture, protocol facts, the hardware
quirk encyclopedia, and porting notes — is in AGENTS.md. The
original research journal (in Russian, hypotheses and dead ends included)
is preserved in git history as CONTEXT.md.
GPL-3.0-or-later. In short: use, modify and redistribute freely, but any distributed derivative must remain open source under the same terms.