1#[allow(clippy::wildcard_imports)]
4use super::*;
5
6impl FilterGraphBuilder {
7 #[must_use]
15 pub fn afade_in(mut self, start_sec: f64, duration_sec: f64) -> Self {
16 self.steps.push(FilterStep::AFadeIn {
17 start: start_sec,
18 duration: duration_sec,
19 });
20 self
21 }
22
23 #[must_use]
29 pub fn afade_out(mut self, start_sec: f64, duration_sec: f64) -> Self {
30 self.steps.push(FilterStep::AFadeOut {
31 start: start_sec,
32 duration: duration_sec,
33 });
34 self
35 }
36
37 #[must_use]
42 pub fn areverse(mut self) -> Self {
43 self.steps.push(FilterStep::AReverse);
44 self
45 }
46
47 #[must_use]
61 pub fn loudness_normalize(mut self, target_lufs: f32, true_peak_db: f32, lra: f32) -> Self {
62 self.steps.push(FilterStep::LoudnessNormalize {
63 target_lufs,
64 true_peak_db,
65 lra,
66 });
67 self
68 }
69
70 #[must_use]
80 pub fn normalize_peak(mut self, target_db: f32) -> Self {
81 self.steps.push(FilterStep::NormalizePeak { target_db });
82 self
83 }
84
85 #[must_use]
94 pub fn agate(mut self, threshold_db: f32, attack_ms: f32, release_ms: f32) -> Self {
95 self.steps.push(FilterStep::ANoiseGate {
96 threshold_db,
97 attack_ms,
98 release_ms,
99 });
100 self
101 }
102
103 #[must_use]
112 pub fn compressor(
113 mut self,
114 threshold_db: f32,
115 ratio: f32,
116 attack_ms: f32,
117 release_ms: f32,
118 makeup_db: f32,
119 ) -> Self {
120 self.steps.push(FilterStep::ACompressor {
121 threshold_db,
122 ratio,
123 attack_ms,
124 release_ms,
125 makeup_db,
126 });
127 self
128 }
129
130 #[must_use]
135 pub fn stereo_to_mono(mut self) -> Self {
136 self.steps.push(FilterStep::StereoToMono);
137 self
138 }
139
140 #[must_use]
148 pub fn channel_map(mut self, mapping: &str) -> Self {
149 self.steps.push(FilterStep::ChannelMap {
150 mapping: mapping.to_string(),
151 });
152 self
153 }
154
155 #[must_use]
162 pub fn audio_delay(mut self, ms: f64) -> Self {
163 self.steps.push(FilterStep::AudioDelay { ms });
164 self
165 }
166
167 #[must_use]
173 pub fn concat_audio(mut self, n_segments: u32) -> Self {
174 self.steps.push(FilterStep::ConcatAudio { n: n_segments });
175 self
176 }
177
178 #[must_use]
180 pub fn volume(mut self, gain_db: f64) -> Self {
181 self.steps.push(FilterStep::Volume(gain_db));
182 self
183 }
184
185 #[must_use]
188 pub fn amix(mut self, inputs: usize) -> Self {
189 self.steps.push(FilterStep::Amix(inputs));
190 self
191 }
192
193 #[must_use]
203 pub fn equalizer(mut self, bands: Vec<EqBand>) -> Self {
204 self.steps.push(FilterStep::ParametricEq { bands });
205 self
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn filter_step_volume_should_produce_correct_args() {
215 let step = FilterStep::Volume(-6.0);
216 assert_eq!(step.filter_name(), "volume");
217 assert_eq!(step.args(), "volume=-6dB");
218 }
219
220 #[test]
221 fn volume_should_convert_db_to_ffmpeg_string() {
222 assert_eq!(FilterStep::Volume(-6.0).args(), "volume=-6dB");
223 assert_eq!(FilterStep::Volume(6.0).args(), "volume=6dB");
224 assert_eq!(FilterStep::Volume(0.0).args(), "volume=0dB");
225 }
226
227 #[test]
228 fn filter_step_amix_should_produce_correct_args() {
229 let step = FilterStep::Amix(3);
230 assert_eq!(step.filter_name(), "amix");
231 assert_eq!(step.args(), "inputs=3:normalize=0");
232 }
233
234 #[test]
235 fn filter_step_parametric_eq_should_have_filter_name_equalizer() {
236 let step = FilterStep::ParametricEq {
237 bands: vec![EqBand::Peak {
238 freq_hz: 1000.0,
239 gain_db: 3.0,
240 q: 1.0,
241 }],
242 };
243 assert_eq!(step.filter_name(), "equalizer");
244 }
245
246 #[test]
247 fn eq_band_peak_should_produce_correct_args() {
248 let band = EqBand::Peak {
249 freq_hz: 1000.0,
250 gain_db: 3.0,
251 q: 1.0,
252 };
253 assert_eq!(band.args(), "f=1000:g=3:width_type=q:width=1");
254 }
255
256 #[test]
257 fn eq_band_low_shelf_should_produce_correct_args() {
258 let band = EqBand::LowShelf {
259 freq_hz: 200.0,
260 gain_db: -3.0,
261 slope: 1.0,
262 };
263 assert_eq!(band.args(), "f=200:g=-3:s=1");
264 }
265
266 #[test]
267 fn eq_band_high_shelf_should_produce_correct_args() {
268 let band = EqBand::HighShelf {
269 freq_hz: 8000.0,
270 gain_db: 2.0,
271 slope: 0.5,
272 };
273 assert_eq!(band.args(), "f=8000:g=2:s=0.5");
274 }
275
276 #[test]
277 fn builder_equalizer_with_single_peak_band_should_succeed() {
278 let result = FilterGraph::builder()
279 .equalizer(vec![EqBand::Peak {
280 freq_hz: 1000.0,
281 gain_db: 3.0,
282 q: 1.0,
283 }])
284 .build();
285 assert!(
286 result.is_ok(),
287 "equalizer with single Peak band must build successfully, got {result:?}"
288 );
289 }
290
291 #[test]
292 fn builder_equalizer_with_multiple_bands_should_succeed() {
293 let result = FilterGraph::builder()
294 .equalizer(vec![
295 EqBand::LowShelf {
296 freq_hz: 200.0,
297 gain_db: -2.0,
298 slope: 1.0,
299 },
300 EqBand::Peak {
301 freq_hz: 1000.0,
302 gain_db: 3.0,
303 q: 1.4,
304 },
305 EqBand::HighShelf {
306 freq_hz: 8000.0,
307 gain_db: 1.0,
308 slope: 0.5,
309 },
310 ])
311 .build();
312 assert!(
313 result.is_ok(),
314 "equalizer with three bands must build successfully, got {result:?}"
315 );
316 }
317
318 #[test]
319 fn builder_equalizer_with_empty_bands_should_return_invalid_config() {
320 let result = FilterGraph::builder().equalizer(vec![]).build();
321 assert!(
322 matches!(result, Err(FilterError::InvalidConfig { .. })),
323 "expected InvalidConfig for empty bands, got {result:?}"
324 );
325 }
326
327 #[test]
328 fn filter_step_afade_in_should_have_correct_filter_name() {
329 let step = FilterStep::AFadeIn {
330 start: 0.0,
331 duration: 1.0,
332 };
333 assert_eq!(step.filter_name(), "afade");
334 }
335
336 #[test]
337 fn filter_step_afade_out_should_have_correct_filter_name() {
338 let step = FilterStep::AFadeOut {
339 start: 4.0,
340 duration: 1.0,
341 };
342 assert_eq!(step.filter_name(), "afade");
343 }
344
345 #[test]
346 fn filter_step_afade_in_should_produce_correct_args() {
347 let step = FilterStep::AFadeIn {
348 start: 0.0,
349 duration: 1.0,
350 };
351 assert_eq!(step.args(), "type=in:start_time=0:duration=1");
352 }
353
354 #[test]
355 fn filter_step_afade_out_should_produce_correct_args() {
356 let step = FilterStep::AFadeOut {
357 start: 4.0,
358 duration: 1.0,
359 };
360 assert_eq!(step.args(), "type=out:start_time=4:duration=1");
361 }
362
363 #[test]
364 fn builder_afade_in_with_valid_params_should_succeed() {
365 let result = FilterGraph::builder().afade_in(0.0, 1.0).build();
366 assert!(
367 result.is_ok(),
368 "afade_in(0.0, 1.0) must build successfully, got {result:?}"
369 );
370 }
371
372 #[test]
373 fn builder_afade_out_with_valid_params_should_succeed() {
374 let result = FilterGraph::builder().afade_out(4.0, 1.0).build();
375 assert!(
376 result.is_ok(),
377 "afade_out(4.0, 1.0) must build successfully, got {result:?}"
378 );
379 }
380
381 #[test]
382 fn builder_afade_in_with_zero_duration_should_return_invalid_config() {
383 let result = FilterGraph::builder().afade_in(0.0, 0.0).build();
384 assert!(
385 matches!(result, Err(FilterError::InvalidConfig { .. })),
386 "expected InvalidConfig for duration=0.0, got {result:?}"
387 );
388 }
389
390 #[test]
391 fn builder_afade_out_with_negative_duration_should_return_invalid_config() {
392 let result = FilterGraph::builder().afade_out(4.0, -1.0).build();
393 assert!(
394 matches!(result, Err(FilterError::InvalidConfig { .. })),
395 "expected InvalidConfig for duration=-1.0, got {result:?}"
396 );
397 }
398
399 #[test]
400 fn filter_step_areverse_should_produce_correct_filter_name_and_empty_args() {
401 let step = FilterStep::AReverse;
402 assert_eq!(step.filter_name(), "areverse");
403 assert_eq!(step.args(), "");
404 }
405
406 #[test]
407 fn builder_areverse_should_succeed() {
408 let result = FilterGraph::builder().areverse().build();
409 assert!(
410 result.is_ok(),
411 "areverse must build successfully, got {result:?}"
412 );
413 }
414
415 #[test]
416 fn filter_step_loudness_normalize_should_produce_correct_filter_name() {
417 let step = FilterStep::LoudnessNormalize {
418 target_lufs: -23.0,
419 true_peak_db: -1.0,
420 lra: 7.0,
421 };
422 assert_eq!(step.filter_name(), "ebur128");
423 }
424
425 #[test]
426 fn filter_step_loudness_normalize_should_produce_correct_args() {
427 let step = FilterStep::LoudnessNormalize {
428 target_lufs: -23.0,
429 true_peak_db: -1.0,
430 lra: 7.0,
431 };
432 assert_eq!(step.args(), "peak=true:metadata=1");
433 }
434
435 #[test]
436 fn builder_loudness_normalize_with_valid_params_should_succeed() {
437 let result = FilterGraph::builder()
438 .loudness_normalize(-23.0, -1.0, 7.0)
439 .build();
440 assert!(
441 result.is_ok(),
442 "loudness_normalize(-23.0, -1.0, 7.0) must build successfully, got {result:?}"
443 );
444 }
445
446 #[test]
447 fn builder_loudness_normalize_with_zero_target_lufs_should_return_invalid_config() {
448 let result = FilterGraph::builder()
449 .loudness_normalize(0.0, -1.0, 7.0)
450 .build();
451 assert!(
452 matches!(result, Err(FilterError::InvalidConfig { .. })),
453 "expected InvalidConfig for target_lufs=0.0, got {result:?}"
454 );
455 }
456
457 #[test]
458 fn builder_loudness_normalize_with_positive_target_lufs_should_return_invalid_config() {
459 let result = FilterGraph::builder()
460 .loudness_normalize(5.0, -1.0, 7.0)
461 .build();
462 assert!(
463 matches!(result, Err(FilterError::InvalidConfig { .. })),
464 "expected InvalidConfig for target_lufs=5.0, got {result:?}"
465 );
466 }
467
468 #[test]
469 fn builder_loudness_normalize_with_positive_true_peak_should_return_invalid_config() {
470 let result = FilterGraph::builder()
471 .loudness_normalize(-23.0, 1.0, 7.0)
472 .build();
473 assert!(
474 matches!(result, Err(FilterError::InvalidConfig { .. })),
475 "expected InvalidConfig for true_peak_db=1.0, got {result:?}"
476 );
477 }
478
479 #[test]
480 fn builder_loudness_normalize_with_zero_lra_should_return_invalid_config() {
481 let result = FilterGraph::builder()
482 .loudness_normalize(-23.0, -1.0, 0.0)
483 .build();
484 assert!(
485 matches!(result, Err(FilterError::InvalidConfig { .. })),
486 "expected InvalidConfig for lra=0.0, got {result:?}"
487 );
488 }
489
490 #[test]
491 fn builder_loudness_normalize_with_negative_lra_should_return_invalid_config() {
492 let result = FilterGraph::builder()
493 .loudness_normalize(-23.0, -1.0, -7.0)
494 .build();
495 assert!(
496 matches!(result, Err(FilterError::InvalidConfig { .. })),
497 "expected InvalidConfig for lra=-7.0, got {result:?}"
498 );
499 }
500
501 #[test]
502 fn filter_step_normalize_peak_should_have_correct_filter_name() {
503 let step = FilterStep::NormalizePeak { target_db: -1.0 };
504 assert_eq!(step.filter_name(), "astats");
505 }
506
507 #[test]
508 fn filter_step_normalize_peak_should_have_correct_args() {
509 let step = FilterStep::NormalizePeak { target_db: -1.0 };
510 assert_eq!(step.args(), "metadata=1");
511 }
512
513 #[test]
514 fn builder_normalize_peak_valid_should_build_successfully() {
515 let result = FilterGraph::builder().normalize_peak(-1.0).build();
516 assert!(
517 result.is_ok(),
518 "normalize_peak(-1.0) must build successfully, got {result:?}"
519 );
520 }
521
522 #[test]
523 fn builder_normalize_peak_with_zero_target_db_should_build_successfully() {
524 let result = FilterGraph::builder().normalize_peak(0.0).build();
526 assert!(
527 result.is_ok(),
528 "normalize_peak(0.0) must build successfully, got {result:?}"
529 );
530 }
531
532 #[test]
533 fn builder_normalize_peak_with_positive_target_db_should_return_invalid_config() {
534 let result = FilterGraph::builder().normalize_peak(1.0).build();
535 assert!(
536 matches!(result, Err(FilterError::InvalidConfig { .. })),
537 "expected InvalidConfig for target_db=1.0, got {result:?}"
538 );
539 }
540
541 #[test]
542 fn filter_step_agate_should_have_correct_filter_name() {
543 let step = FilterStep::ANoiseGate {
544 threshold_db: -40.0,
545 attack_ms: 10.0,
546 release_ms: 100.0,
547 };
548 assert_eq!(step.filter_name(), "agate");
549 }
550
551 #[test]
552 fn filter_step_agate_should_produce_correct_args_for_minus_40_db() {
553 let step = FilterStep::ANoiseGate {
554 threshold_db: -40.0,
555 attack_ms: 10.0,
556 release_ms: 100.0,
557 };
558 let args = step.args();
560 assert!(
561 args.starts_with("threshold=0.010000:"),
562 "expected args to start with threshold=0.010000:, got {args}"
563 );
564 assert!(
565 args.contains("attack=10:"),
566 "expected attack=10: in args, got {args}"
567 );
568 assert!(
569 args.contains("release=100"),
570 "expected release=100 in args, got {args}"
571 );
572 }
573
574 #[test]
575 fn filter_step_agate_should_produce_correct_args_for_zero_db() {
576 let step = FilterStep::ANoiseGate {
577 threshold_db: 0.0,
578 attack_ms: 5.0,
579 release_ms: 50.0,
580 };
581 let args = step.args();
583 assert!(
584 args.starts_with("threshold=1.000000:"),
585 "expected threshold=1.000000: in args, got {args}"
586 );
587 }
588
589 #[test]
590 fn builder_agate_valid_should_build_successfully() {
591 let result = FilterGraph::builder().agate(-40.0, 10.0, 100.0).build();
592 assert!(
593 result.is_ok(),
594 "agate(-40.0, 10.0, 100.0) must build successfully, got {result:?}"
595 );
596 }
597
598 #[test]
599 fn builder_agate_with_zero_attack_should_return_invalid_config() {
600 let result = FilterGraph::builder().agate(-40.0, 0.0, 100.0).build();
601 assert!(
602 matches!(result, Err(FilterError::InvalidConfig { .. })),
603 "expected InvalidConfig for attack_ms=0.0, got {result:?}"
604 );
605 }
606
607 #[test]
608 fn builder_agate_with_negative_attack_should_return_invalid_config() {
609 let result = FilterGraph::builder().agate(-40.0, -1.0, 100.0).build();
610 assert!(
611 matches!(result, Err(FilterError::InvalidConfig { .. })),
612 "expected InvalidConfig for attack_ms=-1.0, got {result:?}"
613 );
614 }
615
616 #[test]
617 fn builder_agate_with_zero_release_should_return_invalid_config() {
618 let result = FilterGraph::builder().agate(-40.0, 10.0, 0.0).build();
619 assert!(
620 matches!(result, Err(FilterError::InvalidConfig { .. })),
621 "expected InvalidConfig for release_ms=0.0, got {result:?}"
622 );
623 }
624
625 #[test]
626 fn builder_agate_with_negative_release_should_return_invalid_config() {
627 let result = FilterGraph::builder().agate(-40.0, 10.0, -50.0).build();
628 assert!(
629 matches!(result, Err(FilterError::InvalidConfig { .. })),
630 "expected InvalidConfig for release_ms=-50.0, got {result:?}"
631 );
632 }
633
634 #[test]
635 fn filter_step_compressor_should_have_correct_filter_name() {
636 let step = FilterStep::ACompressor {
637 threshold_db: -20.0,
638 ratio: 4.0,
639 attack_ms: 10.0,
640 release_ms: 100.0,
641 makeup_db: 6.0,
642 };
643 assert_eq!(step.filter_name(), "acompressor");
644 }
645
646 #[test]
647 fn filter_step_compressor_should_produce_correct_args() {
648 let step = FilterStep::ACompressor {
649 threshold_db: -20.0,
650 ratio: 4.0,
651 attack_ms: 10.0,
652 release_ms: 100.0,
653 makeup_db: 6.0,
654 };
655 assert_eq!(
656 step.args(),
657 "threshold=-20dB:ratio=4:attack=10:release=100:makeup=6dB"
658 );
659 }
660
661 #[test]
662 fn builder_compressor_valid_should_build_successfully() {
663 let result = FilterGraph::builder()
664 .compressor(-20.0, 4.0, 10.0, 100.0, 6.0)
665 .build();
666 assert!(
667 result.is_ok(),
668 "compressor(-20.0, 4.0, 10.0, 100.0, 6.0) must build successfully, got {result:?}"
669 );
670 }
671
672 #[test]
673 fn builder_compressor_with_unity_ratio_should_build_successfully() {
674 let result = FilterGraph::builder()
676 .compressor(-20.0, 1.0, 10.0, 100.0, 0.0)
677 .build();
678 assert!(
679 result.is_ok(),
680 "compressor with ratio=1.0 must build successfully, got {result:?}"
681 );
682 }
683
684 #[test]
685 fn builder_compressor_with_ratio_below_one_should_return_invalid_config() {
686 let result = FilterGraph::builder()
687 .compressor(-20.0, 0.5, 10.0, 100.0, 0.0)
688 .build();
689 assert!(
690 matches!(result, Err(FilterError::InvalidConfig { .. })),
691 "expected InvalidConfig for ratio=0.5, got {result:?}"
692 );
693 }
694
695 #[test]
696 fn builder_compressor_with_zero_attack_should_return_invalid_config() {
697 let result = FilterGraph::builder()
698 .compressor(-20.0, 4.0, 0.0, 100.0, 0.0)
699 .build();
700 assert!(
701 matches!(result, Err(FilterError::InvalidConfig { .. })),
702 "expected InvalidConfig for attack_ms=0.0, got {result:?}"
703 );
704 }
705
706 #[test]
707 fn builder_compressor_with_zero_release_should_return_invalid_config() {
708 let result = FilterGraph::builder()
709 .compressor(-20.0, 4.0, 10.0, 0.0, 0.0)
710 .build();
711 assert!(
712 matches!(result, Err(FilterError::InvalidConfig { .. })),
713 "expected InvalidConfig for release_ms=0.0, got {result:?}"
714 );
715 }
716
717 #[test]
718 fn filter_step_stereo_to_mono_should_have_correct_filter_name() {
719 assert_eq!(FilterStep::StereoToMono.filter_name(), "pan");
720 }
721
722 #[test]
723 fn filter_step_stereo_to_mono_should_produce_correct_args() {
724 assert_eq!(FilterStep::StereoToMono.args(), "mono|c0=0.5*c0+0.5*c1");
725 }
726
727 #[test]
728 fn builder_stereo_to_mono_should_build_successfully() {
729 let result = FilterGraph::builder().stereo_to_mono().build();
730 assert!(
731 result.is_ok(),
732 "stereo_to_mono() must build successfully, got {result:?}"
733 );
734 }
735
736 #[test]
737 fn filter_step_channel_map_should_have_correct_filter_name() {
738 let step = FilterStep::ChannelMap {
739 mapping: "FR|FL".to_string(),
740 };
741 assert_eq!(step.filter_name(), "channelmap");
742 }
743
744 #[test]
745 fn filter_step_channel_map_should_produce_correct_args() {
746 let step = FilterStep::ChannelMap {
747 mapping: "FR|FL".to_string(),
748 };
749 assert_eq!(step.args(), "map=FR|FL");
750 }
751
752 #[test]
753 fn builder_channel_map_valid_should_build_successfully() {
754 let result = FilterGraph::builder().channel_map("FR|FL").build();
755 assert!(
756 result.is_ok(),
757 "channel_map(\"FR|FL\") must build successfully, got {result:?}"
758 );
759 }
760
761 #[test]
762 fn builder_channel_map_with_empty_mapping_should_return_invalid_config() {
763 let result = FilterGraph::builder().channel_map("").build();
764 assert!(
765 matches!(result, Err(FilterError::InvalidConfig { .. })),
766 "expected InvalidConfig for empty mapping, got {result:?}"
767 );
768 }
769
770 #[test]
771 fn filter_step_audio_delay_positive_should_have_correct_filter_name() {
772 let step = FilterStep::AudioDelay { ms: 100.0 };
773 assert_eq!(step.filter_name(), "adelay");
774 }
775
776 #[test]
777 fn filter_step_audio_delay_negative_should_have_correct_filter_name() {
778 let step = FilterStep::AudioDelay { ms: -100.0 };
781 assert_eq!(step.filter_name(), "adelay");
782 }
783
784 #[test]
785 fn filter_step_audio_delay_positive_should_produce_adelay_args() {
786 let step = FilterStep::AudioDelay { ms: 100.0 };
787 assert_eq!(step.args(), "delays=100:all=1");
788 }
789
790 #[test]
791 fn filter_step_audio_delay_zero_should_produce_adelay_args() {
792 let step = FilterStep::AudioDelay { ms: 0.0 };
793 assert_eq!(step.args(), "delays=0:all=1");
794 }
795
796 #[test]
797 fn filter_step_audio_delay_negative_should_produce_atrim_args() {
798 let step = FilterStep::AudioDelay { ms: -100.0 };
799 assert_eq!(step.args(), "start=0.1");
801 }
802
803 #[test]
804 fn builder_audio_delay_positive_should_build_successfully() {
805 let result = FilterGraph::builder().audio_delay(100.0).build();
806 assert!(
807 result.is_ok(),
808 "audio_delay(100.0) must build successfully, got {result:?}"
809 );
810 }
811
812 #[test]
813 fn builder_audio_delay_zero_should_build_successfully() {
814 let result = FilterGraph::builder().audio_delay(0.0).build();
815 assert!(
816 result.is_ok(),
817 "audio_delay(0.0) must build successfully, got {result:?}"
818 );
819 }
820
821 #[test]
822 fn builder_audio_delay_negative_should_build_successfully() {
823 let result = FilterGraph::builder().audio_delay(-100.0).build();
824 assert!(
825 result.is_ok(),
826 "audio_delay(-100.0) must build successfully, got {result:?}"
827 );
828 }
829
830 #[test]
831 fn filter_step_concat_audio_should_have_correct_filter_name() {
832 let step = FilterStep::ConcatAudio { n: 2 };
833 assert_eq!(step.filter_name(), "concat");
834 }
835
836 #[test]
837 fn filter_step_concat_audio_should_produce_correct_args_for_n2() {
838 let step = FilterStep::ConcatAudio { n: 2 };
839 assert_eq!(step.args(), "n=2:v=0:a=1");
840 }
841
842 #[test]
843 fn filter_step_concat_audio_should_produce_correct_args_for_n3() {
844 let step = FilterStep::ConcatAudio { n: 3 };
845 assert_eq!(step.args(), "n=3:v=0:a=1");
846 }
847
848 #[test]
849 fn builder_concat_audio_valid_should_build_successfully() {
850 let result = FilterGraph::builder().concat_audio(2).build();
851 assert!(
852 result.is_ok(),
853 "concat_audio(2) must build successfully, got {result:?}"
854 );
855 }
856
857 #[test]
858 fn builder_concat_audio_with_n1_should_return_invalid_config() {
859 let result = FilterGraph::builder().concat_audio(1).build();
860 assert!(
861 matches!(result, Err(FilterError::InvalidConfig { .. })),
862 "expected InvalidConfig for n=1, got {result:?}"
863 );
864 }
865
866 #[test]
867 fn builder_concat_audio_with_n0_should_return_invalid_config() {
868 let result = FilterGraph::builder().concat_audio(0).build();
869 assert!(
870 matches!(result, Err(FilterError::InvalidConfig { .. })),
871 "expected InvalidConfig for n=0, got {result:?}"
872 );
873 }
874}