use num::{FromPrimitive, Rational64, Signed};
use std::str::FromStr;
use crate::errors::FramerateParseError;
use crate::framerate::Ntsc;
use std::convert::TryFrom;
use std::fmt::Debug;
pub type FramerateSourceResult = Result<num::Rational64, FramerateParseError>;
const DROP_DIVISOR_PLAYBACK: num::Rational64 = num::Rational64::new_raw(30000, 1001);
const DROP_DIVISOR_TIMEBASE: num::Rational64 = num::Rational64::new_raw(30, 1);
fn validate_ntsc_value(
value: &num::Rational64,
ntsc: Ntsc,
is_base: bool,
) -> Result<(), FramerateParseError> {
if value.is_negative() {
return Err(FramerateParseError::Negative(
"framerates cannot be negative".to_string(),
));
}
if !ntsc.is_ntsc() {
return Ok(());
}
if is_base {
if !value.is_integer() {
return Err(FramerateParseError::Ntsc(
"ntsc timebases must be whole numbers".to_string(),
));
}
} else if value.denom() != &1001 {
return Err(FramerateParseError::Ntsc(
"ntsc framerates must be n/1001".to_string(),
));
}
if ntsc != Ntsc::DropFrame {
return Ok(());
}
let drop_divisor = match is_base {
true => DROP_DIVISOR_TIMEBASE,
false => DROP_DIVISOR_PLAYBACK,
};
if value % drop_divisor != num::Rational64::from_integer(0) {
let rate_type = match is_base {
true => "timebase",
false => "playback",
};
let err_message = format!(
"dropframe must have {0} divisible by {1} (multiple of 29.97)",
rate_type, drop_divisor,
);
return Err(FramerateParseError::DropFrame(err_message));
}
Ok(())
}
pub trait FramerateSource: Debug {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult;
}
impl<T> FramerateSource for &T
where
T: FramerateSource,
{
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
(*self).to_playback(ntsc, is_timebase)
}
}
impl FramerateSource for &dyn FramerateSource {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
(*self).to_playback(ntsc, is_timebase)
}
}
impl FramerateSource for Rational64 {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
validate_ntsc_value(self, ntsc, is_timebase)?;
let value = if is_timebase && ntsc.is_ntsc() {
self.round() * 1000 / 1001
} else {
*self
};
Ok(value)
}
}
impl FramerateSource for num::Rational32 {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
let numerator: i32 = *self.numer();
let denominator: i32 = *self.denom();
let rat64 = num::Rational64::new(numerator.into(), denominator.into());
rat64.to_playback(ntsc, is_timebase)
}
}
impl FramerateSource for i64 {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
let rational = num::Rational64::from_integer(*self);
rational.to_playback(ntsc, is_timebase)
}
}
impl FramerateSource for u64 {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
let i64_val = match i64::try_from(*self) {
Ok(converted) => converted,
Err(err) => {
return Err(FramerateParseError::Conversion(format!(
"error converting u64 to i64 : {}",
err
)))
}
};
i64_val.to_playback(ntsc, is_timebase)
}
}
impl FramerateSource for i32 {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
i64::from(*self).to_playback(ntsc, is_timebase)
}
}
impl FramerateSource for u32 {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
i64::from(*self).to_playback(ntsc, is_timebase)
}
}
impl FramerateSource for i16 {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
i64::from(*self).to_playback(ntsc, is_timebase)
}
}
impl FramerateSource for u16 {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
i64::from(*self).to_playback(ntsc, is_timebase)
}
}
impl FramerateSource for i8 {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
i64::from(*self).to_playback(ntsc, is_timebase)
}
}
impl FramerateSource for u8 {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
i64::from(*self).to_playback(ntsc, is_timebase)
}
}
impl FramerateSource for f64 {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
if !ntsc.is_ntsc() {
return Err(FramerateParseError::Imprecise(
"float values cannot be parsed for non-NTSC Framerates due to imprecision"
.to_string(),
));
}
let mut rational = match num::Rational64::from_f64(*self) {
None => {
return Err(FramerateParseError::Conversion(
"could not parse rational from f64".to_string(),
))
}
Some(rational) => rational,
};
if !is_timebase && ntsc.is_ntsc() {
rational = rational.round() * 1000 / 1001;
}
rational.to_playback(ntsc, is_timebase)
}
}
impl FramerateSource for f32 {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
f64::from(*self).to_playback(ntsc, is_timebase)
}
}
impl FramerateSource for &str {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
if let Ok(parsed) = num::Rational64::from_str(self) {
return parsed.to_playback(ntsc, is_timebase);
}
if let Ok(parsed) = i64::from_str(self) {
return parsed.to_playback(ntsc, is_timebase);
}
if let Ok(parsed) = f64::from_str(self) {
return parsed.to_playback(ntsc, is_timebase);
}
Err(FramerateParseError::Conversion(format!(
"could not parse '{0}' as rational, int, or float for framerate",
self
)))
}
}
impl FramerateSource for String {
fn to_playback(&self, ntsc: Ntsc, is_timebase: bool) -> FramerateSourceResult {
let str: &str = self.as_str();
str.to_playback(ntsc, is_timebase)
}
}