use super::constants::*;
use super::{VideoStandard, VitcWriterConfig};
use crate::{FrameRate, Timecode, TimecodeError};
pub struct VitcEncoder {
#[allow(dead_code)]
config: VitcWriterConfig,
current_field: u8,
}
impl VitcEncoder {
pub fn new(config: VitcWriterConfig) -> Self {
VitcEncoder {
config,
current_field: 1,
}
}
pub fn encode_line(
&mut self,
timecode: &Timecode,
field: u8,
) -> Result<Vec<u8>, TimecodeError> {
let bits = self.timecode_to_bits(timecode, field)?;
let pixels = self.bits_to_pixels(&bits);
Ok(pixels)
}
fn timecode_to_bits(
&self,
timecode: &Timecode,
field: u8,
) -> Result<[bool; BITS_PER_LINE], TimecodeError> {
let mut bits = [false; BITS_PER_LINE];
bits[0] = true;
bits[1] = true;
let mut data_bits = [false; DATA_BITS];
let frame_units = timecode.frames % 10;
let frame_tens = timecode.frames / 10;
let second_units = timecode.seconds % 10;
let second_tens = timecode.seconds / 10;
let minute_units = timecode.minutes % 10;
let minute_tens = timecode.minutes / 10;
let hour_units = timecode.hours % 10;
let hour_tens = timecode.hours / 10;
self.encode_bcd(&mut data_bits, 0, frame_units);
self.encode_nibble(&mut data_bits, 4, (timecode.user_bits & 0xF) as u8);
self.encode_bcd(&mut data_bits, 8, frame_tens);
data_bits[10] = timecode.frame_rate.drop_frame;
data_bits[11] = false;
self.encode_nibble(&mut data_bits, 12, ((timecode.user_bits >> 4) & 0xF) as u8);
self.encode_bcd(&mut data_bits, 16, second_units);
self.encode_nibble(&mut data_bits, 20, ((timecode.user_bits >> 8) & 0xF) as u8);
self.encode_bcd(&mut data_bits, 24, second_tens);
data_bits[27] = field == 2;
self.encode_nibble(&mut data_bits, 28, ((timecode.user_bits >> 12) & 0xF) as u8);
self.encode_bcd(&mut data_bits, 32, minute_units);
self.encode_nibble(&mut data_bits, 36, ((timecode.user_bits >> 16) & 0xF) as u8);
self.encode_bcd(&mut data_bits, 40, minute_tens);
data_bits[43] = false;
self.encode_nibble(&mut data_bits, 44, ((timecode.user_bits >> 20) & 0xF) as u8);
self.encode_bcd(&mut data_bits, 48, hour_units);
self.encode_nibble(&mut data_bits, 52, ((timecode.user_bits >> 24) & 0xF) as u8);
self.encode_bcd(&mut data_bits, 56, hour_tens);
data_bits[58] = false;
self.encode_nibble(&mut data_bits, 59, ((timecode.user_bits >> 28) & 0xF) as u8);
let crc = self.calculate_crc(&data_bits[0..72]);
for i in 0..8 {
data_bits[74 + i] = (crc & (1 << i)) != 0;
}
bits[SYNC_START_BITS..(DATA_BITS + SYNC_START_BITS)]
.copy_from_slice(&data_bits[..DATA_BITS]);
bits[84] = false;
bits[85] = false;
bits[86] = true;
bits[87] = true;
bits[88] = true;
bits[89] = true;
Ok(bits)
}
fn encode_bcd(&self, bits: &mut [bool; DATA_BITS], start: usize, value: u8) {
for i in 0..4 {
if start + i < DATA_BITS {
bits[start + i] = (value & (1 << i)) != 0;
}
}
}
fn encode_nibble(&self, bits: &mut [bool; DATA_BITS], start: usize, value: u8) {
for i in 0..4 {
if start + i < DATA_BITS {
bits[start + i] = (value & (1 << i)) != 0;
}
}
}
fn calculate_crc(&self, bits: &[bool]) -> u8 {
let mut crc = 0u8;
for &bit in bits {
let feedback = ((crc & 0x80) != 0) ^ bit;
crc <<= 1;
if feedback {
crc ^= 0x07; }
}
crc
}
fn bits_to_pixels(&self, bits: &[bool; BITS_PER_LINE]) -> Vec<u8> {
let mut pixels = Vec::with_capacity(BITS_PER_LINE * PIXELS_PER_BIT);
for &bit in bits {
let level = if bit { WHITE_LEVEL } else { BLACK_LEVEL };
for _ in 0..PIXELS_PER_BIT {
pixels.push(level);
}
}
pixels
}
pub fn reset(&mut self) {
self.current_field = 1;
}
pub fn set_field(&mut self, field: u8) {
self.current_field = field;
}
pub fn field(&self) -> u8 {
self.current_field
}
}
pub struct MultiLineVitcWriter {
encoder: VitcEncoder,
lines: Vec<u16>,
}
impl MultiLineVitcWriter {
pub fn new(config: VitcWriterConfig) -> Self {
let lines = config.scan_lines.clone();
MultiLineVitcWriter {
encoder: VitcEncoder::new(config),
lines,
}
}
pub fn encode_all_lines(
&mut self,
timecode: &Timecode,
field: u8,
) -> Result<Vec<(u16, Vec<u8>)>, TimecodeError> {
let mut results = Vec::new();
for &line in &self.lines {
let pixels = self.encoder.encode_line(timecode, field)?;
results.push((line, pixels));
}
Ok(results)
}
}
pub struct VitcLineBuffer {
#[allow(dead_code)]
video_standard: VideoStandard,
lines: Vec<(u16, u8, Vec<u8>)>, }
impl VitcLineBuffer {
pub fn new(video_standard: VideoStandard) -> Self {
VitcLineBuffer {
video_standard,
lines: Vec::new(),
}
}
pub fn add_line(&mut self, line_number: u16, field: u8, pixels: Vec<u8>) {
self.lines
.retain(|(ln, f, _)| !(*ln == line_number && *f == field));
self.lines.push((line_number, field, pixels));
}
pub fn get_field_lines(&self, field: u8) -> Vec<(u16, &[u8])> {
self.lines
.iter()
.filter(|(_, f, _)| *f == field)
.map(|(ln, _, pixels)| (*ln, pixels.as_slice()))
.collect()
}
pub fn clear(&mut self) {
self.lines.clear();
}
pub fn line_count(&self) -> usize {
self.lines.len()
}
}
pub struct PixelLevelAdjuster {
black_level: u8,
white_level: u8,
}
impl PixelLevelAdjuster {
pub fn new(video_standard: VideoStandard) -> Self {
match video_standard {
VideoStandard::Ntsc => PixelLevelAdjuster {
black_level: 16,
white_level: 235,
},
VideoStandard::Pal => PixelLevelAdjuster {
black_level: 16,
white_level: 235,
},
}
}
pub fn with_levels(black_level: u8, white_level: u8) -> Self {
PixelLevelAdjuster {
black_level,
white_level,
}
}
pub fn bit_to_pixel(&self, bit: bool) -> u8 {
if bit {
self.white_level
} else {
self.black_level
}
}
pub fn black_level(&self) -> u8 {
self.black_level
}
pub fn white_level(&self) -> u8 {
self.white_level
}
}
pub struct RiseTimeShaper {
rise_time_pixels: usize,
}
impl RiseTimeShaper {
pub fn new(rise_time_pixels: usize) -> Self {
RiseTimeShaper {
rise_time_pixels: rise_time_pixels.max(1),
}
}
pub fn shape_pixels(&self, pixels: &[u8]) -> Vec<u8> {
let mut shaped = Vec::with_capacity(pixels.len());
if pixels.is_empty() {
return shaped;
}
shaped.push(pixels[0]);
for i in 1..pixels.len() {
let current = pixels[i];
let prev = pixels[i - 1];
if current != prev {
let diff = current as i16 - prev as i16;
for j in 0..self.rise_time_pixels.min(pixels.len() - i) {
let progress = (j + 1) as f32 / self.rise_time_pixels as f32;
let value = prev as f32 + diff as f32 * progress;
shaped.push(value as u8);
}
for _ in 0..self.rise_time_pixels.min(pixels.len() - i) {
if i < pixels.len() {
shaped.push(current);
}
}
} else {
shaped.push(current);
}
}
shaped.truncate(pixels.len());
shaped
}
}
pub struct BlankingInserter {
blanking_level: u8,
}
impl BlankingInserter {
pub fn new(blanking_level: u8) -> Self {
BlankingInserter { blanking_level }
}
pub fn insert_blanking(
&self,
vitc_pixels: &[u8],
total_width: usize,
start_offset: usize,
) -> Vec<u8> {
let mut full_line = vec![self.blanking_level; total_width];
let end_offset = (start_offset + vitc_pixels.len()).min(total_width);
for (i, &pixel) in vitc_pixels.iter().enumerate() {
if start_offset + i < end_offset {
full_line[start_offset + i] = pixel;
}
}
full_line
}
}
pub struct VitcFrameGenerator {
writer: MultiLineVitcWriter,
current_timecode: Option<Timecode>,
#[allow(dead_code)]
frame_rate: FrameRate,
}
impl VitcFrameGenerator {
pub fn new(config: VitcWriterConfig) -> Self {
let frame_rate = config.frame_rate;
VitcFrameGenerator {
writer: MultiLineVitcWriter::new(config),
current_timecode: None,
frame_rate,
}
}
pub fn set_timecode(&mut self, timecode: Timecode) {
self.current_timecode = Some(timecode);
}
pub fn generate_frame(&mut self) -> Result<Vec<(u16, u8, Vec<u8>)>, TimecodeError> {
if let Some(ref mut tc) = self.current_timecode {
let mut results = Vec::new();
let field1_lines = self.writer.encode_all_lines(tc, 1)?;
for (line, pixels) in field1_lines {
results.push((line, 1, pixels));
}
let field2_lines = self.writer.encode_all_lines(tc, 2)?;
for (line, pixels) in field2_lines {
results.push((line, 2, pixels));
}
tc.increment()?;
Ok(results)
} else {
Err(TimecodeError::InvalidConfiguration)
}
}
pub fn current_timecode(&self) -> Option<&Timecode> {
self.current_timecode.as_ref()
}
}
pub struct VitcUserBitsHelper;
impl VitcUserBitsHelper {
pub fn validate_user_bits(user_bits: u32) -> bool {
let _ = user_bits;
true
}
pub fn extract_group(user_bits: u32, group: u8) -> u8 {
let shift = (group as u32) * 4;
((user_bits >> shift) & 0xF) as u8
}
pub fn set_group(user_bits: u32, group: u8, value: u8) -> u32 {
let shift = (group as u32) * 4;
let mask = !(0xF << shift);
(user_bits & mask) | ((value as u32 & 0xF) << shift)
}
}
pub struct PixelPatternValidator;
impl PixelPatternValidator {
pub fn validate_pattern(pixels: &[u8]) -> bool {
if pixels.len() < BITS_PER_LINE * PIXELS_PER_BIT {
return false;
}
for &pixel in pixels {
if !(16..=235).contains(&pixel) {
return false;
}
}
true
}
pub fn check_sync_pattern(pixels: &[u8]) -> bool {
if pixels.len() < 4 {
return false;
}
pixels[0] > 200 && pixels[1] > 200 && pixels[2] > 200 && pixels[3] > 200
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encoder_creation() {
let config = VitcWriterConfig::default();
let encoder = VitcEncoder::new(config);
assert_eq!(encoder.field(), 1);
}
#[test]
fn test_encode_line() {
let config = VitcWriterConfig::default();
let mut encoder = VitcEncoder::new(config);
let timecode = Timecode::new(1, 2, 3, 4, FrameRate::Fps25).expect("valid timecode");
let pixels = encoder
.encode_line(&timecode, 1)
.expect("encode should succeed");
assert_eq!(pixels.len(), BITS_PER_LINE * PIXELS_PER_BIT);
}
#[test]
fn test_crc_calculation() {
let config = VitcWriterConfig::default();
let encoder = VitcEncoder::new(config);
let bits = [false; 72];
let crc = encoder.calculate_crc(&bits);
assert_eq!(crc, 0);
}
#[test]
fn test_pixel_level_adjuster() {
let adjuster = PixelLevelAdjuster::new(VideoStandard::Pal);
assert_eq!(adjuster.bit_to_pixel(false), 16);
assert_eq!(adjuster.bit_to_pixel(true), 235);
}
#[test]
fn test_user_bits_helper() {
let user_bits = 0x12345678u32;
assert_eq!(VitcUserBitsHelper::extract_group(user_bits, 0), 0x8);
assert_eq!(VitcUserBitsHelper::extract_group(user_bits, 1), 0x7);
let modified = VitcUserBitsHelper::set_group(user_bits, 0, 0xA);
assert_eq!(VitcUserBitsHelper::extract_group(modified, 0), 0xA);
}
#[test]
fn test_line_buffer() {
let mut buffer = VitcLineBuffer::new(VideoStandard::Pal);
buffer.add_line(19, 1, vec![16; 180]);
buffer.add_line(21, 1, vec![16; 180]);
assert_eq!(buffer.line_count(), 2);
let field1_lines = buffer.get_field_lines(1);
assert_eq!(field1_lines.len(), 2);
}
}