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]
220 pub fn cpu_used(mut self, cpu_used: u8) -> Self {
221 self.base
222 .options
223 .push(("cpu-used".to_string(), cpu_used.to_string()));
224 self
225 }
226
227 #[must_use]
229 pub fn tile_columns(mut self, columns: u8) -> Self {
230 self.base
231 .options
232 .push(("tile-columns".to_string(), columns.to_string()));
233 self
234 }
235
236 #[must_use]
238 pub fn tile_rows(mut self, rows: u8) -> Self {
239 self.base
240 .options
241 .push(("tile-rows".to_string(), rows.to_string()));
242 self
243 }
244
245 #[must_use]
247 pub fn frame_parallel(mut self, enable: bool) -> Self {
248 self.base.options.push((
249 "frame-parallel".to_string(),
250 if enable { "1" } else { "0" }.to_string(),
251 ));
252 self
253 }
254
255 #[must_use]
257 pub fn auto_alt_ref(mut self, frames: u8) -> Self {
258 self.base
259 .options
260 .push(("auto-alt-ref".to_string(), frames.to_string()));
261 self
262 }
263
264 #[must_use]
266 pub fn lag_in_frames(mut self, lag: u32) -> Self {
267 self.base
268 .options
269 .push(("lag-in-frames".to_string(), lag.to_string()));
270 self
271 }
272
273 #[must_use]
275 pub fn row_mt(mut self, enable: bool) -> Self {
276 self.base.options.push((
277 "row-mt".to_string(),
278 if enable { "1" } else { "0" }.to_string(),
279 ));
280 self
281 }
282
283 #[must_use]
285 pub fn build(self) -> CodecConfig {
286 self.base
287 }
288}
289
290impl Default for Vp9Config {
291 fn default() -> Self {
292 Self::new()
293 }
294}
295
296#[derive(Debug, Clone)]
298pub struct Av1Config {
299 base: CodecConfig,
300}
301
302impl Av1Config {
303 #[must_use]
305 pub fn new() -> Self {
306 Self {
307 base: CodecConfig::new("av1"),
308 }
309 }
310
311 #[must_use]
313 pub fn cpu_used(mut self, cpu_used: u8) -> Self {
314 self.base
315 .options
316 .push(("cpu-used".to_string(), cpu_used.to_string()));
317 self
318 }
319
320 #[must_use]
322 pub fn tiles(mut self, columns: u8, rows: u8) -> Self {
323 self.base
324 .options
325 .push(("tiles".to_string(), format!("{columns}x{rows}")));
326 self
327 }
328
329 #[must_use]
331 pub fn row_mt(mut self, enable: bool) -> Self {
332 self.base.options.push((
333 "row-mt".to_string(),
334 if enable { "1" } else { "0" }.to_string(),
335 ));
336 self
337 }
338
339 #[must_use]
341 pub fn usage(mut self, usage: Av1Usage) -> Self {
342 self.base
343 .options
344 .push(("usage".to_string(), usage.as_str().to_string()));
345 self
346 }
347
348 #[must_use]
350 pub fn enable_film_grain(mut self, enable: bool) -> Self {
351 self.base.options.push((
352 "enable-film-grain".to_string(),
353 if enable { "1" } else { "0" }.to_string(),
354 ));
355 self
356 }
357
358 #[must_use]
360 pub fn build(self) -> CodecConfig {
361 self.base
362 }
363}
364
365impl Default for Av1Config {
366 fn default() -> Self {
367 Self::new()
368 }
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
373pub enum Av1Usage {
374 Good,
376 Realtime,
378}
379
380impl Av1Usage {
381 #[must_use]
382 fn as_str(self) -> &'static str {
383 match self {
384 Self::Good => "good",
385 Self::Realtime => "realtime",
386 }
387 }
388}
389
390#[derive(Debug, Clone)]
392pub struct OpusConfig {
393 base: CodecConfig,
394}
395
396impl OpusConfig {
397 #[must_use]
399 pub fn new() -> Self {
400 Self {
401 base: CodecConfig::new("opus"),
402 }
403 }
404
405 #[must_use]
407 pub fn application(mut self, app: OpusApplication) -> Self {
408 self.base
409 .options
410 .push(("application".to_string(), app.as_str().to_string()));
411 self
412 }
413
414 #[must_use]
416 pub fn complexity(mut self, complexity: u8) -> Self {
417 self.base
418 .options
419 .push(("complexity".to_string(), complexity.to_string()));
420 self
421 }
422
423 #[must_use]
425 pub fn frame_duration(mut self, duration_ms: f32) -> Self {
426 self.base
427 .options
428 .push(("frame_duration".to_string(), duration_ms.to_string()));
429 self
430 }
431
432 #[must_use]
434 pub fn vbr(mut self, enable: bool) -> Self {
435 self.base.options.push((
436 "vbr".to_string(),
437 if enable { "on" } else { "off" }.to_string(),
438 ));
439 self
440 }
441
442 #[must_use]
444 pub fn build(self) -> CodecConfig {
445 self.base
446 }
447}
448
449impl Default for OpusConfig {
450 fn default() -> Self {
451 Self::new()
452 }
453}
454
455#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
457pub enum OpusApplication {
458 Voip,
460 Audio,
462 LowDelay,
464}
465
466impl OpusApplication {
467 #[must_use]
468 fn as_str(self) -> &'static str {
469 match self {
470 Self::Voip => "voip",
471 Self::Audio => "audio",
472 Self::LowDelay => "lowdelay",
473 }
474 }
475}
476
477#[must_use]
479pub fn codec_config_from_quality(codec: &str, quality: QualityMode) -> CodecConfig {
480 let preset = quality.to_preset();
481 let crf = quality.to_crf();
482
483 match codec {
484 "h264" => H264Config::new()
485 .profile(H264Profile::High)
486 .refs(3)
487 .bframes(3)
488 .build()
489 .preset(preset)
490 .rate_control(RateControlMode::Crf(crf)),
491 "vp9" => Vp9Config::new()
492 .cpu_used(preset.cpu_used())
493 .row_mt(true)
494 .build()
495 .preset(preset)
496 .rate_control(RateControlMode::Crf(crf)),
497 "av1" => Av1Config::new()
498 .cpu_used(preset.cpu_used())
499 .row_mt(true)
500 .usage(Av1Usage::Good)
501 .build()
502 .preset(preset)
503 .rate_control(RateControlMode::Crf(crf)),
504 "opus" => OpusConfig::new()
505 .application(OpusApplication::Audio)
506 .complexity(10)
507 .vbr(true)
508 .build()
509 .preset(preset),
510 _ => CodecConfig::new(codec)
511 .preset(preset)
512 .rate_control(RateControlMode::Crf(crf)),
513 }
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519
520 #[test]
521 fn test_codec_config_new() {
522 let config = CodecConfig::new("h264");
523 assert_eq!(config.codec, "h264");
524 assert_eq!(config.preset, QualityPreset::Medium);
525 }
526
527 #[test]
528 fn test_h264_config() {
529 let config = H264Config::new()
530 .profile(H264Profile::High)
531 .level("4.0")
532 .refs(3)
533 .bframes(3)
534 .cabac(true)
535 .dct8x8(true)
536 .build();
537
538 assert_eq!(config.codec, "h264");
539 assert_eq!(config.profile, Some("high".to_string()));
540 assert_eq!(config.level, Some("4.0".to_string()));
541 assert!(config.options.len() > 0);
542 }
543
544 #[test]
545 fn test_vp9_config() {
546 let config = Vp9Config::new()
547 .cpu_used(4)
548 .tile_columns(2)
549 .tile_rows(1)
550 .row_mt(true)
551 .build();
552
553 assert_eq!(config.codec, "vp9");
554 assert!(config
555 .options
556 .iter()
557 .any(|(k, v)| k == "cpu-used" && v == "4"));
558 assert!(config
559 .options
560 .iter()
561 .any(|(k, v)| k == "row-mt" && v == "1"));
562 }
563
564 #[test]
565 fn test_av1_config() {
566 let config = Av1Config::new()
567 .cpu_used(6)
568 .tiles(4, 2)
569 .usage(Av1Usage::Good)
570 .row_mt(true)
571 .build();
572
573 assert_eq!(config.codec, "av1");
574 assert!(config
575 .options
576 .iter()
577 .any(|(k, v)| k == "cpu-used" && v == "6"));
578 assert!(config
579 .options
580 .iter()
581 .any(|(k, v)| k == "tiles" && v == "4x2"));
582 }
583
584 #[test]
585 fn test_opus_config() {
586 let config = OpusConfig::new()
587 .application(OpusApplication::Audio)
588 .complexity(10)
589 .vbr(true)
590 .build();
591
592 assert_eq!(config.codec, "opus");
593 assert!(config
594 .options
595 .iter()
596 .any(|(k, v)| k == "application" && v == "audio"));
597 assert!(config
598 .options
599 .iter()
600 .any(|(k, v)| k == "complexity" && v == "10"));
601 }
602
603 #[test]
604 fn test_codec_config_from_quality() {
605 let config = codec_config_from_quality("h264", QualityMode::High);
606 assert_eq!(config.codec, "h264");
607 assert_eq!(config.preset, QualityPreset::Slow);
608 assert_eq!(config.rate_control, RateControlMode::Crf(20));
609 }
610
611 #[test]
612 fn test_h264_profiles() {
613 assert_eq!(H264Profile::Baseline.as_str(), "baseline");
614 assert_eq!(H264Profile::Main.as_str(), "main");
615 assert_eq!(H264Profile::High.as_str(), "high");
616 }
617
618 #[test]
619 fn test_av1_usage() {
620 assert_eq!(Av1Usage::Good.as_str(), "good");
621 assert_eq!(Av1Usage::Realtime.as_str(), "realtime");
622 }
623
624 #[test]
625 fn test_opus_application() {
626 assert_eq!(OpusApplication::Voip.as_str(), "voip");
627 assert_eq!(OpusApplication::Audio.as_str(), "audio");
628 assert_eq!(OpusApplication::LowDelay.as_str(), "lowdelay");
629 }
630}