1#![allow(dead_code)]
6#![allow(clippy::cast_precision_loss)]
7
8use crate::{FrameRate, FrameRateInfo, Timecode, TimecodeError};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct VitcParseResult {
13 pub timecode: Option<Timecode>,
15 pub crc_ok: bool,
17 pub raw_nibbles: Vec<u8>,
19}
20
21#[derive(Debug, Clone)]
25pub struct VitcParser {
26 frame_rate: FrameRate,
27 clock_period_samples: usize,
28 sync_threshold: f32,
29}
30
31impl VitcParser {
32 pub fn new(frame_rate: FrameRate, pixels_per_line: usize) -> Self {
34 let clock_period_samples = pixels_per_line / 90;
36 Self {
37 frame_rate,
38 clock_period_samples: clock_period_samples.max(1),
39 sync_threshold: 0.5,
40 }
41 }
42
43 pub fn set_sync_threshold(&mut self, threshold: f32) {
45 self.sync_threshold = threshold.clamp(0.0, 1.0);
46 }
47
48 pub fn parse_line(&self, pixels: &[f32]) -> VitcParseResult {
50 if pixels.len() < 90 {
51 return VitcParseResult {
52 timecode: None,
53 crc_ok: false,
54 raw_nibbles: Vec::new(),
55 };
56 }
57
58 let bits = self.sample_bits(pixels);
59 if bits.len() < 90 {
60 return VitcParseResult {
61 timecode: None,
62 crc_ok: false,
63 raw_nibbles: Vec::new(),
64 };
65 }
66
67 let sync_ok = bits[0] == 1 && bits[1] == 0 && bits[88] == 0 && bits[89] == 1;
69 if !sync_ok {
70 return VitcParseResult {
71 timecode: None,
72 crc_ok: false,
73 raw_nibbles: Vec::new(),
74 };
75 }
76
77 let mut nibbles = Vec::with_capacity(16);
79 let mut crc_accum: u8 = 0;
80 for group in 0..8 {
81 let base = 2 + group * 9;
82 let lo_nibble =
83 bits[base] | (bits[base + 1] << 1) | (bits[base + 2] << 2) | (bits[base + 3] << 3);
84 let hi_nibble = bits[base + 4]
85 | (bits[base + 5] << 1)
86 | (bits[base + 6] << 2)
87 | (bits[base + 7] << 3);
88 let _group_crc = bits[base + 8];
90 nibbles.push(lo_nibble);
91 nibbles.push(hi_nibble);
92 crc_accum ^= lo_nibble ^ hi_nibble;
93 }
94
95 let crc_ok = crc_accum == 0;
96
97 let frames_units = nibbles[0] & 0x0F;
103 let frames_tens = nibbles[1] & 0x03;
104 let seconds_units = nibbles[2] & 0x0F;
105 let seconds_tens = nibbles[3] & 0x07;
106 let minutes_units = nibbles[4] & 0x0F;
107 let minutes_tens = nibbles[5] & 0x07;
108 let hours_units = nibbles[6] & 0x0F;
109 let hours_tens = nibbles[7] & 0x03;
110
111 let frames = frames_tens * 10 + frames_units;
112 let seconds = seconds_tens * 10 + seconds_units;
113 let minutes = minutes_tens * 10 + minutes_units;
114 let hours = hours_tens * 10 + hours_units;
115
116 let timecode = Timecode::new(hours, minutes, seconds, frames, self.frame_rate).ok();
117
118 VitcParseResult {
119 timecode,
120 crc_ok,
121 raw_nibbles: nibbles,
122 }
123 }
124
125 fn sample_bits(&self, pixels: &[f32]) -> Vec<u8> {
127 let period = self.clock_period_samples.max(1);
128 let count = pixels.len() / period;
129 pixels
130 .chunks(period)
131 .take(count)
132 .map(|chunk| {
133 let avg = chunk.iter().sum::<f32>() / chunk.len() as f32;
134 if avg >= self.sync_threshold {
135 1u8
136 } else {
137 0u8
138 }
139 })
140 .collect()
141 }
142}
143
144#[derive(Debug, Clone)]
146pub struct LtcDecoder {
147 frame_rate: FrameRate,
148 sample_rate: u32,
149 buffer: Vec<f32>,
151 buffer_pos: usize,
152 half_period_samples: usize,
153 decoded_bits: Vec<u8>,
154 last_sample: f32,
155 bit_count: usize,
156}
157
158impl LtcDecoder {
159 pub fn new(frame_rate: FrameRate, sample_rate: u32) -> Self {
161 let fps = frame_rate.frames_per_second() as f64;
162 let bits_per_frame = 80usize;
163 let samples_per_frame = sample_rate as f64 / fps;
164 let samples_per_bit = samples_per_frame / bits_per_frame as f64;
165 let half_period = (samples_per_bit / 2.0).round() as usize;
166
167 Self {
168 frame_rate,
169 sample_rate,
170 buffer: vec![0.0; half_period * 2],
171 buffer_pos: 0,
172 half_period_samples: half_period.max(1),
173 decoded_bits: Vec::with_capacity(80),
174 last_sample: 0.0,
175 bit_count: 0,
176 }
177 }
178
179 pub fn feed(&mut self, samples: &[f32]) -> Vec<Timecode> {
181 let mut results = Vec::new();
182
183 for &s in samples {
184 let crossed = (s >= 0.0) != (self.last_sample >= 0.0);
186 self.last_sample = s;
187
188 if crossed {
189 self.bit_count += 1;
190 if self.bit_count.is_multiple_of(2) {
192 self.decoded_bits.push(1);
193 } else {
194 self.decoded_bits.push(0);
195 }
196
197 if self.decoded_bits.len() >= 80 {
198 if let Some(tc) = self.try_decode_frame() {
199 results.push(tc);
200 }
201 self.decoded_bits.clear();
202 self.bit_count = 0;
203 }
204 }
205
206 self.buffer[self.buffer_pos] = s;
207 self.buffer_pos = (self.buffer_pos + 1) % self.buffer.len();
208 }
209
210 results
211 }
212
213 fn try_decode_frame(&self) -> Option<Timecode> {
215 if self.decoded_bits.len() < 80 {
216 return None;
217 }
218
219 let sync_pattern = [0u8, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1];
221 let sync_ok = self.decoded_bits[64..80]
222 .iter()
223 .zip(sync_pattern.iter())
224 .all(|(a, b)| a == b);
225
226 if !sync_ok {
227 return None;
228 }
229
230 let frames = self.extract_bcd(0, 4, 2);
231 let seconds = self.extract_bcd(8, 4, 3);
232 let minutes = self.extract_bcd(24, 4, 3);
233 let hours = self.extract_bcd(40, 4, 2);
234
235 Timecode::new(hours, minutes, seconds, frames, self.frame_rate).ok()
236 }
237
238 fn extract_bcd(&self, offset: usize, unit_bits: usize, tens_bits: usize) -> u8 {
240 let mut units = 0u8;
241 for i in 0..unit_bits {
242 if offset + i < self.decoded_bits.len() {
243 units |= self.decoded_bits[offset + i] << i;
244 }
245 }
246 let mut tens = 0u8;
247 for i in 0..tens_bits {
248 let idx = offset + unit_bits + 1 + i; if idx < self.decoded_bits.len() {
250 tens |= self.decoded_bits[idx] << i;
251 }
252 }
253 tens * 10 + units
254 }
255
256 pub fn frame_rate(&self) -> FrameRate {
258 self.frame_rate
259 }
260
261 pub fn sample_rate(&self) -> u32 {
263 self.sample_rate
264 }
265}
266
267#[derive(Debug, Clone)]
269pub struct TimecodeValidator {
270 frame_rate: FrameRate,
271 last_tc: Option<Timecode>,
272 discontinuity_count: u32,
273 validate_continuity: bool,
274}
275
276impl TimecodeValidator {
277 pub fn new(frame_rate: FrameRate) -> Self {
279 Self {
280 frame_rate,
281 last_tc: None,
282 discontinuity_count: 0,
283 validate_continuity: true,
284 }
285 }
286
287 pub fn set_validate_continuity(&mut self, enabled: bool) {
289 self.validate_continuity = enabled;
290 }
291
292 pub fn validate(&self, tc: &Timecode) -> Result<(), TimecodeError> {
294 let fps = self.frame_rate.frames_per_second() as u8;
295 if tc.hours > 23 {
296 return Err(TimecodeError::InvalidHours);
297 }
298 if tc.minutes > 59 {
299 return Err(TimecodeError::InvalidMinutes);
300 }
301 if tc.seconds > 59 {
302 return Err(TimecodeError::InvalidSeconds);
303 }
304 if tc.frames >= fps {
305 return Err(TimecodeError::InvalidFrames);
306 }
307 if self.frame_rate.is_drop_frame()
308 && tc.seconds == 0
309 && tc.frames < 2
310 && !tc.minutes.is_multiple_of(10)
311 {
312 return Err(TimecodeError::InvalidDropFrame);
313 }
314 Ok(())
315 }
316
317 pub fn validate_sequence(&mut self, tc: Timecode) -> Result<bool, TimecodeError> {
319 self.validate(&tc)?;
320
321 let is_continuous = if let Some(ref last) = self.last_tc {
322 if self.validate_continuity {
323 let expected = last.to_frames() + 1;
324 tc.to_frames() == expected
325 } else {
326 true
327 }
328 } else {
329 true
330 };
331
332 if !is_continuous {
333 self.discontinuity_count += 1;
334 }
335
336 self.last_tc = Some(tc);
337 Ok(is_continuous)
338 }
339
340 pub fn discontinuity_count(&self) -> u32 {
342 self.discontinuity_count
343 }
344
345 pub fn reset(&mut self) {
347 self.last_tc = None;
348 self.discontinuity_count = 0;
349 }
350
351 pub fn last_timecode(&self) -> Option<&Timecode> {
353 self.last_tc.as_ref()
354 }
355}
356
357pub fn parse_timecode_string(s: &str, frame_rate: FrameRate) -> Result<Timecode, TimecodeError> {
359 let bytes = s.as_bytes();
360 if bytes.len() < 11 {
361 return Err(TimecodeError::InvalidConfiguration);
362 }
363
364 let parse_two = |b: &[u8]| -> Result<u8, TimecodeError> {
365 if b.len() < 2 {
366 return Err(TimecodeError::InvalidConfiguration);
367 }
368 let hi = (b[0] as char)
369 .to_digit(10)
370 .ok_or(TimecodeError::InvalidConfiguration)? as u8;
371 let lo = (b[1] as char)
372 .to_digit(10)
373 .ok_or(TimecodeError::InvalidConfiguration)? as u8;
374 Ok(hi * 10 + lo)
375 };
376
377 let hours = parse_two(&bytes[0..2])?;
378 if bytes[2] != b':' {
379 return Err(TimecodeError::InvalidConfiguration);
380 }
381 let minutes = parse_two(&bytes[3..5])?;
382 if bytes[5] != b':' {
383 return Err(TimecodeError::InvalidConfiguration);
384 }
385 let seconds = parse_two(&bytes[6..8])?;
386 let sep = bytes[8];
387 if sep != b':' && sep != b';' {
388 return Err(TimecodeError::InvalidConfiguration);
389 }
390 let frames = parse_two(&bytes[9..11])?;
391
392 Timecode::new(hours, minutes, seconds, frames, frame_rate)
393}
394
395#[derive(Debug, Clone, Copy, PartialEq, Eq)]
397pub enum VitcLine {
398 Line14,
400 Line16,
402 Line19,
404 Line21,
406 Custom(u16),
408}
409
410impl VitcLine {
411 pub fn line_number(&self) -> u16 {
413 match self {
414 VitcLine::Line14 => 14,
415 VitcLine::Line16 => 16,
416 VitcLine::Line19 => 19,
417 VitcLine::Line21 => 21,
418 VitcLine::Custom(n) => *n,
419 }
420 }
421}
422
423#[derive(Debug, Clone)]
425pub struct DecodedTimecode {
426 pub timecode: Timecode,
428 pub frame_rate_info: FrameRateInfo,
430 pub confidence: f32,
432 pub source: TimecodeSource,
434}
435
436#[derive(Debug, Clone, Copy, PartialEq, Eq)]
438pub enum TimecodeSource {
439 Ltc,
441 Vitc,
443 Mtc,
445 Metadata,
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452
453 #[test]
454 fn test_parse_timecode_string_25fps() {
455 let tc =
456 parse_timecode_string("01:02:03:04", FrameRate::Fps25).expect("valid timecode string");
457 assert_eq!(tc.hours, 1);
458 assert_eq!(tc.minutes, 2);
459 assert_eq!(tc.seconds, 3);
460 assert_eq!(tc.frames, 4);
461 }
462
463 #[test]
464 fn test_parse_timecode_string_drop_frame() {
465 let tc = parse_timecode_string("01:02:03;04", FrameRate::Fps2997DF)
466 .expect("valid timecode string");
467 assert_eq!(tc.hours, 1);
468 assert_eq!(tc.seconds, 3);
469 assert_eq!(tc.frames, 4);
470 }
471
472 #[test]
473 fn test_parse_timecode_string_invalid() {
474 assert!(parse_timecode_string("bad", FrameRate::Fps25).is_err());
475 assert!(parse_timecode_string("01:02:03X04", FrameRate::Fps25).is_err());
476 }
477
478 #[test]
479 fn test_vitc_parser_new() {
480 let parser = VitcParser::new(FrameRate::Fps25, 720);
481 assert_eq!(parser.frame_rate, FrameRate::Fps25);
482 assert!(parser.clock_period_samples >= 1);
483 }
484
485 #[test]
486 fn test_vitc_parser_short_line() {
487 let parser = VitcParser::new(FrameRate::Fps25, 720);
488 let short = vec![0.5f32; 10];
489 let result = parser.parse_line(&short);
490 assert!(result.timecode.is_none());
491 assert!(!result.crc_ok);
492 }
493
494 #[test]
495 fn test_vitc_parser_all_zeros() {
496 let parser = VitcParser::new(FrameRate::Fps25, 720);
497 let pixels = vec![0.0f32; 720];
498 let result = parser.parse_line(&pixels);
499 assert!(result.timecode.is_none());
501 }
502
503 #[test]
504 fn test_ltc_decoder_new() {
505 let dec = LtcDecoder::new(FrameRate::Fps25, 48000);
506 assert_eq!(dec.frame_rate(), FrameRate::Fps25);
507 assert_eq!(dec.sample_rate(), 48000);
508 }
509
510 #[test]
511 fn test_ltc_decoder_feed_silence() {
512 let mut dec = LtcDecoder::new(FrameRate::Fps25, 48000);
513 let silence = vec![0.0f32; 48000];
514 let results = dec.feed(&silence);
515 assert!(results.is_empty());
517 }
518
519 #[test]
520 fn test_timecode_validator_valid() {
521 let validator = TimecodeValidator::new(FrameRate::Fps25);
522 let tc = Timecode::new(1, 2, 3, 4, FrameRate::Fps25).expect("valid timecode");
523 assert!(validator.validate(&tc).is_ok());
524 }
525
526 #[test]
527 fn test_timecode_validator_invalid_frames() {
528 let validator = TimecodeValidator::new(FrameRate::Fps25);
529 let tc = Timecode::from_raw_fields(0, 0, 0, 30, 25, false, 0); assert!(validator.validate(&tc).is_err());
531 }
532
533 #[test]
534 fn test_timecode_validator_sequence_continuity() {
535 let mut validator = TimecodeValidator::new(FrameRate::Fps25);
536 let tc1 = Timecode::new(0, 0, 0, 0, FrameRate::Fps25).expect("valid timecode");
537 let tc2 = Timecode::new(0, 0, 0, 1, FrameRate::Fps25).expect("valid timecode");
538 assert!(validator
539 .validate_sequence(tc1)
540 .expect("validation should succeed"));
541 assert!(validator
542 .validate_sequence(tc2)
543 .expect("validation should succeed"));
544 assert_eq!(validator.discontinuity_count(), 0);
545 }
546
547 #[test]
548 fn test_timecode_validator_sequence_discontinuity() {
549 let mut validator = TimecodeValidator::new(FrameRate::Fps25);
550 let tc1 = Timecode::new(0, 0, 0, 0, FrameRate::Fps25).expect("valid timecode");
551 let tc2 = Timecode::new(0, 0, 1, 5, FrameRate::Fps25).expect("valid timecode");
552 validator
553 .validate_sequence(tc1)
554 .expect("validation should succeed");
555 let cont = validator
556 .validate_sequence(tc2)
557 .expect("validation should succeed");
558 assert!(!cont);
559 assert_eq!(validator.discontinuity_count(), 1);
560 }
561
562 #[test]
563 fn test_timecode_validator_reset() {
564 let mut validator = TimecodeValidator::new(FrameRate::Fps25);
565 let tc = Timecode::new(0, 0, 1, 5, FrameRate::Fps25).expect("valid timecode");
566 validator
567 .validate_sequence(tc)
568 .expect("validation should succeed");
569 validator.reset();
570 assert_eq!(validator.discontinuity_count(), 0);
571 assert!(validator.last_timecode().is_none());
572 }
573
574 #[test]
575 fn test_vitc_line_numbers() {
576 assert_eq!(VitcLine::Line14.line_number(), 14);
577 assert_eq!(VitcLine::Line19.line_number(), 19);
578 assert_eq!(VitcLine::Custom(22).line_number(), 22);
579 }
580
581 #[test]
582 fn test_timecode_source_eq() {
583 assert_eq!(TimecodeSource::Ltc, TimecodeSource::Ltc);
584 assert_ne!(TimecodeSource::Ltc, TimecodeSource::Vitc);
585 }
586}