What happens under the hood for any of these commands — demux → decode-once pump → multi-GPU encode → mux — is in pipeline & architecture.
The rivet binary has these subcommands: transcode,
probe, devices,
capabilities, pipe,
batch (feature batch), ipc (feature ipc),
and serve (feature server). Build it with:
cargo build --release # GPU decode + GPU encode tiers
cargo build --release --features rav1e-fallback,rav1d-fallback,h26x-fallback # + software AV1 / H.264 / H.265
cargo build --release --features nvidia # + NVENC AV1 encoder (Windows or Linux)The binary is at target/release/rivet. Run rivet --help or
rivet <command> --help for generated usage at any time.
rivet encodes AV1 (default, royalty-clean), H.264, or H.265 — select with
--codec av1|h264|h265. The output container is MP4 (single file) or CMAF/HLS (segmented); all three codecs work in both. See the compatibility matrix for codecs in.
One vocabulary, every surface. Every worded value below —
--mode,--audio,--color,--bit-depth,--seam-mode,--encode,--decode,--gpu-family,--encode-policy, … — means whatrivet::settingssays it means, and nothing else. The CLI hands each word to the sameTranscodeSettingsthe IPC socket reads askey=value, the HTTP API as query/JSON fields, and the batch manifest as YAML/JSON keys —--audio opus,audio=opus,?audio=opusandaudio: opusare one code path. The flag tables here therefore also document those keys.
rivet transcode <INPUT> [OPTIONS]
Transcodes <INPUT> (any supported container/codec) to AV1 (default), H.264, or
H.265 — pick with --codec.
| Argument | Description |
|---|---|
<INPUT> |
Input media file. Container/codec is auto-detected. |
| Flag | Values / default | Description |
|---|---|---|
-o, --output <PATH> |
default <input>.av1.mp4 |
Output file (single mode, one rung) or directory (multi-rung single mode, or HLS). |
--mode <MODE> |
single (default), hls |
Output shape: one self-contained MP4 per rung, or a CMAF/HLS package. |
--rung <WxH[@RATE]> |
repeatable | A ladder rung, e.g. --rung 1920x1080 --rung 1280x720. Omit for a single rung at the source resolution. WxH@RATE (1280x720@3M) codes that rung to a bitrate — see --video-bitrate. |
--ladder |
flag | Auto-derive a standard ABR ladder from the source resolution (instead of --rung). |
--max-short-side <N> |
default 1080 |
With --ladder, cap the tallest rung's short side. |
--segment-seconds <S> |
default 4.0 |
HLS target segment length (segments still break on keyframes). |
--crf <N> |
encoder-native | Constant rate factor (lower = better quality). Names the quantiser directly; when set, --target is not consulted. |
--video-bitrate <BPS> |
e.g. 3M, 800k |
Code every rung that does not name its own (--rung WxH@RATE, or bitrate= in --encode-policy) to this bitrate rather than to --target: the encoder's rate controller picks a quantiser per picture to spend it. The native software H.264 / H.265 encoder (h26x-fallback) codes to a rate. On a host whose encode pool is GPUs the job is refused before a frame is decoded, by name, saying how to reach the software pool. A CRF, --seam-mode constqp or --codec av1 beside a rate is refused too. Measured in codec-encode.md. |
--video-buffer <DURATION> |
default 1s for a bitrate rung; e.g. 500ms; 0 for none |
The coded picture buffer every bitrate rung declares (the stream's HRD) and keeps to. It bounds any stretch of the stream at the rate plus the buffer, which is what bounds an HLS segment's peak and so its BANDWIDTH. The unit is required. |
--target <T> |
visually_lossless, high, standard (default), low, vmaf=N |
Perceptual quality target for every rung. vmaf=N aims for a VMAF score — mapped to each backend's quantiser through the calibrated tables in codec::encode::tuning, so the same target means the same perceived quality on NVENC, QSV, AMF and rav1e. Measure it with bench/. |
--gop <FRAMES> (--keyframe-interval) |
frames | GOP length for every rung (default: two seconds at the output rate). Single file: the keyframe cadence and, across GPUs, the chunk grid. HLS: the segment grid stays --segment-seconds; a shorter GOP adds keyframes inside each segment (for seeking); a longer one is silently the segment, since every segment opens on an IDR anyway. |
--audio <POLICY> |
auto (default), opus, drop |
auto: passthrough AAC/Opus/AC-3/E-AC-3, transcode MP3/Vorbis to Opus, drop the rest. opus: force Opus. drop: video only. |
--audio-bitrate <BPS> |
e.g. 240k |
Opus target for transcoded audio. Omit to derive it from the channel layout — 64k mono, 96k stereo, 320k for 5.1. Ignored for passthrough tracks, which keep the bitrate they were authored at. |
--audio-filter <CHAIN> |
e.g. channelmap=FL-FL|FR-FR:stereo |
Audio filter chain applied to decoded PCM before the encoder — see audio filters. Forces a decode/re-encode, so it can't be combined with a passthrough-only source codec. |
--subtitles <SELECTION> |
all (default), none, eng,deu |
Which of the source's text subtitle tracks to carry: every one, none, or a language list. Single file: a tx3g track per language. HLS: a WebVTT rendition per language. Bitmap subtitles (PGS / VobSub / DVB) are always dropped. See Subtitles. |
--max-fps <F> |
— | Cap the output frame rate (source cadence otherwise preserved). |
--color <POLICY> |
sdr (default), hdr10, hlg, passthrough |
Output color / tonemap policy — see Color & bit depth. |
--pixel-format <FMT> |
auto (default), 8bit, 10bit |
Output luma bit depth. |
--filter <CHAIN> |
e.g. crop=1280:720,hflip |
Video filter chain applied before scaling — see Video filters. |
--trim-start <S> |
seconds | Splice/trim: keep from this time. The output is re-based to zero. Trimmed jobs take the serial encode path. |
--trim-end <S> |
seconds | Splice/trim: keep until this time. The kept range is [start, end), exact at any frame rate. To join clips, use rivet splice. |
--codec <CODEC> |
av1 (default), h264, h265 |
Output video codec. av1 is royalty-clean (the project default); h264/h265 are for legacy-player compatibility (patent-licensing caveats). All three work for single-file MP4 and CMAF/HLS. H.264/H.265 are encoded on NVENC (validated on RTX 3090) + QSV (validated on Intel Arc); AMF H.264/H.265 is a follow-up. |
| Flag | Description |
|---|---|
--encode <PLAN> |
The encode plan — which cards, and how the work is laid across them, as one value so the halves cannot contradict: all (default) — every capable card, each worker serving every rung and taking the next chunk of whichever is furthest behind (a card idles only when the job is out of work); per-rung — every card, each pinned to its own rungs (one rung, one GPU when the ladder fits the pool; predictable placement, idle cards when a rung is blocked); single — one card, one encoder per rung, serial (single-file output is seam-free by construction); gpu:N — single, pinned to card N; family:nvidia|amd|intel — one vendor's cards, ladder-scheduled. |
--gpu <N> / --single-gpu / --gpu-family <VENDOR> |
Older spellings of --encode gpu:N / single / family:VENDOR. Still work; --encode wins when both are given. |
--decode <PLAN> |
The decode plan — which card(s), and whether the decode is one pump or split into ranges, as one value: auto (default) — cut an un-spliced H.264/H.265 source into one range per capable card at keyframes on chunk boundaries, one decode pump per card, each decoding its own stretch (whole where the source cannot be split); whole — one decoder for the whole source (the control arm of any comparison); fastest — benchmark every decode-capable card on a prefix of the input and put one decoder on the quickest; gpu:N — one decoder pinned to card N (e.g. an iGPU while the dGPUs encode); ranges:N — a range count (more than the cards is legal and is how the split is exercised on a one-card host). Output is byte-identical whichever you pick. --decode-gpu N still works and means gpu:N. |
--encode-policy <recommended|off|SPEC> |
Per-rung encoder knobs by ladder position. recommended is the measured ladder policy (+2 libaom-CQ steps softer per rung going down, no top bonus, one tile below 4K, three reference frames — about −20% storage on a five-rung ladder for a fraction of a VMAF point); off is none (the default); or the rule grammar, e.g. qstep=2;top:q=-2;short<=2159:tiles=1x1;any:refs=3 — see output-spec.md. |
--seam-mode <parallel|constqp> |
Seam quality on the multi-GPU single-file path — how the chunks it stitches are rate-controlled. Nothing else: no seams at all is an encode plan (--encode single), not a seam mode. serial still parses as the older spelling of --encode single. |
See GPU scheduling for how
AllGpus / SingleGpu / Family actually distribute work.
family:VENDOR and gpu:N name silicon. When nothing they name can encode the
job's codec in this build — the family is absent, the card index does not
exist, or the card is there but the build cannot drive it for that codec — the
job is refused before a frame is decoded, on every path (single-file
serial, chunked, HLS), and the error says what is present and what would work:
error: transcoding in.mp4: no encoder matches `--encode family:intel` for H.264 on this host: no Intel GPU is present. Present: NVIDIA GeForce RTX 3090 (gpu 0, NVIDIA, encodes H.264); AMD Radeon(TM) Graphics (gpu 1, AMD, cannot encode H.264 in this build). Fix: pin a card that can (`--encode family:nvidia` or `--encode gpu:0`) or drop the pin (`--encode all`, the default) to use them; to run on the software H.264 encoder (`h26x-fallback`) instead, drop the pin and hide the cards (`CUDA_VISIBLE_DEVICES=-1` hides NVIDIA), or build without the vendor features — the software pool takes the job only when no card can encode H.264 and none is pinned.
A pin never falls through: not to another vendor, and not to the software
encoders — on the serial path either, where a pinned card that fails to start
fails the job naming the vendor rather than sliding down the NVIDIA → AMD →
Intel → software chain. The software pool (the ladder on CPU leases, one
software encoder per slot) is what an unpinned plan (all, per-rung,
single) gets when no card can encode the codec in this build and a software
encoder is compiled in (h26x-fallback / rav1e-fallback): hide the cards
(CUDA_VISIBLE_DEVICES=-1 for NVIDIA) or build without nvidia / amd /
qsv. TRANSCODE_ENCODER_BACKEND=h26x|rav1e|nvenc|amf|qsv still pins a
backend by name on the serial path.
When more than one GPU encodes a single file, each rung is chunked at GOP boundaries, encoded in parallel, and the AV1 packets are stitched into one MP4. Each chunk is an independent IDR-led GOP, so the result always plays — but each chunk's rate control is independent, so quality can step at the ~2 s seams. AMD (AMF) and Intel (QSV) chunks are constant-QP and already seam-flat; this knob chiefly governs NVENC (which otherwise runs VBR per chunk):
| Mode | Seams | Speed | Notes |
|---|---|---|---|
parallel (default) |
possible mild NVENC steps | fastest (all GPUs) | each chunk uses its encoder's normal rate control |
constqp |
flat | fast (all GPUs) | forces constant-QP; the QP is derived from the quality target, so quality still tracks it |
Wanting no seams at all is not a seam mode — it is one encoder per rung,
which is --encode single (or gpu:N); --seam-mode serial still parses as
that, for scripts that predate the split of the two questions.
(Single-GPU hosts, --single-gpu/--gpu, and HLS jobs are unaffected — HLS
segments are independent files by design.)
The decode pump tonemaps only when the policy says so — it never decides on its own:
--color |
Output | Bit depth | Needs |
|---|---|---|---|
sdr (default) |
tonemap HDR → SDR BT.709 | 8-bit | any encoder |
passthrough |
source color verbatim | source | 10-bit encoder if source is 10-bit |
hdr10 |
BT.2020 + PQ | 10-bit | a 10-bit encoder (below) |
hlg |
BT.2020 + HLG | 10-bit | a 10-bit encoder (below) |
10-bit / HDR output needs a 10-bit encoder for the output codec in this
build, and rivet transcode checks that before anything is decoded:
--codec |
10-bit / HDR with | 8-bit only on |
|---|---|---|
av1 (Main) |
nvidia (NVENC), amd (AMF), qsv (oneVPL P010), on a GPU with AV1 encode |
the software tier, rav1e (rav1e-fallback) |
h265 (Main 10) |
nvidia, amd, qsv, or the software tier h26x-fallback |
— |
h264 (High 10) |
the software tier h26x-fallback only |
NVENC, AMF, QSV (no hardware Hi10P encoder) |
It's 4:2:0 10-bit, HDR-tagged in the container (colr/mdcv/clli) and, for
H.264 / H.265, in the SPS VUI. A request the build can't produce for its codec
is refused by name, before the input is decoded — here on a --features nvidia
build:
$ rivet transcode in.mp4 -o out.mp4 --codec h264 --color hdr10
error: building output spec: invalid output spec: h264 at 10 bits (color=Hdr10, bit_depth=Auto) cannot be encoded: this build encodes h264 with nvenc (8-bit SDR). h264 at 10 bits needs the software tier (build with `h26x-fallback`); no hardware backend encodes h264 at 10 bits
What the source makes of the output is checked too, once the input is
probed and still before a frame is decoded. --pixel-format auto keeps a
10-bit source at 10 bits, and --color passthrough keeps an HDR source's
transfer, so a request that names neither can still need a 10-bit or HDR
encoder. Until 2026-09-14 such a job passed validation, started decoding and
failed building the encoder ("all 1 rung(s) failed"). Here on a
--features nvidia build, with a 10-bit SDR HEVC source:
$ rivet transcode clip10_hevc.mp4 -o out.mp4 --codec h264
error: transcoding clip10_hevc.mp4: invalid OutputSpec: h264 at 10 bits (color=TonemapToSdr, bit_depth=Auto) cannot be encoded: this build encodes h264 with nvenc (8-bit SDR). h264 at 10 bits needs the software tier (build with `h26x-fallback`); no hardware backend encodes h264 at 10 bits; the source is Yuv420p10le and bit_depth=Auto keeps its 10 bits: `--pixel-format 8bit` encodes it at 8 bits
A splice is checked against its first clip, which the output follows; an HDR
source under --color passthrough is told --color sdr tonemaps it.
On a build with both a card and the software tier (--features nvidia,h26x-fallback), a 10-bit H.264 output passes that check — h26x
encodes it — and every path then gets an encoder that can take it:
- The serial single-file encoder (one card, and every splice) is built by
the dispatcher, which falls back from NVENC, with no 10-bit H.264, to
h26x. - The chunk-and-stitch engine and the HLS ladder lease an encoder per
chunk or segment, with no fallback, so their pool is built for the output's
depth: a card whose encoder takes the codec only at 8 bits is left out, and
software slots take its place. Until 2026-09-14 they leased the card and
failed after decoding had started (
ladder worker 0 failed: creating encoder for segment: … NVENC on GPU 0 does not support 10-bit H264 encode). - A policy that pins the card (
--encode family:nvidia,--encode gpu:N) gets neither fallback nor software, so it is refused before decoding, naming the format:no encoder matches --encode family:nvidia for 10-bit H.264 on this host … drop the pin.
A backend pinned by name counts as well: TRANSCODE_ENCODER_BACKEND=h26x builds
the software encoder with or without h26x-fallback (the feature only gates the
automatic fallback), so it makes --codec h264|h265 at 10 bits valid on any
build; the pin applies to the serial single-file path. The check is against the build, not the card: a --features nvidia binary
accepts --codec av1 --color hdr10, and a GPU without AV1 encode (an RTX
30-series, say) then refuses it when the encoder is built. rivet capabilities
prints the per-codec answer.
12-bit sources (HEVC Main 12 / RExt 4:2:2 / 4:4:4 12-bit from the native decoder)
are accepted: they are narrowed to 10-bit with rounding (or to 8-bit for an
8-bit output), and 4:2:2 / 4:4:4 chroma is downsampled to 4:2:0. No encoder in the
tree takes more than 10 bits, so --pixel-format auto gives a 10-bit output for
a 12-bit source.
--chroma-downsample box|lanczos picks the 4:4:4 → 4:2:0 chroma filter for
4:4:4 sources (ProRes 4444, HEVC RExt 4:4:4): box (default) is the 2×2 average
and keeps outputs byte-identical to earlier releases; lanczos is a separable
Lanczos-2 sited where 4:2:0 decoders expect the chroma (co-sited horizontally,
midway vertically), measurably closer to the source after a round trip (numbers
in codec-encode.md). The
same word is the chroma-downsample settings key on the IPC socket, the HTTP API,
and the batch manifest (chroma_downsample:). No effect on 4:2:0 / 4:2:2 sources.
Two knobs beyond the --audio policy, both affecting transcoded audio only
(a passthrough track is copied verbatim by definition):
--audio-bitratesets the Opus target, ffmpeg-style (240k,1.5M, or a plain bits-per-second count). Omitted, the encoder derives it from the channel layout: 64 kbps per uncoupled stream + 96 kbps per coupled pair, so 64k mono, 96k stereo, 320k for 5.1.--audio-filterruns a chain over the decoded PCM before the encoder — todaychannelmap, for remapping / reordering / selecting channels. Full reference: audio filters.
Multichannel is carried end to end: 3–8 channels ride Opus's channel-mapping
family 1 (RFC 7845 §5.1.1.2). The limit is on the decode side — rivet decodes
MP3, Vorbis, AC-3 and E-AC-3, so a 5.1 Vorbis / AC-3 / E-AC-3 source can be
re-encoded to Opus 5.1 (--audio opus) while a 5.1 AAC source can only be
passed through.
Asking for --audio drop together with either knob is rejected rather than
silently ignored.
--subtitles all (the default) carries every text subtitle track the
source has — the -c:s copy equivalent. --subtitles none drops them, and a
language list such as --subtitles eng,deu keeps only those tracks, in that
order. Codes match by language, not spelling (en finds a track tagged eng,
ger finds deu), and a listed language the source lacks is logged, not an
error. rivet probe lists the tracks a file has.
| Source | Carried |
|---|---|
Matroska S_TEXT/UTF8 (SRT) |
✅ |
Matroska S_TEXT/ASS / S_TEXT/SSA |
✅ (markup stripped) |
Matroska S_TEXT/WEBVTT |
✅ (tags stripped) |
MP4 tx3g (mov_text) |
✅ |
MP4 wvtt (WebVTT in ISOBMFF) |
✅ (tags stripped) |
| PGS / VobSub / DVB (bitmap) | ❌ dropped with a warning — no text form exists |
Where they go depends on the output:
- single — one
tx3gtrack per language (3GPP timed text, what ffmpeg callsmov_text, the only subtitle format MP4 natively holds), each with its ownmdhdlanguage code for the player's track picker. - hls — one segmented-WebVTT rendition per language under
subs/<lang>/(seg-NNNNN.vtt+subtitles.m3u8), listed in the master playlist as anEXT-X-MEDIA:TYPE=SUBTITLESgroup that every variant names withSUBTITLES="subs". The subtitle segments sit on the video's segment grid — same boundaries, sameEXTINFdurations — and a cue that spans a boundary is repeated on both sides, per RFC 8216 §3.5. The first rendition isDEFAULT=YES.
Styling is not preserved: tx3g keeps style in side boxes keyed by byte range
rather than inline, so ASS override blocks ({\an8}), SRT/WebVTT tags
(<i>, <font>), and the ASS field prefix are stripped down to the text. Cue
timing is preserved; in tx3g, cue gaps become empty samples so the timeline
stays aligned.
Trims and splices carry them too: --trim-start/--trim-end clip the cues to
the kept window and re-base them to zero, and rivet splice
moves each clip's cues onto the joined timeline and merges tracks by language.
- single — one MP4 per rung. One rung → the
-ofile (faststart AV1 + audio). Multiple rungs →-omust be a directory; files are named per rung. - hls —
-ois the asset root:master.m3u8, anaudio/rendition group, andvideo/<height>p/{init.mp4, seg-*.m4s, playlist.m3u8}per rung, segment-aligned across the ladder for clean ABR.
# Single MP4 at the source resolution
rivet transcode input.mkv -o output.mp4
# Explicit 3-rung ladder → a directory of MP4s
rivet transcode input.mkv -o out_dir/ --rung 1920x1080 --rung 1280x720 --rung 640x360
# Auto ABR ladder capped at 1080p short side
rivet transcode input.mkv -o out_dir/ --ladder --max-short-side 1080
# CMAF/HLS package, 4 s segments
rivet transcode input.mkv -o hls_dir/ --mode hls --ladder --segment-seconds 4
# Quality + audio + frame-rate knobs
rivet transcode input.mkv -o out.mp4 --crf 28 --audio opus --max-fps 30
# Re-encode 5.1 audio to Opus at 240 kbps, re-tagging side surrounds as back
rivet transcode input.mkv -o out.mp4 --audio opus --audio-bitrate 240k \
--audio-filter 'channelmap=FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1'
# Non-local-means denoise with explicit patch / research-window sizes
rivet transcode input.mkv -o out.mp4 --filter 'nlmeans=s=1:p=7:pc=5:r=3:rc=3'
# Temporal denoise (ffmpeg's hqdn3d parameters: luma/chroma spatial, luma/chroma temporal)
rivet transcode input.mkv -o out.mp4 --filter 'hqdn3d=4:3:6:4.5'
# Keep every text subtitle track (the default), only some languages, or none
rivet transcode input.mkv -o out.mp4 --subtitles eng,deu
rivet transcode input.mkv -o out.mp4 --subtitles none
# Pin to one GPU / one vendor / decode elsewhere
rivet transcode input.mkv -o out.mp4 --gpu 1
rivet transcode input.mkv -o out.mp4 --encode family:nvidia --decode gpu:0
# Benchmark decoders up front and decode on the fastest GPU (multi-GPU hosts)
rivet transcode input.mkv -o out.mp4 --decode fastest
# HDR10 passthrough (needs a nvidia/amd hardware or ffmpeg build)
rivet transcode input.mkv -o out.mp4 --color hdr10 --pixel-format 10bit
# Splice/trim: cut a single input to [2s, 7s)
rivet transcode input.mkv -o cut.mp4 --trim-start 2 --trim-end 7Concatenate (and per-clip trim) several inputs into one output. Clips are
joined in order; each is decoded with its own decoder, trimmed to its window,
and the kept frames are re-encoded into one continuous, zero-based timeline (the
muxer numbers frames by count, so the join is gap-free with no PTS rewriting).
Because everything is re-encoded to a uniform output, the inputs may differ
in codec, resolution, or color — output config follows the first clip. Audio
is trimmed per clip and concatenated to match. Outputs a single MP4
(--mode single, the default) or a CMAF/HLS package (--mode hls) — for HLS the
spliced frame stream feeds the same multi-GPU engine as a normal ladder, so
segments stay keyframe-aligned across the join.
rivet splice -o <OUTPUT> [OPTIONS] <CLIP>...
<OUTPUT> is a file for --mode single, or a directory for --mode hls. Each
<CLIP> is a path, or PATH@START-END to trim it (seconds, either side
optional). @ is the separator so a Windows drive C:\… is unambiguous:
| Clip spec | Meaning |
|---|---|
a.mp4 |
the whole clip |
a.mp4@2-7 |
seconds [2, 7) |
a.mp4@2- |
from 2 s to the end |
a.mp4@-7 |
from the start to 7 s |
| Flag | Values / default | Description |
|---|---|---|
-o, --output <PATH> |
required | Output MP4 file (single) or directory (hls). |
--mode <MODE> |
single (default), hls |
Output shape: one MP4, or a CMAF/HLS package. |
--segment-seconds <S> |
default 4.0 |
HLS target segment length (--mode hls only). |
--codec <CODEC> |
av1 (default), h264, h265 |
Output video codec (as for transcode). |
--crf <N> |
encoder-native | Constant rate factor. |
--target <TARGET> |
standard (default), visually_lossless, high, low, vmaf=N |
Perceptual quality target, as for transcode. |
--gop <N> |
two seconds | GOP length in frames (alias --keyframe-interval). |
--color <POLICY> |
sdr (default), hdr10, hlg, passthrough |
Output colour, as for transcode. The output follows the first clip; later clips are mapped into it. |
--pixel-format <DEPTH> |
auto (default), 8bit, 10bit |
Output bit depth, as for transcode. 8bit is how a 10-bit first clip is joined into 8-bit H.264 on a build whose H.264 encoder is 8-bit — the remedy the depth refusal names. |
--chroma-downsample <FILTER> |
box (default), lanczos |
4:4:4 → 4:2:0 chroma filter for 4:4:4 clips. |
--filter <CHAIN> |
none | Video filter chain applied to every clip before scaling, as for transcode. |
--video-bitrate <BPS> / --video-buffer <DURATION> |
e.g. 3M / 500ms |
Code the output to a rate, with its coded picture buffer (1 s unless given), as for transcode. |
--audio <POLICY> |
auto (default), opus, drop |
Audio handling. |
--audio-bitrate <BPS> |
derived | Opus bitrate for transcoded audio (ignored for passthrough). |
--audio-filter <CHAIN> |
none | Audio filter chain before the Opus encoder, as for transcode. |
--subtitles <SELECTION> |
all (default), none, eng,deu |
Subtitle tracks to carry, as for transcode. Each clip's cues are clipped to its trim window, moved onto the joined timeline, and merged by language. |
--decode <PLAN> |
auto (default), whole, fastest, gpu:N, ranges:N |
The decode plan, as for transcode (--decode-gpu N still works). |
--encode <PLAN> |
all (default), per-rung, single, gpu:N, family:VENDOR |
The encode plan, as for transcode. A splice always takes the serial encode path, so here it chooses the card. |
Text subtitles come along: each clip's cues are trimmed with the clip, shifted by the length of the clips before it, and tracks join by language (clip 2's
engcontinues clip 1'seng; a language only some clips have is carried where it exists).
# Join three clips end-to-end
rivet splice -o out.mp4 intro.mp4 body.mkv outro.mov
# Join with per-clip trims (first 5 s of A, then 10–20 s of B, then all of C)
rivet splice -o out.mp4 a.mp4@0-5 b.mp4@10-20 c.mp4 --codec h265
# A single trimmed clip is just a trim (same as transcode --trim-*)
rivet splice -o cut.mp4 a.mp4@2-7
# Concatenate straight into an HLS package
rivet splice -o out_hls/ --mode hls a.mp4 b.mp4 c.mp4 --codec h265The library equivalents are
rivet::run_splice_job(Vec<Clip>, &spec, …)andOutputSpec::with_trim(start, end)for the single-input case.
rivet probe <INPUT> [--json]
Inspect a file without transcoding. --json emits a machine-readable object
(video_codec, width, height, frame_rate, duration); otherwise a human
summary is printed.
rivet probe input.mkv
rivet probe input.mkv --jsonrivet devices [--json]
List the GPUs rivet detects on this host — vendor, name, generation, VRAM, PCI
address, and (NVIDIA only, via NVML) a live load snapshot (GPU / encoder /
decoder utilization, memory, temperature). --json emits
{ "gpus": [ { index, vendor, name, generation, vram_mib, pci, load? } ] }.
rivet devices
rivet devices --jsonThis is hardware inventory — what's plugged in. What this build can actually
do with it is rivet capabilities (it depends on which
GPU feature the binary was compiled with).
rivet capabilities [--json]
rivet caps [--json]
Report what this build + host can do:
- Encode — AV1 / H.264 / H.265 4:2:0: the compiled backends
(
nvenc/amf/qsv/rav1e/h26x), then by codec the bit depth (8 or 10) and whether HDR (PQ/HLG, BT.2020) is producible for each output codec, with each compiled backend's own answer: H.264 is 8-bit SDR on every hardware backend and 10-bit HDR only onh26x; AV1 is 8-bit onrav1e. The by-codec answer is whatrivet transcodechecks--color/--pixel-formatagainst (rivet::spec::CodecOutputCaps). The last line,every codec, is what every output codec meets (the lowest depth, HDR only when every codec has it).--jsoncarries the same numbers:encode.by_codec—[{"codec","max_bit_depth","hdr","backends":[{"backend","max_bit_depth","hdr"}]}]— andencode.max_bit_depth/encode.hdrforevery codec. Until 2026-09-14 the JSON fields were the union — the best codec's answer — and said 10-bit HDR on anh26x-fallback-only build, which has no AV1 encoder; the text report led with that union (max depth/HDRlines) until 2026-09-18. Readby_codecfor one codec's answer. - Decode — a codec → backends table (which of
nvdec/amf/qsv/rav1ddecodeh264/hevc/vp8/vp9/av1/mpeg2/mpeg4/prores;rav1ddecodes AV1 only). - Devices — a one-line summary of the detected GPUs.
A backend only appears if its feature was compiled in (--features nvidia
etc.); the actual silicon (e.g. NVENC AV1 needs Ada+) is verified at encode time.
cargo build --release --features qsv
rivet capabilities # Encode: qsv 10-bit HDR · Decode: h264/hevc/av1/vp9 → qsv
rivet caps --jsonrivet pipe [--crf N] [--target T] [--gop FRAMES]
[--video-bitrate BPS] [--video-buffer DURATION]
[--audio auto|opus|drop] [--audio-bitrate BPS] [--audio-filter CHAIN]
[--color sdr|hdr10|hlg|passthrough] [--bit-depth auto|8bit|10bit]
[--max-fps F] [--width W] [--height H] [--gpu I]
[--decode PLAN] [--encode PLAN] [--filter CHAIN]
Stream a transcode through standard I/O: read media from stdin, write the
AV1/MP4 to stdout (progress goes to stderr so stdout stays clean). With no
flags it's the zero-config transcode (rivet::transcode_bytes: source
resolution, AV1, audio passthrough, and rivet transcode's default picture —
an HDR source tonemapped to 8-bit SDR, an SDR source at its own depth). A 10-bit
SDR source on a build whose AV1 encoder is 8-bit (rav1e) is refused before
anything is decoded, naming the setting that narrows it: --pixel-format 8bit
(alias of --bit-depth), which sends the job through the job engine, as any
flag does. (Until 2026-09-18 it asked rav1e for 10-bit AV1 and failed with "no
Av1 encoder available … rebuild with --features rav1e-fallback".) The flags override per
job — --width/--height scale, --color/--bit-depth set HDR/depth,
--crf/--speed set quality:
cat input.mkv | rivet pipe > output.mp4 # defaults
cat input.mkv | rivet pipe --crf 28 --width 1280 --height 720 > out.mp4
ffmpeg -i src.mov -f matroska - | rivet pipe --color hdr10 | ./my-uploaderSingle MP4 only — for an HLS package or a multi-rung ladder use
transcode with a directory output, or the
HTTP API.
cargo build --release --features batch # opt-in
rivet batch <MANIFEST> [--dry-run] [--stop-on-error]
Convert many files in one run from a YAML or JSON manifest — you list the
files (and how), rivet does them. Each job is an input (file or glob), an output,
and any transcode setting, on top of optional shared defaults. --dry-run
parses + expands globs + lists the planned jobs without converting; --stop-on-error
aborts on the first failure (default keeps going and exits non-zero if any failed).
rivet batch jobs.yaml --dry-run
rivet batch jobs.yamloutput_dir: out
defaults: { crf: 28, color: sdr }
jobs:
- input: in/a.mkv
output: out/a.mp4
crf: 24
- input: "clips/*.mp4" # glob -> one job per file -> out/<name>.mp4
output: out/Full DSL reference: batch.md — every key, the output-path rules,
glob inputs, defaults merge, and JSON examples. A ready-to-edit manifest is in
examples/batch.yaml / .json.
cargo build --release --features ipc # opt-in; the subcommand only exists in an ipc build
rivet ipc --socket <PATH>
Run a Unix-domain-socket server (opt-in ipc feature; Unix only at runtime)
so a long-running application can stream jobs in and out without spawning a
process per file or going through HTTP. rivet pipe (stdin/stdout streaming) is
always available and needs no feature.
Bind a socket, then for each connection: the client optionally writes a
settings header line, then the input media, half-closes its write side
(signals end-of-input), and reads the transcoded AV1/MP4 back until EOF. One
thread per connection; the process-wide GPU pool serializes the actual GPU work,
so concurrent clients simply queue.
Settings header (optional): if the stream begins with #rivet, the first
line is parsed as space-separated key=value settings and stripped before
decode. The keys are the shared TranscodeSettings vocabulary — the same names
as the CLI flags (mode rung ladder max-short-side segment-seconds
crf target gop video-bitrate video-buffer audio audio-bitrate
audio-filter subtitles color bit-depth
seam max-fps encode decode gpu gpu-family single-gpu decode-gpu
encode-policy width height filter codec), with the same values and
the same meaning — a #rivet encode=per-rung decode=whole header is exactly
--encode per-rung --decode whole. Real container
magic bytes never start with #rivet, so a raw media stream without a header
just gets the defaults. (A single socket connection produces one MP4, so
mode=hls/multi-rung isn't supported here — use the HTTP API for that.)
#rivet crf=28 color=hdr10 width=1280 height=720\n
<media bytes…>
rivet ipc --socket /tmp/rivet.sock &
# any client that does write → shutdown(WR) → read works, e.g. socat (no header):
socat - UNIX-CONNECT:/tmp/rivet.sock < input.mkv > output.mp4A minimal client with settings (Python):
import socket
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect("/tmp/rivet.sock")
s.sendall(b"#rivet crf=28 width=1280 height=720\n") # optional settings header
s.sendall(open("input.mkv", "rb").read())
s.shutdown(socket.SHUT_WR) # end-of-input
out = b"".join(iter(lambda: s.recv(65536), b""))
open("output.mp4", "wb").write(out) # AV1/MP4Single MP4 per connection. On Windows rivet ipc is unavailable — use
rivet pipe (stdin/stdout) or rivet serve
(HTTP).
rivet serve [--addr <ADDR>]
Runs the HTTP transcode API (requires a --features server build). --addr
defaults to 127.0.0.1:8080. See the HTTP API reference for endpoints.
cargo build --release --features server,nvidia
rivet serve --addr 0.0.0.0:8080| Variable | Effect |
|---|---|
RUST_LOG |
Log filter, e.g. RUST_LOG=debug or RUST_LOG=rivet=info. |
TRANSCODE_ENCODER_BACKEND |
Force an encoder backend: nvenc | amf | qsv. |
DISABLE_NVDEC |
Skip NVDEC for every codec (fall through to the next decode tier). |
DISABLE_NVDEC_<CODEC> |
Skip NVDEC for one family, e.g. DISABLE_NVDEC_AV1=1. |
RIVET_TEST_MEDIA |
Integration tests: directory of real media to run against. |