1mod cache;
8mod color;
9mod encode;
10mod hdr;
11mod hw_encode;
12mod path;
13mod probe;
14#[cfg(feature = "revelo")]
15mod probe_revelo;
16#[cfg(feature = "revelo")]
17pub use probe_revelo::probe as probe_revelo;
18
19pub use cache::*;
20pub use color::*;
21pub use encode::*;
22pub use hdr::*;
23pub use hw_encode::*;
24pub use path::*;
25pub use probe::*;
26
27use serde::{Deserialize, Serialize};
28use std::fmt;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37pub enum Codec {
38 #[serde(rename = "libx264")]
40 X264,
41 #[serde(rename = "libx265")]
43 X265,
44 #[serde(rename = "libsvtav1")]
46 SvtAv1,
47 #[serde(rename = "libvpx-vp9")]
49 Vp9,
50
51 #[serde(rename = "h264_nvenc")]
54 NvencH264,
55 #[serde(rename = "h264_qsv")]
57 QsvH264,
58 #[serde(rename = "h264_videotoolbox")]
60 VideoToolboxH264,
61 #[serde(rename = "h264_vaapi")]
63 VaapiH264,
64 #[serde(rename = "h264_amf")]
66 AmfH264,
67
68 #[serde(rename = "hevc_nvenc")]
71 NvencH265,
72 #[serde(rename = "hevc_qsv")]
74 QsvH265,
75 #[serde(rename = "hevc_videotoolbox")]
77 VideoToolboxH265,
78 #[serde(rename = "hevc_vaapi")]
80 VaapiH265,
81 #[serde(rename = "hevc_amf")]
83 AmfH265,
84
85 #[serde(rename = "av1_nvenc")]
89 NvencAv1,
90 #[serde(rename = "av1_qsv")]
92 QsvAv1,
93 #[serde(rename = "av1_vaapi")]
95 VaapiAv1,
96 #[serde(rename = "av1_amf")]
98 AmfAv1,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum EncoderBackend {
104 Software,
106 Nvenc,
108 Qsv,
110 VideoToolbox,
112 Vaapi,
114 Amf,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum CodecFamily {
121 H264,
123 H265,
125 Av1,
127 Vp9,
129}
130
131impl Codec {
132 pub fn as_str(&self) -> &'static str {
134 match self {
135 Codec::X264 => "libx264",
136 Codec::X265 => "libx265",
137 Codec::SvtAv1 => "libsvtav1",
138 Codec::Vp9 => "libvpx-vp9",
139 Codec::NvencH264 => "h264_nvenc",
140 Codec::QsvH264 => "h264_qsv",
141 Codec::VideoToolboxH264 => "h264_videotoolbox",
142 Codec::VaapiH264 => "h264_vaapi",
143 Codec::AmfH264 => "h264_amf",
144 Codec::NvencH265 => "hevc_nvenc",
145 Codec::QsvH265 => "hevc_qsv",
146 Codec::VideoToolboxH265 => "hevc_videotoolbox",
147 Codec::VaapiH265 => "hevc_vaapi",
148 Codec::AmfH265 => "hevc_amf",
149 Codec::NvencAv1 => "av1_nvenc",
150 Codec::QsvAv1 => "av1_qsv",
151 Codec::VaapiAv1 => "av1_vaapi",
152 Codec::AmfAv1 => "av1_amf",
153 }
154 }
155
156 pub fn backend(&self) -> EncoderBackend {
158 match self {
159 Codec::X264 | Codec::X265 | Codec::SvtAv1 | Codec::Vp9 => EncoderBackend::Software,
160 Codec::NvencH264 | Codec::NvencH265 | Codec::NvencAv1 => EncoderBackend::Nvenc,
161 Codec::QsvH264 | Codec::QsvH265 | Codec::QsvAv1 => EncoderBackend::Qsv,
162 Codec::VideoToolboxH264 | Codec::VideoToolboxH265 => EncoderBackend::VideoToolbox,
163 Codec::VaapiH264 | Codec::VaapiH265 | Codec::VaapiAv1 => EncoderBackend::Vaapi,
164 Codec::AmfH264 | Codec::AmfH265 | Codec::AmfAv1 => EncoderBackend::Amf,
165 }
166 }
167
168 pub fn family(&self) -> CodecFamily {
170 match self {
171 Codec::X264
172 | Codec::NvencH264
173 | Codec::QsvH264
174 | Codec::VideoToolboxH264
175 | Codec::VaapiH264
176 | Codec::AmfH264 => CodecFamily::H264,
177 Codec::X265
178 | Codec::NvencH265
179 | Codec::QsvH265
180 | Codec::VideoToolboxH265
181 | Codec::VaapiH265
182 | Codec::AmfH265 => CodecFamily::H265,
183 Codec::SvtAv1 | Codec::NvencAv1 | Codec::QsvAv1 | Codec::VaapiAv1 | Codec::AmfAv1 => {
184 CodecFamily::Av1
185 }
186 Codec::Vp9 => CodecFamily::Vp9,
187 }
188 }
189
190 pub fn is_hardware(&self) -> bool {
192 !matches!(self.backend(), EncoderBackend::Software)
193 }
194
195 pub fn is_software(&self) -> bool {
197 matches!(self.backend(), EncoderBackend::Software)
198 }
199}
200
201impl fmt::Display for Codec {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 f.write_str(self.as_str())
204 }
205}
206
207impl std::str::FromStr for Codec {
208 type Err = anyhow::Error;
209
210 fn from_str(s: &str) -> Result<Self, Self::Err> {
211 match s {
212 "libx264" | "x264" | "h264" => Ok(Codec::X264),
213 "libx265" | "x265" | "h265" | "hevc" => Ok(Codec::X265),
214 "libsvtav1" | "svtav1" | "av1" => Ok(Codec::SvtAv1),
215 "libvpx-vp9" | "vp9" | "libvpx" => Ok(Codec::Vp9),
216 "h264_nvenc" | "nvenc" | "nvenc_h264" => Ok(Codec::NvencH264),
218 "hevc_nvenc" | "nvenc_h265" | "nvenc_hevc" => Ok(Codec::NvencH265),
219 "h264_qsv" | "qsv" | "qsv_h264" => Ok(Codec::QsvH264),
221 "hevc_qsv" | "qsv_h265" | "qsv_hevc" => Ok(Codec::QsvH265),
222 "h264_videotoolbox" | "vt" | "vt_h264" | "videotoolbox" => Ok(Codec::VideoToolboxH264),
224 "hevc_videotoolbox" | "vt_h265" | "vt_hevc" => Ok(Codec::VideoToolboxH265),
225 "h264_vaapi" | "vaapi" | "vaapi_h264" => Ok(Codec::VaapiH264),
227 "hevc_vaapi" | "vaapi_h265" | "vaapi_hevc" => Ok(Codec::VaapiH265),
228 "h264_amf" | "amf" | "amf_h264" => Ok(Codec::AmfH264),
230 "hevc_amf" | "amf_h265" | "amf_hevc" => Ok(Codec::AmfH265),
231 "av1_nvenc" | "nvenc_av1" => Ok(Codec::NvencAv1),
233 "av1_qsv" | "qsv_av1" => Ok(Codec::QsvAv1),
234 "av1_vaapi" | "vaapi_av1" => Ok(Codec::VaapiAv1),
235 "av1_amf" | "amf_av1" => Ok(Codec::AmfAv1),
236 _ => Err(anyhow::anyhow!("unknown codec: {s}")),
237 }
238 }
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
243pub struct Resolution {
244 pub width: i32,
246 pub height: i32,
248}
249
250impl Resolution {
251 pub const fn new(width: i32, height: i32) -> Self {
253 Self { width, height }
254 }
255
256 pub fn label(&self) -> String {
258 match self.height {
259 h if h >= 2160 => "2160p".into(),
260 h if h >= 1440 => "1440p".into(),
261 h if h >= 1080 => "1080p".into(),
262 h if h >= 720 => "720p".into(),
263 h if h >= 480 => "480p".into(),
264 h if h >= 360 => "360p".into(),
265 h if h >= 240 => "240p".into(),
266 h => format!("{h}p"),
267 }
268 }
269}
270
271impl fmt::Display for Resolution {
272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 write!(f, "{}x{}", self.width, self.height)
274 }
275}
276
277impl std::str::FromStr for Resolution {
278 type Err = anyhow::Error;
279
280 fn from_str(s: &str) -> Result<Self, Self::Err> {
281 match s.to_lowercase().as_str() {
282 "2160p" | "4k" => Ok(RES_2160P),
283 "1440p" => Ok(RES_1440P),
284 "1080p" => Ok(RES_1080P),
285 "720p" => Ok(RES_720P),
286 "480p" => Ok(RES_480P),
287 "360p" => Ok(RES_360P),
288 "240p" => Ok(RES_240P),
289 other => {
290 if let Some((w, h)) = other.split_once('x') {
291 Ok(Resolution::new(w.parse()?, h.parse()?))
292 } else {
293 Err(anyhow::anyhow!("invalid resolution: {other}"))
294 }
295 }
296 }
297 }
298}
299
300pub const RES_2160P: Resolution = Resolution::new(3840, 2160);
302pub const RES_1440P: Resolution = Resolution::new(2560, 1440);
304pub const RES_1080P: Resolution = Resolution::new(1920, 1080);
306pub const RES_720P: Resolution = Resolution::new(1280, 720);
308pub const RES_480P: Resolution = Resolution::new(854, 480);
310pub const RES_360P: Resolution = Resolution::new(640, 360);
312pub const RES_240P: Resolution = Resolution::new(426, 240);
314
315#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
317#[serde(rename_all = "lowercase")]
318pub enum RateControlMode {
319 #[default]
321 Crf,
322 CappedCrf,
324 Qp,
326 Vbr,
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 #[test]
335 fn test_codec_as_str() {
336 assert_eq!(Codec::X264.as_str(), "libx264");
337 assert_eq!(Codec::X265.as_str(), "libx265");
338 assert_eq!(Codec::SvtAv1.as_str(), "libsvtav1");
339 assert_eq!(Codec::Vp9.as_str(), "libvpx-vp9");
340 assert_eq!(Codec::NvencH264.as_str(), "h264_nvenc");
341 assert_eq!(Codec::QsvH264.as_str(), "h264_qsv");
342 assert_eq!(Codec::VideoToolboxH264.as_str(), "h264_videotoolbox");
343 assert_eq!(Codec::VaapiH264.as_str(), "h264_vaapi");
344 assert_eq!(Codec::AmfH264.as_str(), "h264_amf");
345 assert_eq!(Codec::NvencH265.as_str(), "hevc_nvenc");
346 }
347
348 #[test]
349 fn test_codec_display() {
350 assert_eq!(format!("{}", Codec::X264), "libx264");
351 assert_eq!(format!("{}", Codec::NvencH264), "h264_nvenc");
352 }
353
354 #[test]
355 fn test_codec_from_str() {
356 assert_eq!("libx264".parse::<Codec>().unwrap(), Codec::X264);
357 assert_eq!("x264".parse::<Codec>().unwrap(), Codec::X264);
358 assert_eq!("h264".parse::<Codec>().unwrap(), Codec::X264);
359 assert_eq!("libx265".parse::<Codec>().unwrap(), Codec::X265);
360 assert_eq!("x265".parse::<Codec>().unwrap(), Codec::X265);
361 assert_eq!("h265".parse::<Codec>().unwrap(), Codec::X265);
362 assert_eq!("hevc".parse::<Codec>().unwrap(), Codec::X265);
363 assert_eq!("libsvtav1".parse::<Codec>().unwrap(), Codec::SvtAv1);
364 assert_eq!("svtav1".parse::<Codec>().unwrap(), Codec::SvtAv1);
365 assert_eq!("av1".parse::<Codec>().unwrap(), Codec::SvtAv1);
366 assert_eq!("libvpx-vp9".parse::<Codec>().unwrap(), Codec::Vp9);
367 assert_eq!("vp9".parse::<Codec>().unwrap(), Codec::Vp9);
368 assert_eq!("h264_nvenc".parse::<Codec>().unwrap(), Codec::NvencH264);
369 assert_eq!("nvenc".parse::<Codec>().unwrap(), Codec::NvencH264);
370 assert_eq!("hevc_nvenc".parse::<Codec>().unwrap(), Codec::NvencH265);
371 assert_eq!("h264_qsv".parse::<Codec>().unwrap(), Codec::QsvH264);
372 assert_eq!("qsv".parse::<Codec>().unwrap(), Codec::QsvH264);
373 assert_eq!("vt".parse::<Codec>().unwrap(), Codec::VideoToolboxH264);
374 assert_eq!("h264_vaapi".parse::<Codec>().unwrap(), Codec::VaapiH264);
375 assert_eq!("vaapi".parse::<Codec>().unwrap(), Codec::VaapiH264);
376 assert_eq!("h264_amf".parse::<Codec>().unwrap(), Codec::AmfH264);
377 assert_eq!("amf".parse::<Codec>().unwrap(), Codec::AmfH264);
378 assert!("unknown".parse::<Codec>().is_err());
379 }
380
381 #[test]
382 fn test_codec_backend() {
383 assert_eq!(Codec::X264.backend(), EncoderBackend::Software);
384 assert_eq!(Codec::NvencH264.backend(), EncoderBackend::Nvenc);
385 assert_eq!(Codec::QsvH264.backend(), EncoderBackend::Qsv);
386 assert_eq!(Codec::VideoToolboxH264.backend(), EncoderBackend::VideoToolbox);
387 assert_eq!(Codec::VaapiH264.backend(), EncoderBackend::Vaapi);
388 assert_eq!(Codec::AmfH264.backend(), EncoderBackend::Amf);
389 }
390
391 #[test]
392 fn test_codec_family() {
393 assert_eq!(Codec::X264.family(), CodecFamily::H264);
394 assert_eq!(Codec::NvencH264.family(), CodecFamily::H264);
395 assert_eq!(Codec::X265.family(), CodecFamily::H265);
396 assert_eq!(Codec::NvencH265.family(), CodecFamily::H265);
397 assert_eq!(Codec::SvtAv1.family(), CodecFamily::Av1);
398 assert_eq!(Codec::Vp9.family(), CodecFamily::Vp9);
399 }
400
401 #[test]
402 fn test_codec_is_hardware() {
403 assert!(!Codec::X264.is_hardware());
404 assert!(!Codec::X265.is_hardware());
405 assert!(!Codec::SvtAv1.is_hardware());
406 assert!(Codec::NvencH264.is_hardware());
407 assert!(Codec::QsvH265.is_hardware());
408 assert!(Codec::VideoToolboxH264.is_hardware());
409 }
410
411 #[test]
412 fn test_codec_is_software() {
413 assert!(Codec::X264.is_software());
414 assert!(!Codec::NvencH264.is_software());
415 }
416
417 #[test]
418 fn test_codec_serde_roundtrip() {
419 for codec in &[
420 Codec::X264,
421 Codec::X265,
422 Codec::SvtAv1,
423 Codec::NvencH264,
424 Codec::NvencH265,
425 Codec::QsvH264,
426 ] {
427 let json = serde_json::to_string(codec).unwrap();
428 let back: Codec = serde_json::from_str(&json).unwrap();
429 assert_eq!(*codec, back);
430 }
431 }
432
433 #[test]
434 fn test_av1_hw_codec_as_str() {
435 assert_eq!(Codec::NvencAv1.as_str(), "av1_nvenc");
436 assert_eq!(Codec::QsvAv1.as_str(), "av1_qsv");
437 assert_eq!(Codec::VaapiAv1.as_str(), "av1_vaapi");
438 assert_eq!(Codec::AmfAv1.as_str(), "av1_amf");
439 }
440
441 #[test]
442 fn test_av1_hw_codec_from_str() {
443 assert_eq!("av1_nvenc".parse::<Codec>().unwrap(), Codec::NvencAv1);
444 assert_eq!("nvenc_av1".parse::<Codec>().unwrap(), Codec::NvencAv1);
445 assert_eq!("av1_qsv".parse::<Codec>().unwrap(), Codec::QsvAv1);
446 assert_eq!("av1_vaapi".parse::<Codec>().unwrap(), Codec::VaapiAv1);
447 assert_eq!("av1_amf".parse::<Codec>().unwrap(), Codec::AmfAv1);
448 }
449
450 #[test]
451 fn test_av1_hw_codec_backend_and_family() {
452 for codec in &[Codec::NvencAv1, Codec::QsvAv1, Codec::VaapiAv1, Codec::AmfAv1] {
453 assert_eq!(codec.family(), CodecFamily::Av1);
454 assert!(codec.is_hardware());
455 }
456 assert_eq!(Codec::NvencAv1.backend(), EncoderBackend::Nvenc);
457 assert_eq!(Codec::QsvAv1.backend(), EncoderBackend::Qsv);
458 assert_eq!(Codec::VaapiAv1.backend(), EncoderBackend::Vaapi);
459 assert_eq!(Codec::AmfAv1.backend(), EncoderBackend::Amf);
460 }
461
462 #[test]
463 fn test_codec_eq() {
464 assert_eq!(Codec::X264, Codec::X264);
465 assert_ne!(Codec::X264, Codec::X265);
466 assert_ne!(Codec::X264, Codec::NvencH264);
467 }
468
469 #[test]
470 fn test_codec_hash() {
471 use std::collections::HashSet;
472 let mut set = HashSet::new();
473 set.insert(Codec::X264);
474 set.insert(Codec::X264);
475 assert_eq!(set.len(), 1);
476 }
477
478 #[test]
479 fn test_resolution_new() {
480 let r = Resolution::new(1920, 1080);
481 assert_eq!(r.width, 1920);
482 assert_eq!(r.height, 1080);
483 }
484
485 #[test]
486 fn test_resolution_label() {
487 assert_eq!(Resolution::new(3840, 2160).label(), "2160p");
488 assert_eq!(Resolution::new(2560, 1440).label(), "1440p");
489 assert_eq!(Resolution::new(1920, 1080).label(), "1080p");
490 assert_eq!(Resolution::new(1280, 720).label(), "720p");
491 assert_eq!(Resolution::new(854, 480).label(), "480p");
492 assert_eq!(Resolution::new(640, 360).label(), "360p");
493 assert_eq!(Resolution::new(426, 240).label(), "240p");
494 assert_eq!(Resolution::new(320, 200).label(), "200p");
495 }
496
497 #[test]
498 fn test_resolution_display() {
499 assert_eq!(format!("{}", Resolution::new(1920, 1080)), "1920x1080");
500 assert_eq!(format!("{}", Resolution::new(640, 360)), "640x360");
501 }
502
503 #[test]
504 fn test_resolution_from_str() {
505 assert_eq!("1080p".parse::<Resolution>().unwrap(), RES_1080P);
506 assert_eq!("720p".parse::<Resolution>().unwrap(), RES_720P);
507 assert_eq!("480p".parse::<Resolution>().unwrap(), RES_480P);
508 assert_eq!("360p".parse::<Resolution>().unwrap(), RES_360P);
509 assert_eq!("240p".parse::<Resolution>().unwrap(), RES_240P);
510 assert_eq!("1440p".parse::<Resolution>().unwrap(), RES_1440P);
511 assert_eq!("2160p".parse::<Resolution>().unwrap(), RES_2160P);
512 assert_eq!("4k".parse::<Resolution>().unwrap(), RES_2160P);
513 assert_eq!("1920x1080".parse::<Resolution>().unwrap(), RES_1080P);
514 assert_eq!("640x360".parse::<Resolution>().unwrap(), RES_360P);
515 assert!("invalid".parse::<Resolution>().is_err());
516 }
517
518 #[test]
519 fn test_resolution_serde_roundtrip() {
520 let r = RES_1080P;
521 let json = serde_json::to_string(&r).unwrap();
522 let back: Resolution = serde_json::from_str(&json).unwrap();
523 assert_eq!(r, back);
524 }
525
526 #[test]
527 fn test_resolution_const_equality() {
528 assert_eq!(RES_2160P, Resolution::new(3840, 2160));
529 assert_eq!(RES_1440P, Resolution::new(2560, 1440));
530 assert_eq!(RES_1080P, Resolution::new(1920, 1080));
531 assert_eq!(RES_720P, Resolution::new(1280, 720));
532 assert_eq!(RES_480P, Resolution::new(854, 480));
533 assert_eq!(RES_360P, Resolution::new(640, 360));
534 assert_eq!(RES_240P, Resolution::new(426, 240));
535 }
536
537 #[test]
538 fn test_rate_control_mode_default() {
539 assert_eq!(RateControlMode::default(), RateControlMode::Crf);
540 }
541
542 #[test]
543 fn test_rate_control_mode_serde() {
544 let json = serde_json::to_string(&RateControlMode::Crf).unwrap();
545 assert_eq!(json, "\"crf\"");
546 let back: RateControlMode = serde_json::from_str("\"vbr\"").unwrap();
547 assert_eq!(back, RateControlMode::Vbr);
548 }
549
550 #[test]
551 fn test_ffmpeg_path_default() {
552 let path = ffmpeg_path();
553 assert!(!path.is_empty());
554 }
555
556 #[test]
557 fn test_ffprobe_path_default() {
558 let path = ffprobe_path();
559 assert!(!path.is_empty());
560 }
561
562 #[test]
563 fn test_ffmpeg_path_respects_env() {
564 let old = std::env::var("VISER_FFMPEG").ok();
566 unsafe {
567 std::env::set_var("VISER_FFMPEG", "/custom/ffmpeg");
568 }
569 assert_eq!(ffmpeg_path(), "/custom/ffmpeg");
570 unsafe {
571 match old {
572 Some(v) => std::env::set_var("VISER_FFMPEG", v),
573 None => std::env::remove_var("VISER_FFMPEG"),
574 }
575 }
576 }
577
578 #[test]
579 fn test_ffprobe_path_respects_env() {
580 let old = std::env::var("VISER_FFPROBE").ok();
582 unsafe {
583 std::env::set_var("VISER_FFPROBE", "/custom/ffprobe");
584 }
585 assert_eq!(ffprobe_path(), "/custom/ffprobe");
586 unsafe {
587 match old {
588 Some(v) => std::env::set_var("VISER_FFPROBE", v),
589 None => std::env::remove_var("VISER_FFPROBE"),
590 }
591 }
592 }
593}