use super::*;
use ::std::string::ToString;
const MAP: &[(ChannelLayout, &str, &str)] = &[
(ChannelLayout::Mono, "MONO", "mono"),
(ChannelLayout::Stereo, "STEREO", "stereo"),
(ChannelLayout::StereoDownmix, "STEREO_DOWNMIX", "downmix"),
(ChannelLayout::Binaural, "BINAURAL", "binaural"),
(ChannelLayout::Ch2_1, "2POINT1", "2.1"),
(ChannelLayout::Ch3_0, "SURROUND", "3.0"),
(ChannelLayout::Ch3_0Back, "2_1", "3.0(back)"),
(ChannelLayout::Ch3_1, "3POINT1", "3.1"),
(ChannelLayout::Ch3_1_2, "3POINT1POINT2", "3.1.2"),
(ChannelLayout::Ch4_0, "4POINT0", "4.0"),
(ChannelLayout::Ch4_1, "4POINT1", "4.1"),
(ChannelLayout::Quad, "QUAD", "quad"),
(ChannelLayout::QuadSide, "2_2", "quad(side)"),
(ChannelLayout::Ch5_0, "5POINT0", "5.0(side)"),
(ChannelLayout::Ch5_0Back, "5POINT0_BACK", "5.0"),
(ChannelLayout::Ch5_1, "5POINT1", "5.1(side)"),
(ChannelLayout::Ch5_1Back, "5POINT1_BACK", "5.1"),
(ChannelLayout::Ch5_1_2, "5POINT1POINT2", "5.1.2"),
(
ChannelLayout::Ch5_1_2Back,
"5POINT1POINT2_BACK",
"5.1.2(back)",
),
(ChannelLayout::Ch5_1_4Back, "5POINT1POINT4_BACK", "5.1.4"),
(ChannelLayout::Ch6_0, "6POINT0", "6.0"),
(ChannelLayout::Ch6_0Front, "6POINT0_FRONT", "6.0(front)"),
(ChannelLayout::Ch6_1, "6POINT1", "6.1"),
(ChannelLayout::Ch6_1Back, "6POINT1_BACK", "6.1(back)"),
(ChannelLayout::Ch6_1Front, "6POINT1_FRONT", "6.1(front)"),
(ChannelLayout::Ch7_0, "7POINT0", "7.0"),
(ChannelLayout::Ch7_0Front, "7POINT0_FRONT", "7.0(front)"),
(ChannelLayout::Ch7_1, "7POINT1", "7.1"),
(ChannelLayout::Ch7_1Wide, "7POINT1_WIDE", "7.1(wide-side)"),
(
ChannelLayout::Ch7_1WideBack,
"7POINT1_WIDE_BACK",
"7.1(wide)",
),
(ChannelLayout::Ch7_1_2, "7POINT1POINT2", "7.1.2"),
(ChannelLayout::Ch7_1_4Back, "7POINT1POINT4_BACK", "7.1.4"),
(ChannelLayout::Ch7_2_3, "7POINT2POINT3", "7.2.3"),
(ChannelLayout::Ch9_1_4Back, "9POINT1POINT4_BACK", "9.1.4"),
(ChannelLayout::Ch9_1_6, "9POINT1POINT6", "9.1.6"),
(ChannelLayout::Ch22_2, "22POINT2", "22.2"),
(ChannelLayout::Hexagonal, "HEXAGONAL", "hexagonal"),
(ChannelLayout::Octagonal, "OCTAGONAL", "octagonal"),
(
ChannelLayout::Hexadecagonal,
"HEXADECAGONAL",
"hexadecagonal",
),
(ChannelLayout::Cube, "CUBE", "cube"),
];
const AMBISONIC: &[(ChannelLayout, &str)] = &[
(ChannelLayout::Ambisonic1, "ambisonic1"),
(ChannelLayout::Ambisonic2, "ambisonic2"),
(ChannelLayout::Ambisonic3, "ambisonic3"),
];
#[test]
fn channel_layout_slugs_match_ffmpegs_map() {
for (layout, constant, ffmpeg_name) in MAP {
assert_eq!(
layout.as_str(),
*ffmpeg_name,
"AV_CH_LAYOUT_{constant} is named {ffmpeg_name:?} by FFmpeg, not {:?}",
layout.as_str()
);
assert_eq!(
ffmpeg_name.parse::<ChannelLayout>().unwrap(),
*layout,
"FFmpeg's {ffmpeg_name:?} must read back as the AV_CH_LAYOUT_{constant} variant"
);
}
}
#[test]
fn ambisonic_slugs_round_trip() {
for (layout, slug) in AMBISONIC {
assert_eq!(layout.as_str(), *slug);
assert_eq!(slug.parse::<ChannelLayout>().unwrap(), *layout);
}
}
#[test]
fn every_rostered_variant_has_a_transcribed_source() {
assert_eq!(
MAP.len() + AMBISONIC.len(),
ChannelLayout::ROSTER.len(),
"roster has {} entries but only {} are accounted for by the FFmpeg map \
({}) plus the ambisonics ({})",
ChannelLayout::ROSTER.len(),
MAP.len() + AMBISONIC.len(),
MAP.len(),
AMBISONIC.len()
);
for layout in ChannelLayout::ROSTER {
assert!(
MAP.iter().any(|(mapped, ..)| mapped == layout)
|| AMBISONIC.iter().any(|(mapped, _)| mapped == layout),
"{layout} is rostered but appears in neither table"
);
}
}
#[test]
fn the_map_is_transcribed_in_full() {
const FFMPEG_N9_MAP_ENTRIES: usize = 40;
assert_eq!(
MAP.len(),
FFMPEG_N9_MAP_ENTRIES,
"FFmpeg n9.0 names {FFMPEG_N9_MAP_ENTRIES} layouts; {} are transcribed",
MAP.len()
);
}
#[test]
fn the_unqualified_five_point_slugs_are_the_back_layouts() {
assert_eq!("5.0".parse(), Ok(ChannelLayout::Ch5_0Back));
assert_eq!("5.1".parse(), Ok(ChannelLayout::Ch5_1Back));
assert_eq!("5.0(side)".parse(), Ok(ChannelLayout::Ch5_0));
assert_eq!("5.1(side)".parse(), Ok(ChannelLayout::Ch5_1));
assert_eq!(ChannelLayout::Ch5_0Back.as_str(), "5.0");
assert_eq!(ChannelLayout::Ch5_1Back.as_str(), "5.1");
assert_eq!(ChannelLayout::Ch5_0.as_str(), "5.0(side)");
assert_eq!(ChannelLayout::Ch5_1.as_str(), "5.1(side)");
}
#[test]
fn the_unqualified_wide_slug_is_the_back_layout() {
assert_eq!("7.1(wide)".parse(), Ok(ChannelLayout::Ch7_1WideBack));
assert_eq!("7.1(wide-side)".parse(), Ok(ChannelLayout::Ch7_1Wide));
assert_eq!(ChannelLayout::Ch7_1WideBack.as_str(), "7.1(wide)");
assert_eq!(ChannelLayout::Ch7_1Wide.as_str(), "7.1(wide-side)");
}
#[test]
fn the_back_qualifier_is_not_a_rule() {
assert_eq!("5.1.2".parse(), Ok(ChannelLayout::Ch5_1_2));
assert_eq!("5.1.2(back)".parse(), Ok(ChannelLayout::Ch5_1_2Back));
assert_eq!(ChannelLayout::Ch5_1_2.as_str(), "5.1.2");
assert_eq!(ChannelLayout::Ch5_1_2Back.as_str(), "5.1.2(back)");
assert_eq!(ChannelLayout::Ch5_1_4Back.as_str(), "5.1.4");
assert_eq!(ChannelLayout::Ch7_1_4Back.as_str(), "7.1.4");
assert_eq!(ChannelLayout::Ch9_1_4Back.as_str(), "9.1.4");
for slug in ["5.1.4(back)", "7.1.4(back)", "9.1.4(back)"] {
assert!(
slug.parse::<ChannelLayout>().unwrap().is_other(),
"{slug} is not a name FFmpeg gives anything"
);
}
}
#[test]
fn the_top_back_alias_is_not_a_second_layout() {
assert!("7.1(top-back)".parse::<ChannelLayout>().unwrap().is_other());
assert_eq!(
ChannelLayout::ROSTER
.iter()
.filter(|layout| layout.as_str() == "5.1.2(back)")
.count(),
1
);
}
#[test]
fn a_layout_outside_the_map_lands_in_other() {
let v: ChannelLayout = "fl+fr+tfl".parse().unwrap();
assert!(v.is_other());
assert_eq!(v.as_str(), "fl+fr+tfl");
assert_eq!(v.to_string(), "fl+fr+tfl");
}
#[test]
fn display_matches_as_str() {
assert_eq!(ChannelLayout::Stereo.to_string(), "stereo");
assert_eq!(ChannelLayout::Ch5_1.to_string(), "5.1(side)");
assert_eq!(
ChannelLayout::Other(SmolStr::new("custom_layout")).to_string(),
"custom_layout"
);
}
#[test]
fn is_variant_predicates() {
assert!(ChannelLayout::Mono.is_mono());
assert!(ChannelLayout::Stereo.is_stereo());
assert!(ChannelLayout::Ch5_1.is_ch_5_1());
assert!(ChannelLayout::Ch5_1_2Back.is_ch_5_1_2_back());
assert!(ChannelLayout::QuadSide.is_quad_side());
assert!(ChannelLayout::Other(SmolStr::new("x")).is_other());
}
#[test]
fn channellayout_slugs_are_lowercase_canonical_and_fold() {
const SLUGS: &[&str] = &["mono", "stereo", "5.1", "7.1", "quad"];
for (i, slug) in SLUGS.iter().enumerate() {
assert!(
!slug.bytes().any(|b| b.is_ascii_uppercase()),
"slug {slug:?} is not lowercase-canonical"
);
for prior in &SLUGS[..i] {
assert!(
!prior.eq_ignore_ascii_case(slug),
"two variants fold onto {slug:?}"
);
}
let v: ChannelLayout = slug.parse().unwrap();
assert!(!v.is_other(), "`{slug}` should be a named variant");
assert_eq!(v.as_str(), *slug, "`{slug}` is not its own canonical form");
}
assert_eq!("mono", "MONO".parse::<ChannelLayout>().unwrap().as_str());
let escaped: ChannelLayout = "MONO_X".parse().unwrap();
assert!(escaped.is_other());
assert_eq!(escaped.as_str(), "mono_x");
assert_eq!(ChannelLayout::other("MONO_X"), escaped);
}
#[test]
fn channel_layout_unwrap_other_borrowed_view() {
let v = ChannelLayout::other("fl+fr+tfl");
assert_eq!(v.unwrap_other_ref().as_str(), "fl+fr+tfl");
assert!(v.try_unwrap_other_ref().is_ok());
assert!(ChannelLayout::Stereo.try_unwrap_other_ref().is_err());
}
#[test]
fn rosters_are_well_formed() {
crate::roster_tests::check(
ChannelLayout::ROSTER,
"ChannelLayout",
ChannelLayout::as_str,
);
}