Skip to content

Commit 2556c96

Browse files
authored
Merge branch 'master' into fix/networking-debug-out-and-stderr
2 parents c1eb060 + 90128d8 commit 2556c96

File tree

9 files changed

+122
-12
lines changed

9 files changed

+122
-12
lines changed

docs/CHANGES.TXT

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
0.96.7 (unreleased)
22
-------------------
33
- Fix: Replace DEBUG_OUT 0/1 integer toggle in networking.c with CMake opt-in (-DNETWORKING_DEBUG=ON); fix 3 error messages using printf() instead of fprintf(stderr) (#2174)
4+
- Fix: Remove strdup() memory leaks in WebVTT styling encoder, fix invalid CSS rgba(0,256,0) green value, fix missing free(unescaped) on write-error path (#2154)
45
- Fix: Prevent crash in Rust timing module when logging out-of-range PTS/FTS timestamps from malformed streams.
56

67
0.96.6 (2026-02-19)

src/lib_ccx/ccx_encoders_srt.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ static int write_stringz_as_srt_to_output(char *string, struct encoder_ctx *cont
4747
// Scan for \n in the string and replace it with a 0
4848
while (pos_r < len)
4949
{
50-
if (string[pos_r] == '\\' && string[pos_r + 1] == 'n')
50+
if (pos_r < len - 1 && string[pos_r] == '\\' && string[pos_r + 1] == 'n')
5151
{
5252
unescaped[pos_w] = 0;
5353
pos_r += 2;
@@ -62,7 +62,7 @@ static int write_stringz_as_srt_to_output(char *string, struct encoder_ctx *cont
6262
unescaped[pos_w] = 0;
6363
// Now read the unescaped string (now several string'z and write them)
6464
unsigned char *begin = unescaped;
65-
while (begin < unescaped + len)
65+
while (begin < unescaped + pos_w)
6666
{
6767
unsigned int u = encode_line(context, el, begin);
6868
if (context->encoding != CCX_ENC_UNICODE)

src/lib_ccx/ccx_encoders_webvtt.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ static const char *webvtt_inline_css = "\r\nSTYLE\n\n"
9494
" background-color: rgba(255, 255, 255, 0.5);\n"
9595
"}\n"
9696
"::cue(c.bg_green.bg_semi-transparent) {\n"
97-
" background-color: rgba(0, 256, 0, 0.5);\n"
97+
" background-color: rgba(0, 255, 0, 0.5);\n"
9898
"}\n"
9999
"::cue(c.bg_blue.bg_semi-transparent) {\n"
100100
" background-color: rgba(0, 0, 255, 0.5);\n"
@@ -189,6 +189,7 @@ int write_stringz_as_webvtt(char *string, struct encoder_ctx *context, LLONG ms_
189189
if (written != context->encoded_crlf_length)
190190
{
191191
free(el);
192+
free(unescaped);
192193
return -1;
193194
}
194195
begin += strlen((const char *)begin) + 1;
@@ -502,16 +503,16 @@ int write_cc_buffer_as_webvtt(struct eia608_screen *data, struct encoder_ctx *co
502503
if (open_font != FONT_REGULAR)
503504
{
504505
if (open_font & FONT_ITALICS)
505-
write_wrapped(context->out->fh, strdup("<i>"), 3);
506+
write_wrapped(context->out->fh, "<i>", 3);
506507
if (open_font & FONT_UNDERLINED)
507-
write_wrapped(context->out->fh, strdup("<u>"), 3);
508+
write_wrapped(context->out->fh, "<u>", 3);
508509
}
509510

510511
// opening events for colors
511512
int open_color = color_events[j] & 0xFF; // Last 16 bytes
512513
if (open_color != COL_WHITE)
513514
{
514-
write_wrapped(context->out->fh, strdup("<c."), 3);
515+
write_wrapped(context->out->fh, "<c.", 3);
515516
write_wrapped(context->out->fh, color_text[open_color][0], strlen(color_text[open_color][0]));
516517
write_wrapped(context->out->fh, ">", 1);
517518
}
@@ -532,17 +533,17 @@ int write_cc_buffer_as_webvtt(struct eia608_screen *data, struct encoder_ctx *co
532533
int close_color = color_events[j] >> 16; // First 16 bytes
533534
if (close_color != COL_WHITE)
534535
{
535-
write_wrapped(context->out->fh, strdup("</c>"), 4);
536+
write_wrapped(context->out->fh, "</c>", 4);
536537
}
537538

538539
// closing events for fonts
539540
int close_font = font_events[j] >> 16; // First 16 bytes
540541
if (close_font != FONT_REGULAR)
541542
{
542543
if (close_font & FONT_UNDERLINED)
543-
write_wrapped(context->out->fh, strdup("</u>"), 4);
544+
write_wrapped(context->out->fh, "</u>", 4);
544545
if (close_font & FONT_ITALICS)
545-
write_wrapped(context->out->fh, strdup("</i>"), 4);
546+
write_wrapped(context->out->fh, "</i>", 4);
546547
}
547548
}
548549
}

src/lib_ccx/utility.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,13 @@ void sleep_secs(int secs)
269269
#endif
270270
}
271271

272+
#ifndef DISABLE_RUST
273+
extern int ccxr_hex_to_int(char high, char low);
274+
int hex_to_int(char high, char low)
275+
{
276+
return ccxr_hex_to_int(high, low);
277+
}
278+
#else
272279
int hex_to_int(char high, char low)
273280
{
274281
unsigned char h, l;
@@ -286,6 +293,15 @@ int hex_to_int(char high, char low)
286293
return -1;
287294
return h * 16 + l;
288295
}
296+
#endif
297+
298+
#ifndef DISABLE_RUST
299+
extern int ccxr_hex_string_to_int(const char *string, int len);
300+
int hex_string_to_int(char *string, int len)
301+
{
302+
return ccxr_hex_string_to_int(string, len);
303+
}
304+
#else
289305
int hex_string_to_int(char *string, int len)
290306
{
291307
int total_return = 0;
@@ -302,6 +318,7 @@ int hex_string_to_int(char *string, int len)
302318
}
303319
return total_return;
304320
}
321+
#endif
305322

306323
#ifndef _WIN32
307324
void m_signal(int sig, void (*func)(int))

src/rust/lib_ccxr/src/util/hex.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/// Converts a single hex character to its integer value.
2+
/// Returns None if the character is not valid (0-9, a-f, or A-F).
3+
fn hex_char_to_val(c: char) -> Option<i32> {
4+
match c {
5+
'0'..='9' => Some(c as i32 - '0' as i32),
6+
'a'..='f' => Some(c as i32 - 'a' as i32 + 10),
7+
'A'..='F' => Some(c as i32 - 'A' as i32 + 10),
8+
_ => None,
9+
}
10+
}
11+
12+
/// Converts two hex characters into a combined integer value.
13+
/// Returns None if either character is invalid.
14+
pub fn hex_to_int(high: char, low: char) -> Option<i32> {
15+
let h = hex_char_to_val(high)?;
16+
let l = hex_char_to_val(low)?;
17+
Some(h * 16 + l)
18+
}
19+
20+
pub fn hex_string_to_int(string: &str, len: usize) -> Option<i32> {
21+
let mut result = 0;
22+
for c in string.chars().take(len) {
23+
result = result * 16 + hex_char_to_val(c)?;
24+
}
25+
Some(result)
26+
}
27+
28+
#[cfg(test)]
29+
mod tests {
30+
use super::*;
31+
32+
#[test]
33+
fn test_hex_to_int() {
34+
assert_eq!(hex_to_int('4', 'f'), Some(79));
35+
assert_eq!(hex_to_int('0', '0'), Some(0));
36+
assert_eq!(hex_to_int('f', 'f'), Some(255));
37+
assert_eq!(hex_to_int('A', 'F'), Some(175));
38+
assert_eq!(hex_to_int('z', '1'), None);
39+
}
40+
41+
#[test]
42+
fn test_hex_string_to_int() {
43+
assert_eq!(hex_string_to_int("4f", 2), Some(79));
44+
assert_eq!(hex_string_to_int("ff", 2), Some(255));
45+
assert_eq!(hex_string_to_int("00", 2), Some(0));
46+
assert_eq!(hex_string_to_int("ffff", 4), Some(65535));
47+
assert_eq!(hex_string_to_int("4f", 1), Some(4));
48+
assert_eq!(hex_string_to_int("FF", 2), Some(255));
49+
assert_eq!(hex_string_to_int("zz", 2), None);
50+
}
51+
}

src/rust/lib_ccxr/src/util/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
pub mod bits;
1414
pub mod encoders_helper;
1515
pub mod encoding;
16+
pub mod hex;
1617
pub mod levenshtein;
1718
pub mod log;
1819
pub mod time;

src/rust/src/encoder/g608.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,20 @@ pub fn write_cc_buffer_as_g608(data: &eia608_screen, context: &mut encoder_ctx)
191191
}
192192

193193
// Create timeline string for timestamps
194-
let timestamp_line = format!(
195-
"{h1:02}:{m1:02}:{s1:02},{ms1:03} --> {h2:02}:{m2:02}:{s2:02},{ms2:03}{encoded_clrf}"
196-
);
194+
let is_webvtt = context.write_format == crate::bindings::ccx_output_format::CCX_OF_WEBVTT;
195+
196+
let timestamp_line = if is_webvtt {
197+
// WebVTT Standard requires dots instead of commas for milliseconds
198+
// and supports trailing settings (e.g., vertical text direction, alignment)
199+
format!(
200+
"{h1:02}:{m1:02}:{s1:02}.{ms1:03} --> {h2:02}:{m2:02}:{s2:02}.{ms2:03}{encoded_clrf}"
201+
)
202+
} else {
203+
// Legacy SRT Standard requires commas
204+
format!(
205+
"{h1:02}:{m1:02}:{s1:02},{ms1:03} --> {h2:02}:{m2:02}:{s2:02},{ms2:03}{encoded_clrf}"
206+
)
207+
};
197208

198209
// Encode and write timestamp line
199210
let used = encode_line(context, buffer_slice, timestamp_line.as_bytes());

src/rust/src/libccxr_exports/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,4 @@ pub unsafe extern "C" fn ccxr_levenshtein_dist_char(
129129

130130
ans.min(c_int::MAX as usize) as c_int
131131
}
132+
pub mod util;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use std::ffi::{c_char, c_int};
2+
3+
/// Rust equivalent for `hex_to_int` function in C. Uses C-native types as input and output.
4+
///
5+
/// # Safety
6+
///
7+
/// `high` and `low` must be valid ASCII characters. Invalid characters will
8+
/// return -1 rather than causing undefined behavior.
9+
#[no_mangle]
10+
pub unsafe extern "C" fn ccxr_hex_to_int(high: c_char, low: c_char) -> c_int {
11+
lib_ccxr::util::hex::hex_to_int(high as u8 as char, low as u8 as char).unwrap_or(-1)
12+
}
13+
14+
/// Rust equivalent for `hex_string_to_int` function in C. Uses C-native types as input and output.
15+
///
16+
/// # Safety
17+
///
18+
/// `string` must be a valid null-terminated C string. If null, returns -1.
19+
/// Invalid hex characters will return -1 rather than causing undefined behavior.
20+
#[no_mangle]
21+
pub unsafe extern "C" fn ccxr_hex_string_to_int(string: *const c_char, len: c_int) -> c_int {
22+
if string.is_null() {
23+
return -1;
24+
}
25+
let s = std::ffi::CStr::from_ptr(string).to_str().unwrap_or("");
26+
lib_ccxr::util::hex::hex_string_to_int(s, len as usize).unwrap_or(-1)
27+
}

0 commit comments

Comments
 (0)