1use std::collections::HashMap;
11use std::sync::OnceLock;
12
13use super::constants::*;
14use super::{VideoStandard, VitcWriterConfig};
15use crate::{FrameRate, Timecode, TimecodeError};
16
17#[derive(Debug, Clone)]
25pub struct VitcPattern {
26 pub scan_lines: Vec<u16>,
28 pub bits_per_sample: u8,
30}
31
32#[derive(Debug)]
34pub struct VitcPatternCache {
35 pub crc_table: [u8; 256],
37 pub patterns: HashMap<u8, VitcPattern>,
39}
40
41fn build_crc_table() -> [u8; 256] {
43 let mut table = [0u8; 256];
44 for (i, entry) in table.iter_mut().enumerate() {
45 let mut byte = i as u8;
46 for _ in 0..8 {
47 let feedback = (byte & 0x80) != 0;
48 byte <<= 1;
49 if feedback {
50 byte ^= 0x07;
51 }
52 }
53 *entry = byte;
54 }
55 table
56}
57
58pub fn get_vitc_cache() -> &'static VitcPatternCache {
63 static CACHE: OnceLock<VitcPatternCache> = OnceLock::new();
64 CACHE.get_or_init(|| {
65 let crc_table = build_crc_table();
66
67 let mut patterns = HashMap::new();
68
69 patterns.insert(
71 24u8,
72 VitcPattern {
73 scan_lines: vec![14, 16],
74 bits_per_sample: 2,
75 },
76 );
77
78 patterns.insert(
80 25u8,
81 VitcPattern {
82 scan_lines: vec![19, 21],
83 bits_per_sample: 2,
84 },
85 );
86
87 patterns.insert(
89 30u8,
90 VitcPattern {
91 scan_lines: vec![14, 16],
92 bits_per_sample: 2,
93 },
94 );
95
96 patterns.insert(
98 50u8,
99 VitcPattern {
100 scan_lines: vec![7, 9, 320, 322],
101 bits_per_sample: 2,
102 },
103 );
104
105 patterns.insert(
107 60u8,
108 VitcPattern {
109 scan_lines: vec![7, 9, 270, 272],
110 bits_per_sample: 2,
111 },
112 );
113
114 VitcPatternCache {
115 crc_table,
116 patterns,
117 }
118 })
119}
120
121pub struct VitcEncoder {
123 #[allow(dead_code)]
125 config: VitcWriterConfig,
126 current_field: u8,
128}
129
130impl VitcEncoder {
131 pub fn new(config: VitcWriterConfig) -> Self {
133 VitcEncoder {
134 config,
135 current_field: 1,
136 }
137 }
138
139 pub fn encode_line(
141 &mut self,
142 timecode: &Timecode,
143 field: u8,
144 ) -> Result<Vec<u8>, TimecodeError> {
145 let bits = self.timecode_to_bits(timecode, field)?;
147
148 let pixels = self.bits_to_pixels(&bits);
150
151 Ok(pixels)
152 }
153
154 fn timecode_to_bits(
156 &self,
157 timecode: &Timecode,
158 field: u8,
159 ) -> Result<[bool; BITS_PER_LINE], TimecodeError> {
160 let mut bits = [false; BITS_PER_LINE];
161
162 bits[0] = true;
164 bits[1] = true;
165
166 let mut data_bits = [false; DATA_BITS];
168
169 let frame_units = timecode.frames % 10;
171 let frame_tens = timecode.frames / 10;
172 let second_units = timecode.seconds % 10;
173 let second_tens = timecode.seconds / 10;
174 let minute_units = timecode.minutes % 10;
175 let minute_tens = timecode.minutes / 10;
176 let hour_units = timecode.hours % 10;
177 let hour_tens = timecode.hours / 10;
178
179 self.encode_bcd(&mut data_bits, 0, frame_units);
181
182 self.encode_nibble(&mut data_bits, 4, (timecode.user_bits & 0xF) as u8);
184
185 self.encode_bcd(&mut data_bits, 8, frame_tens);
187
188 data_bits[10] = timecode.frame_rate.drop_frame;
190
191 data_bits[11] = false;
193
194 self.encode_nibble(&mut data_bits, 12, ((timecode.user_bits >> 4) & 0xF) as u8);
196
197 self.encode_bcd(&mut data_bits, 16, second_units);
199
200 self.encode_nibble(&mut data_bits, 20, ((timecode.user_bits >> 8) & 0xF) as u8);
202
203 self.encode_bcd(&mut data_bits, 24, second_tens);
205
206 data_bits[27] = field == 2;
208
209 self.encode_nibble(&mut data_bits, 28, ((timecode.user_bits >> 12) & 0xF) as u8);
211
212 self.encode_bcd(&mut data_bits, 32, minute_units);
214
215 self.encode_nibble(&mut data_bits, 36, ((timecode.user_bits >> 16) & 0xF) as u8);
217
218 self.encode_bcd(&mut data_bits, 40, minute_tens);
220
221 data_bits[43] = false;
223
224 self.encode_nibble(&mut data_bits, 44, ((timecode.user_bits >> 20) & 0xF) as u8);
226
227 self.encode_bcd(&mut data_bits, 48, hour_units);
229
230 self.encode_nibble(&mut data_bits, 52, ((timecode.user_bits >> 24) & 0xF) as u8);
232
233 self.encode_bcd(&mut data_bits, 56, hour_tens);
235
236 data_bits[58] = false;
238
239 self.encode_nibble(&mut data_bits, 59, ((timecode.user_bits >> 28) & 0xF) as u8);
241
242 let crc = self.calculate_crc(&data_bits[0..72]);
244 for i in 0..8 {
245 data_bits[74 + i] = (crc & (1 << i)) != 0;
246 }
247
248 bits[SYNC_START_BITS..(DATA_BITS + SYNC_START_BITS)]
250 .copy_from_slice(&data_bits[..DATA_BITS]);
251
252 bits[84] = false;
254 bits[85] = false;
255 bits[86] = true;
256 bits[87] = true;
257 bits[88] = true;
258 bits[89] = true;
259
260 Ok(bits)
261 }
262
263 fn encode_bcd(&self, bits: &mut [bool; DATA_BITS], start: usize, value: u8) {
265 for i in 0..4 {
266 if start + i < DATA_BITS {
267 bits[start + i] = (value & (1 << i)) != 0;
268 }
269 }
270 }
271
272 fn encode_nibble(&self, bits: &mut [bool; DATA_BITS], start: usize, value: u8) {
274 for i in 0..4 {
275 if start + i < DATA_BITS {
276 bits[start + i] = (value & (1 << i)) != 0;
277 }
278 }
279 }
280
281 fn calculate_crc(&self, bits: &[bool]) -> u8 {
283 let mut crc = 0u8;
284
285 for &bit in bits {
286 let feedback = ((crc & 0x80) != 0) ^ bit;
287 crc <<= 1;
288 if feedback {
289 crc ^= 0x07; }
291 }
292
293 crc
294 }
295
296 fn bits_to_pixels(&self, bits: &[bool; BITS_PER_LINE]) -> Vec<u8> {
298 let mut pixels = Vec::with_capacity(BITS_PER_LINE * PIXELS_PER_BIT);
299
300 for &bit in bits {
301 let level = if bit { WHITE_LEVEL } else { BLACK_LEVEL };
302
303 for _ in 0..PIXELS_PER_BIT {
305 pixels.push(level);
306 }
307 }
308
309 pixels
310 }
311
312 pub fn reset(&mut self) {
314 self.current_field = 1;
315 }
316
317 pub fn set_field(&mut self, field: u8) {
319 self.current_field = field;
320 }
321
322 pub fn field(&self) -> u8 {
324 self.current_field
325 }
326}
327
328pub struct MultiLineVitcWriter {
330 encoder: VitcEncoder,
332 lines: Vec<u16>,
334}
335
336impl MultiLineVitcWriter {
337 pub fn new(config: VitcWriterConfig) -> Self {
339 let lines = config.scan_lines.clone();
340 MultiLineVitcWriter {
341 encoder: VitcEncoder::new(config),
342 lines,
343 }
344 }
345
346 pub fn encode_all_lines(
348 &mut self,
349 timecode: &Timecode,
350 field: u8,
351 ) -> Result<Vec<(u16, Vec<u8>)>, TimecodeError> {
352 let mut results = Vec::new();
353
354 for &line in &self.lines {
355 let pixels = self.encoder.encode_line(timecode, field)?;
356 results.push((line, pixels));
357 }
358
359 Ok(results)
360 }
361}
362
363pub struct VitcLineBuffer {
365 #[allow(dead_code)]
367 video_standard: VideoStandard,
368 lines: Vec<(u16, u8, Vec<u8>)>, }
371
372impl VitcLineBuffer {
373 pub fn new(video_standard: VideoStandard) -> Self {
375 VitcLineBuffer {
376 video_standard,
377 lines: Vec::new(),
378 }
379 }
380
381 pub fn add_line(&mut self, line_number: u16, field: u8, pixels: Vec<u8>) {
383 self.lines
385 .retain(|(ln, f, _)| !(*ln == line_number && *f == field));
386
387 self.lines.push((line_number, field, pixels));
388 }
389
390 pub fn get_field_lines(&self, field: u8) -> Vec<(u16, &[u8])> {
392 self.lines
393 .iter()
394 .filter(|(_, f, _)| *f == field)
395 .map(|(ln, _, pixels)| (*ln, pixels.as_slice()))
396 .collect()
397 }
398
399 pub fn clear(&mut self) {
401 self.lines.clear();
402 }
403
404 pub fn line_count(&self) -> usize {
406 self.lines.len()
407 }
408}
409
410pub struct PixelLevelAdjuster {
412 black_level: u8,
414 white_level: u8,
416}
417
418impl PixelLevelAdjuster {
419 pub fn new(video_standard: VideoStandard) -> Self {
421 match video_standard {
422 VideoStandard::Ntsc => PixelLevelAdjuster {
423 black_level: 16,
424 white_level: 235,
425 },
426 VideoStandard::Pal => PixelLevelAdjuster {
427 black_level: 16,
428 white_level: 235,
429 },
430 }
431 }
432
433 pub fn with_levels(black_level: u8, white_level: u8) -> Self {
435 PixelLevelAdjuster {
436 black_level,
437 white_level,
438 }
439 }
440
441 pub fn bit_to_pixel(&self, bit: bool) -> u8 {
443 if bit {
444 self.white_level
445 } else {
446 self.black_level
447 }
448 }
449
450 pub fn black_level(&self) -> u8 {
452 self.black_level
453 }
454
455 pub fn white_level(&self) -> u8 {
457 self.white_level
458 }
459}
460
461pub struct RiseTimeShaper {
463 rise_time_pixels: usize,
465}
466
467impl RiseTimeShaper {
468 pub fn new(rise_time_pixels: usize) -> Self {
470 RiseTimeShaper {
471 rise_time_pixels: rise_time_pixels.max(1),
472 }
473 }
474
475 pub fn shape_pixels(&self, pixels: &[u8]) -> Vec<u8> {
477 let mut shaped = Vec::with_capacity(pixels.len());
478
479 if pixels.is_empty() {
480 return shaped;
481 }
482
483 shaped.push(pixels[0]);
484
485 for i in 1..pixels.len() {
486 let current = pixels[i];
487 let prev = pixels[i - 1];
488
489 if current != prev {
490 let diff = current as i16 - prev as i16;
492 for j in 0..self.rise_time_pixels.min(pixels.len() - i) {
493 let progress = (j + 1) as f32 / self.rise_time_pixels as f32;
494 let value = prev as f32 + diff as f32 * progress;
495 shaped.push(value as u8);
496 }
497 for _ in 0..self.rise_time_pixels.min(pixels.len() - i) {
499 if i < pixels.len() {
500 shaped.push(current);
501 }
502 }
503 } else {
504 shaped.push(current);
505 }
506 }
507
508 shaped.truncate(pixels.len());
509 shaped
510 }
511}
512
513pub struct BlankingInserter {
515 blanking_level: u8,
517}
518
519impl BlankingInserter {
520 pub fn new(blanking_level: u8) -> Self {
522 BlankingInserter { blanking_level }
523 }
524
525 pub fn insert_blanking(
527 &self,
528 vitc_pixels: &[u8],
529 total_width: usize,
530 start_offset: usize,
531 ) -> Vec<u8> {
532 let mut full_line = vec![self.blanking_level; total_width];
533
534 let end_offset = (start_offset + vitc_pixels.len()).min(total_width);
536 for (i, &pixel) in vitc_pixels.iter().enumerate() {
537 if start_offset + i < end_offset {
538 full_line[start_offset + i] = pixel;
539 }
540 }
541
542 full_line
543 }
544}
545
546pub struct VitcFrameGenerator {
548 writer: MultiLineVitcWriter,
550 current_timecode: Option<Timecode>,
552 #[allow(dead_code)]
554 frame_rate: FrameRate,
555}
556
557impl VitcFrameGenerator {
558 pub fn new(config: VitcWriterConfig) -> Self {
560 let frame_rate = config.frame_rate;
561 VitcFrameGenerator {
562 writer: MultiLineVitcWriter::new(config),
563 current_timecode: None,
564 frame_rate,
565 }
566 }
567
568 pub fn set_timecode(&mut self, timecode: Timecode) {
570 self.current_timecode = Some(timecode);
571 }
572
573 pub fn generate_frame(&mut self) -> Result<Vec<(u16, u8, Vec<u8>)>, TimecodeError> {
575 if let Some(ref mut tc) = self.current_timecode {
576 let mut results = Vec::new();
577
578 let field1_lines = self.writer.encode_all_lines(tc, 1)?;
580 for (line, pixels) in field1_lines {
581 results.push((line, 1, pixels));
582 }
583
584 let field2_lines = self.writer.encode_all_lines(tc, 2)?;
586 for (line, pixels) in field2_lines {
587 results.push((line, 2, pixels));
588 }
589
590 tc.increment()?;
592
593 Ok(results)
594 } else {
595 Err(TimecodeError::InvalidConfiguration)
596 }
597 }
598
599 pub fn current_timecode(&self) -> Option<&Timecode> {
601 self.current_timecode.as_ref()
602 }
603}
604
605pub struct VitcUserBitsHelper;
607
608impl VitcUserBitsHelper {
609 pub fn validate_user_bits(user_bits: u32) -> bool {
611 let _ = user_bits;
613 true
614 }
615
616 pub fn extract_group(user_bits: u32, group: u8) -> u8 {
618 let shift = (group as u32) * 4;
619 ((user_bits >> shift) & 0xF) as u8
620 }
621
622 pub fn set_group(user_bits: u32, group: u8, value: u8) -> u32 {
624 let shift = (group as u32) * 4;
625 let mask = !(0xF << shift);
626 (user_bits & mask) | ((value as u32 & 0xF) << shift)
627 }
628}
629
630pub struct PixelPatternValidator;
632
633impl PixelPatternValidator {
634 pub fn validate_pattern(pixels: &[u8]) -> bool {
636 if pixels.len() < BITS_PER_LINE * PIXELS_PER_BIT {
637 return false;
638 }
639
640 for &pixel in pixels {
642 if !(16..=235).contains(&pixel) {
643 return false;
644 }
645 }
646
647 true
648 }
649
650 pub fn check_sync_pattern(pixels: &[u8]) -> bool {
652 if pixels.len() < 4 {
653 return false;
654 }
655
656 pixels[0] > 200 && pixels[1] > 200 && pixels[2] > 200 && pixels[3] > 200
658 }
659}
660
661#[cfg(test)]
662mod tests {
663 use super::*;
664
665 #[test]
666 fn test_encoder_creation() {
667 let config = VitcWriterConfig::default();
668 let encoder = VitcEncoder::new(config);
669 assert_eq!(encoder.field(), 1);
670 }
671
672 #[test]
673 fn test_encode_line() {
674 let config = VitcWriterConfig::default();
675 let mut encoder = VitcEncoder::new(config);
676
677 let timecode = Timecode::new(1, 2, 3, 4, FrameRate::Fps25).expect("valid timecode");
678 let pixels = encoder
679 .encode_line(&timecode, 1)
680 .expect("encode should succeed");
681
682 assert_eq!(pixels.len(), BITS_PER_LINE * PIXELS_PER_BIT);
683 }
684
685 #[test]
686 fn test_crc_calculation() {
687 let config = VitcWriterConfig::default();
688 let encoder = VitcEncoder::new(config);
689
690 let bits = [false; 72];
691 let crc = encoder.calculate_crc(&bits);
692 assert_eq!(crc, 0);
693 }
694
695 #[test]
696 fn test_pixel_level_adjuster() {
697 let adjuster = PixelLevelAdjuster::new(VideoStandard::Pal);
698 assert_eq!(adjuster.bit_to_pixel(false), 16);
699 assert_eq!(adjuster.bit_to_pixel(true), 235);
700 }
701
702 #[test]
703 fn test_user_bits_helper() {
704 let user_bits = 0x12345678u32;
705 assert_eq!(VitcUserBitsHelper::extract_group(user_bits, 0), 0x8);
706 assert_eq!(VitcUserBitsHelper::extract_group(user_bits, 1), 0x7);
707
708 let modified = VitcUserBitsHelper::set_group(user_bits, 0, 0xA);
709 assert_eq!(VitcUserBitsHelper::extract_group(modified, 0), 0xA);
710 }
711
712 #[test]
713 fn test_line_buffer() {
714 let mut buffer = VitcLineBuffer::new(VideoStandard::Pal);
715 buffer.add_line(19, 1, vec![16; 180]);
716 buffer.add_line(21, 1, vec![16; 180]);
717
718 assert_eq!(buffer.line_count(), 2);
719
720 let field1_lines = buffer.get_field_lines(1);
721 assert_eq!(field1_lines.len(), 2);
722 }
723
724 #[test]
727 fn test_vitc_cache_is_computed_on_first_call() {
728 let cache = get_vitc_cache();
729 assert_eq!(cache.crc_table[0], 0);
731 assert_eq!(cache.crc_table[1], 7);
732 }
733
734 #[test]
735 fn test_vitc_cache_second_call_returns_same_reference() {
736 let ptr1 = get_vitc_cache() as *const VitcPatternCache;
737 let ptr2 = get_vitc_cache() as *const VitcPatternCache;
738 assert_eq!(ptr1, ptr2, "OnceLock must return the same static reference");
739 }
740
741 #[test]
742 fn test_vitc_pattern_25fps_scan_lines() {
743 let cache = get_vitc_cache();
744 let pat = cache
745 .patterns
746 .get(&25)
747 .expect("25fps pattern must be present");
748 assert!(
749 pat.scan_lines.contains(&19),
750 "PAL VITC must include line 19"
751 );
752 assert!(
753 pat.scan_lines.contains(&21),
754 "PAL VITC must include line 21"
755 );
756 }
757
758 #[test]
759 fn test_vitc_pattern_2997fps_scan_lines() {
760 let cache = get_vitc_cache();
761 let pat = cache
763 .patterns
764 .get(&30)
765 .expect("30fps pattern must be present");
766 assert!(
767 !pat.scan_lines.is_empty(),
768 "30fps VITC must have at least one scan line"
769 );
770 }
771
772 #[test]
773 fn test_vitc_crc_table_256_entries() {
774 let cache = get_vitc_cache();
775 assert_eq!(cache.crc_table.len(), 256);
776 }
777
778 #[test]
779 fn test_vitc_crc_table_correct_polynomial() {
780 let cache = get_vitc_cache();
785 assert_eq!(cache.crc_table[0], 0);
786 assert_eq!(cache.crc_table[1], 7);
787 assert_eq!(cache.crc_table[128], 0x89);
788 }
789
790 #[test]
791 fn test_vitc_pattern_common_rates_present() {
792 let cache = get_vitc_cache();
793 for fps in [24u8, 25, 30, 50, 60] {
795 assert!(
796 cache.patterns.contains_key(&fps),
797 "pattern for {}fps must be in cache",
798 fps
799 );
800 }
801 }
802}