diff --git a/src/lib_ccx/ccx_decoders_isdb.c b/src/lib_ccx/ccx_decoders_isdb.c index b06471e7f..8e87081b2 100644 --- a/src/lib_ccx/ccx_decoders_isdb.c +++ b/src/lib_ccx/ccx_decoders_isdb.c @@ -1,9 +1,33 @@ - #include "ccx_decoders_isdb.h" #include "lib_ccx.h" #include "utility.h" #include "limits.h" +#ifndef DISABLE_RUST + +extern int ccxr_isdb_set_global_time( + struct lib_cc_decode *dec_ctx, + unsigned long long timestamp); + +extern void ccxr_delete_isdb_decoder( + void **isdb_ctx); + +extern struct ISDBSubContext *ccxr_init_isdb_decoder( + void); + +extern int ccxr_isdb_parse_data_group( + struct ISDBSubContext *codec_ctx, + const unsigned char *buf, + struct cc_subtitle *sub); + +extern int ccxr_isdbsub_decode( + struct lib_cc_decode *dec_ctx, + const unsigned char *buf, + size_t buf_len, + struct cc_subtitle *sub); + +#endif + // #define DEBUG // #define COMMAND_DEBUG @@ -281,6 +305,9 @@ typedef struct */ void delete_isdb_decoder(void **isdb_ctx) { +#ifndef DISABLE_RUST + ccxr_delete_isdb_decoder(isdb_ctx); +#else ISDBSubContext *ctx = *isdb_ctx; struct ISDBText *text = NULL; struct ISDBText *text1 = NULL; @@ -298,6 +325,7 @@ void delete_isdb_decoder(void **isdb_ctx) free(text); } freep(isdb_ctx); +#endif } static void init_layout(ISDBSubLayout *ls) @@ -312,6 +340,9 @@ static void init_layout(ISDBSubLayout *ls) void *init_isdb_decoder(void) { +#ifndef DISABLE_RUST + return ccxr_init_isdb_decoder(); +#else ISDBSubContext *ctx; ctx = malloc(sizeof(ISDBSubContext)); @@ -326,6 +357,7 @@ void *init_isdb_decoder(void) ctx->current_state.rollup_mode = 0; init_layout(&ctx->current_state.layout_state); return ctx; +#endif } /** @@ -469,6 +501,7 @@ static int ccx_strstr_ignorespace(const unsigned char *str1, const unsigned char } return 1; } + /** * Copy data not more then len provided * User should check for return type to check how much data he has got @@ -1328,6 +1361,9 @@ static int parse_caption_statement_data(ISDBSubContext *ctx, int lang_id, const */ int isdb_parse_data_group(void *codec_ctx, const uint8_t *buf, struct cc_subtitle *sub) { +#ifndef DISABLE_RUST + return ccxr_isdb_parse_data_group(codec_ctx, buf, sub); +#else ISDBSubContext *ctx = codec_ctx; const uint8_t *buf_pivot = buf; int id = (*buf >> 2); @@ -1393,10 +1429,14 @@ int isdb_parse_data_group(void *codec_ctx, const uint8_t *buf, struct cc_subtitl buf += 2; return buf - buf_pivot; +#endif } int isdbsub_decode(struct lib_cc_decode *dec_ctx, const uint8_t *buf, size_t buf_size, struct cc_subtitle *sub) { +#ifndef DISABLE_RUST + return ccxr_isdbsub_decode(dec_ctx, buf, buf_size, sub); +#else const uint8_t *header_end = NULL; int ret = 0; ISDBSubContext *ctx = dec_ctx->private_data; @@ -1421,10 +1461,16 @@ int isdbsub_decode(struct lib_cc_decode *dec_ctx, const uint8_t *buf, size_t buf return -1; return 1; +#endif } + int isdb_set_global_time(struct lib_cc_decode *dec_ctx, uint64_t timestamp) { +#ifndef DISABLE_RUST + return ccxr_isdb_set_global_time(dec_ctx, timestamp); +#else ISDBSubContext *ctx = dec_ctx->private_data; ctx->timestamp = timestamp; return CCX_OK; -} +#endif +} \ No newline at end of file diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 37aa15a8b..93ca302ac 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -610,6 +610,8 @@ dependencies = [ "bitflags 2.9.0", "crc32fast", "derive_more", + "env_logger", + "log", "num_enum", "strum 0.26.3", "strum_macros 0.26.4", @@ -1441,4 +1443,4 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.99", -] +] \ No newline at end of file diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index b94861db2..96fa7b309 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -29,6 +29,7 @@ num-integer = "0.1.46" lib_ccxr = { path = "lib_ccxr" } url = "2.5.4" + [build-dependencies] bindgen = "0.64.0" pkg-config = "0.3.32" @@ -42,4 +43,4 @@ hardsubx_ocr = ["rsmpeg", "tesseract-sys", "leptonica-sys"] [profile.release-with-debug] inherits = "release" -debug = true +debug = true \ No newline at end of file diff --git a/src/rust/lib_ccxr/Cargo.lock b/src/rust/lib_ccxr/Cargo.lock index 8ec94ecf1..10873ab23 100644 --- a/src/rust/lib_ccxr/Cargo.lock +++ b/src/rust/lib_ccxr/Cargo.lock @@ -2,6 +2,26 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "bitflags" version = "2.9.0" @@ -62,6 +82,19 @@ dependencies = [ "syn", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -89,6 +122,21 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + [[package]] name = "icu_collections" version = "1.5.0" @@ -251,6 +299,8 @@ dependencies = [ "bitflags", "crc32fast", "derive_more", + "env_logger", + "log", "num_enum", "strum", "strum_macros", @@ -259,12 +309,24 @@ dependencies = [ "url", ] +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + [[package]] name = "litemap" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + [[package]] name = "memchr" version = "2.7.4" @@ -344,6 +406,35 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "rustc_version" version = "0.4.1" @@ -438,6 +529,15 @@ dependencies = [ "syn", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -545,6 +645,110 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "0.5.40" diff --git a/src/rust/lib_ccxr/Cargo.toml b/src/rust/lib_ccxr/Cargo.toml index 6162aef3a..3e7a78923 100644 --- a/src/rust/lib_ccxr/Cargo.toml +++ b/src/rust/lib_ccxr/Cargo.toml @@ -15,6 +15,8 @@ strum = "0.26.3" strum_macros = "0.26.4" crc32fast = "1.4.2" num_enum = "0.6.1" +log = "0.4.26" +env_logger = "0.8.4" [features] default = [ @@ -29,4 +31,4 @@ wtv_debug = [] enable_ffmpeg = [] debug_out = [] debug = [] -with_libcurl = [] +with_libcurl = [] \ No newline at end of file diff --git a/src/rust/lib_ccxr/src/decoder_isdb/exit_codes.rs b/src/rust/lib_ccxr/src/decoder_isdb/exit_codes.rs new file mode 100644 index 000000000..118ffe7a3 --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_isdb/exit_codes.rs @@ -0,0 +1,56 @@ +// Status codes +pub const CCX_OK: i64 = 0; +pub const CCX_FALSE: i64 = 0; +pub const CCX_TRUE: i64 = 1; +pub const CCX_EAGAIN: i64 = -100; +pub const CCX_EOF: i64 = -101; +pub const CCX_EINVAL: i64 = -102; +pub const CCX_ENOSUPP: i64 = -103; +pub const CCX_ENOMEM: i64 = -104; + +pub const NUM_BYTES_PER_PACKET: i64 = 35; // Class + type (repeated for convenience) + data + zero +pub const NUM_XDS_BUFFERS: i64 = 9; // CEA recommends no more than one level of interleaving. Play it safe + +pub const MAXBFRAMES: usize = 50; +pub const SORTBUF: usize = 2 * MAXBFRAMES + 1; + +// Time at which we switched to XDS mode, -1 means it hasn't happened yet +pub const TS_START_OF_XDS: i64 = -1; + +// Exit codes +pub const EXIT_OK: i64 = 0; +pub const EXIT_NO_INPUT_FILES: i64 = 2; +pub const EXIT_TOO_MANY_INPUT_FILES: i64 = 3; +pub const EXIT_INCOMPATIBLE_PARAMETERS: i64 = 4; +pub const EXIT_UNABLE_TO_DETERMINE_FILE_SIZE: i64 = 6; +pub const EXIT_MALFORMED_PARAMETER: i64 = 7; +pub const EXIT_READ_ERROR: i64 = 8; +pub const EXIT_NO_CAPTIONS: i64 = 10; +pub const EXIT_WITH_HELP: i64 = 11; +pub const EXIT_NOT_CLASSIFIED: i64 = 300; +pub const EXIT_ERROR_IN_CAPITALIZATION_FILE: i64 = 501; +pub const EXIT_BUFFER_FULL: i64 = 502; +pub const EXIT_MISSING_ASF_HEADER: i64 = 1001; +pub const EXIT_MISSING_RCWT_HEADER: i64 = 1002; + +// Common exit codes +pub const CCX_COMMON_EXIT_FILE_CREATION_FAILED: i64 = 5; +pub const CCX_COMMON_EXIT_UNSUPPORTED: i64 = 9; +pub const EXIT_NOT_ENOUGH_MEMORY: i64 = 500; +pub const CCX_COMMON_EXIT_BUG_BUG: i64 = 1000; + +// Define max width in characters/columns on the screen +pub const CCX_DECODER_608_SCREEN_ROWS: usize = 15; +pub const CCX_DECODER_608_SCREEN_WIDTH: usize = 32; + +//isdb specific codes +pub const CCX_DTVCC_MAX_SERVICES: usize = 63; +pub const CCX_DTVCC_MAX_WINDOWS: usize = 8; +pub const CCX_DTVCC_MAX_ROWS: usize = 15; +pub const CCX_DTVCC_SCREENGRID_COLUMNS: usize = 210; +pub const CCX_DTVCC_MAX_PACKET_LENGTH: usize = 128; +pub const CCX_DTVCC_SCREENGRID_ROWS: usize = 75; + +pub const CCX_MESSAGES_QUIET: i32 = 0; +pub const CCX_MESSAGES_STDOUT: i32 = 1; +pub const CCX_MESSAGES_STDERR: i32 = 2; diff --git a/src/rust/lib_ccxr/src/decoder_isdb/functions_common.rs b/src/rust/lib_ccxr/src/decoder_isdb/functions_common.rs new file mode 100644 index 000000000..d4c55e7a0 --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_isdb/functions_common.rs @@ -0,0 +1,173 @@ +use crate::decoder_isdb::structs_xds::{CcSubtitle, CcxEncodingType, SubDataType, SubType}; + +/// # Safety +/// +/// - The caller must ensure that `sub` is a valid, mutable reference to a `CcSubtitle`. +/// - The `sub` parameter must point to a properly initialized `CcSubtitle` structure. +/// - The `info` and `mode` strings must not exceed 4 bytes in length; otherwise, they will be truncated. +/// - The function uses unsafe operations to manipulate pointers, so improper usage may result in undefined behavior. +pub fn add_cc_sub_text( + sub: &mut CcSubtitle, + str: &str, + start_time: i64, + end_time: i64, + info: &str, + mode: &str, + e_type: CcxEncodingType, +) -> i32 { + if str.is_empty() { + return 0; + } + + // Traverse to the last subtitle node if there are existing subtitles + let mut current_sub = sub; + while let Some(next_sub) = unsafe { current_sub.next.as_mut() } { + current_sub = next_sub; + } + + // Allocate a new subtitle node if needed + if current_sub.nb_data > 0 { + let new_sub = Box::new(CcSubtitle { + prev: current_sub as *mut CcSubtitle, + ..Default::default() + }); + + current_sub.next = Box::into_raw(new_sub); + + current_sub = unsafe { &mut *current_sub.next }; + } + + // Populate the subtitle fields + current_sub.subtype = SubType::Text; + current_sub.enc_type = e_type; + current_sub.data = Box::into_raw(Box::new(str.to_string())) as *mut std::ffi::c_void; + current_sub.datatype = SubDataType::Generic; + current_sub.nb_data = str.len() as u32; + current_sub.start_time = start_time; + current_sub.end_time = end_time; + + if !info.is_empty() { + current_sub.info[..info.len().min(4)] + .copy_from_slice(&info.as_bytes()[..info.len().min(4)]); + } + + if !mode.is_empty() { + current_sub.mode[..mode.len().min(4)] + .copy_from_slice(&mode.as_bytes()[..mode.len().min(4)]); + } + + current_sub.got_output = true; + current_sub.next = std::ptr::null_mut(); + + 0 +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::decoder_isdb::structs_xds::{CcSubtitle, CcxEncodingType, SubDataType, SubType}; + + #[test] + fn test_add_cc_sub_text_basic() { + let mut subtitle = CcSubtitle::default(); + let result = add_cc_sub_text( + &mut subtitle, + "Hello, world!", + 1000, + 2000, + "info", + "mode", + CcxEncodingType::Utf8, + ); + + assert_eq!(result, 0); + assert_eq!(subtitle.nb_data, 13); // "Hello, world!" has 13 characters + assert_eq!(subtitle.start_time, 1000); + assert_eq!(subtitle.end_time, 2000); + assert_eq!(subtitle.subtype, SubType::Text); + assert_eq!(subtitle.enc_type, CcxEncodingType::Utf8); + assert_eq!(subtitle.datatype, SubDataType::Generic); + assert!(subtitle.got_output); + } + + #[test] + fn test_add_cc_sub_text_empty_string() { + let mut subtitle = CcSubtitle::default(); + let result = add_cc_sub_text( + &mut subtitle, + "", + 1000, + 2000, + "info", + "mode", + CcxEncodingType::Utf8, + ); + + assert_eq!(result, 0); + assert_eq!(subtitle.nb_data, 0); + assert_eq!(subtitle.start_time, 0); + assert_eq!(subtitle.end_time, 0); + assert!(!subtitle.got_output); + } + + #[test] + fn test_add_cc_sub_text_truncated_info_and_mode() { + let mut subtitle = CcSubtitle::default(); + let result = add_cc_sub_text( + &mut subtitle, + "Test", + 1000, + 2000, + "longinfo", + "longmode", + CcxEncodingType::Utf8, + ); + + assert_eq!(result, 0); + assert_eq!(subtitle.nb_data, 4); // "Test" has 4 characters + assert_eq!(subtitle.start_time, 1000); + assert_eq!(subtitle.end_time, 2000); + assert_eq!(subtitle.info, *b"long"); // Only the first 4 bytes of "longinfo" are used + assert_eq!(&subtitle.mode[..4], b"long"); // Only the first 4 bytes of "longmode" are used + assert!(subtitle.got_output); + } + + #[test] + fn test_add_cc_sub_text_multiple_nodes() { + let mut subtitle = CcSubtitle::default(); + add_cc_sub_text( + &mut subtitle, + "First", + 1000, + 2000, + "info", + "mode", + CcxEncodingType::Utf8, + ); + + let result = add_cc_sub_text( + &mut subtitle, + "Second", + 3000, + 4000, + "info2", + "mode2", + CcxEncodingType::Utf8, + ); + + assert_eq!(result, 0); + + // Verify the first node + assert_eq!(subtitle.nb_data, 5); // "First" has 5 characters + assert_eq!(subtitle.start_time, 1000); + assert_eq!(subtitle.end_time, 2000); + assert!(subtitle.next.is_null() == false); + + // Verify the second node + let second_subtitle = unsafe { &*subtitle.next }; + assert_eq!(second_subtitle.nb_data, 6); // "Second" has 6 characters + assert_eq!(second_subtitle.start_time, 3000); + assert_eq!(second_subtitle.end_time, 4000); + assert!(second_subtitle.next.is_null()); + } +} diff --git a/src/rust/lib_ccxr/src/decoder_isdb/functions_isdb.rs b/src/rust/lib_ccxr/src/decoder_isdb/functions_isdb.rs new file mode 100644 index 000000000..36a59b735 --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_isdb/functions_isdb.rs @@ -0,0 +1,1986 @@ +use crate::decoder_isdb::exit_codes::*; +use crate::decoder_isdb::functions_common::*; +use crate::decoder_isdb::structs_ccdecode::*; +use crate::decoder_isdb::structs_isdb::*; +use crate::decoder_isdb::structs_xds::*; + +use std::os::raw::c_char; + +use log::info; // use info!() for isdb_log and isdb_command_log + +pub fn is_horizontal_layout(format: WritingFormat) -> bool { + matches!( + format, + WritingFormat::HorizontalStdDensity + | WritingFormat::HorizontalHighDensity + | WritingFormat::HorizontalWesternLang + | WritingFormat::Horizontal1920x1080 + | WritingFormat::Horizontal960x540 + | WritingFormat::Horizontal720x480 + | WritingFormat::Horizontal1280x720 + | WritingFormat::HorizontalCustom + ) +} + +#[no_mangle] +pub fn layout_get_width(format: WritingFormat) -> u32 { + match format { + WritingFormat::Horizontal960x540 | WritingFormat::Vertical960x540 => 960, + _ => 720, + } +} + +#[no_mangle] +pub fn layout_get_height(format: WritingFormat) -> u32 { + match format { + WritingFormat::Horizontal960x540 | WritingFormat::Vertical960x540 => 540, + _ => 480, + } +} + +/// # Safety +/// +/// - The caller must ensure that `ls` is a valid, non-null pointer to an `ISDBSubLayout`. +/// - Improper usage of the pointer may result in undefined behavior. +pub unsafe fn init_layout(ls: *mut ISDBSubLayout) { + unsafe { + (*ls).font_size = 36; + (*ls).display_area.x = 0; + (*ls).display_area.y = 0; + (*ls).font_scale.fscx = 100; + (*ls).font_scale.fscy = 100; + } +} + +/// # Safety +/// +/// - The caller must ensure that `text` is a valid, mutable reference to an `ISDBText`. +/// - The function manipulates raw pointers and performs unsafe operations, so improper usage may result in undefined behavior. +pub fn reserve_buf(text: &mut ISDBText, len: usize) -> Result<(), &'static str> { + // Check if the current buffer length is sufficient + if text.len >= text.used + len { + return Ok(()); + } + + // Calculate the new buffer length (always in multiples of 128) + let blen = ((text.used + len + 127) >> 7) << 7; + + // Attempt to resize the buffer + let mut new_buf = Vec::with_capacity(blen); + new_buf.extend_from_slice(unsafe { std::slice::from_raw_parts(text.buf, text.used) }); + + // Update the buffer and its length + text.buf = new_buf.as_mut_ptr(); + text.len = blen; + + // Log the expansion + info!("expanded ctx->text({})", blen); + + Ok(()) +} + +/// # Safety +/// +/// - The caller must ensure that `str1` and `str2` are valid, null-terminated C-style strings. +/// - Passing invalid or null pointers will result in undefined behavior. +pub unsafe fn ccx_strstr_ignorespace(str1: *const c_char, str2: *const c_char) -> i32 { + unsafe { + let mut i = 0; + while *str2.add(i) != 0 { + if (*str2.add(i) as u8).is_ascii_whitespace() { + i += 1; + continue; + } + if *str2.add(i) != *str1.add(i) { + return 0; + } + i += 1; + } + 1 + } +} + +/// # Safety +/// +/// - The caller must ensure that `isdb_ctx` is a valid, mutable reference to an `Option>`. +/// - The function performs unsafe operations to free memory, so improper usage may result in undefined behavior. +pub fn delete_isdb_decoder(isdb_ctx: &mut Option>) { + if let Some(ctx) = isdb_ctx.as_mut() { + // Iterate over `text_list_head` and free its elements + while let Some(text) = unsafe { (ctx.text_list_head.next as *mut ISDBText).as_mut() } { + unsafe { + // Remove the element from the list + (*text.list.prev).next = text.list.next; + (*text.list.next).prev = text.list.prev; + } + // Free the buffer + if !text.buf.is_null() { + unsafe { drop(Box::from_raw(text.buf)) }; + } + // Free the text structure + unsafe { drop(Box::from_raw(text)) }; + } + + // Iterate over `buffered_text` and free its elements + while let Some(text) = unsafe { (ctx.buffered_text.next as *mut ISDBText).as_mut() } { + unsafe { + // Remove the element from the list + (*text.list.prev).next = text.list.next; + (*text.list.next).prev = text.list.prev; + } + // Free the buffer + if !text.buf.is_null() { + unsafe { drop(Box::from_raw(text.buf)) }; + } + // Free the text structure + unsafe { drop(Box::from_raw(text)) }; + } + + // Free the ISDBSubContext itself + *isdb_ctx = None; + } +} + +/// # Safety +/// +/// - This function is safe as it does not perform any unsafe operations. +/// - The caller is responsible for managing the lifetime of the returned `Box`. +pub fn init_isdb_decoder() -> Option> { + // Allocate memory for ISDBSubContext + let mut ctx = Box::new(ISDBSubContext { + text_list_head: ListHead { + next: std::ptr::null_mut(), + prev: std::ptr::null_mut(), + }, + buffered_text: ListHead { + next: std::ptr::null_mut(), + prev: std::ptr::null_mut(), + }, + prev_timestamp: u64::MAX, + current_state: ISDBSubState { + clut_high_idx: 0, + rollup_mode: 0, + ..Default::default() + }, + ..Default::default() + }); + + // Initialize the list heads + ctx.text_list_head.next = &mut ctx.text_list_head as *mut ListHead; + ctx.text_list_head.prev = &mut ctx.text_list_head as *mut ListHead; + ctx.buffered_text.next = &mut ctx.buffered_text as *mut ListHead; + ctx.buffered_text.prev = &mut ctx.buffered_text as *mut ListHead; + + // Initialize the layout state + unsafe { + init_layout(&mut ctx.current_state.layout_state); + } + + Some(ctx) +} + +/// # Safety +/// +/// - The caller must ensure that `ls` is a valid, mutable reference to an `ISDBSubLayout`. +/// - The function manipulates raw pointers and performs unsafe operations, so improper usage may result in undefined behavior. +pub fn allocate_text_node(ls: &mut ISDBSubLayout) -> Option> { + // assign ls to uls + let _uls = ls as *mut ISDBSubLayout; // ls unused + + // Allocate memory for the ISDBText structure + let mut text = Box::new(ISDBText { + buf: std::ptr::null_mut(), + len: 128, + used: 0, + pos: ISDBPos { x: 0, y: 0 }, + txt_tail: 0, + timestamp: 0, + refcount: 0, + list: ListHead { + next: std::ptr::null_mut(), + prev: std::ptr::null_mut(), + }, + }); + + // Allocate memory for the buffer + let mut buf = vec![0u8; 128]; + text.buf = buf.as_mut_ptr() as *mut c_char; + std::mem::forget(buf); // Prevent Rust from deallocating the buffer + + Some(text) +} + +/// # Safety +/// +/// - The caller must ensure that `ctx` is a valid, mutable reference to an `ISDBSubContext`. +/// - The function manipulates raw pointers and performs unsafe operations, so improper usage may result in undefined behavior. +pub fn append_char(ctx: &mut ISDBSubContext, ch: char) -> i32 { + let ls = &mut ctx.current_state.layout_state; + let mut text: Option<&mut ISDBText> = None; + + // Current Line Position + let cur_lpos; + // Space taken by character + let csp; + + if is_horizontal_layout(ls.format) { + cur_lpos = ls.cursor_pos.x; + csp = ls.font_size * ls.font_scale.fscx / 100; + } else { + cur_lpos = ls.cursor_pos.y; + csp = ls.font_size * ls.font_scale.fscy / 100; + } + + // Iterate over the text list + let mut current = ctx.text_list_head.next; + while let Some(current_text) = unsafe { (current as *mut ISDBText).as_mut() } { + // Text Line Position + let text_lpos = if is_horizontal_layout(ls.format) { + current_text.pos.x + } else { + current_text.pos.y + }; + + match text_lpos.cmp(&cur_lpos) { + std::cmp::Ordering::Equal => { + text = Some(current_text); + break; + } + std::cmp::Ordering::Greater => { + // Allocate Text here so that list is always sorted + let mut new_text = allocate_text_node(ls).expect("Failed to allocate text node"); + new_text.pos.x = ls.cursor_pos.x; + new_text.pos.y = ls.cursor_pos.y; + + // Add the new text node before the current one + unsafe { + (*new_text.list.prev).next = &mut new_text.list as *mut ListHead; + (*new_text.list.next).prev = &mut new_text.list as *mut ListHead; + } + + text = Some(&mut *Box::leak(new_text)); + break; + } + _ => {} + } + + current = current_text.list.next; + } + + if text.is_none() { + // If no matching text node was found, allocate a new one and add it to the tail + let mut new_text = allocate_text_node(ls).expect("Failed to allocate text node"); + new_text.pos.x = ls.cursor_pos.x; + new_text.pos.y = ls.cursor_pos.y; + + // Add the new text node to the tail + unsafe { + (*ctx.text_list_head.prev).next = &mut new_text.list as *mut ListHead; + (*new_text.list.prev).prev = &mut ctx.text_list_head as *mut ListHead; + } + + text = Some(Box::leak(new_text)); + } + + let text = text.unwrap(); + + // Check position of character and if moving backward then clean text + if is_horizontal_layout(ls.format) { + if ls.cursor_pos.y < text.pos.y { + text.pos.y = ls.cursor_pos.y; + text.used = 0; + } + ls.cursor_pos.y += csp; + text.pos.y += csp; + } else { + if ls.cursor_pos.y < text.pos.y { + text.pos.y = ls.cursor_pos.y; + text.used = 0; + } + ls.cursor_pos.x += csp; + text.pos.x += csp; + } + + // Reserve buffer space for the character + reserve_buf(text, 2).expect("Failed to reserve buffer space"); // +1 for terminating '\0' + + // Append the character to the buffer + unsafe { + *text.buf.add(text.used) = ch as i8; + text.used += 1; + *text.buf.add(text.used) = b'\0' as i8; + } + + 1 +} + +/// # Safety +/// +/// - The caller must ensure that `ctx` is a valid, mutable reference to an `ISDBSubContext`. +/// - The `buffer` must be a valid, mutable slice with sufficient capacity to hold the text. +/// - The function manipulates raw pointers and performs unsafe operations, so improper usage may result in undefined behavior. +pub fn get_text(ctx: &mut ISDBSubContext, buffer: &mut [u8], len: usize) -> usize { + let ls = &mut ctx.current_state.layout_state; + let mut index = 0; + + if ctx.cfg_no_rollup != 0 || ctx.cfg_no_rollup == ctx.current_state.rollup_mode { + // If the buffered_text list is empty, copy text_list_head to buffered_text + if ctx.buffered_text.next == &mut ctx.buffered_text as *mut ListHead { + let mut current = ctx.text_list_head.next; + while let Some(text) = unsafe { (current as *mut ISDBText).as_mut() } { + let mut sb_text = allocate_text_node(ls).expect("Failed to allocate text node"); + unsafe { + // Add to buffered_text + sb_text.list.next = &mut ctx.buffered_text as *mut ListHead; + sb_text.list.prev = ctx.buffered_text.prev; + (*ctx.buffered_text.prev).next = &mut sb_text.list as *mut ListHead; + ctx.buffered_text.prev = &mut sb_text.list as *mut ListHead; + } + reserve_buf(&mut sb_text, text.used).expect("Failed to reserve buffer space"); + unsafe { + std::ptr::copy_nonoverlapping(text.buf, sb_text.buf, text.used); + *sb_text.buf.add(text.used) = b'\0' as i8; + } + sb_text.used = text.used; + + current = text.list.next; + } + return 0; + } + + // Update buffered_text for new entries in text_list_head + let mut current = ctx.text_list_head.next; + while let Some(text) = unsafe { (current as *mut ISDBText).as_mut() } { + let mut found = false; + let mut sb_current = ctx.buffered_text.next; + while let Some(sb_text) = unsafe { (sb_current as *mut ISDBText).as_mut() } { + if unsafe { ccx_strstr_ignorespace(text.buf, sb_text.buf) } != 0 { + found = true; + if unsafe { ccx_strstr_ignorespace(sb_text.buf, text.buf) } == 0 { + reserve_buf(sb_text, text.used).expect("Failed to reserve buffer space"); + unsafe { + std::ptr::copy_nonoverlapping(text.buf, sb_text.buf, text.used); + } + } + break; + } + sb_current = sb_text.list.next; + } + if !found { + let mut sb_text = allocate_text_node(ls).expect("Failed to allocate text node"); + unsafe { + // Add to buffered_text + sb_text.list.next = &mut ctx.buffered_text as *mut ListHead; + sb_text.list.prev = ctx.buffered_text.prev; + (*ctx.buffered_text.prev).next = &mut sb_text.list as *mut ListHead; + ctx.buffered_text.prev = &mut sb_text.list as *mut ListHead; + } + reserve_buf(&mut sb_text, text.used).expect("Failed to reserve buffer space"); + unsafe { + std::ptr::copy_nonoverlapping(text.buf, sb_text.buf, text.used); + *sb_text.buf.add(text.used) = b'\0' as i8; + } + sb_text.used = text.used; + } + current = text.list.next; + } + + // Flush buffered_text if text is not in text_list_head + let mut sb_current = ctx.buffered_text.next; + while sb_current != &mut ctx.buffered_text as *mut ListHead { + let sb_text = unsafe { &mut *(sb_current as *mut ISDBText) }; + let mut found = false; + let mut current = ctx.text_list_head.next; + while let Some(text) = unsafe { (current as *mut ISDBText).as_mut() } { + if unsafe { ccx_strstr_ignorespace(text.buf, sb_text.buf) } != 0 { + found = true; + break; + } + current = text.list.next; + } + if !found && len - index > sb_text.used + 2 && sb_text.used > 0 { + unsafe { + std::ptr::copy_nonoverlapping( + sb_text.buf as *mut u8, + buffer.as_mut_ptr().add(index), + sb_text.used, + ); + *buffer.as_mut_ptr().add(index + sb_text.used) = b'\n'; + *buffer.as_mut_ptr().add(index + sb_text.used + 1) = b'\r'; + } + index += sb_text.used + 2; + sb_text.used = 0; + + // Remove from buffered_text + unsafe { + (*sb_text.list.prev).next = sb_text.list.next; + (*sb_text.list.next).prev = sb_text.list.prev; + } + } + sb_current = sb_text.list.next; + } + } else { + // Copy text_list_head directly to buffer + let mut current = ctx.text_list_head.next; + while let Some(text) = unsafe { (current as *mut ISDBText).as_mut() } { + if len - index > text.used + 2 && text.used > 0 { + unsafe { + std::ptr::copy_nonoverlapping( + text.buf as *mut u8, + buffer.as_mut_ptr().add(index), + text.used, + ); + *buffer.as_mut_ptr().add(index + text.used) = b'\n'; + *buffer.as_mut_ptr().add(index + text.used + 1) = b'\r'; + } + index += text.used + 2; + text.used = 0; + unsafe { + *text.buf = b'\0' as i8; + } + } + current = text.list.next; + } + } + index +} + +/// # Safety +/// +/// - The caller must ensure that `ctx` is a valid, mutable reference to an `ISDBSubContext`. +/// - The `arg` slice must be valid and properly initialized. +/// - Improper usage of the context or slice may result in undefined behavior. +pub fn set_writing_format(ctx: &mut ISDBSubContext, arg: &[u8]) { + let ls = &mut ctx.current_state.layout_state; + + // One param means its initialization + if arg.get(1) == Some(&0x20) { + ls.format = match arg[0] & 0x0F { + 0 => WritingFormat::HorizontalStdDensity, + 1 => WritingFormat::VerticalStdDensity, + 2 => WritingFormat::HorizontalHighDensity, + 3 => WritingFormat::VerticalHighDensity, + 4 => WritingFormat::HorizontalWesternLang, + 5 => WritingFormat::Horizontal1920x1080, + 6 => WritingFormat::Vertical1920x1080, + 7 => WritingFormat::Horizontal960x540, + 8 => WritingFormat::Vertical960x540, + 9 => WritingFormat::Horizontal720x480, + 10 => WritingFormat::Vertical720x480, + 11 => WritingFormat::Horizontal1280x720, + 12 => WritingFormat::Vertical1280x720, + _ => WritingFormat::None, + }; + return; + } + + // P1 I1 P2 I2 P31 ~ P3i I3 P41 ~ P4j I4 F + if arg.get(1) == Some(&0x3B) { + ls.format = WritingFormat::HorizontalCustom; + let mut arg_iter = arg.iter().skip(2); + + if let Some(&value) = arg_iter.next() { + match value & 0x0F { + 0 => { + // ctx.font_size = SMALL_FONT_SIZE; + } + 1 => { + // ctx.font_size = MIDDLE_FONT_SIZE; + } + 2 => { + // ctx.font_size = STANDARD_FONT_SIZE; + } + _ => {} + } + } + + // P3 + info!("character numbers in one line in decimal:"); + for &value in arg_iter.by_ref() { + if value == 0x3B || value == 0x20 { + break; + } + ctx.nb_char = value as i32; + print!(" {:x}", value & 0x0F); + } + + if arg_iter.clone().next() == Some(&0x20) { + return; + } + + // P4 + info!("line numbers in decimal:"); + for &value in arg_iter { + if value == 0x20 { + break; + } + ctx.nb_line = value as i32; + print!(" {:x}", value & 0x0F); + } + } +} + +/// # Safety +/// +/// - The caller must ensure that `ctx` is a valid, mutable reference to an `ISDBSubContext`. +/// - Improper usage of the context may result in undefined behavior. +pub fn move_penpos(ctx: &mut ISDBSubContext, col: i32, row: i32) { + let ls = &mut ctx.current_state.layout_state; + + ls.cursor_pos.x = row; + ls.cursor_pos.y = col; +} + +/// # Safety +/// +/// - The caller must ensure that `ctx` is a valid, mutable reference to an `ISDBSubContext`. +/// - Improper usage of the context may result in undefined behavior. +pub fn set_position(ctx: &mut ISDBSubContext, p1: i32, p2: i32) { + let ls = &mut ctx.current_state.layout_state; + let (cw, ch, col, row); + + if is_horizontal_layout(ls.format) { + cw = (ls.font_size + ls.cell_spacing.col) * ls.font_scale.fscx / 100; + ch = (ls.font_size + ls.cell_spacing.row) * ls.font_scale.fscy / 100; + // Pen position is at bottom left + col = p2 * cw; + row = p1 * ch + ch; + } else { + cw = (ls.font_size + ls.cell_spacing.col) * ls.font_scale.fscy / 100; + ch = (ls.font_size + ls.cell_spacing.row) * ls.font_scale.fscx / 100; + // Pen position is at upper center, + // but in -90deg rotated coordinates, it is at middle left. + col = p2 * cw; + row = p1 * ch + ch / 2; + } + + move_penpos(ctx, col, row); +} + +/// # Safety +/// +/// - The caller must ensure that `q` is a valid slice of bytes. +/// - If `p1` or `p2` are provided, they must be valid, mutable references to `u32`. +/// - Improper usage of the slice or references may result in undefined behavior. +pub fn get_csi_params(q: &[u8], p1: Option<&mut u32>, p2: Option<&mut u32>) -> i32 { + let mut q_iter = q.iter(); + let mut q_pivot = 0; + + if let Some(p1_ref) = p1 { + *p1_ref = 0; + for &byte in q_iter.by_ref() { + if !(0x30..=0x39).contains(&byte) { + break; + } + *p1_ref *= 10; + *p1_ref += (byte - 0x30) as u32; + q_pivot += 1; + } + if let Some(&byte) = q_iter.next() { + q_pivot += 1; + if byte != 0x20 && byte != 0x3B { + return -1; + } + } else { + return -1; + } + } + + if let Some(p2_ref) = p2 { + *p2_ref = 0; + for &byte in q_iter { + if !(0x30..=0x39).contains(&byte) { + break; + } + *p2_ref *= 10; + *p2_ref += (byte - 0x30) as u32; + q_pivot += 1; + } + q_pivot += 1; // Account for the final increment after the loop + } + + q_pivot +} + +/// # Safety +/// +/// - The caller must ensure that `ctx` is a valid, mutable reference to an `ISDBSubContext`. +/// - The `buf` slice must be valid and properly initialized. +/// - Improper usage of the context or slice may result in undefined behavior. +pub fn parse_csi(ctx: &mut ISDBSubContext, buf: &[u8]) -> usize { + let mut arg = [0u8; 10]; + let mut i = 0; + let ret; // should be mut and init to 0 + let mut p1 = 0; + let mut p2 = 0; + let _buf_pivot = buf; // Unused variable + let state = &mut ctx.current_state; + let ls = &mut state.layout_state; + + // Copy buf into arg + let mut buf_iter = buf.iter(); + for &byte in buf_iter.by_ref() { + if byte == 0x20 { + break; + } + if i >= arg.len() { + info!("Unexpected CSI {} >= {}", arg.len(), i); + break; + } + arg[i] = byte; + i += 1; + } + + // Ignore terminating 0x20 character + if let Some(&byte) = buf_iter.next() { + arg[i] = byte; + } + + // Process the CSI command + if let Some(&command) = buf_iter.next() { + match command { + command if command == CsiCommand::Swf as u8 => { + info!("Command: CSI: SWF"); + set_writing_format(ctx, &arg[..=i]); + } + command if command == CsiCommand::Ccc as u8 => { + info!("Command: CSI: CCC"); + ret = get_csi_params(&arg[..=i], Some(&mut p1), None); + if ret > 0 { + ls.ccc = + IsdbCCComposition::try_from(p1 as u8).unwrap_or(IsdbCCComposition::None); + } + } + command if command == CsiCommand::Sdf as u8 => { + ret = get_csi_params(&arg[..=i], Some(&mut p1), Some(&mut p2)); + if ret > 0 { + ls.display_area.w = p1 as i32; + ls.display_area.h = p2 as i32; + } + info!("Command: CSI: SDF (w: {}, h: {})", p1, p2); + } + command if command == CsiCommand::Ssm as u8 => { + ret = get_csi_params(&arg[..=i], Some(&mut p1), Some(&mut p2)); + if ret > 0 { + ls.font_size = p1 as i32; + } + info!("Command: CSI: SSM (x: {}, y: {})", p1, p2); + } + command if command == CsiCommand::Sdp as u8 => { + ret = get_csi_params(&arg[..=i], Some(&mut p1), Some(&mut p2)); + if ret > 0 { + ls.display_area.x = p1 as i32; + ls.display_area.y = p2 as i32; + } + info!("Command: CSI: SDP (x: {}, y: {})", p1, p2); + } + command if command == CsiCommand::Rcs as u8 => { + ret = get_csi_params(&arg[..=i], Some(&mut p1), None); + if ret > 0 { + ctx.current_state.raster_color = DEFAULT_CLUT + [(((ctx.current_state.clut_high_idx as u32) << 4) | p1) as usize]; + } + info!("Command: CSI: RCS ({})", p1); + } + command if command == CsiCommand::Shs as u8 => { + ret = get_csi_params(&arg[..=i], Some(&mut p1), None); + if ret > 0 { + ls.cell_spacing.col = p1 as i32; + } + info!("Command: CSI: SHS ({})", p1); + } + command if command == CsiCommand::Svs as u8 => { + ret = get_csi_params(&arg[..=i], Some(&mut p1), None); + if ret > 0 { + ls.cell_spacing.row = p1 as i32; + } + info!("Command: CSI: SVS ({})", p1); + } + command if command == CsiCommand::Acps as u8 => { + info!("Command: CSI: ACPS"); + ret = get_csi_params(&arg[..=i], Some(&mut p1), Some(&mut p2)); + if ret > 0 { + ls.acps[0] = p1 as i32; + ls.acps[1] = p2 as i32; + } + } + _ => { + info!("Command: CSI: Unknown command 0x{:x}", command); + } + } + } + + // Return the number of bytes processed + buf.len() - buf_iter.as_slice().len() +} + +/// # Safety +/// +/// - The caller must ensure that `ctx` is a valid, mutable reference to an `ISDBSubContext`. +/// - The `buf` slice must be valid and properly initialized. +/// - Improper usage of the context or slice may result in undefined behavior. +pub fn parse_command(ctx: &mut ISDBSubContext, buf: &[u8]) -> usize { + let _buf_pivot = buf; // Unused variable + let mut buf_iter = buf.iter(); + let ret; // should be mut and init to 0 + + let code_lo = buf[0] & 0x0F; + let code_hi = (buf[0] & 0xF0) >> 4; + + let state = &mut ctx.current_state; + let ls = &mut state.layout_state; + + buf_iter.next(); // Move to the next byte + + match code_hi { + 0x00 => match code_lo { + 0x0 => info!("Command: NUL"), + 0x7 => info!("Command: BEL"), + 0x8 => info!("Command: ABP"), + 0x9 => info!("Command: APF"), + 0xA => info!("Command: APD"), + 0xB => info!("Command: APU"), + 0xC => info!("Command: CS clear Screen"), + 0xD => info!("Command: APR"), + 0xE => info!("Command: LS1"), + 0xF => info!("Command: LS0"), + _ => info!("Command: Unknown"), + }, + 0x01 => match code_lo { + 0x6 => info!("Command: PAPF"), + 0x8 => info!("Command: CAN"), + 0x9 => info!("Command: SS2"), + 0xB => info!("Command: ESC"), + 0xC => { + info!("Command: APS"); + if let (Some(&p1), Some(&p2)) = (buf_iter.next(), buf_iter.next()) { + set_position(ctx, (p1 & 0x3F) as i32, (p2 & 0x3F) as i32); + } + } + 0xD => info!("Command: SS3"), + 0xE => info!("Command: RS"), + 0xF => info!("Command: US"), + _ => info!("Command: Unknown"), + }, + 0x08 => match code_lo { + 0x0..=0x7 => { + info!( + "Command: Forground color (0x{:X})", + DEFAULT_CLUT[code_lo as usize] + ); + state.fg_color = DEFAULT_CLUT[code_lo as usize]; + } + 0x8 => { + info!("Command: SSZ"); + ls.font_scale.fscx = 50; + ls.font_scale.fscy = 50; + } + 0x9 => { + info!("Command: MSZ"); + ls.font_scale.fscx = 200; + ls.font_scale.fscy = 200; + } + 0xA => { + info!("Command: NSZ"); + ls.font_scale.fscx = 100; + ls.font_scale.fscy = 100; + } + 0xB => { + info!("Command: SZX"); + buf_iter.next(); + } + _ => info!("Command: Unknown"), + }, + 0x09 => match code_lo { + 0x0 => { + if let Some(&byte) = buf_iter.next() { + match byte & 0xF0 { + 0x20 => { + info!("Command: COL: Set Clut {}", byte & 0x0F); + ctx.current_state.clut_high_idx = byte & 0x0F; + } + 0x40 => { + info!( + "Command: COL: Set Forground 0x{:08X}", + DEFAULT_CLUT[(byte & 0x0F) as usize] + ); + ctx.current_state.fg_color = DEFAULT_CLUT[(byte & 0x0F) as usize]; + } + 0x50 => { + info!( + "Command: COL: Set Background 0x{:08X}", + DEFAULT_CLUT[(byte & 0x0F) as usize] + ); + ctx.current_state.bg_color = DEFAULT_CLUT[(byte & 0x0F) as usize]; + } + 0x60 => { + info!( + "Command: COL: Set half Forground 0x{:08X}", + DEFAULT_CLUT[(byte & 0x0F) as usize] + ); + ctx.current_state.hfg_color = DEFAULT_CLUT[(byte & 0x0F) as usize]; + } + 0x70 => { + info!( + "Command: COL: Set Half Background 0x{:08X}", + DEFAULT_CLUT[(byte & 0x0F) as usize] + ); + ctx.current_state.hbg_color = DEFAULT_CLUT[(byte & 0x0F) as usize]; + } + _ => {} + } + } + } + 0x1 => { + info!("Command: FLC"); + buf_iter.next(); + } + 0x2 => { + info!("Command: CDC"); + buf_iter.nth(2); // Skip 3 bytes + } + 0x3 => { + info!("Command: POL"); + buf_iter.next(); + } + 0x4 => { + info!("Command: WMM"); + buf_iter.nth(2); // Skip 3 bytes + } + 0x5 => { + info!("Command: MACRO"); + buf_iter.next(); + } + 0x7 => { + info!("Command: HLC"); + buf_iter.next(); + } + 0x8 => { + info!("Command: RPC"); + buf_iter.next(); + } + 0x9 => info!("Command: SPL"), + 0xA => info!("Command: STL"), + 0xB => { + ret = parse_csi(ctx, buf_iter.as_slice()); + buf_iter.nth(ret - 1); // Skip the parsed bytes + } + 0xD => { + info!("Command: TIME"); + buf_iter.nth(1); // Skip 2 bytes + } + _ => info!("Command: Unknown"), + }, + _ => {} + } + + buf.len() - buf_iter.as_slice().len() +} + +/// # Safety +/// +/// - The caller must ensure that `ctx` is a valid, mutable reference to an `ISDBSubContext`. +/// - The `buf` slice must be valid and properly initialized. +/// - Improper usage of the context or slice may result in undefined behavior. +pub fn parse_caption_management_data(ctx: &mut ISDBSubContext, buf: &[u8]) -> usize { + let _buf_pivot = buf; // Unused variable + let mut buf_iter = buf.iter(); + + // Parse TMD + if let Some(&byte) = buf_iter.next() { + ctx.tmd = match byte >> 6 { + 0 => IsdbTmd::Free, + 1 => IsdbTmd::RealTime, + 2 => IsdbTmd::OffsetTime, + _ => IsdbTmd::Free, + }; + info!("CC MGMT DATA: TMD: {:?}", ctx.tmd); + + match ctx.tmd { + IsdbTmd::Free => { + info!("Playback time is not restricted to synchronize to the clock."); + } + IsdbTmd::OffsetTime => { + // + // This 36-bit field indicates offset time to add to the playback time when the + // clock control mode is in offset time mode. Offset time is coded in the + // order of hour, minute, second and millisecond, using nine 4-bit binary + // coded decimals (BCD). + // + // +-----------+-----------+---------+--------------+ + // | hour | minute | sec | millisecond | + // +-----------+-----------+---------+--------------+ + // | 2 (4bit) | 2 (4bit) | 2 (4bit)| 3 (4bit) | + // +-----------+-----------+---------+--------------+ + + // Parse offset time + if let ( + Some(&hour_byte), + Some(&min_byte), + Some(&sec_byte), + Some(&milli_byte1), + Some(&milli_byte2), + ) = ( + buf_iter.next(), + buf_iter.next(), + buf_iter.next(), + buf_iter.next(), + buf_iter.next(), + ) { + ctx.offset_time.hour = ((hour_byte >> 4) * 10 + (hour_byte & 0xF)) as i32; + ctx.offset_time.min = ((min_byte >> 4) * 10 + (min_byte & 0xF)) as i32; + ctx.offset_time.sec = ((sec_byte >> 4) * 10 + (sec_byte & 0xF)) as i32; + ctx.offset_time.milli = ((milli_byte1 >> 4) * 100 + + ((milli_byte1 & 0xF) * 10) + + (milli_byte2 & 0xF)) as i32; + + info!( + "CC MGMT DATA: OTD( h:{} m:{} s:{} millis:{} )", + ctx.offset_time.hour, + ctx.offset_time.min, + ctx.offset_time.sec, + ctx.offset_time.milli + ); + } + } + _ => { + info!( + "Playback time is in accordance with the time of the clock, \ + which is calibrated by clock signal (TDT). Playback time is \ + given by PTS." + ); + } + } + } + + // Parse number of languages + if let Some(&nb_lang) = buf_iter.next() { + ctx.nb_lang = nb_lang as i32; + info!("CC MGMT DATA: nb languages: {}", ctx.nb_lang); + } + + // Parse language data + for _ in 0..ctx.nb_lang { + if let Some(&lang_byte) = buf_iter.next() { + info!("CC MGMT DATA: {}", (lang_byte & 0x1F) >> 5); + ctx.dmf = lang_byte & 0x0F; + info!("CC MGMT DATA: DMF 0x{:X}", ctx.dmf); + + if ctx.dmf == 0xC || ctx.dmf == 0xD || ctx.dmf == 0xE { + if let Some(&dc_byte) = buf_iter.next() { + ctx.dc = dc_byte; + if ctx.dc == 0x00 { + info!("Attenuation Due to Rain"); + } + } + } + + if let (Some(&lang1), Some(&lang2), Some(&lang3)) = + (buf_iter.next(), buf_iter.next(), buf_iter.next()) + { + info!( + "CC MGMT DATA: languages: {}{}{}", + lang1 as char, lang2 as char, lang3 as char + ); + } + + if let Some(&format_byte) = buf_iter.next() { + info!("CC MGMT DATA: Format: 0x{:X}", format_byte >> 4); + info!("CC MGMT DATA: TCS: 0x{:X}", (format_byte >> 2) & 0x3); + ctx.current_state.rollup_mode = ((format_byte & 0x3) != 0) as i32; + info!( + "CC MGMT DATA: Rollup mode: 0x{:X}", + ctx.current_state.rollup_mode + ); + } + } + } + + buf.len() - buf_iter.as_slice().len() +} + +/// # Safety +/// +/// - The caller must ensure that `ctx` is a valid, mutable reference to an `ISDBSubContext`. +/// - The `buf` slice must be valid and properly initialized. +/// - Improper usage of the context or slice may result in undefined behavior. +pub fn parse_statement(ctx: &mut ISDBSubContext, buf: &[u8]) -> i32 { + let _buf_pivot = buf; // Unused variable + let mut buf_iter = buf.iter(); + let mut ret; + + while buf.len() - buf_iter.as_slice().len() < buf.len() { + if let Some(&byte) = buf_iter.next() { + let code = (byte & 0xF0) >> 4; + let code_lo = byte & 0x0F; + + if code <= 0x1 { + ret = parse_command(ctx, buf_iter.as_slice()); + } + // Special case *1(SP) + else if code == 0x2 && code_lo == 0x0 { + ret = append_char(ctx, byte as char) as usize; + } + // Special case *3(DEL) + else if code == 0x7 && code_lo == 0xF { + // TODO: DEL should have block in fg color + ret = append_char(ctx, byte as char) as usize; + } else if code <= 0x7 { + ret = append_char(ctx, byte as char) as usize; + } else if code <= 0x9 { + ret = parse_command(ctx, buf_iter.as_slice()); + } + // Special case *2(10/0) + else if (code == 0xA && code_lo == 0x0) || (code == 0x0F && code_lo == 0xF) { + // TODO: handle + ret = 1; // Placeholder for handling + } else { + ret = append_char(ctx, byte as char) as usize; + } + + // No need to check for `ret < 0` as `ret` is of type `usize` + // if ret < 0 { + // break; + // } + + buf_iter.nth(ret - 1); // Move the iterator forward by `ret` bytes + } + } + + 0 +} + +/// # Safety +/// +/// - The caller must ensure that `ctx` is a valid, mutable reference to an `ISDBSubContext`. +/// - The `buf` slice must be valid and properly initialized. +/// - Improper usage of the context or slice may result in undefined behavior. +pub fn parse_data_unit(ctx: &mut ISDBSubContext, buf: &[u8]) -> i32 { + let mut buf_iter = buf.iter(); + + // Skip the unit separator + buf_iter.next(); + + // Parse unit parameter + let unit_parameter = if let Some(&byte) = buf_iter.next() { + byte + } else { + return 0; // Return early if no data + }; + + // Parse length (RB24 equivalent) + if let (Some(&b1), Some(&b2), Some(&b3)) = (buf_iter.next(), buf_iter.next(), buf_iter.next()) { + let _len = ((b1 as u32) << 16) | ((b2 as u32) << 8) | (b3 as u32); + // Use `_len` if needed or keep it as a placeholder to suppress the warning + } else { + return 0; // Return early if length cannot be parsed + } + + // Process based on unit parameter + match unit_parameter { + 0x20 => { + parse_statement(ctx, buf_iter.as_slice()); + } + _ => { + // Handle other cases if needed + } + } + + 0 +} + +/// # Safety +/// +/// - The caller must ensure that `ctx` is a valid, mutable reference to an `ISDBSubContext`. +/// - The `buf` slice must be valid and properly initialized. +/// - The `sub` parameter must be a valid, mutable reference to a `CcSubtitle`. +/// - Improper usage of the context, slice, or subtitle may result in undefined behavior. +pub fn parse_caption_statement_data( + ctx: &mut ISDBSubContext, + lang_id: i32, + buf: &[u8], + sub: &mut CcSubtitle, +) -> i32 { + // assinn land_id to ulang_id + let _ulang_id = lang_id; // Unused variable + + let mut buf_iter = buf.iter(); + let mut buffer = [0u8; 1024]; + + // Parse TMD + let tmd = if let Some(&byte) = buf_iter.next() { + byte >> 6 + } else { + return -1; + }; + + // Skip timing data if TMD is 1 or 2 + if tmd == 1 || tmd == 2 { + buf_iter.nth(4); // Skip 5 bytes + } + + // Parse length (RB24 equivalent) + let _len = if let (Some(&b1), Some(&b2), Some(&b3)) = // len unused + (buf_iter.next(), buf_iter.next(), buf_iter.next()) + { + ((b1 as u32) << 16) | ((b2 as u32) << 8) | (b3 as u32) + } else { + return -1; + }; + + // Parse data unit + let ret = parse_data_unit(ctx, buf_iter.as_slice()); + if ret < 0 { + return -1; + } + + // Get text from the context + let ret = get_text(ctx, &mut buffer, 1024); + + // no need to check for `ret < 0` as `ret` is of type `usize` + // if ret < 0 { + // return CCX_OK as i32; + // } + + // Copy data if present in the buffer + if ret > 0 { + add_cc_sub_text( + sub, + std::str::from_utf8(&buffer[..ret]).unwrap_or(""), + ctx.prev_timestamp as i64, + ctx.timestamp as i64, + "NA", + "ISDB", + CcxEncodingType::Utf8, + ); + if sub.start_time == sub.end_time { + sub.end_time += 2; + } + ctx.prev_timestamp = ctx.timestamp; + } + + 0 +} + +/// # Safety +/// +/// - The caller must ensure that `codec_ctx` is a valid, mutable reference to an `ISDBSubContext`. +/// - The `buf` slice must be valid and properly initialized. +/// - The `sub` parameter must be a valid, mutable reference to a `CcSubtitle`. +/// - Improper usage of the context, slice, or subtitle may result in undefined behavior. +pub fn isdb_parse_data_group( + codec_ctx: &mut ISDBSubContext, + buf: &[u8], + sub: &mut CcSubtitle, +) -> i32 { + let _buf_pivot = buf; // Unused variable + let mut buf_iter = buf.iter(); + + // Parse ID + let id = if let Some(&byte) = buf_iter.next() { + byte >> 2 + } else { + return -1; + }; + + // Debug variables (unused in release mode) + #[cfg(debug_assertions)] + let _version = id & 2; + + // Skip unused bytes + buf_iter.nth(2); + + // Parse group size (RB16 equivalent) + let group_size = if let (Some(&b1), Some(&b2)) = (buf_iter.next(), buf_iter.next()) { + u16::from_be_bytes([b1, b2]) as usize + } else { + return -1; + }; + + info!("ISDB (Data group) group_size {}", group_size); + + // Check and update timestamps + if codec_ctx.prev_timestamp > codec_ctx.timestamp { + codec_ctx.prev_timestamp = codec_ctx.timestamp; + } + + // Process based on ID + let _ret = match id & 0x0F { + // ret is unused variable + 0 => { + // Caption management + parse_caption_management_data(codec_ctx, buf_iter.as_slice()) + } + 1..=7 => { + // Caption statement data + info!("ISDB {} language", id & 0x0F); + parse_caption_statement_data(codec_ctx, (id & 0x0F) as i32, buf_iter.as_slice(), sub) + as usize + } + _ => { + // Not allowed in spec + info!("ISDB: Invalid ID in data group"); + 0 + } + }; + + // no need to check for `ret < 0` as `ret` is of type `usize` + // if ret < 0 { + // return -1; + // } + + // Skip processed group size + buf_iter.nth(group_size - 1); + + // Skip CRC (2 bytes) + buf_iter.nth(1); + + (buf.len() - buf_iter.as_slice().len()) as i32 +} + +/// # Safety +/// +/// - The caller must ensure that `dec_ctx` is a valid, mutable reference to a `LibCcDecode`. +/// - The `buf` slice must be valid and properly initialized. +/// - The `sub` parameter must be a valid, mutable reference to a `CcSubtitle`. +/// - Improper usage of the context, slice, or subtitle may result in undefined behavior. +pub fn isdbsub_decode(dec_ctx: &mut LibCcDecode, buf: &[u8], sub: &mut CcSubtitle) -> i32 { + let mut buf_iter = buf.iter(); + + // Check for synchronization byte + if let Some(&byte) = buf_iter.next() { + if byte != 0x80 { + info!("\nNot a Synchronized PES\n"); + return -1; + } + } else { + return -1; + } + + // Skip private data stream (0xFF) + buf_iter.next(); + + // Parse header end + let header_end = if let Some(&byte) = buf_iter.next() { + buf_iter.as_slice().len() - (byte & 0x0F) as usize + } else { + return -1; + }; + + buf_iter.next(); // Skip one more byte + + // Skip header bytes + while buf_iter.as_slice().len() > header_end { + buf_iter.next(); + } + + // Set rollup configuration + if let Some(ctx) = unsafe { (dec_ctx.private_data as *mut ISDBSubContext).as_mut() } { + ctx.cfg_no_rollup = dec_ctx.no_rollup; + + // Parse data group + let ret = isdb_parse_data_group(ctx, buf_iter.as_slice(), sub); + if ret < 0 { + return -1; + } + } + + 1 +} + +/// # Safety +/// +/// - The caller must ensure that `ctx` is a valid, mutable reference to an `ISDBSubContext`. +/// - Improper usage of the context may result in undefined behavior. +pub fn isdb_set_global_time(ctx: &mut ISDBSubContext, timestamp: u64) -> i32 { + ctx.timestamp = timestamp; + CCX_OK as i32 +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::ffi::c_char; + use std::ffi::CString; + + #[test] + fn test_is_horizontal_layout() { + assert!(is_horizontal_layout(WritingFormat::HorizontalStdDensity)); + assert!(is_horizontal_layout(WritingFormat::HorizontalHighDensity)); + assert!(is_horizontal_layout(WritingFormat::HorizontalWesternLang)); + assert!(is_horizontal_layout(WritingFormat::Horizontal1920x1080)); + assert!(!is_horizontal_layout(WritingFormat::Vertical960x540)); + assert!(!is_horizontal_layout(WritingFormat::Vertical720x480)); + } + + #[test] + fn test_layout_get_width() { + assert_eq!(layout_get_width(WritingFormat::Horizontal960x540), 960); + assert_eq!(layout_get_width(WritingFormat::Vertical960x540), 960); + assert_eq!(layout_get_width(WritingFormat::Horizontal720x480), 720); + assert_eq!(layout_get_width(WritingFormat::Vertical720x480), 720); + } + + #[test] + fn test_layout_get_height() { + assert_eq!(layout_get_height(WritingFormat::Horizontal960x540), 540); + assert_eq!(layout_get_height(WritingFormat::Vertical960x540), 540); + assert_eq!(layout_get_height(WritingFormat::Horizontal720x480), 480); + assert_eq!(layout_get_height(WritingFormat::Vertical720x480), 480); + } + + #[test] + fn test_init_layout() { + let mut layout = ISDBSubLayout { + font_size: 0, + display_area: Default::default(), + font_scale: Default::default(), + cursor_pos: Default::default(), + format: WritingFormat::None, + cell_spacing: Default::default(), + ccc: Default::default(), + acps: [0; 2], + }; + + unsafe { + init_layout(&mut layout); + } + + assert_eq!(layout.font_size, 36); + assert_eq!(layout.display_area.x, 0); + assert_eq!(layout.display_area.y, 0); + assert_eq!(layout.font_scale.fscx, 100); + assert_eq!(layout.font_scale.fscy, 100); + } + + #[test] + fn test_ccx_strstr_ignorespace_exact_match() { + let str1 = CString::new("hello").unwrap(); + let str2 = CString::new("hello").unwrap(); + + unsafe { + assert_eq!(ccx_strstr_ignorespace(str1.as_ptr(), str2.as_ptr()), 1); + } + } + + #[test] + fn test_ccx_strstr_ignorespace_no_match() { + let str1 = CString::new("hello").unwrap(); + let str2 = CString::new("world").unwrap(); + + unsafe { + assert_eq!(ccx_strstr_ignorespace(str1.as_ptr(), str2.as_ptr()), 0); + } + } + + #[test] + fn test_ccx_strstr_ignorespace_empty_strings() { + let str1 = CString::new("").unwrap(); + let str2 = CString::new("").unwrap(); + + unsafe { + assert_eq!(ccx_strstr_ignorespace(str1.as_ptr(), str2.as_ptr()), 1); + } + } + + #[test] + fn test_ccx_strstr_ignorespace_spaces_only() { + let str1 = CString::new(" ").unwrap(); + let str2 = CString::new("").unwrap(); + + unsafe { + assert_eq!(ccx_strstr_ignorespace(str1.as_ptr(), str2.as_ptr()), 1); + } + } + + #[test] + fn test_ccx_strstr_ignorespace_mismatched_lengths() { + let str1 = CString::new("hello").unwrap(); + let str2 = CString::new("hello world").unwrap(); + + unsafe { + assert_eq!(ccx_strstr_ignorespace(str1.as_ptr(), str2.as_ptr()), 0); + } + } + + #[test] + fn test_init_isdb_decoder_success() { + // Call the function to initialize the decoder + let isdb_ctx = init_isdb_decoder(); + + // Ensure the context is initialized + assert!(isdb_ctx.is_some()); + + // Verify the initialized values + let ctx = isdb_ctx.unwrap(); + assert_eq!(ctx.prev_timestamp, u64::MAX); + assert_eq!(ctx.current_state.clut_high_idx, 0); + assert_eq!(ctx.current_state.rollup_mode, 0); + + // Verify the list heads are properly initialized + assert_eq!( + ctx.text_list_head.next, + &ctx.text_list_head as *const _ as *mut _ + ); + assert_eq!( + ctx.text_list_head.prev, + &ctx.text_list_head as *const _ as *mut _ + ); + assert_eq!( + ctx.buffered_text.next, + &ctx.buffered_text as *const _ as *mut _ + ); + assert_eq!( + ctx.buffered_text.prev, + &ctx.buffered_text as *const _ as *mut _ + ); + + // Verify the layout state is initialized + let layout = &ctx.current_state.layout_state; + assert_eq!(layout.font_size, 36); + assert_eq!(layout.display_area.x, 0); + assert_eq!(layout.display_area.y, 0); + assert_eq!(layout.font_scale.fscx, 100); + assert_eq!(layout.font_scale.fscy, 100); + } + + #[test] + fn test_init_isdb_decoder_no_crash() { + // Ensure the function does not crash when called multiple times + let _ctx1 = init_isdb_decoder(); + let _ctx2 = init_isdb_decoder(); + } + + #[test] + fn test_allocate_text_node_buffer_allocation() { + // Create a mock ISDBSubLayout + let mut layout = ISDBSubLayout { + font_size: 36, + display_area: DispArea { + x: 0, + y: 0, + w: 0, + h: 0, + }, + font_scale: FScale { + fscx: 100, + fscy: 100, + }, + cursor_pos: ISDBPos { x: 0, y: 0 }, + format: WritingFormat::HorizontalStdDensity, + cell_spacing: Spacing { col: 0, row: 0 }, + ccc: IsdbCCComposition::None, + acps: [0; 2], + }; + + // Call the function to allocate a text node + let mut text_node = allocate_text_node(&mut layout).unwrap(); + + // Allocate memory for the buffer + let mut buf = vec![0u8; 128]; + text_node.buf = buf.as_mut_ptr() as *mut c_char; + std::mem::forget(buf); // Prevent Rust from deallocating the buffer + + // Verify the buffer is allocated + assert!(!text_node.buf.is_null()); + } + + #[test] + fn test_set_writing_format_invalid_format() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + current_state: ISDBSubState { + layout_state: ISDBSubLayout { + format: WritingFormat::None, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + // Call set_writing_format with an invalid format argument + set_writing_format(&mut ctx, &[0xFF, 0x20]); + + // Verify the writing format remains None + assert_eq!(ctx.current_state.layout_state.format, WritingFormat::None); + } + + #[test] + fn test_move_penpos_horizontal_layout() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + current_state: ISDBSubState { + layout_state: ISDBSubLayout { + cursor_pos: ISDBPos { x: 0, y: 0 }, + format: WritingFormat::HorizontalStdDensity, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + // Move the pen position + move_penpos(&mut ctx, 10, 20); + + // Verify the pen position is updated correctly + assert_eq!(ctx.current_state.layout_state.cursor_pos.x, 20); + assert_eq!(ctx.current_state.layout_state.cursor_pos.y, 10); + } + + #[test] + fn test_move_penpos_vertical_layout() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + current_state: ISDBSubState { + layout_state: ISDBSubLayout { + cursor_pos: ISDBPos { x: 0, y: 0 }, + format: WritingFormat::Vertical960x540, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + // Move the pen position + move_penpos(&mut ctx, 15, 25); + + // Verify the pen position is updated correctly + assert_eq!(ctx.current_state.layout_state.cursor_pos.x, 25); + assert_eq!(ctx.current_state.layout_state.cursor_pos.y, 15); + } + + #[test] + fn test_move_penpos_no_change() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + current_state: ISDBSubState { + layout_state: ISDBSubLayout { + cursor_pos: ISDBPos { x: 5, y: 5 }, + format: WritingFormat::HorizontalStdDensity, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + // Move the pen position to the same coordinates + move_penpos(&mut ctx, 5, 5); + + // Verify the pen position remains unchanged + assert_eq!(ctx.current_state.layout_state.cursor_pos.x, 5); + assert_eq!(ctx.current_state.layout_state.cursor_pos.y, 5); + } + + #[test] + fn test_get_csi_params_invalid_input() { + let input = b"123x"; + let mut p1 = 0; + + let result = get_csi_params(input, Some(&mut p1), None); + + // Verify the result + assert_eq!(result, -1); // Invalid character 'x' + } + + #[test] + fn test_get_csi_params_partial_input() { + let input = b"123;"; + let mut p1 = 0; + let mut p2 = 0; + + let result = get_csi_params(input, Some(&mut p1), Some(&mut p2)); + + // Verify the result + assert_eq!(result, -1); // Missing second parameter + } + + #[test] + fn test_parse_command_nul() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + current_state: ISDBSubState { + layout_state: ISDBSubLayout { + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + // Input buffer for the NUL command + let buf = b"\x00"; + + // Call parse_command + let result = parse_command(&mut ctx, buf); + + // Verify the command is processed + assert_eq!(result, 1); // NUL command processes 1 byte + } + + #[test] + fn test_parse_command_apf() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + current_state: ISDBSubState { + layout_state: ISDBSubLayout { + cursor_pos: ISDBPos { x: 0, y: 0 }, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + // Input buffer for the APF (Advance Pen Forward) command + let buf = b"\x09"; + + // Call parse_command + let result = parse_command(&mut ctx, buf); + + // Verify the command is processed + assert_eq!(result, 1); // APF command processes 1 byte + } + + #[test] + fn test_parse_command_ssz() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + current_state: ISDBSubState { + layout_state: ISDBSubLayout { + font_scale: FScale { + fscx: 100, + fscy: 100, + }, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + // Input buffer for the SSZ (Small Size) command + let buf = b"\x88"; + + // Call parse_command + let result = parse_command(&mut ctx, buf); + + // Verify the font scale is updated + assert_eq!(result, 1); // SSZ command processes 1 byte + assert_eq!(ctx.current_state.layout_state.font_scale.fscx, 50); + assert_eq!(ctx.current_state.layout_state.font_scale.fscy, 50); + } + + #[test] + fn test_parse_command_unknown() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + current_state: ISDBSubState { + layout_state: ISDBSubLayout { + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + // Input buffer for an unknown command + let buf = b"\xFF"; + + // Call parse_command + let result = parse_command(&mut ctx, buf); + + // Verify the command is processed but does not modify the context + assert_eq!(result, 1); // Unknown command processes 1 byte + } + + #[test] + fn test_parse_caption_management_data_free_mode() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + tmd: IsdbTmd::Free, + nb_lang: 0, + ..Default::default() + }; + + // Input buffer for free mode + let buf = b"\x00\x01"; + + // Call parse_caption_management_data + let result = parse_caption_management_data(&mut ctx, buf); + + // Verify the TMD and number of languages + assert_eq!(result, buf.len()); + assert_eq!(ctx.tmd, IsdbTmd::Free); + assert_eq!(ctx.nb_lang, 1); + } + + #[test] + fn test_parse_caption_management_data_languages() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + nb_lang: 0, + dmf: 0, + dc: 0, + ..Default::default() + }; + + // Input buffer with language data + let buf = b"\x00\x02\xC1\x45\x4E\x47\x10"; + + // Call parse_caption_management_data + let result = parse_caption_management_data(&mut ctx, buf); + + // Verify the number of languages and language details + assert_eq!(result, buf.len()); + assert_eq!(ctx.nb_lang, 2); + assert_eq!(ctx.dmf, 0x01); + assert_eq!(ctx.dc, 0); + } + + #[test] + fn test_parse_caption_management_data_invalid_buffer() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + tmd: IsdbTmd::Free, + nb_lang: 0, + ..Default::default() + }; + + // Input buffer with insufficient data + let buf = b"\x80"; + + // Call parse_caption_management_data + let result = parse_caption_management_data(&mut ctx, buf); + + // Verify that the function processes only the available data + assert_eq!(result, buf.len()); + assert_eq!(ctx.tmd, IsdbTmd::OffsetTime); + assert_eq!(ctx.nb_lang, 0); // No languages parsed + } + + #[test] + fn test_parse_statement_unknown_code() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + current_state: ISDBSubState { + layout_state: ISDBSubLayout { + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + // Input buffer for an unknown code + let buf = b"\xFF"; + + // Call parse_statement + let result = parse_statement(&mut ctx, buf); + + // Verify the unknown code is processed without errors + assert_eq!(result, 0); // `parse_statement` returns 0 on success + } + + #[test] + fn test_parse_data_unit_empty_buffer() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + ..Default::default() + }; + + // Input buffer with no data + let buf = b""; + + // Call parse_data_unit + let result = parse_data_unit(&mut ctx, buf); + + // Verify the function handles an empty buffer gracefully + assert_eq!(result, 0); // No data to process + } + + #[test] + fn test_parse_data_unit_invalid_length() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + ..Default::default() + }; + + // Input buffer with an invalid length (less than 3 bytes for RB24) + let buf = b"\x1F\x20\x00"; + + // Call parse_data_unit + let result = parse_data_unit(&mut ctx, buf); + + // Verify the function handles invalid length gracefully + assert_eq!(result, 0); // No valid data to process + } + + #[test] + fn test_parse_data_unit_unknown_unit_parameter() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + ..Default::default() + }; + + // Input buffer with an unknown unit parameter + let buf = b"\x1F\xFF\x00\x00\x00"; + + // Call parse_data_unit + let result = parse_data_unit(&mut ctx, buf); + + // Verify the function processes the unknown unit parameter without errors + assert_eq!(result, 0); // Unknown unit parameter is ignored + } + + #[test] + fn test_parse_caption_statement_data_invalid_buffer() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + tmd: IsdbTmd::Free, + ..Default::default() + }; + + // Create a mock CcSubtitle + let mut sub = CcSubtitle { + ..Default::default() + }; + + // Input buffer with insufficient data + let buf = b"\x80"; + + // Call parse_caption_statement_data + let result = parse_caption_statement_data(&mut ctx, 1, buf, &mut sub); + + // Verify the function handles the invalid buffer gracefully + assert_eq!(result, -1); + } + + #[test] + fn test_isdb_parse_data_group_insufficient_data() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + ..Default::default() + }; + + // Create a mock CcSubtitle + let mut sub = CcSubtitle { + ..Default::default() + }; + + // Input buffer with insufficient data + let buf = b"\x00"; + + // Call isdb_parse_data_group + let result = isdb_parse_data_group(&mut ctx, buf, &mut sub); + + // Verify the function handles insufficient data gracefully + assert_eq!(result, -1); + } + + #[test] + fn test_isdbsub_decode_invalid_sync_byte() { + // Create a mock LibCcDecode + let mut dec_ctx = LibCcDecode { + private_data: Box::into_raw(Box::new(ISDBSubContext { + cfg_no_rollup: 0, + ..Default::default() + })) as *mut _, + no_rollup: 1, + ..Default::default() + }; + + // Create a mock CcSubtitle + let mut sub = CcSubtitle { + ..Default::default() + }; + + // Input buffer with an invalid synchronization byte + let buf = b"\x00\xFF\x00\x00\x00\x00\x00\x00"; + + // Call isdbsub_decode + let result = isdbsub_decode(&mut dec_ctx, buf, &mut sub); + + // Verify the function returns an error + assert_eq!(result, -1); + } + + #[test] + fn test_isdbsub_decode_empty_buffer() { + // Create a mock LibCcDecode + let mut dec_ctx = LibCcDecode { + private_data: Box::into_raw(Box::new(ISDBSubContext { + cfg_no_rollup: 0, + ..Default::default() + })) as *mut _, + no_rollup: 1, + ..Default::default() + }; + + // Create a mock CcSubtitle + let mut sub = CcSubtitle { + ..Default::default() + }; + + // Input buffer with no data + let buf = b""; + + // Call isdbsub_decode + let result = isdbsub_decode(&mut dec_ctx, buf, &mut sub); + + // Verify the function returns an error + assert_eq!(result, -1); + } + + #[test] + fn test_isdb_set_global_time_valid_timestamp() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + timestamp: 0, + ..Default::default() + }; + + // Set a valid timestamp + let result = isdb_set_global_time(&mut ctx, 123456789); + + // Verify the timestamp is updated correctly + assert_eq!(result, CCX_OK as i32); + assert_eq!(ctx.timestamp, 123456789); + } + + #[test] + fn test_isdb_set_global_time_zero_timestamp() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + timestamp: 987654321, + ..Default::default() + }; + + // Set the timestamp to zero + let result = isdb_set_global_time(&mut ctx, 0); + + // Verify the timestamp is updated correctly + assert_eq!(result, CCX_OK as i32); + assert_eq!(ctx.timestamp, 0); + } + + #[test] + fn test_isdb_set_global_time_large_timestamp() { + // Create a mock ISDBSubContext + let mut ctx = ISDBSubContext { + timestamp: 0, + ..Default::default() + }; + + // Set a large timestamp + let result = isdb_set_global_time(&mut ctx, u64::MAX); + + // Verify the timestamp is updated correctly + assert_eq!(result, CCX_OK as i32); + assert_eq!(ctx.timestamp, u64::MAX); + } +} diff --git a/src/rust/lib_ccxr/src/decoder_isdb/mod.rs b/src/rust/lib_ccxr/src/decoder_isdb/mod.rs new file mode 100644 index 000000000..70b098088 --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_isdb/mod.rs @@ -0,0 +1,6 @@ +pub mod exit_codes; +pub mod functions_common; +pub mod functions_isdb; +pub mod structs_ccdecode; +pub mod structs_isdb; +pub mod structs_xds; diff --git a/src/rust/lib_ccxr/src/decoder_isdb/structs_ccdecode.rs b/src/rust/lib_ccxr/src/decoder_isdb/structs_ccdecode.rs new file mode 100644 index 000000000..807b1ff15 --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_isdb/structs_ccdecode.rs @@ -0,0 +1,281 @@ +use crate::decoder_isdb::exit_codes::*; +use crate::decoder_isdb::structs_xds::XdsBuffer; + +use crate::time::Timestamp; + +use std::ptr::null_mut; + +pub struct LibCcDecode { + pub cc_stats: [i32; 4], + pub saw_caption_block: i32, + pub processed_enough: i32, // If 1, we have enough lines, time, etc. + + /* 608 contexts - note that this shouldn't be global, they should be + * per program */ + pub context_cc608_field_1: *mut std::ffi::c_void, + pub context_cc608_field_2: *mut std::ffi::c_void, + + pub no_rollup: i32, // If 1, write one line at a time + pub noscte20: i32, + pub fix_padding: i32, // Replace 0000 with 8080 in HDTV (needed for some cards) + pub write_format: crate::common::OutputFormat, // 0 = Raw, 1 = srt, 2 = SMI + pub extraction_start: Option, + pub extraction_end: Option, // Segment we actually process + pub subs_delay: i64, // ms to delay (or advance) subs + pub extract: i32, // Extract 1st, 2nd or both fields + pub fullbin: i32, // Disable pruning of padding cc blocks + // TODO when cc_subtitle completed + // pub dec_sub: cc_subtitle, + pub in_bufferdatatype: crate::common::BufferdataType, + pub hauppauge_mode: u32, // If 1, use PID=1003, process specially and so on + + pub frames_since_last_gop: i32, + /* GOP-based timing */ + pub saw_gop_header: i32, + /* Time info for timed-transcript */ + pub max_gop_length: i32, // (Maximum) length of a group of pictures + pub last_gop_length: i32, // Length of the previous group of pictures + pub total_pulldownfields: u32, + pub total_pulldownframes: u32, + pub program_number: i32, + pub list: HList, + pub timing: *mut crate::time::TimingContext, + pub codec: crate::common::Codec, // Can also be SelectCodec + + // Set to true if data is buffered + pub has_ccdata_buffered: i32, + pub is_alloc: i32, + + pub avc_ctx: *mut AvcCtx, + pub private_data: *mut std::ffi::c_void, + + /* General video information */ + pub current_hor_size: u32, + pub current_vert_size: u32, + pub current_aspect_ratio: u32, + pub current_frame_rate: u32, // Assume standard fps, 29.97 + + /* Required in es_function.c */ + pub no_bitstream_error: i32, + pub saw_seqgoppic: i32, + pub in_pic_data: i32, + + pub current_progressive_sequence: u32, + pub current_pulldownfields: u32, + + pub temporal_reference: i32, + pub picture_coding_type: crate::common::FrameType, + pub num_key_frames: u32, + pub picture_structure: u32, + pub repeat_first_field: u32, + pub progressive_frame: u32, + pub pulldownfields: u32, + + /* Required in es_function.c and es_userdata.c */ + pub top_field_first: u32, // Needs to be global + + /* Stats. Modified in es_userdata.c */ + pub stat_numuserheaders: i32, + pub stat_dvdccheaders: i32, + pub stat_scte20ccheaders: i32, + pub stat_replay5000headers: i32, + pub stat_replay4000headers: i32, + pub stat_dishheaders: i32, + pub stat_hdtv: i32, + pub stat_divicom: i32, + pub false_pict_header: i32, + // TODO when 708 completed + // pub dtvcc: *mut DtvccCtx, + pub current_field: i32, + + // Analyse/use the picture information + pub maxtref: i32, // Use to remember the temporal reference number + + pub cc_data_count: [i32; SORTBUF], + // Store fts; + pub cc_fts: [i64; SORTBUF], + // Store HD CC packets + pub cc_data_pkts: [[u8; 10 * 31 * 3 + 1]; SORTBUF], // *10, because MP4 seems to have different limits + + // The sequence number of the current anchor frame. All currently read + // B-Frames belong to this I- or P-frame. + pub anchor_seq_number: i32, + pub xds_ctx: *mut XdsContext, + // TODO when vbi completed + // pub vbi_decoder: *mut CcxDecoderVbiCtx, + + // TODO when cc_subtitle completed + // pub writedata: Option< + // extern "C" fn( + // data: *const u8, + // length: i32, + // private_data: *mut std::ffi::c_void, + // sub: *mut cc_subtitle, + // ) -> i32, + // >, + + // DVB subtitle related + pub ocr_quantmode: i32, + pub prev: *mut LibCcDecode, +} +impl Default for LibCcDecode { + fn default() -> Self { + LibCcDecode { + cc_stats: [0; 4], + saw_caption_block: 0, + processed_enough: 0, + context_cc608_field_1: std::ptr::null_mut(), + context_cc608_field_2: std::ptr::null_mut(), + no_rollup: 0, + noscte20: 0, + fix_padding: 0, + write_format: crate::common::OutputFormat::Raw, + extraction_start: None, + extraction_end: None, + subs_delay: 0, + extract: 0, + fullbin: 0, + in_bufferdatatype: crate::common::BufferdataType::Unknown, + hauppauge_mode: 0, + frames_since_last_gop: 0, + saw_gop_header: 0, + max_gop_length: 0, + last_gop_length: 0, + total_pulldownfields: 0, + total_pulldownframes: 0, + program_number: 0, + list: HList::default(), + timing: std::ptr::null_mut(), + codec: crate::common::Codec::Dvb, + has_ccdata_buffered: 0, + is_alloc: 0, + avc_ctx: std::ptr::null_mut(), + private_data: std::ptr::null_mut(), + current_hor_size: 0, + current_vert_size: 0, + current_aspect_ratio: 0, + current_frame_rate: 0, + no_bitstream_error: 0, + saw_seqgoppic: 0, + in_pic_data: 0, + current_progressive_sequence: 0, + current_pulldownfields: 0, + temporal_reference: 0, + picture_coding_type: crate::common::FrameType::ResetOrUnknown, + num_key_frames: 0, + picture_structure: 0, + repeat_first_field: 0, + progressive_frame: 0, + pulldownfields: 0, + top_field_first: 0, + stat_numuserheaders: 0, + stat_dvdccheaders: 0, + stat_scte20ccheaders: 0, + stat_replay5000headers: 0, + stat_replay4000headers: 0, + stat_dishheaders: 0, + stat_hdtv: 0, + stat_divicom: 0, + false_pict_header: 0, + current_field: 0, + maxtref: 0, + cc_data_count: [0; SORTBUF], + cc_fts: [0; SORTBUF], + cc_data_pkts: [[0; 10 * 31 * 3 + 1]; SORTBUF], + anchor_seq_number: 0, + xds_ctx: std::ptr::null_mut(), + ocr_quantmode: 0, + prev: std::ptr::null_mut(), + } + } +} + +// HList (Hyperlinked List) +#[derive(Debug)] +pub struct HList { + // A lot of the HList struct is not implemented yet + pub next: *mut HList, + pub prev: *mut HList, +} +impl Default for HList { + fn default() -> Self { + HList { + next: null_mut(), + prev: null_mut(), + } + } +} + +pub struct AvcCtx { + pub cc_count: u8, // Number of closed caption blocks + pub cc_data: *mut u8, // Pointer to buffer holding CC data + pub cc_databufsize: i64, // Buffer size for CC data + pub cc_buffer_saved: i32, // Was the CC buffer saved after the last update? + + pub got_seq_para: i32, // Flag indicating if sequence parameters were received + pub nal_ref_idc: u32, // NAL reference ID + pub seq_parameter_set_id: i64, // Sequence parameter set ID + pub log2_max_frame_num: i32, // Log2 of max frame number + pub pic_order_cnt_type: i32, // Picture order count type + pub log2_max_pic_order_cnt_lsb: i32, // Log2 of max picture order count LSB + pub frame_mbs_only_flag: i32, // Flag indicating if only frame MBs are used + + // Use and throw stats for debugging (TODO: clean up later) + pub num_nal_unit_type_7: i64, // Number of NAL units of type 7 + pub num_vcl_hrd: i64, // Number of VCL HRD parameters encountered + pub num_nal_hrd: i64, // Number of NAL HRD parameters encountered + pub num_jump_in_frames: i64, // Number of frame jumps detected + pub num_unexpected_sei_length: i64, // Number of unexpected SEI lengths + + pub ccblocks_in_avc_total: i32, // Total CC blocks in AVC stream + pub ccblocks_in_avc_lost: i32, // Lost CC blocks in AVC stream + + pub frame_num: i64, // Current frame number + pub lastframe_num: i64, // Last processed frame number + pub currref: i32, // Current reference index + pub maxidx: i32, // Maximum index value for ordering + pub lastmaxidx: i32, // Last max index + + // Used to find tref zero in PTS mode + pub minidx: i32, // Minimum reference index + pub lastminidx: i32, // Last minimum reference index + + // Used to remember the max temporal reference number (POC mode) + pub maxtref: i32, // Max temporal reference + pub last_gop_maxtref: i32, // Last GOP max temporal reference + + // Used for PTS ordering of CC blocks + pub currefpts: i64, // Current reference PTS + pub last_pic_order_cnt_lsb: i64, // Last picture order count LSB + pub last_slice_pts: i64, // Last slice PTS +} + +pub struct XdsContext { + // Program Identification Number (Start Time) for current program + pub current_xds_min: i32, + pub current_xds_hour: i32, + pub current_xds_date: i32, + pub current_xds_month: i32, + pub current_program_type_reported: i32, // No. + pub xds_start_time_shown: i32, + pub xds_program_length_shown: i32, + pub xds_program_description: [[char; 33]; 8], // Program descriptions (8 entries of 33 characters each) + + pub current_xds_network_name: [char; 33], // Network name + pub current_xds_program_name: [char; 33], // Program name + pub current_xds_call_letters: [char; 7], // Call letters + pub current_xds_program_type: [char; 33], // Program type + + pub xds_buffers: [XdsBuffer; NUM_XDS_BUFFERS as usize], // Array of XDS buffers + pub cur_xds_buffer_idx: i32, // Current XDS buffer index + pub cur_xds_packet_class: i32, // Current XDS packet class + pub cur_xds_payload: *mut u8, // Pointer to the current XDS payload + pub cur_xds_payload_length: i32, // Length of the current XDS payload + pub cur_xds_packet_type: i32, // Current XDS packet type + pub timing: *mut crate::time::TimingContext, // Pointer to timing context + + pub current_ar_start: u32, // Current AR start time + pub current_ar_end: u32, // Current AR end time + + pub xds_write_to_file: i32, // Set to 1 if XDS data is to be written to a file +} diff --git a/src/rust/lib_ccxr/src/decoder_isdb/structs_isdb.rs b/src/rust/lib_ccxr/src/decoder_isdb/structs_isdb.rs new file mode 100644 index 000000000..df1514b09 --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_isdb/structs_isdb.rs @@ -0,0 +1,796 @@ +use crate::decoder_isdb::exit_codes::*; +use crate::decoder_isdb::structs_xds::{CcxDecoder608ColorCode, CcxEncodingType}; + +use crate::common::OutputFormat; // ccxoutputformat + +use crate::common::StreamMode; // //CcxStreamModeEnum + +use crate::common::Codec; // ccxcodetype - 2 options{Codec, SelectCodec} - use appropriately + +use crate::time::TimestampFormat; // ccxoutputdateformat + +use crate::time::TimingContext; // ccxcommontimingctx + +use crate::time::Timestamp; // ccx_boundary_time + +use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_ulonglong, c_void}; + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct ListHead { + pub next: *mut ListHead, + pub prev: *mut ListHead, +} + +#[repr(C)] +pub struct DtvccCtx { + pub is_active: c_int, + pub active_services_count: c_int, + pub services_active: [c_int; CCX_DTVCC_MAX_SERVICES], // 0 - inactive, 1 - active + pub report_enabled: c_int, + pub report: *mut CcxDecoderDtvccReport, + pub decoders: [DtvccServiceDecoder; CCX_DTVCC_MAX_SERVICES], + pub current_packet: [c_uchar; CCX_DTVCC_MAX_PACKET_LENGTH], + pub current_packet_length: c_int, + pub is_current_packet_header_parsed: c_int, + pub last_sequence: c_int, + pub encoder: *mut c_void, // we can't include header, so keeping it this way + pub no_rollup: c_int, + pub timing: *mut TimingContext, +} + +#[repr(C)] +pub struct CcxDecoderDtvccReport { + pub reset_count: c_int, + pub services: [c_uint; CCX_DTVCC_MAX_SERVICES], +} + +#[repr(C)] +pub struct DtvccServiceDecoder { + pub windows: [DtvccWindow; CCX_DTVCC_MAX_WINDOWS], + pub current_window: c_int, + pub tv: *mut DtvccTvScreen, + pub cc_count: c_int, +} + +#[repr(C)] +pub struct DtvccWindow { + pub is_defined: c_int, + pub number: c_int, + pub priority: c_int, + pub col_lock: c_int, + pub row_lock: c_int, + pub visible: c_int, + pub anchor_vertical: c_int, + pub relative_pos: c_int, + pub anchor_horizontal: c_int, + pub row_count: c_int, + pub anchor_point: c_int, + pub col_count: c_int, + pub pen_style: c_int, + pub win_style: c_int, + pub commands: [c_uchar; 6], // Commands used to create this window + pub attribs: DtvccWindowAttribs, + pub pen_row: c_int, + pub pen_column: c_int, + pub rows: [*mut DtvccSymbol; CCX_DTVCC_MAX_ROWS], + pub pen_colors: [[DtvccPenColor; CCX_DTVCC_SCREENGRID_COLUMNS]; CCX_DTVCC_MAX_ROWS], + pub pen_attribs: [[DtvccPenAttribs; CCX_DTVCC_SCREENGRID_COLUMNS]; CCX_DTVCC_MAX_ROWS], + pub pen_color_pattern: DtvccPenColor, + pub pen_attribs_pattern: DtvccPenAttribs, + pub memory_reserved: c_int, + pub is_empty: c_int, + pub time_ms_show: i64, // Assuming LLONG is equivalent to i64 + pub time_ms_hide: i64, +} + +#[repr(C)] +pub struct DtvccWindowAttribs { + pub justify: c_int, + pub print_direction: c_int, + pub scroll_direction: c_int, + pub word_wrap: c_int, + pub display_effect: c_int, + pub effect_direction: c_int, + pub effect_speed: c_int, + pub fill_color: c_int, + pub fill_opacity: c_int, + pub border_type: c_int, + pub border_color: c_int, +} + +#[repr(C)] +pub struct DtvccSymbol { + pub sym: u16, // symbol itself, at least 16 bit + pub init: c_uchar, // initialized or not. could be 0 or 1 +} + +#[repr(C)] +pub struct DtvccPenColor { + pub fg_color: c_int, + pub fg_opacity: c_int, + pub bg_color: c_int, + pub bg_opacity: c_int, + pub edge_color: c_int, +} + +#[repr(C)] +pub struct DtvccPenAttribs { + pub pen_size: c_int, + pub offset: c_int, + pub text_tag: c_int, + pub font_tag: c_int, + pub edge_type: c_int, + pub underline: c_int, + pub italic: c_int, +} + +#[repr(C)] +pub struct DtvccTvScreen { + pub chars: [[DtvccSymbol; CCX_DTVCC_SCREENGRID_COLUMNS]; CCX_DTVCC_SCREENGRID_ROWS], + pub pen_colors: [[DtvccPenColor; CCX_DTVCC_SCREENGRID_COLUMNS]; CCX_DTVCC_SCREENGRID_ROWS], + pub pen_attribs: [[DtvccPenAttribs; CCX_DTVCC_SCREENGRID_COLUMNS]; CCX_DTVCC_SCREENGRID_ROWS], + pub time_ms_show: i64, + pub time_ms_hide: i64, + pub cc_count: c_uint, + pub service_number: c_int, + pub old_cc_time_end: c_int, +} + +#[repr(C)] +pub struct CcxDecoderVbiCtx { + pub vbi_decoder_inited: c_int, + pub zvbi_decoder: VbiRawDecoder, + // vbi3_raw_decoder zvbi_decoder; + pub vbi_debug_dump: Option>, +} + +#[repr(C)] +pub struct VbiRawDecoder { + /* Sampling parameters */ + /** + * Either 525 (M/NTSC, M/PAL) or 625 (PAL, SECAM), describing the + * scan line system all line numbers refer to. + */ + pub scanning: c_int, + /** + * Format of the raw vbi data. + */ + pub sampling_format: VbiPixfmt, + /** + * Sampling rate in Hz, the number of samples or pixels + * captured per second. + */ + pub sampling_rate: c_int, // Hz + /** + * Number of samples or pixels captured per scan line, + * in bytes. This determines the raw vbi image width and you + * want it large enough to cover all data transmitted in the line (with + * headroom). + */ + pub bytes_per_line: c_int, + /** + * The distance from 0H (leading edge hsync, half amplitude point) + * to the first sample (pixel) captured, in samples (pixels). You want + * an offset small enough not to miss the start of the data + * transmitted. + */ + pub offset: c_int, // 0H, samples + /** + * First scan line to be captured, first and second field + * respectively, according to the ITU-R line numbering scheme + * (see vbi_sliced). Set to zero if the exact line number isn't + * known. + */ + pub start: [c_int; 2], // ITU-R numbering + /** + * Number of scan lines captured, first and second + * field respectively. This can be zero if only data from one + * field is required. The sum @a count[0] + @a count[1] determines the + * raw vbi image height. + */ + pub count: [c_int; 2], // field lines + /** + * In the raw vbi image, normally all lines of the second + * field are supposed to follow all lines of the first field. When + * this flag is set, the scan lines of first and second field + * will be interleaved in memory. This implies @a count[0] and @a count[1] + * are equal. + */ + pub interlaced: c_int, + /** + * Fields must be stored in temporal order, i. e. as the + * lines have been captured. It is assumed that the first field is + * also stored first in memory, however if the hardware cannot reliable + * distinguish fields this flag shall be cleared, which disables + * decoding of data services depending on the field number. + */ + pub synchronous: c_int, + + pub services: c_uint, + pub num_jobs: c_int, + + pub pattern: *mut i8, + pub jobs: [VbiRawDecoderJob; 8], +} + +#[repr(C)] +pub struct VbiRawDecoderJob { + pub id: c_uint, + pub offset: c_int, + pub slicer: VbiBitSlicer, +} + +#[repr(C)] +pub enum VbiPixfmt { + Yuv420 = 1, + Yuyv, + Yvyu, + Uyvy, + Vyuy, + Pal8, + Rgba32Le = 32, + Rgba32Be, + Bgra32Le, + Bgra32Be, + Abgr32Be, /* = 32, // synonyms */ + Abgr32Le, + Argb32Be, + Argb32Le, + Rgb24, + Bgr24, + Rgb16Le, + Rgb16Be, + Bgr16Le, + Bgr16Be, + Rgba15Le, + Rgba15Be, + Bgra15Le, + Bgra15Be, + Argb15Le, + Argb15Be, + Abgr15Le, + Abgr15Be, +} + +#[repr(C)] +pub struct VbiBitSlicer { + pub func: Option< + extern "C" fn(slicer: *mut VbiBitSlicer, raw: *mut c_uchar, buf: *mut c_uchar) -> c_int, + >, + pub cri: c_uint, + pub cri_mask: c_uint, + pub thresh: c_int, + pub cri_bytes: c_int, + pub cri_rate: c_int, + pub oversampling_rate: c_int, + pub phase_shift: c_int, + pub step: c_int, + pub frc: c_uint, + pub frc_bits: c_int, + pub payload: c_int, + pub endian: c_int, + pub skip: c_int, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct ISDBSubContext { + pub nb_char: c_int, + pub nb_line: c_int, + pub timestamp: u64, + pub prev_timestamp: u64, + pub text_list_head: ListHead, + pub buffered_text: ListHead, + pub current_state: ISDBSubState, + pub tmd: IsdbTmd, + pub nb_lang: c_int, + pub offset_time: OffsetTime, + pub dmf: c_uchar, + pub dc: c_uchar, + pub cfg_no_rollup: c_int, +} + +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct ISDBSubState { + pub auto_display: c_int, // bool + pub rollup_mode: c_int, // bool + pub need_init: c_uchar, // bool + pub clut_high_idx: c_uchar, + pub fg_color: c_uint, + pub bg_color: c_uint, + pub hfg_color: c_uint, + pub hbg_color: c_uint, + pub mat_color: c_uint, + pub raster_color: c_uint, + pub layout_state: ISDBSubLayout, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct ISDBSubLayout { + pub format: WritingFormat, + pub display_area: DispArea, + pub font_size: c_int, + pub font_scale: FScale, + pub cell_spacing: Spacing, + pub cursor_pos: ISDBPos, + pub ccc: IsdbCCComposition, + pub acps: [c_int; 2], +} + +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct DispArea { + pub x: c_int, + pub y: c_int, + pub w: c_int, + pub h: c_int, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FScale { + pub fscx: c_int, + pub fscy: c_int, +} + +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct Spacing { + pub col: c_int, + pub row: c_int, +} + +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct ISDBPos { + pub x: c_int, + pub y: c_int, +} + +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct OffsetTime { + pub hour: c_int, + pub min: c_int, + pub sec: c_int, + pub milli: c_int, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub enum WritingFormat { + #[default] + HorizontalStdDensity = 0, + VerticalStdDensity = 1, + HorizontalHighDensity = 2, + VerticalHighDensity = 3, + HorizontalWesternLang = 4, + Horizontal1920x1080 = 5, + Vertical1920x1080 = 6, + Horizontal960x540 = 7, + Vertical960x540 = 8, + Horizontal720x480 = 9, + Vertical720x480 = 10, + Horizontal1280x720 = 11, + Vertical1280x720 = 12, + HorizontalCustom = 100, + None, +} + +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub enum IsdbCCComposition { + #[default] + None = 0, + And = 2, + Or = 3, + Xor = 4, +} + +use std::convert::TryFrom; + +impl TryFrom for IsdbCCComposition { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(IsdbCCComposition::None), + 2 => Ok(IsdbCCComposition::And), + 3 => Ok(IsdbCCComposition::Or), + 4 => Ok(IsdbCCComposition::Xor), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq)] +#[repr(C)] +pub enum IsdbTmd { + #[default] + Free = 0, + RealTime = 0x1, + OffsetTime = 0x2, +} + +#[repr(C)] +pub enum FontSize { + SmallFontSize, + MiddleFontSize, + StandardFontSize, +} + +#[repr(C)] +pub enum CsiCommand { + Gsm = 0x42, + Swf = 0x53, + Ccc = 0x54, + Sdf = 0x56, + Ssm = 0x57, + Shs = 0x58, + Svs = 0x59, + Pld = 0x5B, + Plu = 0x5C, + Gaa = 0x5D, + Src = 0x5E, + Sdp = 0x5F, + Acps = 0x61, + Tcc = 0x62, + Orn = 0x63, + Mdf = 0x64, + Cfs = 0x65, + Xcs = 0x66, + Pra = 0x68, + Acs = 0x69, + Rcs = 0x6E, + Scs = 0x6F, +} + +#[repr(C)] +pub enum Color { + CcxIsdbBlack, + FiRed, + FiGreen, + FiYellow, + FiBlue, + FiMagenta, + FiCyan, + FiWhite, + CcxIsdbTransparent, + HiRed, + HiGreen, + HiYellow, + HiBlue, + HiMagenta, + HiCyan, + HiWhite, +} + +#[no_mangle] +pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> u32 { + ((255 - a as u32) << 24) | ((b as u32) << 16) | ((g as u32) << 8) | (r as u32) +} + +type Rgba = u32; +pub const ZATA: u32 = 0; +pub const DEFAULT_CLUT: [Rgba; 128] = [ + // 0-7 + rgba(0, 0, 0, 255), + rgba(255, 0, 0, 255), + rgba(0, 255, 0, 255), + rgba(255, 255, 0, 255), + rgba(0, 0, 255, 255), + rgba(255, 0, 255, 255), + rgba(0, 255, 255, 255), + rgba(255, 255, 255, 255), + // 8-15 + rgba(0, 0, 0, 0), + rgba(170, 0, 0, 255), + rgba(0, 170, 0, 255), + rgba(170, 170, 0, 255), + rgba(0, 0, 170, 255), + rgba(170, 0, 170, 255), + rgba(0, 170, 170, 255), + rgba(170, 170, 170, 255), + // 16-23 + rgba(0, 0, 85, 255), + rgba(0, 85, 0, 255), + rgba(0, 85, 85, 255), + rgba(0, 85, 170, 255), + rgba(0, 85, 255, 255), + rgba(0, 170, 85, 255), + rgba(0, 170, 255, 255), + rgba(0, 255, 85, 255), + // 24-31 + rgba(0, 255, 170, 255), + rgba(85, 0, 0, 255), + rgba(85, 0, 85, 255), + rgba(85, 0, 170, 255), + rgba(85, 0, 255, 255), + rgba(85, 85, 0, 255), + rgba(85, 85, 85, 255), + rgba(85, 85, 170, 255), + // 32-39 + rgba(85, 85, 255, 255), + rgba(85, 170, 0, 255), + rgba(85, 170, 85, 255), + rgba(85, 170, 170, 255), + rgba(85, 170, 255, 255), + rgba(85, 255, 0, 255), + rgba(85, 255, 85, 255), + rgba(85, 255, 170, 255), + // 40-47 + rgba(85, 255, 255, 255), + rgba(170, 0, 85, 255), + rgba(170, 0, 255, 255), + rgba(170, 85, 0, 255), + rgba(170, 85, 85, 255), + rgba(170, 85, 170, 255), + rgba(170, 85, 255, 255), + rgba(170, 170, 85, 255), + // 48-55 + rgba(170, 170, 255, 255), + rgba(170, 255, 0, 255), + rgba(170, 255, 85, 255), + rgba(170, 255, 170, 255), + rgba(170, 255, 255, 255), + rgba(255, 0, 85, 255), + rgba(255, 0, 170, 255), + rgba(255, 85, 0, 255), + // 56-63 + rgba(255, 85, 85, 255), + rgba(255, 85, 170, 255), + rgba(255, 85, 255, 255), + rgba(255, 170, 0, 255), + rgba(255, 170, 85, 255), + rgba(255, 170, 170, 255), + rgba(255, 170, 255, 255), + rgba(255, 255, 85, 255), + // 64 + rgba(255, 255, 170, 255), + // 65-127 are calculated later. + // Initialize remaining elements to 0 + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, +]; + +#[repr(C)] +pub struct ISDBText { + pub buf: *mut c_char, + pub len: usize, + pub used: usize, + pub pos: ISDBPos, + pub txt_tail: usize, // tail of the text, excluding trailing control sequences. + pub timestamp: c_ulonglong, // Time Stamp when first string is received + pub refcount: c_int, + pub list: ListHead, +} + +pub struct CcxEncodersTranscriptFormat { + pub show_start_time: i32, // Show start and/or end time. + pub show_end_time: i32, // Show start and/or end time. + pub show_mode: i32, // Show which mode if available (E.G.: POP, RU1, ...) + pub show_cc: i32, // Show which CC channel has been captured. + pub relative_timestamp: i32, // Timestamps relative to start of sample or in UTC? + pub xds: i32, // Show XDS or not + pub use_colors: i32, // Add colors or no colors + pub is_final: i32, // Used to determine if these parameters should be changed afterwards. +} + +#[repr(i32)] +pub enum CcxDataSource { + File = 0, + Stdin = 1, + Network = 2, + Tcp = 3, +} + +#[repr(C)] +pub struct CcxDecoder608Settings { + pub direct_rollup: i32, + pub force_rollup: i32, + pub no_rollup: i32, + pub default_color: CcxDecoder608ColorCode, + pub screens_to_process: i32, + pub report: *mut CcxDecoder608Report, +} + +#[repr(C)] +pub struct CcxDecoder608Report { + pub xds: u8, + pub cc_channels: [u8; 4], +} + +#[repr(C)] +pub struct CcxDecoderDtvccSettings { + pub enabled: i32, + pub print_file_reports: i32, + pub no_rollup: i32, + pub report: *mut CcxDecoderDtvccReport, + pub active_services_count: i32, + pub services_enabled: [i32; CCX_DTVCC_MAX_SERVICES], + pub timing: *mut TimingContext, +} + +#[repr(C)] +pub struct DemuxerCfg { + pub m2ts: i32, + pub auto_stream: StreamMode, + pub codec: Codec, + pub nocodec: Codec, + pub ts_autoprogram: u32, + pub ts_allprogram: u32, + pub ts_cappids: [u32; 128], + pub nb_ts_cappid: i32, + pub ts_forced_cappid: u32, + pub ts_forced_program: i32, + pub ts_forced_program_selected: u32, + pub ts_datastreamtype: i32, + pub ts_forced_streamtype: u32, +} + +#[repr(C)] +pub struct EncoderCfg { + pub extract: i32, + pub dtvcc_extract: i32, + pub gui_mode_reports: i32, + pub output_filename: *mut c_char, + pub write_format: OutputFormat, + pub keep_output_closed: i32, + pub force_flush: i32, + pub append_mode: i32, + pub ucla: i32, + pub encoding: CcxEncodingType, + pub date_format: TimestampFormat, + pub millis_separator: c_char, + pub autodash: i32, + pub trim_subs: i32, + pub sentence_cap: i32, + pub splitbysentence: i32, + pub filter_profanity: i32, + pub with_semaphore: i32, + pub start_credits_text: *mut c_char, + pub end_credits_text: *mut c_char, + pub startcreditsnotbefore: Timestamp, + pub startcreditsnotafter: Timestamp, + pub startcreditsforatleast: Timestamp, + pub startcreditsforatmost: Timestamp, + pub endcreditsforatleast: Timestamp, + pub endcreditsforatmost: Timestamp, + pub transcript_settings: CcxEncodersTranscriptFormat, + pub send_to_srv: u32, + pub no_bom: i32, + pub first_input_file: *mut c_char, + pub multiple_files: i32, + pub no_font_color: i32, + pub no_type_setting: i32, + pub cc_to_stdout: i32, + pub line_terminator_lf: i32, + pub subs_delay: i64, + pub program_number: i32, + pub in_format: u8, + pub nospupngocr: i32, + pub force_dropframe: i32, + pub render_font: *mut c_char, + pub render_font_italics: *mut c_char, + pub services_enabled: [i32; CCX_DTVCC_MAX_SERVICES], + pub services_charsets: *mut *mut c_char, + pub all_services_charset: *mut c_char, + pub extract_only_708: i32, +} + +impl Default for ISDBSubLayout { + fn default() -> Self { + Self { + format: WritingFormat::None, + display_area: Default::default(), + font_size: 0, + font_scale: Default::default(), + cell_spacing: Default::default(), + cursor_pos: Default::default(), + ccc: IsdbCCComposition::None, + acps: [0; 2], + } + } +} + +impl Default for FScale { + fn default() -> Self { + Self { + fscx: 100, + fscy: 100, + } + } +} + +impl Default for ISDBSubContext { + fn default() -> Self { + Self { + nb_char: 0, + nb_line: 0, + timestamp: 0, + prev_timestamp: 0, + text_list_head: Default::default(), + buffered_text: Default::default(), + current_state: Default::default(), + tmd: IsdbTmd::Free, + nb_lang: 0, + offset_time: Default::default(), + dmf: 0, + dc: 0, + cfg_no_rollup: 0, + } + } +} + +impl Default for ListHead { + fn default() -> Self { + Self { + next: std::ptr::null_mut(), + prev: std::ptr::null_mut(), + } + } +} diff --git a/src/rust/lib_ccxr/src/decoder_isdb/structs_xds.rs b/src/rust/lib_ccxr/src/decoder_isdb/structs_xds.rs new file mode 100644 index 000000000..33332545c --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_isdb/structs_xds.rs @@ -0,0 +1,392 @@ +use crate::decoder_isdb::exit_codes::*; + +use crate::time::timing::*; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum CcxEia608Format { + SformatCcScreen, + SformatCcLine, + SformatXds, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum CcxDecoder608ColorCode { + ColWhite = 0, + ColGreen = 1, + ColBlue = 2, + ColCyan = 3, + ColRed = 4, + ColYellow = 5, + ColMagenta = 6, + ColUserDefined = 7, + ColBlack = 8, + ColTransparent = 9, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum FontBits { + FontRegular = 0, + FontItalics = 1, + FontUnderlined = 2, + FontUnderlinedItalics = 3, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum CcModes { + ModePopOn = 0, + ModeRollUp2 = 1, + ModeRollUp3 = 2, + ModeRollUp4 = 3, + ModeText = 4, + ModePaintOn = 5, + ModeFakeRollUp1 = 100, // Fake modes to emulate stuff +} + +// The `Eia608Screen` structure +#[derive(Debug)] +pub struct Eia608Screen { + pub format: CcxEia608Format, // Format of data inside this structure + pub characters: [[u8; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], // Characters + pub colors: + [[CcxDecoder608ColorCode; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], // Colors + pub fonts: [[FontBits; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], // Fonts + pub row_used: [i64; CCX_DECODER_608_SCREEN_ROWS], // Any data in row? + pub empty: i64, // Buffer completely empty? + pub start_time: i64, // Start time of this CC buffer + pub end_time: i64, // End time of this CC buffer + pub mode: CcModes, // Mode + pub channel: i64, // Currently selected channel + pub my_field: i64, // Used for sanity checks + pub xds_str: *const u8, // Pointer to XDS string + pub xds_len: usize, // Length of XDS string + pub cur_xds_packet_class: i64, // Class of XDS string +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum SubDataType { + #[default] + Generic = 0, + Dvb = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum SubType { + #[default] + Bitmap, + Cc608, + Text, + Raw, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum CcxEncodingType { + #[default] + Unicode = 0, + Latin1 = 1, + Utf8 = 2, + Ascii = 3, +} + +#[derive(Debug)] +pub struct CcSubtitle { + /** + * A generic data which contains data according to the decoder. + * @warn Decoder can't output multiple types of data. + */ + pub data: *mut std::ffi::c_void, // Pointer to generic data + pub datatype: SubDataType, // Data type (e.g., Generic, DVB) + + /** Number of data */ + pub nb_data: u32, + + /** Type of subtitle */ + pub subtype: SubType, + + /** Encoding type of text, ignored for bitmap or cc_screen subtypes */ + pub enc_type: CcxEncodingType, + + /* Set only when all the data is to be displayed at the same time. + * Unit of time is milliseconds. + */ + pub start_time: i64, + pub end_time: i64, + + /* Flags */ + pub flags: i32, + + /* Index of language table */ + pub lang_index: i32, + + /** Flag to tell that the decoder has given output */ + pub got_output: bool, + + pub mode: [u8; 5], // Mode as a fixed-size array of 5 bytes + pub info: [u8; 4], // Info as a fixed-size array of 4 bytes + + /** Used for DVB end time in milliseconds */ + pub time_out: i32, + + pub next: *mut CcSubtitle, // Pointer to the next subtitle + pub prev: *mut CcSubtitle, // Pointer to the previous subtitle +} + +// XDS classes +pub const XDS_CLASSES: [&str; 8] = [ + "Current", + "Future", + "Channel", + "Miscellaneous", + "Public service", + "Reserved", + "Private data", + "End", +]; + +// XDS program types +pub const XDS_PROGRAM_TYPES: [&str; 96] = [ + "Education", + "Entertainment", + "Movie", + "News", + "Religious", + "Sports", + "Other", + "Action", + "Advertisement", + "Animated", + "Anthology", + "Automobile", + "Awards", + "Baseball", + "Basketball", + "Bulletin", + "Business", + "Classical", + "College", + "Combat", + "Comedy", + "Commentary", + "Concert", + "Consumer", + "Contemporary", + "Crime", + "Dance", + "Documentary", + "Drama", + "Elementary", + "Erotica", + "Exercise", + "Fantasy", + "Farm", + "Fashion", + "Fiction", + "Food", + "Football", + "Foreign", + "Fund-Raiser", + "Game/Quiz", + "Garden", + "Golf", + "Government", + "Health", + "High_School", + "History", + "Hobby", + "Hockey", + "Home", + "Horror", + "Information", + "Instruction", + "International", + "Interview", + "Language", + "Legal", + "Live", + "Local", + "Math", + "Medical", + "Meeting", + "Military", + "Mini-Series", + "Music", + "Mystery", + "National", + "Nature", + "Police", + "Politics", + "Premiere", + "Pre-Recorded", + "Product", + "Professional", + "Public", + "Racing", + "Reading", + "Repair", + "Repeat", + "Review", + "Romance", + "Science", + "Series", + "Service", + "Shopping", + "Soap_Opera", + "Special", + "Suspense", + "Talk", + "Technical", + "Tennis", + "Travel", + "Variety", + "Video", + "Weather", + "Western", +]; + +// XDS class constants +pub const XDS_CLASS_CURRENT: u8 = 0; +pub const XDS_CLASS_FUTURE: u8 = 1; +pub const XDS_CLASS_CHANNEL: u8 = 2; +pub const XDS_CLASS_MISC: u8 = 3; +pub const XDS_CLASS_PUBLIC: u8 = 4; +pub const XDS_CLASS_RESERVED: u8 = 5; +pub const XDS_CLASS_PRIVATE: u8 = 6; +pub const XDS_CLASS_END: u8 = 7; +pub const XDS_CLASS_OUT_OF_BAND: u8 = 0x40; // Not a real class, a marker for packets for out-of-band data + +// Types for the classes current and future +pub const XDS_TYPE_PIN_START_TIME: u8 = 1; +pub const XDS_TYPE_LENGTH_AND_CURRENT_TIME: u8 = 2; +pub const XDS_TYPE_PROGRAM_NAME: u8 = 3; +pub const XDS_TYPE_PROGRAM_TYPE: u8 = 4; +pub const XDS_TYPE_CONTENT_ADVISORY: u8 = 5; +pub const XDS_TYPE_AUDIO_SERVICES: u8 = 6; +pub const XDS_TYPE_CGMS: u8 = 8; // Copy Generation Management System +pub const XDS_TYPE_ASPECT_RATIO_INFO: u8 = 9; // Appears in CEA-608-B but in E it's been removed as is "reserved" +pub const XDS_TYPE_PROGRAM_DESC_1: u8 = 0x10; +pub const XDS_TYPE_PROGRAM_DESC_2: u8 = 0x11; +pub const XDS_TYPE_PROGRAM_DESC_3: u8 = 0x12; +pub const XDS_TYPE_PROGRAM_DESC_4: u8 = 0x13; +pub const XDS_TYPE_PROGRAM_DESC_5: u8 = 0x14; +pub const XDS_TYPE_PROGRAM_DESC_6: u8 = 0x15; +pub const XDS_TYPE_PROGRAM_DESC_7: u8 = 0x16; +pub const XDS_TYPE_PROGRAM_DESC_8: u8 = 0x17; + +// Types for the class channel +pub const XDS_TYPE_NETWORK_NAME: u8 = 1; +pub const XDS_TYPE_CALL_LETTERS_AND_CHANNEL: u8 = 2; +pub const XDS_TYPE_TSID: u8 = 4; // Transmission Signal Identifier + +// Types for miscellaneous packets +pub const XDS_TYPE_TIME_OF_DAY: u8 = 1; +pub const XDS_TYPE_LOCAL_TIME_ZONE: u8 = 4; +pub const XDS_TYPE_OUT_OF_BAND_CHANNEL_NUMBER: u8 = 0x40; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct XdsBuffer { + pub in_use: i64, // Whether the buffer is in use + pub xds_class: i64, // XDS class (e.g., XDS_CLASS_CURRENT, etc.) + pub xds_type: i64, // XDS type (e.g., XDS_TYPE_PROGRAM_NAME, etc.) + pub bytes: [u8; NUM_BYTES_PER_PACKET as usize], // Data bytes (size defined by NUM_BYTES_PER_PACKET) + pub used_bytes: i64, // Number of bytes used in the buffer +} + +#[repr(C)] +pub struct CcxDecodersXdsContext { + // Program Identification Number (Start Time) for current program + pub current_xds_min: i64, + pub current_xds_hour: i64, + pub current_xds_date: i64, + pub current_xds_month: i64, + pub current_program_type_reported: i64, // No. + pub xds_start_time_shown: i64, + pub xds_program_length_shown: i64, + pub xds_program_description: [[u8; 33]; 8], // 8 strings of 33 bytes each + + pub current_xds_network_name: [u8; 33], // String of 33 bytes + pub current_xds_program_name: [u8; 33], // String of 33 bytes + pub current_xds_call_letters: [u8; 7], // String of 7 bytes + pub current_xds_program_type: [u8; 33], // String of 33 bytes + + pub xds_buffers: [XdsBuffer; NUM_XDS_BUFFERS as usize], // Array of XdsBuffer + pub cur_xds_buffer_idx: i64, + pub cur_xds_packet_class: i64, + pub cur_xds_payload: *mut u8, // Pointer to payload + pub cur_xds_payload_length: i64, + pub cur_xds_packet_type: i64, + pub timing: TimingContext, // Replacing ccx_common_timing_ctx with TimingContext + + pub current_ar_start: i64, + pub current_ar_end: i64, + + pub xds_write_to_file: i64, // Set to 1 if XDS data is to be written to file +} + +impl Default for CcxDecodersXdsContext { + fn default() -> Self { + Self { + current_xds_min: -1, + current_xds_hour: -1, + current_xds_date: -1, + current_xds_month: -1, + current_program_type_reported: 0, + xds_start_time_shown: 0, + xds_program_length_shown: 0, + xds_program_description: [[0; 33]; 8], + current_xds_network_name: [0; 33], + current_xds_program_name: [0; 33], + current_xds_call_letters: [0; 7], + current_xds_program_type: [0; 33], + xds_buffers: [XdsBuffer { + in_use: 0, + xds_class: -1, + xds_type: -1, + bytes: [0; NUM_BYTES_PER_PACKET as usize], + used_bytes: 0, + }; NUM_XDS_BUFFERS as usize], + cur_xds_buffer_idx: -1, + cur_xds_packet_class: -1, + cur_xds_payload: std::ptr::null_mut(), + cur_xds_payload_length: 0, + cur_xds_packet_type: 0, + timing: TimingContext::default(), + current_ar_start: 0, + current_ar_end: 0, + xds_write_to_file: 0, + } + } +} + +impl Default for XdsBuffer { + fn default() -> Self { + Self { + in_use: 0, + xds_class: -1, + xds_type: -1, + bytes: [0; NUM_BYTES_PER_PACKET as usize], + used_bytes: 0, + } + } +} + +impl Default for CcSubtitle { + fn default() -> Self { + Self { + data: std::ptr::null_mut(), + datatype: SubDataType::Generic, + nb_data: 0, + subtype: SubType::Cc608, + enc_type: CcxEncodingType::Utf8, + start_time: 0, + end_time: 0, + flags: 0, + lang_index: 0, + got_output: false, + mode: [0; 5], + info: [0; 4], + time_out: 0, + next: std::ptr::null_mut(), + prev: std::ptr::null_mut(), + } + } +} diff --git a/src/rust/lib_ccxr/src/lib.rs b/src/rust/lib_ccxr/src/lib.rs index 9f32678db..bd76547ab 100644 --- a/src/rust/lib_ccxr/src/lib.rs +++ b/src/rust/lib_ccxr/src/lib.rs @@ -1,5 +1,6 @@ pub mod activity; pub mod common; +pub mod decoder_isdb; pub mod hardsubx; pub mod subtitle; pub mod teletext; diff --git a/src/rust/src/libccxr_exports/isdb_exports.rs b/src/rust/src/libccxr_exports/isdb_exports.rs new file mode 100644 index 000000000..6892e3fa0 --- /dev/null +++ b/src/rust/src/libccxr_exports/isdb_exports.rs @@ -0,0 +1,95 @@ +use lib_ccxr::decoder_isdb::exit_codes::*; +use lib_ccxr::decoder_isdb::functions_isdb::*; +use lib_ccxr::decoder_isdb::structs_ccdecode::*; +use lib_ccxr::decoder_isdb::structs_isdb::*; +use lib_ccxr::decoder_isdb::structs_xds::*; + +/// # Safety +/// +/// - The caller must ensure that `isdb_ctx` is a valid, non-null pointer to a mutable `Option>`. +/// - The function dereferences `isdb_ctx` and calls `as_mut()` on it, so passing an invalid or null pointer will result in undefined behavior. +/// - After calling this function, the memory associated with the `ISDBSubContext` may be freed, so the caller must not use the pointer again. +#[no_mangle] +pub unsafe extern "C" fn ccxr_delete_isdb_decoder(isdb_ctx: *mut Option>) { + if let Some(ctx) = unsafe { isdb_ctx.as_mut() } { + delete_isdb_decoder(ctx); + } +} + +/// # Safety +/// +/// - The caller is responsible for managing the lifetime of the returned pointer. +/// - The returned pointer must eventually be passed to a function that properly deallocates it (e.g., `ccxr_delete_isdb_decoder`). +/// - Using the pointer after it has been deallocated will result in undefined behavior. +#[no_mangle] +pub extern "C" fn ccxr_init_isdb_decoder() -> *mut ISDBSubContext { + if let Some(ctx) = init_isdb_decoder() { + Box::into_raw(ctx) + } else { + std::ptr::null_mut() + } +} + +/// # Safety +/// +/// - The caller must ensure that `codec_ctx` is a valid, non-null pointer to an `ISDBSubContext`. +/// - The caller must ensure that `buf` points to a valid, null-terminated buffer of `u8` values. +/// - The caller must ensure that `sub` is a valid, non-null pointer to a `CcSubtitle`. +/// - Passing invalid or null pointers to any of these parameters will result in undefined behavior. +#[no_mangle] +pub unsafe extern "C" fn ccxr_isdb_parse_data_group( + codec_ctx: *mut ISDBSubContext, + buf: *const u8, + sub: *mut CcSubtitle, +) -> i32 { + if let (Some(codec_ctx), Some(sub)) = (unsafe { codec_ctx.as_mut() }, unsafe { sub.as_mut() }) { + let mut len = 0; + while unsafe { *buf.add(len) } != 0 { + len += 1; + } + let buf_slice = unsafe { std::slice::from_raw_parts(buf, len) }; + isdb_parse_data_group(codec_ctx, buf_slice, sub) + } else { + -1 + } +} + +/// # Safety +/// +/// - The caller must ensure that `dec_ctx` is a valid, non-null pointer to a `LibCcDecode`. +/// - The caller must ensure that `buf` points to a valid buffer of at least `buf_len` bytes. +/// - The caller must ensure that `sub` is a valid, non-null pointer to a `CcSubtitle`. +/// - Passing invalid or null pointers to any of these parameters will result in undefined behavior. +#[no_mangle] +pub unsafe extern "C" fn ccxr_isdbsub_decode( + dec_ctx: *mut LibCcDecode, + buf: *const u8, + buf_len: usize, + sub: *mut CcSubtitle, +) -> i32 { + if let (Some(dec_ctx), Some(sub)) = (unsafe { dec_ctx.as_mut() }, unsafe { sub.as_mut() }) { + let buf_slice = unsafe { std::slice::from_raw_parts(buf, buf_len) }; + isdbsub_decode(dec_ctx, buf_slice, sub) + } else { + -1 + } +} + +/// # Safety +/// +/// - The caller must ensure that `dec_ctx` is a valid, non-null pointer to a `LibCcDecode`. +/// - The `private_data` field of `LibCcDecode` must point to a valid `ISDBSubContext`. +/// - Passing an invalid or null pointer, or an invalid `private_data` field, will result in undefined behavior. +#[no_mangle] +pub unsafe extern "C" fn ccxr_isdb_set_global_time( + dec_ctx: *mut LibCcDecode, + timestamp: u64, +) -> i32 { + if let Some(ctx) = + unsafe { (dec_ctx.as_mut()).and_then(|d| (d.private_data as *mut ISDBSubContext).as_mut()) } + { + isdb_set_global_time(ctx, timestamp) + } else { + CCX_EINVAL as i32 + } +} diff --git a/src/rust/src/libccxr_exports/mod.rs b/src/rust/src/libccxr_exports/mod.rs index c2f64a258..d803158d3 100644 --- a/src/rust/src/libccxr_exports/mod.rs +++ b/src/rust/src/libccxr_exports/mod.rs @@ -1,5 +1,6 @@ //! Provides C-FFI functions that are direct equivalent of functions available in C. +pub mod isdb_exports; pub mod time; use crate::ccx_options; use lib_ccxr::util::log::*;