use core::{fmt, str::FromStr};
use ffmpeg_next::ffi::AVDiscard;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum Discard {
Nothing,
#[default]
Default,
NonReference,
Bidirectional,
NonIntra,
NonKey,
All,
}
impl Discard {
pub const ALL: [Self; 7] = [
Self::Nothing,
Self::Default,
Self::NonReference,
Self::Bidirectional,
Self::NonIntra,
Self::NonKey,
Self::All,
];
pub const fn as_str(self) -> &'static str {
match self {
Self::Nothing => "none",
Self::Default => "default",
Self::NonReference => "noref",
Self::Bidirectional => "bidir",
Self::NonIntra => "nointra",
Self::NonKey => "nokey",
Self::All => "all",
}
}
pub const fn as_av(self) -> AVDiscard {
match self {
Self::Nothing => AVDiscard::AVDISCARD_NONE,
Self::Default => AVDiscard::AVDISCARD_DEFAULT,
Self::NonReference => AVDiscard::AVDISCARD_NONREF,
Self::Bidirectional => AVDiscard::AVDISCARD_BIDIR,
Self::NonIntra => AVDiscard::AVDISCARD_NONINTRA,
Self::NonKey => AVDiscard::AVDISCARD_NONKEY,
Self::All => AVDiscard::AVDISCARD_ALL,
}
}
}
impl fmt::Display for Discard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)]
#[error("not an FFmpeg discard name: none, default, noref, bidir, nointra, nokey or all")]
#[non_exhaustive]
pub struct ParseDiscardError;
impl FromStr for Discard {
type Err = ParseDiscardError;
fn from_str(text: &str) -> Result<Self, Self::Err> {
Self::ALL
.into_iter()
.find(|value| value.as_str().eq_ignore_ascii_case(text))
.ok_or(ParseDiscardError)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub struct DecoderOptions {
skip_frame: Discard,
skip_loop_filter: Discard,
skip_idct: Discard,
}
impl DecoderOptions {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new() -> Self {
Self {
skip_frame: Discard::Default,
skip_loop_filter: Discard::Default,
skip_idct: Discard::Default,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn skip_frame(&self) -> Discard {
self.skip_frame
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn skip_loop_filter(&self) -> Discard {
self.skip_loop_filter
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn skip_idct(&self) -> Discard {
self.skip_idct
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_skip_frame(mut self, value: Discard) -> Self {
self.skip_frame = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_skip_loop_filter(mut self, value: Discard) -> Self {
self.skip_loop_filter = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_skip_idct(mut self, value: Discard) -> Self {
self.skip_idct = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_skip_frame(&mut self, value: Discard) -> &mut Self {
self.skip_frame = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_skip_loop_filter(&mut self, value: Discard) -> &mut Self {
self.skip_loop_filter = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_skip_idct(&mut self, value: Discard) -> &mut Self {
self.skip_idct = value;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_discard_is_ffmpegs_value_and_name() {
let expected = [
(Discard::Nothing, -16, "none"),
(Discard::Default, 0, "default"),
(Discard::NonReference, 8, "noref"),
(Discard::Bidirectional, 16, "bidir"),
(Discard::NonIntra, 24, "nointra"),
(Discard::NonKey, 32, "nokey"),
(Discard::All, 48, "all"),
];
assert_eq!(Discard::ALL.len(), expected.len());
for (value, av, name) in expected {
assert_eq!(value.as_av() as i32, av, "{name}");
assert_eq!(std::string::ToString::to_string(&value), name);
assert_eq!(name.parse::<Discard>(), Ok(value));
assert_eq!(name.to_ascii_uppercase().parse::<Discard>(), Ok(value));
}
assert_eq!("NoRef".parse::<Discard>(), Ok(Discard::NonReference));
for refused in ["", "nonref", "non_reference", " noref", "noref ", "8"] {
assert_eq!(
refused.parse::<Discard>(),
Err(ParseDiscardError),
"{refused:?}"
);
}
assert_eq!(Discard::default(), Discard::Default);
}
#[test]
fn the_options_default_to_libavcodecs_and_set_each_field() {
let options = DecoderOptions::new();
assert_eq!(options, DecoderOptions::default());
assert_eq!(
(
options.skip_frame(),
options.skip_loop_filter(),
options.skip_idct()
),
(Discard::Default, Discard::Default, Discard::Default)
);
let built = options
.with_skip_frame(Discard::NonReference)
.with_skip_loop_filter(Discard::All)
.with_skip_idct(Discard::NonKey);
assert_eq!(
(
built.skip_frame(),
built.skip_loop_filter(),
built.skip_idct()
),
(Discard::NonReference, Discard::All, Discard::NonKey)
);
let mut set = DecoderOptions::new();
set
.set_skip_frame(Discard::NonReference)
.set_skip_loop_filter(Discard::All)
.set_skip_idct(Discard::NonKey);
assert_eq!(set, built);
}
}