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}