Skip to main content

rivet/
validate.rs

1//! Optional input-policy validation.
2//!
3//! These are **advisory** helpers — the job engine ([`crate::job::run_job`])
4//! does *not* enforce them, so rivet transcodes whatever it's given. They
5//! exist so policy-bearing callers (e.g. a hosted service) can gate uploads
6//! with the same limits the reference transcoder uses.
7
8use codec::frame::{PixelFormat, StreamInfo};
9
10/// Minimum accepted short side (pixels).
11pub const MIN_RESOLUTION: u32 = 360;
12/// Minimum accepted frame rate (fps).
13pub const MIN_FRAME_RATE: f64 = 15.0;
14/// Maximum accepted duration (seconds).
15pub const MAX_DURATION_SECS: f64 = 900.0;
16
17/// Why a stream was rejected by [`validate_stream`].
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ValidationErrorKind {
20    ResolutionTooSmall,
21    FrameRateTooSmall,
22    DurationTooLong,
23    UnsupportedPixelFormat,
24}
25
26/// A validation rejection: a machine-readable [`ValidationErrorKind`] plus a
27/// human message.
28#[derive(Debug, Clone)]
29pub struct ValidationError {
30    pub kind: ValidationErrorKind,
31    pub message: String,
32}
33
34impl std::fmt::Display for ValidationError {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        write!(f, "{}", self.message)
37    }
38}
39
40impl std::error::Error for ValidationError {}
41
42/// Gate a demuxed stream against the reference resolution/frame-rate/duration/
43/// pixel-format policy. Accepts `Yuv420p`, `Yuv420p10le`, `Yuv444p10le`,
44/// `Yuva444p10le` (the 4:4:4 formats are downsampled to 4:2:0 by the engine).
45pub fn validate_stream(info: &StreamInfo) -> Result<(), ValidationError> {
46    if info.width < MIN_RESOLUTION || info.height < MIN_RESOLUTION {
47        return Err(ValidationError {
48            kind: ValidationErrorKind::ResolutionTooSmall,
49            message: format!(
50                "Video resolution {}x{} is below the minimum {}x{}.",
51                info.width, info.height, MIN_RESOLUTION, MIN_RESOLUTION
52            ),
53        });
54    }
55    if info.frame_rate < MIN_FRAME_RATE {
56        return Err(ValidationError {
57            kind: ValidationErrorKind::FrameRateTooSmall,
58            message: format!(
59                "Video frame rate {:.1} fps is below the minimum {:.0} fps.",
60                info.frame_rate, MIN_FRAME_RATE
61            ),
62        });
63    }
64    if info.duration > MAX_DURATION_SECS {
65        return Err(ValidationError {
66            kind: ValidationErrorKind::DurationTooLong,
67            message: format!(
68                "Video duration {:.0}s exceeds the maximum {}s.",
69                info.duration, MAX_DURATION_SECS
70            ),
71        });
72    }
73    if !matches!(
74        info.pixel_format,
75        PixelFormat::Yuv420p
76            | PixelFormat::Yuv420p10le
77            | PixelFormat::Yuv444p10le
78            | PixelFormat::Yuva444p10le
79    ) {
80        return Err(ValidationError {
81            kind: ValidationErrorKind::UnsupportedPixelFormat,
82            message: format!(
83                "Pixel format {} is not supported.",
84                info.pixel_format.as_ffmpeg_str()
85            ),
86        });
87    }
88    Ok(())
89}
90
91/// Whether a source pixel format needs the per-frame 4:4:4 → 4:2:0 chroma
92/// downsample before encode. The engine consults this to set up the pump.
93pub fn needs_chroma_downsample(format: PixelFormat) -> bool {
94    matches!(
95        format,
96        PixelFormat::Yuv444p10le | PixelFormat::Yuva444p10le | PixelFormat::Yuv444p
97    )
98}