From 97c981d4ef0e29b6fb42b0561b2068ec2e541b52 Mon Sep 17 00:00:00 2001 From: VivianVRodrigues Date: Wed, 3 Sep 2025 11:34:40 +0000 Subject: [PATCH 01/10] Some updates of encoder_srt_rust --- src/lib_ccx/ccx_encoders_srt.c | 3 + src/rust/build.rs | 1 + src/rust/src/encoder/ccxr_encoder_srt.rs | 97 +++++++++++++++++++++ src/rust/src/libccxr_exports/encoder_ctx.rs | 47 ++++++++++ src/rust/src/utils.rs | 24 +++++ src/rust/wrapper.h | 2 + 6 files changed, 174 insertions(+) create mode 100644 src/rust/src/encoder/ccxr_encoder_srt.rs create mode 100644 src/rust/src/libccxr_exports/encoder_ctx.rs diff --git a/src/lib_ccx/ccx_encoders_srt.c b/src/lib_ccx/ccx_encoders_srt.c index 9b3a027c5..64c62f0ad 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; 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..44c2df3de --- /dev/null +++ b/src/rust/src/encoder/ccxr_encoder_srt.rs @@ -0,0 +1,97 @@ +use crate::utils::write_wrapper_os; +use crate::common::encode_line; +use crate::libccxr_exports::time::ccxr_millis_to_time; +use std::ffi::CString; +use std::io; +use std::os::raw::{c_int, c_void}; +use crate::encoder::context::EncoderCtx; // assuming you have this +use crate::bindings::ccx_s_write; + +/// Safe wrapper to get the file handle from EncoderCtxRust +fn get_out_fh(out: Option<*mut ccx_s_write>) -> io::Result { + if let Some(ptr) = out { + if ptr.is_null() { + return Err(io::Error::new(io::ErrorKind::Other, "Null out pointer")); + } + let out_ref = unsafe { &mut *ptr }; + Ok(out_ref.fh) // assuming fh is i32 in C + } else { + Err(io::Error::new(io::ErrorKind::Other, "No out pointer set")) + } +} + +/// Rewrite of write_stringz_srt_r using EncoderCtxRust +#[no_mangle] +pub unsafe extern "C" fn ccxr_write_stringz_srt( + string: &str, + context_ptr: &mut crate::EncoderCtxRust, + ms_start: i64, + ms_end: i64, +) -> Result<(), io::Error> { + if string.is_empty() { + return Ok(()); + } + let mut context = copy_encoder_ctx_c_to_rust(context_ptr); + // Convert times + let (h1, m1, s1, ms1) = ccxr_millis_to_time(ms_start); + let (h2, m2, s2, ms2) = ccxr_millis_to_time(ms_end - 1); + + // Increment counter + context.srt_counter += 1; + + // Get encoded CRLF string + let crlf = context.encoded_crlf.as_deref().unwrap_or("\r\n"); + + // Get file handle + let fh = get_out_fh(context.out)?; + + // Timeline counter line + let timeline = format!("{}{}", context.srt_counter, crlf); + let used = encode_line(&timeline, &mut context.buffer)?; + write_wrapper_os(fh, &context.buffer[..used])?; + + // Timeline timecode line + let timeline = format!( + "{:02}:{:02}:{:02},{:03} --> {:02}:{:02}:{:02},{:03}{}", + h1, m1, s1, ms1, + h2, m2, s2, ms2, + crlf + ); + let used = encode_line(&timeline, &mut context.buffer)?; + write_wrapper_os(fh, &context.buffer[..used])?; + + // --- Handle the text itself --- + let mut unescaped = Vec::with_capacity(string.len() + 1); + + let mut chars = string.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 = encode_line(&line, &mut context.buffer)?; + write_wrapper_os(fh, &context.buffer[..used])?; + write_wrapper_os(fh, crlf.as_bytes())?; + } + pos += end + 1; + } else { + break; + } + } + + // Final CRLF + write_wrapper_os(fh, crlf.as_bytes())?; + + Ok(()) +} 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..f704b3674 --- /dev/null +++ b/src/rust/src/libccxr_exports/encoder_ctx.rs @@ -0,0 +1,47 @@ +use crate::bindings::{encoder_ctx,ccx_s_write}; +use std::os::raw::{c_int, c_uint}; + +/// Rust representation of encoder_ctx +#[derive(Debug)] +pub struct EncoderCtxRust { + pub srt_counter: u32, + pub buffer: Option>, // Owned Vec, not slice + pub encoded_crlf: Option, // Owned String + pub out: Option<*mut ccx_s_write>,// Single pointer +} +pub fn copy_encoder_ctx_c_to_rust(c_ctx: *mut encoder_ctx) -> EncoderCtxRust{ + assert!(!c_ctx.is_null()); + let c = unsafe { &* c_ctx }; + + // Copy buffer into Vec + let buffer = if !c.buffer.is_null() && c.capacity > 0 { + let slice = unsafe { std::slice::from_raw_parts(c.buffer, c.capacity as usize) }; + Some(slice.to_vec()) + } else { + None + }; + + // Copy encoded_crlf into String + let encoded_crlf = if !c.encoded_crlf.is_null() { + let bytes = unsafe { std::slice::from_raw_parts(c.encoded_crlf, 16) }; + // trim at first 0 + let len = bytes.iter().position(|&b| b == 0).unwrap_or(16); + Some(String::from_utf8_lossy(&bytes[..len]).to_string()) + } else { + None + }; + + // Single output pointer + let out = if !c.out.is_null() { + Some(c.out) + } else { + None + }; + + EncoderCtxRust { + srt_counter: c.srt_counter, + buffer, + encoded_crlf, + out, + } +} \ No newline at end of file diff --git a/src/rust/src/utils.rs b/src/rust/src/utils.rs index 90ada59dd..64e11ac36 100644 --- a/src/rust/src/utils.rs +++ b/src/rust/src/utils.rs @@ -1,3 +1,10 @@ +use std::io; +use std::os::raw::{c_int, c_void, c_char}; +use libc::size_t; + +extern "C" { + fn write(fd: c_int, buf: *const c_void, count: size_t) -> isize; +} //! Some utility functions to deal with values from C bindings use std::ffi; @@ -74,3 +81,20 @@ pub fn get_zero_allocated_obj() -> Box { Box::from_raw(allocation) } } + +pub fn write_wrapper_os(fd: c_int, mut buf: &[u8])-> Result<(), io::Error>{ + while !buf.is_empty() { + let written = unsafe { + write(fd, buf.as_ptr() as *const c_void, buf.len() as size_t) + }; + + if written == -1 { + return Err(io::Error::last_os_error()); + } + + // Move the slice forward by the number of bytes written + buf = &buf[written as usize..]; + } + + Ok(()) +} 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 From 5b1e7a5cb99e05a708ff88be08c915db43b415b2 Mon Sep 17 00:00:00 2001 From: Vivian Rodrigues Date: Fri, 5 Sep 2025 11:13:56 +0530 Subject: [PATCH 02/10] some updates --- src/lib_ccx/utility.h | 1 + src/rust/src/encoder/ccxr_encoder_srt.rs | 114 ++++++++++++++++---- src/rust/src/libccxr_exports/encoder_ctx.rs | 30 ++++++ 3 files changed, 123 insertions(+), 22 deletions(-) diff --git a/src/lib_ccx/utility.h b/src/lib_ccx/utility.h index 6fc6eafa1..56925ea14 100644 --- a/src/lib_ccx/utility.h +++ b/src/lib_ccx/utility.h @@ -34,6 +34,7 @@ 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); #endif int levenshtein_dist_char (const char *s1, const char *s2, unsigned s1len, unsigned s2len); diff --git a/src/rust/src/encoder/ccxr_encoder_srt.rs b/src/rust/src/encoder/ccxr_encoder_srt.rs index 44c2df3de..bac5aa1fe 100644 --- a/src/rust/src/encoder/ccxr_encoder_srt.rs +++ b/src/rust/src/encoder/ccxr_encoder_srt.rs @@ -1,10 +1,11 @@ use crate::utils::write_wrapper_os; use crate::common::encode_line; use crate::libccxr_exports::time::ccxr_millis_to_time; +use crate::libccxr_exports::encoder_ctx::{copy_encoder_ctx_c_to_rust, copy_encoder_ctx_rust_to_c, EncoderCtxRust}; +use lib_ccxr::util::encoding::Encoding; use std::ffi::CString; use std::io; use std::os::raw::{c_int, c_void}; -use crate::encoder::context::EncoderCtx; // assuming you have this use crate::bindings::ccx_s_write; /// Safe wrapper to get the file handle from EncoderCtxRust @@ -20,35 +21,87 @@ fn get_out_fh(out: Option<*mut ccx_s_write>) -> io::Result { } } +/// Safe wrapper for the existing encode_line function that works with EncoderCtxRust +fn safe_encode_line( + rust_ctx: &mut EncoderCtxRust, + c_ctx: *mut crate::bindings::encoder_ctx, + text: &[u8], +) -> Result { + if rust_ctx.buffer.is_none() { + return Err(io::Error::new(io::ErrorKind::Other, "No buffer available")); + } + + // Temporarily copy Rust context to C context for encoding + unsafe { + copy_encoder_ctx_rust_to_c(rust_ctx, c_ctx); + } + + let c_ctx_ref = unsafe { &mut *c_ctx }; + let buffer_slice = unsafe { + std::slice::from_raw_parts_mut(c_ctx_ref.buffer, c_ctx_ref.capacity as usize) + }; + + // Use the existing encode_line function from common.rs + let used = encode_line(c_ctx_ref, buffer_slice, text); + + // Copy the encoded buffer back to Rust context + if let Some(ref mut buffer) = rust_ctx.buffer { + let copy_len = std::cmp::min(used as usize, buffer.len()); + buffer[..copy_len].copy_from_slice(&buffer_slice[..copy_len]); + } + + Ok(used as usize) +} + /// Rewrite of write_stringz_srt_r using EncoderCtxRust #[no_mangle] pub unsafe extern "C" fn ccxr_write_stringz_srt( - string: &str, - context_ptr: &mut crate::EncoderCtxRust, + context: *mut crate::bindings::encoder_ctx, + string: *const std::os::raw::c_char, ms_start: i64, ms_end: i64, -) -> Result<(), io::Error> { - if string.is_empty() { - return Ok(()); +) -> c_int { + if context.is_null() || string.is_null() { + return 0; } - let mut context = copy_encoder_ctx_c_to_rust(context_ptr); + + 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 (h1, m1, s1, ms1) = ccxr_millis_to_time(ms_start); let (h2, m2, s2, ms2) = ccxr_millis_to_time(ms_end - 1); // Increment counter - context.srt_counter += 1; + rust_context.srt_counter += 1; // Get encoded CRLF string - let crlf = context.encoded_crlf.as_deref().unwrap_or("\r\n"); + let crlf = rust_context.encoded_crlf.as_deref().unwrap_or("\r\n"); // Get file handle - let fh = get_out_fh(context.out)?; + let fh = match get_out_fh(rust_context.out) { + Ok(fh) => fh, + Err(_) => return 0, + }; // Timeline counter line - let timeline = format!("{}{}", context.srt_counter, crlf); - let used = encode_line(&timeline, &mut context.buffer)?; - write_wrapper_os(fh, &context.buffer[..used])?; + let timeline = format!("{}{}", rust_context.srt_counter, crlf); + let used = match safe_encode_line(&mut rust_context, context, timeline.as_bytes()) { + Ok(u) => u, + Err(_) => return 0, + }; + if write_wrapper_os(fh, &rust_context.buffer.as_ref().unwrap()[..used]).is_err() { + return 0; + } // Timeline timecode line let timeline = format!( @@ -57,13 +110,18 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( h2, m2, s2, ms2, crlf ); - let used = encode_line(&timeline, &mut context.buffer)?; - write_wrapper_os(fh, &context.buffer[..used])?; + let used = match safe_encode_line(&mut rust_context, context, timeline.as_bytes()) { + Ok(u) => u, + Err(_) => return 0, + }; + if write_wrapper_os(fh, &rust_context.buffer.as_ref().unwrap()[..used]).is_err() { + return 0; + } // --- Handle the text itself --- - let mut unescaped = Vec::with_capacity(string.len() + 1); + let mut unescaped = Vec::with_capacity(string_cstr.len() + 1); - let mut chars = string.chars().peekable(); + let mut chars = string_cstr.chars().peekable(); while let Some(c) = chars.next() { if c == '\\' && chars.peek() == Some(&'n') { unescaped.push(0u8); @@ -80,9 +138,16 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( let slice = &unescaped[pos..pos + end]; if !slice.is_empty() { let line = String::from_utf8_lossy(slice); - let used = encode_line(&line, &mut context.buffer)?; - write_wrapper_os(fh, &context.buffer[..used])?; - write_wrapper_os(fh, crlf.as_bytes())?; + let used = match safe_encode_line(&mut rust_context, context, line.as_bytes()) { + Ok(u) => u, + Err(_) => return 0, + }; + if write_wrapper_os(fh, &rust_context.buffer.as_ref().unwrap()[..used]).is_err() { + return 0; + } + if write_wrapper_os(fh, crlf.as_bytes()).is_err() { + return 0; + } } pos += end + 1; } else { @@ -91,7 +156,12 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( } // Final CRLF - write_wrapper_os(fh, crlf.as_bytes())?; + if write_wrapper_os(fh, crlf.as_bytes()).is_err() { + return 0; + } + + // Copy the updated Rust context back to C context + copy_encoder_ctx_rust_to_c(&mut rust_context, context); - Ok(()) + 1 // Success } diff --git a/src/rust/src/libccxr_exports/encoder_ctx.rs b/src/rust/src/libccxr_exports/encoder_ctx.rs index f704b3674..10d32f2ce 100644 --- a/src/rust/src/libccxr_exports/encoder_ctx.rs +++ b/src/rust/src/libccxr_exports/encoder_ctx.rs @@ -1,4 +1,5 @@ use crate::bindings::{encoder_ctx,ccx_s_write}; +use lib_ccxr::util::encoding::Encoding; use std::os::raw::{c_int, c_uint}; /// Rust representation of encoder_ctx @@ -8,6 +9,7 @@ pub struct EncoderCtxRust { pub buffer: Option>, // Owned Vec, not slice pub encoded_crlf: Option, // Owned String pub out: Option<*mut ccx_s_write>,// Single pointer + pub encoding: Encoding, // Encoding type } pub fn copy_encoder_ctx_c_to_rust(c_ctx: *mut encoder_ctx) -> EncoderCtxRust{ assert!(!c_ctx.is_null()); @@ -43,5 +45,33 @@ pub fn copy_encoder_ctx_c_to_rust(c_ctx: *mut encoder_ctx) -> EncoderCtxRust{ buffer, encoded_crlf, out, + encoding: unsafe { Encoding::from_ctype(c.encoding).unwrap_or(Encoding::default()) }, + } +} + +/// Copy the Rust encoder context back to the C encoder context +pub unsafe fn copy_encoder_ctx_rust_to_c(rust_ctx: &mut EncoderCtxRust, c_ctx: *mut encoder_ctx) { + assert!(!c_ctx.is_null()); + let c = &mut *c_ctx; + + // Update the srt_counter + c.srt_counter = rust_ctx.srt_counter; + + // Copy buffer back if it exists + 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); + } + } + + // Copy encoded_crlf back if it exists + if let Some(ref crlf) = rust_ctx.encoded_crlf { + if !c.encoded_crlf.is_null() { + let crlf_bytes = crlf.as_bytes(); + let copy_len = std::cmp::min(crlf_bytes.len(), 15); // Leave room for null terminator + std::ptr::copy_nonoverlapping(crlf_bytes.as_ptr(), c.encoded_crlf, copy_len); + *c.encoded_crlf.add(copy_len) = 0; // Null terminate + } } } \ No newline at end of file From b3471d7882f1cdf9f0da2f769c0add782517dd80 Mon Sep 17 00:00:00 2001 From: Vivian Rodrigues Date: Fri, 5 Sep 2025 11:50:03 +0530 Subject: [PATCH 03/10] some changes --- src/rust/Cargo.lock | 1 + src/rust/Cargo.toml | 1 + src/rust/src/encoder/ccxr_encoder_srt.rs | 25 +++++++++++++++------ src/rust/src/encoder/mod.rs | 1 + src/rust/src/libccxr_exports/encoder_ctx.rs | 18 ++++++--------- src/rust/src/libccxr_exports/mod.rs | 1 + src/rust/src/utils.rs | 10 ++------- 7 files changed, 31 insertions(+), 26 deletions(-) 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/src/encoder/ccxr_encoder_srt.rs b/src/rust/src/encoder/ccxr_encoder_srt.rs index bac5aa1fe..e892c85a1 100644 --- a/src/rust/src/encoder/ccxr_encoder_srt.rs +++ b/src/rust/src/encoder/ccxr_encoder_srt.rs @@ -1,11 +1,9 @@ use crate::utils::write_wrapper_os; -use crate::common::encode_line; +use crate::encoder::common::encode_line; use crate::libccxr_exports::time::ccxr_millis_to_time; use crate::libccxr_exports::encoder_ctx::{copy_encoder_ctx_c_to_rust, copy_encoder_ctx_rust_to_c, EncoderCtxRust}; -use lib_ccxr::util::encoding::Encoding; -use std::ffi::CString; use std::io; -use std::os::raw::{c_int, c_void}; +use std::os::raw::c_int; use crate::bindings::ccx_s_write; /// Safe wrapper to get the file handle from EncoderCtxRust @@ -78,14 +76,27 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( let mut rust_context = copy_encoder_ctx_c_to_rust(context); // Convert times - let (h1, m1, s1, ms1) = ccxr_millis_to_time(ms_start); - let (h2, m2, s2, ms2) = ccxr_millis_to_time(ms_end - 1); + 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); + } // Increment counter rust_context.srt_counter += 1; // Get encoded CRLF string - let crlf = rust_context.encoded_crlf.as_deref().unwrap_or("\r\n"); + let crlf = rust_context.encoded_crlf.as_deref().unwrap_or("\r\n").to_string(); // Get file handle let fh = match get_out_fh(rust_context.out) { 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 index 10d32f2ce..6de71ee06 100644 --- a/src/rust/src/libccxr_exports/encoder_ctx.rs +++ b/src/rust/src/libccxr_exports/encoder_ctx.rs @@ -1,6 +1,6 @@ use crate::bindings::{encoder_ctx,ccx_s_write}; +use crate::encoder::FromCType; use lib_ccxr::util::encoding::Encoding; -use std::os::raw::{c_int, c_uint}; /// Rust representation of encoder_ctx #[derive(Debug)] @@ -24,13 +24,11 @@ pub fn copy_encoder_ctx_c_to_rust(c_ctx: *mut encoder_ctx) -> EncoderCtxRust{ }; // Copy encoded_crlf into String - let encoded_crlf = if !c.encoded_crlf.is_null() { - let bytes = unsafe { std::slice::from_raw_parts(c.encoded_crlf, 16) }; + let encoded_crlf = { + let bytes = &c.encoded_crlf; // trim at first 0 let len = bytes.iter().position(|&b| b == 0).unwrap_or(16); Some(String::from_utf8_lossy(&bytes[..len]).to_string()) - } else { - None }; // Single output pointer @@ -67,11 +65,9 @@ pub unsafe fn copy_encoder_ctx_rust_to_c(rust_ctx: &mut EncoderCtxRust, c_ctx: * // Copy encoded_crlf back if it exists if let Some(ref crlf) = rust_ctx.encoded_crlf { - if !c.encoded_crlf.is_null() { - let crlf_bytes = crlf.as_bytes(); - let copy_len = std::cmp::min(crlf_bytes.len(), 15); // Leave room for null terminator - std::ptr::copy_nonoverlapping(crlf_bytes.as_ptr(), c.encoded_crlf, copy_len); - *c.encoded_crlf.add(copy_len) = 0; // Null terminate - } + let crlf_bytes = crlf.as_bytes(); + let copy_len = std::cmp::min(crlf_bytes.len(), 15); // Leave room for null terminator + c.encoded_crlf[..copy_len].copy_from_slice(&crlf_bytes[..copy_len]); + c.encoded_crlf[copy_len] = 0; // Null terminate } } \ No newline at end of file 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 64e11ac36..7c28a472c 100644 --- a/src/rust/src/utils.rs +++ b/src/rust/src/utils.rs @@ -1,11 +1,6 @@ +//! Some utility functions to deal with values from C bindings use std::io; use std::os::raw::{c_int, c_void, c_char}; -use libc::size_t; - -extern "C" { - fn write(fd: c_int, buf: *const c_void, count: size_t) -> isize; -} -//! Some utility functions to deal with values from C bindings use std::ffi; /// Check if the value is true (Set to 1). Use only for values from C bindings @@ -47,7 +42,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(); @@ -85,7 +79,7 @@ pub fn get_zero_allocated_obj() -> Box { pub fn write_wrapper_os(fd: c_int, mut buf: &[u8])-> Result<(), io::Error>{ while !buf.is_empty() { let written = unsafe { - write(fd, buf.as_ptr() as *const c_void, buf.len() as size_t) + libc::write(fd, buf.as_ptr() as *const c_void, buf.len()) }; if written == -1 { From df81343bda90e629aebb18eb660a957a23831a82 Mon Sep 17 00:00:00 2001 From: Vivian Rodrigues Date: Sun, 26 Oct 2025 22:27:48 +0530 Subject: [PATCH 04/10] Refactor SRT encoder to use ccxr_encoder_ctx with Vec - Renamed CcxSWriteRust to ccxr_s_write for consistency - Renamed EncoderCtxRust to ccxr_encoder_ctx - Changed out field from raw pointer to Vec - Unified encode_line functions into single optimal version - Added ccxr_write_cc_bitmap_as_srt (OCR stub) - Added ccxr_write_cc_subtitle_as_srt with proper cleanup - Added ccxr_write_cc_buffer_as_srt using Rust context - Updated C code to call Rust implementations - Improved type safety and eliminated unsafe pointer dereferencing --- src/lib_ccx/ccx_encoders_srt.c | 9 + src/lib_ccx/utility.h | 3 + src/rust/src/encoder/ccxr_encoder_srt.rs | 333 +++++++++--- src/rust/src/encoder/common.rs | 194 ++++--- src/rust/src/encoder/g608.rs | 5 +- src/rust/src/libccxr_exports/encoder_ctx.rs | 568 ++++++++++++++++++-- src/rust/src/utils.rs | 19 +- 7 files changed, 886 insertions(+), 245 deletions(-) diff --git a/src/lib_ccx/ccx_encoders_srt.c b/src/lib_ccx/ccx_encoders_srt.c index 64c62f0ad..6ada803fc 100644 --- a/src/lib_ccx/ccx_encoders_srt.c +++ b/src/lib_ccx/ccx_encoders_srt.c @@ -83,6 +83,9 @@ int write_stringz_as_srt(char *string, struct encoder_ctx *context, LLONG ms_sta int write_cc_bitmap_as_srt(struct cc_subtitle *sub, struct encoder_ctx *context) { + #ifndef DISABLE_RUST + return ccxr_write_cc_bitmap_as_srt(sub, context); + #endif int ret = 0; #ifdef ENABLE_OCR struct cc_bitmap *rect; @@ -145,6 +148,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; @@ -173,6 +179,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 56925ea14..cf779d5b2 100644 --- a/src/lib_ccx/utility.h +++ b/src/lib_ccx/utility.h @@ -35,6 +35,9 @@ 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_bitmap_as_srt(struct cc_subtitle *sub, struct encoder_ctx *context); +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/src/encoder/ccxr_encoder_srt.rs b/src/rust/src/encoder/ccxr_encoder_srt.rs index e892c85a1..9a9bf9b91 100644 --- a/src/rust/src/encoder/ccxr_encoder_srt.rs +++ b/src/rust/src/encoder/ccxr_encoder_srt.rs @@ -1,57 +1,12 @@ -use crate::utils::write_wrapper_os; +use crate::encoder::g608::write_wrapped; use crate::encoder::common::encode_line; use crate::libccxr_exports::time::ccxr_millis_to_time; -use crate::libccxr_exports::encoder_ctx::{copy_encoder_ctx_c_to_rust, copy_encoder_ctx_rust_to_c, EncoderCtxRust}; -use std::io; -use std::os::raw::c_int; -use crate::bindings::ccx_s_write; - -/// Safe wrapper to get the file handle from EncoderCtxRust -fn get_out_fh(out: Option<*mut ccx_s_write>) -> io::Result { - if let Some(ptr) = out { - if ptr.is_null() { - return Err(io::Error::new(io::ErrorKind::Other, "Null out pointer")); - } - let out_ref = unsafe { &mut *ptr }; - Ok(out_ref.fh) // assuming fh is i32 in C - } else { - Err(io::Error::new(io::ErrorKind::Other, "No out pointer set")) - } -} - -/// Safe wrapper for the existing encode_line function that works with EncoderCtxRust -fn safe_encode_line( - rust_ctx: &mut EncoderCtxRust, - c_ctx: *mut crate::bindings::encoder_ctx, - text: &[u8], -) -> Result { - if rust_ctx.buffer.is_none() { - return Err(io::Error::new(io::ErrorKind::Other, "No buffer available")); - } - - // Temporarily copy Rust context to C context for encoding - unsafe { - copy_encoder_ctx_rust_to_c(rust_ctx, c_ctx); - } - - let c_ctx_ref = unsafe { &mut *c_ctx }; - let buffer_slice = unsafe { - std::slice::from_raw_parts_mut(c_ctx_ref.buffer, c_ctx_ref.capacity as usize) - }; - - // Use the existing encode_line function from common.rs - let used = encode_line(c_ctx_ref, buffer_slice, text); - - // Copy the encoded buffer back to Rust context - if let Some(ref mut buffer) = rust_ctx.buffer { - let copy_len = std::cmp::min(used as usize, buffer.len()); - buffer[..copy_len].copy_from_slice(&buffer_slice[..copy_len]); - } - - Ok(used as usize) -} +//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 EncoderCtxRust +/// 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, @@ -98,19 +53,28 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( // Get encoded CRLF string let crlf = rust_context.encoded_crlf.as_deref().unwrap_or("\r\n").to_string(); - // Get file handle - let fh = match get_out_fh(rust_context.out) { - Ok(fh) => fh, - Err(_) => return 0, + // Get file handle directly from Rust context - now safe! + let fh = if let Some(ref out_vec) = rust_context.out { + if out_vec.is_empty() { + return 0; + } + out_vec[0].fh // ✅ Safe access - no unsafe needed! + } else { + return 0; }; - // Timeline counter line + // Timeline counter line - encode directly using Rust context let timeline = format!("{}{}", rust_context.srt_counter, crlf); - let used = match safe_encode_line(&mut rust_context, context, timeline.as_bytes()) { - Ok(u) => u, - Err(_) => return 0, + let timeline_bytes = timeline.as_bytes(); + + // Use the Rust encode_line function directly with Rust context + 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_wrapper_os(fh, &rust_context.buffer.as_ref().unwrap()[..used]).is_err() { + if write_wrapped(fh, buffer_slice).is_err() { return 0; } @@ -121,11 +85,14 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( h2, m2, s2, ms2, crlf ); - let used = match safe_encode_line(&mut rust_context, context, timeline.as_bytes()) { - Ok(u) => u, - Err(_) => return 0, + 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_wrapper_os(fh, &rust_context.buffer.as_ref().unwrap()[..used]).is_err() { + if write_wrapped(fh, buffer_slice).is_err() { return 0; } @@ -147,19 +114,21 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( 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 = match safe_encode_line(&mut rust_context, context, line.as_bytes()) { - Ok(u) => u, - Err(_) => return 0, - }; - if write_wrapper_os(fh, &rust_context.buffer.as_ref().unwrap()[..used]).is_err() { - return 0; - } - if write_wrapper_os(fh, crlf.as_bytes()).is_err() { - return 0; - } - } + 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; @@ -167,7 +136,7 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( } // Final CRLF - if write_wrapper_os(fh, crlf.as_bytes()).is_err() { + if write_wrapped(fh, crlf.as_bytes()).is_err() { return 0; } @@ -176,3 +145,211 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( 1 // Success } + + +#[no_mangle] +pub unsafe extern "C" fn ccxr_write_cc_bitmap_as_srt( + sub: *mut cc_subtitle, + context: *mut encoder_ctx, +) -> c_int { + // Call the C implementation since OCR functions may not be available + extern "C" { + fn write_cc_bitmap_as_srt(sub: *mut cc_subtitle, context: *mut encoder_ctx) -> c_int; + } + + write_cc_bitmap_as_srt(sub, context) +} + +/// Write text-based cc_subtitle structures as SRT +/// This is a port of write_cc_subtitle_as_srt from C +/// Simplified version that just calls ccxr_write_stringz_srt +#[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; // Save original starting point + let mut lsub = sub; // Track last subtitle processed + let mut current_sub = sub; + + // Phase 1: Process linked list of subtitles (forward pass) + while !current_sub.is_null() { + let sub_ref = &mut *current_sub; + + // Check if this is text type (CC_TEXT = 0 in enum) + const CC_TEXT: u32 = 0; + if sub_ref.type_ == CC_TEXT { + if !sub_ref.data.is_null() { + // Call the existing ccxr_write_stringz_srt function directly + let write_result = ccxr_write_stringz_srt( + context, + sub_ref.data as *const c_char, + sub_ref.start_time, + sub_ref.end_time, + ); + + // Free the data + extern "C" { + fn freep(ptr: *mut *mut std::ffi::c_void); + } + freep(&mut sub_ref.data); + sub_ref.nb_data = 0; + + // Update ret if write succeeded + if write_result > 0 { + ret = 1; + } + } + } + + lsub = current_sub; // Track the last subtitle + current_sub = sub_ref.next; + } + + // Phase 2: Free the subtitle chain structs themselves (backward pass) + // Walk backwards from lsub to osub, freeing each node + 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 +} + +/// Write EIA-608 screen buffers as SRT +/// 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; // Don't write empty screens + } + + // Convert times using Rust context + 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); + + // Increment counter in Rust context + rust_ctx.srt_counter += 1; + + // Get CRLF from Rust context - clone to avoid borrow issues + let crlf = rust_ctx.encoded_crlf.as_deref().unwrap_or("\r\n").to_string(); + + // Get file handle from Rust context - now safe! + 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; + }; + + // Write counter line using Rust context and buffer + let timeline = format!("{}{}", rust_ctx.srt_counter, crlf); + if let Some(ref mut buffer) = rust_ctx.buffer { + let encoding = rust_ctx.encoding; // Extract encoding to avoid borrow issues + let used = encode_line(encoding, buffer, timeline.as_bytes()); + let _ = write_wrapped(fh, &buffer[..used]); + } + + // Write timestamp line using Rust context + 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 { + // Get encoded line from C function + 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; + } + } + } + + // Final CRLF + let _ = write_wrapped(fh, crlf.as_bytes()); + + // Get the updated context back to Rust + let rust_ctx = copy_encoder_ctx_c_to_rust(context); + + // Copy final Rust context back to C (in case anything changed) + 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/libccxr_exports/encoder_ctx.rs b/src/rust/src/libccxr_exports/encoder_ctx.rs index 6de71ee06..b828a4211 100644 --- a/src/rust/src/libccxr_exports/encoder_ctx.rs +++ b/src/rust/src/libccxr_exports/encoder_ctx.rs @@ -1,61 +1,504 @@ -use crate::bindings::{encoder_ctx,ccx_s_write}; +use crate::bindings::{encoder_ctx, ccx_s_write}; use crate::encoder::FromCType; use lib_ccxr::util::encoding::Encoding; +use std::ffi::CStr; -/// Rust representation of encoder_ctx -#[derive(Debug)] -pub struct EncoderCtxRust { +//Complete ccx_s_write in the rust side to match the C side and direct use in the rust code +#[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, + } + } +} +// Convert the C struct (ccx_s_write) to the Rust struct (ccxr_s_write) +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, + }) + } +} + + + +// Complete encoder_ctx in the rust side to match the C side and direct use in the rust code +#[derive(Debug, Clone)] +pub struct ccxr_encoder_ctx { + // Common buffer used by all encoder + pub buffer: Option>, + pub capacity: u32, + + // Keep count of subtitles pub srt_counter: u32, - pub buffer: Option>, // Owned Vec, not slice - pub encoded_crlf: Option, // Owned String - pub out: Option<*mut ccx_s_write>,// Single pointer - pub encoding: Encoding, // Encoding type + pub cea_708_counter: u32, + + // WebVTT header flag + pub wrote_webvtt_header: u32, + pub wrote_ccd_channel_header: i8, + + // Input outputs + pub send_to_srv: u32, // Flag giving hint that output is send to server through network + pub multiple_files: i32, // Used only in Spupng output + pub first_input_file: Option, // Used only in Spupng output and creating name of output file + pub out: Option>, // Rust Vec of write structs (converted from C array) + pub nb_out: i32, // number of member in array of write out array + + // Input file format used in Teletext for exceptional output + pub in_fileformat: u32, // 1 = Normal, 2 = Teletext + + // Keep output file closed when not actually writing to it and start over each time + pub keep_output_closed: u32, + pub force_flush: i32, // Force a flush on the file buffer whenever content is written + pub ucla: i32, // Keep track of whether -UCLA used + + pub timing: Option<*mut crate::bindings::ccx_common_timing_ctx>, // Some encoders need access to PTS, such as WebVTT + + // Encoding and output format + pub encoding: Encoding, + pub write_format: crate::bindings::ccx_output_format, // 0=Raw, 1=srt, 2=SMI + pub generates_file: i32, + pub transcript_settings: Option<*mut crate::bindings::ccx_encoders_transcript_format>, // Keeps the settings for generating transcript output files + + // Various flags + pub no_bom: i32, + pub sentence_cap: i32, // FIX CASE? = Fix case? + pub filter_profanity: i32, + pub trim_subs: i32, // " Remove spaces at sides? " + pub autodash: i32, // Add dashes (-) before each speaker automatically? + pub no_font_color: i32, + pub no_type_setting: i32, + pub gui_mode_reports: i32, // If 1, output in stderr progress updates so the GUI can grab them + + // Temp storage + pub subline: Option>, // Temp storage for storing each line + + // Extraction flags + pub extract: i32, + pub dtvcc_extract: i32, // 1 or 0 depending if we have to handle dtvcc + pub dtvcc_writers: [Option; 4], // CCX_DTVCC_MAX_SERVICES = 4 + + // Timing related variables + pub prev_start: i64, // start time of previous sub + pub subs_delay: i64, + pub last_displayed_subs_ms: i64, + pub date_format: crate::bindings::ccx_output_date_format, // enum ccx_output_date_format + pub millis_separator: i8, + + // Credit stuff + pub startcredits_displayed: i32, + pub start_credits_text: Option, + pub end_credits_text: Option, + pub startcreditsnotbefore: Option, // Where to insert start credits, if possible + pub startcreditsnotafter: Option, + pub startcreditsforatleast: Option, // How long to display them? + pub startcreditsforatmost: Option, + pub endcreditsforatleast: Option, + pub endcreditsforatmost: Option, + + // Preencoded strings + pub encoded_crlf: Option, + pub encoded_crlf_length: u32, + pub encoded_br: Option, + pub encoded_br_length: u32, + + // MCC File + pub header_printed_flag: i32, + pub next_caption_time: Option, + pub cdp_hdr_seq: u32, + pub force_dropframe: i32, + pub new_sentence: i32, // Capitalize next letter? + pub program_number: i32, + + // List and other flags + pub list: Option, + pub sbs_enabled: i32, // split-by-sentence stuff + pub prev: Option<*mut encoder_ctx>, // for dvb subs + pub write_previous: i32, // for dvb in .mkv + pub is_mkv: i32, // are we working with .mkv file + pub last_string: Option, // last recognized DVB sub + + // Segmenting + pub segment_pending: i32, + pub segment_last_key_frame: i32, + + // OCR in SPUPNG + pub nospupngocr: i32, } -pub fn copy_encoder_ctx_c_to_rust(c_ctx: *mut encoder_ctx) -> EncoderCtxRust{ - assert!(!c_ctx.is_null()); - let c = unsafe { &* c_ctx }; - - // Copy buffer into Vec - let buffer = if !c.buffer.is_null() && c.capacity > 0 { - let slice = unsafe { std::slice::from_raw_parts(c.buffer, c.capacity as usize) }; - Some(slice.to_vec()) - } else { - None - }; - - // Copy encoded_crlf into String - let encoded_crlf = { - let bytes = &c.encoded_crlf; - // trim at first 0 - let len = bytes.iter().position(|&b| b == 0).unwrap_or(16); - Some(String::from_utf8_lossy(&bytes[..len]).to_string()) - }; - - // Single output pointer - let out = if !c.out.is_null() { - Some(c.out) - } else { - None - }; - - EncoderCtxRust { - srt_counter: c.srt_counter, - buffer, - encoded_crlf, - out, - encoding: unsafe { Encoding::from_ctype(c.encoding).unwrap_or(Encoding::default()) }, + +// ---- Default implementation ---- +impl Default for ccxr_encoder_ctx { + fn default() -> Self { + ccxr_encoder_ctx { + // Common buffer used by all encoder + buffer: Some(Vec::with_capacity(1024)), + capacity: 1024, + + // Keep count of subtitles + srt_counter: 0, + cea_708_counter: 0, + + // WebVTT header flag + wrote_webvtt_header: 0, + wrote_ccd_channel_header: 0, + + // Input outputs + send_to_srv: 0, + multiple_files: 0, + first_input_file: None, + out: None, + nb_out: 0, + + // Input file format + in_fileformat: 1, // 1 = Normal + + // Keep output file closed + keep_output_closed: 0, + force_flush: 0, + ucla: 0, + + timing: None, + + // Encoding and output format + encoding: Encoding::default(), + write_format: unsafe { std::mem::transmute(1i32) }, // 1 = srt (ccx_output_format_CCX_OF_SRT) + generates_file: 0, + transcript_settings: None, + + // Various flags + 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, + + // Temp storage + subline: Some(Vec::with_capacity(1024)), + + // Extraction flags + extract: 0, + dtvcc_extract: 0, + dtvcc_writers: [None; 4], + + // Timing related variables + prev_start: 0, + subs_delay: 0, + last_displayed_subs_ms: 0, + date_format: unsafe { std::mem::transmute(0i32) }, // 0 = ISO (ccx_output_date_format_CCX_DF_ISO) + millis_separator: b',' as i8, + + // Credit stuff + startcredits_displayed: 0, + start_credits_text: None, + end_credits_text: None, + startcreditsnotbefore: None, + startcreditsnotafter: None, + startcreditsforatleast: None, + startcreditsforatmost: None, + endcreditsforatleast: None, + endcreditsforatmost: None, + + // Preencoded strings + encoded_crlf: Some("\r\n".to_string()), + encoded_crlf_length: 2, + encoded_br: Some("
".to_string()), + encoded_br_length: 4, + + // MCC File + header_printed_flag: 0, + next_caption_time: None, + cdp_hdr_seq: 0, + force_dropframe: 0, + new_sentence: 0, + program_number: 0, + + // List and other flags + list: None, + sbs_enabled: 0, + prev: None, + write_previous: 0, + is_mkv: 0, + last_string: None, + + // Segmenting + segment_pending: 0, + segment_last_key_frame: 0, + + // OCR in SPUPNG + nospupngocr: 0, + } + } +} + +// ---- From C to Rust ---- +impl ccxr_encoder_ctx { + pub unsafe fn from_ctype(c: *mut encoder_ctx) -> Option { + if c.is_null() { + return None; + } + let c_ref = &*c; + + // Copy buffer + 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 + }; + + // Copy subline + 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 + }; + + // Copy string fields + 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 + }; + + // Copy encoded_crlf + 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()) + }; + + // Copy encoded_br + 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()) + }; + + // Copy dtvcc_writers array + let mut dtvcc_writers = [None; 4]; + for i in 0..4 { + dtvcc_writers[i] = Some(c_ref.dtvcc_writers[i]); + } + + Some(ccxr_encoder_ctx { + // Common buffer used by all encoder + buffer, + capacity: c_ref.capacity, + + // Keep count of subtitles + srt_counter: c_ref.srt_counter, + cea_708_counter: c_ref.cea_708_counter, + + // WebVTT header flag + wrote_webvtt_header: c_ref.wrote_webvtt_header, + wrote_ccd_channel_header: c_ref.wrote_ccd_channel_header, + + // Input outputs + 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) }; + // Use the FromCType trait you found! + 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, + + // Input file format + in_fileformat: c_ref.in_fileformat, + + // Keep output file closed + 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 and output format + 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 }, + + // Various flags + 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, + + // Temp storage + subline, + + // Extraction flags + extract: c_ref.extract, + dtvcc_extract: c_ref.dtvcc_extract, + dtvcc_writers, + + // Timing related variables + 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, + + // Credit stuff + 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), + + // Preencoded strings + encoded_crlf, + encoded_crlf_length: c_ref.encoded_crlf_length, + encoded_br, + encoded_br_length: c_ref.encoded_br_length, + + // MCC File + 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 and other flags + 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, + + // Segmenting + segment_pending: c_ref.segment_pending, + segment_last_key_frame: c_ref.segment_last_key_frame, + + // OCR in SPUPNG + nospupngocr: c_ref.nospupngocr, + }) } } -/// Copy the Rust encoder context back to the C encoder context -pub unsafe fn copy_encoder_ctx_rust_to_c(rust_ctx: &mut EncoderCtxRust, c_ctx: *mut encoder_ctx) { - assert!(!c_ctx.is_null()); +// Copy the C encoder_ctx to the Rust encoder_ctx through a function +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() + } +} +// Copy the Rust ccxr_encoder_ctx to the C encoder_ctx through a function +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; - // Update the srt_counter + // Copy the most commonly used fields back c.srt_counter = rust_ctx.srt_counter; + c.cea_708_counter = rust_ctx.cea_708_counter; - // Copy buffer back if it exists + // Copy buffer back (only if C buffer exists and has capacity) 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); @@ -63,11 +506,38 @@ pub unsafe fn copy_encoder_ctx_rust_to_c(rust_ctx: &mut EncoderCtxRust, c_ctx: * } } - // Copy encoded_crlf back if it exists + // Copy subline back + 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); + } + } + + // Copy encoded_crlf back as fixed array [u8; 16] 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); // Leave room for null terminator + 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; // Null terminate } -} \ No newline at end of file + + // Copy encoded_br back as fixed array [u8; 16] + 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; // Null terminate + } + + // Copy other important fields + 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; + + // Note: We don't copy pointer fields back as they shouldn't change + // during the encoding process and could cause memory issues +} diff --git a/src/rust/src/utils.rs b/src/rust/src/utils.rs index 7c28a472c..7ebf8f6de 100644 --- a/src/rust/src/utils.rs +++ b/src/rust/src/utils.rs @@ -1,6 +1,5 @@ //! Some utility functions to deal with values from C bindings -use std::io; -use std::os::raw::{c_int, c_void, c_char}; +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 @@ -76,19 +75,3 @@ pub fn get_zero_allocated_obj() -> Box { } } -pub fn write_wrapper_os(fd: c_int, mut buf: &[u8])-> Result<(), io::Error>{ - while !buf.is_empty() { - let written = unsafe { - libc::write(fd, buf.as_ptr() as *const c_void, buf.len()) - }; - - if written == -1 { - return Err(io::Error::last_os_error()); - } - - // Move the slice forward by the number of bytes written - buf = &buf[written as usize..]; - } - - Ok(()) -} From e7e8afa18db9d4fdb1b6a926c420fdd7a3a3232c Mon Sep 17 00:00:00 2001 From: Vivian Rodrigues Date: Sun, 26 Oct 2025 22:57:02 +0530 Subject: [PATCH 05/10] Fix Linux build: Change char types from i8 to u8 for cross-platform compatibility - wrote_ccd_channel_header: i8 -> u8 - millis_separator: i8 -> u8 - Added type casts in from_ctype conversion This fixes compilation errors on Linux where char is unsigned by default. --- src/rust/src/libccxr_exports/encoder_ctx.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rust/src/libccxr_exports/encoder_ctx.rs b/src/rust/src/libccxr_exports/encoder_ctx.rs index b828a4211..9506c16d9 100644 --- a/src/rust/src/libccxr_exports/encoder_ctx.rs +++ b/src/rust/src/libccxr_exports/encoder_ctx.rs @@ -93,7 +93,7 @@ pub struct ccxr_encoder_ctx { // WebVTT header flag pub wrote_webvtt_header: u32, - pub wrote_ccd_channel_header: i8, + pub wrote_ccd_channel_header: u8, // Input outputs pub send_to_srv: u32, // Flag giving hint that output is send to server through network @@ -141,7 +141,7 @@ pub struct ccxr_encoder_ctx { pub subs_delay: i64, pub last_displayed_subs_ms: i64, pub date_format: crate::bindings::ccx_output_date_format, // enum ccx_output_date_format - pub millis_separator: i8, + pub millis_separator: u8, // Credit stuff pub startcredits_displayed: i32, @@ -246,7 +246,7 @@ impl Default for ccxr_encoder_ctx { subs_delay: 0, last_displayed_subs_ms: 0, date_format: unsafe { std::mem::transmute(0i32) }, // 0 = ISO (ccx_output_date_format_CCX_DF_ISO) - millis_separator: b',' as i8, + millis_separator: b',', // Credit stuff startcredits_displayed: 0, @@ -371,7 +371,7 @@ impl ccxr_encoder_ctx { // WebVTT header flag wrote_webvtt_header: c_ref.wrote_webvtt_header, - wrote_ccd_channel_header: c_ref.wrote_ccd_channel_header, + wrote_ccd_channel_header: c_ref.wrote_ccd_channel_header as u8, // Input outputs send_to_srv: c_ref.send_to_srv, @@ -431,7 +431,7 @@ impl ccxr_encoder_ctx { 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, + millis_separator: c_ref.millis_separator as u8, // Credit stuff startcredits_displayed: c_ref.startcredits_displayed, From 877a02598cdf6ccb32ed0b9e3235694bcd3514eb Mon Sep 17 00:00:00 2001 From: Vivian Rodrigues Date: Sun, 26 Oct 2025 23:54:38 +0530 Subject: [PATCH 06/10] Fix infinite recursion: Remove ccxr_write_cc_bitmap_as_srt wrapper The Rust function was calling C which called Rust again, causing infinite recursion. Removed the wrapper since OCR functionality stays in C for now. --- src/lib_ccx/ccx_encoders_srt.c | 3 --- src/lib_ccx/utility.h | 1 - src/rust/src/encoder/ccxr_encoder_srt.rs | 14 -------------- 3 files changed, 18 deletions(-) diff --git a/src/lib_ccx/ccx_encoders_srt.c b/src/lib_ccx/ccx_encoders_srt.c index 6ada803fc..178f8624f 100644 --- a/src/lib_ccx/ccx_encoders_srt.c +++ b/src/lib_ccx/ccx_encoders_srt.c @@ -83,9 +83,6 @@ int write_stringz_as_srt(char *string, struct encoder_ctx *context, LLONG ms_sta int write_cc_bitmap_as_srt(struct cc_subtitle *sub, struct encoder_ctx *context) { - #ifndef DISABLE_RUST - return ccxr_write_cc_bitmap_as_srt(sub, context); - #endif int ret = 0; #ifdef ENABLE_OCR struct cc_bitmap *rect; diff --git a/src/lib_ccx/utility.h b/src/lib_ccx/utility.h index cf779d5b2..533ec2553 100644 --- a/src/lib_ccx/utility.h +++ b/src/lib_ccx/utility.h @@ -35,7 +35,6 @@ 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_bitmap_as_srt(struct cc_subtitle *sub, struct encoder_ctx *context); 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 diff --git a/src/rust/src/encoder/ccxr_encoder_srt.rs b/src/rust/src/encoder/ccxr_encoder_srt.rs index 9a9bf9b91..18636c1aa 100644 --- a/src/rust/src/encoder/ccxr_encoder_srt.rs +++ b/src/rust/src/encoder/ccxr_encoder_srt.rs @@ -146,20 +146,6 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( 1 // Success } - -#[no_mangle] -pub unsafe extern "C" fn ccxr_write_cc_bitmap_as_srt( - sub: *mut cc_subtitle, - context: *mut encoder_ctx, -) -> c_int { - // Call the C implementation since OCR functions may not be available - extern "C" { - fn write_cc_bitmap_as_srt(sub: *mut cc_subtitle, context: *mut encoder_ctx) -> c_int; - } - - write_cc_bitmap_as_srt(sub, context) -} - /// Write text-based cc_subtitle structures as SRT /// This is a port of write_cc_subtitle_as_srt from C /// Simplified version that just calls ccxr_write_stringz_srt From d6c6717882b6b7683eb062eccc74d005adc9a412 Mon Sep 17 00:00:00 2001 From: Vivian Rodrigues Date: Fri, 26 Dec 2025 23:58:08 +0530 Subject: [PATCH 07/10] Remove all comments from encoder_ctx.rs --- src/rust/src/libccxr_exports/encoder_ctx.rs | 142 +++++--------------- 1 file changed, 33 insertions(+), 109 deletions(-) diff --git a/src/rust/src/libccxr_exports/encoder_ctx.rs b/src/rust/src/libccxr_exports/encoder_ctx.rs index 9506c16d9..ba536d2a5 100644 --- a/src/rust/src/libccxr_exports/encoder_ctx.rs +++ b/src/rust/src/libccxr_exports/encoder_ctx.rs @@ -3,7 +3,6 @@ use crate::encoder::FromCType; use lib_ccxr::util::encoding::Encoding; use std::ffi::CStr; -//Complete ccx_s_write in the rust side to match the C side and direct use in the rust code #[derive(Debug, Clone)] pub struct ccxr_s_write { pub fh: i32, @@ -35,7 +34,6 @@ impl Default for ccxr_s_write { } } } -// Convert the C struct (ccx_s_write) to the Rust struct (ccxr_s_write) impl FromCType for ccxr_s_write { unsafe fn from_ctype(c: ccx_s_write) -> Option { let filename = if !c.filename.is_null() { @@ -78,152 +76,123 @@ impl FromCType for ccxr_s_write { } } - - -// Complete encoder_ctx in the rust side to match the C side and direct use in the rust code #[derive(Debug, Clone)] pub struct ccxr_encoder_ctx { - // Common buffer used by all encoder pub buffer: Option>, pub capacity: u32, - // Keep count of subtitles pub srt_counter: u32, pub cea_708_counter: u32, - // WebVTT header flag pub wrote_webvtt_header: u32, pub wrote_ccd_channel_header: u8, - // Input outputs - pub send_to_srv: u32, // Flag giving hint that output is send to server through network - pub multiple_files: i32, // Used only in Spupng output - pub first_input_file: Option, // Used only in Spupng output and creating name of output file - pub out: Option>, // Rust Vec of write structs (converted from C array) - pub nb_out: i32, // number of member in array of write out array + pub send_to_srv: u32, + pub multiple_files: i32, + pub first_input_file: Option, + pub out: Option>, + pub nb_out: i32, - // Input file format used in Teletext for exceptional output - pub in_fileformat: u32, // 1 = Normal, 2 = Teletext + pub in_fileformat: u32, - // Keep output file closed when not actually writing to it and start over each time pub keep_output_closed: u32, - pub force_flush: i32, // Force a flush on the file buffer whenever content is written - pub ucla: i32, // Keep track of whether -UCLA used + pub force_flush: i32, + pub ucla: i32, - pub timing: Option<*mut crate::bindings::ccx_common_timing_ctx>, // Some encoders need access to PTS, such as WebVTT + pub timing: Option<*mut crate::bindings::ccx_common_timing_ctx>, - // Encoding and output format pub encoding: Encoding, - pub write_format: crate::bindings::ccx_output_format, // 0=Raw, 1=srt, 2=SMI + pub write_format: crate::bindings::ccx_output_format, pub generates_file: i32, - pub transcript_settings: Option<*mut crate::bindings::ccx_encoders_transcript_format>, // Keeps the settings for generating transcript output files + pub transcript_settings: Option<*mut crate::bindings::ccx_encoders_transcript_format>, - // Various flags pub no_bom: i32, - pub sentence_cap: i32, // FIX CASE? = Fix case? + pub sentence_cap: i32, pub filter_profanity: i32, - pub trim_subs: i32, // " Remove spaces at sides? " - pub autodash: i32, // Add dashes (-) before each speaker automatically? + pub trim_subs: i32, + pub autodash: i32, pub no_font_color: i32, pub no_type_setting: i32, - pub gui_mode_reports: i32, // If 1, output in stderr progress updates so the GUI can grab them + pub gui_mode_reports: i32, - // Temp storage - pub subline: Option>, // Temp storage for storing each line + pub subline: Option>, - // Extraction flags pub extract: i32, - pub dtvcc_extract: i32, // 1 or 0 depending if we have to handle dtvcc - pub dtvcc_writers: [Option; 4], // CCX_DTVCC_MAX_SERVICES = 4 + pub dtvcc_extract: i32, + pub dtvcc_writers: [Option; 4], - // Timing related variables - pub prev_start: i64, // start time of previous sub + pub prev_start: i64, pub subs_delay: i64, pub last_displayed_subs_ms: i64, - pub date_format: crate::bindings::ccx_output_date_format, // enum ccx_output_date_format + pub date_format: crate::bindings::ccx_output_date_format, pub millis_separator: u8, - // Credit stuff pub startcredits_displayed: i32, pub start_credits_text: Option, pub end_credits_text: Option, - pub startcreditsnotbefore: Option, // Where to insert start credits, if possible + pub startcreditsnotbefore: Option, pub startcreditsnotafter: Option, - pub startcreditsforatleast: Option, // How long to display them? + pub startcreditsforatleast: Option, pub startcreditsforatmost: Option, pub endcreditsforatleast: Option, pub endcreditsforatmost: Option, - // Preencoded strings pub encoded_crlf: Option, pub encoded_crlf_length: u32, pub encoded_br: Option, pub encoded_br_length: u32, - // MCC File pub header_printed_flag: i32, pub next_caption_time: Option, pub cdp_hdr_seq: u32, pub force_dropframe: i32, - pub new_sentence: i32, // Capitalize next letter? + pub new_sentence: i32, pub program_number: i32, - // List and other flags pub list: Option, - pub sbs_enabled: i32, // split-by-sentence stuff - pub prev: Option<*mut encoder_ctx>, // for dvb subs - pub write_previous: i32, // for dvb in .mkv - pub is_mkv: i32, // are we working with .mkv file - pub last_string: Option, // last recognized DVB sub + pub sbs_enabled: i32, + pub prev: Option<*mut encoder_ctx>, + pub write_previous: i32, + pub is_mkv: i32, + pub last_string: Option, - // Segmenting pub segment_pending: i32, pub segment_last_key_frame: i32, - // OCR in SPUPNG pub nospupngocr: i32, } -// ---- Default implementation ---- impl Default for ccxr_encoder_ctx { fn default() -> Self { ccxr_encoder_ctx { - // Common buffer used by all encoder buffer: Some(Vec::with_capacity(1024)), capacity: 1024, - // Keep count of subtitles srt_counter: 0, cea_708_counter: 0, - // WebVTT header flag wrote_webvtt_header: 0, wrote_ccd_channel_header: 0, - // Input outputs send_to_srv: 0, multiple_files: 0, first_input_file: None, out: None, nb_out: 0, - // Input file format - in_fileformat: 1, // 1 = Normal + in_fileformat: 1, - // Keep output file closed keep_output_closed: 0, force_flush: 0, ucla: 0, timing: None, - // Encoding and output format encoding: Encoding::default(), - write_format: unsafe { std::mem::transmute(1i32) }, // 1 = srt (ccx_output_format_CCX_OF_SRT) + write_format: unsafe { std::mem::transmute(1i32) }, generates_file: 0, transcript_settings: None, - // Various flags no_bom: 0, sentence_cap: 0, filter_profanity: 0, @@ -233,22 +202,18 @@ impl Default for ccxr_encoder_ctx { no_type_setting: 0, gui_mode_reports: 0, - // Temp storage subline: Some(Vec::with_capacity(1024)), - // Extraction flags extract: 0, dtvcc_extract: 0, dtvcc_writers: [None; 4], - // Timing related variables prev_start: 0, subs_delay: 0, last_displayed_subs_ms: 0, - date_format: unsafe { std::mem::transmute(0i32) }, // 0 = ISO (ccx_output_date_format_CCX_DF_ISO) + date_format: unsafe { std::mem::transmute(0i32) }, millis_separator: b',', - // Credit stuff startcredits_displayed: 0, start_credits_text: None, end_credits_text: None, @@ -259,13 +224,11 @@ impl Default for ccxr_encoder_ctx { endcreditsforatleast: None, endcreditsforatmost: None, - // Preencoded strings encoded_crlf: Some("\r\n".to_string()), encoded_crlf_length: 2, encoded_br: Some("
".to_string()), encoded_br_length: 4, - // MCC File header_printed_flag: 0, next_caption_time: None, cdp_hdr_seq: 0, @@ -273,7 +236,6 @@ impl Default for ccxr_encoder_ctx { new_sentence: 0, program_number: 0, - // List and other flags list: None, sbs_enabled: 0, prev: None, @@ -281,17 +243,14 @@ impl Default for ccxr_encoder_ctx { is_mkv: 0, last_string: None, - // Segmenting segment_pending: 0, segment_last_key_frame: 0, - // OCR in SPUPNG nospupngocr: 0, } } } -// ---- From C to Rust ---- impl ccxr_encoder_ctx { pub unsafe fn from_ctype(c: *mut encoder_ctx) -> Option { if c.is_null() { @@ -299,7 +258,6 @@ impl ccxr_encoder_ctx { } let c_ref = &*c; - // Copy buffer 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()) @@ -307,7 +265,6 @@ impl ccxr_encoder_ctx { None }; - // Copy subline let subline = if !c_ref.subline.is_null() { let slice = std::slice::from_raw_parts(c_ref.subline, 1024); Some(slice.to_vec()) @@ -315,7 +272,6 @@ impl ccxr_encoder_ctx { None }; - // Copy string fields 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 { @@ -340,40 +296,33 @@ impl ccxr_encoder_ctx { None }; - // Copy encoded_crlf 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()) }; - // Copy encoded_br 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()) }; - // Copy dtvcc_writers array let mut dtvcc_writers = [None; 4]; for i in 0..4 { dtvcc_writers[i] = Some(c_ref.dtvcc_writers[i]); } Some(ccxr_encoder_ctx { - // Common buffer used by all encoder buffer, capacity: c_ref.capacity, - // Keep count of subtitles srt_counter: c_ref.srt_counter, cea_708_counter: c_ref.cea_708_counter, - // WebVTT header flag wrote_webvtt_header: c_ref.wrote_webvtt_header, wrote_ccd_channel_header: c_ref.wrote_ccd_channel_header as u8, - // Input outputs send_to_srv: c_ref.send_to_srv, multiple_files: c_ref.multiple_files, first_input_file, @@ -381,7 +330,6 @@ impl ccxr_encoder_ctx { 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) }; - // Use the FromCType trait you found! if let Some(rust_write) = unsafe { ccxr_s_write::from_ctype(*c_write) } { out_vec.push(rust_write); } @@ -392,23 +340,19 @@ impl ccxr_encoder_ctx { }, nb_out: c_ref.nb_out, - // Input file format in_fileformat: c_ref.in_fileformat, - // Keep output file closed 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 and output format 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 }, - // Various flags no_bom: c_ref.no_bom, sentence_cap: c_ref.sentence_cap, filter_profanity: c_ref.filter_profanity, @@ -418,22 +362,18 @@ impl ccxr_encoder_ctx { no_type_setting: c_ref.no_type_setting, gui_mode_reports: c_ref.gui_mode_reports, - // Temp storage subline, - // Extraction flags extract: c_ref.extract, dtvcc_extract: c_ref.dtvcc_extract, dtvcc_writers, - // Timing related variables 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, - // Credit stuff startcredits_displayed: c_ref.startcredits_displayed, start_credits_text, end_credits_text, @@ -444,13 +384,11 @@ impl ccxr_encoder_ctx { endcreditsforatleast: Some(c_ref.endcreditsforatleast), endcreditsforatmost: Some(c_ref.endcreditsforatmost), - // Preencoded strings encoded_crlf, encoded_crlf_length: c_ref.encoded_crlf_length, encoded_br, encoded_br_length: c_ref.encoded_br_length, - // MCC File 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, @@ -458,7 +396,6 @@ impl ccxr_encoder_ctx { new_sentence: c_ref.new_sentence, program_number: c_ref.program_number, - // List and other flags list: Some(c_ref.list), sbs_enabled: c_ref.sbs_enabled, prev: if !c_ref.prev.is_null() { Some(c_ref.prev) } else { None }, @@ -466,17 +403,14 @@ impl ccxr_encoder_ctx { is_mkv: c_ref.is_mkv, last_string, - // Segmenting segment_pending: c_ref.segment_pending, segment_last_key_frame: c_ref.segment_last_key_frame, - // OCR in SPUPNG nospupngocr: c_ref.nospupngocr, }) } } -// Copy the C encoder_ctx to the Rust encoder_ctx through a function 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(); @@ -486,7 +420,6 @@ pub fn copy_encoder_ctx_c_to_rust(c_ctx: *mut encoder_ctx) -> ccxr_encoder_ctx { ccxr_encoder_ctx::from_ctype(c_ctx).unwrap_or_default() } } -// Copy the Rust ccxr_encoder_ctx to the C encoder_ctx through a function 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; @@ -494,11 +427,9 @@ pub unsafe fn copy_encoder_ctx_rust_to_c(rust_ctx: &mut ccxr_encoder_ctx, c_ctx: let c = &mut *c_ctx; - // Copy the most commonly used fields back c.srt_counter = rust_ctx.srt_counter; c.cea_708_counter = rust_ctx.cea_708_counter; - // Copy buffer back (only if C buffer exists and has capacity) 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); @@ -506,7 +437,6 @@ pub unsafe fn copy_encoder_ctx_rust_to_c(rust_ctx: &mut ccxr_encoder_ctx, c_ctx: } } - // Copy subline back if let Some(ref subline) = rust_ctx.subline { if !c.subline.is_null() { let copy_len = std::cmp::min(subline.len(), 1024); @@ -514,30 +444,24 @@ pub unsafe fn copy_encoder_ctx_rust_to_c(rust_ctx: &mut ccxr_encoder_ctx, c_ctx: } } - // Copy encoded_crlf back as fixed array [u8; 16] 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; // Null terminate + c.encoded_crlf[copy_len] = 0; } - // Copy encoded_br back as fixed array [u8; 16] 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; // Null terminate + c.encoded_br[copy_len] = 0; } - // Copy other important fields 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; - - // Note: We don't copy pointer fields back as they shouldn't change - // during the encoding process and could cause memory issues } From e15c975aab878d0574058367d413fe64623e678b Mon Sep 17 00:00:00 2001 From: Vivian Rodrigues Date: Sat, 27 Dec 2025 00:27:19 +0530 Subject: [PATCH 08/10] Remove all comments from ccxr_encoder_srt.rs --- src/rust/src/encoder/ccxr_encoder_srt.rs | 76 ++++-------------------- 1 file changed, 10 insertions(+), 66 deletions(-) diff --git a/src/rust/src/encoder/ccxr_encoder_srt.rs b/src/rust/src/encoder/ccxr_encoder_srt.rs index 18636c1aa..09fc7f8c8 100644 --- a/src/rust/src/encoder/ccxr_encoder_srt.rs +++ b/src/rust/src/encoder/ccxr_encoder_srt.rs @@ -46,28 +46,18 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( unsafe { ccxr_millis_to_time(ms_end - 1, &mut h2, &mut m2, &mut s2, &mut ms2); } - - // Increment counter rust_context.srt_counter += 1; - - // Get encoded CRLF string let crlf = rust_context.encoded_crlf.as_deref().unwrap_or("\r\n").to_string(); - - // Get file handle directly from Rust context - now safe! let fh = if let Some(ref out_vec) = rust_context.out { if out_vec.is_empty() { return 0; } - out_vec[0].fh // ✅ Safe access - no unsafe needed! + out_vec[0].fh; } else { return 0; }; - - // Timeline counter line - encode directly using Rust context let timeline = format!("{}{}", rust_context.srt_counter, crlf); let timeline_bytes = timeline.as_bytes(); - - // Use the Rust encode_line function directly with Rust context 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]) @@ -77,8 +67,6 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( if write_wrapped(fh, buffer_slice).is_err() { return 0; } - - // Timeline timecode line let timeline = format!( "{:02}:{:02}:{:02},{:03} --> {:02}:{:02}:{:02},{:03}{}", h1, m1, s1, ms1, @@ -95,8 +83,6 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( if write_wrapped(fh, buffer_slice).is_err() { return 0; } - - // --- Handle the text itself --- let mut unescaped = Vec::with_capacity(string_cstr.len() + 1); let mut chars = string_cstr.chars().peekable(); @@ -134,21 +120,14 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( break; } } - - // Final CRLF if write_wrapped(fh, crlf.as_bytes()).is_err() { return 0; } - - // Copy the updated Rust context back to C context copy_encoder_ctx_rust_to_c(&mut rust_context, context); - 1 // Success + 1 } -/// Write text-based cc_subtitle structures as SRT -/// This is a port of write_cc_subtitle_as_srt from C -/// Simplified version that just calls ccxr_write_stringz_srt #[no_mangle] pub unsafe extern "C" fn ccxr_write_cc_subtitle_as_srt( sub: *mut cc_subtitle, @@ -159,50 +138,37 @@ pub unsafe extern "C" fn ccxr_write_cc_subtitle_as_srt( } let mut ret = 0; - let osub = sub; // Save original starting point - let mut lsub = sub; // Track last subtitle processed + let osub = sub; + let mut lsub = sub; let mut current_sub = sub; - - // Phase 1: Process linked list of subtitles (forward pass) while !current_sub.is_null() { let sub_ref = &mut *current_sub; - - // Check if this is text type (CC_TEXT = 0 in enum) const CC_TEXT: u32 = 0; if sub_ref.type_ == CC_TEXT { if !sub_ref.data.is_null() { - // Call the existing ccxr_write_stringz_srt function directly let write_result = ccxr_write_stringz_srt( context, sub_ref.data as *const c_char, sub_ref.start_time, sub_ref.end_time, ); - - // Free the data + extern "C" { fn freep(ptr: *mut *mut std::ffi::c_void); } freep(&mut sub_ref.data); sub_ref.nb_data = 0; - - // Update ret if write succeeded if write_result > 0 { ret = 1; } } } - - lsub = current_sub; // Track the last subtitle + lsub = current_sub; current_sub = sub_ref.next; } - - // Phase 2: Free the subtitle chain structs themselves (backward pass) - // Walk backwards from lsub to osub, freeing each node extern "C" { fn freep(ptr: *mut *mut std::ffi::c_void); - } - + } while lsub != osub { if lsub.is_null() { break; @@ -211,11 +177,9 @@ pub unsafe extern "C" fn ccxr_write_cc_subtitle_as_srt( freep(&mut (lsub as *mut std::ffi::c_void)); lsub = current_sub; } - ret } -/// Write EIA-608 screen buffers as SRT /// This is a port of write_cc_buffer_as_srt from C /// Uses Rust ccxr_encoder_ctx for all operations #[no_mangle] @@ -240,12 +204,9 @@ pub unsafe extern "C" fn ccxr_write_cc_buffer_as_srt( break; } } - if empty_buf { - return 0; // Don't write empty screens - } - - // Convert times using Rust context + return 0; + } let mut h1 = 0u32; let mut m1 = 0u32; let mut s1 = 0u32; @@ -257,14 +218,8 @@ pub unsafe extern "C" fn ccxr_write_cc_buffer_as_srt( 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); - - // Increment counter in Rust context rust_ctx.srt_counter += 1; - - // Get CRLF from Rust context - clone to avoid borrow issues let crlf = rust_ctx.encoded_crlf.as_deref().unwrap_or("\r\n").to_string(); - - // Get file handle from Rust context - now safe! let fh = if let Some(ref out_vec) = rust_ctx.out { if out_vec.is_empty() { return 0; @@ -273,16 +228,12 @@ pub unsafe extern "C" fn ccxr_write_cc_buffer_as_srt( } else { return 0; }; - - // Write counter line using Rust context and buffer let timeline = format!("{}{}", rust_ctx.srt_counter, crlf); if let Some(ref mut buffer) = rust_ctx.buffer { - let encoding = rust_ctx.encoding; // Extract encoding to avoid borrow issues + let encoding = rust_ctx.encoding; let used = encode_line(encoding, buffer, timeline.as_bytes()); let _ = write_wrapped(fh, &buffer[..used]); } - - // Write timestamp line using Rust context let timeline = format!( "{:02}:{:02}:{:02},{:03} --> {:02}:{:02}:{:02},{:03}{}", h1, m1, s1, ms1, h2, m2, s2, ms2, crlf @@ -311,7 +262,6 @@ pub unsafe extern "C" fn ccxr_write_cc_buffer_as_srt( for i in 0..15 { if screen_data.row_used[i] != 0 { - // Get encoded line from C function let length = get_decoder_line_encoded( context, (*context).subline, @@ -327,14 +277,8 @@ pub unsafe extern "C" fn ccxr_write_cc_buffer_as_srt( } } } - - // Final CRLF let _ = write_wrapped(fh, crlf.as_bytes()); - - // Get the updated context back to Rust let rust_ctx = copy_encoder_ctx_c_to_rust(context); - - // Copy final Rust context back to C (in case anything changed) copy_encoder_ctx_rust_to_c(&mut rust_ctx.clone(), context); if wrote_something { 1 } else { 0 } From 8b88a619d897475cd7162ffb760eb2c5d1458d40 Mon Sep 17 00:00:00 2001 From: Vivian Rodrigues Date: Mon, 29 Dec 2025 18:16:25 +0530 Subject: [PATCH 09/10] Fix: Remove semicolon from fh assignment to fix type mismatch --- src/rust/src/encoder/ccxr_encoder_srt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rust/src/encoder/ccxr_encoder_srt.rs b/src/rust/src/encoder/ccxr_encoder_srt.rs index 09fc7f8c8..5daa9a9fe 100644 --- a/src/rust/src/encoder/ccxr_encoder_srt.rs +++ b/src/rust/src/encoder/ccxr_encoder_srt.rs @@ -52,7 +52,7 @@ pub unsafe extern "C" fn ccxr_write_stringz_srt( if out_vec.is_empty() { return 0; } - out_vec[0].fh; + out_vec[0].fh } else { return 0; }; From b2e3d5bb8fd60e1212d3c40cdd790f589727f4a3 Mon Sep 17 00:00:00 2001 From: Vivian Rodrigues Date: Tue, 30 Dec 2025 23:36:42 +0530 Subject: [PATCH 10/10] Added Forward declaration for struct type in utility.h --- src/lib_ccx/utility.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib_ccx/utility.h b/src/lib_ccx/utility.h index 533ec2553..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);