1use crate::framerate::FrameRate;
2
3pub fn frames_to_components(total_frames: i64, rate: FrameRate) -> (u8, u8, u8, u8) {
4 let nom = rate.nominal();
5
6 if !rate.is_drop_frame() {
7 return non_drop_decompose(total_frames, nom);
8 }
9
10 let drop = rate.drop_count();
11 let frames_per_min = nom * 60 - drop;
12 let frames_per_10min = frames_per_min * 10 + drop;
13
14 let abs_frames = if total_frames < 0 {
15 let day_frames = frames_per_10min * 6 * 24;
16 ((total_frames % day_frames as i64) + day_frames as i64) as u64
17 } else {
18 total_frames as u64
19 };
20
21 let first_minute_frames = (nom * 60) as u64;
22
23 let ten_min_blocks = abs_frames / frames_per_10min as u64;
24 let remainder = abs_frames % frames_per_10min as u64;
25
26 let (minutes_in_block, frames_in_block) = if remainder < first_minute_frames {
27 (0u64, remainder)
28 } else {
29 let adjusted = remainder - first_minute_frames;
30 let mins = 1 + adjusted / frames_per_min as u64;
31 let fr = adjusted % frames_per_min as u64 + drop as u64;
32 (mins, fr)
33 };
34
35 let total_minutes = ten_min_blocks * 10 + minutes_in_block;
36 let hours = (total_minutes / 60) % 24;
37 let minutes = total_minutes % 60;
38 let seconds = frames_in_block / nom as u64;
39 let frames = frames_in_block % nom as u64;
40
41 (hours as u8, minutes as u8, seconds as u8, frames as u8)
42}
43
44pub fn components_to_frames(h: u8, m: u8, s: u8, f: u8, rate: FrameRate) -> i64 {
45 let nom = rate.nominal();
46
47 let nominal_frames =
48 h as i64 * 3600 * nom as i64
49 + m as i64 * 60 * nom as i64
50 + s as i64 * nom as i64
51 + f as i64;
52
53 if !rate.is_drop_frame() {
54 return nominal_frames;
55 }
56
57 let drop = rate.drop_count() as i64;
58 let total_minutes = h as i64 * 60 + m as i64;
59 let drop_adjustment = drop * (total_minutes - total_minutes / 10);
60
61 nominal_frames - drop_adjustment
62}
63
64fn non_drop_decompose(total_frames: i64, nom: u32) -> (u8, u8, u8, u8) {
65 let fps = nom as u64;
66 let day_frames = fps * 86400;
67
68 let abs_frames = if total_frames < 0 {
69 ((total_frames % day_frames as i64) + day_frames as i64) as u64
70 } else {
71 total_frames as u64
72 };
73
74 let frames = abs_frames % fps;
75 let total_secs = abs_frames / fps;
76 let seconds = total_secs % 60;
77 let total_mins = total_secs / 60;
78 let minutes = total_mins % 60;
79 let hours = (total_mins / 60) % 24;
80
81 (hours as u8, minutes as u8, seconds as u8, frames as u8)
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn non_drop_zero() {
90 assert_eq!(frames_to_components(0, FrameRate::Fps24), (0, 0, 0, 0));
91 }
92
93 #[test]
94 fn non_drop_one_second() {
95 assert_eq!(frames_to_components(24, FrameRate::Fps24), (0, 0, 1, 0));
96 }
97
98 #[test]
99 fn non_drop_one_hour() {
100 assert_eq!(
101 frames_to_components(86400, FrameRate::Fps24),
102 (1, 0, 0, 0)
103 );
104 }
105
106 #[test]
107 fn non_drop_roundtrip() {
108 for frames in [0, 1, 12, 24, 1439, 86400, 172800, 2073599] {
109 let (h, m, s, f) = frames_to_components(frames, FrameRate::Fps24);
110 assert_eq!(
111 components_to_frames(h, m, s, f, FrameRate::Fps24),
112 frames
113 );
114 }
115 }
116
117 #[test]
118 fn drop_frame_minute_boundary_29_97() {
119 assert_eq!(
121 frames_to_components(1799, FrameRate::Fps29_97Df),
122 (0, 0, 59, 29)
123 );
124 assert_eq!(
126 frames_to_components(1800, FrameRate::Fps29_97Df),
127 (0, 1, 0, 2)
128 );
129 }
130
131 #[test]
132 fn drop_frame_10th_minute_29_97() {
133 let ten_min_frames = 17982i64;
136 assert_eq!(
137 frames_to_components(ten_min_frames, FrameRate::Fps29_97Df),
138 (0, 10, 0, 0)
139 );
140 }
141
142 #[test]
143 fn drop_frame_roundtrip_29_97() {
144 for frames in [0, 1, 2, 1798, 1799, 1800, 1801, 1802, 17982, 17983, 107892] {
145 let (h, m, s, f) = frames_to_components(frames, FrameRate::Fps29_97Df);
146 let back = components_to_frames(h, m, s, f, FrameRate::Fps29_97Df);
147 assert_eq!(back, frames, "roundtrip failed at frame {frames}: ({h}:{m}:{s};{f})");
148 }
149 }
150
151 #[test]
152 fn drop_frame_59_94_minute_boundary() {
153 assert_eq!(
156 frames_to_components(3599, FrameRate::Fps59_94Df),
157 (0, 0, 59, 59)
158 );
159 assert_eq!(
161 frames_to_components(3600, FrameRate::Fps59_94Df),
162 (0, 1, 0, 4)
163 );
164 }
165
166 #[test]
167 fn drop_frame_59_94_roundtrip() {
168 for frames in [0, 4, 3599, 3600, 3604, 35964, 35968] {
169 let (h, m, s, f) = frames_to_components(frames, FrameRate::Fps59_94Df);
170 let back = components_to_frames(h, m, s, f, FrameRate::Fps59_94Df);
171 assert_eq!(back, frames, "59.94DF roundtrip failed at frame {frames}");
172 }
173 }
174
175 #[test]
176 fn negative_wraps_to_24h() {
177 let (h, _, _, _) = frames_to_components(-24, FrameRate::Fps24);
178 assert_eq!(h, 23);
179 }
180}