oximedia_timecode/
timecode_generator.rs1#![allow(dead_code)]
2use crate::{frame_rate_from_info, FrameRate, Timecode, TimecodeError};
10
11#[derive(Debug, Clone)]
19pub struct TimecodeGenerator {
20 current: Timecode,
23 pub running: bool,
25}
26
27impl TimecodeGenerator {
28 pub fn new(start: Timecode) -> Self {
37 Self {
38 current: start,
39 running: true,
40 }
41 }
42
43 pub fn at_midnight(frame_rate: FrameRate) -> Result<Self, TimecodeError> {
51 let tc = Timecode::new(0, 0, 0, 0, frame_rate)?;
52 Ok(Self::new(tc))
53 }
54
55 pub fn next(&mut self) -> Timecode {
64 let out = self.current;
65 if self.running {
66 let _ = self.current.increment();
68 }
69 out
70 }
71
72 pub fn peek(&self) -> Timecode {
74 self.current
75 }
76
77 pub fn reset(&mut self) -> Result<(), TimecodeError> {
84 let rate = frame_rate_from_info(&self.current.frame_rate);
85 self.current = Timecode::new(0, 0, 0, 0, rate)?;
86 Ok(())
87 }
88
89 pub fn reset_to(&mut self, tc: Timecode) {
93 self.current = tc;
94 }
95
96 pub fn seek(&mut self, tc: Timecode) {
98 self.current = tc;
99 }
100
101 pub fn skip_frames(&mut self, n: i64) -> Result<(), TimecodeError> {
111 let rate = frame_rate_from_info(&self.current.frame_rate);
112 let fps = self.current.frame_rate.fps as i64;
113 let frames_per_day = fps * 86_400;
114
115 let current_frames = self.current.to_frames() as i64;
116 let new_frames = if frames_per_day > 0 {
118 ((current_frames + n).rem_euclid(frames_per_day)) as u64
119 } else {
120 (current_frames + n).max(0) as u64
121 };
122
123 self.current = Timecode::from_frames(new_frames, rate)?;
124 Ok(())
125 }
126
127 pub fn start(&mut self) {
129 self.running = true;
130 }
131
132 pub fn stop(&mut self) {
134 self.running = false;
135 }
136
137 pub fn frame_rate(&self) -> FrameRate {
139 frame_rate_from_info(&self.current.frame_rate)
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 fn make_gen_25() -> TimecodeGenerator {
148 TimecodeGenerator::at_midnight(FrameRate::Fps25).expect("midnight ok")
149 }
150
151 #[test]
152 fn test_generator_starts_at_midnight() {
153 let gen = make_gen_25();
154 let tc = gen.peek();
155 assert_eq!(tc.hours, 0);
156 assert_eq!(tc.minutes, 0);
157 assert_eq!(tc.seconds, 0);
158 assert_eq!(tc.frames, 0);
159 }
160
161 #[test]
162 fn test_next_increments() {
163 let mut gen = make_gen_25();
164 let tc0 = gen.next();
165 let tc1 = gen.next();
166 assert_eq!(tc0.to_frames() + 1, tc1.to_frames());
167 }
168
169 #[test]
170 fn test_next_returns_current_before_increment() {
171 let mut gen = make_gen_25();
172 let peek = gen.peek();
173 let got = gen.next();
174 assert_eq!(peek, got);
175 }
176
177 #[test]
178 fn test_stop_freezes_position() {
179 let mut gen = make_gen_25();
180 gen.stop();
181 let a = gen.next();
182 let b = gen.next();
183 assert_eq!(a, b);
184 }
185
186 #[test]
187 fn test_start_resumes_after_stop() {
188 let mut gen = make_gen_25();
189 gen.stop();
190 let _ = gen.next();
191 gen.start();
192 let before = gen.peek().to_frames();
193 let _ = gen.next();
194 let after = gen.peek().to_frames();
195 assert_eq!(after, before + 1);
196 }
197
198 #[test]
199 fn test_reset_to_midnight() {
200 let mut gen = make_gen_25();
201 for _ in 0..100 {
203 let _ = gen.next();
204 }
205 gen.reset().expect("reset ok");
206 assert_eq!(gen.peek().to_frames(), 0);
207 }
208
209 #[test]
210 fn test_reset_to_arbitrary_tc() {
211 let mut gen = make_gen_25();
212 let target = Timecode::new(12, 34, 56, 10, FrameRate::Fps25).expect("valid");
213 gen.reset_to(target);
214 assert_eq!(gen.peek(), target);
215 }
216
217 #[test]
218 fn test_seek_alias() {
219 let mut gen = make_gen_25();
220 let target = Timecode::new(1, 0, 0, 0, FrameRate::Fps25).expect("valid");
221 gen.seek(target);
222 assert_eq!(gen.peek(), target);
223 }
224
225 #[test]
226 fn test_skip_forward() {
227 let mut gen = make_gen_25();
228 gen.skip_frames(100).expect("skip ok");
229 assert_eq!(gen.peek().to_frames(), 100);
230 }
231
232 #[test]
233 fn test_skip_backward_wraps() {
234 let mut gen = make_gen_25();
235 gen.skip_frames(-1).expect("skip ok");
237 let frames_per_day = 25u64 * 86_400;
238 assert_eq!(gen.peek().to_frames(), frames_per_day - 1);
239 }
240
241 #[test]
242 fn test_skip_forward_wraps_midnight() {
243 let mut gen = make_gen_25();
244 let frames_per_day = 25i64 * 86_400;
245 gen.skip_frames(frames_per_day).expect("skip ok");
247 assert_eq!(gen.peek().to_frames(), 0);
248 }
249
250 #[test]
251 fn test_drop_frame_generator_next_at_minute_boundary() {
252 let start = Timecode::new(0, 0, 59, 29, FrameRate::Fps2997DF).expect("valid");
254 let mut gen = TimecodeGenerator::new(start);
255 let tc = gen.next(); assert_eq!(tc.frames, 29);
257 let next = gen.next(); assert_eq!(next.minutes, 1);
259 assert_eq!(next.seconds, 0);
260 assert_eq!(next.frames, 2);
261 }
262}