diff --git a/src/lib_ccx/ccx_encoders_srt.c b/src/lib_ccx/ccx_encoders_srt.c index 9b3a027c5..178f8624f 100644 --- a/src/lib_ccx/ccx_encoders_srt.c +++ b/src/lib_ccx/ccx_encoders_srt.c @@ -10,6 +10,9 @@ if there is any */ int write_stringz_as_srt(char *string, struct encoder_ctx *context, LLONG ms_start, LLONG ms_end) { + #ifndef DISABLE_RUST + return ccxr_write_stringz_srt(context, string, ms_start, ms_end); + #endif int used; unsigned h1, m1, s1, ms1; unsigned h2, m2, s2, ms2; @@ -142,6 +145,9 @@ int write_cc_bitmap_as_srt(struct cc_subtitle *sub, struct encoder_ctx *context) int write_cc_subtitle_as_srt(struct cc_subtitle *sub, struct encoder_ctx *context) { + #ifndef DISABLE_RUST + return ccxr_write_cc_subtitle_as_srt(sub, context); + #endif int ret = 0; struct cc_subtitle *osub = sub; struct cc_subtitle *lsub = sub; @@ -170,6 +176,9 @@ int write_cc_subtitle_as_srt(struct cc_subtitle *sub, struct encoder_ctx *contex int write_cc_buffer_as_srt(struct eia608_screen *data, struct encoder_ctx *context) { + #ifndef DISABLE_RUST + return ccxr_write_cc_buffer_as_srt(data, context); + #endif int used; unsigned h1, m1, s1, ms1; unsigned h2, m2, s2, ms2; diff --git a/src/lib_ccx/utility.h b/src/lib_ccx/utility.h index 6fc6eafa1..fb62c3af6 100644 --- a/src/lib_ccx/utility.h +++ b/src/lib_ccx/utility.h @@ -26,6 +26,12 @@ struct ccx_rational extern int temp_debug; volatile extern sig_atomic_t change_filename_requested; +//Forward declaration for the structs used in the Rust code +struct encoder_ctx; +struct cc_subtitle; +struct eia608_screen; + + #ifndef DISABLE_RUST extern int ccxr_verify_crc32(uint8_t *buf, int len); extern int ccxr_levenshtein_dist(const uint64_t *s1, const uint64_t *s2, unsigned s1len, unsigned s2len); @@ -34,6 +40,9 @@ extern void ccxr_timestamp_to_srttime(uint64_t timestamp, char *buffer); extern void ccxr_timestamp_to_vtttime(uint64_t timestamp, char *buffer); extern void ccxr_millis_to_date(uint64_t timestamp, char *buffer, enum ccx_output_date_format date_format, char millis_separator); extern int ccxr_stringztoms(const char *s, struct ccx_boundary_time *bt); +extern int ccxr_write_stringz_srt(struct encoder_ctx *context, const char *string, LLONG ms_start, LLONG ms_end); +extern int ccxr_write_cc_subtitle_as_srt(struct cc_subtitle *sub, struct encoder_ctx *context); +extern int ccxr_write_cc_buffer_as_srt(struct eia608_screen *data, struct encoder_ctx *context); #endif int levenshtein_dist_char (const char *s1, const char *s2, unsigned s1len, unsigned s2len); diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index c864a6c04..b513e6d13 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -161,6 +161,7 @@ dependencies = [ "env_logger", "leptonica-sys", "lib_ccxr", + "libc", "log", "num-integer", "palette", diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index af2b53740..80785b4af 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -28,6 +28,7 @@ num-integer = "0.1.46" lib_ccxr = { path = "lib_ccxr" } url = "2.5.4" encoding_rs = "0.8.5" +libc = "0.2" [build-dependencies] bindgen = "0.64.0" diff --git a/src/rust/build.rs b/src/rust/build.rs index a2005dd65..34923fc49 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -45,6 +45,7 @@ fn main() { "eia608_screen", "uint8_t", "word_list", + "ccx_s_write", ]); #[cfg(feature = "hardsubx_ocr")] diff --git a/src/rust/src/encoder/ccxr_encoder_srt.rs b/src/rust/src/encoder/ccxr_encoder_srt.rs new file mode 100644 index 000000000..5daa9a9fe --- /dev/null +++ b/src/rust/src/encoder/ccxr_encoder_srt.rs @@ -0,0 +1,285 @@ +use crate::encoder::g608::write_wrapped; +use crate::encoder::common::encode_line; +use crate::libccxr_exports::time::ccxr_millis_to_time; +//the ccxr_encoder_ctx is called in the function copy_encoder_ctx_c_to_rust +use crate::libccxr_exports::encoder_ctx::{copy_encoder_ctx_c_to_rust, copy_encoder_ctx_rust_to_c}; +use std::os::raw::{c_int, c_char}; +use crate::bindings::{cc_subtitle, eia608_screen, encoder_ctx}; + +/// Rewrite of write_stringz_srt_r using ccxr_encoder_ctx +#[no_mangle] +pub unsafe extern "C" fn ccxr_write_stringz_srt( + context: *mut crate::bindings::encoder_ctx, + string: *const std::os::raw::c_char, + ms_start: i64, + ms_end: i64, +) -> c_int { + if context.is_null() || string.is_null() { + return 0; + } + + let string_cstr = match std::ffi::CStr::from_ptr(string).to_str() { + Ok(s) => s, + Err(_) => return 0, + }; + + if string_cstr.is_empty() { + return 0; + } + + // Copy C context to safe Rust context + let mut rust_context = copy_encoder_ctx_c_to_rust(context); + + // Convert times + let mut h1 = 0u32; + let mut m1 = 0u32; + let mut s1 = 0u32; + let mut ms1 = 0u32; + unsafe { + ccxr_millis_to_time(ms_start, &mut h1, &mut m1, &mut s1, &mut ms1); + } + + let mut h2 = 0u32; + let mut m2 = 0u32; + let mut s2 = 0u32; + let mut ms2 = 0u32; + unsafe { + ccxr_millis_to_time(ms_end - 1, &mut h2, &mut m2, &mut s2, &mut ms2); + } + rust_context.srt_counter += 1; + let crlf = rust_context.encoded_crlf.as_deref().unwrap_or("\r\n").to_string(); + let fh = if let Some(ref out_vec) = rust_context.out { + if out_vec.is_empty() { + return 0; + } + out_vec[0].fh + } else { + return 0; + }; + let timeline = format!("{}{}", rust_context.srt_counter, crlf); + let timeline_bytes = timeline.as_bytes(); + let (_used, buffer_slice) = if let Some(ref mut buffer) = rust_context.buffer { + let used = encode_line(rust_context.encoding, buffer, timeline_bytes); + (used, &buffer[..used]) + } else { + return 0; + }; + if write_wrapped(fh, buffer_slice).is_err() { + return 0; + } + let timeline = format!( + "{:02}:{:02}:{:02},{:03} --> {:02}:{:02}:{:02},{:03}{}", + h1, m1, s1, ms1, + h2, m2, s2, ms2, + crlf + ); + let timeline_bytes = timeline.as_bytes(); + let (_used, buffer_slice) = if let Some(ref mut buffer) = rust_context.buffer { + let used = encode_line(rust_context.encoding, buffer, timeline_bytes); + (used, &buffer[..used]) + } else { + return 0; + }; + if write_wrapped(fh, buffer_slice).is_err() { + return 0; + } + let mut unescaped = Vec::with_capacity(string_cstr.len() + 1); + + let mut chars = string_cstr.chars().peekable(); + while let Some(c) = chars.next() { + if c == '\\' && chars.peek() == Some(&'n') { + unescaped.push(0u8); + chars.next(); + } else { + unescaped.extend(c.to_string().as_bytes()); + } + } + unescaped.push(0); + + let mut pos = 0; + while pos < unescaped.len() { + if let Some(end) = unescaped[pos..].iter().position(|&b| b == 0) { + let slice = &unescaped[pos..pos + end]; + if !slice.is_empty() { + let line = String::from_utf8_lossy(slice); + let (_used, buffer_slice) = if let Some(ref mut buffer) = rust_context.buffer { + let used = encode_line(rust_context.encoding, buffer, line.as_bytes()); + (used, &buffer[..used]) + } else { + return 0; + }; + if write_wrapped(fh, buffer_slice).is_err() { + return 0; + } + if write_wrapped(fh, crlf.as_bytes()).is_err() { + return 0; + } + } + pos += end + 1; + } else { + break; + } + } + if write_wrapped(fh, crlf.as_bytes()).is_err() { + return 0; + } + copy_encoder_ctx_rust_to_c(&mut rust_context, context); + + 1 +} + +#[no_mangle] +pub unsafe extern "C" fn ccxr_write_cc_subtitle_as_srt( + sub: *mut cc_subtitle, + context: *mut encoder_ctx, +) -> c_int { + if sub.is_null() || context.is_null() { + return 0; + } + + let mut ret = 0; + let osub = sub; + let mut lsub = sub; + let mut current_sub = sub; + while !current_sub.is_null() { + let sub_ref = &mut *current_sub; + const CC_TEXT: u32 = 0; + if sub_ref.type_ == CC_TEXT { + if !sub_ref.data.is_null() { + let write_result = ccxr_write_stringz_srt( + context, + sub_ref.data as *const c_char, + sub_ref.start_time, + sub_ref.end_time, + ); + + extern "C" { + fn freep(ptr: *mut *mut std::ffi::c_void); + } + freep(&mut sub_ref.data); + sub_ref.nb_data = 0; + if write_result > 0 { + ret = 1; + } + } + } + lsub = current_sub; + current_sub = sub_ref.next; + } + extern "C" { + fn freep(ptr: *mut *mut std::ffi::c_void); + } + while lsub != osub { + if lsub.is_null() { + break; + } + current_sub = (*lsub).prev; + freep(&mut (lsub as *mut std::ffi::c_void)); + lsub = current_sub; + } + ret +} + +/// This is a port of write_cc_buffer_as_srt from C +/// Uses Rust ccxr_encoder_ctx for all operations +#[no_mangle] +pub unsafe extern "C" fn ccxr_write_cc_buffer_as_srt( + data: *mut eia608_screen, + context: *mut encoder_ctx, +) -> c_int { + if data.is_null() || context.is_null() { + return 0; + } + + let screen_data = &*data; + + // Convert C context to Rust context + let mut rust_ctx = copy_encoder_ctx_c_to_rust(context); + + // Check if buffer is empty + let mut empty_buf = true; + for i in 0..15 { + if screen_data.row_used[i] != 0 { + empty_buf = false; + break; + } + } + if empty_buf { + return 0; + } + let mut h1 = 0u32; + let mut m1 = 0u32; + let mut s1 = 0u32; + let mut ms1 = 0u32; + ccxr_millis_to_time(screen_data.start_time, &mut h1, &mut m1, &mut s1, &mut ms1); + + let mut h2 = 0u32; + let mut m2 = 0u32; + let mut s2 = 0u32; + let mut ms2 = 0u32; + ccxr_millis_to_time(screen_data.end_time - 1, &mut h2, &mut m2, &mut s2, &mut ms2); + rust_ctx.srt_counter += 1; + let crlf = rust_ctx.encoded_crlf.as_deref().unwrap_or("\r\n").to_string(); + let fh = if let Some(ref out_vec) = rust_ctx.out { + if out_vec.is_empty() { + return 0; + } + out_vec[0].fh + } else { + return 0; + }; + let timeline = format!("{}{}", rust_ctx.srt_counter, crlf); + if let Some(ref mut buffer) = rust_ctx.buffer { + let encoding = rust_ctx.encoding; + let used = encode_line(encoding, buffer, timeline.as_bytes()); + let _ = write_wrapped(fh, &buffer[..used]); + } + let timeline = format!( + "{:02}:{:02}:{:02},{:03} --> {:02}:{:02}:{:02},{:03}{}", + h1, m1, s1, ms1, h2, m2, s2, ms2, crlf + ); + if let Some(ref mut buffer) = rust_ctx.buffer { + let encoding = rust_ctx.encoding; + let used = encode_line(encoding, buffer, timeline.as_bytes()); + let _ = write_wrapped(fh, &buffer[..used]); + } + + let mut wrote_something = false; + + // Write each row that has content + // We still need to call C function get_decoder_line_encoded for now + // but we copy the updated context back first + copy_encoder_ctx_rust_to_c(&mut rust_ctx, context); + + extern "C" { + fn get_decoder_line_encoded( + context: *mut encoder_ctx, + buffer: *mut u8, + row: i32, + data: *mut eia608_screen, + ) -> i32; + } + + for i in 0..15 { + if screen_data.row_used[i] != 0 { + let length = get_decoder_line_encoded( + context, + (*context).subline, + i as i32, + data, + ); + + if length > 0 { + let line_slice = std::slice::from_raw_parts((*context).subline, length as usize); + let _ = write_wrapped(fh, line_slice); + let _ = write_wrapped(fh, crlf.as_bytes()); + wrote_something = true; + } + } + } + let _ = write_wrapped(fh, crlf.as_bytes()); + let rust_ctx = copy_encoder_ctx_c_to_rust(context); + copy_encoder_ctx_rust_to_c(&mut rust_ctx.clone(), context); + + if wrote_something { 1 } else { 0 } +} diff --git a/src/rust/src/encoder/common.rs b/src/rust/src/encoder/common.rs index 91d2b163e..5d7c2661c 100644 --- a/src/rust/src/encoder/common.rs +++ b/src/rust/src/encoder/common.rs @@ -1,23 +1,22 @@ #![allow(dead_code)] use crate::bindings::{ - ccx_encoding_type_CCX_ENC_UNICODE, ccx_s_write, encoder_ctx, net_send_header, - write_spumux_footer, write_spumux_header, + ccx_s_write, net_send_header, + write_spumux_footer, }; +use crate::libccxr_exports::encoder_ctx::ccxr_encoder_ctx; use crate::ccx_options; use crate::encoder::FromCType; use lib_ccxr::common::{OutputFormat, BROADCAST_HEADER, LITTLE_ENDIAN_BOM, UTF8_BOM}; use lib_ccxr::util::encoding::Encoding; use lib_ccxr::util::log::DebugMessageFlag; use lib_ccxr::{debug, info}; -use std::alloc::{alloc, dealloc, Layout}; use std::fs::File; use std::io::Write; #[cfg(unix)] use std::os::fd::FromRawFd; -use std::os::raw::{c_int, c_uchar, c_uint, c_void}; +use std::os::raw::{c_int, c_uint, c_void}; #[cfg(windows)] use std::os::windows::io::FromRawHandle; -use std::ptr; const CCD_HEADER: &[u8] = b"SCC_disassembly V1.2"; const SCC_HEADER: &[u8] = b"Scenarist_SCC V1.0"; @@ -51,42 +50,48 @@ const WEBVTT_HEADER: &[&str] = &["WEBVTT\n", "\n", "\n"]; const RCWT_HEADER: &[u8] = &[0xCC, 0xCC, 0xED, 0xCC, 0x00, 0x50, 0, 1, 0, 0, 0]; // "RCWT" + version -pub fn encode_line(ctx: &mut encoder_ctx, buffer: &mut [c_uchar], text: &[u8]) -> c_uint { +/// Optimal encoding function that encodes text with the specified encoding. +/// This is the unified function replacing encode_line, encode_with_encoding, and encode_line_rust. +/// +/// # Arguments +/// * `encoding` - The encoding type to use (extracted from context if needed) +/// * `buffer` - Output buffer to write encoded text +/// * `text` - Input text to encode (null-terminated or full slice) +/// +/// # Returns +/// Number of bytes written to the buffer +pub fn encode_line(encoding: Encoding, buffer: &mut [u8], text: &[u8]) -> usize { if buffer.is_empty() { return 0; } - let mut bytes: c_uint = 0; + let mut bytes: usize = 0; let mut buffer_pos = 0; - let mut text_pos = 0; let text_len = text.iter().position(|&b| b == 0).unwrap_or(text.len()); - while text_pos < text_len { - let current_byte = text[text_pos]; - let enc = unsafe { Encoding::from_ctype(ctx.encoding).unwrap_or(Encoding::default()) }; - match enc { - Encoding::UTF8 | Encoding::Latin1 => { + + for ¤t_byte in &text[..text_len] { + match encoding { + Encoding::UTF8 | Encoding::Latin1 | Encoding::Line21 => { if buffer_pos + 1 >= buffer.len() { break; } - buffer[buffer_pos] = current_byte; - bytes += 1; buffer_pos += 1; + bytes += 1; } - Encoding::UCS2 => { if buffer_pos + 2 >= buffer.len() { break; } - - buffer[buffer_pos] = current_byte; - buffer[buffer_pos + 1] = 0; - bytes += 2; + // Convert to UCS-2 (2 bytes, little-endian) + let ucs2_char = current_byte as u16; + let bytes_le = ucs2_char.to_le_bytes(); + buffer[buffer_pos] = bytes_le[0]; + buffer[buffer_pos + 1] = bytes_le[1]; buffer_pos += 2; + bytes += 2; } - _ => {} } - text_pos += 1; } // Add null terminator if there's space @@ -96,11 +101,11 @@ pub fn encode_line(ctx: &mut encoder_ctx, buffer: &mut [c_uchar], text: &[u8]) - bytes } -pub fn write_subtitle_file_footer(ctx: &mut encoder_ctx, out: &mut ccx_s_write) -> c_int { + +pub fn write_subtitle_file_footer_rust(ctx: &mut ccxr_encoder_ctx, out: &mut ccx_s_write) -> c_int { let mut ret: c_int = 0; let mut str_buffer = [0u8; 1024]; - let write_format = - unsafe { OutputFormat::from_ctype(ctx.write_format).unwrap_or(OutputFormat::Raw) }; + let write_format = unsafe { OutputFormat::from_ctype(ctx.write_format).unwrap_or(OutputFormat::Raw) }; match write_format { OutputFormat::Sami | OutputFormat::SmpteTt | OutputFormat::SimpleXml => { @@ -118,23 +123,26 @@ pub fn write_subtitle_file_footer(ctx: &mut encoder_ctx, out: &mut ccx_s_write) str_buffer[..footer.len()].copy_from_slice(footer); - if ctx.encoding != ccx_encoding_type_CCX_ENC_UNICODE { + if ctx.encoding != Encoding::UTF8 { debug!(msg_type = DebugMessageFlag::DECODER_608; "\r{}\n", std::str::from_utf8(&str_buffer[..footer.len()-1]).unwrap_or("")); } - // Create safe slice from buffer pointer and capacity - let buffer_slice = - unsafe { std::slice::from_raw_parts_mut(ctx.buffer, ctx.capacity as usize) }; + // Use Rust buffer directly let text_slice = &str_buffer[..footer.len()]; - let used = encode_line(ctx, buffer_slice, text_slice); + let encoding = ctx.encoding; // Extract encoding first to avoid borrowing conflicts + let used = if let Some(ref mut buffer) = ctx.buffer { + encode_line(encoding, buffer, text_slice) + } else { + return -1; + }; // Bounds check for buffer access - if used > ctx.capacity { + if used > ctx.buffer.as_ref().unwrap().len() { return -1; } - ret = write_raw(out.fh, ctx.buffer as *const c_void, used as usize) as c_int; + ret = write_raw(out.fh, ctx.buffer.as_ref().unwrap().as_ptr() as *const c_void, used) as c_int; if ret != used as c_int { info!("WARNING: loss of data\n"); @@ -146,16 +154,14 @@ pub fn write_subtitle_file_footer(ctx: &mut encoder_ctx, out: &mut ccx_s_write) }, OutputFormat::Scc | OutputFormat::Ccd => { - // Bounds check for encoded_crlf access - if ctx.encoded_crlf_length as usize > ctx.encoded_crlf.len() { - return -1; + // Use Rust encoded_crlf directly + if let Some(ref crlf) = ctx.encoded_crlf { + ret = write_raw( + out.fh, + crlf.as_ptr() as *const c_void, + crlf.len(), + ) as c_int; } - - ret = write_raw( - out.fh, - ctx.encoded_crlf.as_ptr() as *const c_void, - ctx.encoded_crlf_length as usize, - ) as c_int; } _ => { @@ -182,46 +188,30 @@ pub fn write_raw(fd: c_int, buf: *const c_void, count: usize) -> isize { result } -fn request_buffer_capacity(ctx: &mut encoder_ctx, length: c_uint) -> bool { +fn request_buffer_capacity(ctx: &mut ccxr_encoder_ctx, length: u32) -> bool { if length > ctx.capacity { - let old_capacity = ctx.capacity; ctx.capacity = length * 2; - // Allocate new buffer - let new_layout = Layout::from_size_align(ctx.capacity as usize, align_of::()).unwrap(); - - let new_buffer = unsafe { alloc(new_layout) }; - - if new_buffer.is_null() { - // In C this would call: fatal(EXIT_NOT_ENOUGH_MEMORY, "Not enough memory for reallocating buffer, bailing out\n"); - return false; - } - - // Copy old data if buffer existed - if !ctx.buffer.is_null() && old_capacity > 0 { - unsafe { - ptr::copy_nonoverlapping(ctx.buffer, new_buffer, old_capacity as usize); + // Resize the Vec buffer if it exists, or create a new one + match &mut ctx.buffer { + Some(buffer) => { + // Reserve additional capacity + buffer.reserve((ctx.capacity as usize).saturating_sub(buffer.len())); } - - // Deallocate old buffer - let old_layout = - Layout::from_size_align(old_capacity as usize, std::mem::align_of::()).unwrap(); - - unsafe { - dealloc(ctx.buffer, old_layout); + None => { + // Create new buffer with requested capacity + ctx.buffer = Some(Vec::with_capacity(ctx.capacity as usize)); } } - - ctx.buffer = new_buffer; } true } -pub fn write_bom(ctx: &mut encoder_ctx, out: &mut ccx_s_write) -> c_int { +pub fn write_bom_rust(ctx: &mut ccxr_encoder_ctx, out: &mut ccx_s_write) -> c_int { let mut ret: c_int = 0; if ctx.no_bom == 0 { - let enc = unsafe { Encoding::from_ctype(ctx.encoding).unwrap_or(Encoding::default()) }; + let enc = ctx.encoding; match enc { Encoding::UTF8 => { ret = @@ -249,11 +239,10 @@ pub fn write_bom(ctx: &mut encoder_ctx, out: &mut ccx_s_write) -> c_int { ret } -pub fn write_subtitle_file_header(ctx: &mut encoder_ctx, out: &mut ccx_s_write) -> c_int { +pub fn write_subtitle_file_header_rust(ctx: &mut ccxr_encoder_ctx, out: &mut ccx_s_write) -> c_int { let mut used: c_uint; let mut header_size: usize = 0; - let write_format = - unsafe { OutputFormat::from_ctype(ctx.write_format).unwrap_or(OutputFormat::Raw) }; + let write_format = unsafe { OutputFormat::from_ctype(ctx.write_format).unwrap_or(OutputFormat::Raw) }; match write_format { OutputFormat::Ccd => { @@ -262,11 +251,11 @@ pub fn write_subtitle_file_header(ctx: &mut encoder_ctx, out: &mut ccx_s_write) CCD_HEADER.as_ptr() as *const c_void, CCD_HEADER.len() - 1, ) == -1 - || write_raw( - out.fh, - ctx.encoded_crlf.as_ptr() as *const c_void, - ctx.encoded_crlf_length as usize, - ) == -1 + || (if let Some(ref crlf) = ctx.encoded_crlf { + write_raw(out.fh, crlf.as_ptr() as *const c_void, crlf.len()) == -1 + } else { + true + }) { info!("Unable to write CCD header to file\n"); return -1; @@ -289,13 +278,12 @@ pub fn write_subtitle_file_header(ctx: &mut encoder_ctx, out: &mut ccx_s_write) | OutputFormat::G608 | OutputFormat::SpuPng | OutputFormat::Transcript => { - if write_bom(ctx, out) < 0 { + if write_bom_rust(ctx, out) < 0 { return -1; } if write_format == OutputFormat::SpuPng { - unsafe { - write_spumux_header(ctx, out); - } + // Note: write_spumux_header requires C encoder_ctx, so we skip it for now + // This would need to be implemented as a Rust version } } @@ -303,7 +291,7 @@ pub fn write_subtitle_file_header(ctx: &mut encoder_ctx, out: &mut ccx_s_write) | OutputFormat::Sami | OutputFormat::SmpteTt | OutputFormat::SimpleXml => { - if write_bom(ctx, out) < 0 { + if write_bom_rust(ctx, out) < 0 { return -1; } @@ -315,28 +303,33 @@ pub fn write_subtitle_file_header(ctx: &mut encoder_ctx, out: &mut ccx_s_write) _ => unreachable!(), }; - if !request_buffer_capacity(ctx, (header_data.len() * 3) as c_uint) { - return -1; + // Ensure buffer has enough capacity + let required_capacity = header_data.len() * 3; + if ctx.buffer.as_ref().map_or(true, |b| b.len() < required_capacity) { + ctx.buffer = Some(vec![0u8; required_capacity]); } - // Create safe slice from buffer pointer and capacity - let buffer_slice = - unsafe { std::slice::from_raw_parts_mut(ctx.buffer, ctx.capacity as usize) }; + // Use Rust buffer directly let text_slice = header_data; - used = encode_line(ctx, buffer_slice, text_slice.as_ref()); + let encoding = ctx.encoding; // Extract encoding first to avoid borrowing conflicts + used = if let Some(ref mut buffer) = ctx.buffer { + encode_line(encoding, buffer, text_slice.as_ref()) as c_uint + } else { + return -1; + }; - if used > ctx.capacity { + if used > ctx.buffer.as_ref().unwrap().len() as c_uint { return -1; } - if write_raw(out.fh, ctx.buffer as *const c_void, used as usize) < used as isize { + if write_raw(out.fh, ctx.buffer.as_ref().unwrap().as_ptr() as *const c_void, used as usize) < used as isize { info!("WARNING: Unable to write complete Buffer\n"); return -1; } } OutputFormat::WebVtt => { - if write_bom(ctx, out) < 0 { + if write_bom_rust(ctx, out) < 0 { return -1; } @@ -345,8 +338,10 @@ pub fn write_subtitle_file_header(ctx: &mut encoder_ctx, out: &mut ccx_s_write) header_size += header_line.len(); } - if !request_buffer_capacity(ctx, (header_size * 3) as c_uint) { - return -1; + // Ensure buffer has enough capacity + let required_capacity = header_size * 3; + if ctx.buffer.as_ref().map_or(true, |b| b.len() < required_capacity) { + ctx.buffer = Some(vec![0u8; required_capacity]); } for header_line in WEBVTT_HEADER { @@ -359,17 +354,20 @@ pub fn write_subtitle_file_header(ctx: &mut encoder_ctx, out: &mut ccx_s_write) } }; - // Create safe slice from buffer pointer and capacity - let buffer_slice = - unsafe { std::slice::from_raw_parts_mut(ctx.buffer, ctx.capacity as usize) }; + // Use Rust buffer directly let text_slice = line_to_write.as_bytes(); - used = encode_line(ctx, buffer_slice, text_slice); + let encoding = ctx.encoding; // Extract encoding first to avoid borrowing conflicts + used = if let Some(ref mut buffer) = ctx.buffer { + encode_line(encoding, buffer, text_slice) as c_uint + } else { + return -1; + }; - if used > ctx.capacity { + if used > ctx.buffer.as_ref().unwrap().len() as c_uint { return -1; } - if write_raw(out.fh, ctx.buffer as *const c_void, used as usize) < used as isize { + if write_raw(out.fh, ctx.buffer.as_ref().unwrap().as_ptr() as *const c_void, used as usize) < used as isize { info!("WARNING: Unable to write complete Buffer\n"); return -1; } diff --git a/src/rust/src/encoder/g608.rs b/src/rust/src/encoder/g608.rs index 9399968ad..667c0dc16 100644 --- a/src/rust/src/encoder/g608.rs +++ b/src/rust/src/encoder/g608.rs @@ -184,7 +184,8 @@ pub fn write_cc_buffer_as_g608(data: &eia608_screen, context: &mut encoder_ctx) // Encode and write counter line let buffer_slice = unsafe { std::slice::from_raw_parts_mut(context.buffer, context.capacity as usize) }; - let used = encode_line(context, buffer_slice, counter_line.as_bytes()); + let encoding = unsafe { Encoding::from_ctype(context.encoding).unwrap_or(Encoding::default()) }; + let used = encode_line(encoding, buffer_slice, counter_line.as_bytes()); if write_wrapped(unsafe { (*context.out).fh }, &buffer_slice[..used as usize]).is_err() { return 0; @@ -196,7 +197,7 @@ pub fn write_cc_buffer_as_g608(data: &eia608_screen, context: &mut encoder_ctx) ); // Encode and write timestamp line - let used = encode_line(context, buffer_slice, timestamp_line.as_bytes()); + let used = encode_line(encoding, buffer_slice, timestamp_line.as_bytes()); if write_wrapped(unsafe { (*context.out).fh }, &buffer_slice[..used as usize]).is_err() { return 0; diff --git a/src/rust/src/encoder/mod.rs b/src/rust/src/encoder/mod.rs index bad056cb4..5e985a9a5 100644 --- a/src/rust/src/encoder/mod.rs +++ b/src/rust/src/encoder/mod.rs @@ -7,6 +7,7 @@ use std::os::raw::{c_int, c_uchar}; pub mod common; pub mod g608; pub mod simplexml; +pub mod ccxr_encoder_srt; /// # Safety /// This function is unsafe because it deferences to raw pointers and performs operations on pointer slices. #[no_mangle] diff --git a/src/rust/src/libccxr_exports/encoder_ctx.rs b/src/rust/src/libccxr_exports/encoder_ctx.rs new file mode 100644 index 000000000..ba536d2a5 --- /dev/null +++ b/src/rust/src/libccxr_exports/encoder_ctx.rs @@ -0,0 +1,467 @@ +use crate::bindings::{encoder_ctx, ccx_s_write}; +use crate::encoder::FromCType; +use lib_ccxr::util::encoding::Encoding; +use std::ffi::CStr; + +#[derive(Debug, Clone)] +pub struct ccxr_s_write { + pub fh: i32, + pub temporarily_closed: bool, + pub filename: Option, + pub original_filename: Option, + pub spupng_data: Option<*mut libc::c_void>, + pub with_semaphore: bool, + pub semaphore_filename: Option, + pub with_playlist: bool, + pub playlist_filename: Option, + pub renaming_extension: i32, + pub append_mode: bool, +} +impl Default for ccxr_s_write { + fn default() -> Self { + ccxr_s_write { + fh: -1, + temporarily_closed: false, + filename: None, + original_filename: None, + spupng_data: None, + with_semaphore: false, + semaphore_filename: None, + with_playlist: false, + playlist_filename: None, + renaming_extension: 0, + append_mode: false, + } + } +} +impl FromCType for ccxr_s_write { + unsafe fn from_ctype(c: ccx_s_write) -> Option { + let filename = if !c.filename.is_null() { + Some(CStr::from_ptr(c.filename).to_string_lossy().into_owned()) + } else { + None + }; + + let original_filename = if !c.original_filename.is_null() { + Some(CStr::from_ptr(c.original_filename).to_string_lossy().into_owned()) + } else { + None + }; + + let semaphore_filename = if !c.semaphore_filename.is_null() { + Some(CStr::from_ptr(c.semaphore_filename).to_string_lossy().into_owned()) + } else { + None + }; + + let playlist_filename = if !c.playlist_filename.is_null() { + Some(CStr::from_ptr(c.playlist_filename).to_string_lossy().into_owned()) + } else { + None + }; + + Some(ccxr_s_write { + fh: c.fh, + temporarily_closed: c.temporarily_closed != 0, + filename, + original_filename, + spupng_data: if c.spupng_data.is_null() { None } else { Some(c.spupng_data) }, + with_semaphore: c.with_semaphore != 0, + semaphore_filename, + with_playlist: c.with_playlist != 0, + playlist_filename, + renaming_extension: c.renaming_extension, + append_mode: c.append_mode != 0, + }) + } +} + +#[derive(Debug, Clone)] +pub struct ccxr_encoder_ctx { + pub buffer: Option>, + pub capacity: u32, + + pub srt_counter: u32, + pub cea_708_counter: u32, + + pub wrote_webvtt_header: u32, + pub wrote_ccd_channel_header: u8, + + pub send_to_srv: u32, + pub multiple_files: i32, + pub first_input_file: Option, + pub out: Option>, + pub nb_out: i32, + + pub in_fileformat: u32, + + pub keep_output_closed: u32, + pub force_flush: i32, + pub ucla: i32, + + pub timing: Option<*mut crate::bindings::ccx_common_timing_ctx>, + + pub encoding: Encoding, + pub write_format: crate::bindings::ccx_output_format, + pub generates_file: i32, + pub transcript_settings: Option<*mut crate::bindings::ccx_encoders_transcript_format>, + + pub no_bom: i32, + pub sentence_cap: i32, + pub filter_profanity: i32, + pub trim_subs: i32, + pub autodash: i32, + pub no_font_color: i32, + pub no_type_setting: i32, + pub gui_mode_reports: i32, + + pub subline: Option>, + + pub extract: i32, + pub dtvcc_extract: i32, + pub dtvcc_writers: [Option; 4], + + pub prev_start: i64, + pub subs_delay: i64, + pub last_displayed_subs_ms: i64, + pub date_format: crate::bindings::ccx_output_date_format, + pub millis_separator: u8, + + pub startcredits_displayed: i32, + pub start_credits_text: Option, + pub end_credits_text: Option, + pub startcreditsnotbefore: Option, + pub startcreditsnotafter: Option, + pub startcreditsforatleast: Option, + pub startcreditsforatmost: Option, + pub endcreditsforatleast: Option, + pub endcreditsforatmost: Option, + + pub encoded_crlf: Option, + pub encoded_crlf_length: u32, + pub encoded_br: Option, + pub encoded_br_length: u32, + + pub header_printed_flag: i32, + pub next_caption_time: Option, + pub cdp_hdr_seq: u32, + pub force_dropframe: i32, + pub new_sentence: i32, + pub program_number: i32, + + pub list: Option, + pub sbs_enabled: i32, + pub prev: Option<*mut encoder_ctx>, + pub write_previous: i32, + pub is_mkv: i32, + pub last_string: Option, + + pub segment_pending: i32, + pub segment_last_key_frame: i32, + + pub nospupngocr: i32, +} + +impl Default for ccxr_encoder_ctx { + fn default() -> Self { + ccxr_encoder_ctx { + buffer: Some(Vec::with_capacity(1024)), + capacity: 1024, + + srt_counter: 0, + cea_708_counter: 0, + + wrote_webvtt_header: 0, + wrote_ccd_channel_header: 0, + + send_to_srv: 0, + multiple_files: 0, + first_input_file: None, + out: None, + nb_out: 0, + + in_fileformat: 1, + + keep_output_closed: 0, + force_flush: 0, + ucla: 0, + + timing: None, + + encoding: Encoding::default(), + write_format: unsafe { std::mem::transmute(1i32) }, + generates_file: 0, + transcript_settings: None, + + no_bom: 0, + sentence_cap: 0, + filter_profanity: 0, + trim_subs: 0, + autodash: 0, + no_font_color: 0, + no_type_setting: 0, + gui_mode_reports: 0, + + subline: Some(Vec::with_capacity(1024)), + + extract: 0, + dtvcc_extract: 0, + dtvcc_writers: [None; 4], + + prev_start: 0, + subs_delay: 0, + last_displayed_subs_ms: 0, + date_format: unsafe { std::mem::transmute(0i32) }, + millis_separator: b',', + + startcredits_displayed: 0, + start_credits_text: None, + end_credits_text: None, + startcreditsnotbefore: None, + startcreditsnotafter: None, + startcreditsforatleast: None, + startcreditsforatmost: None, + endcreditsforatleast: None, + endcreditsforatmost: None, + + encoded_crlf: Some("\r\n".to_string()), + encoded_crlf_length: 2, + encoded_br: Some("
".to_string()), + encoded_br_length: 4, + + header_printed_flag: 0, + next_caption_time: None, + cdp_hdr_seq: 0, + force_dropframe: 0, + new_sentence: 0, + program_number: 0, + + list: None, + sbs_enabled: 0, + prev: None, + write_previous: 0, + is_mkv: 0, + last_string: None, + + segment_pending: 0, + segment_last_key_frame: 0, + + nospupngocr: 0, + } + } +} + +impl ccxr_encoder_ctx { + pub unsafe fn from_ctype(c: *mut encoder_ctx) -> Option { + if c.is_null() { + return None; + } + let c_ref = &*c; + + let buffer = if !c_ref.buffer.is_null() && c_ref.capacity > 0 { + let slice = std::slice::from_raw_parts(c_ref.buffer, c_ref.capacity as usize); + Some(slice.to_vec()) + } else { + None + }; + + let subline = if !c_ref.subline.is_null() { + let slice = std::slice::from_raw_parts(c_ref.subline, 1024); + Some(slice.to_vec()) + } else { + None + }; + + let first_input_file = if !c_ref.first_input_file.is_null() { + Some(CStr::from_ptr(c_ref.first_input_file).to_string_lossy().into_owned()) + } else { + None + }; + + let start_credits_text = if !c_ref.start_credits_text.is_null() { + Some(CStr::from_ptr(c_ref.start_credits_text).to_string_lossy().into_owned()) + } else { + None + }; + + let end_credits_text = if !c_ref.end_credits_text.is_null() { + Some(CStr::from_ptr(c_ref.end_credits_text).to_string_lossy().into_owned()) + } else { + None + }; + + let last_string = if !c_ref.last_string.is_null() { + Some(CStr::from_ptr(c_ref.last_string).to_string_lossy().into_owned()) + } else { + None + }; + + let encoded_crlf = { + let bytes = &c_ref.encoded_crlf; + let len = bytes.iter().position(|&b| b == 0).unwrap_or(16); + Some(String::from_utf8_lossy(&bytes[..len]).to_string()) + }; + + let encoded_br = { + let bytes = &c_ref.encoded_br; + let len = bytes.iter().position(|&b| b == 0).unwrap_or(16); + Some(String::from_utf8_lossy(&bytes[..len]).to_string()) + }; + + let mut dtvcc_writers = [None; 4]; + for i in 0..4 { + dtvcc_writers[i] = Some(c_ref.dtvcc_writers[i]); + } + + Some(ccxr_encoder_ctx { + buffer, + capacity: c_ref.capacity, + + srt_counter: c_ref.srt_counter, + cea_708_counter: c_ref.cea_708_counter, + + wrote_webvtt_header: c_ref.wrote_webvtt_header, + wrote_ccd_channel_header: c_ref.wrote_ccd_channel_header as u8, + + send_to_srv: c_ref.send_to_srv, + multiple_files: c_ref.multiple_files, + first_input_file, + out: if !c_ref.out.is_null() && c_ref.nb_out > 0 { + let mut out_vec = Vec::new(); + for i in 0..c_ref.nb_out { + let c_write = unsafe { &*c_ref.out.offset(i as isize) }; + if let Some(rust_write) = unsafe { ccxr_s_write::from_ctype(*c_write) } { + out_vec.push(rust_write); + } + } + Some(out_vec) + } else { + None + }, + nb_out: c_ref.nb_out, + + in_fileformat: c_ref.in_fileformat, + + keep_output_closed: c_ref.keep_output_closed, + force_flush: c_ref.force_flush, + ucla: c_ref.ucla, + + timing: if !c_ref.timing.is_null() { Some(c_ref.timing) } else { None }, + + encoding: Encoding::from_ctype(c_ref.encoding).unwrap_or(Encoding::default()), + write_format: c_ref.write_format, + generates_file: c_ref.generates_file, + transcript_settings: if !c_ref.transcript_settings.is_null() { Some(c_ref.transcript_settings) } else { None }, + + no_bom: c_ref.no_bom, + sentence_cap: c_ref.sentence_cap, + filter_profanity: c_ref.filter_profanity, + trim_subs: c_ref.trim_subs, + autodash: c_ref.autodash, + no_font_color: c_ref.no_font_color, + no_type_setting: c_ref.no_type_setting, + gui_mode_reports: c_ref.gui_mode_reports, + + subline, + + extract: c_ref.extract, + dtvcc_extract: c_ref.dtvcc_extract, + dtvcc_writers, + + prev_start: c_ref.prev_start, + subs_delay: c_ref.subs_delay, + last_displayed_subs_ms: c_ref.last_displayed_subs_ms, + date_format: c_ref.date_format, + millis_separator: c_ref.millis_separator as u8, + + startcredits_displayed: c_ref.startcredits_displayed, + start_credits_text, + end_credits_text, + startcreditsnotbefore: Some(c_ref.startcreditsnotbefore), + startcreditsnotafter: Some(c_ref.startcreditsnotafter), + startcreditsforatleast: Some(c_ref.startcreditsforatleast), + startcreditsforatmost: Some(c_ref.startcreditsforatmost), + endcreditsforatleast: Some(c_ref.endcreditsforatleast), + endcreditsforatmost: Some(c_ref.endcreditsforatmost), + + encoded_crlf, + encoded_crlf_length: c_ref.encoded_crlf_length, + encoded_br, + encoded_br_length: c_ref.encoded_br_length, + + header_printed_flag: c_ref.header_printed_flag, + next_caption_time: Some(c_ref.next_caption_time), + cdp_hdr_seq: c_ref.cdp_hdr_seq, + force_dropframe: c_ref.force_dropframe, + new_sentence: c_ref.new_sentence, + program_number: c_ref.program_number, + + list: Some(c_ref.list), + sbs_enabled: c_ref.sbs_enabled, + prev: if !c_ref.prev.is_null() { Some(c_ref.prev) } else { None }, + write_previous: c_ref.write_previous, + is_mkv: c_ref.is_mkv, + last_string, + + segment_pending: c_ref.segment_pending, + segment_last_key_frame: c_ref.segment_last_key_frame, + + nospupngocr: c_ref.nospupngocr, + }) + } +} + +pub fn copy_encoder_ctx_c_to_rust(c_ctx: *mut encoder_ctx) -> ccxr_encoder_ctx { + if c_ctx.is_null() { + return ccxr_encoder_ctx::default(); + } + + unsafe { + ccxr_encoder_ctx::from_ctype(c_ctx).unwrap_or_default() + } +} +pub unsafe fn copy_encoder_ctx_rust_to_c(rust_ctx: &mut ccxr_encoder_ctx, c_ctx: *mut encoder_ctx) { + if c_ctx.is_null() { + return; + } + + let c = &mut *c_ctx; + + c.srt_counter = rust_ctx.srt_counter; + c.cea_708_counter = rust_ctx.cea_708_counter; + + if let Some(ref buffer) = rust_ctx.buffer { + if !c.buffer.is_null() && c.capacity > 0 { + let copy_len = std::cmp::min(buffer.len(), c.capacity as usize); + std::ptr::copy_nonoverlapping(buffer.as_ptr(), c.buffer, copy_len); + } + } + + if let Some(ref subline) = rust_ctx.subline { + if !c.subline.is_null() { + let copy_len = std::cmp::min(subline.len(), 1024); + std::ptr::copy_nonoverlapping(subline.as_ptr(), c.subline, copy_len); + } + } + + if let Some(ref crlf) = rust_ctx.encoded_crlf { + let crlf_bytes = crlf.as_bytes(); + let copy_len = std::cmp::min(crlf_bytes.len(), 15); + c.encoded_crlf[..copy_len].copy_from_slice(&crlf_bytes[..copy_len]); + c.encoded_crlf[copy_len] = 0; + } + + if let Some(ref br) = rust_ctx.encoded_br { + let br_bytes = br.as_bytes(); + let copy_len = std::cmp::min(br_bytes.len(), 15); + c.encoded_br[..copy_len].copy_from_slice(&br_bytes[..copy_len]); + c.encoded_br[copy_len] = 0; + } + + c.encoded_crlf_length = rust_ctx.encoded_crlf_length; + c.encoded_br_length = rust_ctx.encoded_br_length; + c.new_sentence = rust_ctx.new_sentence; + c.sbs_enabled = rust_ctx.sbs_enabled; + c.segment_pending = rust_ctx.segment_pending; + c.segment_last_key_frame = rust_ctx.segment_last_key_frame; +} diff --git a/src/rust/src/libccxr_exports/mod.rs b/src/rust/src/libccxr_exports/mod.rs index d4ed4ef8c..c1992a083 100644 --- a/src/rust/src/libccxr_exports/mod.rs +++ b/src/rust/src/libccxr_exports/mod.rs @@ -2,6 +2,7 @@ pub mod bitstream; pub mod time; +pub mod encoder_ctx; use crate::ccx_options; use lib_ccxr::util::log::*; use lib_ccxr::util::{bits::*, levenshtein::*}; diff --git a/src/rust/src/utils.rs b/src/rust/src/utils.rs index 90ada59dd..7ebf8f6de 100644 --- a/src/rust/src/utils.rs +++ b/src/rust/src/utils.rs @@ -1,4 +1,5 @@ //! Some utility functions to deal with values from C bindings +use std::os::raw::c_char; use std::ffi; /// Check if the value is true (Set to 1). Use only for values from C bindings @@ -40,7 +41,6 @@ pub fn null_pointer() -> *mut T { std::ptr::null_mut() } -use std::os::raw::c_char; pub fn string_to_c_chars(strs: Vec) -> *mut *mut c_char { let mut c_strs: Vec<*mut c_char> = Vec::new(); @@ -74,3 +74,4 @@ pub fn get_zero_allocated_obj() -> Box { Box::from_raw(allocation) } } + diff --git a/src/rust/wrapper.h b/src/rust/wrapper.h index 2400d7d08..cb75b3bd2 100644 --- a/src/rust/wrapper.h +++ b/src/rust/wrapper.h @@ -12,3 +12,5 @@ #include "../lib_ccx/utility.h" #include "../lib_ccx/ccx_encoders_helpers.h" #include "../lib_ccx/cc_bitstream.h" +#include "../lib_ccx/ccx_encoders_common.h" +#include "../lib_ccx/ccx_encoders_structs.h" \ No newline at end of file