1use std::fmt;
4use std::str::FromStr;
5
6use crate::{ColorSpace, Error};
7use ffcine_core::color::named_enum;
8
9named_enum!(
10 Codec {
12 HevcNvenc = "hevc_nvenc",
13 H264Nvenc = "h264_nvenc",
14 Av1Nvenc = "av1_nvenc",
15 HevcVaapi = "hevc_vaapi",
16 HevcVulkan = "hevc_vulkan",
18 H264Vulkan = "h264_vulkan",
19 Av1Vulkan = "av1_vulkan",
20 Libx264 = "libx264",
21 Libx265 = "libx265",
22 Png = "png",
24 }
25);
26
27impl Codec {
28 pub const fn is_hardware(self) -> bool {
30 matches!(
31 self,
32 Self::HevcNvenc | Self::H264Nvenc | Self::Av1Nvenc | Self::HevcVaapi
33 ) || self.is_vulkan()
34 }
35
36 pub const fn is_vulkan(self) -> bool {
38 matches!(self, Self::HevcVulkan | Self::H264Vulkan | Self::Av1Vulkan)
39 }
40}
41
42named_enum!(
43 PixelFormat {
45 P010 = "p010le",
47 Nv12 = "nv12",
48 Yuv420p = "yuv420p",
49 Yuv420p10 = "yuv420p10le",
50 Gbrp16 = "gbrp16le",
51 Rgba = "rgba",
53 }
54);
55
56#[derive(Debug, Clone, Copy, PartialEq)]
58pub enum RateControl {
59 ConstQp(u8),
61 Crf(f32),
63 Bitrate(u64),
65}
66
67impl FromStr for RateControl {
68 type Err = Error;
69 fn from_str(s: &str) -> Result<Self, Error> {
71 let invalid = || Error::Invalid {
72 what: "rate control (qp=N, crf=N, bitrate=N)",
73 value: s.to_owned(),
74 };
75 let (k, v) = s.split_once('=').ok_or_else(invalid)?;
76 match k {
77 "qp" => v.parse().map(Self::ConstQp).map_err(|_| invalid()),
78 "crf" => v.parse().map(Self::Crf).map_err(|_| invalid()),
79 "bitrate" => v.parse().map(Self::Bitrate).map_err(|_| invalid()),
80 _ => Err(invalid()),
81 }
82 }
83}
84
85impl fmt::Display for RateControl {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 match self {
88 Self::ConstQp(q) => write!(f, "qp={q}"),
89 Self::Crf(c) => write!(f, "crf={c}"),
90 Self::Bitrate(b) => write!(f, "bitrate={b}"),
91 }
92 }
93}
94
95#[derive(Debug, Clone, PartialEq)]
97pub struct EncoderSettings {
98 pub codec: Codec,
99 pub preset: Option<String>,
100 pub tune: Option<String>,
101 pub profile: Option<String>,
102 pub rate: RateControl,
103 pub pixel_format: PixelFormat,
104 pub gop: Option<u32>,
106 pub color: ColorSpace,
108 pub extra: Vec<(String, String)>,
110}
111
112impl EncoderSettings {
113 pub fn nvenc_hevc_master() -> Self {
116 Self {
117 codec: Codec::HevcNvenc,
118 preset: Some("p6".into()),
119 tune: Some("hq".into()),
120 profile: Some("main10".into()),
121 rate: RateControl::ConstQp(12),
122 pixel_format: PixelFormat::P010,
123 gop: Some(24),
124 color: ColorSpace::BT709_TV,
125 extra: Vec::new(),
126 }
127 }
128
129 pub fn x264_preview() -> Self {
131 Self {
132 codec: Codec::Libx264,
133 preset: Some("veryfast".into()),
134 tune: None,
135 profile: None,
136 rate: RateControl::Crf(10.0),
137 pixel_format: PixelFormat::Yuv420p,
138 gop: Some(24),
139 color: ColorSpace::BT709_TV,
140 extra: Vec::new(),
141 }
142 }
143}
144
145impl EncoderSettings {
146 pub fn png_still() -> Self {
149 Self {
150 codec: Codec::Png,
151 preset: None,
152 tune: None,
153 profile: None,
154 rate: RateControl::ConstQp(0),
155 pixel_format: PixelFormat::Rgba,
156 gop: None,
157 color: ColorSpace::BT709_RGB,
158 extra: Vec::new(),
159 }
160 }
161}
162
163impl Default for EncoderSettings {
164 fn default() -> Self {
165 Self::nvenc_hevc_master()
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn rate_control_round_trip() {
175 for s in ["qp=12", "crf=18.5", "bitrate=50000000"] {
176 assert_eq!(s.parse::<RateControl>().unwrap().to_string(), s);
177 }
178 assert!("vbr".parse::<RateControl>().is_err());
179 }
180
181 #[test]
182 fn master_preset() {
183 let e = EncoderSettings::default();
184 assert!(e.codec.is_hardware());
185 assert_eq!(e.pixel_format.as_str(), "p010le");
186 }
187}