Skip to main content

rivet/spec/
policy.rs

1//! Policy enums — how video/audio codec, container, muxer, output-mode, color,
2//! bit-depth, encode/decode distribution, chunk-seam handling, and GPU family
3//! are selected. All types are `pub` and re-exported from the parent `spec`
4//! module so callers reach them as `rivet::spec::VideoCodecPolicy`, etc.
5
6use codec::frame::VideoCodec;
7
8/// Output **video** codec policy — the video analogue of [`AudioCodecPolicy`].
9/// Selects which codec the encoder produces:
10/// - `Av1` *(default)* — royalty-clean (AV1 + Opus in MP4 = zero royalty exposure).
11/// - `H264` / `H265` — for legacy-player compatibility; they carry the
12///   patent-licensing obligations AV1 was chosen to avoid.
13///
14/// All three work for single-file MP4 **and** CMAF/HLS. Resolve to the
15/// encoder/muxer's [`VideoCodec`] with [`VideoCodecPolicy::codec`].
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
17pub enum VideoCodecPolicy {
18    #[default]
19    Av1,
20    H264,
21    H265,
22}
23
24impl VideoCodecPolicy {
25    /// Resolve to the low-level [`VideoCodec`] the encoder + muxer consume.
26    pub fn codec(self) -> VideoCodec {
27        match self {
28            VideoCodecPolicy::Av1 => VideoCodec::Av1,
29            VideoCodecPolicy::H264 => VideoCodec::H264,
30            VideoCodecPolicy::H265 => VideoCodec::H265,
31        }
32    }
33}
34
35/// Output **audio** codec policy — how the source audio track is handled.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
37pub enum AudioCodecPolicy {
38    /// Passthrough AAC / Opus / AC-3 / E-AC-3 verbatim; transcode MP3 /
39    /// Vorbis to Opus; drop anything else.
40    #[default]
41    Auto,
42    /// Keep/produce Opus: passthrough Opus, transcode everything else to Opus.
43    ForceOpus,
44    /// Drop audio entirely (video-only output).
45    Drop,
46}
47
48/// Deprecated alias for [`AudioCodecPolicy`] (renamed for symmetry with
49/// [`VideoCodecPolicy`]).
50#[deprecated(since = "0.1.5", note = "renamed to AudioCodecPolicy")]
51pub type AudioPolicy = AudioCodecPolicy;
52
53/// Output container.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
55pub enum Container {
56    /// Plain MP4 (ISO-BMFF), one self-contained file.
57    #[default]
58    Mp4,
59    /// Fragmented MP4 (CMAF) — `moof`+`mdat` segments, for HLS/DASH.
60    Cmaf,
61}
62
63/// Muxer — how the container bytes are assembled.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
65pub enum Muxer {
66    /// `Av1Mp4Muxer` — a single faststart MP4 with interleaved A/V.
67    #[default]
68    Mp4File,
69    /// `CmafVideoMuxer` + `CmafAudioMuxer` + HLS playlists.
70    CmafHls,
71}
72
73/// The high-level shape of the output.
74#[derive(Debug, Clone, PartialEq)]
75pub enum OutputMode {
76    /// One self-contained file per rung.
77    SingleFile,
78    /// Segmented CMAF + HLS: a media playlist per rung, a shared audio
79    /// rendition, and a master playlist. `segment_seconds` is the target
80    /// segment length (segments still break on keyframes).
81    Hls { segment_seconds: f32 },
82}
83
84impl Default for OutputMode {
85    fn default() -> Self {
86        OutputMode::SingleFile
87    }
88}
89
90/// How the decode pump selects its GPU — the decode-side counterpart to
91/// [`EncodePolicy`]. A sum type so the modes stay mutually exclusive: you can't
92/// accidentally ask for "a specific GPU **and** the fastest".
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
94pub enum DecodePolicy {
95    /// Follow the encode policy: the first device of the selected family/set,
96    /// round-robin for per-rung pumps. The default.
97    #[default]
98    Auto,
99    /// Pin decode to this physical GPU index (e.g. decode on an iGPU while the
100    /// dGPUs encode).
101    SpecificGpu(u32),
102    /// Benchmark every decode-capable GPU on a short prefix of the input before
103    /// the job and pin the pump to the fastest. The engine resolves this to
104    /// `SpecificGpu` once the winner is known; a no-op on single-GPU hosts.
105    FastestGpu,
106}
107
108impl DecodePolicy {
109    /// The concrete pinned GPU index, if any. `Auto` and an unresolved
110    /// `FastestGpu` both return `None`, so the engine follows the encode policy.
111    pub fn gpu_index(self) -> Option<u32> {
112        match self {
113            DecodePolicy::SpecificGpu(i) => Some(i),
114            DecodePolicy::Auto | DecodePolicy::FastestGpu => None,
115        }
116    }
117
118    /// Whether the engine should benchmark decoders and resolve a fastest GPU.
119    pub fn is_fastest(self) -> bool {
120        matches!(self, DecodePolicy::FastestGpu)
121    }
122}
123
124impl std::str::FromStr for DecodePolicy {
125    type Err = String;
126
127    /// Parse `auto` / `fastest` / a GPU index — the `--decode-gpu` value space.
128    fn from_str(s: &str) -> Result<Self, Self::Err> {
129        match s.trim().to_ascii_lowercase().as_str() {
130            "" | "auto" => Ok(DecodePolicy::Auto),
131            "fastest" => Ok(DecodePolicy::FastestGpu),
132            other => other.parse::<u32>().map(DecodePolicy::SpecificGpu).map_err(|_| {
133                format!("decode-gpu must be 'auto', 'fastest', or a GPU index; got '{other}'")
134            }),
135        }
136    }
137}
138
139/// Selects how a job's encode work is distributed across the host's GPUs.
140///
141/// Applies to both the single-file and HLS paths: `AllGpus` runs the multi-GPU
142/// engine (decode once, chunk each rung across every GPU, stitch); `SingleGpu`
143/// constrains the GPU pool to one device and (for single-file) takes the serial
144/// encode path with no chunk overhead.
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
146pub enum EncodePolicy {
147    /// Use **all** available GPUs (the multi-GPU lease-pool engine). For
148    /// single-file this chunk-encodes each rung across the GPUs and stitches
149    /// the packets; it falls back to single-GPU serial encode when only one
150    /// GPU is present or the frame count is unknown. This is the default.
151    #[default]
152    AllGpus,
153    /// Use a **single** GPU. `None` picks the first available GPU; `Some(i)`
154    /// pins to GPU index `i`. Single-file uses the serial encode path.
155    SingleGpu(Option<u32>),
156    /// Use every GPU of one **vendor family** (and only that family) — e.g.
157    /// `Family(GpuFamily::Nvidia)` on a host with an NVIDIA discrete + an
158    /// integrated AMD/Intel GPU uses just the NVIDIA cards. With more than one
159    /// device in the family, single-file chunks across them like `AllGpus`.
160    Family(GpuFamily),
161}
162
163/// A GPU vendor family, for constraining encode to one vendor's devices.
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub enum GpuFamily {
166    Nvidia,
167    Amd,
168    Intel,
169}
170
171/// How the multi-GPU **single-file** path keeps quality consistent across the
172/// chunk seams it stitches into one continuous video.
173///
174/// Only relevant when more than one GPU encodes a single file (the `AllGpus` /
175/// `Family` policies on a multi-GPU host); single-GPU hosts, `SingleGpu`, and
176/// HLS (whose segments are independent by design) are unaffected. AMD (AMF) and
177/// Intel (QSV) chunks are already constant-QP, so their seams are quality-flat
178/// — this chiefly governs **NVENC**, which otherwise runs VBR per chunk and can
179/// leave a mild quality step at the ~2 s boundaries.
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
181pub enum ChunkSeamMode {
182    /// Default. Chunk across GPUs for throughput; each chunk uses its encoder's
183    /// normal rate control (VBR on NVENC). Fastest; NVENC may show mild quality
184    /// steps at the seams on complex content.
185    #[default]
186    Parallel,
187    /// Chunk across GPUs but force **constant-QP** so the seams are
188    /// quality-flat, keeping the multi-GPU speedup. The QP is derived from the
189    /// `QualityTarget` (via the per-encoder tuning CQ), so quality still tracks
190    /// the target — the hand-rolled NVENC sets a real const-QP rather than a
191    /// preset default. AMD/QSV are unchanged (already constant-QP).
192    ParallelConstQp,
193    /// Encode the whole file with **one encoder** — seam-free and
194    /// `QualityTarget`-accurate, at the cost of the multi-GPU single-file
195    /// speedup. (Like `SingleGpu`, but leaves multi-GPU in place for HLS jobs.)
196    Serial,
197}
198
199/// Output **color** policy — the gamut (which colors are representable) and the
200/// transfer curve (SDR vs HDR), plus whether to tonemap an HDR source down. This
201/// is the *color* half of the decision; bit depth is the separate [`BitDepth`]
202/// half (though the HDR variants here imply 10-bit on their own).
203///
204/// The decode pump never tonemaps on its own — this policy decides.
205///
206/// Glossary (the jargon these variants use):
207/// - **BT.709** — the standard HD / SDR color gamut. What the vast majority of
208///   video uses; "SDR" output means BT.709.
209/// - **BT.2020** — the *wide* gamut used by HDR: more saturated, deeper colors.
210/// - **PQ** (SMPTE ST 2084) — the HDR10 transfer curve (absolute brightness, up
211///   to 10,000 nits).
212/// - **HLG** (ARIB STD-B67) — the broadcast-friendly HDR transfer curve
213///   (relative brightness; degrades gracefully on SDR screens).
214/// - **tonemap** — squeeze an HDR signal's brightness/gamut down into SDR so it
215///   looks right on ordinary (BT.709, 8-bit) screens.
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
217pub enum ColorPolicy {
218    /// **SDR out.** Tonemap HDR (PQ / HLG) sources down to 8-bit **BT.709** SDR;
219    /// SDR sources pass through unchanged. The default — maximally web-compatible.
220    /// (Convenience builder: [`super::OutputSpec::web_sdr`].)
221    #[default]
222    TonemapToSdr,
223    /// **Verbatim.** Keep the source's gamut, transfer, and bit depth as-is — no
224    /// tonemap, no re-signaling. An HDR source stays HDR (needs a 10-bit
225    /// encoder); an SDR source stays SDR. (Builder: [`super::OutputSpec::passthrough`].)
226    Passthrough,
227    /// **HDR10 out.** Force **BT.2020** gamut + **PQ** transfer, 10-bit. Sets
228    /// 10-bit on its own, so you do *not* also need [`BitDepth::TenBit`].
229    /// (Builder: [`super::OutputSpec::hdr10`].)
230    Hdr10,
231    /// **HLG out.** Force **BT.2020** gamut + **HLG** transfer, 10-bit. Implies
232    /// 10-bit. (Builder: [`super::OutputSpec::hlg`].)
233    Hlg,
234}
235
236impl ColorPolicy {
237    /// Whether the decode pump tonemaps HDR→SDR under this policy.
238    pub fn tonemaps(self) -> bool {
239        matches!(self, ColorPolicy::TonemapToSdr)
240    }
241
242    /// Whether this policy signals HDR (PQ/HLG) in the output bitstream.
243    pub fn is_hdr(self) -> bool {
244        matches!(self, ColorPolicy::Hdr10 | ColorPolicy::Hlg)
245    }
246}
247
248/// Output **bit depth** — bits per sample. The on-disk pixel format is *derived*
249/// from this (the encoder is always AV1 4:2:0, the web-safe chroma subsampling):
250/// 8-bit → **`yuv420p`**, 10-bit → **`yuv420p10le`** (`le` = little-endian 16-bit
251/// words holding 10 valid bits). Bit depth is one axis; gamut + SDR/HDR transfer
252/// is the orthogonal [`ColorPolicy`] axis.
253///
254/// You rarely set this by hand: `Auto` derives it from the color policy.
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
256pub enum BitDepth {
257    /// Derive depth from the [`ColorPolicy`]: 8-bit for an SDR tonemap, 10-bit
258    /// for HDR (`Hdr10` / `Hlg`), the source's own depth for `Passthrough`. The
259    /// default — the right choice almost always.
260    #[default]
261    Auto,
262    /// Force **8-bit** 4:2:0 (`yuv420p`) — universal web compatibility.
263    EightBit,
264    /// Force **10-bit** 4:2:0 (`yuv420p10le`) — higher precision (banding-free
265    /// gradients), and required by the HDR policies. Needs a 10-bit-capable
266    /// encoder: NVENC (`nvidia`), AMF (`amd`), QSV (`qsv`), or `ffmpeg`.
267    TenBit,
268}