From 8014d8b0c0938d1ad76911aa8c7256d81fcf7496 Mon Sep 17 00:00:00 2001 From: Elbert Ronnie Date: Sun, 27 Aug 2023 17:03:03 +0530 Subject: [PATCH 1/5] create lib_ccxr and libccxr_exports --- .gitignore | 3 +- src/rust/Cargo.lock | 144 +++++++++++++++++----------- src/rust/Cargo.toml | 1 + src/rust/lib_ccxr/Cargo.lock | 7 ++ src/rust/lib_ccxr/Cargo.toml | 17 ++++ src/rust/lib_ccxr/src/lib.rs | 1 + src/rust/lib_ccxr/src/util/mod.rs | 1 + src/rust/src/lib.rs | 1 + src/rust/src/libccxr_exports/mod.rs | 1 + 9 files changed, 117 insertions(+), 59 deletions(-) create mode 100644 src/rust/lib_ccxr/Cargo.lock create mode 100644 src/rust/lib_ccxr/Cargo.toml create mode 100644 src/rust/lib_ccxr/src/lib.rs create mode 100644 src/rust/lib_ccxr/src/util/mod.rs create mode 100644 src/rust/src/libccxr_exports/mod.rs diff --git a/.gitignore b/.gitignore index a1ef235b5..3f8a2ed82 100644 --- a/.gitignore +++ b/.gitignore @@ -149,7 +149,8 @@ src/rust/CMakeCache.txt src/rust/Makefile src/rust/cmake_install.cmake src/rust/target/ +src/rust/lib_ccxr/target/ windows/ccx_rust.lib windows/*/debug/* windows/*/CACHEDIR.TAG -windows/.rustc_info.json \ No newline at end of file +windows/.rustc_info.json diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 89013e51f..d42a787f1 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] @@ -99,9 +99,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "camino" -version = "1.1.4" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" [[package]] name = "ccx_rust" @@ -111,6 +111,7 @@ dependencies = [ "env_logger", "iconv", "leptonica-sys", + "lib_ccxr", "log", "palette", "rsmpeg", @@ -123,7 +124,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" dependencies = [ - "nom 5.1.2", + "nom 5.1.3", ] [[package]] @@ -143,9 +144,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ed9a53e5d4d9c573ae844bfac6872b159cb1d1585a83b29e7a64b7eef7332a" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", @@ -175,9 +176,9 @@ checksum = "74c57ab96715773d9cb9789b38eb7cbf04b3c6f5624a9d98f51761603376767c" [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "env_logger" @@ -246,20 +247,24 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "leptonica-sys" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "811a92997ff15e0d7323c1e8fa7190331dd02ea50d9d7cfaa4fdc2b21a613a2e" +checksum = "eff3f1dc2f0112411228f8db99ca8a6a1157537a7887b28b1c91fdc4051fb326" dependencies = [ "bindgen 0.64.0", "pkg-config", "vcpkg", ] +[[package]] +name = "lib_ccxr" +version = "0.1.0" + [[package]] name = "libc" -version = "0.2.140" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libloading" @@ -273,12 +278,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" @@ -294,9 +296,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nom" -version = "5.1.2" +version = "5.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" dependencies = [ "memchr", "version_check", @@ -314,18 +316,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", ] [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "palette" @@ -353,9 +355,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "peeking_take_while" @@ -365,9 +367,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "phf" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ "phf_macros", "phf_shared", @@ -375,9 +377,9 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared", "rand", @@ -385,46 +387,46 @@ dependencies = [ [[package]] name = "phf_macros" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] name = "phf_shared" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ "siphasher", ] [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "proc-macro2" -version = "1.0.52" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -446,9 +448,21 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "regex" -version = "1.7.1" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" dependencies = [ "aho-corasick", "memchr", @@ -457,9 +471,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rsmpeg" @@ -495,9 +509,23 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.158" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] [[package]] name = "shlex" @@ -507,9 +535,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "siphasher" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "strsim" @@ -530,9 +558,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.4" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c622ae390c9302e214c31013517c2061ecb2699935882c60a9b37f82f8625ae" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -571,22 +599,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.4", + "syn 2.0.29", ] [[package]] @@ -600,9 +628,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-width" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index e8edc2096..f736deb3a 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -18,6 +18,7 @@ palette = "0.6.0" rsmpeg = { version = "0.14.1", optional = true, features = ["link_system_ffmpeg"] } tesseract-sys = { version = "0.5.14", optional = true, default-features = false} leptonica-sys = { version = "0.4.3", optional = true, default-features = false} +lib_ccxr = { path = "lib_ccxr" } [build-dependencies] bindgen = "0.58.1" diff --git a/src/rust/lib_ccxr/Cargo.lock b/src/rust/lib_ccxr/Cargo.lock new file mode 100644 index 000000000..7532d4515 --- /dev/null +++ b/src/rust/lib_ccxr/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "lib_ccxr" +version = "0.1.0" diff --git a/src/rust/lib_ccxr/Cargo.toml b/src/rust/lib_ccxr/Cargo.toml new file mode 100644 index 000000000..ca3612505 --- /dev/null +++ b/src/rust/lib_ccxr/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "lib_ccxr" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[features] +default = ["enable_sharing", "wtv_debug", "enable_ffmpeg", "debug", "with_libcurl"] +enable_sharing = [] +wtv_debug = [] +enable_ffmpeg = [] +debug_out = [] +debug = [] +with_libcurl = [] diff --git a/src/rust/lib_ccxr/src/lib.rs b/src/rust/lib_ccxr/src/lib.rs new file mode 100644 index 000000000..812d1edf2 --- /dev/null +++ b/src/rust/lib_ccxr/src/lib.rs @@ -0,0 +1 @@ +pub mod util; diff --git a/src/rust/lib_ccxr/src/util/mod.rs b/src/rust/lib_ccxr/src/util/mod.rs new file mode 100644 index 000000000..daf5935ac --- /dev/null +++ b/src/rust/lib_ccxr/src/util/mod.rs @@ -0,0 +1 @@ +//! Provides basic utilities used throughout the program. diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index a76faa419..1fec8fa5e 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -15,6 +15,7 @@ pub mod bindings { pub mod decoder; #[cfg(feature = "hardsubx_ocr")] pub mod hardsubx; +pub mod libccxr_exports; pub mod utils; #[cfg(windows)] diff --git a/src/rust/src/libccxr_exports/mod.rs b/src/rust/src/libccxr_exports/mod.rs new file mode 100644 index 000000000..e365e0fb2 --- /dev/null +++ b/src/rust/src/libccxr_exports/mod.rs @@ -0,0 +1 @@ +//! Provides C-FFI functions that are direct equivalent of functions available in C. From 8f348fe2d9dc0ca382542e5d5ec6537b8a995116 Mon Sep 17 00:00:00 2001 From: Elbert Ronnie Date: Sun, 27 Aug 2023 18:20:30 +0530 Subject: [PATCH 2/5] add log module --- src/lib_ccx/lib_ccx.c | 8 + src/rust/Cargo.lock | 15 +- src/rust/build.rs | 1 + src/rust/lib_ccxr/Cargo.lock | 9 + src/rust/lib_ccxr/Cargo.toml | 1 + src/rust/lib_ccxr/src/util/log.rs | 546 ++++++++++++++++++++++++++++ src/rust/lib_ccxr/src/util/mod.rs | 2 + src/rust/src/lib.rs | 1 + src/rust/src/libccxr_exports/mod.rs | 31 ++ 9 files changed, 611 insertions(+), 3 deletions(-) create mode 100644 src/rust/lib_ccxr/src/util/log.rs diff --git a/src/lib_ccx/lib_ccx.c b/src/lib_ccx/lib_ccx.c index 74a0bc88a..48a0ed7a6 100644 --- a/src/lib_ccx/lib_ccx.c +++ b/src/lib_ccx/lib_ccx.c @@ -6,6 +6,10 @@ #include "ccx_decoders_708.h" #include "ccx_decoders_isdb.h" +#ifndef DISABLE_RUST +extern void ccxr_init_basic_logger(); +#endif + struct ccx_common_logging_t ccx_common_logging; static struct ccx_decoders_common_settings_t *init_decoder_setting( struct ccx_s_options *opt) @@ -100,6 +104,10 @@ struct lib_ccx_ctx *init_libraries(struct ccx_s_options *opt) ccx_common_logging.log_ftn = &mprint; ccx_common_logging.gui_ftn = &activity_library_process; +#ifndef DISABLE_RUST + ccxr_init_basic_logger(); +#endif + struct lib_ccx_ctx *ctx = malloc(sizeof(struct lib_ccx_ctx)); if (!ctx) ccx_common_logging.fatal_ftn(EXIT_NOT_ENOUGH_MEMORY, "init_libraries: Not enough memory allocating lib_ccx_ctx context."); diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index d42a787f1..cf9f55bdf 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -52,7 +52,7 @@ version = "0.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr 0.4.0", "clang-sys", "clap", @@ -75,7 +75,7 @@ version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr 0.6.0", "clang-sys", "lazy_static", @@ -97,6 +97,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "camino" version = "1.1.6" @@ -161,7 +167,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim", "textwrap", "unicode-width", @@ -259,6 +265,9 @@ dependencies = [ [[package]] name = "lib_ccxr" version = "0.1.0" +dependencies = [ + "bitflags 2.4.0", +] [[package]] name = "libc" diff --git a/src/rust/build.rs b/src/rust/build.rs index f8ecc04c8..4df835845 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -26,6 +26,7 @@ fn main() { "lib_cc_decode", "cc_subtitle", "ccx_output_format", + "ccx_s_options", ]); #[cfg(feature = "hardsubx_ocr")] diff --git a/src/rust/lib_ccxr/Cargo.lock b/src/rust/lib_ccxr/Cargo.lock index 7532d4515..d7ac327fc 100644 --- a/src/rust/lib_ccxr/Cargo.lock +++ b/src/rust/lib_ccxr/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "lib_ccxr" version = "0.1.0" +dependencies = [ + "bitflags", +] diff --git a/src/rust/lib_ccxr/Cargo.toml b/src/rust/lib_ccxr/Cargo.toml index ca3612505..fb032a7ce 100644 --- a/src/rust/lib_ccxr/Cargo.toml +++ b/src/rust/lib_ccxr/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bitflags = "2.3.1" [features] default = ["enable_sharing", "wtv_debug", "enable_ffmpeg", "debug", "with_libcurl"] diff --git a/src/rust/lib_ccxr/src/util/log.rs b/src/rust/lib_ccxr/src/util/log.rs new file mode 100644 index 000000000..13c7870c4 --- /dev/null +++ b/src/rust/lib_ccxr/src/util/log.rs @@ -0,0 +1,546 @@ +//! Provides primitives for logging functionality +//! +//! The interface of this module is highly inspired by the famous log crate of rust. +//! +//! The first step before using any of the logging functionality is to setup a logger. This can be +//! done by creating a [`CCExtractorLogger`] and calling [`set_logger`] with it. To gain access to +//! the instance of [`CCExtractorLogger`], [`logger`] or [`logger_mut`] can be used. +//! +//! There are 4 types of logging messages based on its importance and severity denoted by their +//! respective macros. +//! - [`fatal!`] +//! - [`error!`] +//! - [`info!`] +//! - [`debug!`] +//! +//! Hex dumps can be logged for debugging by [`hex_dump`] and [`hex_dump_with_start_idx`]. Communication +//! with the GUI is possible through [`send_gui`]. +//! +//! # Conversion Guide +//! +//! | From | To | +//! |------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------| +//! | `EXIT_*`, `CCX_COMMON_EXIT_*` | [`ExitCause`] | +//! | `CCX_MESSAGES_*` | [`OutputTarget`] | +//! | `CCX_DMT_*`, `ccx_debug_message_types` | [`DebugMessageFlag`] | +//! | `temp_debug`, `ccx_options.debug_mask`, `ccx_options.debug_mask_on_debug` | [`DebugMessageMask`] | +//! | `ccx_options.messages_target`, `temp_debug`, `ccx_options.debug_mask`, `ccx_options.debug_mask_on_debug`, `ccx_options.gui_mode_reports` | [`CCExtractorLogger`] | +//! | `fatal`, `ccx_common_logging.fatal_ftn` | [`fatal!`] | +//! | `mprint`, `ccx_common_logging.log_ftn` | [`info!`] | +//! | `dbg_print`, `ccx_common_logging.debug_ftn` | [`debug!`] | +//! | `activity_library_process`, `ccx_common_logging.gui_ftn` | [`send_gui`] | +//! | `dump` | [`hex_dump`] | +//! | `dump` | [`hex_dump_with_start_idx`] | + +use bitflags::bitflags; +use std::fmt::Arguments; +use std::sync::{OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard}; + +static LOGGER: OnceLock> = OnceLock::new(); + +/// The possible targets for logging messages. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum OutputTarget { + Stdout, + Stderr, + Quiet, +} + +bitflags! { + /// A bitflag for the types of a Debug Message. + /// + /// Each debug message can belong to one or more of these types. The + /// constants of this struct can be used as bitflags for one message to + /// belong to more than one type. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct DebugMessageFlag: u16 { + /// Show information related to parsing the container + const PARSE = 0b0000000000000001; + /// Show video stream related information + const VIDEO_STREAM = 0b0000000000000010; + /// Show GOP and PTS timing information + const TIME = 0b0000000000000100; + /// Show lots of debugging output + const VERBOSE = 0b0000000000001000; + /// Show CC-608 decoder debug + const DECODER_608 = 0b0000000000010000; + /// Show CC-708 decoder debug + const DECODER_708 = 0b0000000000100000; + /// Show XDS decoder debug + const DECODER_XDS = 0b0000000001000000; + /// Show Caption blocks with FTS timing + const CB_RAW = 0b0000000010000000; + /// Generic, always displayed even if no debug is selected + const GENERIC_NOTICE = 0b0000000100000000; + /// Show teletext debug + const TELETEXT = 0b0000001000000000; + /// Show Program Allocation Table dump + const PAT = 0b0000010000000000; + /// Show Program Map Table dump + const PMT = 0b0000100000000000; + /// Show Levenshtein distance calculations + const LEVENSHTEIN = 0b0001000000000000; + /// Show DVB debug + const DVB = 0b0010000000000000; + /// Dump defective TS packets + const DUMP_DEF = 0b0100000000000000; + /// Extracted captions sharing service + #[cfg(feature = "enable_sharing")] + const SHARE = 0b1000000000000000; + } +} + +/// All possible causes for crashing the program instantly. Used in `cause` key of [`fatal!`] macro. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ExitCause { + Ok, + Failure, + NoInputFiles, + TooManyInputFiles, + IncompatibleParameters, + UnableToDetermineFileSize, + MalformedParameter, + ReadError, + NoCaptions, + WithHelp, + NotClassified, + ErrorInCapitalizationFile, + BufferFull, + MissingAsfHeader, + MissingRcwtHeader, + + FileCreationFailed, + Unsupported, + NotEnoughMemory, + Bug, +} + +/// A message to be sent to GUI for XDS. Used in [`send_gui`]. +pub enum GuiXdsMessage<'a> { + ProgramName(&'a str), + ProgramIdNr { + minute: u8, + hour: u8, + date: u8, + month: u8, + }, + ProgramDescription { + line_num: i32, + desc: &'a str, + }, + CallLetters(&'a str), +} + +/// A mask to filter the debug messages based on its type specified by [`DebugMessageFlag`]. +/// +/// This operates on one of the two modes: Normal Mode and Debug Mode. The mask used when in Debug Mode is a superset +/// of the mask used when in Normal Mode. One can switch between the two modes by [`DebugMessageMask::set_debug_mode`]. +#[derive(Debug)] +pub struct DebugMessageMask { + debug_mode: bool, + mask_on_normal: DebugMessageFlag, + mask_on_debug: DebugMessageFlag, +} + +/// A global logger used throughout CCExtractor and stores the settings related to logging. +/// +/// A global logger can be setup up initially using [`set_logger`]. Use the following convenience +/// macros for logging: [`fatal!`], [`error!`], [`info!`] and [`debug!`]. +#[derive(Debug)] +pub struct CCExtractorLogger { + target: OutputTarget, + debug_mask: DebugMessageMask, + gui_mode: bool, +} + +impl DebugMessageMask { + /// Creates a new [`DebugMessageFlag`] given a mask to be used for Normal Mode and an additional mask to be + /// used in Debug Mode. + /// + /// Note that while in Debug Mode, the mask for Normal Mode will still be valid. + /// `extra_mask_on_debug` only specifies additional flags to be set on Debug Mode. + pub const fn new( + mask_on_normal: DebugMessageFlag, + extra_mask_on_debug: DebugMessageFlag, + ) -> DebugMessageMask { + DebugMessageMask { + debug_mode: false, + mask_on_normal, + mask_on_debug: extra_mask_on_debug.union(mask_on_normal), + } + } + + /// Set the mode to Normal or Debug Mode based on `false` or `true` respectively. + pub fn set_debug_mode(&mut self, mode: bool) { + self.debug_mode = mode; + } + + /// Check if the current mode is set to Debug Mode. + pub fn debug_mode(&self) -> bool { + self.debug_mode + } + + /// Return the mask according to its mode. + pub fn mask(&self) -> DebugMessageFlag { + if self.debug_mode { + self.mask_on_debug + } else { + self.mask_on_normal + } + } +} + +impl ExitCause { + /// Returns the exit code associated with the cause of the error. + /// + /// The GUI depends on these exit codes. + /// Exit code of 0 means OK as usual. + /// Exit code below 100 means display whatever was output to stderr as a warning. + /// Exit code above or equal to 100 means display whatever was output to stdout as an error. + pub fn exit_code(&self) -> i32 { + match self { + ExitCause::Ok => 0, + ExitCause::Failure => 1, + ExitCause::NoInputFiles => 2, + ExitCause::TooManyInputFiles => 3, + ExitCause::IncompatibleParameters => 4, + ExitCause::UnableToDetermineFileSize => 6, + ExitCause::MalformedParameter => 7, + ExitCause::ReadError => 8, + ExitCause::NoCaptions => 10, + ExitCause::WithHelp => 11, + ExitCause::NotClassified => 300, + ExitCause::ErrorInCapitalizationFile => 501, + ExitCause::BufferFull => 502, + ExitCause::MissingAsfHeader => 1001, + ExitCause::MissingRcwtHeader => 1002, + + ExitCause::FileCreationFailed => 5, + ExitCause::Unsupported => 9, + ExitCause::NotEnoughMemory => 500, + ExitCause::Bug => 1000, + } + } +} + +impl<'a> CCExtractorLogger { + /// Returns a new instance of CCExtractorLogger with the provided settings. + /// + /// `gui_mode` is used to determine if the log massages are intercepted by a GUI. + /// `target` specifies the location for printing the log messages. + /// `debug_mask` is used to filter debug messages based on its type. + pub const fn new( + target: OutputTarget, + debug_mask: DebugMessageMask, + gui_mode: bool, + ) -> CCExtractorLogger { + CCExtractorLogger { + target, + debug_mask, + gui_mode, + } + } + + /// Set the mode to Normal or Debug Mode based on `false` or `true` respectively for the + /// underlying [`DebugMessageMask`]. + /// + /// This method switches the mask used for filtering debug messages. + /// Similar to [`DebugMessageMask::set_debug_mode`]. + pub fn set_debug_mode(&mut self, mode: bool) { + self.debug_mask.set_debug_mode(mode) + } + + /// Check if the current mode is set to Debug Mode. + /// + /// Similar to [`DebugMessageMask::debug_mode`]. + pub fn debug_mode(&self) -> bool { + self.debug_mask.debug_mode() + } + + /// Returns the currently set target for logging messages. + pub fn target(&self) -> OutputTarget { + self.target + } + + /// Check if the messages are intercepted by GUI. + pub fn is_gui_mode(&self) -> bool { + self.gui_mode + } + + fn print(&self, args: &Arguments<'a>) { + match &self.target { + OutputTarget::Stdout => print!("{}", args), + OutputTarget::Stderr => eprint!("{}", args), + OutputTarget::Quiet => {} + } + } + + /// Log a fatal error message. Use [`fatal!`] instead. + /// + /// Used for logging errors dangerous enough to crash the program instantly. + pub fn log_fatal(&self, exit_cause: ExitCause, args: &Arguments<'a>) -> ! { + self.log_error(args); + println!(); // TODO: print end message + std::process::exit(exit_cause.exit_code()) + } + + /// Log an error message. Use [`error!`] instead. + /// + /// Used for logging general errors occuring in the program. + pub fn log_error(&self, args: &Arguments<'a>) { + if self.gui_mode { + eprint!("###MESSAGE#") + } else { + eprint!("\rError: ") + } + + eprintln!("{}", args); + } + + /// Log an informational message. Use [`info!`] instead. + /// + /// Used for logging extra information about the execution of the program. + pub fn log_info(&self, args: &Arguments<'a>) { + // TODO: call activity_header + self.print(&format_args!("{}", args)); + } + + /// Log a debug message. Use [`debug!`] instead. + /// + /// Used for logging debug messages throughout the program. + pub fn log_debug(&self, message_type: DebugMessageFlag, args: &Arguments<'a>) { + if self.debug_mask.mask().intersects(message_type) { + self.print(&format_args!("{}", args)); + } + } + + /// Send a message to GUI. Use [`send_gui`] instead. + /// + /// Used for sending information related to XDS to the GUI. + pub fn send_gui(&self, _message_type: GuiXdsMessage) { + todo!() + } + + /// Log a hex dump which is helpful for debugging purposes. + /// Use [`hex_dump`] or [`hex_dump_with_start_idx`] instead. + /// + /// Setting `clear_high_bit` to true will ignore the highest bit whien displaying the + /// characters. This makes visual CC inspection easier since the highest bit is usually used + /// as a parity bit. + /// + /// The output will contain byte numbers which can be made to start from any number using + /// `start_idx`. This is usually `0`. + pub fn log_hex_dump( + &self, + message_type: DebugMessageFlag, + data: &[u8], + clear_high_bit: bool, + start_idx: usize, + ) { + if self.debug_mask.mask().intersects(message_type) { + let chunked_data = data.chunks(16); + + for (id, chunk) in chunked_data.enumerate() { + self.print(&format_args!("{:05} | ", id * 16 + start_idx)); + for x in chunk { + self.print(&format_args!("{:02X} ", x)); + } + + for _ in 0..(16 - chunk.len()) { + self.print(&format_args!(" ")); + } + + self.print(&format_args!(" | ")); + + for x in chunk { + let c = if x >= &b' ' { + // 0x7F < remove high bit, convenient for visual CC inspection + x & if clear_high_bit { 0x7F } else { 0xFF } + } else { + b' ' + }; + + self.print(&format_args!("{}", c as char)); + } + + self.print(&format_args!("\n")); + } + } + } +} + +/// Setup the global logger. +/// +/// This function can only be called once throught the execution of program. The logger can then be +/// accessed by [`logger`] and [`logger_mut`]. +pub fn set_logger(logger: CCExtractorLogger) -> Result<(), CCExtractorLogger> { + LOGGER + .set(logger.into()) + .map_err(|x| x.into_inner().unwrap()) +} + +/// Get an immutable instance of the global logger. +/// +/// This function will return [`None`] if the logger is not setup initially by [`set_logger`] or if +/// the underlying RwLock fails to generate a read lock. +/// +/// Use [`logger_mut`] to get a mutable instance. +pub fn logger() -> Option> { + LOGGER.get()?.read().ok() +} + +/// Get a mutable instance of the global logger. +/// +/// This function will return [`None`] if the logger is not setup initially by [`set_logger`] or if +/// the underlying RwLock fails to generate a write lock. +/// +/// Use [`logger`] to get an immutable instance. +pub fn logger_mut() -> Option> { + LOGGER.get()?.write().ok() +} + +/// Log a fatal error message. +/// +/// Used for logging errors dangerous enough to crash the program instantly. This macro does not +/// return (i.e. it returns `!`). A logger needs to be setup initially by [`set_logger`]. +/// +/// # Usage +/// This macro requires an [`ExitCause`] which provides the appropriate exit codes for shutting +/// down program. This is provided using a key called `cause` which comes before the `;`. After +/// `;`, the arguments works the same as a [`println!`] macro. +/// +/// # Examples +/// ```no_run +/// # use lib_ccxr::util::log::*; +/// # let actual = 2; +/// # let required = 1; +/// fatal!( +/// cause = ExitCause::TooManyInputFiles; +/// "{} input files were provided but only {} were needed", actual, required +/// ); +/// ``` +#[macro_export] +macro_rules! fatal { + (cause = $exit_cause:expr; $($args:expr),*) => { + $crate::util::log::logger().expect("Logger is not yet initialized") + .log_fatal($exit_cause, &format_args!($($args),*)) + }; +} + +/// Log an error message. +/// +/// Used for logging general errors occuring in the program. A logger needs to be setup +/// initially by [`set_logger`]. +/// +/// # Usage +/// The arguments works the same as a [`println!`] macro. +/// +/// # Examples +/// ```no_run +/// # use lib_ccxr::util::log::*; +/// # let missing_blocks = 2; +/// error!("missing {} additional blocks", missing_blocks); +/// ``` +#[macro_export] +macro_rules! error { + ($($args:expr),*) => { + $crate::util::log::logger().expect("Logger is not yet initialized") + .log_error(&format_args!($($args),*)) + } +} + +/// Log an informational message. +/// +/// Used for logging extra information about the execution of the program. A logger needs to be +/// setup initially by [`set_logger`]. +/// +/// # Usage +/// The arguments works the same as a [`println!`] macro. +/// +/// # Examples +/// ```no_run +/// # use lib_ccxr::util::log::*; +/// info!("Processing the header section"); +/// ``` +#[macro_export] +macro_rules! info { + ($($args:expr),*) => { + $crate::util::log::logger().expect("Logger is not yet initialized") + .log_info(&format_args!($($args),*)) + }; +} + +/// Log a debug message. +/// +/// Used for logging debug messages throughout the program. A logger needs to be setup initially +/// by [`set_logger`]. +/// +/// # Usage +/// This macro requires an [`DebugMessageFlag`] which represents the type of debug message. It is +/// used for filtering the messages. This is provided using a key called `msg_type` which comes +/// before the `;`. After `;`, the arguments works the same as a [`println!`] macro. +/// +/// # Examples +/// ```no_run +/// # use lib_ccxr::util::log::*; +/// # let byte1 = 23u8; +/// # let byte2 = 45u8; +/// debug!( +/// msg_type = DebugMessageFlag::DECODER_708; +/// "Packet Start with contents {} {}", byte1, byte2 +/// ); +/// ``` +#[macro_export] +macro_rules! debug { + (msg_type = $msg_flag:expr; $($args:expr),*) => { + $crate::util::log::logger().expect("Logger is not yet initialized") + .log_debug($msg_flag, &format_args!($($args),*)) + }; +} + +pub use debug; +pub use error; +pub use fatal; +pub use info; + +/// Log a hex dump which is helpful for debugging purposes. +/// +/// Setting `clear_high_bit` to true will ignore the highest bit whien displaying the +/// characters. This makes visual CC inspection easier since the highest bit is usually used +/// as a parity bit. +/// +/// The byte numbers start from `0` by default. Use [`hex_dump_with_start_idx`] if a +/// different starting index is required. +pub fn hex_dump(message_type: DebugMessageFlag, data: &[u8], clear_high_bit: bool) { + logger() + .expect("Logger is not yet initialized") + .log_hex_dump(message_type, data, clear_high_bit, 0) +} + +/// Log a hex dump which is helpful for debugging purposes. +/// +/// Setting `clear_high_bit` to true will ignore the highest bit whien displaying the +/// characters. This makes visual CC inspection easier since the highest bit is usually used +/// as a parity bit. +/// +/// The output will contain byte numbers which can be made to start from any number using +/// `start_idx`. This is usually `0`. +pub fn hex_dump_with_start_idx( + message_type: DebugMessageFlag, + data: &[u8], + clear_high_bit: bool, + start_idx: usize, +) { + logger() + .expect("Logger is not yet initialized") + .log_hex_dump(message_type, data, clear_high_bit, start_idx) +} + +/// Send a message to GUI. +/// +/// Used for sending information related to XDS to the GUI. +pub fn send_gui(message: GuiXdsMessage) { + logger() + .expect("Logger is not yet initialized") + .send_gui(message) +} diff --git a/src/rust/lib_ccxr/src/util/mod.rs b/src/rust/lib_ccxr/src/util/mod.rs index daf5935ac..504f17e26 100644 --- a/src/rust/lib_ccxr/src/util/mod.rs +++ b/src/rust/lib_ccxr/src/util/mod.rs @@ -1 +1,3 @@ //! Provides basic utilities used throughout the program. + +pub mod log; diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 1fec8fa5e..f2d7ba171 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -33,6 +33,7 @@ extern "C" { static mut cb_708: c_int; static mut cb_field1: c_int; static mut cb_field2: c_int; + static mut ccx_options: ccx_s_options; } /// Initialize env logger with custom format, using stdout as target diff --git a/src/rust/src/libccxr_exports/mod.rs b/src/rust/src/libccxr_exports/mod.rs index e365e0fb2..b48d76984 100644 --- a/src/rust/src/libccxr_exports/mod.rs +++ b/src/rust/src/libccxr_exports/mod.rs @@ -1 +1,32 @@ //! Provides C-FFI functions that are direct equivalent of functions available in C. + +use crate::ccx_options; +use lib_ccxr::util::log::*; +use std::convert::TryInto; + +/// Initializes the logger at the rust side. +/// +/// # Safety +/// +/// `ccx_options` in C must initialized properly before calling this function. +#[no_mangle] +pub unsafe extern "C" fn ccxr_init_basic_logger() { + let debug_mask = + DebugMessageFlag::from_bits(ccx_options.debug_mask.try_into().unwrap()).unwrap(); + let debug_mask_on_debug = + DebugMessageFlag::from_bits(ccx_options.debug_mask_on_debug.try_into().unwrap()).unwrap(); + let mask = DebugMessageMask::new(debug_mask, debug_mask_on_debug); + let gui_mode_reports = ccx_options.gui_mode_reports != 0; + let messages_target = match ccx_options.messages_target { + 0 => OutputTarget::Stdout, + 1 => OutputTarget::Stderr, + 2 => OutputTarget::Quiet, + _ => panic!("incorrect value for messages_target"), + }; + set_logger(CCExtractorLogger::new( + messages_target, + mask, + gui_mode_reports, + )) + .unwrap(); +} From f4d4d7c9927218d16982d577948fa33e037a9faf Mon Sep 17 00:00:00 2001 From: Elbert Ronnie Date: Sat, 26 Aug 2023 12:58:49 +0530 Subject: [PATCH 3/5] add common constants module --- src/rust/lib_ccxr/src/common/constants.rs | 415 ++++++++++++++++++++++ src/rust/lib_ccxr/src/common/mod.rs | 21 ++ src/rust/lib_ccxr/src/lib.rs | 1 + 3 files changed, 437 insertions(+) create mode 100644 src/rust/lib_ccxr/src/common/constants.rs create mode 100644 src/rust/lib_ccxr/src/common/mod.rs diff --git a/src/rust/lib_ccxr/src/common/constants.rs b/src/rust/lib_ccxr/src/common/constants.rs new file mode 100644 index 000000000..b310c95b9 --- /dev/null +++ b/src/rust/lib_ccxr/src/common/constants.rs @@ -0,0 +1,415 @@ +use std::ffi::OsStr; + +pub const DTVCC_MAX_SERVICES: usize = 63; + +/// An enum of all the available formats for the subtitle output. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum OutputFormat { + Raw, + Srt, + Sami, + Transcript, + Rcwt, + Null, + SmpteTt, + SpuPng, + DvdRaw, // See -d at http://www.theneitherworld.com/mcpoodle/SCC_TOOLS/DOCS/SCC_TOOLS.HTML#CCExtract + WebVtt, + SimpleXml, + G608, + Curl, + Ssa, + Mcc, + Scc, + Ccd, +} + +// AVC NAL types +pub enum AvcNalType { + Unspecified0 = 0, + CodedSliceNonIdrPicture1 = 1, + CodedSlicePartitionA = 2, + CodedSlicePartitionB = 3, + CodedSlicePartitionC = 4, + CodedSliceIdrPicture = 5, + Sei = 6, + SequenceParameterSet7 = 7, + PictureParameterSet = 8, + AccessUnitDelimiter9 = 9, + EndOfSequence = 10, + EndOfStream = 11, + FillerData = 12, + SequenceParameterSetExtension = 13, + PrefixNalUnit = 14, + SubsetSequenceParameterSet = 15, + Reserved16 = 16, + Reserved17 = 17, + Reserved18 = 18, + CodedSliceAuxiliaryPicture = 19, + CodedSliceExtension = 20, + Reserved21 = 21, + Reserved22 = 22, + Reserved23 = 23, + Unspecified24 = 24, + Unspecified25 = 25, + Unspecified26 = 26, + Unspecified27 = 27, + Unspecified28 = 28, + Unspecified29 = 29, + Unspecified30 = 30, + Unspecified31 = 31, +} + +// MPEG-2 TS stream types +pub enum StreamType { + Unknownstream = 0, + /* + The later constants are defined by MPEG-TS standard + Explore at: https://exiftool.org/TagNames/M2TS.html + */ + VideoMpeg1 = 0x01, + VideoMpeg2 = 0x02, + AudioMpeg1 = 0x03, + AudioMpeg2 = 0x04, + PrivateTableMpeg2 = 0x05, + PrivateMpeg2 = 0x06, + MhegPackets = 0x07, + Mpeg2AnnexADsmCc = 0x08, + ItuTH222_1 = 0x09, + IsoIec13818_6TypeA = 0x0a, + IsoIec13818_6TypeB = 0x0b, + IsoIec13818_6TypeC = 0x0c, + IsoIec13818_6TypeD = 0x0d, + AudioAac = 0x0f, + VideoMpeg4 = 0x10, + VideoH264 = 0x1b, + PrivateUserMpeg2 = 0x80, + AudioAc3 = 0x81, + AudioHdmvDts = 0x82, + AudioDts = 0x8a, +} + +pub enum MpegDescriptor { + /* + The later constants are defined by ETSI EN 300 468 standard + Explore at: https://www.etsi.org/deliver/etsi_en/300400_300499/300468/01.11.01_60/en_300468v011101p.pdf + */ + Registration = 0x05, + DataStreamAlignment = 0x06, + Iso639Language = 0x0a, + VbiDataDescriptor = 0x45, + VbiTeletextDescriptor = 0x46, + TeletextDescriptor = 0x56, + DvbSubtitle = 0x59, + /* User defined */ + CaptionService = 0x86, + DataComp = 0xfd, +} + +pub enum DataSource { + File, + Stdin, + Network, + Tcp, +} + +pub enum StreamMode { + ElementaryOrNotFound = 0, + Transport = 1, + Program = 2, + Asf = 3, + McpoodlesRaw = 4, + Rcwt = 5, // raw captions with time, not used yet. + Myth = 6, // use the myth loop + Mp4 = 7, // mp4, iso- + #[cfg(feature = "wtv_debug")] + HexDump = 8, // hexadecimal dump generated by wtvccdump + Wtv = 9, + #[cfg(feature = "enable_ffmpeg")] + Ffmpeg = 10, + Gxf = 11, + Mkv = 12, + Mxf = 13, + Autodetect = 16, +} + +pub enum BufferdataType { + Unknown, + Pes, + Raw, + H264, + Hauppage, + Teletext, + PrivateMpeg2Cc, + DvbSubtitle, + IsdbSubtitle, + /* BUffer where cc data contain 3 byte cc_valid ccdata 1 ccdata 2 */ + RawType, + DvdSubtitle, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum FrameType { + ResetOrUnknown, + IFrame, + PFrame, + BFrame, + DFrame, +} + +pub enum Codec { + Teletext, + Dvb, + IsdbCc, + AtscCc, +} + +pub enum SelectCodec { + All, + Some(Codec), + None, +} + +/// Caption Distribution Packet +pub enum CdpSectionType { + /* + The later constants are defined by SMPTE ST 334 + Purchase for 80$ at: https://ieeexplore.ieee.org/document/8255806 + */ + Data = 0x72, + SvcInfo = 0x73, + Footer = 0x74, +} + +pub enum Language { + Und, // Undefined + Eng, + Afr, + Amh, + Ara, + Asm, + Aze, + Bel, + Ben, + Bod, + Bos, + Bul, + Cat, + Ceb, + Ces, + Chs, + Chi, + Chr, + Cym, + Dan, + Deu, + Dzo, + Ell, + Enm, + Epo, + Equ, + Est, + Eus, + Fas, + Fin, + Fra, + Frk, + Frm, + Gle, + Glg, + Grc, + Guj, + Hat, + Heb, + Hin, + Hrv, + Hun, + Iku, + Ind, + Isl, + Ita, + Jav, + Jpn, + Kan, + Kat, + Kaz, + Khm, + Kir, + Kor, + Kur, + Lao, + Lat, + Lav, + Lit, + Mal, + Mar, + Mkd, + Mlt, + Msa, + Mya, + Nep, + Nld, + Nor, + Ori, + Osd, + Pan, + Pol, + Por, + Pus, + Ron, + Rus, + San, + Sin, + Slk, + Slv, + Spa, + Sqi, + Srp, + Swa, + Swe, + Syr, + Tam, + Tel, + Tgk, + Tgl, + Tha, + Tir, + Tur, + Uig, + Ukr, + Urd, + Uzb, + Vie, + Yid, +} + +impl OutputFormat { + /// Returns the file extension for the output format if it is a file based format. + pub fn file_extension(&self) -> Option<&OsStr> { + match self { + OutputFormat::Raw => Some(OsStr::new(".raw")), + OutputFormat::Srt => Some(OsStr::new(".srt")), + OutputFormat::Sami => Some(OsStr::new(".smi")), + OutputFormat::Transcript => Some(OsStr::new(".txt")), + OutputFormat::Rcwt => Some(OsStr::new(".bin")), + OutputFormat::Null => None, + OutputFormat::SmpteTt => Some(OsStr::new(".ttml")), + OutputFormat::SpuPng => Some(OsStr::new(".xml")), + OutputFormat::DvdRaw => Some(OsStr::new(".dvdraw")), + OutputFormat::WebVtt => Some(OsStr::new(".vtt")), + OutputFormat::SimpleXml => Some(OsStr::new(".xml")), + OutputFormat::G608 => Some(OsStr::new(".g608")), + OutputFormat::Curl => None, + OutputFormat::Ssa => Some(OsStr::new(".ass")), + OutputFormat::Mcc => Some(OsStr::new(".mcc")), + OutputFormat::Scc => Some(OsStr::new(".scc")), + OutputFormat::Ccd => Some(OsStr::new(".ccd")), + } + } +} + +impl Language { + pub fn to_str(&self) -> &'static str { + match self { + Language::Und => "und", // Undefined + Language::Eng => "eng", + Language::Afr => "afr", + Language::Amh => "amh", + Language::Ara => "ara", + Language::Asm => "asm", + Language::Aze => "aze", + Language::Bel => "bel", + Language::Ben => "ben", + Language::Bod => "bod", + Language::Bos => "bos", + Language::Bul => "bul", + Language::Cat => "cat", + Language::Ceb => "ceb", + Language::Ces => "ces", + Language::Chs => "chs", + Language::Chi => "chi", + Language::Chr => "chr", + Language::Cym => "cym", + Language::Dan => "dan", + Language::Deu => "deu", + Language::Dzo => "dzo", + Language::Ell => "ell", + Language::Enm => "enm", + Language::Epo => "epo", + Language::Equ => "equ", + Language::Est => "est", + Language::Eus => "eus", + Language::Fas => "fas", + Language::Fin => "fin", + Language::Fra => "fra", + Language::Frk => "frk", + Language::Frm => "frm", + Language::Gle => "gle", + Language::Glg => "glg", + Language::Grc => "grc", + Language::Guj => "guj", + Language::Hat => "hat", + Language::Heb => "heb", + Language::Hin => "hin", + Language::Hrv => "hrv", + Language::Hun => "hun", + Language::Iku => "iku", + Language::Ind => "ind", + Language::Isl => "isl", + Language::Ita => "ita", + Language::Jav => "jav", + Language::Jpn => "jpn", + Language::Kan => "kan", + Language::Kat => "kat", + Language::Kaz => "kaz", + Language::Khm => "khm", + Language::Kir => "kir", + Language::Kor => "kor", + Language::Kur => "kur", + Language::Lao => "lao", + Language::Lat => "lat", + Language::Lav => "lav", + Language::Lit => "lit", + Language::Mal => "mal", + Language::Mar => "mar", + Language::Mkd => "mkd", + Language::Mlt => "mlt", + Language::Msa => "msa", + Language::Mya => "mya", + Language::Nep => "nep", + Language::Nld => "nld", + Language::Nor => "nor", + Language::Ori => "ori", + Language::Osd => "osd", + Language::Pan => "pan", + Language::Pol => "pol", + Language::Por => "por", + Language::Pus => "pus", + Language::Ron => "ron", + Language::Rus => "rus", + Language::San => "san", + Language::Sin => "sin", + Language::Slk => "slk", + Language::Slv => "slv", + Language::Spa => "spa", + Language::Sqi => "sqi", + Language::Srp => "srp", + Language::Swa => "swa", + Language::Swe => "swe", + Language::Syr => "syr", + Language::Tam => "tam", + Language::Tel => "tel", + Language::Tgk => "tgk", + Language::Tgl => "tgl", + Language::Tha => "tha", + Language::Tir => "tir", + Language::Tur => "tur", + Language::Uig => "uig", + Language::Ukr => "ukr", + Language::Urd => "urd", + Language::Uzb => "uzb", + Language::Vie => "vie", + Language::Yid => "yid", + } + } +} diff --git a/src/rust/lib_ccxr/src/common/mod.rs b/src/rust/lib_ccxr/src/common/mod.rs new file mode 100644 index 000000000..502820224 --- /dev/null +++ b/src/rust/lib_ccxr/src/common/mod.rs @@ -0,0 +1,21 @@ +//! Provides common types throughout the codebase. +//! +//! # Conversion Guide +//! +//! | From | To | +//! |-------------------------|----------------------------| +//! | `ccx_output_format` | [`OutputFormat`] | +//! | `ccx_avc_nal_types` | [`AvcNalType`] | +//! | `ccx_stream_type` | [`StreamType`] | +//! | `ccx_mpeg_descriptor` | [`MpegDescriptor`] | +//! | `ccx_datasource` | [`DataSource`] | +//! | `ccx_stream_mode_enum` | [`StreamMode`] | +//! | `ccx_bufferdata_type` | [`BufferdataType`] | +//! | `ccx_frame_type` | [`FrameType`] | +//! | `ccx_code_type` | [`Codec`], [`SelectCodec`] | +//! | `cdp_section_type` | [`CdpSectionType`] | +//! | `language[NB_LANGUAGE]` | [`Language`] | + +mod constants; + +pub use constants::*; diff --git a/src/rust/lib_ccxr/src/lib.rs b/src/rust/lib_ccxr/src/lib.rs index 812d1edf2..45ee8e79c 100644 --- a/src/rust/lib_ccxr/src/lib.rs +++ b/src/rust/lib_ccxr/src/lib.rs @@ -1 +1,2 @@ +pub mod common; pub mod util; From 49cdd1fd917f86ac59027139a8ad53eb8559bcc8 Mon Sep 17 00:00:00 2001 From: Elbert Ronnie Date: Sun, 27 Aug 2023 18:38:05 +0530 Subject: [PATCH 4/5] add time units module --- src/lib_ccx/utility.c | 33 + src/rust/Cargo.lock | 79 +++ src/rust/build.rs | 8 +- src/rust/lib_ccxr/Cargo.lock | 165 +++++ src/rust/lib_ccxr/Cargo.toml | 3 + src/rust/lib_ccxr/src/util/mod.rs | 2 + .../lib_ccxr/src/util/time/c_functions.rs | 35 + src/rust/lib_ccxr/src/util/time/mod.rs | 27 + src/rust/lib_ccxr/src/util/time/units.rs | 634 ++++++++++++++++++ src/rust/src/libccxr_exports/mod.rs | 4 + src/rust/src/libccxr_exports/time.rs | 107 +++ 11 files changed, 1096 insertions(+), 1 deletion(-) create mode 100644 src/rust/lib_ccxr/src/util/time/c_functions.rs create mode 100644 src/rust/lib_ccxr/src/util/time/mod.rs create mode 100644 src/rust/lib_ccxr/src/util/time/units.rs create mode 100644 src/rust/src/libccxr_exports/time.rs diff --git a/src/lib_ccx/utility.c b/src/lib_ccx/utility.c index cb3cb6152..a950008c1 100644 --- a/src/lib_ccx/utility.c +++ b/src/lib_ccx/utility.c @@ -9,6 +9,13 @@ int temp_debug = 0; // This is a convenience variable used to enable/disable debug on variable conditions. Find references to understand. volatile sig_atomic_t change_filename_requested = 0; +#ifndef DISABLE_RUST +extern void ccxr_timestamp_to_srttime(uint64_t timestamp, char *buffer); +extern void ccxr_timestamp_to_vtttime(uint64_t timestamp, char *buffer); +extern void ccxr_millis_to_date(uint64_t timestamp, char *buffer, enum ccx_output_date_format date_format, char millis_separator); +extern int ccxr_stringztoms(const char *s, struct ccx_boundary_time *bt); +#endif + static uint32_t crc32_table[] = { 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, @@ -86,6 +93,10 @@ int verify_crc32(uint8_t *buf, int len) int stringztoms(const char *s, struct ccx_boundary_time *bt) { +#ifndef DISABLE_RUST + return ccxr_stringztoms(s, bt); +#endif + unsigned ss = 0, mm = 0, hh = 0; int value = -1; int colons = 0; @@ -130,6 +141,10 @@ int stringztoms(const char *s, struct ccx_boundary_time *bt) } void timestamp_to_srttime(uint64_t timestamp, char *buffer) { +#ifndef DISABLE_RUST + return ccxr_timestamp_to_srttime(timestamp, buffer); +#endif + uint64_t p = timestamp; uint8_t h = (uint8_t)(p / 3600000); uint8_t m = (uint8_t)(p / 60000 - 60 * h); @@ -139,6 +154,10 @@ void timestamp_to_srttime(uint64_t timestamp, char *buffer) } void timestamp_to_vtttime(uint64_t timestamp, char *buffer) { +#ifndef DISABLE_RUST + return ccxr_timestamp_to_vtttime(timestamp, buffer); +#endif + uint64_t p = timestamp; uint8_t h = (uint8_t)(p / 3600000); uint8_t m = (uint8_t)(p / 60000 - 60 * h); @@ -193,6 +212,20 @@ int levenshtein_dist_char(const char *s1, const char *s2, unsigned s1len, unsign void millis_to_date(uint64_t timestamp, char *buffer, enum ccx_output_date_format date_format, char millis_separator) { +#ifndef DISABLE_RUST + switch (date_format) + { + case ODF_NONE: + case ODF_HHMMSS: + case ODF_HHMMSSMS: + case ODF_SECONDS: + case ODF_DATE: + return ccxr_millis_to_date(timestamp, buffer, date_format, millis_separator); + default: + fatal(CCX_COMMON_EXIT_BUG_BUG, "Invalid value for date_format in millis_to_date()\n"); + } +#endif + time_t secs; unsigned int millis; char c_temp[80]; diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index d42a787f1..4e870dbb7 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -168,6 +168,31 @@ dependencies = [ "vec_map", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + [[package]] name = "dyn_buf" version = "0.1.0" @@ -233,6 +258,12 @@ dependencies = [ "libc", ] +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + [[package]] name = "lazy_static" version = "1.4.0" @@ -259,6 +290,11 @@ dependencies = [ [[package]] name = "lib_ccxr" version = "0.1.0" +dependencies = [ + "derive_more", + "thiserror", + "time", +] [[package]] name = "libc" @@ -493,6 +529,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rusty_ffmpeg" version = "0.13.1+ffmpeg.6.0" @@ -507,6 +552,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + [[package]] name = "serde" version = "1.0.188" @@ -617,6 +668,34 @@ dependencies = [ "syn 2.0.29", ] +[[package]] +name = "time" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +dependencies = [ + "deranged", + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +dependencies = [ + "time-core", +] + [[package]] name = "toml" version = "0.5.11" diff --git a/src/rust/build.rs b/src/rust/build.rs index f8ecc04c8..3885248c2 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -26,6 +26,8 @@ fn main() { "lib_cc_decode", "cc_subtitle", "ccx_output_format", + "ccx_boundary_time", + "gop_time_code", ]); #[cfg(feature = "hardsubx_ocr")] @@ -71,4 +73,8 @@ fn main() { .expect("Couldn't write bindings!"); } -const RUSTIFIED_ENUMS: &[&str] = &["dtvcc_(window|pen)_.*", "ccx_output_format"]; +const RUSTIFIED_ENUMS: &[&str] = &[ + "dtvcc_(window|pen)_.*", + "ccx_output_format", + "ccx_output_date_format", +]; diff --git a/src/rust/lib_ccxr/Cargo.lock b/src/rust/lib_ccxr/Cargo.lock index 7532d4515..5cea85df6 100644 --- a/src/rust/lib_ccxr/Cargo.lock +++ b/src/rust/lib_ccxr/Cargo.lock @@ -2,6 +2,171 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + [[package]] name = "lib_ccxr" version = "0.1.0" +dependencies = [ + "derive_more", + "thiserror", + "time", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "time" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +dependencies = [ + "deranged", + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +dependencies = [ + "time-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" diff --git a/src/rust/lib_ccxr/Cargo.toml b/src/rust/lib_ccxr/Cargo.toml index ca3612505..35d1ce860 100644 --- a/src/rust/lib_ccxr/Cargo.toml +++ b/src/rust/lib_ccxr/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +thiserror = "1.0.39" +time = { version = "0.3.27", features = ["macros", "formatting"] } +derive_more = "0.99.17" [features] default = ["enable_sharing", "wtv_debug", "enable_ffmpeg", "debug", "with_libcurl"] diff --git a/src/rust/lib_ccxr/src/util/mod.rs b/src/rust/lib_ccxr/src/util/mod.rs index daf5935ac..1f40764f9 100644 --- a/src/rust/lib_ccxr/src/util/mod.rs +++ b/src/rust/lib_ccxr/src/util/mod.rs @@ -1 +1,3 @@ //! Provides basic utilities used throughout the program. + +pub mod time; diff --git a/src/rust/lib_ccxr/src/util/time/c_functions.rs b/src/rust/lib_ccxr/src/util/time/c_functions.rs new file mode 100644 index 000000000..65d837e35 --- /dev/null +++ b/src/rust/lib_ccxr/src/util/time/c_functions.rs @@ -0,0 +1,35 @@ +//! Provides Rust equivalent for functions in C. Uses Rust-native types as input and output. + +use super::*; + +/// Rust equivalent for `timestamp_to_srttime` function in C. Uses Rust-native types as input and +/// output. +pub fn timestamp_to_srttime( + timestamp: Timestamp, + buffer: &mut String, +) -> Result<(), TimestampError> { + timestamp.write_srt_time(buffer) +} + +/// Rust equivalent for `timestamp_to_vtttime` function in C. Uses Rust-native types as input and +/// output. +pub fn timestamp_to_vtttime( + timestamp: Timestamp, + buffer: &mut String, +) -> Result<(), TimestampError> { + timestamp.write_vtt_time(buffer) +} + +/// Rust equivalent for `millis_to_date` function in C. Uses Rust-native types as input and output. +pub fn millis_to_date( + timestamp: Timestamp, + buffer: &mut String, + date_format: TimestampFormat, +) -> Result<(), TimestampError> { + timestamp.write_formatted_time(buffer, date_format) +} + +/// Rust equivalent for `stringztoms` function in C. Uses Rust-native types as input and output. +pub fn stringztoms(s: &str) -> Option { + Timestamp::parse_optional_hhmmss_from_str(s).ok() +} diff --git a/src/rust/lib_ccxr/src/util/time/mod.rs b/src/rust/lib_ccxr/src/util/time/mod.rs new file mode 100644 index 000000000..64c67f4d5 --- /dev/null +++ b/src/rust/lib_ccxr/src/util/time/mod.rs @@ -0,0 +1,27 @@ +//! Provide types for storing time in different formats +//! +//! Time can be represented in one of following formats: +//! - [`Timestamp`] as number of milliseconds +//! - [`MpegClockTick`] as number of clock ticks (as defined in the MPEG standard) +//! - [`FrameCount`] as number of frames +//! - [`GopTimeCode`] as a GOP time code (as defined in the MPEG standard) +//! +//! # Conversion Guide +//! +//! | From | To | +//! |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------| +//! | `ccx_boundary_time` | [`Option`](Timestamp) | +//! | any fts | [`Timestamp`] | +//! | `ccx_output_date_format` | [`TimestampFormat`] | +//! | any pts | [`MpegClockTick`] | +//! | any frame count | [`FrameCount`] | +//! | `gop_time_code` | [`GopTimeCode`] | +//! | `print_mstime_static` | [`Timestamp::to_hms_millis_time`] | +//! | `gop_accepted` | [`GopTimeCode::did_rollover`] + some additional logic | +//! | `calculate_ms_gop_time` | [`GopTimeCode::new`], [`GopTimeCode::timestamp`] | + +mod units; + +pub mod c_functions; + +pub use units::*; diff --git a/src/rust/lib_ccxr/src/util/time/units.rs b/src/rust/lib_ccxr/src/util/time/units.rs new file mode 100644 index 000000000..dba22d3df --- /dev/null +++ b/src/rust/lib_ccxr/src/util/time/units.rs @@ -0,0 +1,634 @@ +use derive_more::{Add, Neg, Sub}; +use std::convert::TryInto; +use std::fmt::Write; +use std::num::TryFromIntError; +use std::time::{SystemTime, UNIX_EPOCH}; +use thiserror::Error; +use time::macros::{datetime, format_description}; +use time::{error::Format, Duration}; + +/// Represents a timestamp in milliseconds. +/// +/// The number can be negetive. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Add, Sub, Neg)] +pub struct Timestamp { + millis: i64, +} + +/// Represents an error during operations on [`Timestamp`]. +#[derive(Error, Debug)] +pub enum TimestampError { + #[error("input parameter given is out of range")] + InputOutOfRangeError, + #[error("timestamp is out of range")] + OutOfRangeError(#[from] TryFromIntError), + #[error("error ocurred during formatting")] + FormattingError(#[from] std::fmt::Error), + #[error("error ocurred during formatting a date")] + DateFormattingError(#[from] Format), + #[error("error ocurred during parsing")] + ParsingError, +} + +/// Represents the different string formats for [`Timestamp`]. +pub enum TimestampFormat { + /// Format: blank string. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::{Timestamp, TimestampFormat}; + /// let timestamp = Timestamp::from_millis(6524365); + /// let output = timestamp.to_formatted_time(TimestampFormat::None).unwrap(); + /// assert_eq!(output, ""); + /// ``` + None, + + /// Format: `{hour:02}:{minute:02}:{second:02}`. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::{Timestamp, TimestampFormat}; + /// let timestamp = Timestamp::from_millis(6524365); + /// let output = timestamp.to_formatted_time(TimestampFormat::HHMMSS).unwrap(); + /// assert_eq!(output, "01:48:44"); + /// ``` + HHMMSS, + + /// Format: `{second:02}{millis_separator}{millis:03}`. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::{Timestamp, TimestampFormat}; + /// let timestamp = Timestamp::from_millis(6524365); + /// let output = timestamp.to_formatted_time( + /// TimestampFormat::Seconds { + /// millis_separator: ',', + /// }, + /// ).unwrap(); + /// assert_eq!(output, "6524,365"); + /// ``` + Seconds { millis_separator: char }, + + /// Format: + /// `{year:04}{month:02}{day:02}{hour:02}{minute:02}{second:02}{millis_separator}{millis:03}`. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::{Timestamp, TimestampFormat}; + /// // 11 March 2023 14:53:36.749 in UNIX timestamp. + /// let timestamp = Timestamp::from_millis(1678546416749); + /// let output = timestamp.to_formatted_time( + /// TimestampFormat::Date { + /// millis_separator: ',', + /// }, + /// ).unwrap(); + /// assert_eq!(output, "20230311145336,749"); + /// ``` + Date { millis_separator: char }, + + /// Format: `{hour:02}:{minute:02}:{second:02},{millis:03}`. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::{Timestamp, TimestampFormat}; + /// let timestamp = Timestamp::from_millis(6524365); + /// let output = timestamp.to_formatted_time(TimestampFormat::HHMMSSFFF).unwrap(); + /// assert_eq!(output, "01:48:44,365"); + /// ``` + HHMMSSFFF, +} + +impl Timestamp { + /// Create a new [`Timestamp`] based on the number of milliseconds since the Unix Epoch. + pub fn now() -> Timestamp { + let duration = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("System Time cannot be behind the Unix Epoch"); + + Timestamp { + millis: duration.as_millis() as i64, + } + } + + /// Create a new [`Timestamp`] from number of milliseconds. + pub const fn from_millis(millis: i64) -> Timestamp { + Timestamp { millis } + } + + /// Create a new [`Timestamp`] from hours, minutes, seconds and milliseconds. + /// + /// It will fail if any parameter doesn't follow their respective ranges: + /// + /// | Parameter | Range | + /// |-----------|---------| + /// | minutes | 0 - 59 | + /// | seconds | 0 - 59 | + /// | millis | 0 - 999 | + pub fn from_hms_millis( + hours: u8, + minutes: u8, + seconds: u8, + millis: u16, + ) -> Result { + if minutes < 60 && seconds < 60 && millis < 1000 { + Ok(Timestamp::from_millis( + (hours as i64) * 3_600_000 + + (minutes as i64) * 60_000 + + (seconds as i64) * 1000 + + millis as i64, + )) + } else { + Err(TimestampError::InputOutOfRangeError) + } + } + + /// Returns the number of milliseconds. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::Timestamp; + /// let timestamp = Timestamp::from_millis(6524365); + /// assert_eq!(timestamp.millis(), 6524365); + /// ``` + pub fn millis(&self) -> i64 { + self.millis + } + + /// Returns the number of whole seconds. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::Timestamp; + /// let timestamp = Timestamp::from_millis(6524365); + /// assert_eq!(timestamp.seconds(), 6524); + /// ``` + pub fn seconds(&self) -> i64 { + self.millis / 1000 + } + + /// Returns the number of whole seconds and leftover milliseconds as unsigned integers. + /// + /// It will return an [`TimestampError::OutOfRangeError`] if the timestamp is negetive. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::Timestamp; + /// let timestamp = Timestamp::from_millis(6524365); + /// assert_eq!(timestamp.as_sec_millis().unwrap(), (6524, 365)); + /// ``` + pub fn as_sec_millis(&self) -> Result<(u64, u16), TimestampError> { + let millis: u64 = self.millis.try_into()?; + let s = millis / 1000; + let u = millis % 1000; + Ok((s, u as u16)) + } + + /// Returns the time in the form of hours, minutes, seconds and milliseconds as unsigned + /// integers. + /// + /// It will return an [`TimestampError::OutOfRangeError`] if the timestamp is negetive. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::Timestamp; + /// let timestamp = Timestamp::from_millis(6524365); + /// assert_eq!(timestamp.as_hms_millis().unwrap(), (1, 48, 44, 365)); + /// ``` + /// ```rust + /// # use lib_ccxr::util::time::{Timestamp, TimestampError}; + /// let timestamp = Timestamp::from_millis(1678546416749); + /// assert!(matches!( + /// timestamp.as_hms_millis().unwrap_err(), + /// TimestampError::OutOfRangeError(_) + /// )); + /// ``` + pub fn as_hms_millis(&self) -> Result<(u8, u8, u8, u16), TimestampError> { + let millis: u64 = self.millis.try_into()?; + let h = millis / 3600000; + let m = millis / 60000 - 60 * h; + let s = millis / 1000 - 3600 * h - 60 * m; + let u = millis - 3600000 * h - 60000 * m - 1000 * s; + if h > 24 { + println!("{}", h) + } + Ok((h.try_into()?, m as u8, s as u8, u as u16)) + } + + /// Fills `output` with the [`Timestamp`] using SRT's timestamp format. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::Timestamp; + /// let timestamp = Timestamp::from_millis(6524365); + /// let mut output = String::new(); + /// timestamp.write_srt_time(&mut output); + /// assert_eq!(output, "01:48:44,365"); + /// ``` + pub fn write_srt_time(&self, output: &mut String) -> Result<(), TimestampError> { + let (h, m, s, u) = self.as_hms_millis()?; + write!(output, "{:02}:{:02}:{:02},{:03}", h, m, s, u)?; + Ok(()) + } + + /// Fills `output` with the [`Timestamp`] using VTT's timestamp format. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::Timestamp; + /// let timestamp = Timestamp::from_millis(6524365); + /// let mut output = String::new(); + /// timestamp.write_vtt_time(&mut output); + /// assert_eq!(output, "01:48:44.365"); + /// ``` + pub fn write_vtt_time(&self, output: &mut String) -> Result<(), TimestampError> { + let (h, m, s, u) = self.as_hms_millis()?; + write!(output, "{:02}:{:02}:{:02}.{:03}", h, m, s, u)?; + Ok(()) + } + + /// Fills `output` with the [`Timestamp`] using + /// "{sign}{hour:02}:{minute:02}:{second:02}{sep}{millis:03}" format, where `sign` can be `-` + /// if time is negetive or blank if it is positive. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::Timestamp; + /// let timestamp = Timestamp::from_millis(6524365); + /// let mut output = String::new(); + /// timestamp.write_hms_millis_time(&mut output, ':'); + /// assert_eq!(output, "01:48:44:365"); + /// ``` + pub fn write_hms_millis_time( + &self, + output: &mut String, + sep: char, + ) -> Result<(), TimestampError> { + let sign = if self.millis < 0 { "-" } else { "" }; + let timestamp = if self.millis < 0 { -*self } else { *self }; + let (h, m, s, u) = timestamp.as_hms_millis()?; + write!(output, "{}{:02}:{:02}:{:02}{}{:03}", sign, h, m, s, sep, u)?; + Ok(()) + } + + /// Fills `output` with the [`Timestamp`] using ctime's format. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::Timestamp; + /// let timestamp = Timestamp::from_millis(6524365); + /// let mut output = String::new(); + /// timestamp.write_ctime(&mut output); + /// assert_eq!(output, "Thu Jan 01 01:48:44 1970"); + /// ``` + pub fn write_ctime(&self, output: &mut String) -> Result<(), TimestampError> { + let (sec, millis) = self.as_sec_millis()?; + let d = datetime!(1970-01-01 0:00) + + Duration::new(sec.try_into()?, (millis as i32) * 1_000_000); + let format = format_description!( + "[weekday repr:short] [month repr:short] [day] [hour]:[minute]:[second] [year]" + ); + write!(output, "{}", d.format(&format)?)?; + Ok(()) + } + + /// Fills `output` with the [`Timestamp`] using format specified by [`TimestampFormat`]. + /// + /// See [`TimestampFormat`] for examples. + pub fn write_formatted_time( + &self, + output: &mut String, + format: TimestampFormat, + ) -> Result<(), TimestampError> { + match format { + TimestampFormat::None => Ok(()), + TimestampFormat::HHMMSS => { + let (h, m, s, _) = self.as_hms_millis()?; + write!(output, "{:02}:{:02}:{:02}", h, m, s)?; + Ok(()) + } + TimestampFormat::Seconds { millis_separator } => { + let (sec, millis) = self.as_sec_millis()?; + write!(output, "{}{}{:03}", sec, millis_separator, millis)?; + Ok(()) + } + TimestampFormat::Date { millis_separator } => { + let (sec, millis) = self.as_sec_millis()?; + let d = datetime!(1970-01-01 0:00) + + Duration::new(sec.try_into()?, (millis as i32) * 1_000_000); + let format1 = format_description!("[year][month][day][hour][minute][second]"); + let format2 = format_description!("[subsecond digits:3]"); + + write!( + output, + "{}{}{}", + d.format(&format1)?, + millis_separator, + d.format(&format2)? + )?; + Ok(()) + } + TimestampFormat::HHMMSSFFF => self.write_srt_time(output), + } + } + + /// Returns a formatted [`Timestamp`] using SRT's timestamp format. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::Timestamp; + /// let timestamp = Timestamp::from_millis(6524365); + /// assert_eq!(timestamp.to_srt_time().unwrap(), "01:48:44,365"); + /// ``` + pub fn to_srt_time(&self) -> Result { + let mut s = String::new(); + self.write_srt_time(&mut s)?; + Ok(s) + } + + /// Returns a formatted [`Timestamp`] using VTT's timestamp format. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::Timestamp; + /// let timestamp = Timestamp::from_millis(6524365); + /// assert_eq!(timestamp.to_vtt_time().unwrap(), "01:48:44.365"); + /// ``` + pub fn to_vtt_time(&self) -> Result { + let mut s = String::new(); + self.write_vtt_time(&mut s)?; + Ok(s) + } + + /// Returns a formatted [`Timestamp`] using + /// "{sign}{hour:02}:{minute:02}:{second:02}{sep}{millis:03}" format, where `sign` can be `-` + /// if time is negetive or blank if it is positive. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::Timestamp; + /// let timestamp = Timestamp::from_millis(6524365); + /// assert_eq!(timestamp.to_hms_millis_time(':').unwrap(), "01:48:44:365"); + /// ``` + pub fn to_hms_millis_time(&self, sep: char) -> Result { + let mut s = String::new(); + self.write_hms_millis_time(&mut s, sep)?; + Ok(s) + } + + /// Returns a formatted [`Timestamp`] using ctime's format. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::Timestamp; + /// let timestamp = Timestamp::from_millis(6524365); + /// assert_eq!(timestamp.to_ctime().unwrap(), "Thu Jan 01 01:48:44 1970"); + /// ``` + pub fn to_ctime(&self) -> Result { + let mut s = String::new(); + self.write_ctime(&mut s)?; + Ok(s) + } + + /// Returns a formatted [`Timestamp`] using format specified by [`TimestampFormat`]. + /// + /// See [`TimestampFormat`] for examples. + pub fn to_formatted_time(&self, format: TimestampFormat) -> Result { + let mut s = String::new(); + self.write_formatted_time(&mut s, format)?; + Ok(s) + } + + /// Creates a [`Timestamp`] by parsing `input` using format `SS` or `MM:SS` or `HH:MM:SS`. + /// + /// # Examples + /// ```rust + /// # use lib_ccxr::util::time::Timestamp; + /// let timestamp = Timestamp::parse_optional_hhmmss_from_str("01:12:45").unwrap(); + /// assert_eq!(timestamp, Timestamp::from_millis(4_365_000)); + /// ``` + pub fn parse_optional_hhmmss_from_str(input: &str) -> Result { + let mut numbers = input + .split(':') + .map(|x| x.parse::().map_err(|_| TimestampError::ParsingError)) + .rev(); + + let mut millis: u64 = 0; + + let seconds: u64 = numbers.next().ok_or(TimestampError::ParsingError)??.into(); + if seconds > 59 { + return Err(TimestampError::InputOutOfRangeError); + } + millis += seconds * 1000; + + if let Some(x) = numbers.next() { + let minutes: u64 = x?.into(); + if minutes > 59 { + return Err(TimestampError::InputOutOfRangeError); + } + millis += 60_000 * minutes; + } + + if let Some(x) = numbers.next() { + let hours: u64 = x?.into(); + millis += 3_600_000 * hours; + } + + if numbers.next().is_some() { + return Err(TimestampError::ParsingError); + } + + Ok(Timestamp::from_millis(millis.try_into()?)) + } +} + +/// Represent the number of clock ticks as defined in Mpeg standard. +/// +/// This number can never be negetive. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Add, Sub)] +pub struct MpegClockTick(i64); + +impl MpegClockTick { + /// The ratio to convert a clock tick to time duration. + pub const MPEG_CLOCK_FREQ: i64 = 90000; + + /// Create a value representing `ticks` clock ticks. + pub fn new(ticks: i64) -> MpegClockTick { + MpegClockTick(ticks) + } + + /// Returns the number of clock ticks. + pub fn as_i64(&self) -> i64 { + self.0 + } + + /// Converts the clock ticks to its equivalent time duration. + /// + /// The conversion ratio used is [`MpegClockTick::MPEG_CLOCK_FREQ`]. + pub fn as_timestamp(&self) -> Timestamp { + Timestamp::from_millis(self.0 / (MpegClockTick::MPEG_CLOCK_FREQ / 1000)) + } +} + +/// Represents the number of frames. +/// +/// This number can never be negetive. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Add, Sub)] +pub struct FrameCount(u64); + +impl FrameCount { + /// Create a value representing `frames` number of frames. + pub const fn new(frames: u64) -> FrameCount { + FrameCount(frames) + } + + /// Returns the number of frames. + pub fn as_u64(&self) -> u64 { + self.0 + } + + /// Converts the frames to its equivalent time duration. + /// + /// The conversion ratio used is `fps`. + pub fn as_timestamp(&self, fps: f64) -> Timestamp { + Timestamp::from_millis((self.0 as f64 * 1000.0 / fps) as i64) + } + + /// Converts the frames to its equivalent number of clock ticks. + /// + /// The conversion ratio used is [`MpegClockTick::MPEG_CLOCK_FREQ`] and `fps`. + pub fn as_mpeg_clock_tick(&self, fps: f64) -> MpegClockTick { + MpegClockTick::new(((self.0 * MpegClockTick::MPEG_CLOCK_FREQ as u64) as f64 / fps) as i64) + } +} + +/// Represents a GOP Time code as defined in the Mpeg standard. +/// +/// This structure stores its time in the form of hours, minutes, seconds and pictures. This +/// structure also stores its time in the form of a [`Timestamp`] when it is created. This +/// [`Timestamp`] can be modified by [`timestamp_mut`](GopTimeCode::timestamp_mut) and an +/// additional 24 hours may be added on rollover, so it is not necessary that the above two +/// formats refer to the same time. Therefore it is recommended to only rely on the +/// [`Timestamp`] instead of the other format. +#[derive(Copy, Clone, Debug)] +pub struct GopTimeCode { + drop_frame: bool, + time_code_hours: u8, + time_code_minutes: u8, + time_code_seconds: u8, + time_code_pictures: u8, + timestamp: Timestamp, +} + +impl GopTimeCode { + /// Create a new [`GopTimeCode`] from the specified parameters. + /// + /// The number of frames or pictures is converted to time duration using `fps`. + /// + /// If `rollover` is true, then an extra of 24 hours will added. + /// + /// It will return [`None`] if any parameter doesn't follow their respective ranges: + /// + /// | Parameter | Range | + /// |-----------|--------| + /// | hours | 0 - 23 | + /// | minutes | 0 - 59 | + /// | seconds | 0 - 59 | + /// | pictures | 0 - 59 | + pub fn new( + drop_frame: bool, + hours: u8, + minutes: u8, + seconds: u8, + pictures: u8, + fps: f64, + rollover: bool, + ) -> Option { + if hours < 24 && minutes < 60 && seconds < 60 && pictures < 60 { + let millis = (1000.0 * (pictures as f64) / fps) as u16; + let extra_hours = if rollover { 24 } else { 0 }; + let timestamp = + Timestamp::from_hms_millis(hours + extra_hours, minutes, seconds, millis) + .expect("The fps given is probably too low"); + + Some(GopTimeCode { + drop_frame, + time_code_hours: hours, + time_code_minutes: minutes, + time_code_seconds: seconds, + time_code_pictures: pictures, + timestamp, + }) + } else { + None + } + } + + /// Returns the GOP time code in its equivalent time duration. + pub fn timestamp(&self) -> Timestamp { + self.timestamp + } + + /// Returns a mutable reference to internal [`Timestamp`]. + pub fn timestamp_mut(&mut self) -> &mut Timestamp { + &mut self.timestamp + } + + /// Check if a rollover has ocurred by comparing the previous [`GopTimeCode`] that is `prev` + /// with the current [`GopTimeCode`]. + pub fn did_rollover(&self, prev: &GopTimeCode) -> bool { + prev.time_code_hours == 23 + && prev.time_code_minutes == 59 + && self.time_code_hours == 0 + && self.time_code_minutes == 0 + } + + /// Constructs a [`GopTimeCode`] from its individual fields. + /// + /// # Safety + /// + /// The fields other than [`Timestamp`] may not be accurate if it is changed using + /// [`timestamp_mut`](GopTimeCode::timestamp_mut). + pub unsafe fn from_raw_parts( + drop_frame: bool, + hours: u8, + minutes: u8, + seconds: u8, + pictures: u8, + timestamp: Timestamp, + ) -> GopTimeCode { + GopTimeCode { + drop_frame, + time_code_hours: hours, + time_code_minutes: minutes, + time_code_seconds: seconds, + time_code_pictures: pictures, + timestamp, + } + } + + /// Returns the individuals field of a [`GopTimeCode`]. + /// + /// # Safety + /// + /// The fields other than [`Timestamp`] may not be accurate if it is changed using + /// [`timestamp_mut`](GopTimeCode::timestamp_mut). + pub unsafe fn as_raw_parts(&self) -> (bool, u8, u8, u8, u8, Timestamp) { + let GopTimeCode { + drop_frame, + time_code_hours, + time_code_minutes, + time_code_seconds, + time_code_pictures, + timestamp, + } = *self; + + ( + drop_frame, + time_code_hours, + time_code_minutes, + time_code_seconds, + time_code_pictures, + timestamp, + ) + } +} diff --git a/src/rust/src/libccxr_exports/mod.rs b/src/rust/src/libccxr_exports/mod.rs index e365e0fb2..4b8b7b045 100644 --- a/src/rust/src/libccxr_exports/mod.rs +++ b/src/rust/src/libccxr_exports/mod.rs @@ -1 +1,5 @@ //! Provides C-FFI functions that are direct equivalent of functions available in C. + +mod time; + +pub use time::*; diff --git a/src/rust/src/libccxr_exports/time.rs b/src/rust/src/libccxr_exports/time.rs new file mode 100644 index 000000000..0938e936b --- /dev/null +++ b/src/rust/src/libccxr_exports/time.rs @@ -0,0 +1,107 @@ +#![allow(clippy::useless_conversion)] + +use crate::bindings::*; + +use std::ffi::CStr; +use std::os::raw::{c_char, c_int}; + +use lib_ccxr::util::time::{c_functions as c, *}; + +/// Helper function that converts a Rust-String (`string`) to C-String (`buffer`). +/// +/// # Safety +/// +/// `buffer` must have enough allocated space for `string` to fit. +unsafe fn write_string_into_pointer(buffer: *mut c_char, string: &str) { + let buffer = std::slice::from_raw_parts_mut(buffer as *mut u8, string.len() + 1); + buffer[..string.len()].copy_from_slice(string.as_bytes()); + buffer[string.len()] = b'\0'; +} + +/// Rust equivalent for `timestamp_to_srttime` function in C. Uses C-native types as input and +/// output. +/// +/// # Safety +/// +/// `buffer` must have enough allocated space for the formatted `timestamp` to fit. +#[no_mangle] +pub unsafe extern "C" fn ccxr_timestamp_to_srttime(timestamp: u64, buffer: *mut c_char) { + let mut s = String::new(); + let timestamp = Timestamp::from_millis(timestamp as i64); + + let _ = c::timestamp_to_srttime(timestamp, &mut s); + + write_string_into_pointer(buffer, &s); +} + +/// Rust equivalent for `timestamp_to_vtttime` function in C. Uses C-native types as input and +/// output. +/// +/// # Safety +/// +/// `buffer` must have enough allocated space for the formatted `timestamp` to fit. +#[no_mangle] +pub unsafe extern "C" fn ccxr_timestamp_to_vtttime(timestamp: u64, buffer: *mut c_char) { + let mut s = String::new(); + let timestamp = Timestamp::from_millis(timestamp as i64); + + let _ = c::timestamp_to_vtttime(timestamp, &mut s); + + write_string_into_pointer(buffer, &s); +} + +/// Rust equivalent for `millis_to_date` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `buffer` must have enough allocated space for the formatted `timestamp` to fit. +#[no_mangle] +pub unsafe extern "C" fn ccxr_millis_to_date( + timestamp: u64, + buffer: *mut c_char, + date_format: ccx_output_date_format, + millis_separator: c_char, +) { + let mut s = String::new(); + let timestamp = Timestamp::from_millis(timestamp as i64); + let date_format = match date_format { + ccx_output_date_format::ODF_NONE => TimestampFormat::None, + ccx_output_date_format::ODF_HHMMSS => TimestampFormat::HHMMSS, + ccx_output_date_format::ODF_HHMMSSMS => TimestampFormat::HHMMSSFFF, + ccx_output_date_format::ODF_SECONDS => TimestampFormat::Seconds { + millis_separator: millis_separator as u8 as char, + }, + ccx_output_date_format::ODF_DATE => TimestampFormat::Date { + millis_separator: millis_separator as u8 as char, + }, + }; + + let _ = c::millis_to_date(timestamp, &mut s, date_format); + + write_string_into_pointer(buffer, &s); +} + +/// Rust equivalent for `stringztoms` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `s` must contain valid utf-8 data and have a nul terminator at the end of the string. +#[no_mangle] +pub unsafe extern "C" fn ccxr_stringztoms(s: *const c_char, bt: *mut ccx_boundary_time) -> c_int { + let s = CStr::from_ptr(s).to_str().unwrap(); + + let option_timestamp = c::stringztoms(s); + + if let Some(timestamp) = option_timestamp { + if let Ok((h, m, s, _)) = timestamp.as_hms_millis() { + (*bt).set = 1; + (*bt).hh = h.into(); + (*bt).mm = m.into(); + (*bt).ss = s.into(); + (*bt).time_in_ms = (timestamp.millis() / 1000) * 1000; + return 0; + } + }; + + -1 +} From cea527d298d0b8470fef17eecd0641bc4de758a8 Mon Sep 17 00:00:00 2001 From: Elbert Ronnie Date: Sun, 27 Aug 2023 22:26:21 +0530 Subject: [PATCH 5/5] add timing module --- src/lib_ccx/ccx_common_timing.c | 48 ++ src/rust/build.rs | 2 + .../lib_ccxr/src/util/time/c_functions.rs | 58 ++ src/rust/lib_ccxr/src/util/time/mod.rs | 20 +- src/rust/lib_ccxr/src/util/time/timing.rs | 563 ++++++++++++++++++ src/rust/src/lib.rs | 358 +++++------ src/rust/src/libccxr_exports/time.rs | 426 ++++++++++++- 7 files changed, 1299 insertions(+), 176 deletions(-) create mode 100644 src/rust/lib_ccxr/src/util/time/timing.rs diff --git a/src/lib_ccx/ccx_common_timing.c b/src/lib_ccx/ccx_common_timing.c index d1fca4282..e30e9fc3f 100644 --- a/src/lib_ccx/ccx_common_timing.c +++ b/src/lib_ccx/ccx_common_timing.c @@ -30,6 +30,18 @@ int gop_rollover = 0; struct ccx_common_timing_settings_t ccx_common_timing_settings; +#ifndef DISABLE_RUST +void ccxr_add_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts); +void ccxr_set_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts); +int ccxr_set_fts(struct ccx_common_timing_ctx *ctx); +LLONG ccxr_get_fts(struct ccx_common_timing_ctx *ctx, int current_field); +LLONG ccxr_get_fts_max(struct ccx_common_timing_ctx *ctx); +char *ccxr_print_mstime_static(LLONG mstime, char *buf); +void ccxr_print_debug_timing(struct ccx_common_timing_ctx *ctx); +void ccxr_calculate_ms_gop_time(struct gop_time_code *g); +int ccxr_gop_accepted(struct gop_time_code *g); +#endif + void ccx_common_timing_init(LLONG *file_position, int no_sync) { ccx_common_timing_settings.disable_sync_check = 0; @@ -73,11 +85,19 @@ struct ccx_common_timing_ctx *init_timing_ctx(struct ccx_common_timing_settings_ void add_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts) { +#ifndef DISABLE_RUST + return ccxr_add_current_pts(ctx, pts); +#endif + set_current_pts(ctx, ctx->current_pts + pts); } void set_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts) { +#ifndef DISABLE_RUST + return ccxr_set_current_pts(ctx, pts); +#endif + LLONG prev_pts = ctx->current_pts; ctx->current_pts = pts; if (ctx->pts_set == 0) @@ -95,6 +115,10 @@ void set_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts) int set_fts(struct ccx_common_timing_ctx *ctx) { +#ifndef DISABLE_RUST + return ccxr_set_fts(ctx); +#endif + int pts_jump = 0; // ES don't have PTS unless GOP timing is used @@ -266,6 +290,10 @@ int set_fts(struct ccx_common_timing_ctx *ctx) LLONG get_fts(struct ccx_common_timing_ctx *ctx, int current_field) { +#ifndef DISABLE_RUST + return ccxr_get_fts(ctx, current_field); +#endif + LLONG fts; switch (current_field) @@ -290,6 +318,10 @@ LLONG get_fts(struct ccx_common_timing_ctx *ctx, int current_field) LLONG get_fts_max(struct ccx_common_timing_ctx *ctx) { +#ifndef DISABLE_RUST + return ccxr_get_fts_max(ctx); +#endif + // This returns the maximum FTS that belonged to a frame. Caption block // counters are not applicable. return ctx->fts_max + ctx->fts_global; @@ -322,6 +354,10 @@ size_t print_mstime_buff(LLONG mstime, char *fmt, char *buf) char *print_mstime_static(LLONG mstime) { static char buf[15]; // 14 should be long enough +#ifndef DISABLE_RUST + return ccxr_print_mstime_static(mstime, buf); +#endif + print_mstime_buff(mstime, "%02u:%02u:%02u:%03u", buf); return buf; } @@ -329,6 +365,10 @@ char *print_mstime_static(LLONG mstime) /* Helper function for to display debug timing info. */ void print_debug_timing(struct ccx_common_timing_ctx *ctx) { +#ifndef DISABLE_RUST + return ccxr_print_debug_timing(ctx); +#endif + // Avoid wrong "Calc. difference" and "Asynchronous by" numbers // for uninitialized min_pts LLONG tempmin_pts = (ctx->min_pts == 0x01FFFFFFFFLL ? ctx->sync_pts : ctx->min_pts); @@ -355,6 +395,10 @@ void print_debug_timing(struct ccx_common_timing_ctx *ctx) void calculate_ms_gop_time(struct gop_time_code *g) { +#ifndef DISABLE_RUST + return ccxr_calculate_ms_gop_time(g); +#endif + int seconds = (g->time_code_hours * 3600) + (g->time_code_minutes * 60) + g->time_code_seconds; g->ms = (LLONG)(1000 * (seconds + g->time_code_pictures / current_fps)); if (gop_rollover) @@ -363,6 +407,10 @@ void calculate_ms_gop_time(struct gop_time_code *g) int gop_accepted(struct gop_time_code *g) { +#ifndef DISABLE_RUST + return ccxr_gop_accepted(g); +#endif + if (!((g->time_code_hours <= 23) && (g->time_code_minutes <= 59) && (g->time_code_seconds <= 59) && (g->time_code_pictures <= 59))) return 0; diff --git a/src/rust/build.rs b/src/rust/build.rs index e1636e2be..82772f29b 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -28,6 +28,7 @@ fn main() { "ccx_output_format", "ccx_boundary_time", "gop_time_code", + "ccx_common_timing_settings_t", "ccx_s_options", ]); @@ -78,4 +79,5 @@ const RUSTIFIED_ENUMS: &[&str] = &[ "dtvcc_(window|pen)_.*", "ccx_output_format", "ccx_output_date_format", + "ccx_frame_type", ]; diff --git a/src/rust/lib_ccxr/src/util/time/c_functions.rs b/src/rust/lib_ccxr/src/util/time/c_functions.rs index 65d837e35..4460e0175 100644 --- a/src/rust/lib_ccxr/src/util/time/c_functions.rs +++ b/src/rust/lib_ccxr/src/util/time/c_functions.rs @@ -33,3 +33,61 @@ pub fn millis_to_date( pub fn stringztoms(s: &str) -> Option { Timestamp::parse_optional_hhmmss_from_str(s).ok() } + +/// Rust equivalent for `add_current_pts` function in C. Uses Rust-native types as input and output. +pub fn add_current_pts(ctx: &mut TimingContext, pts: MpegClockTick) { + ctx.add_current_pts(pts) +} + +/// Rust equivalent for `set_current_pts` function in C. Uses Rust-native types as input and output. +pub fn set_current_pts(ctx: &mut TimingContext, pts: MpegClockTick) { + ctx.set_current_pts(pts) +} + +/// Rust equivalent for `set_fts` function in C. Uses Rust-native types as input and output. +pub fn set_fts(ctx: &mut TimingContext) -> bool { + ctx.set_fts() +} + +/// Rust equivalent for `get_fts` function in C. Uses Rust-native types as input and output. +pub fn get_fts(ctx: &mut TimingContext, current_field: CaptionField) -> Timestamp { + ctx.get_fts(current_field) +} + +/// Rust equivalent for `get_fts_max` function in C. Uses Rust-native types as input and output. +pub fn get_fts_max(ctx: &mut TimingContext) -> Timestamp { + ctx.get_fts_max() +} + +/// Rust equivalent for `print_mstime_static` function in C. Uses Rust-native types as input and output. +pub fn print_mstime_static(mstime: Timestamp, sep: char) -> String { + mstime.to_hms_millis_time(sep).unwrap() +} + +/// Rust equivalent for `print_debug_timing` function in C. Uses Rust-native types as input and output. +pub fn print_debug_timing(ctx: &mut TimingContext) { + ctx.print_debug_timing() +} + +/// Rust equivalent for `calculate_ms_gop_time` function in C. Uses Rust-native types as input and output. +pub fn calculate_ms_gop_time(g: GopTimeCode) -> Timestamp { + g.timestamp() +} + +/// Rust equivalent for `gop_accepted` function in C. Uses Rust-native types as input and output. +pub fn gop_accepted(g: GopTimeCode) -> bool { + let mut timing_info = GLOBAL_TIMING_INFO.write().unwrap(); + + let gop_time = if let Some(gt) = timing_info.gop_time { + gt + } else { + return true; + }; + + if g.did_rollover(&gop_time) { + timing_info.gop_rollover = true; + true + } else { + gop_time.timestamp() <= g.timestamp() + } +} diff --git a/src/rust/lib_ccxr/src/util/time/mod.rs b/src/rust/lib_ccxr/src/util/time/mod.rs index 64c67f4d5..5fbab7d75 100644 --- a/src/rust/lib_ccxr/src/util/time/mod.rs +++ b/src/rust/lib_ccxr/src/util/time/mod.rs @@ -1,4 +1,5 @@ -//! Provide types for storing time in different formats +//! Provide types for storing time in different formats and manage timing information while +//! decoding. //! //! Time can be represented in one of following formats: //! - [`Timestamp`] as number of milliseconds @@ -6,6 +7,9 @@ //! - [`FrameCount`] as number of frames //! - [`GopTimeCode`] as a GOP time code (as defined in the MPEG standard) //! +//! [`GLOBAL_TIMING_INFO`] and [`TimingContext`] are used for managing time-related information +//! during the deocoding process. +//! //! # Conversion Guide //! //! | From | To | @@ -16,12 +20,26 @@ //! | any pts | [`MpegClockTick`] | //! | any frame count | [`FrameCount`] | //! | `gop_time_code` | [`GopTimeCode`] | +//! | `current_field` | [`CaptionField`] | +//! | `ccx_common_timing_ctx.pts_set` | [`PtsSet`] | +//! | `ccx_common_timing_settings_t` | [`TimingSettings`] | +//! | `ccx_common_timing_ctx` | [`TimingContext`] | +//! | `cb_708`, `cb_field1`, `cb_field2`, `pts_big_change`, `current_fps`, `frames_since_ref_time`, `total_frames_count`, `gop_time`, `first_gop_time`, `fts_at_gop_start`, `gop_rollover`, `ccx_common_timing_settings` | [`GlobalTimingInfo`], [`GLOBAL_TIMING_INFO`] | +//! | `init_timing_ctx` | [`TimingContext::new`] | +//! | `add_current_pts` | [`TimingContext::add_current_pts`] | +//! | `set_current_pts` | [`TimingContext::set_current_pts`] | +//! | `set_fts` | [`TimingContext::set_fts`] | +//! | `get_fts` | [`TimingContext::get_fts`] | +//! | `get_fts_max` | [`TimingContext::get_fts_max`] | //! | `print_mstime_static` | [`Timestamp::to_hms_millis_time`] | +//! | `print_debug_timing` | [`TimingContext::print_debug_timing`] | //! | `gop_accepted` | [`GopTimeCode::did_rollover`] + some additional logic | //! | `calculate_ms_gop_time` | [`GopTimeCode::new`], [`GopTimeCode::timestamp`] | +mod timing; mod units; pub mod c_functions; +pub use timing::*; pub use units::*; diff --git a/src/rust/lib_ccxr/src/util/time/timing.rs b/src/rust/lib_ccxr/src/util/time/timing.rs new file mode 100644 index 000000000..4b7399c76 --- /dev/null +++ b/src/rust/lib_ccxr/src/util/time/timing.rs @@ -0,0 +1,563 @@ +use crate::common::FrameType; +use crate::util::log::{debug, info, DebugMessageFlag}; +use crate::util::time::{FrameCount, GopTimeCode, MpegClockTick, Timestamp}; +use std::sync::RwLock; + +/// The maximum allowed difference between [`TimingContext::current_pts`] and [`TimingContext::sync_pts`] in seconds. +/// +/// If the difference crosses this value, a PTS jump has occured and is handled accordingly. +const MAX_DIF: i64 = 5; + +/// A unique global instance of [`GlobalTimingInfo`] to be used throughout the program. +pub static GLOBAL_TIMING_INFO: RwLock = RwLock::new(GlobalTimingInfo::new()); + +const DEFAULT_FRAME_RATE: f64 = 30000.0 / 1001.0; + +/// Represents the status of [`TimingContext::current_pts`] and [`TimingContext::min_pts`] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PtsSet { + No, + Received, + MinPtsSet, +} + +/// Represent the field of 608 or 708 caption. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum CaptionField { + Field1, + Field2, + Cea708, +} + +/// A collective struct for storing timing-related information when decoding a file. +/// +/// [`GlobalTimingInfo`] serves a similar purpose. The only difference is that its lifetime is +/// global. +#[derive(Debug)] +pub struct TimingContext { + pub pts_set: PtsSet, + /// if true then don't adjust again. + min_pts_adjusted: bool, + pub current_pts: MpegClockTick, + pub current_picture_coding_type: FrameType, + /// Store temporal reference of current frame. + pub current_tref: FrameCount, + pub min_pts: MpegClockTick, + pub sync_pts: MpegClockTick, + /// No screen should start before this FTS + pub minimum_fts: Timestamp, + /// Time stamp of current file (w/ fts_offset, w/o fts_global). + pub fts_now: Timestamp, + /// Time before first sync_pts. + pub fts_offset: Timestamp, + /// Time before first GOP. + pub fts_fc_offset: Timestamp, + /// Remember the maximum fts that we saw in current file. + pub fts_max: Timestamp, + /// Duration of previous files (-ve mode). + pub fts_global: Timestamp, + pub sync_pts2fts_set: bool, + pub sync_pts2fts_fts: Timestamp, + pub sync_pts2fts_pts: MpegClockTick, + /// PTS resets when current_pts is lower than prev. + pts_reset: bool, +} + +/// Settings for overall timing functionality in [`TimingContext`]. +#[derive(Debug)] +pub struct TimingSettings { + /// If true, timeline jumps will be ignored. This is important in several input formats that are assumed to have correct timing, no matter what. + pub disable_sync_check: bool, + + /// If true, there will be no sync at all. Mostly useful for debugging. + pub no_sync: bool, + + // Needs to be set, as it's used in set_fts. + pub is_elementary_stream: bool, +} + +/// A collective struct to store global timing-related information while decoding a file. +/// +/// [`TimingContext`] serves a similar purpose. The only difference is that its lifetime is not +/// global and its information could be reset while execution of program. +#[derive(Debug)] +pub struct GlobalTimingInfo { + // Count 608 (per field) and 708 blocks since last set_fts() call + pub cb_field1: u64, + pub cb_field2: u64, + pub cb_708: u64, + pub pts_big_change: bool, + pub current_fps: f64, + pub frames_since_ref_time: FrameCount, + pub total_frames_count: FrameCount, + pub gop_time: Option, + pub first_gop_time: Option, + pub fts_at_gop_start: Timestamp, + pub gop_rollover: bool, + pub timing_settings: TimingSettings, +} + +impl TimingContext { + /// Create a new [`TimingContext`]. + pub fn new() -> TimingContext { + TimingContext { + pts_set: PtsSet::No, + min_pts_adjusted: false, + current_pts: MpegClockTick::new(0), + current_picture_coding_type: FrameType::ResetOrUnknown, + current_tref: FrameCount::new(0), + min_pts: MpegClockTick::new(0x01FFFFFFFF), + sync_pts: MpegClockTick::new(0), + minimum_fts: Timestamp::from_millis(0), + fts_now: Timestamp::from_millis(0), + fts_offset: Timestamp::from_millis(0), + fts_fc_offset: Timestamp::from_millis(0), + fts_max: Timestamp::from_millis(0), + fts_global: Timestamp::from_millis(0), + sync_pts2fts_set: false, + sync_pts2fts_fts: Timestamp::from_millis(0), + sync_pts2fts_pts: MpegClockTick::new(0), + pts_reset: false, + } + } + + /// Add `pts` to `TimingContext::current_pts`. + /// + /// It also checks for PTS resets. + pub fn add_current_pts(&mut self, pts: MpegClockTick) { + self.set_current_pts(self.current_pts + pts) + } + + /// Set `TimingContext::current_pts` to `pts`. + /// + /// It also checks for PTS resets. + pub fn set_current_pts(&mut self, pts: MpegClockTick) { + let prev_pts = self.current_pts; + self.current_pts = pts; + if self.pts_set == PtsSet::No { + self.pts_set = PtsSet::Received + } + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "PTS: {} ({:8})", self.current_pts.as_timestamp().to_hms_millis_time(':').unwrap(), self.current_pts.as_i64()); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; " FTS: {} \n", self.fts_now.to_hms_millis_time(':').unwrap()); + + // Check if PTS reset + if self.current_pts < prev_pts { + self.pts_reset = true; + } + } + + pub fn set_fts(&mut self) -> bool { + let mut timing_info = GLOBAL_TIMING_INFO.write().unwrap(); + + let mut pts_jump = false; + + // ES don't have PTS unless GOP timing is used + if self.pts_set == PtsSet::No && timing_info.timing_settings.is_elementary_stream { + return true; + } + + // First check for timeline jump (only when min_pts was set (implies sync_pts)). + if self.pts_set == PtsSet::MinPtsSet { + let dif = if timing_info.timing_settings.disable_sync_check { + // Disables sync check. Used for several input formats. + 0 + } else { + (self.current_pts - self.sync_pts).as_timestamp().seconds() + }; + + // This was -0.2 before. TODO: find out why, since dif is integer? + if !(0..MAX_DIF).contains(&dif) { + // ATSC specs: More than 3501 ms means missing component + info!("\nWarning: Reference clock has changed abruptly ({} seconds filepos={}), attempting to synchronize\n", dif, "Unable to get file position"); // TODO: get the file position somehow + info!("Last sync PTS value: {}\n", self.sync_pts.as_i64()); + info!("Current PTS value: {}\n", self.current_pts.as_i64()); + info!("Note: You can disable this behavior by adding -ignoreptsjumps to the parameters.\n"); + + pts_jump = true; + timing_info.pts_big_change = true; + + // Discard the gap if it is not on an I-frame or temporal reference zero. + if self.current_tref.as_u64() != 0 + && self.current_picture_coding_type != FrameType::IFrame + { + self.fts_now = self.fts_max; + info!("Change did not occur on first frame - probably a broken GOP\n"); + return true; + } + } + } + + // If min_pts was set just before a rollover we compensate by "roll-oving" it too + if self.pts_set == PtsSet::MinPtsSet && !self.min_pts_adjusted { + // min_pts set + // We want to be aware of the upcoming rollover, not after it happened, so we don't take + // the 3 most significant bits but the 3 next ones + let min_pts_big_bits = (self.min_pts.as_i64() >> 30) & 7; + let cur_pts_big_bits = (self.current_pts.as_i64() >> 30) & 7; + if cur_pts_big_bits == 7 && min_pts_big_bits == 0 { + // Huge difference possibly means the first min_pts was actually just over the boundary + // Take the current_pts (smaller, accounting for the rollover) instead + self.min_pts = self.current_pts; + self.min_pts_adjusted = true; + } else if (1..=6).contains(&cur_pts_big_bits) { + // Far enough from the boundary + // Prevent the eventual difference with min_pts to make a bad adjustment + self.min_pts_adjusted = true; + } + } + + // Set min_pts, fts_offset + if self.pts_set != PtsSet::No { + self.pts_set = PtsSet::MinPtsSet; + + // Use this part only the first time min_pts is set. Later treat + // it as a reference clock change + if self.current_pts < self.min_pts && !pts_jump { + // If this is the first GOP, and seq 0 was not encountered yet + // we might reset min_pts/fts_offset again + + self.min_pts = self.current_pts; + + // Avoid next async test + self.sync_pts = self.current_pts + - self + .current_tref + .as_mpeg_clock_tick(timing_info.current_fps); + + if self.current_tref.as_u64() == 0 + || (timing_info.total_frames_count - timing_info.frames_since_ref_time).as_u64() + == 0 + { + // Earliest time in GOP. + // OR + // If this is the first frame (PES) there cannot be an offset. + // This part is also reached for dvr-ms/NTSC (RAW) as + // total_frames_count = frames_since_ref_time = 0 when + // this is called for the first time. + self.fts_offset = Timestamp::from_millis(0); + } else { + // It needs to be "+1" because the current frame is + // not yet counted. + let one_frame = FrameCount::new(1); + self.fts_offset = (timing_info.total_frames_count + - timing_info.frames_since_ref_time + + one_frame) + .as_timestamp(timing_info.current_fps); + } + debug!( + msg_type = DebugMessageFlag::TIME; + "\nFirst sync time PTS: {} {:+}ms (time before this PTS)\n", + self.min_pts.as_timestamp().to_hms_millis_time(':').unwrap(), + self.fts_offset.millis() + ); + debug!( + msg_type = DebugMessageFlag::TIME; + "Total_frames_count {} frames_since_ref_time {}\n", + timing_info.total_frames_count.as_u64(), + timing_info.frames_since_ref_time.as_u64() + ); + } + + // -nosync disables syncing + if pts_jump && !timing_info.timing_settings.no_sync { + // The current time in the old time base is calculated using + // sync_pts (set at the beginning of the last GOP) plus the + // time of the frames since then. + self.fts_offset = self.fts_offset + + (self.sync_pts - self.min_pts).as_timestamp() + + timing_info + .frames_since_ref_time + .as_timestamp(timing_info.current_fps); + self.fts_max = self.fts_offset; + + // Start counting again from here + self.pts_set = PtsSet::Received; // Force min to be set again + self.sync_pts2fts_set = false; // Make note of the new conversion values + + // Avoid next async test - the gap might have occured on + // current_tref != 0. + self.sync_pts = self.current_pts + - self + .current_tref + .as_mpeg_clock_tick(timing_info.current_fps); + // Set min_pts = sync_pts as this is used for fts_now + self.min_pts = self.sync_pts; + + debug!( + msg_type = DebugMessageFlag::TIME; + "\nNew min PTS time: {} {:+}ms (time before this PTS)\n", + self.min_pts.as_timestamp().to_hms_millis_time(':').unwrap(), + self.fts_offset.millis() + ); + } + } + + // Set sync_pts, fts_offset + if self.current_tref.as_u64() == 0 { + self.sync_pts = self.current_pts; + } + + // Reset counters + timing_info.cb_field1 = 0; + timing_info.cb_field2 = 0; + timing_info.cb_708 = 0; + + // Avoid wrong "Calc. difference" and "Asynchronous by" numbers + // for uninitialized min_pts + if true { + // CFS: Remove or think decent condition + if self.pts_set != PtsSet::No { + // If pts_set is TRUE we have min_pts + self.fts_now = (self.current_pts - self.min_pts).as_timestamp() + self.fts_offset; + if !self.sync_pts2fts_set { + self.sync_pts2fts_pts = self.current_pts; + self.sync_pts2fts_fts = self.fts_now; + self.sync_pts2fts_set = true; + } + } else { + // No PTS info at all!! + info!("Set PTS called without any global timestamp set\n"); + return false; + } + } + if self.fts_now > self.fts_max { + self.fts_max = self.fts_now; + } + + // If PTS resets, then fix minimum_fts and fts_max + if self.pts_reset { + self.minimum_fts = Timestamp::from_millis(0); + self.fts_max = self.fts_now; + self.pts_reset = false; + } + + true + } + + /// Returns the total FTS. + /// + /// Caption block counters are included. + pub fn get_fts(&self, current_field: CaptionField) -> Timestamp { + let timing_info = GLOBAL_TIMING_INFO.read().unwrap(); + let count = match current_field { + CaptionField::Field1 => timing_info.cb_field1, + CaptionField::Field2 => timing_info.cb_field2, + CaptionField::Cea708 => timing_info.cb_708, + }; + self.fts_now + self.fts_global + Timestamp::from_millis((count as i64) * 1001 / 30) + } + + /// This returns the maximum FTS that belonged to a frame. + /// + /// Caption block counters are not applicable. + pub fn get_fts_max(&self) -> Timestamp { + self.fts_max + self.fts_global + } + + /// Log FTS and PTS information for debugging purpose. + pub fn print_debug_timing(&self) { + let zero = Timestamp::from_millis(0); + let timing_info = GLOBAL_TIMING_INFO.read().unwrap(); + + let gop_time = timing_info.gop_time.map(|x| x.timestamp()).unwrap_or(zero); + let first_gop_time = timing_info + .first_gop_time + .map(|x| x.timestamp()) + .unwrap_or(zero); + + // Avoid wrong "Calc. difference" and "Asynchronous by" numbers + // for uninitialized min_pts + let tempmin_pts = if self.min_pts.as_i64() == 0x01FFFFFFFF { + self.sync_pts + } else { + self.min_pts + }; + + info!( + "Sync time stamps: PTS: {} ", + self.sync_pts + .as_timestamp() + .to_hms_millis_time(':') + .unwrap() + ); + info!( + " GOP start FTS: {}\n", + gop_time.to_hms_millis_time(':').unwrap() + ); + + // Length first GOP to last GOP + let goplenms = gop_time - first_gop_time; + // Length at last sync point + let ptslenms = (self.sync_pts - tempmin_pts).as_timestamp() + self.fts_offset; + + info!( + "Last FTS: {}", + self.get_fts_max().to_hms_millis_time(':').unwrap() + ); + info!( + " GOP start FTS: {}\n", + timing_info + .fts_at_gop_start + .to_hms_millis_time(':') + .unwrap() + ); + + let one_frame = FrameCount::new(1).as_timestamp(timing_info.current_fps); + + // Times are based on last GOP and/or sync time + info!( + "Max FTS diff. to PTS: {:6}ms GOP: {:6}ms\n\n", + (self.get_fts_max() + one_frame - ptslenms) + .to_hms_millis_time(':') + .unwrap(), + (self.get_fts_max() + one_frame - goplenms) + .to_hms_millis_time(':') + .unwrap() + ); + } + + /// Constructs a [`TimingContext`] from its individual fields. + /// + /// # Safety + /// + /// Make sure that [`TimingContext`] is in a valid state. + #[allow(clippy::too_many_arguments)] + pub unsafe fn from_raw_parts( + pts_set: PtsSet, + min_pts_adjusted: bool, + current_pts: MpegClockTick, + current_picture_coding_type: FrameType, + current_tref: FrameCount, + min_pts: MpegClockTick, + sync_pts: MpegClockTick, + minimum_fts: Timestamp, + fts_now: Timestamp, + fts_offset: Timestamp, + fts_fc_offset: Timestamp, + fts_max: Timestamp, + fts_global: Timestamp, + sync_pts2fts_set: bool, + sync_pts2fts_fts: Timestamp, + sync_pts2fts_pts: MpegClockTick, + pts_reset: bool, + ) -> TimingContext { + TimingContext { + pts_set, + min_pts_adjusted, + current_pts, + current_picture_coding_type, + current_tref, + min_pts, + sync_pts, + minimum_fts, + fts_now, + fts_offset, + fts_fc_offset, + fts_max, + fts_global, + sync_pts2fts_set, + sync_pts2fts_fts, + sync_pts2fts_pts, + pts_reset, + } + } + + /// Returns the individual fields of a [`TimingContext`]. + /// + /// # Safety + /// + /// Certain fields are supposed to be private. + #[allow(clippy::type_complexity)] + pub unsafe fn as_raw_parts( + &self, + ) -> ( + PtsSet, + bool, + MpegClockTick, + FrameType, + FrameCount, + MpegClockTick, + MpegClockTick, + Timestamp, + Timestamp, + Timestamp, + Timestamp, + Timestamp, + Timestamp, + bool, + Timestamp, + MpegClockTick, + bool, + ) { + let TimingContext { + pts_set, + min_pts_adjusted, + current_pts, + current_picture_coding_type, + current_tref, + min_pts, + sync_pts, + minimum_fts, + fts_now, + fts_offset, + fts_fc_offset, + fts_max, + fts_global, + sync_pts2fts_set, + sync_pts2fts_fts, + sync_pts2fts_pts, + pts_reset, + } = *self; + + ( + pts_set, + min_pts_adjusted, + current_pts, + current_picture_coding_type, + current_tref, + min_pts, + sync_pts, + minimum_fts, + fts_now, + fts_offset, + fts_fc_offset, + fts_max, + fts_global, + sync_pts2fts_set, + sync_pts2fts_fts, + sync_pts2fts_pts, + pts_reset, + ) + } +} + +impl GlobalTimingInfo { + /// Create a new instance of [`GlobalTimingInfo`]. + const fn new() -> GlobalTimingInfo { + GlobalTimingInfo { + cb_field1: 0, + cb_field2: 0, + cb_708: 0, + pts_big_change: false, + current_fps: DEFAULT_FRAME_RATE, // 29.97 + // TODO: Get from framerates_values[] instead + frames_since_ref_time: FrameCount::new(0), + total_frames_count: FrameCount::new(0), + gop_time: None, + first_gop_time: None, + fts_at_gop_start: Timestamp::from_millis(0), + gop_rollover: false, + timing_settings: TimingSettings { + disable_sync_check: false, + no_sync: false, + is_elementary_stream: false, + }, + } + } +} + +impl Default for TimingContext { + fn default() -> Self { + Self::new() + } +} diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index f2d7ba171..9fbce6bb3 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -1,173 +1,185 @@ -//! Rust library for CCExtractor -//! -//! Currently we are in the process of porting the 708 decoder to rust. See [decoder] - -// Allow C naming style -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] - -/// CCExtractor C bindings generated by bindgen -#[allow(clippy::all)] -pub mod bindings { - include!(concat!(env!("OUT_DIR"), "/bindings.rs")); -} -pub mod decoder; -#[cfg(feature = "hardsubx_ocr")] -pub mod hardsubx; -pub mod libccxr_exports; -pub mod utils; - -#[cfg(windows)] -use std::os::windows::io::{FromRawHandle, RawHandle}; -use std::{io::Write, os::raw::c_int}; - -use bindings::*; -use decoder::Dtvcc; -use utils::is_true; - -use env_logger::{builder, Target}; -use log::{warn, LevelFilter}; - -extern "C" { - static mut cb_708: c_int; - static mut cb_field1: c_int; - static mut cb_field2: c_int; - static mut ccx_options: ccx_s_options; -} - -/// Initialize env logger with custom format, using stdout as target -#[no_mangle] -pub extern "C" fn ccxr_init_logger() { - builder() - .format(|buf, record| writeln!(buf, "[CEA-708] {}", record.args())) - .filter_level(LevelFilter::Debug) - .target(Target::Stdout) - .init(); -} - -/// Process cc_data -/// -/// # Safety -/// dec_ctx should not be a null pointer -/// data should point to cc_data of length cc_count -#[no_mangle] -extern "C" fn ccxr_process_cc_data( - dec_ctx: *mut lib_cc_decode, - data: *const ::std::os::raw::c_uchar, - cc_count: c_int, -) -> c_int { - let mut ret = -1; - let mut cc_data: Vec = (0..cc_count * 3) - .map(|x| unsafe { *data.add(x as usize) }) - .collect(); - let dec_ctx = unsafe { &mut *dec_ctx }; - let dtvcc_ctx = unsafe { &mut *dec_ctx.dtvcc }; - let mut dtvcc = Dtvcc::new(dtvcc_ctx); - for cc_block in cc_data.chunks_exact_mut(3) { - if !validate_cc_pair(cc_block) { - continue; - } - let success = do_cb(dec_ctx, &mut dtvcc, cc_block); - if success { - ret = 0; - } - } - ret -} - -/// Returns `true` if cc_block pair is valid -/// -/// For CEA-708 data, only cc_valid is checked -/// For CEA-608 data, parity is also checked -pub fn validate_cc_pair(cc_block: &mut [u8]) -> bool { - let cc_valid = (cc_block[0] & 4) >> 2; - let cc_type = cc_block[0] & 3; - if cc_valid == 0 { - return false; - } - if cc_type == 0 || cc_type == 1 { - // For CEA-608 data we verify parity. - if verify_parity(cc_block[2]) { - // If the second byte doesn't pass parity, ignore pair - return false; - } - if verify_parity(cc_block[1]) { - // If the first byte doesn't pass parity, - // we replace it with a solid blank and process the pair. - cc_block[1] = 0x7F; - } - } - true -} - -/// Returns `true` if data has odd parity -/// -/// CC uses odd parity (i.e., # of 1's in byte is odd.) -pub fn verify_parity(data: u8) -> bool { - if data.count_ones() & 1 == 1 { - return true; - } - false -} - -/// Process CC data according to its type -pub fn do_cb(ctx: &mut lib_cc_decode, dtvcc: &mut Dtvcc, cc_block: &[u8]) -> bool { - let cc_valid = (cc_block[0] & 4) >> 2; - let cc_type = cc_block[0] & 3; - let mut timeok = true; - - if ctx.write_format != ccx_output_format::CCX_OF_DVDRAW - && ctx.write_format != ccx_output_format::CCX_OF_RAW - && (cc_block[0] == 0xFA || cc_block[0] == 0xFC || cc_block[0] == 0xFD) - && (cc_block[1] & 0x7F) == 0 - && (cc_block[2] & 0x7F) == 0 - { - return true; - } - - if cc_valid == 1 || cc_type == 3 { - ctx.cc_stats[cc_type as usize] += 1; - match cc_type { - // Type 0 and 1 are for CEA-608 data. Handled by C code, do nothing - 0 | 1 => {} - // Type 2 and 3 are for CEA-708 data. - 2 | 3 => { - let current_time = unsafe { (*ctx.timing).get_fts(ctx.current_field as u8) }; - ctx.current_field = 3; - - // Check whether current time is within start and end bounds - if is_true(ctx.extraction_start.set) - && current_time < ctx.extraction_start.time_in_ms - { - timeok = false; - } - if is_true(ctx.extraction_end.set) && current_time > ctx.extraction_end.time_in_ms { - timeok = false; - ctx.processed_enough = 1; - } - - if timeok && ctx.write_format != ccx_output_format::CCX_OF_RAW { - dtvcc.process_cc_data(cc_valid, cc_type, cc_block[1], cc_block[2]); - } - unsafe { cb_708 += 1 } - } - _ => warn!("Invalid cc_type"), - } - } - true -} - -#[cfg(windows)] -#[no_mangle] -extern "C" fn ccxr_close_handle(handle: RawHandle) { - use std::fs::File; - - if handle.is_null() { - return; - } - unsafe { - // File will close automatically (due to Drop) once it goes out of scope - let _file = File::from_raw_handle(handle); - } -} +//! Rust library for CCExtractor +//! +//! Currently we are in the process of porting the 708 decoder to rust. See [decoder] + +// Allow C naming style +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +/// CCExtractor C bindings generated by bindgen +#[allow(clippy::all)] +pub mod bindings { + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} +pub mod decoder; +#[cfg(feature = "hardsubx_ocr")] +pub mod hardsubx; +pub mod libccxr_exports; +pub mod utils; + +#[cfg(windows)] +use std::os::windows::io::{FromRawHandle, RawHandle}; +use std::{ + io::Write, + os::raw::{c_double, c_int, c_long, c_uint}, +}; + +use bindings::*; +use decoder::Dtvcc; +use utils::is_true; + +use env_logger::{builder, Target}; +use log::{warn, LevelFilter}; + +extern "C" { + static mut cb_708: c_int; + static mut cb_field1: c_int; + static mut cb_field2: c_int; + static mut pts_big_change: c_uint; + static mut current_fps: c_double; + static mut frames_since_ref_time: c_int; + static mut total_frames_count: c_uint; + static mut gop_time: gop_time_code; + static mut first_gop_time: gop_time_code; + static mut fts_at_gop_start: c_long; + static mut gop_rollover: c_int; + static mut ccx_common_timing_settings: ccx_common_timing_settings_t; + static mut ccx_options: ccx_s_options; +} + +/// Initialize env logger with custom format, using stdout as target +#[no_mangle] +pub extern "C" fn ccxr_init_logger() { + builder() + .format(|buf, record| writeln!(buf, "[CEA-708] {}", record.args())) + .filter_level(LevelFilter::Debug) + .target(Target::Stdout) + .init(); +} + +/// Process cc_data +/// +/// # Safety +/// dec_ctx should not be a null pointer +/// data should point to cc_data of length cc_count +#[no_mangle] +extern "C" fn ccxr_process_cc_data( + dec_ctx: *mut lib_cc_decode, + data: *const ::std::os::raw::c_uchar, + cc_count: c_int, +) -> c_int { + let mut ret = -1; + let mut cc_data: Vec = (0..cc_count * 3) + .map(|x| unsafe { *data.add(x as usize) }) + .collect(); + let dec_ctx = unsafe { &mut *dec_ctx }; + let dtvcc_ctx = unsafe { &mut *dec_ctx.dtvcc }; + let mut dtvcc = Dtvcc::new(dtvcc_ctx); + for cc_block in cc_data.chunks_exact_mut(3) { + if !validate_cc_pair(cc_block) { + continue; + } + let success = do_cb(dec_ctx, &mut dtvcc, cc_block); + if success { + ret = 0; + } + } + ret +} + +/// Returns `true` if cc_block pair is valid +/// +/// For CEA-708 data, only cc_valid is checked +/// For CEA-608 data, parity is also checked +pub fn validate_cc_pair(cc_block: &mut [u8]) -> bool { + let cc_valid = (cc_block[0] & 4) >> 2; + let cc_type = cc_block[0] & 3; + if cc_valid == 0 { + return false; + } + if cc_type == 0 || cc_type == 1 { + // For CEA-608 data we verify parity. + if verify_parity(cc_block[2]) { + // If the second byte doesn't pass parity, ignore pair + return false; + } + if verify_parity(cc_block[1]) { + // If the first byte doesn't pass parity, + // we replace it with a solid blank and process the pair. + cc_block[1] = 0x7F; + } + } + true +} + +/// Returns `true` if data has odd parity +/// +/// CC uses odd parity (i.e., # of 1's in byte is odd.) +pub fn verify_parity(data: u8) -> bool { + if data.count_ones() & 1 == 1 { + return true; + } + false +} + +/// Process CC data according to its type +pub fn do_cb(ctx: &mut lib_cc_decode, dtvcc: &mut Dtvcc, cc_block: &[u8]) -> bool { + let cc_valid = (cc_block[0] & 4) >> 2; + let cc_type = cc_block[0] & 3; + let mut timeok = true; + + if ctx.write_format != ccx_output_format::CCX_OF_DVDRAW + && ctx.write_format != ccx_output_format::CCX_OF_RAW + && (cc_block[0] == 0xFA || cc_block[0] == 0xFC || cc_block[0] == 0xFD) + && (cc_block[1] & 0x7F) == 0 + && (cc_block[2] & 0x7F) == 0 + { + return true; + } + + if cc_valid == 1 || cc_type == 3 { + ctx.cc_stats[cc_type as usize] += 1; + match cc_type { + // Type 0 and 1 are for CEA-608 data. Handled by C code, do nothing + 0 | 1 => {} + // Type 2 and 3 are for CEA-708 data. + 2 | 3 => { + let current_time = unsafe { (*ctx.timing).get_fts(ctx.current_field as u8) }; + ctx.current_field = 3; + + // Check whether current time is within start and end bounds + if is_true(ctx.extraction_start.set) + && current_time < ctx.extraction_start.time_in_ms + { + timeok = false; + } + if is_true(ctx.extraction_end.set) && current_time > ctx.extraction_end.time_in_ms { + timeok = false; + ctx.processed_enough = 1; + } + + if timeok && ctx.write_format != ccx_output_format::CCX_OF_RAW { + dtvcc.process_cc_data(cc_valid, cc_type, cc_block[1], cc_block[2]); + } + unsafe { cb_708 += 1 } + } + _ => warn!("Invalid cc_type"), + } + } + true +} + +#[cfg(windows)] +#[no_mangle] +extern "C" fn ccxr_close_handle(handle: RawHandle) { + use std::fs::File; + + if handle.is_null() { + return; + } + unsafe { + // File will close automatically (due to Drop) once it goes out of scope + let _file = File::from_raw_handle(handle); + } +} diff --git a/src/rust/src/libccxr_exports/time.rs b/src/rust/src/libccxr_exports/time.rs index 0938e936b..0a96e2be9 100644 --- a/src/rust/src/libccxr_exports/time.rs +++ b/src/rust/src/libccxr_exports/time.rs @@ -1,10 +1,16 @@ #![allow(clippy::useless_conversion)] -use crate::bindings::*; +use crate::{ + bindings::*, cb_708, cb_field1, cb_field2, ccx_common_timing_settings as timing_settings, + current_fps, first_gop_time, frames_since_ref_time, fts_at_gop_start, gop_rollover, gop_time, + pts_big_change, total_frames_count, +}; +use std::convert::TryInto; use std::ffi::CStr; -use std::os::raw::{c_char, c_int}; +use std::os::raw::{c_char, c_int, c_long}; +use lib_ccxr::common::FrameType; use lib_ccxr::util::time::{c_functions as c, *}; /// Helper function that converts a Rust-String (`string`) to C-String (`buffer`). @@ -105,3 +111,419 @@ pub unsafe extern "C" fn ccxr_stringztoms(s: *const c_char, bt: *mut ccx_boundar -1 } + +/// Construct a [`TimingContext`] from a pointer of `ccx_common_timing_ctx`. +/// +/// It is used to move data of [`TimingContext`] from C to Rust. +/// +/// # Safety +/// +/// `ctx` should not be null. +unsafe fn generate_timing_context(ctx: *const ccx_common_timing_ctx) -> TimingContext { + let pts_set = match (*ctx).pts_set { + 0 => PtsSet::No, + 1 => PtsSet::Received, + 2 => PtsSet::MinPtsSet, + _ => panic!("incorrect value for pts_set"), + }; + + let min_pts_adjusted = (*ctx).min_pts_adjusted != 0; + let current_pts = MpegClockTick::new((*ctx).current_pts); + + let current_picture_coding_type = match (*ctx).current_picture_coding_type { + ccx_frame_type::CCX_FRAME_TYPE_RESET_OR_UNKNOWN => FrameType::ResetOrUnknown, + ccx_frame_type::CCX_FRAME_TYPE_I_FRAME => FrameType::IFrame, + ccx_frame_type::CCX_FRAME_TYPE_P_FRAME => FrameType::PFrame, + ccx_frame_type::CCX_FRAME_TYPE_B_FRAME => FrameType::BFrame, + ccx_frame_type::CCX_FRAME_TYPE_D_FRAME => FrameType::DFrame, + }; + + let current_tref = FrameCount::new((*ctx).current_tref.try_into().unwrap()); + let min_pts = MpegClockTick::new((*ctx).min_pts); + let sync_pts = MpegClockTick::new((*ctx).sync_pts); + let minimum_fts = Timestamp::from_millis((*ctx).minimum_fts); + let fts_now = Timestamp::from_millis((*ctx).fts_now); + let fts_offset = Timestamp::from_millis((*ctx).fts_offset); + let fts_fc_offset = Timestamp::from_millis((*ctx).fts_fc_offset); + let fts_max = Timestamp::from_millis((*ctx).fts_max); + let fts_global = Timestamp::from_millis((*ctx).fts_global); + let sync_pts2fts_set = (*ctx).sync_pts2fts_set != 0; + let sync_pts2fts_fts = Timestamp::from_millis((*ctx).sync_pts2fts_fts); + let sync_pts2fts_pts = MpegClockTick::new((*ctx).sync_pts2fts_pts); + let pts_reset = (*ctx).pts_reset != 0; + + TimingContext::from_raw_parts( + pts_set, + min_pts_adjusted, + current_pts, + current_picture_coding_type, + current_tref, + min_pts, + sync_pts, + minimum_fts, + fts_now, + fts_offset, + fts_fc_offset, + fts_max, + fts_global, + sync_pts2fts_set, + sync_pts2fts_fts, + sync_pts2fts_pts, + pts_reset, + ) +} + +/// Copy the contents [`TimingContext`] to a `ccx_common_timing_ctx`. +/// +/// It is used to move data of [`TimingContext`] from Rust to C. +/// +/// # Safety +/// +/// `ctx` should not be null. +unsafe fn write_back_to_common_timing_ctx( + ctx: *mut ccx_common_timing_ctx, + timing_ctx: &TimingContext, +) { + let ( + pts_set, + min_pts_adjusted, + current_pts, + current_picture_coding_type, + current_tref, + min_pts, + sync_pts, + minimum_fts, + fts_now, + fts_offset, + fts_fc_offset, + fts_max, + fts_global, + sync_pts2fts_set, + sync_pts2fts_fts, + sync_pts2fts_pts, + pts_reset, + ) = timing_ctx.as_raw_parts(); + + (*ctx).pts_set = match pts_set { + PtsSet::No => 0, + PtsSet::Received => 1, + PtsSet::MinPtsSet => 2, + }; + + (*ctx).min_pts_adjusted = if min_pts_adjusted { 1 } else { 0 }; + (*ctx).current_pts = current_pts.as_i64(); + + (*ctx).current_picture_coding_type = match current_picture_coding_type { + FrameType::ResetOrUnknown => ccx_frame_type::CCX_FRAME_TYPE_RESET_OR_UNKNOWN, + FrameType::IFrame => ccx_frame_type::CCX_FRAME_TYPE_I_FRAME, + FrameType::PFrame => ccx_frame_type::CCX_FRAME_TYPE_P_FRAME, + FrameType::BFrame => ccx_frame_type::CCX_FRAME_TYPE_B_FRAME, + FrameType::DFrame => ccx_frame_type::CCX_FRAME_TYPE_D_FRAME, + }; + + (*ctx).current_tref = current_tref.as_u64().try_into().unwrap(); + (*ctx).min_pts = min_pts.as_i64(); + (*ctx).sync_pts = sync_pts.as_i64(); + (*ctx).minimum_fts = minimum_fts.millis(); + (*ctx).fts_now = fts_now.millis(); + (*ctx).fts_offset = fts_offset.millis(); + (*ctx).fts_fc_offset = fts_fc_offset.millis(); + (*ctx).fts_max = fts_max.millis(); + (*ctx).fts_global = fts_global.millis(); + (*ctx).sync_pts2fts_set = if sync_pts2fts_set { 1 } else { 0 }; + (*ctx).sync_pts2fts_fts = sync_pts2fts_fts.millis(); + (*ctx).sync_pts2fts_pts = sync_pts2fts_pts.as_i64(); + (*ctx).pts_reset = if pts_reset { 1 } else { 0 }; +} + +/// Write to [`GLOBAL_TIMING_INFO`] from the equivalent static variables in C. +/// +/// It is used to move data of [`GLOBAL_TIMING_INFO`] from C to Rust. +/// +/// # Safety +/// +/// All the static variables should be initialized and in valid state. +unsafe fn apply_timing_info() { + let mut timing_info = GLOBAL_TIMING_INFO.write().unwrap(); + + timing_info.cb_field1 = cb_field1.try_into().unwrap(); + timing_info.cb_field2 = cb_field2.try_into().unwrap(); + timing_info.cb_708 = cb_708.try_into().unwrap(); + timing_info.pts_big_change = pts_big_change != 0; + timing_info.current_fps = current_fps; + timing_info.frames_since_ref_time = FrameCount::new(frames_since_ref_time.try_into().unwrap()); + timing_info.total_frames_count = FrameCount::new(total_frames_count.try_into().unwrap()); + timing_info.gop_time = generate_gop_time_code(gop_time); + timing_info.first_gop_time = generate_gop_time_code(first_gop_time); + timing_info.fts_at_gop_start = Timestamp::from_millis(fts_at_gop_start.try_into().unwrap()); + timing_info.gop_rollover = gop_rollover != 0; + timing_info.timing_settings.disable_sync_check = timing_settings.disable_sync_check != 0; + timing_info.timing_settings.no_sync = timing_settings.no_sync != 0; + timing_info.timing_settings.is_elementary_stream = timing_settings.is_elementary_stream != 0; +} + +/// Write from [`GLOBAL_TIMING_INFO`] to the equivalent static variables in C. +/// +/// It is used to move data of [`GLOBAL_TIMING_INFO`] from Rust to C. +/// +/// # Safety +/// +/// All the static variables should be initialized and in valid state. +unsafe fn write_back_from_timing_info() { + let timing_info = GLOBAL_TIMING_INFO.read().unwrap(); + + cb_field1 = timing_info.cb_field1.try_into().unwrap(); + cb_field2 = timing_info.cb_field2.try_into().unwrap(); + cb_708 = timing_info.cb_708.try_into().unwrap(); + pts_big_change = if timing_info.pts_big_change { 1 } else { 0 }; + current_fps = timing_info.current_fps; + frames_since_ref_time = timing_info + .frames_since_ref_time + .as_u64() + .try_into() + .unwrap(); + total_frames_count = timing_info.total_frames_count.as_u64().try_into().unwrap(); + gop_time = write_gop_time_code(timing_info.gop_time); + first_gop_time = write_gop_time_code(timing_info.first_gop_time); + fts_at_gop_start = timing_info.fts_at_gop_start.millis().try_into().unwrap(); + gop_rollover = if timing_info.gop_rollover { 1 } else { 0 }; + timing_settings.disable_sync_check = if timing_info.timing_settings.disable_sync_check { + 1 + } else { + 0 + }; + timing_settings.no_sync = if timing_info.timing_settings.no_sync { + 1 + } else { + 0 + }; + timing_settings.is_elementary_stream = if timing_info.timing_settings.is_elementary_stream { + 1 + } else { + 0 + }; +} + +/// Construct a [`GopTimeCode`] from `gop_time_code`. +unsafe fn generate_gop_time_code(g: gop_time_code) -> Option { + if g.inited == 0 { + None + } else { + Some(GopTimeCode::from_raw_parts( + g.drop_frame_flag != 0, + g.time_code_hours.try_into().unwrap(), + g.time_code_minutes.try_into().unwrap(), + g.time_code_seconds.try_into().unwrap(), + g.time_code_pictures.try_into().unwrap(), + Timestamp::from_millis(g.ms), + )) + } +} + +/// Construct a `gop_time_code` from [`GopTimeCode`]. +unsafe fn write_gop_time_code(g: Option) -> gop_time_code { + if let Some(gop) = g { + let ( + drop_frame, + time_code_hours, + time_code_minutes, + time_code_seconds, + time_code_pictures, + timestamp, + ) = gop.as_raw_parts(); + + gop_time_code { + drop_frame_flag: if drop_frame { 1 } else { 0 }, + time_code_hours: time_code_hours.try_into().unwrap(), + time_code_minutes: time_code_minutes.try_into().unwrap(), + marker_bit: 0, + time_code_seconds: time_code_seconds.try_into().unwrap(), + time_code_pictures: time_code_pictures.try_into().unwrap(), + inited: 1, + ms: timestamp.millis(), + } + } else { + gop_time_code { + drop_frame_flag: 0, + time_code_hours: 0, + time_code_minutes: 0, + marker_bit: 0, + time_code_seconds: 0, + time_code_pictures: 0, + inited: 0, + ms: 0, + } + } +} + +/// Rust equivalent for `add_current_pts` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `ctx` must not be null. +#[no_mangle] +pub unsafe extern "C" fn ccxr_add_current_pts(ctx: *mut ccx_common_timing_ctx, pts: c_long) { + apply_timing_info(); + let mut context = generate_timing_context(ctx); + + c::add_current_pts(&mut context, MpegClockTick::new(pts.try_into().unwrap())); + + write_back_to_common_timing_ctx(ctx, &context); + write_back_from_timing_info(); +} + +/// Rust equivalent for `set_current_pts` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `ctx` must not be null. +#[no_mangle] +pub unsafe extern "C" fn ccxr_set_current_pts(ctx: *mut ccx_common_timing_ctx, pts: c_long) { + apply_timing_info(); + let mut context = generate_timing_context(ctx); + + c::set_current_pts(&mut context, MpegClockTick::new(pts.try_into().unwrap())); + + write_back_to_common_timing_ctx(ctx, &context); + write_back_from_timing_info(); +} + +/// Rust equivalent for `set_fts` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `ctx` must not be null. +#[no_mangle] +pub unsafe extern "C" fn ccxr_set_fts(ctx: *mut ccx_common_timing_ctx) -> c_int { + apply_timing_info(); + let mut context = generate_timing_context(ctx); + + let ans = c::set_fts(&mut context); + + write_back_to_common_timing_ctx(ctx, &context); + write_back_from_timing_info(); + + if ans { + 1 + } else { + 0 + } +} + +/// Rust equivalent for `get_fts` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `ctx` must not be null. `current_field` must be 1, 2 or 3. +#[no_mangle] +pub unsafe extern "C" fn ccxr_get_fts( + ctx: *mut ccx_common_timing_ctx, + current_field: c_int, +) -> c_long { + apply_timing_info(); + let mut context = generate_timing_context(ctx); + + let caption_field = match current_field { + 1 => CaptionField::Field1, + 2 => CaptionField::Field2, + 3 => CaptionField::Cea708, + _ => panic!("incorrect value for caption field"), + }; + + let ans = c::get_fts(&mut context, caption_field); + + write_back_to_common_timing_ctx(ctx, &context); + write_back_from_timing_info(); + + ans.millis().try_into().unwrap() +} + +/// Rust equivalent for `get_fts_max` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `ctx` must not be null. +#[no_mangle] +pub unsafe extern "C" fn ccxr_get_fts_max(ctx: *mut ccx_common_timing_ctx) -> c_long { + apply_timing_info(); + let mut context = generate_timing_context(ctx); + + let ans = c::get_fts_max(&mut context); + + write_back_to_common_timing_ctx(ctx, &context); + write_back_from_timing_info(); + + ans.millis().try_into().unwrap() +} + +/// Rust equivalent for `print_mstime_static` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `buf` must not be null. It must have sufficient length to hold the time in string form. +#[no_mangle] +pub unsafe extern "C" fn ccxr_print_mstime_static(mstime: c_long, buf: *mut c_char) -> *mut c_char { + let time = Timestamp::from_millis(mstime.try_into().unwrap()); + let ans = c::print_mstime_static(time, ':'); + write_string_into_pointer(buf, &ans); + buf +} + +/// Rust equivalent for `print_debug_timing` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `ctx` must not be null. +#[no_mangle] +pub unsafe extern "C" fn ccxr_print_debug_timing(ctx: *mut ccx_common_timing_ctx) { + apply_timing_info(); + let mut context = generate_timing_context(ctx); + + c::print_debug_timing(&mut context); + + write_back_to_common_timing_ctx(ctx, &context); + write_back_from_timing_info(); +} + +/// Rust equivalent for `calculate_ms_gop_time` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `g` must not be null. +#[no_mangle] +pub unsafe extern "C" fn ccxr_calculate_ms_gop_time(g: *mut gop_time_code) { + apply_timing_info(); + let timing_info = GLOBAL_TIMING_INFO.read().unwrap(); + + (*g).ms = GopTimeCode::new( + (*g).drop_frame_flag != 0, + (*g).time_code_hours.try_into().unwrap(), + (*g).time_code_minutes.try_into().unwrap(), + (*g).time_code_seconds.try_into().unwrap(), + (*g).time_code_pictures.try_into().unwrap(), + timing_info.current_fps, + timing_info.gop_rollover, + ) + .unwrap() + .timestamp() + .millis() +} + +/// Rust equivalent for `gop_accepted` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `g` must not be null. +#[no_mangle] +pub unsafe extern "C" fn ccxr_gop_accepted(g: *mut gop_time_code) -> c_int { + if let Some(gop) = generate_gop_time_code(*g) { + let ans = c::gop_accepted(gop); + if ans { + 1 + } else { + 0 + } + } else { + 0 + } +}