use num::integer::{div_rem, lcm};
use num::rational::Ratio;
use num::{abs, FromPrimitive, Rational64, Signed, ToPrimitive, Zero};
use crate::{
consts::{
PERFS_PER_6INCHES_16, PERFS_PER_FOOT_35, PREMIERE_TICKS_PER_SECOND, SECONDS_PER_HOUR,
SECONDS_PER_MINUTE,
},
source_ppro_ticks::PremiereTicksSource,
timecode_parse::round_seconds_to_frame,
Framerate, FramesSource, Ntsc, SecondsSource, TimecodeParseError,
};
use std::ops::{Add, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub};
use std::{cmp::Ordering, ops::AddAssign};
use std::{
fmt::{Display, Formatter},
ops::SubAssign,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TimecodeSections {
pub negative: bool,
pub hours: i64,
pub minutes: i64,
pub seconds: i64,
pub frames: i64,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum FilmFormat {
FF35mm4perf,
FF35mm3perf,
FF35mm2perf,
FF16mm,
}
impl FilmFormat {
pub fn perfs_per_foot(&self) -> i64 {
match self {
FilmFormat::FF16mm => PERFS_PER_6INCHES_16,
FilmFormat::FF35mm4perf | FilmFormat::FF35mm3perf | FilmFormat::FF35mm2perf => {
PERFS_PER_FOOT_35
}
}
}
pub fn footage_modulus_perf_count(&self) -> i64 {
lcm(self.perfs_per_frame(), self.perfs_per_foot())
}
pub fn footage_modulus_frame_count(&self) -> i64 {
self.footage_modulus_perf_count() / self.perfs_per_frame()
}
pub fn footage_modulus_footage_count(&self) -> i64 {
self.footage_modulus_perf_count() / self.perfs_per_foot()
}
pub fn perfs_per_frame(&self) -> i64 {
match self {
FilmFormat::FF16mm => 1,
FilmFormat::FF35mm4perf => 4,
FilmFormat::FF35mm3perf => 3,
FilmFormat::FF35mm2perf => 2,
}
}
pub fn allows_perf_field(&self) -> bool {
self.perfs_per_foot() % self.perfs_per_frame() != 0
}
}
#[derive(Debug)]
pub struct FeetFramesStr<'a> {
pub(crate) input: &'a str,
pub(crate) format: FilmFormat,
}
impl<'a> FeetFramesStr<'a> {
pub fn new(input: &'a str, format: FilmFormat) -> Self {
FeetFramesStr { input, format }
}
}
pub type TimecodeParseResult = Result<Timecode, TimecodeParseError>;
#[derive(Clone, Copy, Debug)]
pub struct Timecode {
seconds: Rational64,
rate: Framerate,
}
impl Timecode {
pub fn rate(&self) -> Framerate {
self.rate
}
pub fn seconds(&self) -> Rational64 {
self.seconds
}
pub fn sections(&self) -> TimecodeSections {
let mut frames_int = abs(self.frames());
let timebase = self.rate.timebase();
if self.rate.ntsc() == Ntsc::DropFrame {
frames_int += drop_frame_adjustment(frames_int, self.rate);
}
let mut frames = Rational64::from_integer(frames_int);
let frames_per_minute = timebase * SECONDS_PER_MINUTE;
let frames_per_hour = timebase * SECONDS_PER_HOUR;
let hours = (frames / frames_per_hour).floor();
frames %= frames_per_hour;
let minutes = (frames / frames_per_minute).floor();
frames %= frames_per_minute;
let seconds = (frames / timebase).floor();
frames = (frames % timebase).round();
TimecodeSections {
negative: self.seconds.is_negative(),
hours: hours.to_integer(),
minutes: minutes.to_integer(),
seconds: seconds.to_integer(),
frames: frames.to_integer(),
}
}
pub fn timecode(&self) -> String {
let sections = self.sections();
let sign = if self.seconds.is_negative() { "-" } else { "" };
let frame_sep = if self.rate.ntsc() == Ntsc::DropFrame {
";"
} else {
":"
};
format!(
"{}{:02}:{:02}:{:02}{}{:02}",
sign, sections.hours, sections.minutes, sections.seconds, frame_sep, sections.frames,
)
}
pub fn frames(&self) -> i64 {
let rational_frames = self.seconds * self.rate.playback();
if rational_frames.denom() == &1 {
return *rational_frames.numer();
};
rational_frames.round().to_integer()
}
pub fn runtime(&self, precision: usize) -> String {
let mut seconds = abs(self.seconds);
let hours = (seconds / SECONDS_PER_HOUR).floor().to_integer();
seconds %= SECONDS_PER_HOUR;
let minutes = (seconds / SECONDS_PER_MINUTE).floor().to_integer();
seconds %= SECONDS_PER_MINUTE;
let fract = seconds.fract();
let seconds_int = seconds.floor().to_integer();
let fract_str = if fract == Rational64::zero() {
".0".to_string()
} else {
let formatted = format!("{:.1$}", fract.to_f64().unwrap_or(0.0), precision);
let mut formatted = formatted.trim_start_matches('0');
formatted = formatted.trim_end_matches('0');
formatted.to_string()
};
let sign = if self.seconds.is_negative() { "-" } else { "" };
format!(
"{}{:02}:{:02}:{:02}{}",
sign, hours, minutes, seconds_int, fract_str,
)
}
pub fn premiere_ticks(&self) -> i64 {
let seconds128 =
Ratio::<i128>::new(*self.seconds.numer() as i128, *self.seconds.denom() as i128);
let seconds_int = (seconds128 * PREMIERE_TICKS_PER_SECOND)
.round()
.to_integer();
seconds_int as i64
}
pub fn feet_and_frames(&self, rep: FilmFormat) -> String {
let total_frames = abs(self.frames());
let negative = self.seconds.is_negative();
let total_perfs = total_frames * rep.perfs_per_frame();
let last_perf = total_perfs + rep.perfs_per_frame() - 1;
let feet = last_perf / rep.perfs_per_foot();
let frames = (last_perf % rep.perfs_per_foot()) / rep.perfs_per_frame();
let sign = if negative { "-" } else { "" };
if rep.allows_perf_field() {
let perf_marker = feet % rep.footage_modulus_footage_count();
format!("{}{}+{:02}.{}", sign, feet, frames, perf_marker)
} else {
format!("{}{}+{:02}", sign, feet, frames)
}
}
pub fn rebase(&self, rate: Framerate) -> Self {
Timecode::with_i64_frames(self.frames(), rate)
}
pub fn abs(&self) -> Self {
Timecode::with_rational_seconds(abs(self.seconds), self.rate)
}
pub fn with_frames<T: FramesSource>(frames: T, rate: Framerate) -> TimecodeParseResult {
let frame_count = frames.to_frames(rate)?;
Ok(Self::with_i64_frames(frame_count, rate))
}
pub fn with_seconds<T: SecondsSource>(seconds: T, rate: Framerate) -> TimecodeParseResult {
let seconds_rat = seconds.to_seconds(rate)?;
Ok(Self::with_rational_seconds(seconds_rat, rate))
}
pub fn with_premiere_ticks<T: PremiereTicksSource>(
ticks: T,
rate: Framerate,
) -> TimecodeParseResult {
let tick_count = ticks.to_ticks(rate)?;
let seconds128 =
Ratio::<i128>::from_integer(tick_count as i128) / PREMIERE_TICKS_PER_SECOND;
let seconds = Rational64::new(*seconds128.numer() as i64, *seconds128.denom() as i64);
Self::with_seconds(seconds, rate)
}
fn with_i64_frames(frame_count: i64, rate: Framerate) -> Timecode {
let seconds = Rational64::from_integer(frame_count) / rate.playback();
Self::with_rational_seconds(seconds, rate)
}
fn with_rational_seconds(seconds: Rational64, rate: Framerate) -> Timecode {
let seconds = round_seconds_to_frame(seconds, rate);
Timecode { seconds, rate }
}
}
impl Display for Timecode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "[{} @ {}]", self.timecode(), self.rate)
}
}
impl PartialEq for Timecode {
fn eq(&self, other: &Self) -> bool {
self.seconds == other.seconds
}
}
impl Eq for Timecode {}
impl PartialOrd for Timecode {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Timecode {
fn cmp(&self, other: &Self) -> Ordering {
self.seconds.cmp(&other.seconds)
}
}
impl Add for Timecode {
type Output = Timecode;
fn add(self, rhs: Self) -> Self::Output {
let new_seconds = self.seconds + rhs.seconds;
Timecode::with_rational_seconds(new_seconds, self.rate())
}
}
impl<T> AddAssign<T> for Timecode
where
Timecode: Add<T, Output = Timecode>,
{
fn add_assign(&mut self, rhs: T) {
*self = *self + rhs
}
}
impl Sub for Timecode {
type Output = Timecode;
fn sub(self, rhs: Self) -> Self::Output {
let new_seconds = self.seconds - rhs.seconds;
Timecode::with_rational_seconds(new_seconds, self.rate)
}
}
impl<T> SubAssign<T> for Timecode
where
Timecode: Sub<T, Output = Timecode>,
{
fn sub_assign(&mut self, rhs: T) {
*self = *self - rhs
}
}
impl Mul<Rational64> for Timecode {
type Output = Timecode;
fn mul(self, rhs: Rational64) -> Self::Output {
let new_seconds = self.seconds * rhs;
Timecode::with_rational_seconds(new_seconds, self.rate)
}
}
impl Mul<f64> for Timecode {
type Output = Timecode;
fn mul(self, rhs: f64) -> Self::Output {
let rhs_rat = Rational64::from_f64(rhs).unwrap();
let new_seconds = self.seconds * rhs_rat;
Timecode::with_rational_seconds(new_seconds, self.rate)
}
}
impl Mul<Timecode> for f64 {
type Output = Timecode;
fn mul(self, rhs: Timecode) -> Self::Output {
rhs * self
}
}
impl Mul<i64> for Timecode {
type Output = Timecode;
fn mul(self, rhs: i64) -> Self::Output {
let rhs_rat = Rational64::from_integer(rhs);
let new_seconds = self.seconds * rhs_rat;
Timecode::with_rational_seconds(new_seconds, self.rate)
}
}
impl Mul<Timecode> for i64 {
type Output = Timecode;
fn mul(self, rhs: Timecode) -> Self::Output {
rhs * self
}
}
impl<T> MulAssign<T> for Timecode
where
Timecode: Mul<T, Output = Timecode>,
{
fn mul_assign(&mut self, rhs: T) {
*self = *self * rhs
}
}
impl Div<Rational64> for Timecode {
type Output = Timecode;
fn div(self, rhs: Rational64) -> Self::Output {
let mut frames_rat = Rational64::from_integer(self.frames());
frames_rat /= rhs;
frames_rat = frames_rat.floor();
Timecode::with_i64_frames(frames_rat.to_integer(), self.rate)
}
}
impl Rem<Rational64> for Timecode {
type Output = Timecode;
fn rem(self, rhs: Rational64) -> Self::Output {
let mut frames_rat = Rational64::from_integer(self.frames());
frames_rat %= rhs;
frames_rat = frames_rat.round();
Timecode::with_i64_frames(frames_rat.to_integer(), self.rate)
}
}
impl Div<f64> for Timecode {
type Output = Timecode;
fn div(self, rhs: f64) -> Self::Output {
let rhs_rat = Rational64::from_f64(rhs).unwrap();
self / rhs_rat
}
}
impl Rem<f64> for Timecode {
type Output = Timecode;
fn rem(self, rhs: f64) -> Self::Output {
let rhs_rat = Rational64::from_f64(rhs).unwrap();
self % rhs_rat
}
}
impl Div<i64> for Timecode {
type Output = Timecode;
fn div(self, rhs: i64) -> Self::Output {
let frames_divided = self.frames() / rhs;
Timecode::with_i64_frames(frames_divided, self.rate)
}
}
impl Rem<i64> for Timecode {
type Output = Timecode;
fn rem(self, rhs: i64) -> Self::Output {
let frames_remainder = self.frames() % rhs;
Timecode::with_i64_frames(frames_remainder, self.rate)
}
}
impl<T> DivAssign<T> for Timecode
where
Timecode: Div<T, Output = Timecode>,
{
fn div_assign(&mut self, rhs: T) {
*self = *self / rhs
}
}
impl<T> RemAssign<T> for Timecode
where
Timecode: Rem<T, Output = Timecode>,
{
fn rem_assign(&mut self, rhs: T) {
*self = *self % rhs
}
}
impl Neg for Timecode {
type Output = Self;
fn neg(self) -> Self::Output {
Timecode::with_rational_seconds(-self.seconds, self.rate)
}
}
fn drop_frame_adjustment(mut frame_number: i64, rate: Framerate) -> i64 {
let framerate = rate.playback().to_f64().unwrap();
let dropped_per_min = rate.drop_frames_per_minute().unwrap();
let frames_per_hour = (framerate * 60.0 * 60.0).round().to_i64().unwrap();
let frames_per_24_hours = frames_per_hour * 24;
let frames_per_10_min = (framerate * 60.0 * 10.0).round().to_i64().unwrap();
let frames_per_min = (framerate * 60.0).round().to_i64().unwrap();
frame_number %= frames_per_24_hours;
let (tens_of_mins, remaining_mins) = div_rem(frame_number, frames_per_10_min);
let tens_of_mins_adjustment = dropped_per_min * 9 * tens_of_mins;
if remaining_mins > dropped_per_min {
let remaining_minutes_adjustment =
dropped_per_min * (remaining_mins - dropped_per_min) / frames_per_min;
tens_of_mins_adjustment + remaining_minutes_adjustment
} else {
tens_of_mins_adjustment
}
}