ffdrop

A drag-to-encode frontend for ffmpeg and sips. It gathers the transcodes you run over and over in video/animation production — encode, ProRes intermediates, color-tag fixes, range conversion, audio extract, PNG sequences — and now image conversion with ICC profile handling, behind a drop surface instead of a terminal window.

macOS / Apple Silicon (arm64) shells out to the system ffmpeg/ffprobe + sips signed & notarized v0.10.0

Presets

Each preset maps directly to one ffmpeg call. No hidden heuristics.

PresetCodec / profilePixel formatAudioContainer
Video → MP4 (h264) libx264 · profile main · CRF (default 20) yuv420p aac 192k / -an .mp4 +faststart
Video → MP4 (h264, fast/HW) h264_videotoolbox · -q:v (hardware) yuv420p aac 192k / -an .mp4 +faststart
Video → MP4 (HEVC/h265) libx265 · CRF · -tag:v hvc1 yuv420p aac 192k / -an .mp4 +faststart
Video → MP4 (HEVC, fast/HW) hevc_videotoolbox · -q:v · hvc1 yuv420p aac 192k / -an .mp4 +faststart
Video → ProRes 422 Proxy prores_ks · -profile:v 0 yuv422p10le pcm_s16le .mov
Video → ProRes 422 LT prores_ks · -profile:v 1 yuv422p10le pcm_s16le .mov
Video → ProRes 422 HQ prores_ks · -profile:v 3 yuv422p10le pcm_s16le .mov
Video → WAV (extract) -vn · pcm_s16le passthrough → PCM .wav
Video → MP3 (extract) -vn · libmp3lame -q:a 2 VBR ~190 kbps .mp3
Video → Fool QuickTime gamma -c copy · -color_trc iec61966-2-1 + h264_metadata transfer=13 passthrough passthrough .mp4 +write_colr
Video → Undo gamma hack -c copy · -color_trc bt709 + h264_metadata transfer=1 passthrough passthrough .mp4 +write_colr
Image sequence → MP4 h264 libx264 · CRF 20 yuv420p .mp4 +faststart
Image sequence → ProRes 422 HQ prores_ks · -profile:v 3 yuv422p10le .mov

ProRes presets set -vendor apl0. Image sequences (PNG/JPEG/TIFF) are detected by a numbered pattern in the dropped folder (first type wins); framerate is chosen in the UI and fed as -framerate. HW presets encode via Apple VideoToolbox (fast, -q:v instead of CRF); HEVC gets the hvc1 tag for QuickTime playback.

Color-tag & range

Rewrite color metadata without re-encoding, or convert range at the pixel level. Per-file override of primaries / transfer / matrix / range, read from ffprobe and shown in the info panel.

Metadata-only (lossless)

Rewrites the color atoms with -c copy — no new encode, the bitstream is untouched. Fixes mistagged transfer/primaries/matrix/range when the tag is wrong rather than the pixel data. (For the Apple-specific ~1.96 gamma shift, see the QuickTime hack below.)

ffmpeg -i in.mov -c copy \
  -color_primaries bt709 \
  -color_trc bt709 \
  -colorspace bt709 \
  -color_range tv \
  in_retagged.mov

Range rescale (destructive)

When the pixel values are actually mis-scaled: tick pixel rescale and a scale step is added to the filter chain. tv → limited, pc → full, in_range is auto-detected.

-vf scale=in_range=auto:out_range=limited

The flags map 1:1 to ffmpeg: -color_primaries / -color_trc / -colorspace / -color_range.

QuickTime gamma (ugly hack)

macOS ColorSync (QuickTime, Preview, spacebar in Finder, Safari, Chrome) renders Rec.709 at Gamma ~1.96 — flatter and lighter than 2.2–2.4. VLC and editors show it correctly. Fool QuickTime gamma re-tags transfer to sRGB (13) on both the container colr atom and the H.264 VUI flag, so ColorSync lands on the ~2.2 curve. The pixels are untouched. It's a zero-sum trade, not a fix: the file then looks wrong in VLC / Premiere / AE / DaVinci. One and the same file can't look identical in both camps. Undo gamma hack writes transfer back to bt709 — a bit-identical round trip. h264 only; for Mac viewing only, never a master.

# fool: container + bitstream → sRGB
-c copy -color_trc iec61966-2-1 -color_primaries bt709 -colorspace bt709 \
  -movflags +faststart+write_colr \
  -bsf:v h264_metadata=transfer_characteristics=13:colour_primaries=1:matrix_coefficients=1

See the command as you build it

Every option you touch in the UI assembles the actual ffmpeg command in real time — so you can watch it take shape before anything runs. Flip a toggle and the exact flag appears; a typical h264 command ends up looking something like this:

