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