use core::str::FromStr;
use derive_more::{Display, IsVariant, TryUnwrap, Unwrap};
use smol_str::SmolStr;
#[cfg_attr(
feature = "quickcheck",
derive(::quickcheck_richderive::Arbitrary),
quickcheck(arbitrary = "crate::quickcheck_helpers::strings::subtitle_format")
)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Display, IsVariant, Unwrap, TryUnwrap)]
#[display("{}", self.as_str())]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
#[non_exhaustive]
pub enum Format {
Srt,
WebVtt,
Ass,
Ssa,
Sub,
#[is_variant(ignore)]
Mpl2,
Lrc,
Smi,
Stl,
Sbv,
Ttml,
MovText,
DvdSub,
PgsSub,
HdmvPgs,
DvbSub,
XSub,
Other(SmolStr),
}
impl Default for Format {
#[cfg_attr(not(tarpaulin), inline(always))]
fn default() -> Self {
Self::Other(SmolStr::new_inline(""))
}
}
impl Format {
#[inline(always)]
pub const fn is_mpl2(&self) -> bool {
matches!(self, Self::Mpl2)
}
pub fn as_str(&self) -> &str {
match self {
Self::Srt => "srt",
Self::WebVtt => "webvtt",
Self::Ass => "ass",
Self::Ssa => "ssa",
Self::Sub => "microdvd",
Self::Mpl2 => "mpl2",
Self::Lrc => "lrc",
Self::Smi => "sami",
Self::Stl => "stl",
Self::Sbv => "subviewer",
Self::Ttml => "ttml",
Self::MovText => "mov_text",
Self::DvdSub => "dvd_subtitle",
Self::PgsSub => "hdmv_pgs_subtitle",
Self::HdmvPgs => "hdmv_pgs_subtitle",
Self::DvbSub => "dvb_subtitle",
Self::XSub => "xsub",
Self::Other(s) => s.as_str(),
}
}
#[inline(always)]
pub const fn as_extension(&self) -> &'static str {
match self {
Self::Srt => "srt",
Self::WebVtt => "vtt",
Self::Ass => "ass",
Self::Ssa => "ssa",
Self::Sub => "sub",
Self::Mpl2 => "mpl",
Self::Lrc => "lrc",
Self::Smi => "smi",
Self::Stl => "stl",
Self::Sbv => "sbv",
Self::Ttml => "ttml",
Self::MovText => "",
Self::DvdSub => "",
Self::PgsSub => "",
Self::HdmvPgs => "",
Self::DvbSub => "",
Self::XSub => "",
Self::Other(_) => "",
}
}
pub const fn is_image_based(&self) -> Option<bool> {
match self {
Self::DvdSub | Self::PgsSub | Self::HdmvPgs | Self::DvbSub | Self::XSub => Some(true),
Self::Srt
| Self::WebVtt
| Self::Ass
| Self::Ssa
| Self::Sub
| Self::Mpl2
| Self::Lrc
| Self::Smi
| Self::Stl
| Self::Sbv
| Self::Ttml
| Self::MovText => Some(false),
Self::Other(_) => None,
}
}
}
impl FromStr for Format {
type Err = core::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"srt" => Self::Srt,
"webvtt" => Self::WebVtt,
"ass" => Self::Ass,
"ssa" => Self::Ssa,
"microdvd" => Self::Sub,
"mpl2" => Self::Mpl2,
"lrc" => Self::Lrc,
"sami" => Self::Smi,
"stl" => Self::Stl,
"subviewer" => Self::Sbv,
"ttml" => Self::Ttml,
"mov_text" => Self::MovText,
"dvd_subtitle" => Self::DvdSub,
"hdmv_pgs_subtitle" => Self::PgsSub,
"dvb_subtitle" => Self::DvbSub,
"xsub" => Self::XSub,
other => Self::Other(SmolStr::new(other)),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use ::std::string::ToString;
const NAMED_SLUGS: &[(&str, Format)] = &[
("srt", Format::Srt),
("webvtt", Format::WebVtt),
("ass", Format::Ass),
("ssa", Format::Ssa),
("microdvd", Format::Sub),
("mpl2", Format::Mpl2),
("lrc", Format::Lrc),
("sami", Format::Smi),
("stl", Format::Stl),
("subviewer", Format::Sbv),
("ttml", Format::Ttml),
("mov_text", Format::MovText),
("dvd_subtitle", Format::DvdSub),
("hdmv_pgs_subtitle", Format::PgsSub),
("dvb_subtitle", Format::DvbSub),
("xsub", Format::XSub),
];
#[test]
fn as_str_round_trips_for_every_named_variant() {
for (slug, variant) in NAMED_SLUGS {
assert_eq!(variant.as_str(), *slug, "as_str mismatch for {variant:?}");
let parsed: Format = slug.parse().unwrap();
assert_eq!(&parsed, variant, "FromStr mismatch for {slug:?}");
}
}
#[test]
fn hdmv_pgs_slug_canonicalises_to_pgs_sub() {
assert_eq!(Format::HdmvPgs.as_str(), "hdmv_pgs_subtitle");
assert_eq!(Format::PgsSub.as_str(), "hdmv_pgs_subtitle");
let parsed: Format = "hdmv_pgs_subtitle".parse().unwrap();
assert_eq!(parsed, Format::PgsSub);
}
#[test]
fn from_str_is_total_for_unknown_slug() {
let parsed: Format = "definitely_not_a_real_subtitle_format_xyz".parse().unwrap();
assert!(matches!(parsed, Format::Other(_)));
assert_eq!(parsed.as_str(), "definitely_not_a_real_subtitle_format_xyz");
}
#[test]
fn is_image_based_classifies_known_variants() {
assert_eq!(Format::DvdSub.is_image_based(), Some(true));
assert_eq!(Format::PgsSub.is_image_based(), Some(true));
assert_eq!(Format::HdmvPgs.is_image_based(), Some(true));
assert_eq!(Format::DvbSub.is_image_based(), Some(true));
assert_eq!(Format::XSub.is_image_based(), Some(true));
assert_eq!(Format::Srt.is_image_based(), Some(false));
assert_eq!(Format::WebVtt.is_image_based(), Some(false));
assert_eq!(Format::Ass.is_image_based(), Some(false));
assert_eq!(Format::MovText.is_image_based(), Some(false));
assert_eq!(Format::Other(SmolStr::new("weird")).is_image_based(), None,);
}
#[test]
fn display_matches_as_str() {
for (_slug, variant) in NAMED_SLUGS {
assert_eq!(variant.to_string(), variant.as_str());
}
assert_eq!(
Format::Other(SmolStr::new("custom_fmt")).to_string(),
"custom_fmt",
);
}
#[test]
fn is_variant_predicates() {
assert!(Format::Srt.is_srt());
assert!(!Format::Srt.is_web_vtt());
assert!(Format::Other(SmolStr::new("x")).is_other());
}
#[test]
fn as_extension_matches_disk_form() {
for (variant, ext) in [
(Format::Srt, "srt"),
(Format::WebVtt, "vtt"),
(Format::Ass, "ass"),
(Format::Ssa, "ssa"),
(Format::Sub, "sub"),
(Format::Mpl2, "mpl"),
(Format::Lrc, "lrc"),
(Format::Smi, "smi"),
(Format::Stl, "stl"),
(Format::Sbv, "sbv"),
(Format::Ttml, "ttml"),
] {
assert_eq!(variant.as_extension(), ext, "{variant:?}");
}
for variant in [
Format::MovText,
Format::DvdSub,
Format::PgsSub,
Format::HdmvPgs,
Format::DvbSub,
Format::XSub,
] {
assert_eq!(variant.as_extension(), "", "{variant:?}");
}
assert_eq!(Format::Other(SmolStr::new("custom")).as_extension(), "");
}
}