Skip to main content

ebustl_parser/
time.rs

1//! Time codes.
2
3use std::fmt;
4
5/// A time code: hours, minutes, seconds and frames.
6///
7/// Time codes are ordered by hours, minutes, seconds and frames.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9pub struct Time {
10    /// Hours, 0-23 in Time Code In and Out
11    pub hours: u8,
12    /// Minutes, 0-59
13    pub minutes: u8,
14    /// Seconds, 0-59
15    pub seconds: u8,
16    /// Frames, less than the number of frames per second
17    pub frames: u8,
18}
19
20impl Time {
21    /// Formats the time as `H:M:S,ms`, with the frames converted to milliseconds at `fps`
22    /// frames per second.
23    ///
24    /// # Example
25    ///
26    /// ```rust
27    /// use ebustl_parser::Time;
28    ///
29    /// let time = Time { hours: 1, minutes: 2, seconds: 3, frames: 12 };
30    /// assert_eq!("1:2:3,480", time.format_fps(25));
31    /// ```
32    pub fn format_fps(&self, fps: usize) -> String {
33        format!(
34            "{}:{}:{},{}",
35            self.hours,
36            self.minutes,
37            self.seconds,
38            self.frames as usize * 1000 / fps
39        )
40    }
41    pub(crate) fn serialize(&self) -> Vec<u8> {
42        vec![self.hours, self.minutes, self.seconds, self.frames]
43    }
44
45    /// Formats the time as HH:MM:SS:FF, e.g. for messages.
46    pub(crate) fn hhmmssff(&self) -> String {
47        format!(
48            "{:02}:{:02}:{:02}:{:02}",
49            self.hours, self.minutes, self.seconds, self.frames
50        )
51    }
52
53    /// Returns `true` if each element is in the range the spec gives for Time Code In and Out:
54    /// hours 0-23, minutes and seconds 0-59 and frames less than `fps`.
55    ///
56    /// # Example
57    ///
58    /// ```rust
59    /// use ebustl_parser::Time;
60    ///
61    /// let time = Time { hours: 10, minutes: 0, seconds: 0, frames: 29 };
62    /// assert!(time.is_valid(30));
63    /// assert!(!time.is_valid(25));
64    /// ```
65    pub fn is_valid(&self, fps: usize) -> bool {
66        self.range_error(fps, true).is_none()
67    }
68
69    /// Describes the first element that is outside the ranges in the spec, see
70    /// [`Time::is_valid`]. Hours are only checked if `check_hours` is set.
71    pub(crate) fn range_error(&self, fps: usize, check_hours: bool) -> Option<String> {
72        let formatted = self.hhmmssff();
73        if check_hours && self.hours > 23 {
74            Some(format!("{formatted} has hours {} above 23", self.hours))
75        } else if self.minutes > 59 {
76            Some(format!("{formatted} has minutes {} above 59", self.minutes))
77        } else if self.seconds > 59 {
78            Some(format!("{formatted} has seconds {} above 59", self.seconds))
79        } else if usize::from(self.frames) >= fps {
80            Some(format!(
81                "{formatted} has frame {} above {} at {fps} frames per second",
82                self.frames,
83                fps.saturating_sub(1)
84            ))
85        } else {
86            None
87        }
88    }
89}
90
91impl fmt::Display for Time {
92    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93        write!(
94            f,
95            "{}:{}:{}/{})",
96            self.hours, self.minutes, self.seconds, self.frames
97        )
98    }
99}