1use crate::{QualityMode, QualityPreset, RateControlMode, TuneMode};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone)]
8pub struct CodecConfig {
9 pub codec: String,
11 pub preset: QualityPreset,
13 pub tune: Option<TuneMode>,
15 pub profile: Option<String>,
17 pub level: Option<String>,
19 pub rate_control: RateControlMode,
21 pub options: Vec<(String, String)>,
23}
24
25impl CodecConfig {
26 #[must_use]
28 pub fn new(codec: impl Into<String>) -> Self {
29 Self {
30 codec: codec.into(),
31 preset: QualityPreset::Medium,
32 tune: None,
33 profile: None,
34 level: None,
35 rate_control: RateControlMode::Crf(23),
36 options: Vec::new(),
37 }
38 }
39
40 #[must_use]
42 pub fn preset(mut self, preset: QualityPreset) -> Self {
43 self.preset = preset;
44 self
45 }
46
47 #[must_use]
49 pub fn tune(mut self, tune: TuneMode) -> Self {
50 self.tune = Some(tune);
51 self
52 }
53
54 #[must_use]
56 pub fn profile(mut self, profile: impl Into<String>) -> Self {
57 self.profile = Some(profile.into());
58 self
59 }
60
61 #[must_use]
63 pub fn level(mut self, level: impl Into<String>) -> Self {
64 self.level = Some(level.into());
65 self
66 }
67
68 #[must_use]
70 pub fn rate_control(mut self, mode: RateControlMode) -> Self {
71 self.rate_control = mode;
72 self
73 }
74
75 #[must_use]
77 pub fn option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
78 self.options.push((key.into(), value.into()));
79 self
80 }
81}
82
83#[derive(Debug, Clone)]
85pub struct H264Config {
86 base: CodecConfig,
87}
88
89impl H264Config {
90 #[must_use]
92 pub fn new() -> Self {
93 Self {
94 base: CodecConfig::new("h264"),
95 }
96 }
97
98 #[must_use]
100 pub fn profile(mut self, profile: H264Profile) -> Self {
101 self.base.profile = Some(profile.as_str().to_string());
102 self
103 }
104
105 #[must_use]
107 pub fn level(mut self, level: impl Into<String>) -> Self {
108 self.base.level = Some(level.into());
109 self
110 }
111
112 #[must_use]
114 pub fn cabac(mut self, enable: bool) -> Self {
115 self.base.options.push((
116 "cabac".to_string(),
117 if enable { "1" } else { "0" }.to_string(),
118 ));
119 self
120 }
121
122 #[must_use]
124 pub fn refs(mut self, refs: u8) -> Self {
125 self.base
126 .options
127 .push(("refs".to_string(), refs.to_string()));
128 self
129 }
130
131 #[must_use]
133 pub fn bframes(mut self, bframes: u8) -> Self {
134 self.base
135 .options
136 .push(("bframes".to_string(), bframes.to_string()));
137 self
138 }
139
140 #[must_use]
142 pub fn dct8x8(mut self, enable: bool) -> Self {
143 self.base.options.push((
144 "8x8dct".to_string(),
145 if enable { "1" } else { "0" }.to_string(),
146 ));
147 self
148 }
149
150 #[must_use]
152 pub fn deblock(mut self, alpha: i8, beta: i8) -> Self {
153 self.base
154 .options
155 .push(("deblock".to_string(), format!("{alpha}:{beta}")));
156 self
157 }
158
159 #[must_use]
161 pub fn build(self) -> CodecConfig {
162 self.base
163 }
164}
165
166impl Default for H264Config {
167 fn default() -> Self {
168 Self::new()
169 }
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174pub enum H264Profile {
175 Baseline,
177 Main,
179 High,
181 High10,
183 High422,
185 High444,
187}
188
189impl H264Profile {
190 #[must_use]
191 fn as_str(self) -> &'static str {
192 match self {
193 Self::Baseline => "baseline",
194 Self::Main => "main",
195 Self::High => "high",
196 Self::High10 => "high10",
197 Self::High422 => "high422",
198 Self::High444 => "high444",
199 }
200 }
201}
202
203#[derive(Debug, Clone)]
205pub struct Vp9Config {
206 base: CodecConfig,
207}
208
209impl Vp9Config {
210 #[must_use]
212 pub fn new() -> Self {
213 Self {
214 base: CodecConfig::new("vp9"),
215 }
216 }
217
218 #[must_use]
223 pub fn crf(crf_value: u8) -> Self {
224 let mut cfg = Self::new();
225 cfg.base.rate_control = RateControlMode::Crf(crf_value);
226 cfg
227 }
228
229 #[must_use]
231 pub fn cpu_used(mut self, cpu_used: u8) -> Self {
232 self.base
233 .options
234 .push(("cpu-used".to_string(), cpu_used.to_string()));
235 self
236 }
237
238 #[must_use]
240 pub fn tile_columns(mut self, columns: u8) -> Self {
241 self.base
242 .options
243 .push(("tile-columns".to_string(), columns.to_string()));
244 self
245 }
246
247 #[must_use]
249 pub fn with_tile_columns(mut self, cols: u8) -> Self {
250 self.base
251 .options
252 .push(("tile-columns".to_string(), cols.to_string()));
253 self
254 }
255
256 #[must_use]
258 pub fn tile_rows(mut self, rows: u8) -> Self {
259 self.base
260 .options
261 .push(("tile-rows".to_string(), rows.to_string()));
262 self
263 }
264
265 #[must_use]
267 pub fn frame_parallel(mut self, enable: bool) -> Self {
268 self.base.options.push((
269 "frame-parallel".to_string(),
270 if enable { "1" } else { "0" }.to_string(),
271 ));
272 self
273 }
274
275 #[must_use]
277 pub fn with_frame_parallel(mut self, enabled: bool) -> Self {
278 self.base.options.push((
279 "frame-parallel".to_string(),
280 if enabled { "1" } else { "0" }.to_string(),
281 ));
282 self
283 }
284
285 #[must_use]
287 pub fn auto_alt_ref(mut self, frames: u8) -> Self {
288 self.base
289 .options
290 .push(("auto-alt-ref".to_string(), frames.to_string()));
291 self
292 }
293
294 #[must_use]
298 pub fn lag_in_frames(mut self, lag: u32) -> Self {
299 self.base
300 .options
301 .push(("lag-in-frames".to_string(), lag.to_string()));
302 self
303 }
304
305 #[must_use]
307 pub fn with_lag_in_frames(mut self, frames: u8) -> Self {
308 self.base
309 .options
310 .push(("lag-in-frames".to_string(), frames.to_string()));
311 self
312 }
313
314 #[must_use]
316 pub fn row_mt(mut self, enable: bool) -> Self {
317 self.base.options.push((
318 "row-mt".to_string(),
319 if enable { "1" } else { "0" }.to_string(),
320 ));
321 self
322 }
323
324 #[must_use]
326 pub fn with_row_mt(mut self, enabled: bool) -> Self {
327 self.base.options.push((
328 "row-mt".to_string(),
329 if enabled { "1" } else { "0" }.to_string(),
330 ));
331 self
332 }
333
334 #[must_use]
339 pub fn screen_content() -> Self {
340 let mut cfg = Self::crf(33);
341 cfg.base
342 .options
343 .push(("cpu-used".to_string(), "5".to_string()));
344 cfg.base
345 .options
346 .push(("tile-columns".to_string(), "2".to_string()));
347 cfg.base
348 .options
349 .push(("row-mt".to_string(), "1".to_string()));
350 cfg.base
351 .options
352 .push(("lag-in-frames".to_string(), "0".to_string()));
353 cfg
354 }
355
356 #[must_use]
358 pub fn build(self) -> CodecConfig {
359 self.base
360 }
361}
362
363impl Default for Vp9Config {
364 fn default() -> Self {
365 Self::new()
366 }
367}
368
369#[derive(Debug, Clone)]
371pub struct Av1Config {
372 base: CodecConfig,
373}
374
375impl Av1Config {
376 #[must_use]
378 pub fn new() -> Self {
379 Self {
380 base: CodecConfig::new("av1"),
381 }
382 }
383
384 #[must_use]
386 pub fn cpu_used(mut self, cpu_used: u8) -> Self {
387 self.base
388 .options
389 .push(("cpu-used".to_string(), cpu_used.to_string()));
390 self
391 }
392
393 #[must_use]
395 pub fn tiles(mut self, columns: u8, rows: u8) -> Self {
396 self.base
397 .options
398 .push(("tiles".to_string(), format!("{columns}x{rows}")));
399 self
400 }
401
402 #[must_use]
404 pub fn row_mt(mut self, enable: bool) -> Self {
405 self.base.options.push((
406 "row-mt".to_string(),
407 if enable { "1" } else { "0" }.to_string(),
408 ));
409 self
410 }
411
412 #[must_use]
414 pub fn usage(mut self, usage: Av1Usage) -> Self {
415 self.base
416 .options
417 .push(("usage".to_string(), usage.as_str().to_string()));
418 self
419 }
420
421 #[must_use]
423 pub fn enable_film_grain(mut self, enable: bool) -> Self {
424 self.base.options.push((
425 "enable-film-grain".to_string(),
426 if enable { "1" } else { "0" }.to_string(),
427 ));
428 self
429 }
430
431 #[must_use]
433 pub fn build(self) -> CodecConfig {
434 self.base
435 }
436}
437
438impl Default for Av1Config {
439 fn default() -> Self {
440 Self::new()
441 }
442}
443
444#[derive(Debug, Clone, Copy, PartialEq, Eq)]
446pub enum Av1Usage {
447 Good,
449 Realtime,
451}
452
453impl Av1Usage {
454 #[must_use]
455 fn as_str(self) -> &'static str {
456 match self {
457 Self::Good => "good",
458 Self::Realtime => "realtime",
459 }
460 }
461}
462
463#[derive(Debug, Clone)]
465pub struct OpusConfig {
466 base: CodecConfig,
467}
468
469impl OpusConfig {
470 #[must_use]
472 pub fn new() -> Self {
473 Self {
474 base: CodecConfig::new("opus"),
475 }
476 }
477
478 #[must_use]
483 pub fn voice() -> Self {
484 Self::new()
485 .application(OpusApplication::Voip)
486 .complexity(10)
487 .vbr(true)
488 .with_fec(true)
489 }
490
491 #[must_use]
496 pub fn music() -> Self {
497 Self::new()
498 .application(OpusApplication::Audio)
499 .complexity(10)
500 .vbr(true)
501 .frame_duration(20.0)
502 }
503
504 #[must_use]
508 pub fn fullband() -> Self {
509 let mut cfg = Self::new()
510 .application(OpusApplication::Audio)
511 .complexity(10)
512 .vbr(true);
513 cfg.base
514 .options
515 .push(("cutoff".to_string(), "20000".to_string()));
516 cfg
517 }
518
519 #[must_use]
521 pub fn application(mut self, app: OpusApplication) -> Self {
522 self.base
523 .options
524 .push(("application".to_string(), app.as_str().to_string()));
525 self
526 }
527
528 #[must_use]
530 pub fn complexity(mut self, complexity: u8) -> Self {
531 self.base
532 .options
533 .push(("complexity".to_string(), complexity.to_string()));
534 self
535 }
536
537 #[must_use]
539 pub fn frame_duration(mut self, duration_ms: f32) -> Self {
540 self.base
541 .options
542 .push(("frame_duration".to_string(), duration_ms.to_string()));
543 self
544 }
545
546 #[must_use]
548 pub fn vbr(mut self, enable: bool) -> Self {
549 self.base.options.push((
550 "vbr".to_string(),
551 if enable { "on" } else { "off" }.to_string(),
552 ));
553 self
554 }
555
556 #[must_use]
558 pub fn with_vbr(mut self, enabled: bool) -> Self {
559 self.base.options.push((
560 "vbr".to_string(),
561 if enabled { "on" } else { "off" }.to_string(),
562 ));
563 self
564 }
565
566 #[must_use]
571 pub fn with_constrained_vbr(mut self, enabled: bool) -> Self {
572 self.base.options.push((
573 "cvbr".to_string(),
574 if enabled { "1" } else { "0" }.to_string(),
575 ));
576 self
577 }
578
579 #[must_use]
584 pub fn with_dtx(mut self, enabled: bool) -> Self {
585 self.base.options.push((
586 "dtx".to_string(),
587 if enabled { "1" } else { "0" }.to_string(),
588 ));
589 self
590 }
591
592 #[must_use]
597 pub fn with_fec(mut self, enabled: bool) -> Self {
598 self.base.options.push((
599 "inband_fec".to_string(),
600 if enabled { "1" } else { "0" }.to_string(),
601 ));
602 self
603 }
604
605 #[must_use]
610 pub fn with_packet_loss_perc(mut self, pct: u8) -> Self {
611 self.base
612 .options
613 .push(("packet_loss_perc".to_string(), pct.to_string()));
614 self
615 }
616
617 #[must_use]
619 pub fn build(self) -> CodecConfig {
620 self.base
621 }
622}
623
624impl Default for OpusConfig {
625 fn default() -> Self {
626 Self::new()
627 }
628}
629
630#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
632pub enum OpusApplication {
633 Voip,
635 Audio,
637 LowDelay,
639}
640
641impl OpusApplication {
642 #[must_use]
643 fn as_str(self) -> &'static str {
644 match self {
645 Self::Voip => "voip",
646 Self::Audio => "audio",
647 Self::LowDelay => "lowdelay",
648 }
649 }
650}
651
652#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
654pub enum Ffv1Level {
655 Level1,
657 Level3,
659}
660
661impl Ffv1Level {
662 #[must_use]
664 pub fn as_u8(self) -> u8 {
665 match self {
666 Self::Level1 => 1,
667 Self::Level3 => 3,
668 }
669 }
670}
671
672#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
674pub enum Ffv1Coder {
675 GolombRice,
677 Range,
679}
680
681impl Ffv1Coder {
682 #[must_use]
684 pub fn as_u8(self) -> u8 {
685 match self {
686 Self::GolombRice => 0,
687 Self::Range => 1,
688 }
689 }
690}
691
692#[derive(Debug, Clone)]
694pub struct Ffv1Config {
695 pub level: Ffv1Level,
697 pub coder: Ffv1Coder,
699 pub slice_count: u8,
701 pub context_model: u8,
703 pub checksum: bool,
705}
706
707impl Ffv1Config {
708 #[must_use]
710 pub fn new() -> Self {
711 Self {
712 level: Ffv1Level::Level3,
713 coder: Ffv1Coder::Range,
714 slice_count: 4,
715 context_model: 0,
716 checksum: true,
717 }
718 }
719
720 #[must_use]
725 pub fn lossless_archive() -> Self {
726 Self {
727 level: Ffv1Level::Level3,
728 coder: Ffv1Coder::Range,
729 slice_count: 16,
730 context_model: 1,
731 checksum: true,
732 }
733 }
734
735 #[must_use]
740 pub fn lossless_fast() -> Self {
741 Self {
742 level: Ffv1Level::Level1,
743 coder: Ffv1Coder::GolombRice,
744 slice_count: 4,
745 context_model: 0,
746 checksum: false,
747 }
748 }
749
750 #[must_use]
754 pub fn with_slices(mut self, count: u8) -> Self {
755 self.slice_count = count;
756 self
757 }
758
759 #[must_use]
761 pub fn build(self) -> CodecConfig {
762 let mut cfg = CodecConfig::new("ffv1");
763 cfg.options
764 .push(("level".to_string(), self.level.as_u8().to_string()));
765 cfg.options
766 .push(("coder".to_string(), self.coder.as_u8().to_string()));
767 cfg.options
768 .push(("slices".to_string(), self.slice_count.to_string()));
769 cfg.options
770 .push(("context".to_string(), self.context_model.to_string()));
771 cfg.options.push((
772 "slicecrc".to_string(),
773 if self.checksum { "1" } else { "0" }.to_string(),
774 ));
775 cfg.rate_control = RateControlMode::Crf(0); cfg
777 }
778}
779
780impl Default for Ffv1Config {
781 fn default() -> Self {
782 Self::new()
783 }
784}
785
786#[derive(Debug, Clone)]
788pub struct FlacConfig {
789 pub compression_level: u8,
791 pub block_size: u32,
793 pub verify: bool,
795}
796
797impl FlacConfig {
798 #[must_use]
800 pub fn new() -> Self {
801 Self {
802 compression_level: 5,
803 block_size: 4096,
804 verify: false,
805 }
806 }
807
808 #[must_use]
810 pub fn archival() -> Self {
811 Self {
812 compression_level: 8,
813 block_size: 4096,
814 verify: true,
815 }
816 }
817
818 #[must_use]
820 pub fn streaming() -> Self {
821 Self {
822 compression_level: 4,
823 block_size: 4096,
824 verify: false,
825 }
826 }
827
828 #[must_use]
830 pub fn fast() -> Self {
831 Self {
832 compression_level: 0,
833 block_size: 4096,
834 verify: false,
835 }
836 }
837
838 #[must_use]
840 pub fn build(self) -> CodecConfig {
841 let mut cfg = CodecConfig::new("flac");
842 cfg.options.push((
843 "compression_level".to_string(),
844 self.compression_level.to_string(),
845 ));
846 cfg.options
847 .push(("blocksize".to_string(), self.block_size.to_string()));
848 cfg.options.push((
849 "lpc_coeff_precision".to_string(),
850 "15".to_string(), ));
852 if self.verify {
853 cfg.options.push(("verify".to_string(), "1".to_string()));
854 }
855 cfg.rate_control = RateControlMode::Crf(0); cfg
857 }
858}
859
860impl Default for FlacConfig {
861 fn default() -> Self {
862 Self::new()
863 }
864}
865
866#[derive(Debug, Clone)]
871pub struct JxlConfig {
872 pub quality: Option<f32>,
876 pub effort: JxlEffort,
878 pub progressive: bool,
880 pub photon_noise_iso: Option<u32>,
886 pub extra_channels: u8,
888 pub modular: bool,
890 pub color_space: JxlColorSpace,
892 pub bit_depth: u8,
894}
895
896#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
898pub enum JxlEffort {
899 Lightning,
901 Thunder,
903 Falcon,
905 Cheetah,
907 Hare,
909 Wombat,
911 Squirrel,
913 Kitten,
915 Tortoise,
917 Glacier,
919}
920
921impl JxlEffort {
922 #[must_use]
924 pub fn as_u8(self) -> u8 {
925 match self {
926 Self::Lightning => 1,
927 Self::Thunder => 2,
928 Self::Falcon => 3,
929 Self::Cheetah => 4,
930 Self::Hare => 5,
931 Self::Wombat => 6,
932 Self::Squirrel => 7,
933 Self::Kitten => 8,
934 Self::Tortoise => 9,
935 Self::Glacier => 10,
936 }
937 }
938
939 #[must_use]
941 pub fn as_str(self) -> &'static str {
942 match self {
943 Self::Lightning => "lightning",
944 Self::Thunder => "thunder",
945 Self::Falcon => "falcon",
946 Self::Cheetah => "cheetah",
947 Self::Hare => "hare",
948 Self::Wombat => "wombat",
949 Self::Squirrel => "squirrel",
950 Self::Kitten => "kitten",
951 Self::Tortoise => "tortoise",
952 Self::Glacier => "glacier",
953 }
954 }
955}
956
957#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
959pub enum JxlColorSpace {
960 Rgb,
962 Xyb,
964 Gray,
966}
967
968impl JxlColorSpace {
969 #[must_use]
971 pub fn as_str(self) -> &'static str {
972 match self {
973 Self::Rgb => "rgb",
974 Self::Xyb => "xyb",
975 Self::Gray => "gray",
976 }
977 }
978}
979
980impl JxlConfig {
981 #[must_use]
983 pub fn new() -> Self {
984 Self {
985 quality: Some(75.0),
986 effort: JxlEffort::Squirrel,
987 progressive: false,
988 photon_noise_iso: None,
989 extra_channels: 0,
990 modular: false,
991 color_space: JxlColorSpace::Xyb,
992 bit_depth: 8,
993 }
994 }
995
996 #[must_use]
1001 pub fn lossless() -> Self {
1002 Self {
1003 quality: None,
1004 effort: JxlEffort::Tortoise,
1005 progressive: false,
1006 photon_noise_iso: None,
1007 extra_channels: 0,
1008 modular: true,
1009 color_space: JxlColorSpace::Rgb,
1010 bit_depth: 8,
1011 }
1012 }
1013
1014 #[must_use]
1019 pub fn web() -> Self {
1020 Self {
1021 quality: Some(80.0),
1022 effort: JxlEffort::Squirrel,
1023 progressive: true,
1024 photon_noise_iso: None,
1025 extra_channels: 0,
1026 modular: false,
1027 color_space: JxlColorSpace::Xyb,
1028 bit_depth: 8,
1029 }
1030 }
1031
1032 #[must_use]
1037 pub fn photography() -> Self {
1038 Self {
1039 quality: Some(90.0),
1040 effort: JxlEffort::Kitten,
1041 progressive: true,
1042 photon_noise_iso: Some(400),
1043 extra_channels: 0,
1044 modular: false,
1045 color_space: JxlColorSpace::Xyb,
1046 bit_depth: 16,
1047 }
1048 }
1049
1050 #[must_use]
1052 pub fn with_quality(mut self, quality: f32) -> Self {
1053 self.quality = Some(quality);
1054 self
1055 }
1056
1057 #[must_use]
1059 pub fn with_effort(mut self, effort: JxlEffort) -> Self {
1060 self.effort = effort;
1061 self
1062 }
1063
1064 #[must_use]
1066 pub fn with_progressive(mut self, progressive: bool) -> Self {
1067 self.progressive = progressive;
1068 self
1069 }
1070
1071 #[must_use]
1073 pub fn with_photon_noise(mut self, iso: u32) -> Self {
1074 self.photon_noise_iso = Some(iso);
1075 self
1076 }
1077
1078 #[must_use]
1080 pub fn with_bit_depth(mut self, depth: u8) -> Self {
1081 self.bit_depth = depth;
1082 self
1083 }
1084
1085 #[must_use]
1087 pub fn with_modular(mut self, modular: bool) -> Self {
1088 self.modular = modular;
1089 self
1090 }
1091
1092 #[must_use]
1094 pub fn is_lossless(&self) -> bool {
1095 self.quality.is_none()
1096 }
1097
1098 #[must_use]
1100 pub fn build(self) -> CodecConfig {
1101 let mut cfg = CodecConfig::new("jxl");
1102
1103 if let Some(q) = self.quality {
1104 cfg.options.push(("quality".to_string(), format!("{q:.1}")));
1105 } else {
1106 cfg.options.push(("lossless".to_string(), "1".to_string()));
1107 }
1108
1109 cfg.options
1110 .push(("effort".to_string(), self.effort.as_u8().to_string()));
1111
1112 if self.progressive {
1113 cfg.options
1114 .push(("progressive".to_string(), "1".to_string()));
1115 }
1116
1117 if let Some(iso) = self.photon_noise_iso {
1118 cfg.options
1119 .push(("photon_noise_iso".to_string(), iso.to_string()));
1120 }
1121
1122 if self.modular {
1123 cfg.options.push(("modular".to_string(), "1".to_string()));
1124 }
1125
1126 cfg.options.push((
1127 "color_space".to_string(),
1128 self.color_space.as_str().to_string(),
1129 ));
1130
1131 cfg.options
1132 .push(("bit_depth".to_string(), self.bit_depth.to_string()));
1133
1134 cfg.rate_control = if self.is_lossless() {
1136 RateControlMode::Crf(0)
1137 } else {
1138 let q = self.quality.unwrap_or(75.0);
1140 let crf = ((100.0 - q) * 0.63) as u8;
1141 RateControlMode::Crf(crf)
1142 };
1143
1144 cfg
1145 }
1146}
1147
1148impl Default for JxlConfig {
1149 fn default() -> Self {
1150 Self::new()
1151 }
1152}
1153
1154#[must_use]
1156pub fn codec_config_from_quality(codec: &str, quality: QualityMode) -> CodecConfig {
1157 let preset = quality.to_preset();
1158 let crf = quality.to_crf();
1159
1160 match codec {
1161 "h264" => H264Config::new()
1162 .profile(H264Profile::High)
1163 .refs(3)
1164 .bframes(3)
1165 .build()
1166 .preset(preset)
1167 .rate_control(RateControlMode::Crf(crf)),
1168 "vp9" => Vp9Config::new()
1169 .cpu_used(preset.cpu_used())
1170 .row_mt(true)
1171 .build()
1172 .preset(preset)
1173 .rate_control(RateControlMode::Crf(crf)),
1174 "av1" => Av1Config::new()
1175 .cpu_used(preset.cpu_used())
1176 .row_mt(true)
1177 .usage(Av1Usage::Good)
1178 .build()
1179 .preset(preset)
1180 .rate_control(RateControlMode::Crf(crf)),
1181 "opus" => OpusConfig::new()
1182 .application(OpusApplication::Audio)
1183 .complexity(10)
1184 .vbr(true)
1185 .build()
1186 .preset(preset),
1187 _ => CodecConfig::new(codec)
1188 .preset(preset)
1189 .rate_control(RateControlMode::Crf(crf)),
1190 }
1191}
1192
1193#[cfg(test)]
1194mod tests {
1195 use super::*;
1196
1197 #[test]
1198 fn test_codec_config_new() {
1199 let config = CodecConfig::new("h264");
1200 assert_eq!(config.codec, "h264");
1201 assert_eq!(config.preset, QualityPreset::Medium);
1202 }
1203
1204 #[test]
1205 fn test_h264_config() {
1206 let config = H264Config::new()
1207 .profile(H264Profile::High)
1208 .level("4.0")
1209 .refs(3)
1210 .bframes(3)
1211 .cabac(true)
1212 .dct8x8(true)
1213 .build();
1214
1215 assert_eq!(config.codec, "h264");
1216 assert_eq!(config.profile, Some("high".to_string()));
1217 assert_eq!(config.level, Some("4.0".to_string()));
1218 assert!(config.options.len() > 0);
1219 }
1220
1221 #[test]
1222 fn test_vp9_config() {
1223 let config = Vp9Config::new()
1224 .cpu_used(4)
1225 .tile_columns(2)
1226 .tile_rows(1)
1227 .row_mt(true)
1228 .build();
1229
1230 assert_eq!(config.codec, "vp9");
1231 assert!(config
1232 .options
1233 .iter()
1234 .any(|(k, v)| k == "cpu-used" && v == "4"));
1235 assert!(config
1236 .options
1237 .iter()
1238 .any(|(k, v)| k == "row-mt" && v == "1"));
1239 }
1240
1241 #[test]
1242 fn test_av1_config() {
1243 let config = Av1Config::new()
1244 .cpu_used(6)
1245 .tiles(4, 2)
1246 .usage(Av1Usage::Good)
1247 .row_mt(true)
1248 .build();
1249
1250 assert_eq!(config.codec, "av1");
1251 assert!(config
1252 .options
1253 .iter()
1254 .any(|(k, v)| k == "cpu-used" && v == "6"));
1255 assert!(config
1256 .options
1257 .iter()
1258 .any(|(k, v)| k == "tiles" && v == "4x2"));
1259 }
1260
1261 #[test]
1262 fn test_opus_config() {
1263 let config = OpusConfig::new()
1264 .application(OpusApplication::Audio)
1265 .complexity(10)
1266 .vbr(true)
1267 .build();
1268
1269 assert_eq!(config.codec, "opus");
1270 assert!(config
1271 .options
1272 .iter()
1273 .any(|(k, v)| k == "application" && v == "audio"));
1274 assert!(config
1275 .options
1276 .iter()
1277 .any(|(k, v)| k == "complexity" && v == "10"));
1278 }
1279
1280 #[test]
1281 fn test_codec_config_from_quality() {
1282 let config = codec_config_from_quality("h264", QualityMode::High);
1283 assert_eq!(config.codec, "h264");
1284 assert_eq!(config.preset, QualityPreset::Slow);
1285 assert_eq!(config.rate_control, RateControlMode::Crf(20));
1286 }
1287
1288 #[test]
1289 fn test_h264_profiles() {
1290 assert_eq!(H264Profile::Baseline.as_str(), "baseline");
1291 assert_eq!(H264Profile::Main.as_str(), "main");
1292 assert_eq!(H264Profile::High.as_str(), "high");
1293 }
1294
1295 #[test]
1296 fn test_av1_usage() {
1297 assert_eq!(Av1Usage::Good.as_str(), "good");
1298 assert_eq!(Av1Usage::Realtime.as_str(), "realtime");
1299 }
1300
1301 #[test]
1302 fn test_opus_application() {
1303 assert_eq!(OpusApplication::Voip.as_str(), "voip");
1304 assert_eq!(OpusApplication::Audio.as_str(), "audio");
1305 assert_eq!(OpusApplication::LowDelay.as_str(), "lowdelay");
1306 }
1307
1308 #[test]
1311 fn test_vp9_crf_mode() {
1312 let config = Vp9Config::crf(33).build();
1313 assert_eq!(config.codec, "vp9");
1314 assert!(
1315 matches!(config.rate_control, RateControlMode::Crf(33)),
1316 "VP9 CRF mode should use Crf(33)"
1317 );
1318 }
1319
1320 #[test]
1321 fn test_vp9_crf_range_boundary() {
1322 let lo = Vp9Config::crf(0).build();
1324 let hi = Vp9Config::crf(63).build();
1325 assert!(matches!(lo.rate_control, RateControlMode::Crf(0)));
1326 assert!(matches!(hi.rate_control, RateControlMode::Crf(63)));
1327 }
1328
1329 #[test]
1330 fn test_vp9_with_tile_columns() {
1331 let config = Vp9Config::new().with_tile_columns(3).build();
1332 assert!(
1333 config
1334 .options
1335 .iter()
1336 .any(|(k, v)| k == "tile-columns" && v == "3"),
1337 "with_tile_columns should set tile-columns option"
1338 );
1339 }
1340
1341 #[test]
1342 fn test_vp9_with_frame_parallel() {
1343 let config_on = Vp9Config::new().with_frame_parallel(true).build();
1344 let config_off = Vp9Config::new().with_frame_parallel(false).build();
1345 assert!(config_on
1346 .options
1347 .iter()
1348 .any(|(k, v)| k == "frame-parallel" && v == "1"));
1349 assert!(config_off
1350 .options
1351 .iter()
1352 .any(|(k, v)| k == "frame-parallel" && v == "0"));
1353 }
1354
1355 #[test]
1356 fn test_vp9_with_lag_in_frames() {
1357 let config = Vp9Config::new().with_lag_in_frames(25).build();
1358 assert!(
1359 config
1360 .options
1361 .iter()
1362 .any(|(k, v)| k == "lag-in-frames" && v == "25"),
1363 "with_lag_in_frames should set lag-in-frames option"
1364 );
1365 }
1366
1367 #[test]
1368 fn test_vp9_with_row_mt() {
1369 let config_on = Vp9Config::new().with_row_mt(true).build();
1370 let config_off = Vp9Config::new().with_row_mt(false).build();
1371 assert!(config_on
1372 .options
1373 .iter()
1374 .any(|(k, v)| k == "row-mt" && v == "1"));
1375 assert!(config_off
1376 .options
1377 .iter()
1378 .any(|(k, v)| k == "row-mt" && v == "0"));
1379 }
1380
1381 #[test]
1382 fn test_vp9_screen_content() {
1383 let config = Vp9Config::screen_content().build();
1384 assert_eq!(config.codec, "vp9");
1385 assert!(matches!(config.rate_control, RateControlMode::Crf(_)));
1387 assert!(config.options.iter().any(|(k, _)| k == "cpu-used"));
1389 assert!(config
1391 .options
1392 .iter()
1393 .any(|(k, v)| k == "row-mt" && v == "1"));
1394 }
1395
1396 #[test]
1399 fn test_ffv1_config_new_defaults() {
1400 let cfg = Ffv1Config::new();
1401 assert!(matches!(cfg.level, Ffv1Level::Level3));
1402 assert!(matches!(cfg.coder, Ffv1Coder::Range));
1403 assert_eq!(cfg.slice_count, 4);
1404 assert_eq!(cfg.context_model, 0);
1405 assert!(cfg.checksum);
1406 }
1407
1408 #[test]
1409 fn test_ffv1_lossless_archive() {
1410 let cfg = Ffv1Config::lossless_archive();
1411 assert!(matches!(cfg.level, Ffv1Level::Level3));
1412 assert!(matches!(cfg.coder, Ffv1Coder::Range));
1413 assert_eq!(cfg.slice_count, 16);
1414 assert_eq!(cfg.context_model, 1);
1415 assert!(cfg.checksum);
1416 }
1417
1418 #[test]
1419 fn test_ffv1_lossless_fast() {
1420 let cfg = Ffv1Config::lossless_fast();
1421 assert!(matches!(cfg.level, Ffv1Level::Level1));
1422 assert!(matches!(cfg.coder, Ffv1Coder::GolombRice));
1423 assert!(!cfg.checksum);
1424 }
1425
1426 #[test]
1427 fn test_ffv1_with_slices() {
1428 let cfg = Ffv1Config::new().with_slices(9);
1429 assert_eq!(cfg.slice_count, 9);
1430 }
1431
1432 #[test]
1433 fn test_ffv1_build() {
1434 let config = Ffv1Config::new().build();
1435 assert_eq!(config.codec, "ffv1");
1436 assert!(config.options.iter().any(|(k, v)| k == "level" && v == "3"));
1437 assert!(config
1438 .options
1439 .iter()
1440 .any(|(k, v)| k == "slices" && v == "4"));
1441 assert!(config
1442 .options
1443 .iter()
1444 .any(|(k, v)| k == "slicecrc" && v == "1"));
1445 }
1446
1447 #[test]
1448 fn test_ffv1_level_values() {
1449 assert_eq!(Ffv1Level::Level1.as_u8(), 1);
1450 assert_eq!(Ffv1Level::Level3.as_u8(), 3);
1451 }
1452
1453 #[test]
1454 fn test_ffv1_coder_values() {
1455 assert_eq!(Ffv1Coder::GolombRice.as_u8(), 0);
1456 assert_eq!(Ffv1Coder::Range.as_u8(), 1);
1457 }
1458
1459 #[test]
1462 fn test_opus_voice_preset() {
1463 let config = OpusConfig::voice().build();
1464 assert_eq!(config.codec, "opus");
1465 assert!(config
1466 .options
1467 .iter()
1468 .any(|(k, v)| k == "application" && v == "voip"));
1469 assert!(config
1470 .options
1471 .iter()
1472 .any(|(k, v)| k == "inband_fec" && v == "1"));
1473 }
1474
1475 #[test]
1476 fn test_opus_music_preset() {
1477 let config = OpusConfig::music().build();
1478 assert_eq!(config.codec, "opus");
1479 assert!(config
1480 .options
1481 .iter()
1482 .any(|(k, v)| k == "application" && v == "audio"));
1483 assert!(config.options.iter().any(|(k, v)| k == "vbr" && v == "on"));
1484 }
1485
1486 #[test]
1487 fn test_opus_fullband_preset() {
1488 let config = OpusConfig::fullband().build();
1489 assert_eq!(config.codec, "opus");
1490 assert!(config.options.iter().any(|(k, _)| k == "cutoff"));
1491 }
1492
1493 #[test]
1494 fn test_opus_with_vbr() {
1495 let on = OpusConfig::new().with_vbr(true).build();
1496 let off = OpusConfig::new().with_vbr(false).build();
1497 assert!(on.options.iter().any(|(k, v)| k == "vbr" && v == "on"));
1498 assert!(off.options.iter().any(|(k, v)| k == "vbr" && v == "off"));
1499 }
1500
1501 #[test]
1502 fn test_opus_with_constrained_vbr() {
1503 let on = OpusConfig::new().with_constrained_vbr(true).build();
1504 let off = OpusConfig::new().with_constrained_vbr(false).build();
1505 assert!(on.options.iter().any(|(k, v)| k == "cvbr" && v == "1"));
1506 assert!(off.options.iter().any(|(k, v)| k == "cvbr" && v == "0"));
1507 }
1508
1509 #[test]
1510 fn test_opus_with_dtx() {
1511 let on = OpusConfig::new().with_dtx(true).build();
1512 let off = OpusConfig::new().with_dtx(false).build();
1513 assert!(on.options.iter().any(|(k, v)| k == "dtx" && v == "1"));
1514 assert!(off.options.iter().any(|(k, v)| k == "dtx" && v == "0"));
1515 }
1516
1517 #[test]
1518 fn test_opus_with_fec() {
1519 let config = OpusConfig::new().with_fec(true).build();
1520 assert!(config
1521 .options
1522 .iter()
1523 .any(|(k, v)| k == "inband_fec" && v == "1"));
1524 let config_off = OpusConfig::new().with_fec(false).build();
1525 assert!(config_off
1526 .options
1527 .iter()
1528 .any(|(k, v)| k == "inband_fec" && v == "0"));
1529 }
1530
1531 #[test]
1532 fn test_opus_with_packet_loss_perc() {
1533 let config = OpusConfig::new().with_packet_loss_perc(10).build();
1534 assert!(
1535 config
1536 .options
1537 .iter()
1538 .any(|(k, v)| k == "packet_loss_perc" && v == "10"),
1539 "packet_loss_perc option should be set"
1540 );
1541 }
1542
1543 #[test]
1546 fn test_flac_new_defaults() {
1547 let cfg = FlacConfig::new();
1548 assert_eq!(cfg.compression_level, 5);
1549 assert_eq!(cfg.block_size, 4096);
1550 assert!(!cfg.verify);
1551 }
1552
1553 #[test]
1554 fn test_flac_archival() {
1555 let cfg = FlacConfig::archival();
1556 assert_eq!(cfg.compression_level, 8);
1557 assert!(cfg.verify);
1558 }
1559
1560 #[test]
1561 fn test_flac_streaming() {
1562 let cfg = FlacConfig::streaming();
1563 assert_eq!(cfg.compression_level, 4);
1564 assert!(!cfg.verify);
1565 }
1566
1567 #[test]
1568 fn test_flac_fast() {
1569 let cfg = FlacConfig::fast();
1570 assert_eq!(cfg.compression_level, 0);
1571 }
1572
1573 #[test]
1574 fn test_flac_build() {
1575 let config = FlacConfig::new().build();
1576 assert_eq!(config.codec, "flac");
1577 assert!(
1578 config.options.iter().any(|(k, _)| k == "compression_level"),
1579 "FLAC config should include compression_level"
1580 );
1581 }
1582
1583 #[test]
1584 fn test_flac_archival_build_sets_verify() {
1585 let config = FlacConfig::archival().build();
1586 assert_eq!(config.codec, "flac");
1587 assert!(
1588 config
1589 .options
1590 .iter()
1591 .any(|(k, v)| k == "verify" && v == "1"),
1592 "Archival FLAC should enable verify"
1593 );
1594 }
1595
1596 #[test]
1599 fn test_jxl_new_defaults() {
1600 let cfg = JxlConfig::new();
1601 assert_eq!(cfg.quality, Some(75.0));
1602 assert_eq!(cfg.effort, JxlEffort::Squirrel);
1603 assert!(!cfg.progressive);
1604 assert!(cfg.photon_noise_iso.is_none());
1605 assert!(!cfg.modular);
1606 assert_eq!(cfg.color_space, JxlColorSpace::Xyb);
1607 assert_eq!(cfg.bit_depth, 8);
1608 assert!(!cfg.is_lossless());
1609 }
1610
1611 #[test]
1612 fn test_jxl_lossless() {
1613 let cfg = JxlConfig::lossless();
1614 assert!(cfg.is_lossless());
1615 assert!(cfg.quality.is_none());
1616 assert!(cfg.modular);
1617 assert_eq!(cfg.color_space, JxlColorSpace::Rgb);
1618 assert_eq!(cfg.effort, JxlEffort::Tortoise);
1619 }
1620
1621 #[test]
1622 fn test_jxl_web() {
1623 let cfg = JxlConfig::web();
1624 assert_eq!(cfg.quality, Some(80.0));
1625 assert!(cfg.progressive);
1626 assert!(!cfg.is_lossless());
1627 }
1628
1629 #[test]
1630 fn test_jxl_photography() {
1631 let cfg = JxlConfig::photography();
1632 assert_eq!(cfg.quality, Some(90.0));
1633 assert_eq!(cfg.photon_noise_iso, Some(400));
1634 assert_eq!(cfg.bit_depth, 16);
1635 assert!(cfg.progressive);
1636 }
1637
1638 #[test]
1639 fn test_jxl_with_quality() {
1640 let cfg = JxlConfig::new().with_quality(50.0);
1641 assert_eq!(cfg.quality, Some(50.0));
1642 }
1643
1644 #[test]
1645 fn test_jxl_with_effort() {
1646 let cfg = JxlConfig::new().with_effort(JxlEffort::Glacier);
1647 assert_eq!(cfg.effort, JxlEffort::Glacier);
1648 }
1649
1650 #[test]
1651 fn test_jxl_with_progressive() {
1652 let cfg = JxlConfig::new().with_progressive(true);
1653 assert!(cfg.progressive);
1654 }
1655
1656 #[test]
1657 fn test_jxl_with_photon_noise() {
1658 let cfg = JxlConfig::new().with_photon_noise(800);
1659 assert_eq!(cfg.photon_noise_iso, Some(800));
1660 }
1661
1662 #[test]
1663 fn test_jxl_with_bit_depth() {
1664 let cfg = JxlConfig::new().with_bit_depth(16);
1665 assert_eq!(cfg.bit_depth, 16);
1666 }
1667
1668 #[test]
1669 fn test_jxl_with_modular() {
1670 let cfg = JxlConfig::new().with_modular(true);
1671 assert!(cfg.modular);
1672 }
1673
1674 #[test]
1675 fn test_jxl_build_lossy() {
1676 let config = JxlConfig::new().build();
1677 assert_eq!(config.codec, "jxl");
1678 assert!(config.options.iter().any(|(k, _)| k == "quality"));
1679 assert!(config.options.iter().any(|(k, _)| k == "effort"));
1680 assert!(config.options.iter().any(|(k, _)| k == "color_space"));
1681 assert!(config.options.iter().any(|(k, _)| k == "bit_depth"));
1682 assert!(!config
1684 .options
1685 .iter()
1686 .any(|(k, v)| k == "lossless" && v == "1"));
1687 }
1688
1689 #[test]
1690 fn test_jxl_build_lossless() {
1691 let config = JxlConfig::lossless().build();
1692 assert_eq!(config.codec, "jxl");
1693 assert!(config
1694 .options
1695 .iter()
1696 .any(|(k, v)| k == "lossless" && v == "1"));
1697 assert!(config
1698 .options
1699 .iter()
1700 .any(|(k, v)| k == "modular" && v == "1"));
1701 assert_eq!(config.rate_control, RateControlMode::Crf(0));
1702 }
1703
1704 #[test]
1705 fn test_jxl_build_progressive() {
1706 let config = JxlConfig::web().build();
1707 assert!(config
1708 .options
1709 .iter()
1710 .any(|(k, v)| k == "progressive" && v == "1"));
1711 }
1712
1713 #[test]
1714 fn test_jxl_build_photon_noise() {
1715 let config = JxlConfig::photography().build();
1716 assert!(config
1717 .options
1718 .iter()
1719 .any(|(k, v)| k == "photon_noise_iso" && v == "400"));
1720 }
1721
1722 #[test]
1723 fn test_jxl_effort_values() {
1724 assert_eq!(JxlEffort::Lightning.as_u8(), 1);
1725 assert_eq!(JxlEffort::Thunder.as_u8(), 2);
1726 assert_eq!(JxlEffort::Falcon.as_u8(), 3);
1727 assert_eq!(JxlEffort::Cheetah.as_u8(), 4);
1728 assert_eq!(JxlEffort::Hare.as_u8(), 5);
1729 assert_eq!(JxlEffort::Wombat.as_u8(), 6);
1730 assert_eq!(JxlEffort::Squirrel.as_u8(), 7);
1731 assert_eq!(JxlEffort::Kitten.as_u8(), 8);
1732 assert_eq!(JxlEffort::Tortoise.as_u8(), 9);
1733 assert_eq!(JxlEffort::Glacier.as_u8(), 10);
1734 }
1735
1736 #[test]
1737 fn test_jxl_effort_names() {
1738 assert_eq!(JxlEffort::Lightning.as_str(), "lightning");
1739 assert_eq!(JxlEffort::Squirrel.as_str(), "squirrel");
1740 assert_eq!(JxlEffort::Glacier.as_str(), "glacier");
1741 }
1742
1743 #[test]
1744 fn test_jxl_color_space_names() {
1745 assert_eq!(JxlColorSpace::Rgb.as_str(), "rgb");
1746 assert_eq!(JxlColorSpace::Xyb.as_str(), "xyb");
1747 assert_eq!(JxlColorSpace::Gray.as_str(), "gray");
1748 }
1749
1750 #[test]
1751 fn test_jxl_default_is_new() {
1752 let cfg = JxlConfig::default();
1753 assert_eq!(cfg.quality, Some(75.0));
1754 assert_eq!(cfg.effort, JxlEffort::Squirrel);
1755 }
1756}