use anyhow::{anyhow, Context};
use lazy_static::lazy_static;
use regex::Regex;
use std::{
fmt::{self, Debug, Display},
ops::{Add, Sub},
str::FromStr,
};
use super::info::Metadata;
pub trait Boundary:
Clone
+ Copy
+ Display
+ FromStr<Err = anyhow::Error>
+ From<f64>
+ Into<f64>
+ Add<Output = Self>
+ Sub<Output = Self>
+ PartialOrd
{
fn to_frame(self, _: &Metadata) -> anyhow::Result<Frame>;
fn to_time(self, _: &Metadata) -> anyhow::Result<Time>;
}
#[derive(Clone, Default, Eq, Hash, PartialEq)]
pub enum BoundaryType {
#[default]
Frame,
Time,
}
impl Display for BoundaryType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
BoundaryType::Frame => "frame",
BoundaryType::Time => "time",
}
)
}
}
impl FromStr for BoundaryType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.to_uppercase().contains("FRAME") {
Ok(BoundaryType::Frame)
} else if s.to_uppercase().contains("TIME") {
Ok(BoundaryType::Time)
} else {
Err(anyhow!("\"{}\" is not a valid boundary type", s))
}
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Frame(usize);
impl Display for Frame {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<usize> for Frame {
fn from(frame: usize) -> Self {
Frame(frame)
}
}
impl From<Frame> for usize {
fn from(frame: Frame) -> Self {
frame.0
}
}
impl From<f64> for Frame {
fn from(frame: f64) -> Self {
Frame(frame.abs() as usize)
}
}
impl From<Frame> for f64 {
fn from(frame: Frame) -> Self {
frame.0 as f64
}
}
impl FromStr for Frame {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from(s.parse::<f64>().context(format!(
"Could not parse a frame number from \"{}\"",
s
))?))
}
}
impl Add for Frame {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Frame(self.0 + rhs.0)
}
}
impl Add<usize> for Frame {
type Output = Self;
fn add(self, rhs: usize) -> Self {
Frame(self.0 + rhs)
}
}
impl Sub for Frame {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Frame(self.0 - rhs.0)
}
}
impl Sub<usize> for Frame {
type Output = Self;
fn sub(self, rhs: usize) -> Self {
Frame(self.0 - rhs)
}
}
impl Boundary for Frame {
fn to_frame(self, _: &Metadata) -> anyhow::Result<Frame> {
Ok(self)
}
fn to_time(self, metadata: &Metadata) -> anyhow::Result<Time> {
metadata.frame_to_time(self)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Time(u64);
impl Display for Time {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:.6}", self.0 as f64 / 1000000_f64)
}
}
impl From<f64> for Time {
fn from(secs: f64) -> Self {
Time((secs.abs() * 1000000_f64) as u64)
}
}
impl From<Time> for f64 {
fn from(time: Time) -> Self {
time.0 as f64 / 1000000_f64
}
}
lazy_static! {
static ref RE_TIME: Regex =
Regex::new(r#"^(?<hours>\d+):(?<mins>[0-5]\d)+:(?<secs>[0-5]\d)+(\.(?<subs>\d{0,6}))*$"#).unwrap();
}
impl FromStr for Time {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !RE_TIME.is_match(s) {
return Err(anyhow!("\"{}\" is not a valid time string", s));
}
let caps = RE_TIME.captures(s).unwrap();
let hours = caps.name("hours").unwrap().as_str().parse::<f64>().unwrap();
if !(0.0..=23.0).contains(&hours) {
return Err(anyhow!("Hours in {} are not valid", s));
}
let mins = caps.name("mins").unwrap().as_str().parse::<f64>().unwrap();
let secs = caps.name("secs").unwrap().as_str().parse::<f64>().unwrap();
Ok(Self::from(
hours * 3600.0
+ mins * 60.0
+ secs
+ match caps.name("subs") {
Some(subs_match) => {
let subs_str = subs_match.as_str();
let subs = subs_str.parse::<f64>().unwrap();
subs * f64::powf(10_f64, -(subs_str.len() as f64))
}
None => 0.0,
},
))
}
}
impl Add for Time {
type Output = Time;
fn add(self, rhs: Self) -> Self {
Time(self.0 + rhs.0)
}
}
impl Sub for Time {
type Output = Time;
fn sub(self, rhs: Self) -> Self {
Time(self.0 - rhs.0)
}
}
impl Boundary for Time {
fn to_frame(self, metadata: &Metadata) -> anyhow::Result<Frame> {
if !metadata.has_frames() {
Err(anyhow!(
"Cannot turn time boundary into frame boundary, since video has no frames"
))
} else {
Ok(metadata.time_to_frame(self).context(format!(
"Cannot turn time boundary {} into frame boundary",
self
))?)
}
}
fn to_time(self, _: &Metadata) -> anyhow::Result<Time> {
Ok(self)
}
}
pub struct Interval<B>
where
B: Boundary,
{
from: B,
to: B,
}
impl<B> Display for Interval<B>
where
B: Boundary,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}, {}]", self.from, self.to)
}
}
lazy_static! {
static ref RE_INTERVAL: Regex = Regex::new(r#"^\[(?<from>[^\[\],]+),(?<to>[^\[\],]+)\]$"#).unwrap();
}
impl<B> FromStr for Interval<B>
where
B: Boundary,
{
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if !RE_INTERVAL.is_match(s) {
return Err(anyhow!("\"{}\" is not a valid interval", s));
}
Ok(Interval::<B> {
from: B::from_str(
RE_INTERVAL
.captures(s)
.unwrap()
.name("from")
.unwrap()
.as_str(),
)?,
to: B::from_str(
RE_INTERVAL
.captures(s)
.unwrap()
.name("to")
.unwrap()
.as_str(),
)?,
})
}
}
impl<B> Interval<B>
where
B: Boundary,
{
pub fn from_from_to(from: B, to: B) -> Self {
if from < to {
Interval::<B> { from, to }
} else {
Interval::<B> { from: to, to: from }
}
}
pub fn from_start_duration(start: B, duration: B) -> Self {
Interval::<B> {
from: start,
to: start + duration,
}
}
pub fn from(&self) -> B {
self.from
}
pub fn to(&self) -> B {
self.to
}
pub fn len(&self) -> f64 {
Into::<f64>::into(self.to - self.from)
}
pub fn is_empty(&self) -> bool {
self.len() == 0.0
}
pub fn to_frames(&self, metadata: &Metadata) -> anyhow::Result<Interval<Frame>> {
let err_msg = format!("Could not convert interval {} into frames", self);
Ok(Interval::<Frame> {
from: self.from.to_frame(metadata).context(err_msg.clone())?,
to: self.to.to_frame(metadata).context(err_msg.clone())?,
})
}
pub fn to_times(&self, metadata: &Metadata) -> anyhow::Result<Interval<Time>> {
let err_msg = format!("Could not convert interval {} into times", self);
Ok(Interval::<Time> {
from: self.from.to_time(metadata).context(err_msg.clone())?,
to: self.to.to_time(metadata).context(err_msg.clone())?,
})
}
}
impl Interval<Frame> {
pub fn to_key_frames(&self, metadata: &Metadata) -> Option<Interval<Frame>> {
if let Some(from) = metadata.key_frame_greater_or_equal_until_limit(self.from, self.to) {
if let Some(to) = metadata.key_frame_less_or_equal_until_limit(self.to, self.from) {
return Some(Interval::<Frame> { from, to });
}
}
None
}
}
lazy_static! {
static ref RE_INTERVALS: Regex = Regex::new(r#"^(?<interval>\[[^\[\],]+,[^\[\],]+\])+$"#).unwrap();
}
pub fn intervals_from_str<B>(s: &str) -> anyhow::Result<Vec<Interval<B>>>
where
B: Boundary,
{
if !RE_INTERVALS.is_match(s) {
return Err(anyhow!("\"{}\" is not a valid list of intervals", s));
}
let mut intervals = vec![];
for s in s.split_inclusive(']').collect::<Vec<_>>() {
let interval = Interval::<B>::from_str(s)
.context(format!("Could not convert \"{}\" into intervals", s))?;
if !interval.is_empty() {
intervals.push(interval)
}
}
Ok(intervals)
}