From e474692ff9136d95066dfe1632a21af8cdb55e3a Mon Sep 17 00:00:00 2001
From: dhanush varma
Date: Thu, 11 Dec 2025 00:57:28 +0530
Subject: [PATCH 01/18] chore: ignore local build artifacts and backups
---
.gitignore | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/.gitignore b/.gitignore
index 986573c9e..98a856c50 100644
--- a/.gitignore
+++ b/.gitignore
@@ -155,3 +155,19 @@ windows/*/debug/*
windows/*/CACHEDIR.TAG
windows/.rustc_info.json
linux/configure~
+
+# local junk
+build-system/
+*.backup
+*.save
+*.patch
+*.a
+README.srt
+no_subs.ts
+
+fix_hardsubx.cmake
+fix_libraries.cmake
+mac/configure~
+*.current
+
+
From 0499f4f43c23549ff9c4b73587b4d8fe450142d4 Mon Sep 17 00:00:00 2001
From: dhanush varma
Date: Thu, 11 Dec 2025 01:37:39 +0530
Subject: [PATCH 02/18] rust: add SMPTE-TT encoder skeleton
---
src/rust/src/encoder/mod.rs | 2 +
src/rust/src/encoder/smptett.rs | 75 +++++++++++++++++++++++++++++++++
2 files changed, 77 insertions(+)
create mode 100644 src/rust/src/encoder/smptett.rs
diff --git a/src/rust/src/encoder/mod.rs b/src/rust/src/encoder/mod.rs
index 853abae5a..5d2d75853 100644
--- a/src/rust/src/encoder/mod.rs
+++ b/src/rust/src/encoder/mod.rs
@@ -7,6 +7,8 @@ use std::os::raw::{c_int, c_uchar};
pub mod common;
pub mod g608;
pub mod simplexml;
+pub mod smptett;
+
/// # 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/encoder/smptett.rs b/src/rust/src/encoder/smptett.rs
new file mode 100644
index 000000000..2177d6cbb
--- /dev/null
+++ b/src/rust/src/encoder/smptett.rs
@@ -0,0 +1,75 @@
+use crate::bindings::{
+ cc_subtitle,
+ eia608_screen,
+ encoder_ctx,
+};
+
+use crate::encoder::common::encode_line;
+use crate::libccxr_exports::time::ccxr_millis_to_time;
+
+use std::os::raw::c_int;
+
+/// Core helper: write a SMPTE-TT paragraph from text + times
+fn write_stringz_as_smptett(
+ text: &str,
+ context: &mut encoder_ctx,
+ ms_start: i64,
+ ms_end: i64,
+) {
+ let mut h1 = 0;
+ let mut m1 = 0;
+ let mut s1 = 0;
+ let mut ms1 = 0;
+
+ let mut h2 = 0;
+ let mut m2 = 0;
+ let mut s2 = 0;
+ let mut ms2 = 0;
+
+ unsafe {
+ ccxr_millis_to_time(ms_start, &mut h1, &mut m1, &mut s1, &mut ms1);
+ ccxr_millis_to_time(ms_end, &mut h2, &mut m2, &mut s2, &mut ms2);
+ }
+
+ let header = format!(
+ "",
+ h1, m1, s1, ms1,
+ h2, m2, s2, ms2
+ );
+
+ encode_line(context, &mut context.buffer, header.as_bytes());
+
+ encode_line(context, &mut context.buffer, line.as_bytes());
+ encode_line(context, &mut context.buffer, &context.encoded_crlf);
+}
+for line in text.split('\n') {
+ encode_line(context, &mut context.buffer, line.as_bytes());
+ encode_line(context, &mut context.buffer, &context.encoded_crlf[..]);
+}
+
+ encode_line(context, &mut context.buffer, b"
");
+}
+
+/// SMPTE-TT encoder entry for CC buffer (CEA-608)
+pub fn write_cc_buffer_as_smptett(
+ _data: &mut eia608_screen,
+ _context: &mut encoder_ctx,
+) -> c_int {
+ 0
+}
+
+/// SMPTE-TT encoder entry for subtitle list (text-based)
+pub fn write_cc_subtitle_as_smptett(
+ _sub: &mut cc_subtitle,
+ _context: &mut encoder_ctx,
+) -> c_int {
+ 0
+}
+
+/// SMPTE-TT encoder entry for bitmap subtitles (OCR)
+pub fn write_cc_bitmap_as_smptett(
+ _sub: &mut cc_subtitle,
+ _context: &mut encoder_ctx,
+) -> c_int {
+ 0
+}
From 0c510c1d129b084a96710a15632ce197cd2a89d2 Mon Sep 17 00:00:00 2001
From: dhanush varma
Date: Fri, 12 Dec 2025 20:21:07 +0530
Subject: [PATCH 03/18] rust: add initial SMPTE-TT encoder skeleton
---
src/rust/src/encoder/smptett.rs | 73 ++++++++++++---------------------
1 file changed, 27 insertions(+), 46 deletions(-)
diff --git a/src/rust/src/encoder/smptett.rs b/src/rust/src/encoder/smptett.rs
index 2177d6cbb..a6cdea5e7 100644
--- a/src/rust/src/encoder/smptett.rs
+++ b/src/rust/src/encoder/smptett.rs
@@ -1,34 +1,24 @@
-use crate::bindings::{
- cc_subtitle,
- eia608_screen,
- encoder_ctx,
-};
-
+use crate::bindings::{cc_subtitle, eia608_screen};
+use crate::encoder_ctx;
use crate::encoder::common::encode_line;
use crate::libccxr_exports::time::ccxr_millis_to_time;
use std::os::raw::c_int;
-/// Core helper: write a SMPTE-TT paragraph from text + times
+
+
fn write_stringz_as_smptett(
text: &str,
context: &mut encoder_ctx,
ms_start: i64,
ms_end: i64,
) {
- let mut h1 = 0;
- let mut m1 = 0;
- let mut s1 = 0;
- let mut ms1 = 0;
-
- let mut h2 = 0;
- let mut m2 = 0;
- let mut s2 = 0;
- let mut ms2 = 0;
+ let (mut h1, mut m1, mut s1, mut ms1) = (0, 0, 0, 0);
+ let (mut h2, mut m2, mut s2, mut ms2) = (0, 0, 0, 0);
unsafe {
ccxr_millis_to_time(ms_start, &mut h1, &mut m1, &mut s1, &mut ms1);
- ccxr_millis_to_time(ms_end, &mut h2, &mut m2, &mut s2, &mut ms2);
+ ccxr_millis_to_time(ms_end, &mut h2, &mut m2, &mut s2, &mut ms2);
}
let header = format!(
@@ -37,39 +27,30 @@ fn write_stringz_as_smptett(
h2, m2, s2, ms2
);
- encode_line(context, &mut context.buffer, header.as_bytes());
+ // Turn raw buffer ptr into slice
+ let buffer = unsafe {
+ std::slice::from_raw_parts_mut(context.buffer , context.capacity as usize)
+ };
- encode_line(context, &mut context.buffer, line.as_bytes());
- encode_line(context, &mut context.buffer, &context.encoded_crlf);
-}
-for line in text.split('\n') {
- encode_line(context, &mut context.buffer, line.as_bytes());
- encode_line(context, &mut context.buffer, &context.encoded_crlf[..]);
-}
+ encode_line(context, buffer, header.as_bytes());
- encode_line(context, &mut context.buffer, b"
");
-}
+ for line in text.split('\n') {
+ let buffer = unsafe {
+ std::slice::from_raw_parts_mut(context.buffer, context.capacity as usize)
+ };
+ encode_line(context, buffer, line.as_bytes());
-/// SMPTE-TT encoder entry for CC buffer (CEA-608)
-pub fn write_cc_buffer_as_smptett(
- _data: &mut eia608_screen,
- _context: &mut encoder_ctx,
-) -> c_int {
- 0
-}
+ let crlf = context.encoded_crlf;
-/// SMPTE-TT encoder entry for subtitle list (text-based)
-pub fn write_cc_subtitle_as_smptett(
- _sub: &mut cc_subtitle,
- _context: &mut encoder_ctx,
-) -> c_int {
- 0
+ let buffer = unsafe {
+ std::slice::from_raw_parts_mut(context.buffer, context.capacity as usize)
+ };
+ encode_line(context, buffer, &crlf);
}
-/// SMPTE-TT encoder entry for bitmap subtitles (OCR)
-pub fn write_cc_bitmap_as_smptett(
- _sub: &mut cc_subtitle,
- _context: &mut encoder_ctx,
-) -> c_int {
- 0
+ let buffer = unsafe {
+ std::slice::from_raw_parts_mut(context.buffer, context.capacity as usize)
+ };
+ encode_line(context, buffer, b"");
}
+
From 8107a94190191f12e6ed7d1e5bde532fa756d2bb Mon Sep 17 00:00:00 2001
From: dhanush varma
Date: Fri, 12 Dec 2025 20:23:03 +0530
Subject: [PATCH 04/18] rust: add initial SMPTE-TT encoder skeleton
---
src/rust/Cargo.lock | 147 +++-----------------------------------------
1 file changed, 7 insertions(+), 140 deletions(-)
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index 8f2bc03fc..8181bb82c 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -109,29 +109,6 @@ dependencies = [
"which",
]
-[[package]]
-name = "bindgen"
-version = "0.69.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
-dependencies = [
- "bitflags 2.10.0",
- "cexpr",
- "clang-sys",
- "itertools 0.12.1",
- "lazy_static",
- "lazycell",
- "log",
- "prettyplease",
- "proc-macro2",
- "quote",
- "regex",
- "rustc-hash 1.1.0",
- "shlex",
- "syn 2.0.108",
- "which",
-]
-
[[package]]
name = "bindgen"
version = "0.71.1"
@@ -141,7 +118,7 @@ dependencies = [
"bitflags 2.10.0",
"cexpr",
"clang-sys",
- "itertools 0.13.0",
+ "itertools",
"log",
"prettyplease",
"proc-macro2",
@@ -164,31 +141,6 @@ version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
-[[package]]
-name = "bon"
-version = "3.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebeb9aaf9329dff6ceb65c689ca3db33dbf15f324909c60e4e5eef5701ce31b1"
-dependencies = [
- "bon-macros",
- "rustversion",
-]
-
-[[package]]
-name = "bon-macros"
-version = "3.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77e9d642a7e3a318e37c2c9427b5a6a48aa1ad55dcd986f3034ab2239045a645"
-dependencies = [
- "darling",
- "ident_case",
- "prettyplease",
- "proc-macro2",
- "quote",
- "rustversion",
- "syn 2.0.108",
-]
-
[[package]]
name = "camino"
version = "1.2.1"
@@ -210,8 +162,7 @@ dependencies = [
"num-integer",
"palette",
"pkg-config",
- "rsmpeg 0.14.2+ffmpeg.6.1",
- "rsmpeg 0.18.0+ffmpeg.8.0",
+ "rsmpeg",
"serial_test",
"strum_macros 0.25.3",
"tempfile",
@@ -307,41 +258,6 @@ dependencies = [
"cfg-if",
]
-[[package]]
-name = "darling"
-version = "0.21.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
-dependencies = [
- "darling_core",
- "darling_macro",
-]
-
-[[package]]
-name = "darling_core"
-version = "0.21.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
-dependencies = [
- "fnv",
- "ident_case",
- "proc-macro2",
- "quote",
- "strsim",
- "syn 2.0.108",
-]
-
-[[package]]
-name = "darling_macro"
-version = "0.21.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
-dependencies = [
- "darling_core",
- "quote",
- "syn 2.0.108",
-]
-
[[package]]
name = "deranged"
version = "0.5.5"
@@ -434,12 +350,6 @@ dependencies = [
"toml",
]
-[[package]]
-name = "fnv"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
-
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@@ -667,12 +577,6 @@ dependencies = [
"zerovec",
]
-[[package]]
-name = "ident_case"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
-
[[package]]
name = "idna"
version = "1.1.0"
@@ -710,15 +614,6 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
-[[package]]
-name = "itertools"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
-dependencies = [
- "either",
-]
-
[[package]]
name = "itertools"
version = "0.13.0"
@@ -1140,25 +1035,11 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rsmpeg"
-version = "0.14.2+ffmpeg.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "927012cd6ae43519f519741f4a69602ce3a47cf84750784da124dffd03527cc0"
-dependencies = [
- "libc",
- "paste",
- "rusty_ffmpeg 0.13.3+ffmpeg.6.1",
- "thiserror 1.0.69",
-]
-
-[[package]]
-name = "rsmpeg"
-version = "0.18.0+ffmpeg.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "523351495c9ff0bf4b99ed1f42f1415fc709526ddb63526cff85022b387c5811"
+version = "0.15.3+ffmpeg.7.1"
+source = "git+https://github.com/CCExtractor/rsmpeg.git#2ddc3c05af684893ee9c6874924fc112bff0401e"
dependencies = [
- "bon",
"paste",
- "rusty_ffmpeg 0.16.7+ffmpeg.8",
+ "rusty_ffmpeg",
"thiserror 2.0.17",
]
@@ -1215,30 +1096,16 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
-[[package]]
-name = "rusty_ffmpeg"
-version = "0.13.3+ffmpeg.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "716adffa5f909c8533611b1dab9ab5666bece35687845865b75ed6a990fc239c"
-dependencies = [
- "bindgen 0.69.5",
- "camino",
- "libc",
- "once_cell",
- "pkg-config",
- "vcpkg",
-]
-
[[package]]
name = "rusty_ffmpeg"
version = "0.16.7+ffmpeg.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f25d40a46450059278c9f9f2616018910b647877a66a2093a83f115f59763967"
+source = "git+https://github.com/CCExtractor/rusty_ffmpeg.git#bdbaf09d5fdda4f3fd1278cdd11abe2a47dd9a99"
dependencies = [
"bindgen 0.71.1",
"camino",
"once_cell",
"pkg-config",
+ "vcpkg",
]
[[package]]
From 0820e95ded4f01efcc64f1dcaea2b16c66dcafa8 Mon Sep 17 00:00:00 2001
From: dhanush varma
Date: Fri, 12 Dec 2025 20:49:42 +0530
Subject: [PATCH 05/18] =?UTF-8?q?Fix=20SMPTE-TT=20encoder=20helper:=20poin?=
=?UTF-8?q?ter=E2=86=92slice=20conversion,=20XML=20escaping,=20borrow=20fi?=
=?UTF-8?q?x,=20unimplemented=20stubs?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/rust/src/encoder/smptett.rs | 89 ++++++++++++++++++++++++---------
1 file changed, 66 insertions(+), 23 deletions(-)
diff --git a/src/rust/src/encoder/smptett.rs b/src/rust/src/encoder/smptett.rs
index a6cdea5e7..00087b767 100644
--- a/src/rust/src/encoder/smptett.rs
+++ b/src/rust/src/encoder/smptett.rs
@@ -1,56 +1,99 @@
-use crate::bindings::{cc_subtitle, eia608_screen};
-use crate::encoder_ctx;
+use crate::bindings::{cc_subtitle, eia608_screen, encoder_ctx};
use crate::encoder::common::encode_line;
use crate::libccxr_exports::time::ccxr_millis_to_time;
use std::os::raw::c_int;
-
+fn escape_xml(s: &str) -> String {
+ let mut out = String::with_capacity(s.len());
+ for c in s.chars() {
+ match c {
+ '&' => out.push_str("&"),
+ '<' => out.push_str("<"),
+ '>' => out.push_str(">"),
+ '"' => out.push_str("""),
+ '\'' => out.push_str("'"),
+ _ => out.push(c),
+ }
+ }
+ out
+}
fn write_stringz_as_smptett(
text: &str,
context: &mut encoder_ctx,
ms_start: i64,
ms_end: i64,
-) {
+){
let (mut h1, mut m1, mut s1, mut ms1) = (0, 0, 0, 0);
let (mut h2, mut m2, mut s2, mut ms2) = (0, 0, 0, 0);
unsafe {
ccxr_millis_to_time(ms_start, &mut h1, &mut m1, &mut s1, &mut ms1);
- ccxr_millis_to_time(ms_end, &mut h2, &mut m2, &mut s2, &mut ms2);
+ ccxr_millis_to_time(ms_end, &mut h2, &mut m2, &mut s2, &mut ms2);
}
+ // Convert raw pointer buffer → Rust slice
+ let buffer: &mut [u8] = unsafe {
+ std::slice::from_raw_parts_mut(context.buffer, context.capacity as usize)
+ };
+
+ // Copy CRLF slice so we don't immutably borrow from context
+ let crlf = context.encoded_crlf;
+
let header = format!(
"",
h1, m1, s1, ms1,
h2, m2, s2, ms2
);
- // Turn raw buffer ptr into slice
- let buffer = unsafe {
- std::slice::from_raw_parts_mut(context.buffer , context.capacity as usize)
- };
-
encode_line(context, buffer, header.as_bytes());
+ encode_line(context, buffer, &crlf);
for line in text.split('\n') {
- let buffer = unsafe {
- std::slice::from_raw_parts_mut(context.buffer, context.capacity as usize)
- };
- encode_line(context, buffer, line.as_bytes());
-
- let crlf = context.encoded_crlf;
+ let escaped = escape_xml(line);
+ encode_line(context, buffer, escaped.as_bytes());
+ encode_line(context, buffer, &crlf);
+ }
- let buffer = unsafe {
- std::slice::from_raw_parts_mut(context.buffer, context.capacity as usize)
- };
+ encode_line(context, buffer, b"
");
encode_line(context, buffer, &crlf);
}
- let buffer = unsafe {
- std::slice::from_raw_parts_mut(context.buffer, context.capacity as usize)
- };
- encode_line(context, buffer, b"");
+
+
+pub fn write_cc_buffer_as_smptett(
+ _: &mut encoder_ctx,
+ _: &eia608_screen,
+ _: i64,
+ _: i64,
+) -> c_int {
+ unimplemented!()
}
+pub fn write_cc_subtitle_as_smptett(_: &mut encoder_ctx, _: &cc_subtitle) -> c_int {
+ unimplemented!()
+}
+
+pub fn write_cc_bitmap_as_smptett(
+ _: &mut encoder_ctx,
+ _: &[u8],
+ _: usize,
+ _: usize,
+) -> c_int {
+ unimplemented!()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::escape_xml;
+
+ #[test]
+ fn test_smptett_escape_basic() {
+ assert_eq!(escape_xml("a & b < c"), "a & b < c");
+ assert_eq!(
+ escape_xml("\"quote\" 'apos' & < >"),
+ ""quote" 'apos' & < >"
+ );
+ }
+}
From b71ad400a8e1c89d59a548981afee48f750b1354 Mon Sep 17 00:00:00 2001
From: Dhanush Varma
Date: Sun, 14 Dec 2025 15:00:37 +0530
Subject: [PATCH 06/18] Complete SMPTE-TT encoder port to Rust
- Implemented write_cc_buffer_as_smptett with full styling support
* Handles italics, bold, underline, and font color styling
* Calculates proper row/column positioning for SMPTE-TT
* Converts HTML-style tags to SMPTE-TT style attributes
- Implemented write_cc_subtitle_as_smptett
* Processes linked list of subtitle structures
* Properly manages memory and frees C-allocated data
* Handles CC_TEXT subtitle types
- Implemented write_cc_bitmap_as_smptett
* Feature-gated for OCR support (hardsubx_ocr)
* Proper cleanup of bitmap data
- Added helper functions:
* escape_xml() for XML entity escaping
* write_stringz_as_smptett() for core formatting
* process_line_styling() for style tag conversion
* write_wrapped() for safe file I/O
- Added comprehensive test coverage:
* XML escaping tests
* Styling conversion tests (italic, bold, no styling)
* All tests passing (268 total)
Matches C implementation behavior while using safe Rust idioms.
Fixes #1789
---
src/rust/src/encoder/smptett.rs | 414 +++++++++++++++++++++++++++++---
1 file changed, 381 insertions(+), 33 deletions(-)
diff --git a/src/rust/src/encoder/smptett.rs b/src/rust/src/encoder/smptett.rs
index 00087b767..9d67e6a71 100644
--- a/src/rust/src/encoder/smptett.rs
+++ b/src/rust/src/encoder/smptett.rs
@@ -1,8 +1,16 @@
-use crate::bindings::{cc_subtitle, eia608_screen, encoder_ctx};
-use crate::encoder::common::encode_line;
+use crate::bindings::{cc_subtitle, eia608_screen, encoder_ctx, ccx_encoding_type_CCX_ENC_UNICODE, subtype_CC_TEXT};
+use crate::encoder::common::{encode_line, write_raw};
use crate::libccxr_exports::time::ccxr_millis_to_time;
+use lib_ccxr::util::log::DebugMessageFlag;
+use lib_ccxr::debug;
-use std::os::raw::c_int;
+use std::os::raw::{c_int, c_void};
+use std::ffi::CStr;
+use std::io;
+
+// Constants from C code
+const ROWS: i32 = 15;
+const COLUMNS: i32 = 32;
fn escape_xml(s: &str) -> String {
let mut out = String::with_capacity(s.len());
@@ -19,18 +27,37 @@ fn escape_xml(s: &str) -> String {
out
}
+/// Write data to file descriptor with retry logic
+fn write_wrapped(fd: c_int, buf: &[u8]) -> Result<(), io::Error> {
+ let mut remaining = buf.len();
+ let mut current_buf = buf.as_ptr();
+
+ while remaining > 0 {
+ let written = write_raw(fd, current_buf as *const c_void, remaining);
+ if written == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ unsafe {
+ current_buf = current_buf.add(written as usize);
+ }
+ remaining -= written as usize;
+ }
+
+ Ok(())
+}
+
fn write_stringz_as_smptett(
text: &str,
context: &mut encoder_ctx,
ms_start: i64,
ms_end: i64,
-){
+) {
let (mut h1, mut m1, mut s1, mut ms1) = (0, 0, 0, 0);
let (mut h2, mut m2, mut s2, mut ms2) = (0, 0, 0, 0);
unsafe {
ccxr_millis_to_time(ms_start, &mut h1, &mut m1, &mut s1, &mut ms1);
- ccxr_millis_to_time(ms_end, &mut h2, &mut m2, &mut s2, &mut ms2);
+ ccxr_millis_to_time(ms_end - 1, &mut h2, &mut m2, &mut s2, &mut ms2);
}
// Convert raw pointer buffer → Rust slice
@@ -38,55 +65,354 @@ fn write_stringz_as_smptett(
std::slice::from_raw_parts_mut(context.buffer, context.capacity as usize)
};
- // Copy CRLF slice so we don't immutably borrow from context
- let crlf = context.encoded_crlf;
-
let header = format!(
- "",
- h1, m1, s1, ms1,
- h2, m2, s2, ms2
+ "
\r\n",
+ h1, m1, s1, ms1, h2, m2, s2, ms2
);
- encode_line(context, buffer, header.as_bytes());
- encode_line(context, buffer, &crlf);
+ if context.encoding != ccx_encoding_type_CCX_ENC_UNICODE {
+ debug!(msg_type = DebugMessageFlag::DECODER_608; "\r{}\n", header.trim_end());
+ }
+
+ let used = encode_line(context, buffer, header.as_bytes());
+ unsafe {
+ let out = &*context.out;
+ let _ = write_wrapped(out.fh, &buffer[..used as usize]);
+ }
+ // Process lines - split by \n in the string
for line in text.split('\n') {
- let escaped = escape_xml(line);
- encode_line(context, buffer, escaped.as_bytes());
- encode_line(context, buffer, &crlf);
+ if !line.is_empty() {
+ let escaped = escape_xml(line);
+ let used = encode_line(context, buffer, escaped.as_bytes());
+
+ if context.encoding != ccx_encoding_type_CCX_ENC_UNICODE {
+ debug!(msg_type = DebugMessageFlag::DECODER_608; "\r{}\n", escaped);
+ }
+
+ unsafe {
+ let out = &*context.out;
+ let _ = write_wrapped(out.fh, &buffer[..used as usize]);
+
+ // Write CRLF
+ let crlf_slice = std::slice::from_raw_parts(
+ context.encoded_crlf.as_ptr(),
+ context.encoded_crlf_length as usize
+ );
+ let _ = write_wrapped(out.fh, crlf_slice);
+ }
+ }
}
- encode_line(context, buffer, b"
");
- encode_line(context, buffer, &crlf);
+ let footer = b"\n";
+ let used = encode_line(context, buffer, footer);
+
+ if context.encoding != ccx_encoding_type_CCX_ENC_UNICODE {
+ debug!(msg_type = DebugMessageFlag::DECODER_608; "\r\n");
+ }
+
+ unsafe {
+ let out = &*context.out;
+ let _ = write_wrapped(out.fh, &buffer[..used as usize]);
+ }
}
-
-
pub fn write_cc_buffer_as_smptett(
- _: &mut encoder_ctx,
- _: &eia608_screen,
- _: i64,
- _: i64,
+ data: &mut eia608_screen,
+ context: &mut encoder_ctx,
) -> c_int {
- unimplemented!()
+ let (mut h1, mut m1, mut s1, mut ms1) = (0, 0, 0, 0);
+ let (mut h2, mut m2, mut s2, mut ms2) = (0, 0, 0, 0);
+ let mut wrote_something = 0;
+
+ unsafe {
+ ccxr_millis_to_time(data.start_time, &mut h1, &mut m1, &mut s1, &mut ms1);
+ ccxr_millis_to_time(data.end_time - 1, &mut h2, &mut m2, &mut s2, &mut ms2);
+ }
+
+ let buffer: &mut [u8] = unsafe {
+ std::slice::from_raw_parts_mut(context.buffer, context.capacity as usize)
+ };
+
+ for row in 0..15 {
+ if data.row_used[row] == 0 {
+ continue;
+ }
+
+ // Calculate row position (10% + row position in 80% of screen)
+ let row1 = ((100.0 * row as f32) / (ROWS as f32 / 0.8)) + 10.0;
+
+ // Find first non-space column
+ let mut firstcol = -1;
+ for column in 0..COLUMNS as usize {
+ let ch = data.characters[row][column];
+ // Check for non-space character (0x20 is space)
+ if ch != 0x20 && ch != 0 {
+ firstcol = column as i32;
+ break;
+ }
+ }
+
+ if firstcol < 0 {
+ continue;
+ }
+
+ // Calculate column position
+ let col1 = ((100.0 * firstcol as f32) / (COLUMNS as f32 / 0.8)) + 10.0;
+
+ wrote_something = 1;
+
+ // Write opening tag with positioning
+ let opening = format!(
+ " \n ",
+ h1, m1, s1, ms1, h2, m2, s2, ms2, col1, row1
+ );
+
+ if context.encoding != ccx_encoding_type_CCX_ENC_UNICODE {
+ debug!(msg_type = DebugMessageFlag::DECODER_608; "\r{}\n", opening.trim_end());
+ }
+
+ let used = encode_line(context, buffer, opening.as_bytes());
+ unsafe {
+ let out = &*context.out;
+ let _ = write_wrapped(out.fh, &buffer[..used as usize]);
+ }
+
+ // Get the decoded line - store it in context.subline
+ unsafe {
+ // This function exists in the C code and should be available via bindings
+ extern "C" {
+ fn get_decoder_line_encoded(
+ ctx: *mut encoder_ctx,
+ buf: *mut u8,
+ line_num: c_int,
+ data: *const eia608_screen,
+ ) -> u32;
+ }
+
+ get_decoder_line_encoded(
+ context as *mut encoder_ctx,
+ context.subline,
+ row as c_int,
+ data as *const eia608_screen,
+ );
+ }
+
+ // Convert subline to Rust string for processing
+ let line_text = unsafe {
+ let subline_slice = std::slice::from_raw_parts(
+ context.subline,
+ 4096 // Max line length
+ );
+ let null_pos = subline_slice.iter().position(|&b| b == 0).unwrap_or(4096);
+ String::from_utf8_lossy(&subline_slice[..null_pos]).to_string()
+ };
+
+ // Process styling (italics, bold, underline, colors)
+ let formatted = process_line_styling(&line_text);
+
+ let used = encode_line(context, buffer, formatted.as_bytes());
+ unsafe {
+ let out = &*context.out;
+ let _ = write_wrapped(out.fh, &buffer[..used as usize]);
+
+ // Write CRLF
+ let crlf_slice = std::slice::from_raw_parts(
+ context.encoded_crlf.as_ptr(),
+ context.encoded_crlf_length as usize
+ );
+ let _ = write_wrapped(out.fh, crlf_slice);
+ }
+
+ // Write closing tags
+ let closing = b" \n
\n";
+ let used = encode_line(context, buffer, closing);
+
+ if context.encoding != ccx_encoding_type_CCX_ENC_UNICODE {
+ debug!(msg_type = DebugMessageFlag::DECODER_608; "\r{}\n",
+ std::str::from_utf8(closing).unwrap_or(""));
+ }
+
+ unsafe {
+ let out = &*context.out;
+ let _ = write_wrapped(out.fh, &buffer[..used as usize]);
+ }
+ }
+
+ wrote_something
+}
+
+fn process_line_styling(line: &str) -> String {
+ let mut result = String::new();
+
+ // Check for italics
+ if let Some(start_pos) = line.find("") {
+ if let Some(end_pos) = line.find("") {
+ result.push_str(&line[..start_pos]);
+ result.push_str("");
+ result.push_str(&line[start_pos + 3..end_pos]);
+ result.push_str("