use std::collections::HashMap;
use std::time::Duration;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Timestamp(pub u64);
impl Timestamp {
pub fn from_millis(ms: u64) -> Self {
Self(ms.saturating_mul(1000))
}
pub fn from_micros(us: u64) -> Self {
Self(us)
}
pub fn from_seconds(s: f64) -> Self {
Self((s * 1_000_000.0) as u64)
}
pub fn from_hms(h: u32, m: u32, s: f64) -> Self {
let total_secs = (h as f64) * 3600.0 + (m as f64) * 60.0 + s;
Self::from_seconds(total_secs)
}
pub fn as_millis(&self) -> u64 {
self.0 / 1000
}
pub fn as_micros(&self) -> u64 {
self.0
}
pub fn as_seconds(&self) -> f64 {
self.0 as f64 / 1_000_000.0
}
pub fn as_duration(&self) -> Duration {
Duration::from_micros(self.0)
}
pub fn to_ffmpeg_time(&self) -> String {
let total_us = self.0;
let ms = (total_us / 1000) % 1000;
let total_secs = total_us / 1_000_000;
let secs = total_secs % 60;
let total_mins = total_secs / 60;
let mins = total_mins % 60;
let hours = total_mins / 60;
format!("{hours:02}:{mins:02}:{secs:02}.{ms:03}")
}
}
impl std::fmt::Display for Timestamp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.to_ffmpeg_time())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct TimeRange {
pub start: Timestamp,
pub end: Timestamp,
}
impl TimeRange {
pub fn new(start: Timestamp, end: Timestamp) -> Self {
Self { start, end }
}
pub fn from_millis(start_ms: u64, end_ms: u64) -> Self {
Self {
start: Timestamp::from_millis(start_ms),
end: Timestamp::from_millis(end_ms),
}
}
pub fn from_seconds(start: f64, end: f64) -> Self {
Self {
start: Timestamp::from_seconds(start),
end: Timestamp::from_seconds(end),
}
}
pub fn duration(&self) -> Duration {
Duration::from_micros(self.end.0.saturating_sub(self.start.0))
}
pub fn duration_ms(&self) -> u64 {
self.end.as_millis().saturating_sub(self.start.as_millis())
}
pub fn contains(&self, ts: Timestamp) -> bool {
ts >= self.start && ts <= self.end
}
pub fn overlaps(&self, other: &TimeRange) -> bool {
self.start < other.end && other.start < self.end
}
pub fn merge(&self, other: &TimeRange) -> Option<TimeRange> {
if self.overlaps(other) {
Some(TimeRange {
start: std::cmp::min(self.start, other.start),
end: std::cmp::max(self.end, other.end),
})
} else {
None
}
}
pub fn split_at(&self, ts: Timestamp) -> (Option<TimeRange>, Option<TimeRange>) {
if ts <= self.start {
(None, Some(*self))
} else if ts >= self.end {
(Some(*self), None)
} else {
(
Some(TimeRange::new(self.start, ts)),
Some(TimeRange::new(ts, self.end)),
)
}
}
pub fn shift(&self, offset_ms: i64) -> Self {
let offset_us = offset_ms * 1000;
let shift = |ts: Timestamp| {
if offset_us >= 0 {
Timestamp(ts.0.saturating_add(offset_us as u64))
} else {
Timestamp(ts.0.saturating_sub(offset_us.unsigned_abs()))
}
};
Self {
start: shift(self.start),
end: shift(self.end),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Segment {
pub range: TimeRange,
pub label: Option<String>,
pub confidence: Option<f32>,
pub metadata: HashMap<String, serde_json::Value>,
}
impl Segment {
pub fn new(range: TimeRange) -> Self {
Self {
range,
label: None,
confidence: None,
metadata: HashMap::new(),
}
}
#[must_use]
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
#[must_use]
pub fn with_confidence(mut self, c: f32) -> Self {
self.confidence = Some(c.clamp(0.0, 1.0));
self
}
#[must_use]
pub fn with_meta(mut self, key: impl Into<String>, val: impl Into<serde_json::Value>) -> Self {
self.metadata.insert(key.into(), val.into());
self
}
}