Skip to content

Commit 772d7e1

Browse files
author
Dhanush Varma
committed
feat: port SRT encoder to Rust
Implement ccxr_write_stringz_as_srt and ccxr_write_cc_buffer_as_srt in src/rust/src/encoder/srt.rs. Covers: - Subtitle counter and timestamp formatting with -1ms overlap prevention - \n unescape handling for multi-line subtitles - Encoding conversion (UTF-8, Latin1, UCS-2) - Autodash detection for CEA-608 screen buffers - Speaker name detection (colon-based) Uses existing Rust encoder infrastructure (encode_line, write_wrapped) and calls C get_decoder_line_encoded for CEA-608 line encoding until that function is also ported. Exported as #[no_mangle] extern C functions ready to replace the C versions in ccx_encoders_srt.c.
1 parent 9f250b1 commit 772d7e1

2 files changed

Lines changed: 368 additions & 0 deletions

File tree

src/rust/src/encoder/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::os::raw::{c_int, c_uchar};
77
pub mod common;
88
pub mod g608;
99
pub mod simplexml;
10+
pub mod srt;
1011
/// # Safety
1112
/// This function is unsafe because it deferences to raw pointers and performs operations on pointer slices.
1213
#[no_mangle]

src/rust/src/encoder/srt.rs

Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
use crate::bindings::{
2+
ccx_encoding_type_CCX_ENC_UNICODE, eia608_screen, encoder_ctx,
3+
};
4+
use crate::encoder::common::encode_line;
5+
use crate::encoder::g608::write_wrapped;
6+
use crate::libccxr_exports::time::ccxr_millis_to_time;
7+
use lib_ccxr::common::CCX_DECODER_608_SCREEN_WIDTH;
8+
use lib_ccxr::util::log::DebugMessageFlag;
9+
use lib_ccxr::debug;
10+
use std::os::raw::{c_int, c_uchar, c_uint};
11+
12+
extern "C" {
13+
fn get_decoder_line_encoded(
14+
ctx: *mut encoder_ctx,
15+
buffer: *mut c_uchar,
16+
line_num: c_int,
17+
data: *const eia608_screen,
18+
) -> c_uint;
19+
}
20+
21+
/// Helper: copy CRLF bytes from context so we don't hold a borrow.
22+
unsafe fn copy_crlf(ctx: &encoder_ctx) -> Vec<u8> {
23+
let len = ctx.encoded_crlf_length as usize;
24+
let slice = std::slice::from_raw_parts(ctx.encoded_crlf.as_ptr(), len);
25+
slice.to_vec()
26+
}
27+
28+
/// Helper: encode into a fresh Vec, avoiding borrow conflicts with ctx.buffer.
29+
unsafe fn encode_to_vec(ctx: &mut encoder_ctx, text: &[u8]) -> Vec<u8> {
30+
let cap = ctx.capacity as usize;
31+
let buf = std::slice::from_raw_parts_mut(ctx.buffer, cap);
32+
let used = encode_line(ctx, buf, text) as usize;
33+
buf[..used].to_vec()
34+
}
35+
36+
/// Core SRT writer — writes a single subtitle entry to a specific output.
37+
///
38+
/// # Safety
39+
/// Accesses raw pointers from encoder context.
40+
pub unsafe fn write_stringz_as_srt_to_output(
41+
string: *const i8,
42+
context: &mut encoder_ctx,
43+
ms_start: i64,
44+
ms_end: i64,
45+
out_fh: c_int,
46+
srt_counter: *mut u32,
47+
) -> c_int {
48+
if string.is_null() {
49+
return 0;
50+
}
51+
let c_str = std::ffi::CStr::from_ptr(string);
52+
let input = match c_str.to_str() {
53+
Ok(s) if !s.is_empty() => s,
54+
_ => return 0,
55+
};
56+
57+
let mut h1: u32 = 0; let mut m1: u32 = 0; let mut s1: u32 = 0; let mut ms1: u32 = 0;
58+
let mut h2: u32 = 0; let mut m2: u32 = 0; let mut s2: u32 = 0; let mut ms2: u32 = 0;
59+
ccxr_millis_to_time(ms_start, &mut h1, &mut m1, &mut s1, &mut ms1);
60+
ccxr_millis_to_time(ms_end - 1, &mut h2, &mut m2, &mut s2, &mut ms2);
61+
62+
let crlf = copy_crlf(context);
63+
let crlf_str = std::str::from_utf8_unchecked(&crlf);
64+
65+
// Write counter
66+
*srt_counter += 1;
67+
let counter_line = format!("{}{}", *srt_counter, crlf_str);
68+
let encoded = encode_to_vec(context, counter_line.as_bytes());
69+
if write_wrapped(out_fh, &encoded).is_err() {
70+
return -1;
71+
}
72+
73+
// Write timestamp
74+
let timeline = format!(
75+
"{:02}:{:02}:{:02},{:03} --> {:02}:{:02}:{:02},{:03}{}",
76+
h1, m1, s1, ms1, h2, m2, s2, ms2, crlf_str
77+
);
78+
let encoded = encode_to_vec(context, timeline.as_bytes());
79+
80+
debug!(msg_type = DebugMessageFlag::DECODER_608; "\n- - - SRT caption - - -\n");
81+
debug!(msg_type = DebugMessageFlag::DECODER_608; "{}", timeline);
82+
83+
if write_wrapped(out_fh, &encoded).is_err() {
84+
return -1;
85+
}
86+
87+
// Unescape \\n and write each line
88+
let parts: Vec<&str> = input.split("\\n").collect();
89+
for part in &parts {
90+
if part.is_empty() {
91+
continue;
92+
}
93+
let mut el = vec![0u8; part.len() * 3 + 1];
94+
let u = encode_line(context, &mut el, part.as_bytes()) as usize;
95+
96+
if context.encoding != ccx_encoding_type_CCX_ENC_UNICODE {
97+
debug!(msg_type = DebugMessageFlag::DECODER_608; "\r");
98+
if let Ok(s) = std::str::from_utf8(&el[..u]) {
99+
debug!(msg_type = DebugMessageFlag::DECODER_608; "{}\n", s);
100+
}
101+
}
102+
103+
if write_wrapped(out_fh, &el[..u]).is_err() {
104+
return -1;
105+
}
106+
if write_wrapped(out_fh, &crlf).is_err() {
107+
return -1;
108+
}
109+
}
110+
111+
debug!(msg_type = DebugMessageFlag::DECODER_608; "- - - - - - - - - - - -\r\n");
112+
113+
if write_wrapped(out_fh, &crlf).is_err() {
114+
return -1;
115+
}
116+
117+
0
118+
}
119+
120+
/// Write a string as SRT to the default output.
121+
///
122+
/// # Safety
123+
/// Accesses raw pointers from encoder context.
124+
#[no_mangle]
125+
pub unsafe extern "C" fn ccxr_write_stringz_as_srt(
126+
string: *const i8,
127+
context: *mut encoder_ctx,
128+
ms_start: i64,
129+
ms_end: i64,
130+
) -> c_int {
131+
if context.is_null() {
132+
return -1;
133+
}
134+
let ctx = &mut *context;
135+
let fh = (*ctx.out).fh;
136+
let counter_ptr = &mut ctx.srt_counter as *mut u32;
137+
write_stringz_as_srt_to_output(string, ctx, ms_start, ms_end, fh, counter_ptr)
138+
}
139+
140+
/// Write a CEA-608 screen buffer as SRT.
141+
///
142+
/// # Safety
143+
/// Accesses raw pointers from encoder context.
144+
#[no_mangle]
145+
pub unsafe extern "C" fn ccxr_write_cc_buffer_as_srt(
146+
data: *const eia608_screen,
147+
context: *mut encoder_ctx,
148+
) -> c_int {
149+
if context.is_null() || data.is_null() {
150+
return 0;
151+
}
152+
let ctx = &mut *context;
153+
let screen = &*data;
154+
155+
let mut wrote_something: c_int = 0;
156+
let mut prev_line_start: i32 = -1;
157+
let mut prev_line_end: i32 = -1;
158+
let mut prev_line_center1: i32 = -1;
159+
let mut prev_line_center2: i32 = -1;
160+
161+
// Skip empty screens
162+
let empty = (0..15).all(|i| screen.row_used[i] == 0);
163+
if empty {
164+
return 0;
165+
}
166+
167+
let mut h1: u32 = 0; let mut m1: u32 = 0; let mut s1: u32 = 0; let mut ms1: u32 = 0;
168+
let mut h2: u32 = 0; let mut m2: u32 = 0; let mut s2: u32 = 0; let mut ms2: u32 = 0;
169+
ccxr_millis_to_time(screen.start_time, &mut h1, &mut m1, &mut s1, &mut ms1);
170+
ccxr_millis_to_time(screen.end_time - 1, &mut h2, &mut m2, &mut s2, &mut ms2);
171+
172+
let crlf = copy_crlf(ctx);
173+
let crlf_str = std::str::from_utf8_unchecked(&crlf);
174+
let out_fh = (*ctx.out).fh;
175+
176+
// Write counter
177+
ctx.srt_counter += 1;
178+
let counter_line = format!("{}{}", ctx.srt_counter, crlf_str);
179+
let encoded = encode_to_vec(ctx, counter_line.as_bytes());
180+
if write_wrapped(out_fh, &encoded).is_err() {
181+
return 0;
182+
}
183+
184+
// Write timestamp
185+
let timeline = format!(
186+
"{:02}:{:02}:{:02},{:03} --> {:02}:{:02}:{:02},{:03}{}",
187+
h1, m1, s1, ms1, h2, m2, s2, ms2, crlf_str
188+
);
189+
let encoded = encode_to_vec(ctx, timeline.as_bytes());
190+
if write_wrapped(out_fh, &encoded).is_err() {
191+
return 0;
192+
}
193+
194+
debug!(msg_type = DebugMessageFlag::DECODER_608; "\n- - - SRT caption ( {}) - - -\n", ctx.srt_counter);
195+
debug!(msg_type = DebugMessageFlag::DECODER_608; "{}", timeline);
196+
197+
for i in 0..15usize {
198+
if screen.row_used[i] == 0 {
199+
continue;
200+
}
201+
202+
// Autodash logic
203+
if ctx.autodash != 0 && ctx.trim_subs != 0 {
204+
let line = &screen.characters[i];
205+
let width = CCX_DECODER_608_SCREEN_WIDTH;
206+
let mut first: i32 = -1;
207+
let mut last: i32 = -1;
208+
209+
for j in 0..width {
210+
if line[j] != b' ' && line[j] != 0 {
211+
if first == -1 { first = j as i32; }
212+
last = j as i32;
213+
}
214+
}
215+
216+
if first != -1 && last != -1 {
217+
let mut do_dash = true;
218+
let mut colon_pos: i32 = -1;
219+
220+
for j in first..=last {
221+
let ch = line[j as usize];
222+
if ch == b':' { colon_pos = j; break; }
223+
if !(ch as char).is_ascii_uppercase() { break; }
224+
}
225+
226+
if prev_line_start == -1 { do_dash = false; }
227+
if first == prev_line_start { do_dash = false; }
228+
if last == prev_line_end { do_dash = false; }
229+
if first > prev_line_start && last < prev_line_end { do_dash = false; }
230+
if (first > prev_line_start && first < prev_line_end)
231+
|| (last > prev_line_start && last < prev_line_end)
232+
{
233+
do_dash = false;
234+
}
235+
236+
let center1 = (first + last) / 2;
237+
let center2 = if colon_pos != -1 {
238+
let mut cp = colon_pos;
239+
while (cp as usize) < width {
240+
let ch = line[cp as usize];
241+
if ch != b':' && ch != b' ' && ch != 0x89 { break; }
242+
cp += 1;
243+
}
244+
(cp + last) / 2
245+
} else {
246+
center1
247+
};
248+
249+
if center1 >= prev_line_center1 - 1 && center1 <= prev_line_center1 + 1 && center1 != -1 {
250+
do_dash = false;
251+
}
252+
if center2 >= prev_line_center2 - 2 && center1 <= prev_line_center2 + 2 && center1 != -1 {
253+
do_dash = false;
254+
}
255+
256+
if do_dash {
257+
let _ = write_wrapped(out_fh, b"- ");
258+
}
259+
260+
prev_line_start = first;
261+
prev_line_end = last;
262+
prev_line_center1 = center1;
263+
prev_line_center2 = center2;
264+
}
265+
}
266+
267+
// Encode and write the line
268+
let length = get_decoder_line_encoded(context, ctx.subline, i as c_int, data);
269+
let sub_slice = std::slice::from_raw_parts(ctx.subline, length as usize);
270+
271+
if ctx.encoding != ccx_encoding_type_CCX_ENC_UNICODE {
272+
debug!(msg_type = DebugMessageFlag::DECODER_608; "\r");
273+
if let Ok(s) = std::str::from_utf8(sub_slice) {
274+
debug!(msg_type = DebugMessageFlag::DECODER_608; "{}\n", s);
275+
}
276+
}
277+
278+
let _ = write_wrapped(out_fh, sub_slice);
279+
let _ = write_wrapped(out_fh, &crlf);
280+
wrote_something = 1;
281+
}
282+
283+
debug!(msg_type = DebugMessageFlag::DECODER_608; "- - - - - - - - - - - -\r\n");
284+
285+
let _ = write_wrapped(out_fh, &crlf);
286+
287+
wrote_something
288+
}
289+
290+
// FFI declarations for teletext multi-page support
291+
extern "C" {
292+
fn get_teletext_output(ctx: *mut encoder_ctx, teletext_page: u16) -> *mut crate::bindings::ccx_s_write;
293+
fn get_teletext_srt_counter(ctx: *mut encoder_ctx, teletext_page: u16) -> *mut u32;
294+
}
295+
296+
/// Write a cc_subtitle linked list as SRT.
297+
///
298+
/// Walks the subtitle chain, handles teletext multi-page output (issue #665),
299+
/// and frees processed subtitle data.
300+
///
301+
/// # Safety
302+
/// Accesses raw pointers from encoder context and subtitle chain.
303+
#[no_mangle]
304+
pub unsafe extern "C" fn ccxr_write_cc_subtitle_as_srt(
305+
sub: *mut crate::bindings::cc_subtitle,
306+
context: *mut encoder_ctx,
307+
) -> c_int {
308+
if context.is_null() || sub.is_null() {
309+
return 0;
310+
}
311+
312+
let ctx = &mut *context;
313+
let mut ret: c_int = 0;
314+
let osub = sub;
315+
let mut current = sub;
316+
let mut lsub = sub;
317+
318+
while !current.is_null() {
319+
let s = &mut *current;
320+
if s.type_ == crate::bindings::subtype_CC_TEXT {
321+
// Teletext multi-page: use page-specific output if available
322+
let out = get_teletext_output(context, s.teletext_page);
323+
let counter = get_teletext_srt_counter(context, s.teletext_page);
324+
325+
if !out.is_null() && !counter.is_null() {
326+
ret = write_stringz_as_srt_to_output(
327+
s.data as *const i8,
328+
ctx,
329+
s.start_time,
330+
s.end_time,
331+
(*out).fh,
332+
&mut *counter,
333+
);
334+
} else {
335+
let fh = (*ctx.out).fh;
336+
let counter_ptr = &mut ctx.srt_counter as *mut u32;
337+
ret = write_stringz_as_srt_to_output(
338+
s.data as *const i8,
339+
ctx,
340+
s.start_time,
341+
s.end_time,
342+
fh,
343+
&mut *counter_ptr,
344+
);
345+
}
346+
347+
// Free subtitle data
348+
if !s.data.is_null() {
349+
crate::ffi_alloc::c_free(s.data);
350+
s.data = std::ptr::null_mut();
351+
}
352+
s.nb_data = 0;
353+
ret = 1;
354+
}
355+
lsub = current;
356+
current = s.next;
357+
}
358+
359+
// Free the linked list (except the original node)
360+
while lsub != osub {
361+
let prev = (*lsub).prev;
362+
crate::ffi_alloc::c_free(lsub);
363+
lsub = prev;
364+
}
365+
366+
ret
367+
}

0 commit comments

Comments
 (0)