1#![allow(
14 clippy::cast_possible_truncation,
15 clippy::cast_precision_loss,
16 clippy::cast_sign_loss,
17 dead_code,
18 clippy::pedantic
19)]
20
21pub mod burn_in;
22pub mod continuity;
23pub mod drop_frame;
24pub mod duration;
25pub mod frame_offset;
26pub mod frame_rate;
27pub mod ltc;
28pub mod ltc_encoder;
29pub mod ltc_parser;
30pub mod midi_timecode;
31pub mod reader;
32pub mod sync;
33pub mod sync_map;
34pub mod tc_calculator;
35pub mod tc_compare;
36pub mod tc_convert;
37pub mod tc_drift;
38pub mod tc_interpolate;
39pub mod tc_math;
40pub mod tc_metadata;
41pub mod tc_range;
42pub mod tc_smpte_ranges;
43pub mod tc_validator;
44pub mod timecode_calculator;
45pub mod timecode_format;
46pub mod timecode_range;
47pub mod vitc;
48
49use std::fmt;
50
51#[derive(Debug, Clone, Copy, PartialEq)]
53pub enum FrameRate {
54 Fps23976,
56 Fps24,
58 Fps25,
60 Fps2997DF,
62 Fps2997NDF,
64 Fps30,
66 Fps50,
68 Fps5994,
70 Fps60,
72}
73
74impl FrameRate {
75 pub fn as_float(&self) -> f64 {
77 match self {
78 FrameRate::Fps23976 => 23.976,
79 FrameRate::Fps24 => 24.0,
80 FrameRate::Fps25 => 25.0,
81 FrameRate::Fps2997DF | FrameRate::Fps2997NDF => 29.97,
82 FrameRate::Fps30 => 30.0,
83 FrameRate::Fps50 => 50.0,
84 FrameRate::Fps5994 => 59.94,
85 FrameRate::Fps60 => 60.0,
86 }
87 }
88
89 pub fn as_rational(&self) -> (u32, u32) {
91 match self {
92 FrameRate::Fps23976 => (24000, 1001),
93 FrameRate::Fps24 => (24, 1),
94 FrameRate::Fps25 => (25, 1),
95 FrameRate::Fps2997DF | FrameRate::Fps2997NDF => (30000, 1001),
96 FrameRate::Fps30 => (30, 1),
97 FrameRate::Fps50 => (50, 1),
98 FrameRate::Fps5994 => (60000, 1001),
99 FrameRate::Fps60 => (60, 1),
100 }
101 }
102
103 pub fn is_drop_frame(&self) -> bool {
105 matches!(self, FrameRate::Fps2997DF)
106 }
107
108 pub fn frames_per_second(&self) -> u32 {
110 match self {
111 FrameRate::Fps23976 => 24,
112 FrameRate::Fps24 => 24,
113 FrameRate::Fps25 => 25,
114 FrameRate::Fps2997DF | FrameRate::Fps2997NDF => 30,
115 FrameRate::Fps30 => 30,
116 FrameRate::Fps50 => 50,
117 FrameRate::Fps5994 => 60,
118 FrameRate::Fps60 => 60,
119 }
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
125pub struct Timecode {
126 pub hours: u8,
128 pub minutes: u8,
130 pub seconds: u8,
132 pub frames: u8,
134 pub frame_rate: FrameRateInfo,
136 pub user_bits: u32,
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
142pub struct FrameRateInfo {
143 pub fps: u8,
145 pub drop_frame: bool,
147}
148
149impl Timecode {
150 pub fn new(
152 hours: u8,
153 minutes: u8,
154 seconds: u8,
155 frames: u8,
156 frame_rate: FrameRate,
157 ) -> Result<Self, TimecodeError> {
158 let fps = frame_rate.frames_per_second() as u8;
159
160 if hours > 23 {
161 return Err(TimecodeError::InvalidHours);
162 }
163 if minutes > 59 {
164 return Err(TimecodeError::InvalidMinutes);
165 }
166 if seconds > 59 {
167 return Err(TimecodeError::InvalidSeconds);
168 }
169 if frames >= fps {
170 return Err(TimecodeError::InvalidFrames);
171 }
172
173 if frame_rate.is_drop_frame() {
175 if seconds == 0 && frames < 2 && !minutes.is_multiple_of(10) {
177 return Err(TimecodeError::InvalidDropFrame);
178 }
179 }
180
181 Ok(Timecode {
182 hours,
183 minutes,
184 seconds,
185 frames,
186 frame_rate: FrameRateInfo {
187 fps,
188 drop_frame: frame_rate.is_drop_frame(),
189 },
190 user_bits: 0,
191 })
192 }
193
194 pub fn with_user_bits(mut self, user_bits: u32) -> Self {
196 self.user_bits = user_bits;
197 self
198 }
199
200 pub fn to_frames(&self) -> u64 {
202 let fps = self.frame_rate.fps as u64;
203 let mut total = self.hours as u64 * 3600 * fps;
204 total += self.minutes as u64 * 60 * fps;
205 total += self.seconds as u64 * fps;
206 total += self.frames as u64;
207
208 if self.frame_rate.drop_frame {
210 let total_minutes = self.hours as u64 * 60 + self.minutes as u64;
212 let dropped_frames = 2 * (total_minutes - total_minutes / 10);
213 total -= dropped_frames;
214 }
215
216 total
217 }
218
219 pub fn from_frames(frames: u64, frame_rate: FrameRate) -> Result<Self, TimecodeError> {
221 let fps = frame_rate.frames_per_second() as u64;
222 let mut remaining = frames;
223
224 if frame_rate.is_drop_frame() {
226 let frames_per_minute = fps * 60 - 2;
228 let frames_per_10_minutes = frames_per_minute * 9 + fps * 60;
229
230 let ten_minute_blocks = remaining / frames_per_10_minutes;
231 remaining += ten_minute_blocks * 18;
232
233 let remaining_in_block = remaining % frames_per_10_minutes;
234 if remaining_in_block >= fps * 60 {
235 let extra_minutes = (remaining_in_block - fps * 60) / frames_per_minute;
236 remaining += (extra_minutes + 1) * 2;
237 }
238 }
239
240 let hours = (remaining / (fps * 3600)) as u8;
241 remaining %= fps * 3600;
242 let minutes = (remaining / (fps * 60)) as u8;
243 remaining %= fps * 60;
244 let seconds = (remaining / fps) as u8;
245 let frame = (remaining % fps) as u8;
246
247 Self::new(hours, minutes, seconds, frame, frame_rate)
248 }
249
250 pub fn increment(&mut self) -> Result<(), TimecodeError> {
252 self.frames += 1;
253
254 if self.frames >= self.frame_rate.fps {
255 self.frames = 0;
256 self.seconds += 1;
257
258 if self.seconds >= 60 {
259 self.seconds = 0;
260 self.minutes += 1;
261
262 if self.frame_rate.drop_frame && !self.minutes.is_multiple_of(10) {
264 self.frames = 2; }
266
267 if self.minutes >= 60 {
268 self.minutes = 0;
269 self.hours += 1;
270
271 if self.hours >= 24 {
272 self.hours = 0;
273 }
274 }
275 }
276 }
277
278 Ok(())
279 }
280
281 pub fn decrement(&mut self) -> Result<(), TimecodeError> {
283 if self.frames > 0 {
284 self.frames -= 1;
285
286 if self.frame_rate.drop_frame
288 && self.seconds == 0
289 && self.frames == 1
290 && !self.minutes.is_multiple_of(10)
291 {
292 self.frames = self.frame_rate.fps - 1;
293 if self.seconds > 0 {
294 self.seconds -= 1;
295 } else {
296 self.seconds = 59;
297 if self.minutes > 0 {
298 self.minutes -= 1;
299 } else {
300 self.minutes = 59;
301 if self.hours > 0 {
302 self.hours -= 1;
303 } else {
304 self.hours = 23;
305 }
306 }
307 }
308 }
309 } else if self.seconds > 0 {
310 self.seconds -= 1;
311 self.frames = self.frame_rate.fps - 1;
312 } else {
313 self.seconds = 59;
314 self.frames = self.frame_rate.fps - 1;
315
316 if self.minutes > 0 {
317 self.minutes -= 1;
318 } else {
319 self.minutes = 59;
320 if self.hours > 0 {
321 self.hours -= 1;
322 } else {
323 self.hours = 23;
324 }
325 }
326 }
327
328 Ok(())
329 }
330}
331
332impl fmt::Display for Timecode {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 let separator = if self.frame_rate.drop_frame { ';' } else { ':' };
335 write!(
336 f,
337 "{:02}:{:02}:{:02}{}{:02}",
338 self.hours, self.minutes, self.seconds, separator, self.frames
339 )
340 }
341}
342
343pub trait TimecodeReader {
345 fn read_timecode(&mut self) -> Result<Option<Timecode>, TimecodeError>;
347
348 fn frame_rate(&self) -> FrameRate;
350
351 fn is_synchronized(&self) -> bool;
353}
354
355pub trait TimecodeWriter {
357 fn write_timecode(&mut self, timecode: &Timecode) -> Result<(), TimecodeError>;
359
360 fn frame_rate(&self) -> FrameRate;
362
363 fn flush(&mut self) -> Result<(), TimecodeError>;
365}
366
367#[derive(Debug, Clone, PartialEq, Eq)]
369pub enum TimecodeError {
370 InvalidHours,
372 InvalidMinutes,
374 InvalidSeconds,
376 InvalidFrames,
378 InvalidDropFrame,
380 SyncNotFound,
382 CrcError,
384 BufferTooSmall,
386 InvalidConfiguration,
388 IoError(String),
390 NotSynchronized,
392}
393
394impl fmt::Display for TimecodeError {
395 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396 match self {
397 TimecodeError::InvalidHours => write!(f, "Invalid hours value"),
398 TimecodeError::InvalidMinutes => write!(f, "Invalid minutes value"),
399 TimecodeError::InvalidSeconds => write!(f, "Invalid seconds value"),
400 TimecodeError::InvalidFrames => write!(f, "Invalid frames value"),
401 TimecodeError::InvalidDropFrame => write!(f, "Invalid drop frame timecode"),
402 TimecodeError::SyncNotFound => write!(f, "Sync word not found"),
403 TimecodeError::CrcError => write!(f, "CRC error"),
404 TimecodeError::BufferTooSmall => write!(f, "Buffer too small"),
405 TimecodeError::InvalidConfiguration => write!(f, "Invalid configuration"),
406 TimecodeError::IoError(e) => write!(f, "IO error: {}", e),
407 TimecodeError::NotSynchronized => write!(f, "Not synchronized"),
408 }
409 }
410}
411
412impl std::error::Error for TimecodeError {}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 #[test]
419 fn test_timecode_creation() {
420 let tc = Timecode::new(1, 2, 3, 4, FrameRate::Fps25).expect("valid timecode");
421 assert_eq!(tc.hours, 1);
422 assert_eq!(tc.minutes, 2);
423 assert_eq!(tc.seconds, 3);
424 assert_eq!(tc.frames, 4);
425 }
426
427 #[test]
428 fn test_timecode_display() {
429 let tc = Timecode::new(1, 2, 3, 4, FrameRate::Fps25).expect("valid timecode");
430 assert_eq!(tc.to_string(), "01:02:03:04");
431
432 let tc_df = Timecode::new(1, 2, 3, 4, FrameRate::Fps2997DF).expect("valid timecode");
433 assert_eq!(tc_df.to_string(), "01:02:03;04");
434 }
435
436 #[test]
437 fn test_timecode_increment() {
438 let mut tc = Timecode::new(0, 0, 0, 24, FrameRate::Fps25).expect("valid timecode");
439 tc.increment().expect("increment should succeed");
440 assert_eq!(tc.frames, 0);
441 assert_eq!(tc.seconds, 1);
442 }
443
444 #[test]
445 fn test_frame_rate() {
446 assert_eq!(FrameRate::Fps25.as_float(), 25.0);
447 assert_eq!(FrameRate::Fps2997DF.as_float(), 29.97);
448 assert!(FrameRate::Fps2997DF.is_drop_frame());
449 assert!(!FrameRate::Fps2997NDF.is_drop_frame());
450 }
451}