ffmpeg -y -hide_banner -i in.mov \
  [-vf scale=trunc(iw/4)*2:trunc(ih/4)*2]   # "scale 50%"
  [-vf scale=in_range=auto:out_range=limited]  # range rescale
  -c:v libx264 -crf N -preset effort \
  -profile:v main -pix_fmt yuv420p -movflags +faststart \
  [-color_primaries … -color_trc … -colorspace … -color_range …] \
  [-tune grain]                              # "preserve grain"
  [-c:a aac -b:a 192k | -an] \
  out_h264_crfN.mp4

The output filename encodes the choices too: _h264_crf20_50_grain_noaudio_colortagged. The scale expression truncates to even dimensions (an h264 requirement). -preset is chosen in the UI (encoder effort, ultrafastveryslow) — it trades compression time for file size, not quality (CRF controls quality). Progress is read from -progress pipe:1 against the frame count from ffprobe, with a live ETA while the job runs.

Image & ICC

Still images go through sips (not ffmpeg — which handles ICC profiles poorly). Drop in PNG / JPEG / TIFF / HEIC / WebP, convert to any output format, with a per-image ICC action. No new dependencies — sips ships with macOS.

Targetsips formatQualityAlpha16-bit
Image → PNGpnglossless (DEFLATE)preservedpreserved
Image → JPEGjpegformatOptions 1–100 (def 95)flattened against white
Image → TIFFtifflosslesspreservedpreserved
Image → HEICheicsips defaultpreservedpreserved

WebP is read as input but not written (sips has no webp encode). File extensions map to sips format names: jpg→jpeg, tif→tiff.

Four ICC actions

Keep / Assign

Keep leaves the source profile untouched. Assign embeds a chosen profile without touching the pixels (sips --embedProfile) — like Photoshop's "Assign Profile". The profile list is filtered to embeddable RGB profiles (the ICC header is read; CMYK/Gray/Lab/abstract tone profiles are filtered out).

sips -s format jpeg \
  --embedProfile "…/sRGB Profile.icc" \
  in.png --out in_sRGB.jpg

Convert / Strip

Convert recomputes the pixels between profiles with a rendering intent (--matchToWithIntent; perceptual/relative/saturation/absolute). Strip tears the color marking down to a physically bare file via custom PNG-chunk and JPEG-marker surgery (not just sips, which always re-embeds a default).

sips --matchToWithIntent \
  "…/Display P3.icc" relative \
  in.png --out in_DisplayP3.png

Pitfalls ffdrop warns about in the UI: Convert downsamples 16-bit → 8-bit (Assign preserves the depth). JPEG has no alpha — transparency is flattened against white (choose PNG/TIFF/HEIC to keep it). The info panel reads the raw file, not sips -g profile (which reports default sRGB even on a profile-less file). The output name encodes profile + quality: image_sRGB_q95.jpg.

Bit depth (8 ↔ 16)

Bit depth is shown right on the file row (pill) and in the info panel — for single images and PNG sequences (read from the first frame). Changing bit depth goes through ffmpeg (sips can't), alpha-aware: rgb24/rgb48be and rgba/rgba64be respectively. PNG/TIFF only. Since ffmpeg doesn't carry the ICC profile, bit depth and ICC action are mutually exclusive — choose a bit depth and ICC greys out, with the app warning that the profile will be stripped. Output name: image_16bit.png.

# 16-bit → 8-bit (no alpha)
ffmpeg -y -i in.png -pix_fmt rgb24 in_8bit.png
# 8-bit → 16-bit (with alpha)
ffmpeg -y -i in.png -pix_fmt rgba64be in_16bit.png

Workflow

The details that make batch runs predictable in real production.

Copy the command

Every file has a Cmd panel that shows the exact ffmpeg/sips argv that runs — the same line, shell-quoted, ready to paste straight into Terminal. The panel can stay pinned open and updates live as you tweak preset, CRF, encoder effort, color/range, bit depth or ICC. No guessing: what you see is what runs.

Output collision protection

Two jobs never overwrite each other — or an existing file. ffdrop reserves the output path and picks the first free variant (name, name (2), name (3)…), accounting for both disk and other queued jobs. Drop a whole batch without losing files.

Folder drop expands

Drop a folder and it's scanned (one level), and every video/image becomes its own row — as if they'd been dragged in individually. A folder that is an image sequence is treated as a sequence instead. Junk and dotfiles are skipped.

Target size & 2-pass

Drive h264/HEVC with CRF (constant quality) or a target size in MB — ffdrop works out the bitrate from the video length (-b:v, audio budget subtracted). To hit the target size precisely: tick 2-pass (software encoder) — an analysis pass followed by an encode pass, with separate progress for each. Scale to any height (scale=-2:N, width auto).

Requirements