diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index 4bb225cfb..cd3349ecb 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Install dependencies run: sudo apt update && sudo apt-get install libgpac-dev libtesseract-dev - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: build run: ./build working-directory: ./linux @@ -38,7 +38,7 @@ jobs: run: mkdir ./linux/artifacts - name: Copy release artifact run: cp ./linux/ccextractor ./linux/artifacts/ - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: CCExtractor Linux build path: ./linux/artifacts @@ -47,7 +47,7 @@ jobs: steps: - name: Install dependencies run: sudo apt update && sudo apt-get install libgpac-dev - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: run autogen run: ./autogen.sh working-directory: ./linux @@ -65,7 +65,7 @@ jobs: steps: - name: Install dependencies run: sudo apt update && sudo apt-get install libgpac-dev - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: cmake run: mkdir build && cd build && cmake ../src - name: build @@ -76,7 +76,7 @@ jobs: cmake_ocr_hardsubx: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies run: sudo apt update && sudo apt install libgpac-dev libtesseract-dev libavformat-dev libavdevice-dev libswscale-dev yasm - name: cmake @@ -94,9 +94,9 @@ jobs: steps: - name: Install dependencies run: sudo apt update && sudo apt-get install libgpac-dev - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | src/rust/.cargo/registry diff --git a/.github/workflows/build_mac.yml b/.github/workflows/build_mac.yml index 219704e6a..c654aceac 100644 --- a/.github/workflows/build_mac.yml +++ b/.github/workflows/build_mac.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Install dependencies run: brew install pkg-config autoconf automake libtool tesseract leptonica gpac - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: build run: ./build.command working-directory: ./mac @@ -38,14 +38,14 @@ jobs: run: mkdir ./mac/artifacts - name: Copy release artifact run: cp ./mac/ccextractor ./mac/artifacts/ - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: CCExtractor mac build path: ./mac/artifacts build_autoconf: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies run: brew install pkg-config autoconf automake libtool gpac - name: run autogen @@ -63,10 +63,10 @@ jobs: cmake: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: dependencies run: brew install gpac - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: cmake run: mkdir build && cd build && cmake ../src - name: build @@ -77,7 +77,7 @@ jobs: cmake_ocr_hardsubx: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies run: brew install pkg-config autoconf automake libtool tesseract leptonica gpac ffmpeg - name: cmake @@ -93,9 +93,9 @@ jobs: build_rust: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | src/rust/.cargo/registry diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index f3877c027..1297de86c 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -3,6 +3,8 @@ name: Build CCExtractor on Windows env: RUSTFLAGS: -Ctarget-feature=+crt-static VCPKG_DEFAULT_TRIPLET: x64-windows-static + VCPKG_DEFAULT_BINARY_CACHE: C:\vcpkg\.cache + VCPKG_COMMIT: fba75d09065fcc76a25dcf386b1d00d33f5175af on: workflow_dispatch: @@ -22,65 +24,83 @@ on: jobs: build_release: - runs-on: windows-2019 + runs-on: windows-2022 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup MSBuild.exe uses: microsoft/setup-msbuild@v1.3.1 - - name: Install llvm and clang - run: choco install llvm gpac + with: + msbuild-architecture: x64 + - name: Install gpac + run: choco install gpac --version 2.4.0 + - name: Setup vcpkg + run: mkdir C:\vcpkg\.cache + - name: Cache vcpkg + id: cache + uses: actions/cache@v3 + with: + path: | + C:\vcpkg\.cache + key: vcpkg-${{ runner.os }}-${{ env.VCPKG_COMMIT }} + - name: Build vcpkg + run: | + git clone https://github.com/microsoft/vcpkg + ./vcpkg/bootstrap-vcpkg.bat + - name: Install dependencies + run: ${{ github.workspace }}/vcpkg/vcpkg.exe install --x-install-root ${{ github.workspace }}/vcpkg/installed/ + working-directory: windows - uses: actions-rs/toolchain@v1 with: toolchain: stable override: true - name: Install Win 10 SDK uses: ilammy/msvc-dev-cmd@v1 - - name: Setup Vcpkg - id: vcpkg - uses: friendlyanon/setup-vcpkg@v1 - with: - committish: "2023.06.20" - cache-version: "2" - ignore-reserve-cache-error: true - - name: Install dependencies - run: cd vcpkg && vcpkg integrate install && vcpkg install leptonica tesseract ffmpeg --triplet x64-windows-static - name: build Release-Full env: LIBCLANG_PATH: "C:\\Program Files\\LLVM\\lib" LLVM_CONFIG_PATH: "C:\\Program Files\\LLVM\\bin\\llvm-config" CARGO_TARGET_DIR: "..\\..\\windows" BINDGEN_EXTRA_CLANG_ARGS: -fmsc-version=0 - VCPKG_ROOT: ${{ github.workspace }}\vcpkg + VCPKG_ROOT: ${{ github.workspace }}/vcpkg run: msbuild ccextractor.sln /p:Configuration=Release-Full /p:Platform=x64 working-directory: ./windows - name: Display version information run: ./ccextractorwinfull.exe --version working-directory: ./windows/x64/Release-Full - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: CCExtractor Windows Release build path: | ./windows/x64/Release-Full/ccextractorwinfull.exe ./windows/x64/Release-Full/*.dll build_debug: - runs-on: windows-2019 + runs-on: windows-2022 steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup MSBuild.exe uses: microsoft/setup-msbuild@v1.3.1 - - name: Install llvm and clang - run: choco install llvm gpac - - name: Setup Vcpkg - id: vcpkg - uses: friendlyanon/setup-vcpkg@v1 with: - committish: "2023.06.20" - cache-version: "2" - ignore-reserve-cache-error: true + msbuild-architecture: x64 + - name: Install gpac + run: choco install gpac --version 2.4.0 + - name: Setup vcpkg + run: mkdir C:\vcpkg\.cache + - name: Cache vcpkg + id: cache + uses: actions/cache@v3 + with: + path: | + C:\vcpkg\.cache + key: vcpkg-${{ runner.os }}-${{ env.VCPKG_COMMIT }} + - name: Build vcpkg + run: | + git clone https://github.com/microsoft/vcpkg + ./vcpkg/bootstrap-vcpkg.bat - name: Install dependencies - run: cd vcpkg && vcpkg integrate install && vcpkg install leptonica tesseract ffmpeg --triplet x64-windows-static + run: ${{ github.workspace }}/vcpkg/vcpkg.exe install --x-install-root ${{ github.workspace }}/vcpkg/installed/ + working-directory: windows - uses: actions-rs/toolchain@v1 with: toolchain: stable @@ -93,13 +113,14 @@ jobs: LLVM_CONFIG_PATH: "C:\\Program Files\\LLVM\\bin\\llvm-config" CARGO_TARGET_DIR: "..\\..\\windows" BINDGEN_EXTRA_CLANG_ARGS: -fmsc-version=0 - VCPKG_ROOT: ${{ github.workspace }}\vcpkg + VCPKG_ROOT: ${{ github.workspace }}/vcpkg run: msbuild ccextractor.sln /p:Configuration=Debug-Full /p:Platform=x64 working-directory: ./windows - name: Display version information + continue-on-error: true run: ./ccextractorwinfull.exe --version working-directory: ./windows/x64/Debug-Full - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: CCExtractor Windows Debug build path: | diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 0cc02cb51..7a3d84f1d 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -19,7 +19,7 @@ jobs: format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Format code run: | find src/ -type f -not -path "src/thirdparty/*" -not -path "src/lib_ccx/zvbi/*" -name '*.c' -not -path "src/GUI/icon_data.c" | xargs clang-format -i @@ -30,9 +30,9 @@ jobs: run: working-directory: ./src/rust steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | src/rust/.cargo/registry diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a2d2f147..7fe41c218 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,13 +10,13 @@ jobs: runs-on: windows-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Get the version id: get_version run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/} shell: bash - name: Setup MSBuild.exe - uses: microsoft/setup-msbuild@v1.3.1 + uses: microsoft/setup-msbuild@v2.0.0 - name: Install llvm and clang uses: egor-tensin/setup-clang@v1 with: @@ -62,20 +62,20 @@ jobs: run: wix build -ext "$HOME\.wix\extensions\WixToolset.UI.wixext\4.0.0-preview.0\tools\WixToolset.UI.wixext.dll" -d "AppVersion=${{ steps.get_version.outputs.VERSION }}.0.0" -o CCExtractor.msi installer.wxs working-directory: ./windows - name: Upload as asset - uses: AButler/upload-release-assets@v2.0 + uses: AButler/upload-release-assets@v3.0 with: files: './windows/CCExtractor.msi;./windows/CCExtractor_win_portable.zip' repo-token: ${{ secrets.GITHUB_TOKEN }} create_linux_package: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: path: ./ccextractor - name: Create .tar.gz without git and windows folders run: tar -pczf ./ccextractor_minimal.tar.gz --exclude "ccextractor/windows" --exclude "ccextractor/.git" ccextractor - name: Upload as asset - uses: AButler/upload-release-assets@v2.0 + uses: AButler/upload-release-assets@v3.0 with: files: './ccextractor_minimal.tar.gz' repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..60fdb744e --- /dev/null +++ b/docker/README.md @@ -0,0 +1,61 @@ +# CCExtractor Docker image + +This dockerfile prepares a minimalist Docker image with CCExtractor. It compiles CCExtractor from sources following instructions from the [Compilation Guide](https://github.com/CCExtractor/ccextractor/blob/master/docs/COMPILATION.MD). + +You can install the latest build of this image by running `docker pull CCExtractor/ccextractor` + +## Build + +You can build the Docker image directly from the Dockerfile provided in [docker](https://github.com/CCExtractor/ccextractor/tree/master/docker) directory of CCExtractor source + +```bash +$ git clone https://github.com/CCExtractor/ccextractor.git && cd ccextractor +$ cd docker/ +$ docker build -t ccextractor . +``` + +## Usage + +The CCExtractor Docker image can be used in several ways, depending on your needs. + +```bash +# General usage +$ docker run ccextractor:latest +``` + +1. Process a local file & use `-o` flag + +To process a local video file, mount a directory containing the input file inside the container: + +```bash +# Use `-o` to specifying output file +$ docker run --rm -v $(pwd):$(pwd) -w $(pwd) ccextractor:latest input.mp4 -o output.srt + +# Alternatively use `--stdout` feature +$ docker run --rm -v $(pwd):$(pwd) -w $(pwd) ccextractor:latest input.mp4 --stdout > output.srt +``` + +Run this command from where your input video file is present, and change `input.mp4` & `output.srt` with the actual name of files. + +2. Enter an interactive environment + +If you need to run CCExtractor with additional options or perform other tasks within the container, you can enter an interactive environment: +bash + +```bash +$ docker run --rm -it --entrypoint='sh' ccextractor:latest +``` + +This will start a Bash shell inside the container, allowing you to run CCExtractor commands manually or perform other operations. + +### Example + +I run help command in image built from `dockerfile` + +```bash +$ docker build -t ccextractor . +$ docker run --rm ccextractor:latest --help +``` + +This will show the `--help` message of CCExtractor tool +From there you can see all the features and flags which can be used. diff --git a/docker/dockerfile b/docker/dockerfile new file mode 100644 index 000000000..98ac85d0f --- /dev/null +++ b/docker/dockerfile @@ -0,0 +1,48 @@ +FROM alpine:latest as base + +FROM base as builder + +RUN apk add --no-cache --update git curl gcc cmake glew glfw \ + tesseract-ocr-dev leptonica-dev clang-dev llvm-dev make pkgconfig \ + zlib-dev libpng-dev libjpeg-turbo-dev openssl-dev freetype-dev libxml2-dev + +RUN cd && git clone https://github.com/gpac/gpac +WORKDIR root/gpac/ +RUN ./configure && make && make install-lib && cd && rm -rf /root/gpac + +WORKDIR /root +RUN git clone https://github.com/CCExtractor/ccextractor.git +RUN apk add bash cargo +RUN export LIB_CLANG_PATH=$(find / -name 'libclang*.so*' 2>/dev/null | grep -v 'No such file' | head -n 1 | xargs dirname) +RUN cd /root/ccextractor/linux && ./pre-build.sh && ./build + +RUN cp /root/ccextractor/linux/ccextractor /ccextractor && rm -rf ~/ccextractor + +FROM base as final + +COPY --from=builder /lib/ld-musl-x86_64.so.1 /lib/ +COPY --from=builder /usr/lib/libtesseract.so.5 /usr/lib/ +COPY --from=builder /usr/lib/libleptonica.so.6 /usr/lib/ +COPY --from=builder /usr/local/lib/libgpac.so.12 /usr/local/lib/ +COPY --from=builder /usr/lib/libstdc++.so.6 /usr/lib/ +COPY --from=builder /usr/lib/libgcc_s.so.1 /usr/lib/ +COPY --from=builder /usr/lib/libgomp.so.1 /usr/lib/ +COPY --from=builder /usr/lib/libpng16.so.16 /usr/lib/ +COPY --from=builder /usr/lib/libjpeg.so.8 /usr/lib/ +COPY --from=builder /usr/lib/libgif.so.7 /usr/lib/ +COPY --from=builder /usr/lib/libtiff.so.6 /usr/lib/ +COPY --from=builder /usr/lib/libwebp.so.7 /usr/lib/ +COPY --from=builder /usr/lib/libwebpmux.so.3 /usr/lib/ +COPY --from=builder /lib/libz.so.1 /lib/ +COPY --from=builder /lib/libssl.so.3 /lib/ +COPY --from=builder /lib/libcrypto.so.3 /lib/ +COPY --from=builder /usr/lib/liblzma.so.5 /usr/lib/ +COPY --from=builder /usr/lib/libzstd.so.1 /usr/lib/ +COPY --from=builder /usr/lib/libsharpyuv.so.0 /usr/lib/ + +COPY --from=builder /ccextractor / + +ENTRYPOINT [ "/ccextractor" ] + +CMD [ "/ccextractor" ] + diff --git a/docs/CHANGES.TXT b/docs/CHANGES.TXT index 5471ea1a9..bf6b6503a 100644 --- a/docs/CHANGES.TXT +++ b/docs/CHANGES.TXT @@ -1,5 +1,14 @@ 0.95 (to be released) ----------------- +- New: Create a Docker image to simplify the CCExtractor usage without any environmental hustle (#1611) +- New: Add time units module in lib_ccxr (#1623) +- New: Add bits and levenshtein module in lib_ccxr (#1627) +- New: Add constants module in lib_ccxr (#1624) +- New: Add log module in lib_ccxr (#1622) +- New: Create `lib_ccxr` and `libccxr_exports` (#1621) +- Fix: Unexpected behavior of get_write_interval (#1609) +- Update: Bump rsmpeg to latest version for ffmpeg bindings (#1600) +- New: Add SCC support for CEA-708 decoder (#1595) - Fix: respect `-stdout` even if multiple CC tracks are present in a Matroska input file (#1453) - Fix: crash in Rust decoder on ATSC1.0 TS Files (#1407) - Removed the --with-gui flag for linux/configure and mac/configure (use the Flutter GUI instead) @@ -21,6 +30,7 @@ - Fix: Repeated values for enums - Cleanup: Remove the (unmaintained) Nuklear GUI code - Cleanup: Reduce the amount of Windows build options in the project file +- Fix: infinite loop in MP4 file type detector. 0.94 (2021-12-14) ----------------- diff --git a/docs/COMPILATION.MD b/docs/COMPILATION.MD index 7e91b73a6..8a8f1d0a5 100644 --- a/docs/COMPILATION.MD +++ b/docs/COMPILATION.MD @@ -10,6 +10,9 @@ Clone the latest repository from Github git clone https://github.com/CCExtractor/ccextractor.git ``` +## Docker +You can now use docker image to build latest source of CCExtractor without any environmental hustle. Follow these [instructions](https://github.com/CCExtractor/ccextractor/tree/master/docker/README.md) for building docker image & usage of it. + ## Linux 1. Make sure all the dependencies are met. @@ -17,22 +20,24 @@ git clone https://github.com/CCExtractor/ccextractor.git Debian: ```bash -sudo apt-get install -y libglew-dev libglfw3-dev cmake gcc libcurl4-gnutls-dev tesseract-ocr libtesseract-dev libleptonica-dev clang libclang-dev +sudo apt-get install -y libgpac-dev libglew-dev libglfw3-dev cmake gcc libcurl4-gnutls-dev tesseract-ocr libtesseract-dev libleptonica-dev clang libclang-dev ``` -RHEL: +RHEL/Fedora: ```bash -yum install -y glew-devel glfw-devel cmake gcc libcurl-devel tesseract-devel leptonica-devel clang +yum install -y glew-devel glfw-devel cmake gcc libcurl-devel tesseract-devel leptonica-devel clang gpac-devel ``` Arch: ```bash -sudo paru -S glew glfw curl tesseract leptonica cmake gcc clang +sudo paru -S glew glfw curl tesseract leptonica cmake gcc clang gpac ``` Rust 1.54 or above is also required. [Install Rust](https://www.rust-lang.org/tools/install). Check specific compilation methods below, on how to compile without rust. +**Note:** On Ubuntu Version 23.10 (Mantic) and later, `libgpac-dev` isn't available, you should build gpac from source by following the easy build instructions [here](https://github.com/gpac/gpac/wiki/GPAC-Build-Guide-for-Linux) + **Note:** On Ubuntu Version 18.04 (Bionic) and later, `libtesseract-dev` is installed rather than `tesseract-ocr-dev`, which does not exist anymore. **Note:** On Ubuntu Version 14.04 (Trusty) and earlier, you should build leptonica and tesseract from source @@ -55,7 +60,7 @@ cd ccextractor/linux ./build -without-rust # compile with debug info -./build -debug # same as ./build_debug +./build -debug # same as ./builddebug # compile with hardsubx [Optional] You need to set these environment variables correctly according to your machine, diff --git a/docs/Rust_migration_guide.md b/docs/Rust_migration_guide.md new file mode 100644 index 000000000..17bb14dcb --- /dev/null +++ b/docs/Rust_migration_guide.md @@ -0,0 +1,71 @@ +# C to Rust Migration Guide + +## Porting C Functions to Rust + +This guide outlines the process of migrating C functions to Rust while maintaining compatibility with existing C code. + +### Step 1: Identify the C Function + +First, identify the C function you want to port. For example, let's consider a function named `net_send_cc()` in a file called `networking.c`: + +```c +void net_send_cc() { + // Some C code +} +``` + +### Step 2: Create a Pure Rust Equivalent + +Write an equivalent function in pure Rust within the `lib_ccxr` module: + +```rust +fn net_send_cc() { + // Rust equivalent code to `net_send_cc` function in `networking.c` +} +``` + +### Step 3: Create a C-Compatible Rust Function + +In the `libccxr_exports` module, create a new function that will be callable from C: + +```rust +#[no_mangle] +pub extern "C" fn ccxr_net_send_cc() { + net_send_cc() // Call the pure Rust function +} +``` + +### Step 4: Declare the Rust Function in C + +In the original C file (`networking.c`), declare the Rust function as an external function: + +```rust +extern void ccxr_net_send_cc(); +``` + +### Step 5: Modify the Original C Function + +Update the original C function to use the Rust implementation when available: + +```c +void net_send_cc() { + #ifndef DISABLE_RUST + return ccxr_net_send_cc(); // Use the Rust implementation + #else + // Original C code + #endif +} +``` + +## Rust module system + +- `lib_ccxr` crate -> **The Idiomatic Rust layer** + + - Path: `src/rust/lib_ccxr` + - This layer will contain the migrated idiomatic Rust. It will have complete documentation and tests. + +- `libccxr_exports` module -> **The C-like Rust layer** + + - Path: `src/rust/src/libccxr_exports` + - This layer will have function names the same as defined in C but with the prefix `ccxr_`. These are the functions defined in the `lib_ccx` crate under appropriate modules. And these functions will be provided to the C library. + - Ex: `extern "C" fn ccxr_() {}` diff --git a/linux/build b/linux/build index 2d5f5652f..385a8d61b 100755 --- a/linux/build +++ b/linux/build @@ -85,7 +85,7 @@ SRC_FREETYPE="../src/thirdparty/freetype/autofit/autofit.c ../src/thirdparty/freetype/type42/type42.c ../src/thirdparty/freetype/winfonts/winfnt.c" BLD_SOURCES="../src/ccextractor.c $SRC_CCX $SRC_GPAC $SRC_ZLIB $SRC_LIBPNG $SRC_HASH $SRC_PROTOBUF $SRC_UTF8PROC $SRC_FREETYPE" -BLD_LINKER="$BLD_LINKER -lm -zmuldefs -l tesseract -l lept -lpthread -ldl -lgpac" +BLD_LINKER="$BLD_LINKER -lm -zmuldefs -l tesseract -l leptonica -lpthread -ldl -lgpac" echo "Running pre-build script..." ./pre-build.sh diff --git a/linux/build-static.sh b/linux/build-static.sh deleted file mode 100755 index c7920a0ad..000000000 --- a/linux/build-static.sh +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env -S sh -ex - -#################################################################### -# setup by tracey apr 2012 -# updated version dec 2016 -# see: http://www.ccextractor.org/doku.php -#################################################################### - - -# build it static! -# simplest way is with linux alpine -# hop onto box with docker on it and cd to dir of the file you are staring at -# You will get a static-compiled binary and english language library file in the end. -if [ ! -e /tmp/cc/ccextractor-README.txt ]; then - rm -rf /tmp/cc; - mkdir -p -m777 /tmp/cc; - mkdir -p -m777 ../lib/tessdata/; - cp ccextractor-README.txt /tmp/cc/; - sudo docker run -v /tmp/cc:/tmp/cc --rm -it alpine:latest /tmp/cc/ccextractor-README.txt; - # NOTE: _AFTER_ testing/validating, you can promote it from "ccextractor.next" to "ccextractor"... ;-) - cp /tmp/cc/*traineddata ../lib/tessdata/; - chmod go-w ../lib/tessdata/; - exit 0; -fi - -# NOW we are inside docker container... -cd /tmp/cc; - - -# we want tesseract (for OCR) -echo ' -http://dl-cdn.alpinelinux.org/alpine/v3.5/main -http://dl-cdn.alpinelinux.org/alpine/v3.5/community -' >| /etc/apk/repositories; -apk update; apk upgrade; - -apk add --update bash zsh alpine-sdk perl; - -# (needed by various static builds below) -# Even though we're going to (re)builid tesseract from source statically, get its dependencies setup by -# installing it now, too. -apk add autoconf automake libtool tesseract-ocr-dev; - - -# Now comes the not-so-fun parts... Many packages _only_ provide .so files in their distros -- not the .a -# needed files for building something with it statically. Step through them now... - - -# libgif -wget https://sourceforge.net/projects/giflib/files/giflib-5.1.4.tar.gz; -zcat giflib*tar.gz | tar xf -; -cd giflib*/; -./configure --disable-shared --enable-static; make; make install; -hash -r; -cd -; - - -# libwebp -git clone https://github.com/webmproject/libwebp; -cd libwebp; -./autogen.sh; -./configure --disable-shared --enable-static; make; make install; -cd -; - - -# leptonica -wget http://www.leptonica.org/source/leptonica-1.73.tar.gz; -zcat leptonica*tar.gz | tar xf -; -cd leptonica*/; -./configure --disable-shared --enable-static; make; make install; -hash -r; -cd -; - - -# tesseract -git clone https://github.com/tesseract-ocr/tesseract; -cd tesseract; -./autogen.sh; -./configure --disable-shared --enable-static; make; make install; -cd -; - - -# ccextractor -- build static -git clone https://github.com/CCExtractor/ccextractor; -cd ccextractor/linux/; -perl -i -pe 's/O3 /O3 -static /' Makefile; -set +e; # this _will_ FAIL at the end.. -make ENABLE_OCR=yes; -set -e; -# I confess hand-compiling (cherrypicking which .a to use when there are 2, etc.) is fragile... -# But it was the _only_ way I could get a fully static build after hours of thrashing... -gcc -Wno-write-strings -Wno-pointer-sign -D_FILE_OFFSET_BITS=64 -DVERSION_FILE_PRESENT -O3 -std=gnu99 -s -DENABLE_OCR -DPNG_NO_CONFIG_H -I/usr/local/include/tesseract -I/usr/local/include/leptonica objs/*.o -o ccextractor \ - --static -lm -lgpac \ - /usr/local/lib/libtesseract.a \ - /usr/local/lib/liblept.a \ - /usr/local/lib/libgif.a \ - /usr/local/lib/libwebp.a \ - /usr/lib/libjpeg.a \ - /usr/lib/libtiff.a \ - /usr/lib/libgomp.a \ - -lstdc++; - -cp ccextractor /tmp/cc/ccextractor.next; -cd -; - -# get english lang trained data -wget https://github.com/tesseract-ocr/tessdata/raw/master/eng.traineddata; diff --git a/linux/build_appimage.sh b/linux/build_appimage.sh new file mode 100755 index 000000000..d586c4eb0 --- /dev/null +++ b/linux/build_appimage.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +set -x +set -e + +# store the path of where the script is +OLD_CWD=$(readlink -f .) + +# store repo root as variable +REPO_ROOT=$(dirname $OLD_CWD) + +# Make a temp directory for building stuff which will be cleaned automatically +BUILD_DIR="$OLD_CWD/temp" + +# Check if temp directory exist, and if so then remove contents from it +# if not then create temp directory +if [ -d "$BUILD_DIR" ]; then + rm -r "$BUILD_DIR/*" | true +else + mkdir -p "$BUILD_DIR" +fi + +# make sure to clean up build dir, even if errors occur +cleanup() { + if [ -d "$BUILD_DIR" ]; then + rm -rf "$BUILD_DIR" + fi +} + +# Automatically trigger Cleanup function +trap cleanup EXIT + +# switch to build dir +pushd "$BUILD_DIR" + +# configure build files with CMake +# we need to explicitly set the install prefix, as CMake's default is /usr/local for some reason... +cmake "$REPO_ROOT/src" + +# build project and install files into AppDir +make -j$(nproc) ENABLE_OCR=yes + +# download linuxdeploy tool +wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + +# make them executable +chmod +x linuxdeploy*.AppImage + +# Create AppDir +mkdir -p "$BUILD_DIR/AppDir" + +# Link of CCExtractor image of any of these resolution(8x8, 16x16, 20x20, 22x22, 24x24, 28x28, 32x32, 36x36, 42x42, +# 48x48, 64x64, 72x72, 96x96, 128x128, 160x160, 192x192, 256x256, 384x384, 480x480, 512x512) in png extension +PNG_LINK="https://ccextractor.org/images/ccextractor.png" + +# Download the image and put it in AppDir +wget "$PNG_LINK" -P AppDir + +# now, build AppImage using linuxdeploy +./linuxdeploy-x86_64.AppImage --appdir=AppDir -e ccextractor --create-desktop-file --output appimage -i AppDir/ccextractor.png + +# Move resulted AppImage binary to base directory +mv ccextractor*.AppImage "$OLD_CWD" diff --git a/linux/configure.ac b/linux/configure.ac index d6080cd13..2bc47a34e 100644 --- a/linux/configure.ac +++ b/linux/configure.ac @@ -15,7 +15,7 @@ AC_PROG_MAKE_SET #Checks for "pkg-config" utility AC_MSG_CHECKING([pkg-config m4 macros]) -if test m4_ifdef([PKG_CHECK_MODULES], [yes], [no]) == yes; then +if test m4_ifdef([PKG_CHECK_MODULES], [yes], [no]) = yes; then AC_MSG_RESULT([yes]); else AC_MSG_RESULT([no]); @@ -140,21 +140,21 @@ AS_IF([ test x$hardsubx = xtrue && test $HAS_AVUTIL -gt 0 ], [AC_MSG_NOTICE(avut AS_IF([ test x$hardsubx = xtrue && test ! $HAS_AVUTIL -gt 0 ], [AC_MSG_ERROR(avutil library not found. Please install the avutil library before proceeding)]) AS_IF([ test x$hardsubx = xtrue && test $HAS_SWSCALE -gt 0 ], [AC_MSG_NOTICE(swscale library found)]) AS_IF([ test x$hardsubx = xtrue && test ! $HAS_SWSCALE -gt 0 ], [AC_MSG_ERROR(swscale library not found. Please install the swscale library before proceeding)]) -AS_IF([ (test x$ocr = xtrue || test x$hardsubx = xtrue) && test $HAS_TESSERACT -gt 0 ], [TESS_VERSION=`tesseract --version 2>&1 | grep tesseract` && AC_MSG_NOTICE(tesseract library found... $TESS_VERSION)]) +AS_IF([ (test x$ocr = xtrue || test x$hardsubx = xtrue) && test $HAS_TESSERACT -gt 0 ], [TESS_VERSION=$(tesseract --version 2>&1 | grep tesseract) && AC_MSG_NOTICE(tesseract library found... $TESS_VERSION)]) AS_IF([ (test x$ocr = xtrue || test x$hardsubx = xtrue) && test ! $HAS_TESSERACT -gt 0 ], [AC_MSG_ERROR(tesserect library not found. Please install the tesseract library before proceeding)]) -AS_IF([ (test x$ocr = xtrue || test x$hardsubx = xtrue) && test $HAS_LEPT -gt 0 ], [LEPT_VERSION=`tesseract --version 2>&1 | grep leptonica` && AC_MSG_NOTICE(leptonica library found... $LEPT_VERSION)]) +AS_IF([ (test x$ocr = xtrue || test x$hardsubx = xtrue) && test $HAS_LEPT -gt 0 ], [LEPT_VERSION=$(tesseract --version 2>&1 | grep leptonica) && AC_MSG_NOTICE(leptonica library found... $LEPT_VERSION)]) AS_IF([ (test x$ocr = xtrue || test x$hardsubx = xtrue) && test ! $HAS_LEPT -gt 0 ], [AC_MSG_ERROR(leptonica library not found. Please install the leptonica library before proceeding)]) #AM_CONDITIONAL(s) for setting values to enable/disable flags in Makefile.am AM_CONDITIONAL(HARDSUBX_IS_ENABLED, [ test x$hardsubx = xtrue ]) AM_CONDITIONAL(OCR_IS_ENABLED, [ test x$ocr = xtrue || test x$hardsubx = xtrue ]) AM_CONDITIONAL(FFMPEG_IS_ENABLED, [ test x$ffmpeg = xtrue ]) -AM_CONDITIONAL(TESSERACT_PRESENT, [ test ! -z `pkg-config --libs-only-l --silence-errors tesseract` ]) -AM_CONDITIONAL(TESSERACT_PRESENT_RPI, [ test -d "/usr/include/tesseract" && test `ls -A /usr/include/tesseract | wc -l` -gt 0 ]) -AM_CONDITIONAL(SYS_IS_LINUX, [ test `uname -s` = "Linux"]) -AM_CONDITIONAL(SYS_IS_MAC, [ test `uname -s` = "Darwin"]) -AM_CONDITIONAL(SYS_IS_APPLE_SILICON, [ test `uname -a | awk '{print $NF}'` = "arm64" ]) -AM_CONDITIONAL(SYS_IS_64_BIT,[test `getconf LONG_BIT` = "64"]) +AM_CONDITIONAL(TESSERACT_PRESENT, [ test ! -z $(pkg-config --libs-only-l --silence-errors tesseract) ]) +AM_CONDITIONAL(TESSERACT_PRESENT_RPI, [ test -d "/usr/include/tesseract" && test $(ls -A /usr/include/tesseract | wc -l) -gt 0 ]) +AM_CONDITIONAL(SYS_IS_LINUX, [ test $(uname -s) = "Linux"]) +AM_CONDITIONAL(SYS_IS_MAC, [ test $(uname -s) = "Darwin"]) +AM_CONDITIONAL(SYS_IS_APPLE_SILICON, [ test $(uname -a | awk '{print $NF}') = "arm64" ]) +AM_CONDITIONAL(SYS_IS_64_BIT,[test $(getconf LONG_BIT) = "64"]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT diff --git a/mac/configure.ac b/mac/configure.ac index 53b841d90..2ddf6dd6d 100644 --- a/mac/configure.ac +++ b/mac/configure.ac @@ -15,7 +15,7 @@ AC_PROG_MAKE_SET #Checks for "pkg-config" utility AC_MSG_CHECKING([pkg-config m4 macros]) -if test m4_ifdef([PKG_CHECK_MODULES], [yes], [no]) == yes; then +if test m4_ifdef([PKG_CHECK_MODULES], [yes], [no]) = yes; then AC_MSG_RESULT([yes]); else AC_MSG_RESULT([no]); @@ -139,21 +139,21 @@ AS_IF([ test x$hardsubx = xtrue && test $HAS_AVUTIL -gt 0 ], [AC_MSG_NOTICE(avut AS_IF([ test x$hardsubx = xtrue && test ! $HAS_AVUTIL -gt 0 ], [AC_MSG_ERROR(avutil library not found. Please install the avutil library before proceeding)]) AS_IF([ test x$hardsubx = xtrue && test $HAS_SWSCALE -gt 0 ], [AC_MSG_NOTICE(swscale library found)]) AS_IF([ test x$hardsubx = xtrue && test ! $HAS_SWSCALE -gt 0 ], [AC_MSG_ERROR(swscale library not found. Please install the swscale library before proceeding)]) -AS_IF([ (test x$ocr = xtrue || test x$hardsubx = xtrue) && test $HAS_TESSERACT -gt 0 ], [TESS_VERSION=`tesseract --version 2>&1 | grep tesseract` && AC_MSG_NOTICE(tesseract library found... $TESS_VERSION)]) +AS_IF([ (test x$ocr = xtrue || test x$hardsubx = xtrue) && test $HAS_TESSERACT -gt 0 ], [TESS_VERSION=$(tesseract --version 2>&1 | grep tesseract) && AC_MSG_NOTICE(tesseract library found... $TESS_VERSION)]) AS_IF([ (test x$ocr = xtrue || test x$hardsubx = xtrue) && test ! $HAS_TESSERACT -gt 0 ], [AC_MSG_ERROR(tesserect library not found. Please install the tesseract library before proceeding)]) -AS_IF([ (test x$ocr = xtrue || test x$hardsubx = xtrue) && test $HAS_LEPT -gt 0 ], [LEPT_VERSION=`tesseract --version 2>&1 | grep leptonica` && AC_MSG_NOTICE(leptonica library found... $LEPT_VERSION)]) +AS_IF([ (test x$ocr = xtrue || test x$hardsubx = xtrue) && test $HAS_LEPT -gt 0 ], [LEPT_VERSION=$(tesseract --version 2>&1 | grep leptonica) && AC_MSG_NOTICE(leptonica library found... $LEPT_VERSION)]) AS_IF([ (test x$ocr = xtrue || test x$hardsubx = xtrue) && test ! $HAS_LEPT -gt 0 ], [AC_MSG_ERROR(leptonica library not found. Please install the leptonica library before proceeding)]) #AM_CONDITIONAL(s) for setting values to enable/disable flags in Makefile.am AM_CONDITIONAL(HARDSUBX_IS_ENABLED, [ test x$hardsubx = xtrue ]) AM_CONDITIONAL(OCR_IS_ENABLED, [ test x$ocr = xtrue || test x$hardsubx = xtrue ]) AM_CONDITIONAL(FFMPEG_IS_ENABLED, [ test x$ffmpeg = xtrue ]) -AM_CONDITIONAL(TESSERACT_PRESENT, [ test ! -z `pkg-config --libs-only-l --silence-errors tesseract` ]) -AM_CONDITIONAL(TESSERACT_PRESENT_RPI, [ test -d "/usr/include/tesseract" && test `ls -A /usr/include/tesseract | wc -l` -gt 0 ]) -AM_CONDITIONAL(SYS_IS_LINUX, [ test `uname -s` = "Linux"]) -AM_CONDITIONAL(SYS_IS_MAC, [ test `uname -s` = "Darwin"]) -AM_CONDITIONAL(SYS_IS_APPLE_SILICON, [ test `uname -a | awk '{print $NF}'` = "arm64" ]) -AM_CONDITIONAL(SYS_IS_64_BIT,[test `getconf LONG_BIT` = "64"]) +AM_CONDITIONAL(TESSERACT_PRESENT, [ test ! -z $(pkg-config --libs-only-l --silence-errors tesseract) ]) +AM_CONDITIONAL(TESSERACT_PRESENT_RPI, [ test -d "/usr/include/tesseract" && test $(ls -A /usr/include/tesseract | wc -l) -gt 0 ]) +AM_CONDITIONAL(SYS_IS_LINUX, [ test $(uname -s) = "Linux"]) +AM_CONDITIONAL(SYS_IS_MAC, [ test $(uname -s) = "Darwin"]) +AM_CONDITIONAL(SYS_IS_APPLE_SILICON, [ test $(uname -a | awk '{print $NF}') = "arm64" ]) +AM_CONDITIONAL(SYS_IS_64_BIT,[test $(getconf LONG_BIT) = "64"]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT diff --git a/src/lib_ccx/activity.c b/src/lib_ccx/activity.c index 095c56d6d..1b002fb58 100644 --- a/src/lib_ccx/activity.c +++ b/src/lib_ccx/activity.c @@ -4,7 +4,6 @@ relevant events. */ #include "lib_ccx.h" #include "ccx_common_option.h" -static int credits_shown = 0; unsigned long net_activity_gui = 0; /* Print current progress. For percentage, -1 -> streaming mode */ @@ -129,11 +128,7 @@ void activity_report_data_read(void) void activity_header(void) { - if (!credits_shown) - { - credits_shown = 1; - mprint("CCExtractor %s, Carlos Fernandez Sanz, Volker Quetschke.\n", VERSION); - mprint("Teletext portions taken from Petr Kutalek's telxcc\n"); - mprint("--------------------------------------------------------------------------\n"); - } + mprint("CCExtractor %s, Carlos Fernandez Sanz, Volker Quetschke.\n", VERSION); + mprint("Teletext portions taken from Petr Kutalek's telxcc\n"); + mprint("--------------------------------------------------------------------------\n"); } diff --git a/src/lib_ccx/ccx_common_common.c b/src/lib_ccx/ccx_common_common.c index 91a765283..f150da7d2 100644 --- a/src/lib_ccx/ccx_common_common.c +++ b/src/lib_ccx/ccx_common_common.c @@ -26,6 +26,9 @@ int fdprintf(int fd, const char *fmt, ...) void millis_to_time(LLONG milli, unsigned *hours, unsigned *minutes, unsigned *seconds, unsigned *ms) { +#ifndef DISABLE_RUST + return ccxr_millis_to_time(milli, hours, minutes, seconds, ms); +#endif /* ifndef DISABLE_RUST */ // LLONG milli = (LLONG) ((ccblock*1000)/29.97); *ms = (unsigned)(milli % 1000); // milliseconds milli = (milli - *ms) / 1000; // Remainder, in seconds diff --git a/src/lib_ccx/ccx_common_common.h b/src/lib_ccx/ccx_common_common.h index b26c6965a..d9f6c3218 100644 --- a/src/lib_ccx/ccx_common_common.h +++ b/src/lib_ccx/ccx_common_common.h @@ -43,6 +43,11 @@ int cc608_parity(unsigned int byte); int fdprintf(int fd, const char *fmt, ...); void millis_to_time(LLONG milli, unsigned *hours, unsigned *minutes,unsigned *seconds, unsigned *ms); + +#ifndef DISABLE_RUST +extern void ccxr_millis_to_time(LLONG milli, unsigned *hours, unsigned *minutes,unsigned *seconds, unsigned *ms); +#endif // !DISABLE_RUST + void freep(void *arg); void dbg_print(LLONG mask, const char *fmt, ...); unsigned char *debug_608_to_ASC(unsigned char *ccdata, int channel); diff --git a/src/lib_ccx/ccx_common_platform.h b/src/lib_ccx/ccx_common_platform.h index 3702696c3..84eae9751 100644 --- a/src/lib_ccx/ccx_common_platform.h +++ b/src/lib_ccx/ccx_common_platform.h @@ -23,6 +23,7 @@ #define STDOUT_FILENO 1 #define STDERR_FILENO 2 #include "inttypes.h" + #undef UINT64_MAX #define UINT64_MAX _UI64_MAX typedef int socklen_t; typedef int ssize_t; diff --git a/src/lib_ccx/ccx_common_timing.c b/src/lib_ccx/ccx_common_timing.c index d1fca4282..1fd123cb9 100644 --- a/src/lib_ccx/ccx_common_timing.c +++ b/src/lib_ccx/ccx_common_timing.c @@ -295,6 +295,34 @@ LLONG get_fts_max(struct ccx_common_timing_ctx *ctx) return ctx->fts_max + ctx->fts_global; } +/** + * SCC Time formatting + */ +size_t print_scc_time(struct ccx_boundary_time time, char *buf) +{ + char *fmt = "%02u:%02u:%02u;%02u"; + double frame; + + frame = ((double)(time.time_in_ms - 1000 * (time.ss + 60 * (time.mm + 60 * time.hh))) * 29.97 / 1000); + + return (size_t)sprintf(buf + time.set, fmt, time.hh, time.mm, time.ss, (unsigned)frame); +} + +struct ccx_boundary_time get_time(LLONG time) +{ + if (time < 0) // Avoid loss of data warning with abs() + time = -time; + + struct ccx_boundary_time result; + result.hh = (unsigned)(time / 1000 / 60 / 60); + result.mm = (unsigned)(time / 1000 / 60 - 60 * result.hh); + result.ss = (unsigned)(time / 1000 - 60 * (result.mm + 60 * result.hh)); + result.time_in_ms = time; + result.set = (time < 0 ? 1 : 0); + + return result; +} + /** * Fill buffer with a time string using specified format * @param fmt has to contain 4 format specifiers for h, m, s and ms respectively diff --git a/src/lib_ccx/ccx_common_timing.h b/src/lib_ccx/ccx_common_timing.h index f54f4302c..1f157173c 100644 --- a/src/lib_ccx/ccx_common_timing.h +++ b/src/lib_ccx/ccx_common_timing.h @@ -77,6 +77,8 @@ struct ccx_common_timing_ctx *init_timing_ctx(struct ccx_common_timing_settings_ void set_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts); void add_current_pts(struct ccx_common_timing_ctx *ctx, LLONG pts); +struct ccx_boundary_time get_time(LLONG mstime); +size_t print_scc_time(struct ccx_boundary_time time, char *buf); int set_fts(struct ccx_common_timing_ctx *ctx); LLONG get_fts(struct ccx_common_timing_ctx *ctx, int current_field); LLONG get_fts_max(struct ccx_common_timing_ctx *ctx); diff --git a/src/lib_ccx/ccx_decoders_708.h b/src/lib_ccx/ccx_decoders_708.h index 8cbd01f8b..0fe0dde7d 100644 --- a/src/lib_ccx/ccx_decoders_708.h +++ b/src/lib_ccx/ccx_decoders_708.h @@ -302,6 +302,7 @@ typedef struct dtvcc_tv_screen LLONG time_ms_hide; unsigned int cc_count; int service_number; + int old_cc_time_end; } dtvcc_tv_screen; /** diff --git a/src/lib_ccx/ccx_decoders_708_encoding.c b/src/lib_ccx/ccx_decoders_708_encoding.c index 5cf9c74f6..b3d9508d4 100644 --- a/src/lib_ccx/ccx_decoders_708_encoding.c +++ b/src/lib_ccx/ccx_decoders_708_encoding.c @@ -12,6 +12,8 @@ EIA-708, SO INTERNALLY WE USE THIS TABLE (FOR CONVENIENCE) A0-FF -> Group G1 as is - non-English characters and symbols */ +#if defined(DISABLE_RUST) + unsigned char dtvcc_get_internal_from_G0(unsigned char g0_char) { return g0_char; @@ -43,3 +45,5 @@ unsigned char dtvcc_get_internal_from_G3(unsigned char g3_char) // Rest unmapped, so we return a blank space return 0x20; } + +#endif diff --git a/src/lib_ccx/ccx_decoders_708_encoding.h b/src/lib_ccx/ccx_decoders_708_encoding.h index 0318a0f1c..ef7ca2a84 100644 --- a/src/lib_ccx/ccx_decoders_708_encoding.h +++ b/src/lib_ccx/ccx_decoders_708_encoding.h @@ -1,11 +1,18 @@ #ifndef _CCX_DECODERS_708_ENCODING_H_ #define _CCX_DECODERS_708_ENCODING_H_ -#define CCX_DTVCC_MUSICAL_NOTE_CHAR 9836 // Unicode Character 'BEAMED SIXTEENTH NOTES' +#define CCX_DTVCC_MUSICAL_NOTE_CHAR 9836 // Unicode Character 'BEAMED SIXTEENTH NOTES' +#ifndef DISABLE_RUST +extern unsigned char dtvcc_get_internal_from_G0(unsigned char g0_char); +extern unsigned char dtvcc_get_internal_from_G1(unsigned char g1_char); +extern unsigned char dtvcc_get_internal_from_G2(unsigned char g2_char); +extern unsigned char dtvcc_get_internal_from_G3(unsigned char g3_char); +#else unsigned char dtvcc_get_internal_from_G0(unsigned char g0_char); unsigned char dtvcc_get_internal_from_G1(unsigned char g1_char); unsigned char dtvcc_get_internal_from_G2(unsigned char g2_char); unsigned char dtvcc_get_internal_from_G3(unsigned char g3_char); +#endif #endif /*_CCX_DECODERS_708_ENCODING_H_*/ diff --git a/src/lib_ccx/ccx_decoders_708_output.c b/src/lib_ccx/ccx_decoders_708_output.c index c1bf3faa2..5697ad242 100644 --- a/src/lib_ccx/ccx_decoders_708_output.c +++ b/src/lib_ccx/ccx_decoders_708_output.c @@ -367,6 +367,155 @@ void dtvcc_write_sami(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, strlen(buf)); } +unsigned char adjust_odd_parity(const unsigned char value) +{ + unsigned int i, ones = 0; + for (i = 0; i < 8; i++) + { + if ((value & (1 << i)) != 0) + { + ones += 1; + } + } + if (ones % 2 == 0) + { + // make the number of ones always odd + return value | 0b10000000; + } + return value; +} + +void dtvcc_write_scc_header(dtvcc_tv_screen *tv, struct encoder_ctx *encoder) +{ + char *buf = (char *)encoder->buffer; + // 18 characters long + 2 new lines + memset(buf, 0, 20); + sprintf(buf, "Scenarist_SCC V1.0\n\n"); + + write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, strlen(buf)); +} + +int count_captions_lines_scc(dtvcc_tv_screen *tv) +{ + int count = 0; + for (int i = 0; i < CCX_DTVCC_SCREENGRID_ROWS; i++) + { + if (!dtvcc_is_row_empty(tv, i)) + { + count++; + } + } + + return count; +} + +/** This function is designed to assign appropriate SSC labels for positioning subtitles based on their length. + * In some scenarios where the video stream provides lengthy subtitles that cannot fit within a single line. + * Single-line subtitle can be placed in 15th row(most bottom row) + * 2 line length subtitles can be placed in 14th and 15th row + * 3 line length subtitles can be placed in 13th, 14th and 15th row + */ +void add_needed_scc_labels(char *buf, int total_subtitle_count, int current_subtitle_count) +{ + switch (total_subtitle_count) + { + case 1: + // row 15, column 00 + sprintf(buf + strlen(buf), " 94e0 94e0"); + break; + case 2: + // 9440: row 14, column 00 | 94e0: row 15, column 00 + sprintf(buf + strlen(buf), current_subtitle_count == 1 ? " 9440 9440" : " 94e0 94e0"); + break; + default: + // 13e0: row 13, column 04 | 9440: row 14, column 00 | 94e0: row 15, column 00 + sprintf(buf + strlen(buf), current_subtitle_count == 1 ? " 13e0 13e0" : (current_subtitle_count == 2 ? " 9440 9440" : " 94e0 94e0")); + } +} + +void dtvcc_write_scc(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder) +{ + dtvcc_tv_screen *tv = decoder->tv; + + if (dtvcc_is_screen_empty(tv, encoder)) + return; + + if (tv->time_ms_show + encoder->subs_delay < 0) + return; + + if (tv->cc_count == 2) + dtvcc_write_scc_header(tv, encoder); + + char *buf = (char *)encoder->buffer; + struct ccx_boundary_time time_show = get_time(tv->time_ms_show + encoder->subs_delay); + // when hiding subtract a frame (1 frame = 34 ms) + struct ccx_boundary_time time_end = get_time(tv->time_ms_hide + encoder->subs_delay - 34); + + if (tv->old_cc_time_end > time_show.time_in_ms) + { + // Correct the frame delay + time_show.time_in_ms -= 1000 / 29.97; + print_scc_time(time_show, buf); + sprintf(buf + strlen(buf), "\t942c 942c"); + time_show.time_in_ms += 1000 / 29.97; + // Clear the buffer and start pop on caption + sprintf(buf + strlen(buf), "94ae 94ae 9420 9420"); + } + else if (tv->old_cc_time_end < time_show.time_in_ms) + { + // Clear the screen for new caption + struct ccx_boundary_time time_to_display = get_time(tv->old_cc_time_end); + print_scc_time(time_to_display, buf); + sprintf(buf + strlen(buf), "\t942c 942c \n\n"); + // Correct the frame delay + time_show.time_in_ms -= 1000 / 29.97; + // Clear the buffer and start pop on caption in new time + print_scc_time(time_show, buf); + sprintf(buf + strlen(buf), "\t94ae 94ae 9420 9420"); + time_show.time_in_ms += 1000 / 29.97; + } + else + { + time_show.time_in_ms -= 1000 / 29.97; + print_scc_time(time_show, buf); + sprintf(buf + strlen(buf), "\t942c 942c 94ae 94ae 9420 9420"); + time_show.time_in_ms += 1000 / 29.97; + } + + int total_subtitle_count = count_captions_lines_scc(tv); + int current_subtitle_count = 0; + + for (int i = 0; i < CCX_DTVCC_SCREENGRID_ROWS; i++) + { + if (!dtvcc_is_row_empty(tv, i)) + { + current_subtitle_count++; + add_needed_scc_labels(buf, total_subtitle_count, current_subtitle_count); + + int first, last, bytes_written = 0; + dtvcc_get_write_interval(tv, i, &first, &last); + for (int j = first; j <= last; j++) + { + if (bytes_written % 2 == 0) + sprintf(buf + strlen(buf), " "); + sprintf(buf + strlen(buf), "%x", adjust_odd_parity(tv->chars[i][j].sym)); + bytes_written += 1; + } + // if byte pair are not even then make it even by adding 0x80 as padding + if (bytes_written % 2 == 1) + sprintf(buf + strlen(buf), "80 "); + else + sprintf(buf + strlen(buf), " "); + } + } + + // Display caption (942f 942f) + sprintf(buf + strlen(buf), "942f 942f \n\n"); + write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, strlen(buf)); + + tv->old_cc_time_end = time_end.time_in_ms; +} + void dtvcc_write(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder) { switch (encoder->write_format) @@ -382,6 +531,9 @@ void dtvcc_write(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struc case CCX_OF_SAMI: dtvcc_write_sami(writer, decoder, encoder); break; + case CCX_OF_SCC: + dtvcc_write_scc(writer, decoder, encoder); + break; case CCX_OF_MCC: printf("REALLY BAD... [%s:%d]\n", __FILE__, __LINE__); break; diff --git a/src/lib_ccx/ccx_decoders_708_output.h b/src/lib_ccx/ccx_decoders_708_output.h index 3b9f6759f..63544df79 100644 --- a/src/lib_ccx/ccx_decoders_708_output.h +++ b/src/lib_ccx/ccx_decoders_708_output.h @@ -8,11 +8,11 @@ void dtvcc_write_done(dtvcc_tv_screen *tv, struct encoder_ctx *encoder); void dtvcc_writer_init(dtvcc_writer_ctx *writer, - char *base_filename, - int program_number, - int service_number, - enum ccx_output_format write_format, - struct encoder_cfg *cfg); + char *base_filename, + int program_number, + int service_number, + enum ccx_output_format write_format, + struct encoder_cfg *cfg); void dtvcc_writer_cleanup(dtvcc_writer_ctx *writer); void dtvcc_writer_output(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder); @@ -30,6 +30,9 @@ void dtvcc_write_transcript(dtvcc_writer_ctx *writer, dtvcc_service_decoder *dec void dtvcc_write_sami_header(dtvcc_tv_screen *tv, struct encoder_ctx *encoder); void dtvcc_write_sami_footer(dtvcc_tv_screen *tv, struct encoder_ctx *encoder); void dtvcc_write_sami(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder); +void dtvcc_write_scc_header(dtvcc_tv_screen *tv, struct encoder_ctx *encoder); +void add_needed_scc_labels(char *buf, int total_subtitle_count, int current_subtitle_count); +void dtvcc_write_scc(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder); void dtvcc_write(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, struct encoder_ctx *encoder); -#endif /*_CCX_DECODERS_708_OUTPUT_H_*/ \ No newline at end of file +#endif /*_CCX_DECODERS_708_OUTPUT_H_*/ diff --git a/src/lib_ccx/lib_ccx.c b/src/lib_ccx/lib_ccx.c index 48a0ed7a6..c8c02e709 100644 --- a/src/lib_ccx/lib_ccx.c +++ b/src/lib_ccx/lib_ccx.c @@ -97,6 +97,8 @@ struct lib_ccx_ctx *init_libraries(struct ccx_s_options *opt) { int ret = 0; + activity_header(); // Brag about writing it :-) + // Set logging functions for libraries ccx_common_logging.debug_ftn = &dbg_print; ccx_common_logging.debug_mask = opt->debug_mask; @@ -105,7 +107,7 @@ struct lib_ccx_ctx *init_libraries(struct ccx_s_options *opt) ccx_common_logging.gui_ftn = &activity_library_process; #ifndef DISABLE_RUST - ccxr_init_basic_logger(); + ccxr_init_basic_logger(opt); #endif struct lib_ccx_ctx *ctx = malloc(sizeof(struct lib_ccx_ctx)); diff --git a/src/lib_ccx/lib_ccx.h b/src/lib_ccx/lib_ccx.h index 92d72e488..3d107826e 100644 --- a/src/lib_ccx/lib_ccx.h +++ b/src/lib_ccx/lib_ccx.h @@ -153,6 +153,10 @@ struct lib_ccx_ctx struct lib_ccx_ctx *init_libraries(struct ccx_s_options *opt); void dinit_libraries( struct lib_ccx_ctx **ctx); +#ifndef DISABLE_RUST +extern void ccxr_init_basic_logger(struct ccx_s_options *opts); +#endif + //ccextractor.c void print_end_msg(void); diff --git a/src/lib_ccx/mp4.c b/src/lib_ccx/mp4.c index 1b5c45119..05df43fe0 100644 --- a/src/lib_ccx/mp4.c +++ b/src/lib_ccx/mp4.c @@ -51,6 +51,16 @@ static int process_avc_sample(struct lib_ccx_ctx *ctx, u32 timescale, GF_AVCConf { u32 nal_length; + if (i + c->nal_unit_size > s->dataLength) + { + mprint("Corrupted packet detected in process_avc_sample. dataLength " + "%u is less than index %u + nal_unit_size %u. Ignoring.\n", + s->dataLength, i, c->nal_unit_size); + // The packet is likely corrupted, it's unsafe to read this many bytes + // even to detect the length of the next `nal`. Ignoring this error, + // hopefully the outer loop in `process_avc_track` can recover. + return status; + } switch (c->nal_unit_size) { case 1: @@ -63,15 +73,28 @@ static int process_avc_sample(struct lib_ccx_ctx *ctx, u32 timescale, GF_AVCConf nal_length = bswap32(*(long *)&s->data[i]); break; } + const u32 previous_index = i; i += c->nal_unit_size; + if (i + nal_length <= previous_index || i + nal_length > s->dataLength) + { + mprint("Corrupted sample detected in process_avc_sample. dataLength %u " + "is less than index %u + nal_unit_size %u + nal_length %u. Ignoring.\n", + s->dataLength, previous_index, c->nal_unit_size, nal_length); + // The packet is likely corrupted, it's unsafe to procell nal_length bytes + // because they are past the sample end. Ignoring this error, hopefully + // the outer loop in `process_avc_track` can recover. + return status; + } s_nalu_stats.total += 1; - s_nalu_stats.type[s->data[i] & 0x1F] += 1; - temp_debug = 0; if (nal_length > 0) + { + // s->data[i] is only relevant and safe to access here. + s_nalu_stats.type[s->data[i] & 0x1F] += 1; do_NAL(enc_ctx, dec_ctx, (unsigned char *)&(s->data[i]), nal_length, sub); + } i += nal_length; } // outer for assert(i == s->dataLength); diff --git a/src/lib_ccx/params.c b/src/lib_ccx/params.c index 1c06588d8..eb1562e50 100644 --- a/src/lib_ccx/params.c +++ b/src/lib_ccx/params.c @@ -356,58 +356,45 @@ void print_usage(void) mprint(" -o outputfilename: Use -o parameters to define output filename if you don't\n"); mprint(" like the default ones (same as infile plus _1 or _2 when\n"); mprint(" needed and file extension, e.g. .srt).\n"); - mprint(" -stdout: Write output to stdout (console) instead of file. If\n"); + mprint(" --stdout: Write output to stdout (console) instead of file. If\n"); mprint(" stdout is used, then -o can't be used. Also\n"); - mprint(" -stdout will redirect all messages to stderr (error).\n"); - mprint(" -pesheader: Dump the PES Header to stdout (console). This is\n"); + mprint(" --stdout will redirect all messages to stderr (error).\n"); + mprint(" --pesheader: Dump the PES Header to stdout (console). This is\n"); mprint(" used for debugging purposes to see the contents\n"); mprint(" of each PES packet header.\n"); - mprint(" -debugdvbsub: Write the DVB subtitle debug traces to console.\n"); - mprint(" -ignoreptsjumps: Ignore PTS jumps (default).\n"); - mprint(" -fixptsjumps: fix pts jumps. Use this parameter if you\n"); + mprint(" --debugdvbsub: Write the DVB subtitle debug traces to console.\n"); + mprint(" --ignoreptsjumps: Ignore PTS jumps (default).\n"); + mprint(" --fixptsjumps: fix pts jumps. Use this parameter if you\n"); mprint(" experience timeline resets/jumps in the output.\n"); - mprint(" -stdin: Reads input from stdin (console) instead of file.\n"); + mprint(" --stdin: Reads input from stdin (console) instead of file.\n"); mprint(" Alternatively, - can be used instead of -stdin\n"); - mprint("You can pass as many input files as you need. They will be processed in order.\n"); - mprint("If a file name is suffixed by +, ccextractor will try to follow a numerical\n"); - mprint("sequence. For example, DVD001.VOB+ means DVD001.VOB, DVD002.VOB and so on\n"); - mprint("until there are no more files.\n"); - mprint("Output will be one single file (either raw or srt). Use this if you made your\n"); - mprint("recording in several cuts (to skip commercials for example) but you want one\n"); - mprint("subtitle file with contiguous timing.\n\n"); mprint("Output file segmentation:\n"); - mprint(" -outinterval x output in interval of x seconds\n"); - mprint(" --segmentonkeyonly -key: When segmenting files, do it only after a I frame\n"); + mprint(" --outinterval x output in interval of x seconds\n"); + mprint(" --segmentonkeyonly: When segmenting files, do it only after a I frame\n"); mprint(" trying to behave like FFmpeg\n\n"); mprint("Network support:\n"); - mprint(" -udp port: Read the input via UDP (listening in the specified port)\n"); - mprint(" instead of reading a file.\n\n"); - mprint(" -udp [host:]port: Read the input via UDP (listening in the specified\n"); - mprint(" port) instead of reading a file. Host can be a\n"); - mprint(" hostname or IPv4 address. If host is not specified\n"); - mprint(" then listens on the local host.\n\n"); - mprint(" -udp [src@host:]port: Read the input via UDP (listening in the specified\n"); + mprint(" --udp [[src@]host:]port: Read the input via UDP (listening in the specified\n"); mprint(" port) instead of reading a file. Host and src can be a\n"); mprint(" hostname or IPv4 address. If host is not specified\n"); mprint(" then listens on the local host.\n\n"); - mprint(" -sendto host[:port]: Sends data in BIN format to the server\n"); + mprint(" --sendto host[:port]: Sends data in BIN format to the server\n"); mprint(" according to the CCExtractor's protocol over\n"); mprint(" TCP. For IPv6 use [address]:port\n"); - mprint(" -tcp port: Reads the input data in BIN format according to\n"); + mprint(" --tcp port: Reads the input data in BIN format according to\n"); mprint(" CCExtractor's protocol, listening specified port on the\n"); mprint(" local host\n"); - mprint(" -tcppassword password: Sets server password for new connections to\n"); + mprint(" --tcp-password password: Sets server password for new connections to\n"); mprint(" tcp server\n"); - mprint(" -tcpdesc description: Sends to the server short description about\n"); + mprint(" --tcp-description description: Sends to the server short description about\n"); mprint(" captions e.g. channel name or file name\n"); mprint("Options that affect what will be processed:\n"); - mprint(" -1, -2, -12: Output Field 1 data, Field 2 data, or both\n"); - mprint(" (DEFAULT is -1)\n"); + mprint(" --output-field 1 / 2 / both: Output Field 1 data, Field 2 data, or both\n"); + mprint(" (DEFAULT is 1)\n"); mprint("Use --append to prevent overwriting of existing files. The output will be\n"); mprint(" appended instead.\n"); - mprint(" -cc2: When in srt/sami mode, process captions in channel 2\n"); - mprint(" instead of channel 1. Alternatively, -CC2 can also be used.\n"); - mprint("-svc --service N1[cs1],N2[cs2]...:\n"); + mprint(" --cc2: When in srt/sami mode, process captions in channel 2\n"); + mprint(" instead of channel 1. Alternatively, --CC2 can also be used.\n"); + mprint(" --service N1[cs1],N2[cs2]...:\n"); mprint(" Enable CEA-708 (DTVCC) captions processing for the listed\n"); mprint(" services. The parameter is a comma delimited list\n"); mprint(" of services numbers, such as \"1,2\" to process the\n"); @@ -421,9 +408,6 @@ void print_usage(void) mprint(" UTF-8 using iconv. See iconv documentation to check if\n"); mprint(" required encoding/charset is supported.\n"); mprint("\n"); - mprint("In general, if you want English subtitles you don't need to use these options\n"); - mprint("as they are broadcast in field 1, channel 1. If you want the second language\n"); - mprint("(usually Spanish) you may need to try -2, or -cc2, or both.\n\n"); mprint("Input formats:\n"); mprint(" With the exception of McPoodle's raw format, which is just the closed\n"); mprint(" caption data with no other info, CCExtractor can usually detect the\n"); @@ -444,9 +428,9 @@ void print_usage(void) #ifdef WTV_DEBUG mprint(" hex -> Hexadecimal dump as generated by wtvccdump.\n"); #endif - mprint(" -ts, -ps, -es, -mp4, -wtv, -mkv and -asf/--dvr-ms can be used as shorts.\n\n"); + mprint(" --ts, --ps, --es, --mp4, --wtv, --mkv and --asf/--dvr-ms can be used as shorts.\n\n"); mprint("Output formats:\n\n"); - mprint(" -out=format\n\n"); + mprint(" --out=format\n\n"); mprint(" where format is one of these:\n"); mprint(" srt -> SubRip (default, so not actually needed).\n"); mprint(" ass/ssa -> SubStation Alpha.\n"); @@ -477,27 +461,27 @@ void print_usage(void) mprint(" report -> Prints to stdout information about captions\n"); mprint(" in specified input. Don't produce any file\n"); mprint(" output\n\n"); - mprint(" -srt, -dvdraw, -sami, -webvtt, -txt, -ttxt and -null can be used as shorts.\n\n"); + mprint(" --srt, --dvdraw, --sami, --webvtt, --txt, --ttxt and --null can be used as shorts.\n\n"); mprint("Options that affect how input files will be processed.\n"); - mprint(" -gt --goptime: Use GOP for timing instead of PTS. This only applies\n"); + mprint(" --goptime: Use GOP for timing instead of PTS. This only applies\n"); mprint(" to Program or Transport Streams with MPEG2 data and\n"); mprint(" overrides the default PTS timing.\n"); mprint(" GOP timing is always used for Elementary Streams.\n"); - mprint(" -nogt --nogoptime: Never use GOP timing (use PTS), even if ccextractor\n"); + mprint(" --no-goptime: Never use GOP timing (use PTS), even if ccextractor\n"); mprint(" detects GOP timing is the reasonable choice.\n"); - mprint(" -fp --fixpadding: Fix padding - some cards (or providers, or whatever)\n"); + mprint(" --fixpadding: Fix padding - some cards (or providers, or whatever)\n"); mprint(" seem to send 0000 as CC padding instead of 8080. If you\n"); mprint(" get bad timing, this might solve it.\n"); - mprint(" -90090: Use 90090 (instead of 90000) as MPEG clock frequency.\n"); + mprint(" --90090: Use 90090 (instead of 90000) as MPEG clock frequency.\n"); mprint(" (reported to be needed at least by Panasonic DMR-ES15\n"); mprint(" DVD Recorder)\n"); - mprint(" -ve --videoedited: By default, ccextractor will process input files in\n"); + mprint(" --videoedited: By default, ccextractor will process input files in\n"); mprint(" sequence as if they were all one large file (i.e.\n"); mprint(" split by a generic, non video-aware tool. If you\n"); mprint(" are processing video hat was split with a editing\n"); - mprint(" tool, use -ve so ccextractor doesn't try to rebuild\n"); + mprint(" tool, use --ve so ccextractor doesn't try to rebuild\n"); mprint(" the original timing.\n"); mprint(" -s --stream [secs]: Consider the file as a continuous stream that is\n"); mprint(" growing as ccextractor processes it, so don't try\n"); @@ -510,80 +494,73 @@ void print_usage(void) mprint(" but not kill ccextractor externally.\n"); mprint(" Note: If -s is used then only one input file is\n"); mprint(" allowed.\n"); - mprint(" -poc --usepicorder: Use the pic_order_cnt_lsb in AVC/H.264 data streams\n"); + mprint(" --usepicorder: Use the pic_order_cnt_lsb in AVC/H.264 data streams\n"); mprint(" to order the CC information. The default way is to\n"); mprint(" use the PTS information. Use this switch only when\n"); mprint(" needed.\n"); - mprint(" -myth: Force MythTV code branch.\n"); - mprint(" -nomyth: Disable MythTV code branch.\n"); + mprint(" --myth: Force MythTV code branch.\n"); + mprint(" --no-myth: Disable MythTV code branch.\n"); mprint(" The MythTV branch is needed for analog captures where\n"); mprint(" the closed caption data is stored in the VBI, such as\n"); mprint(" those with bttv cards (Hauppage 250 for example). This\n"); mprint(" is detected automatically so you don't need to worry\n"); mprint(" about this unless autodetection doesn't work for you.\n"); - mprint(" -wtvconvertfix: This switch works around a bug in Windows 7's built in\n"); + mprint(" --wtvconvertfix: This switch works around a bug in Windows 7's built in\n"); mprint(" software to convert *.wtv to *.dvr-ms. For analog NTSC\n"); mprint(" recordings the CC information is marked as digital\n"); mprint(" captions. Use this switch only when needed.\n"); - mprint(" -wtvmpeg2: Read the captions from the MPEG2 video stream rather\n"); + mprint(" --wtvmpeg2: Read the captions from the MPEG2 video stream rather\n"); mprint(" than the captions stream in WTV files\n"); - mprint(" -pn --program-number: In TS mode, specifically select a program to process.\n"); + mprint(" --program-number: In TS mode, specifically select a program to process.\n"); mprint(" Not needed if the TS only has one. If this parameter\n"); mprint(" is not specified and CCExtractor detects more than one\n"); mprint(" program in the input, it will list the programs found\n"); mprint(" and terminate without doing anything, unless\n"); - mprint(" -autoprogram (see below) is used.\n"); - mprint(" -autoprogram: If there's more than one program in the stream, just use\n"); + mprint(" --autoprogram (see below) is used.\n"); + mprint(" --autoprogram: If there's more than one program in the stream, just use\n"); mprint(" the first one we find that contains a suitable stream.\n"); - mprint(" -multiprogram: Uses multiple programs from the same input stream.\n"); - mprint(" -datapid: Don't try to find out the stream for caption/teletext\n"); + mprint(" --multiprogram: Uses multiple programs from the same input stream.\n"); + mprint(" --datapid: Don't try to find out the stream for caption/teletext\n"); mprint(" data, just use this one instead.\n"); - mprint(" -datastreamtype: Instead of selecting the stream by its PID, select it\n"); + mprint(" --datastreamtype: Instead of selecting the stream by its PID, select it\n"); mprint(" by its type (pick the stream that has this type in\n"); mprint(" the PMT)\n"); - mprint(" -streamtype: Assume the data is of this type, don't autodetect. This\n"); - mprint(" parameter may be needed if -datapid or -datastreamtype\n"); + mprint(" --streamtype: Assume the data is of this type, don't autodetect. This\n"); + mprint(" parameter may be needed if --datapid or -datastreamtype\n"); mprint(" is used and CCExtractor cannot determine how to process\n"); mprint(" the stream. The value will usually be 2 (MPEG video) or\n"); mprint(" 6 (MPEG private data).\n"); - mprint(" -haup --hauppauge: If the video was recorder using a Hauppauge card, it\n"); + mprint(" --hauppauge: If the video was recorder using a Hauppauge card, it\n"); mprint(" might need special processing. This parameter will\n"); mprint(" force the special treatment.\n"); - mprint(" -mp4vidtrack: In MP4 files the closed caption data can be embedded in\n"); + mprint(" --mp4vidtrack: In MP4 files the closed caption data can be embedded in\n"); mprint(" the video track or in a dedicated CC track. If a\n"); mprint(" dedicated track is detected it will be processed instead\n"); mprint(" of the video track. If you need to force the video track\n"); mprint(" to be processed instead use this option.\n"); - mprint(" -noautotimeref: Some streams come with broadcast date information. When\n"); + mprint(" --no-autotimeref: Some streams come with broadcast date information. When\n"); mprint(" such data is available, CCExtractor will set its time\n"); mprint(" reference to the received data. Use this parameter if\n"); mprint(" you prefer your own reference. Note: Current this only\n"); mprint(" affects Teletext in timed transcript with -datets.\n"); - mprint(" --noscte20: Ignore SCTE-20 data if present.\n"); + mprint(" --no-scte20: Ignore SCTE-20 data if present.\n"); mprint(" --webvtt-create-css: Create a separate file for CSS instead of inline.\n"); - mprint(" -deblev: Enable debug so the calculated distance for each two\n"); + mprint(" --deblev: Enable debug so the calculated distance for each two\n"); mprint(" strings is displayed. The output includes both strings,\n"); mprint(" the calculated distance, the maximum allowed distance,\n"); mprint(" and whether the strings are ultimately considered \n"); mprint(" equivalent or not, i.e. the calculated distance is \n"); mprint(" less or equal than the max allowed..\n"); - mprint("-anvid --analyzevideo Analyze the video stream even if it's not used for\n"); + mprint(" --analyzevideo Analyze the video stream even if it's not used for\n"); mprint(" subtitles. This allows to provide video information.\n"); mprint(" --timestamp-map Enable the X-TIMESTAMP-MAP header for WebVTT (HLS)\n"); - mprint("Levenshtein distance:\n\n"); - mprint(" When processing teletext files CCExtractor tries to correct typos by\n"); - mprint(" comparing consecutive lines. If line N+1 is almost identical to line N except\n"); - mprint(" for minor changes (plus next characters) then it assumes that line N that a\n"); - mprint(" typo that was corrected in N+1. This is currently implemented in teletext\n"); - mprint(" because it's where samples files that could benefit from this were available.\n"); - mprint(" You can adjust, or disable, the algorithm settings with the following\n"); - mprint(" parameters.\n\n"); - mprint(" -nolevdist: Don't attempt to correct typos with Levenshtein distance.\n"); - mprint(" -levdistmincnt value: Minimum distance we always allow regardless\n"); + mprint("Levenshtein distance:\n"); + mprint(" --no-levdist: Don't attempt to correct typos with Levenshtein distance.\n"); + mprint(" --levdistmincnt value: Minimum distance we always allow regardless\n"); mprint(" of the length of the strings.Default 2. \n"); mprint(" This means that if the calculated distance \n"); mprint(" is 0,1 or 2, we consider the strings to be equivalent.\n"); - mprint(" -levdistmaxpct value: Maximum distance we allow, as a percentage of\n"); + mprint(" --levdistmaxpct value: Maximum distance we allow, as a percentage of\n"); mprint(" the shortest string length. Default 10%.\n"); mprint(" For example, consider a comparison of one string of \n"); mprint(" 30 characters and one of 60 characters. We want to \n"); @@ -596,31 +573,31 @@ void print_usage(void) mprint(" to 3 between the first 30 characters.\n"); mprint("\n"); mprint("Options that affect what kind of output will be produced:\n"); - mprint(" -chapters: (Experimental) Produces a chapter file from MP4 files.\n"); + mprint(" --chapters: (Experimental) Produces a chapter file from MP4 files.\n"); mprint(" Note that this must only be used with MP4 files,\n"); mprint(" for other files it will simply generate subtitles file.\n"); - mprint(" -bom: Append a BOM (Byte Order Mark) to output files.\n"); + mprint(" --bom: Append a BOM (Byte Order Mark) to output files.\n"); mprint(" Note that most text processing tools in linux will not\n"); mprint(" like BOM.\n"); - mprint(" -nobom: Do not append a BOM (Byte Order Mark) to output\n"); + mprint(" --no-bom: Do not append a BOM (Byte Order Mark) to output\n"); mprint(" files. Note that this may break files when using\n"); mprint(" Windows. This is the default in non-Windows builds.\n"); - mprint(" -unicode: Encode subtitles in Unicode instead of Latin-1.\n"); - mprint(" -utf8: Encode subtitles in UTF-8 (no longer needed.\n"); + mprint(" --unicode: Encode subtitles in Unicode instead of Latin-1.\n"); + mprint(" --utf8: Encode subtitles in UTF-8 (no longer needed.\n"); mprint(" because UTF-8 is now the default).\n"); - mprint(" -latin1: Encode subtitles in Latin-1\n"); - mprint(" -nofc --nofontcolor: For .srt/.sami/.vtt, don't add font color tags.\n"); - mprint(" --nohtmlescape: For .srt/.sami/.vtt, don't covert html unsafe character\n"); - mprint("-nots --notypesetting: For .srt/.sami/.vtt, don't add typesetting tags.\n"); - mprint(" -trim: Trim lines.\n"); - mprint(" -dc --defaultcolor: Select a different default color (instead of\n"); + mprint(" --latin1: Encode subtitles in Latin-1\n"); + mprint(" --no-fontcolor: For .srt/.sami/.vtt, don't add font color tags.\n"); + mprint(" --no-htmlescape: For .srt/.sami/.vtt, don't covert html unsafe character\n"); + mprint(" --no-typesetting: For .srt/.sami/.vtt, don't add typesetting tags.\n"); + mprint(" --trim: Trim lines.\n"); + mprint(" --defaultcolor: Select a different default color (instead of\n"); mprint(" white). This causes all output in .srt/.smi/.vtt\n"); mprint(" files to have a font tag, which makes the files\n"); mprint(" larger. Add the color you want in RGB, such as\n"); - mprint(" -dc #FF0000 for red.\n"); - mprint(" -sc --sentencecap: Sentence capitalization. Use if you hate\n"); + mprint(" --dc #FF0000 for red.\n"); + mprint(" --sentencecap: Sentence capitalization. Use if you hate\n"); mprint(" ALL CAPS in subtitles.\n"); - mprint(" --capfile -caf file: Add the contents of 'file' to the list of words\n"); + mprint(" --capfile file: Add the contents of 'file' to the list of words\n"); mprint(" that must be capitalized. For example, if file\n"); mprint(" is a plain text file that contains\n\n"); mprint(" Tony\n"); @@ -633,58 +610,58 @@ void print_usage(void) mprint("--profanity-file : Add the contents of to the list of words that.\n"); mprint(" must be censored. The content of , follows the\n"); mprint(" same syntax as for the capitalization file\n"); - mprint("-sbs --splitbysentence: Split output text so each frame contains a complete\n"); + mprint(" --splitbysentence: Split output text so each frame contains a complete\n"); mprint(" sentence. Timings are adjusted based on number of\n"); - mprint(" characters\n."); - mprint(" -unixts REF: For timed transcripts that have an absolute date\n"); + mprint(" characters\n"); + mprint(" --unixts REF: For timed transcripts that have an absolute date\n"); mprint(" instead of a timestamp relative to the file start), use\n"); mprint(" this time reference (UNIX timestamp). 0 => Use current\n"); mprint(" system time.\n"); mprint(" ccextractor will automatically switch to transport\n"); mprint(" stream UTC timestamps when available.\n"); - mprint(" -datets: In transcripts, write time as YYYYMMDDHHMMss,ms.\n"); - mprint(" -sects: In transcripts, write time as ss,ms\n"); - mprint(" -UCLA: Transcripts are generated with a specific format\n"); + mprint(" --datets: In transcripts, write time as YYYYMMDDHHMMss,ms.\n"); + mprint(" --sects: In transcripts, write time as ss,ms\n"); + mprint(" --UCLA: Transcripts are generated with a specific format\n"); mprint(" that is convenient for a specific project, feel\n"); mprint(" free to play with it but be aware that this format\n"); mprint(" is really live - don't rely on its output format\n"); mprint(" not changing between versions.\n"); - mprint(" -latrusmap Map Latin symbols to Cyrillic ones in special cases\n"); + mprint(" --latrusmap Map Latin symbols to Cyrillic ones in special cases\n"); mprint(" of Russian Teletext files (issue #1086)\n"); - mprint(" -xds: In timed transcripts, all XDS information will be saved\n"); + mprint(" --xds: In timed transcripts, all XDS information will be saved\n"); mprint(" to the output file.\n"); - mprint(" -lf: Use LF (UNIX) instead of CRLF (DOS, Windows) as line\n"); + mprint(" --lf: Use LF (UNIX) instead of CRLF (DOS, Windows) as line\n"); mprint(" terminator.\n"); - mprint(" -df: For MCC Files, force dropframe frame count.\n"); - mprint(" -autodash: Based on position on screen, attempt to determine\n"); + mprint(" --df: For MCC Files, force dropframe frame count.\n"); + mprint(" --autodash: Based on position on screen, attempt to determine\n"); mprint(" the different speakers and a dash (-) when each\n"); - mprint(" of them talks (.srt/.vtt only, -trim required).\n"); - mprint(" -xmltv mode: produce an XMLTV file containing the EPG data from\n"); + mprint(" of them talks (.srt/.vtt only, --trim required).\n"); + mprint(" --xmltv mode: produce an XMLTV file containing the EPG data from\n"); mprint(" the source TS file. Mode: 1 = full output\n"); mprint(" 2 = live output. 3 = both\n"); - mprint(" -xmltvliveinterval x: interval of x seconds between writing live mode xmltv output.\n"); - mprint("-xmltvoutputinterval x: interval of x seconds between writing full file xmltv output.\n"); - mprint(" -xmltvonlycurrent: Only print current events for xmltv output.\n"); - mprint(" -sem: Create a .sem file for each output file that is open\n"); + mprint(" --xmltvliveinterval x: interval of x seconds between writing live mode xmltv output.\n"); + mprint("--xmltvoutputinterval x: interval of x seconds between writing full file xmltv output.\n"); + mprint(" --xmltvonlycurrent: Only print current events for xmltv output.\n"); + mprint(" --sem: Create a .sem file for each output file that is open\n"); mprint(" and delete it on file close.\n"); - mprint(" -dvblang: For DVB subtitles, select which language's caption\n"); + mprint(" --dvblang: For DVB subtitles, select which language's caption\n"); mprint(" stream will be processed. e.g. 'eng' for English.\n"); mprint(" If there are multiple languages, only this specified\n"); mprint(" language stream will be processed (default).\n"); - mprint(" -ocrlang: Manually select the name of the Tesseract .traineddata\n"); + mprint(" --ocrlang: Manually select the name of the Tesseract .traineddata\n"); mprint(" file. Helpful if you want to OCR a caption stream of\n"); mprint(" one language with the data of another language.\n"); - mprint(" e.g. '-dvblang chs -ocrlang chi_tra' will decode the\n"); + mprint(" e.g. '-dvblang chs --ocrlang chi_tra' will decode the\n"); mprint(" Chinese (Simplified) caption stream but perform OCR\n"); mprint(" using the Chinese (Traditional) trained data\n"); mprint(" This option is also helpful when the traineddata file\n"); mprint(" has non standard names that don't follow ISO specs\n"); - mprint(" -quant mode: How to quantize the bitmap before passing it to tesseract\n"); + mprint(" --quant mode: How to quantize the bitmap before passing it to tesseract\n"); mprint(" for OCR'ing.\n"); mprint(" 0: Don't quantize at all.\n"); mprint(" 1: Use CCExtractor's internal function (default).\n"); mprint(" 2: Reduce distinct color count in image for faster results.\n"); - mprint(" -oem: Select the OEM mode for Tesseract.\n"); + mprint(" --oem: Select the OEM mode for Tesseract.\n"); mprint(" Available modes :\n"); mprint(" 0: OEM_TESSERACT_ONLY - the fastest mode.\n"); mprint(" 1: OEM_LSTM_ONLY - use LSTM algorithm for recognition.\n"); @@ -692,108 +669,89 @@ void print_usage(void) mprint(" Default value depends on the tesseract version linked :\n"); mprint(" Tesseract v3 : default mode is 0,\n"); mprint(" Tesseract v4 : default mode is 1.\n"); - mprint(" -mkvlang: For MKV subtitles, select which language's caption\n"); + mprint(" --mkvlang: For MKV subtitles, select which language's caption\n"); mprint(" stream will be processed. e.g. 'eng' for English.\n"); mprint(" Language codes can be either the 3 letters bibliographic\n"); mprint(" ISO-639-2 form (like \"fre\" for french) or a language\n"); mprint(" code followed by a dash and a country code for specialities\n"); mprint(" in languages (like \"fre-ca\" for Canadian French).\n"); - mprint(" -nospupngocr When processing DVB don't use the OCR to write the text as\n"); + mprint(" --no-spupngocr When processing DVB don't use the OCR to write the text as\n"); mprint(" comments in the XML file.\n"); - mprint(" -font: Specify the full path of the font that is to be used when\n"); + mprint(" --font: Specify the full path of the font that is to be used when\n"); mprint(" generating SPUPNG files. If not specified, you need to\n"); mprint(" have the default font installed (Helvetica for macOS, Calibri\n"); - mprint(" for Windows, and Noto for other operating systems at their\n)"); - mprint(" default location\n)"); - mprint(" -italics: Specify the full path of the italics font that is to be used when\n"); + mprint(" for Windows, and Noto for other operating systems at their)\n"); + mprint(" default location)\n"); + mprint(" --italics: Specify the full path of the italics font that is to be used when\n"); mprint(" generating SPUPNG files. If not specified, you need to\n"); mprint(" have the default font installed (Helvetica Oblique for macOS, Calibri Italic\n"); - mprint(" for Windows, and NotoSans Italic for other operating systems at their\n)"); - mprint(" default location\n)"); + mprint(" for Windows, and NotoSans Italic for other operating systems at their)\n"); + mprint(" default location)\n"); mprint("\n"); mprint("Options that affect how ccextractor reads and writes (buffering):\n"); - mprint(" -bi --bufferinput: Forces input buffering.\n"); - mprint(" -nobi -nobufferinput: Disables input buffering.\n"); - mprint(" -bs --buffersize val: Specify a size for reading, in bytes (suffix with K or\n"); + mprint(" --bufferinput: Forces input buffering.\n"); + mprint(" --no-bufferinput: Disables input buffering.\n"); + mprint(" --buffersize val: Specify a size for reading, in bytes (suffix with K or\n"); mprint(" or M for kilobytes and megabytes). Default is 16M.\n"); - mprint(" -koc: keep-output-close. If used then CCExtractor will close\n"); + mprint(" --koc: keep-output-close. If used then CCExtractor will close\n"); mprint(" the output file after writing each subtitle frame and\n"); mprint(" attempt to create it again when needed.\n"); - mprint(" -ff --forceflush: Flush the file buffer whenever content is written.\n"); + mprint(" --forceflush: Flush the file buffer whenever content is written.\n"); mprint("\n"); mprint("Options that affect the built-in 608 closed caption decoder:\n"); - mprint(" -dru: Direct Roll-Up. When in roll-up mode, write character by\n"); + mprint(" --dru: Direct Roll-Up. When in roll-up mode, write character by\n"); mprint(" character instead of line by line. Note that this\n"); mprint(" produces (much) larger files.\n"); - mprint(" -noru --norollup: If you hate the repeated lines caused by the roll-up\n"); + mprint(" --no-rollup: If you hate the repeated lines caused by the roll-up\n"); mprint(" emulation, you can have ccextractor write only one\n"); mprint(" line at a time, getting rid of these repeated lines.\n"); - mprint(" -ru1 / ru2 / ru3: roll-up captions can consist of 2, 3 or 4 visible\n"); + mprint(" --ru1 / ru2 / ru3: roll-up captions can consist of 2, 3 or 4 visible\n"); mprint(" lines at any time (the number of lines is part of\n"); mprint(" the transmission). If having 3 or 4 lines annoys\n"); - mprint(" you you can use -ru to force the decoder to always\n"); + mprint(" you you can use --ru to force the decoder to always\n"); mprint(" use 1, 2 or 3 lines. Note that 1 line is not\n"); mprint(" a real mode rollup mode, so CCExtractor does what\n"); mprint(" it can.\n"); - mprint(" In -ru1 the start timestamp is actually the timestamp\n"); + mprint(" In --ru1 the start timestamp is actually the timestamp\n"); mprint(" of the first character received which is possibly more\n"); mprint(" accurate.\n"); mprint("\n"); mprint("Options that affect timing:\n"); - mprint(" -delay ms: For srt/sami/webvtt, add this number of milliseconds to\n"); - mprint(" all times. For example, -delay 400 makes subtitles\n"); + mprint(" --delay ms: For srt/sami/webvtt, add this number of milliseconds to\n"); + mprint(" all times. For example, --delay 400 makes subtitles\n"); mprint(" appear 400ms late. You can also use negative numbers\n"); mprint(" to make subs appear early.\n"); - mprint("Notes on times: -startat and -endat times are used first, then -delay.\n"); - mprint("So if you use -srt -startat 3:00 -endat 5:00 -delay 120000, ccextractor will\n"); - mprint("generate a .srt file, with only data from 3:00 to 5:00 in the input file(s)\n"); - mprint("and then add that (huge) delay, which would make the final file start at\n"); - mprint("5:00 and end at 7:00.\n\n"); mprint("Options that affect what segment of the input file(s) to process:\n"); - mprint(" -startat time: Only write caption information that starts after the\n"); + mprint(" --startat time: Only write caption information that starts after the\n"); mprint(" given time.\n"); mprint(" Time can be seconds, MM:SS or HH:MM:SS.\n"); - mprint(" For example, -startat 3:00 means 'start writing from\n"); + mprint(" For example, --startat 3:00 means 'start writing from\n"); mprint(" minute 3.\n"); - mprint(" -endat time: Stop processing after the given time (same format as\n"); + mprint(" --endat time: Stop processing after the given time (same format as\n"); mprint(" -startat).\n"); - mprint(" The -startat and -endat options are honored in all\n"); + mprint(" The --startat and --endat options are honored in all\n"); mprint(" output formats. In all formats with timing information\n"); mprint(" the times are unchanged.\n"); - mprint("-scr --screenfuls num: Write 'num' screenfuls and terminate processing.\n\n"); + mprint(" --screenfuls num: Write 'num' screenfuls and terminate processing.\n\n"); mprint("Options that affect which codec is to be used have to be searched in input\n"); - mprint(" If codec type is not selected then first elementary stream suitable for \n" - " subtitle is selected, please consider -teletext -noteletext override this\n" - " option.\n" - " -codec dvbsub select the dvb subtitle from all elementary stream,\n" + mprint(" --codec dvbsub select the dvb subtitle from all elementary stream,\n" " if stream of dvb subtitle type is not found then \n" " nothing is selected and no subtitle is generated\n" - " -nocodec dvbsub ignore dvb subtitle and follow default behaviour\n" - " -codec teletext select the teletext subtitle from elementary stream\n" - " -nocodec teletext ignore teletext subtitle\n" - " NOTE: option given in form -foo=bar ,-foo = bar and --foo=bar are invalid\n" - " valid option are only in form -foo bar\n" - " nocodec and codec parameter must not be same if found to be same \n" - " then parameter of nocodec is ignored, this flag should be passed \n" - " once, more then one are not supported yet and last parameter would \n" - " taken in consideration\n"); + " --no-codec dvbsub ignore dvb subtitle and follow default behaviour\n" + " --codec teletext select the teletext subtitle from elementary stream\n" + " --no-codec teletext ignore teletext subtitle\n"); mprint("Adding start and end credits:\n"); - mprint(" CCExtractor can _try_ to add a custom message (for credits for example) at\n"); - mprint(" the start and end of the file, looking for a window where there are no\n"); - mprint(" captions. If there is no such window, then no text will be added.\n"); - mprint(" The start window must be between the times given and must have enough time\n"); - mprint(" to display the message for at least the specified time.\n"); mprint(" --startcreditstext txt: Write this text as start credits. If there are\n"); mprint(" several lines, separate them with the\n"); mprint(" characters \\n, for example Line1\\nLine 2.\n"); @@ -816,29 +774,29 @@ void print_usage(void) mprint("Options that affect debug data:\n"); - mprint(" -debug: Show lots of debugging output.\n"); - mprint(" -608: Print debug traces from the EIA-608 decoder.\n"); + mprint(" --debug: Show lots of debugging output.\n"); + mprint(" --608: Print debug traces from the EIA-608 decoder.\n"); mprint(" If you need to submit a bug report, please send\n"); mprint(" the output from this option.\n"); - mprint(" -708: Print debug information from the (currently\n"); + mprint(" --708: Print debug information from the (currently\n"); mprint(" in development) EIA-708 (DTV) decoder.\n"); - mprint(" -goppts: Enable lots of time stamp output.\n"); - mprint(" -xdsdebug: Enable XDS debug data (lots of it).\n"); - mprint(" -vides: Print debug info about the analysed elementary\n"); + mprint(" --goppts: Enable lots of time stamp output.\n"); + mprint(" --xdsdebug: Enable XDS debug data (lots of it).\n"); + mprint(" --vides: Print debug info about the analysed elementary\n"); mprint(" video stream.\n"); - mprint(" -cbraw: Print debug trace with the raw 608/708 data with\n"); + mprint(" --cbraw: Print debug trace with the raw 608/708 data with\n"); mprint(" time stamps.\n"); - mprint(" -nosync: Disable the syncing code. Only useful for debugging\n"); + mprint(" --no-sync: Disable the syncing code. Only useful for debugging\n"); mprint(" purposes.\n"); - mprint(" -fullbin: Disable the removal of trailing padding blocks\n"); + mprint(" --fullbin: Disable the removal of trailing padding blocks\n"); mprint(" when exporting to bin format. Only useful for\n"); mprint(" for debugging purposes.\n"); - mprint(" -parsedebug: Print debug info about the parsed container\n"); + mprint(" --parsedebug: Print debug info about the parsed container\n"); mprint(" file. (Only for TS/ASF files at the moment.)\n"); - mprint(" -parsePAT: Print Program Association Table dump.\n"); - mprint(" -parsePMT: Print Program Map Table dump.\n"); - mprint(" -dumpdef: Hex-dump defective TS packets.\n"); - mprint(" -investigate_packets: If no CC packets are detected based on the PMT, try\n"); + mprint(" --parsePAT: Print Program Association Table dump.\n"); + mprint(" --parsePMT: Print Program Map Table dump.\n"); + mprint(" --dumpdef: Hex-dump defective TS packets.\n"); + mprint(" --investigate-packets: If no CC packets are detected based on the PMT, try\n"); mprint(" to find data in all packets by scanning.\n"); #ifdef ENABLE_SHARING mprint(" -sharing-debug: Print extracted CC sharing service messages\n"); @@ -847,14 +805,14 @@ void print_usage(void) mprint("Teletext related options:\n"); - mprint(" -tpage page: Use this page for subtitles (if this parameter\n"); + mprint(" --tpage page: Use this page for subtitles (if this parameter\n"); mprint(" is not used, try to autodetect). In Spain the\n"); mprint(" page is always 888, may vary in other countries.\n"); - mprint(" -tverbose: Enable verbose mode in the teletext decoder.\n\n"); - mprint(" -teletext: Force teletext mode even if teletext is not detected.\n"); - mprint(" If used, you should also pass -datapid to specify\n"); + mprint(" --tverbose: Enable verbose mode in the teletext decoder.\n\n"); + mprint(" --teletext: Force teletext mode even if teletext is not detected.\n"); + mprint(" If used, you should also pass --datapid to specify\n"); mprint(" the stream ID you want to process.\n"); - mprint(" -noteletext: Disable teletext processing. This might be needed\n"); + mprint(" --no-teletext: Disable teletext processing. This might be needed\n"); mprint(" for video streams that have both teletext packets\n"); mprint(" and CEA-608/708 packets (if teletext is processed\n"); mprint(" then CEA-608/708 processing is disabled).\n"); @@ -862,7 +820,7 @@ void print_usage(void) mprint("Transcript customizing options:\n"); - mprint(" -customtxt format: Use the passed format to customize the (Timed) Transcript\n"); + mprint(" --customtxt format: Use the passed format to customize the (Timed) Transcript\n"); mprint(" output. The format must be like this: 1100100 (7 digits).\n"); mprint(" These indicate whether the next things should be\n"); mprint(" displayed or not in the (timed) transcript. They\n"); @@ -880,117 +838,164 @@ void print_usage(void) mprint(" 1111001 is the default setting for -ucla\n"); mprint(" Make sure you use this parameter after others that might\n"); mprint(" affect these settings (-out, -ucla, -xds, -txt, \n"); - mprint(" -ttxt ...)\n"); + mprint(" --ttxt ...)\n"); mprint("\n"); mprint("Communication with other programs and console output:\n"); - mprint(" --gui_mode_reports: Report progress and interesting events to stderr\n"); + mprint(" --gui-mode-reports: Report progress and interesting events to stderr\n"); mprint(" in a easy to parse format. This is intended to be\n"); mprint(" used by other programs. See docs directory for.\n"); mprint(" details.\n"); - mprint(" --no_progress_bar: Suppress the output of the progress bar\n"); - mprint(" -quiet: Don't write any message.\n"); + mprint(" --no-progress-bar: Suppress the output of the progress bar\n"); + mprint(" --quiet: Don't write any message.\n"); mprint("\n"); #ifdef ENABLE_SHARING mprint("Sharing extracted captions via TCP:\n"); - mprint(" -enable-sharing: Enables real-time sharing of extracted captions\n"); - mprint(" -sharing-url: Set url for sharing service in nanomsg format. Default: \"tcp://*:3269\"\n"); + mprint(" --enable-sharing: Enables real-time sharing of extracted captions\n"); + mprint(" --sharing-url: Set url for sharing service in nanomsg format. Default: \"tcp://*:3269\"\n"); mprint("\n"); mprint("CCTranslate application integration:\n"); - mprint(" -translate: Enable Translation tool and set target languages\n"); - mprint(" in csv format (e.g. -translate ru,fr,it\n"); - mprint(" -translate-auth: Set Translation Service authorization data to make translation possible\n"); + mprint(" --translate: Enable Translation tool and set target languages\n"); + mprint(" in csv format (e.g. --translate ru,fr,it\n"); + mprint(" --translate-auth: Set Translation Service authorization data to make translation possible\n"); mprint(" In case of Google Translate API - API Key\n"); #endif // ENABLE_SHARING - - mprint("Notes on the CEA-708 decoder: While it is starting to be useful, it's\n"); - mprint("a work in progress. A number of things don't work yet in the decoder\n"); - mprint("itself, and many of the auxiliary tools (case conversion to name one)\n"); - mprint("won't do anything yet. Feel free to submit samples that cause problems\n"); - mprint("and feature requests.\n"); - mprint("\n"); - mprint("Notes on spupng output format:\n"); - mprint("One .xml file is created per output field. A set of .png files are created in\n"); - mprint("a directory with the same base name as the corresponding .xml file(s), but with\n"); - mprint("a .d extension. Each .png file will contain an image representing one caption\n"); - mprint("and named subNNNN.png, starting with sub0000.png.\n"); - mprint("For example, the command:\n"); - mprint(" ccextractor -out=spupng input.mpg\n"); - mprint("will create the files:\n"); - mprint(" input.xml\n"); - mprint(" input.d/sub0000.png\n"); - mprint(" input.d/sub0001.png\n"); - mprint(" ...\n"); - mprint("The command:\n"); - mprint(" ccextractor -out=spupng -o /tmp/output -12 input.mpg\n"); - mprint("will create the files:\n"); - mprint(" /tmp/output_1.xml\n"); - mprint(" /tmp/output_1.d/sub0000.png\n"); - mprint(" /tmp/output_1.d/sub0001.png\n"); - mprint(" ...\n"); - mprint(" /tmp/output_2.xml\n"); - mprint(" /tmp/output_2.d/sub0000.png\n"); - mprint(" /tmp/output_2.d/sub0001.png\n"); - mprint(" ...\n"); - mprint("\n"); mprint("Burned-in subtitle extraction:\n"); - mprint(" -hardsubx : Enable the burned-in subtitle extraction subsystem.\n"); + mprint(" --hardsubx : Enable the burned-in subtitle extraction subsystem.\n"); mprint("\n"); - mprint(" NOTE: The following options will work only if -hardsubx is \n"); - mprint(" specified before them:-\n"); + mprint(" NOTE: This is needed to use the below burned-in \n"); + mprint(" subtitle extractor options\n"); mprint("\n"); - mprint(" -tickertext : Search for burned-in ticker text at the bottom of\n"); + mprint(" --tickertext : Search for burned-in ticker text at the bottom of\n"); mprint(" the screen.\n"); mprint("\n"); - mprint(" -ocr_mode : Set the OCR mode to either frame-wise, word-wise\n"); + mprint(" --ocr-mode : Set the OCR mode to either frame-wise, word-wise\n"); mprint(" or letter wise.\n"); - mprint(" e.g. -ocr_mode frame (default), -ocr_mode word, \n"); - mprint(" -ocr_mode letter\n"); + mprint(" e.g. --ocr-mode frame (default), --ocr-mode word, \n"); + mprint(" --ocr-mode letter\n"); mprint("\n"); - mprint(" -subcolor : Specify the color of the subtitles\n"); + mprint(" --subcolor : Specify the color of the subtitles\n"); mprint(" Possible values are in the set \n"); mprint(" {white,yellow,green,cyan,blue,magenta,red}.\n"); mprint(" Alternatively, a custom hue value between 1 and 360 \n"); mprint(" may also be specified.\n"); - mprint(" e.g. -subcolor white or -subcolor 270 (for violet).\n"); + mprint(" e.g. --subcolor white or --subcolor 270 (for violet).\n"); mprint(" Refer to an HSV color chart for values.\n"); mprint("\n"); - mprint(" -min_sub_duration : Specify the minimum duration that a subtitle line \n"); + mprint(" --min-sub-duration : Specify the minimum duration that a subtitle line \n"); mprint(" must exist on the screen.\n"); mprint(" The value is specified in seconds.\n"); mprint(" A lower value gives better results, but takes more \n"); mprint(" processing time.\n"); mprint(" The recommended value is 0.5 (default).\n"); - mprint(" e.g. -min_sub_duration 1.0 (for a duration of 1 second)\n"); + mprint(" e.g. --min-sub-duration 1.0 (for a duration of 1 second)\n"); mprint("\n"); - mprint(" -detect_italics : Specify whether italics are to be detected from the \n"); + mprint(" --detect-italics : Specify whether italics are to be detected from the \n"); mprint(" OCR text.\n"); mprint(" Italic detection automatically enforces the OCR mode \n"); mprint(" to be word-wise"); mprint("\n"); - mprint(" -conf_thresh : Specify the classifier confidence threshold between\n"); + mprint(" --conf-thresh : Specify the classifier confidence threshold between\n"); mprint(" 1 and 100.\n"); mprint(" Try and use a threshold which works for you if you get \n"); mprint(" a lot of garbage text.\n"); - mprint(" e.g. -conf_thresh 50\n"); + mprint(" e.g. --conf-thresh 50\n"); mprint("\n"); - mprint(" -whiteness_thresh : For white subtitles only, specify the luminance \n"); + mprint(" --whiteness-thresh : For white subtitles only, specify the luminance \n"); mprint(" threshold between 1 and 100\n"); mprint(" This threshold is content dependent, and adjusting\n"); mprint(" values may give you better results\n"); mprint(" Recommended values are in the range 80 to 100.\n"); mprint(" The default value is 95\n"); mprint("\n"); - mprint(" -hcc : This option will be used if the file should have both\n"); + mprint(" --hcc : This option will be used if the file should have both\n"); mprint(" closed captions and burned in subtitles\n"); mprint(" An example command for burned-in subtitle extraction is as follows:\n"); - mprint(" ccextractor video.mp4 -hardsubx -subcolor white -detect_italics \n"); - mprint(" -whiteness_thresh 90 -conf_thresh 60\n"); + mprint(" ccextractor video.mp4 --hardsubx -subcolor white --detect-italics \n"); + mprint(" --whiteness-thresh 90 --conf-thresh 60\n"); mprint("\n"); mprint("\n --version : Display current CCExtractor version and detailed information.\n"); + mprint("\n"); + mprint("Notes on File name related options:\n"); + mprint(" You can pass as many input files as you need. They will be processed in order.\n"); + mprint(" If a file name is suffixed by +, ccextractor will try to follow a numerical\n"); + mprint(" sequence. For example, DVD001.VOB+ means DVD001.VOB, DVD002.VOB and so on\n"); + mprint(" until there are no more files.\n"); + mprint(" Output will be one single file (either raw or srt). Use this if you made your\n"); + mprint(" recording in several cuts (to skip commercials for example) but you want one\n"); + mprint(" subtitle file with contiguous timing.\n"); + mprint("\n"); + mprint("Notes on Options that affect what will be processed:\n"); + mprint(" In general, if you want English subtitles you don't need to use these options\n"); + mprint(" as they are broadcast in field 1, channel 1. If you want the second language\n"); + mprint(" (usually Spanish) you may need to try -2, or -cc2, or both.\n"); + mprint("\n"); + mprint("Notes on Levenshtein distance:\n"); + mprint(" When processing teletext files CCExtractor tries to correct typos by\n"); + mprint(" comparing consecutive lines. If line N+1 is almost identical to line N except\n"); + mprint(" for minor changes (plus next characters) then it assumes that line N that a\n"); + mprint(" typo that was corrected in N+1. This is currently implemented in teletext\n"); + mprint(" because it's where samples files that could benefit from this were available.\n"); + mprint(" You can adjust, or disable, the algorithm settings with the following\n"); + mprint(" parameters.\n"); + mprint("\n"); + mprint("Notes on times:\n" + " --startat and --endat times are used first, then -delay.\n"); + mprint(" So if you use --srt -startat 3:00 --endat 5:00 --delay 120000, ccextractor will\n"); + mprint(" generate a .srt file, with only data from 3:00 to 5:00 in the input file(s)\n"); + mprint(" and then add that (huge) delay, which would make the final file start at\n"); + mprint(" 5:00 and end at 7:00.\n"); + mprint("\n"); + mprint("Notes on codec options:\n"); + mprint(" If codec type is not selected then first elementary stream suitable for \n" + " subtitle is selected, please consider --teletext -noteletext override this\n" + " option.\n"); + mprint(" no-codec and codec parameter must not be same if found to be same \n" + " then parameter of no-codec is ignored, this flag should be passed \n" + " once, more then one are not supported yet and last parameter would \n" + " taken in consideration\n"); + mprint("\n"); + mprint("Notes on adding credits:\n"); + mprint(" CCExtractor can _try_ to add a custom message (for credits for example) at\n"); + mprint(" the start and end of the file, looking for a window where there are no\n"); + mprint(" captions. If there is no such window, then no text will be added.\n"); + mprint(" The start window must be between the times given and must have enough time\n"); + mprint(" to display the message for at least the specified time.\n"); + mprint("\n"); + mprint("Notes on the CEA-708 decoder:\n" + " While it is starting to be useful, it's\n"); + mprint(" a work in progress. A number of things don't work yet in the decoder\n"); + mprint(" itself, and many of the auxiliary tools (case conversion to name one)\n"); + mprint(" won't do anything yet. Feel free to submit samples that cause problems\n"); + mprint(" and feature requests.\n"); + mprint("\n"); + mprint("Notes on spupng output format:\n"); + mprint(" One .xml file is created per output field. A set of .png files are created in\n"); + mprint(" a directory with the same base name as the corresponding .xml file(s), but with\n"); + mprint(" a .d extension. Each .png file will contain an image representing one caption\n"); + mprint(" and named subNNNN.png, starting with sub0000.png.\n"); + mprint(" For example, the command:\n"); + mprint(" ccextractor -out=spupng input.mpg\n"); + mprint(" will create the files:\n"); + mprint(" input.xml\n"); + mprint(" input.d/sub0000.png\n"); + mprint(" input.d/sub0001.png\n"); + mprint(" ...\n"); + mprint(" The command:\n"); + mprint(" ccextractor -out=spupng -o /tmp/output --12 input.mpg\n"); + mprint(" will create the files:\n"); + mprint(" /tmp/output_1.xml\n"); + mprint(" /tmp/output_1.d/sub0000.png\n"); + mprint(" /tmp/output_1.d/sub0001.png\n"); + mprint(" ...\n"); + mprint(" /tmp/output_2.xml\n"); + mprint(" /tmp/output_2.d/sub0000.png\n"); + mprint(" /tmp/output_2.d/sub0001.png\n"); + mprint(" ..."); + mprint("\n"); } unsigned char sha256_buf[16384]; @@ -1238,7 +1243,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) version(argv[0]); return EXIT_WITH_HELP; } - if (strcmp(argv[i], "-") == 0 || strcmp(argv[i], "-stdin") == 0) + if (strcmp(argv[i], "-") == 0 || strcmp(argv[i], "--stdin") == 0) { #ifdef WIN32 setmode(fileno(stdin), O_BINARY); @@ -1268,21 +1273,21 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } #ifdef ENABLE_HARDSUBX - // Parse -hardsubx and related parameters - if (strcmp(argv[i], "-hardsubx") == 0) + // Parse --hardsubx and related parameters + if (strcmp(argv[i], "--hardsubx") == 0) { opt->hardsubx = 1; continue; } if (opt->hardsubx == 1) { - if (strcmp(argv[i], "-hcc") == 0) + if (strcmp(argv[i], "--hcc") == 0) { // if extraction of both burned in and non burned in subs opt->hardsubx_and_common = 1; continue; } - if (strcmp(argv[i], "-ocr_mode") == 0) + if (strcmp(argv[i], "--ocr-mode") == 0) { if (i < argc - 1) { @@ -1312,7 +1317,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) fatal(EXIT_MALFORMED_PARAMETER, "-ocr_mode has no argument.\nValid values are {frame,word,letter}\n"); } } - if (strcmp(argv[i], "-subcolor") == 0 || strcmp(argv[i], "-sub_color") == 0) + if (strcmp(argv[i], "--subcolor") == 0) { if (i < argc - 1) { @@ -1362,7 +1367,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) opt->hardsubx_hue = atof(str); if (opt->hardsubx_hue <= 0.0 || opt->hardsubx_hue > 360.0) { - fatal(EXIT_MALFORMED_PARAMETER, "-subcolor has either 0 or an invalid hue value supplied.\nIf you want to detect red subtitles, pass '-subcolor red' or a slightly higher hue value (e.g. 0.1)\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--subcolor has either 0 or an invalid hue value supplied.\nIf you want to detect red subtitles, pass '-subcolor red' or a slightly higher hue value (e.g. 0.1)\n"); } } @@ -1370,10 +1375,10 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-subcolor has no argument.\nValid values are {white,yellow,green,cyan,blue,magenta,red} or a custom hue value between 0 and 360\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--subcolor has no argument.\nValid values are {white,yellow,green,cyan,blue,magenta,red} or a custom hue value between 0 and 360\n"); } } - if (strcmp(argv[i], "-min_sub_duration") == 0) + if (strcmp(argv[i], "--min-sub-duration") == 0) { if (i < argc - 1) { @@ -1384,22 +1389,22 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) opt->hardsubx_min_sub_duration = atof(str); if (opt->hardsubx_min_sub_duration == 0.0) { - fatal(EXIT_MALFORMED_PARAMETER, "-min_sub_duration has either 0 or an invalid value supplied\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--min-sub-duration has either 0 or an invalid value supplied\n"); } continue; } else { - fatal(EXIT_MALFORMED_PARAMETER, "-min_sub_duration has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--min-sub-duration has no argument.\n"); } } - if (strcmp(argv[i], "-detect_italics") == 0) + if (strcmp(argv[i], "--detect-italics") == 0) { opt->hardsubx_detect_italics = 1; continue; } - if (strcmp(argv[i], "-conf_thresh") == 0) + if (strcmp(argv[i], "--conf-thresh") == 0) { if (i < argc - 1) { @@ -1410,17 +1415,17 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) opt->hardsubx_conf_thresh = atof(str); if (opt->hardsubx_conf_thresh <= 0.0 || opt->hardsubx_conf_thresh > 100.0) { - fatal(EXIT_MALFORMED_PARAMETER, "-conf_thresh has either 0 or an invalid value supplied\nValid values are in (0.0,100.0)\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--conf-thresh has either 0 or an invalid value supplied\nValid values are in (0.0,100.0)\n"); } continue; } else { - fatal(EXIT_MALFORMED_PARAMETER, "-conf_thresh has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--conf-thresh has no argument.\n"); } } - if (strcmp(argv[i], "-whiteness_thresh") == 0 || strcmp(argv[i], "-lum_thresh") == 0) + if (strcmp(argv[i], "--whiteness-thresh") == 0 || strcmp(argv[i], "--lum-thresh") == 0) { if (i < argc - 1) { @@ -1431,40 +1436,40 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) opt->hardsubx_lum_thresh = atof(str); if (opt->hardsubx_lum_thresh <= 0.0 || opt->hardsubx_conf_thresh > 100.0) { - fatal(EXIT_MALFORMED_PARAMETER, "-whiteness_thresh has either 0 or an invalid value supplied\nValid values are in (0.0,100.0)\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--whiteness-thresh has either 0 or an invalid value supplied\nValid values are in (0.0,100.0)\n"); } continue; } else { - fatal(EXIT_MALFORMED_PARAMETER, "-whiteness_thresh has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--whiteness-thresh has no argument.\n"); } } } #endif // ENABLE_HARDSUBX - if (strcmp(argv[i], "-chapters") == 0) + if (strcmp(argv[i], "--chapters") == 0) { opt->extract_chapters = 1; continue; } - if (strcmp(argv[i], "-bi") == 0 || strcmp(argv[i], "--bufferinput") == 0) + if (strcmp(argv[i], "--bufferinput") == 0) { opt->buffer_input = 1; continue; } - if (strcmp(argv[i], "-nobi") == 0 || strcmp(argv[i], "--nobufferinput") == 0) + if (strcmp(argv[i], "--no-bufferinput") == 0) { opt->buffer_input = 0; continue; } - if (strcmp(argv[i], "-koc") == 0) + if (strcmp(argv[i], "--koc") == 0) { opt->keep_output_closed = 1; continue; } - if (strcmp(argv[i], "-ff") == 0 || strcmp(argv[i], "--forceflush") == 0) + if (strcmp(argv[i], "--forceflush") == 0) { opt->force_flush = 1; continue; @@ -1474,7 +1479,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) opt->append_mode = 1; continue; } - if (strcmp(argv[i], "-bs") == 0 || strcmp(argv[i], "--buffersize") == 0) + if (strcmp(argv[i], "--buffersize") == 0) { if (i < argc - 1) { @@ -1493,69 +1498,69 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) fatal(EXIT_MALFORMED_PARAMETER, "--buffersize has no argument.\n"); } } - if (strcmp(argv[i], "-dru") == 0) + if (strcmp(argv[i], "--dru") == 0) { opt->settings_608.direct_rollup = 1; continue; } - if (strcmp(argv[i], "-nofc") == 0 || strcmp(argv[i], "--nofontcolor") == 0) + if (strcmp(argv[i], "--no-fontcolor") == 0) { opt->nofontcolor = 1; continue; } - if (strcmp(argv[i], "--nohtmlescape") == 0) + if (strcmp(argv[i], "--no-htmlescape") == 0) { opt->nohtmlescape = 1; continue; } - if (strcmp(argv[i], "-bom") == 0) + if (strcmp(argv[i], "--bom") == 0) { opt->enc_cfg.no_bom = 0; continue; } - if (strcmp(argv[i], "-nobom") == 0) + if (strcmp(argv[i], "--no-bom") == 0) { opt->enc_cfg.no_bom = 1; continue; } - if (strcmp(argv[i], "-sem") == 0) + if (strcmp(argv[i], "--sem") == 0) { opt->enc_cfg.with_semaphore = 1; continue; } - if (strcmp(argv[i], "-nots") == 0 || strcmp(argv[i], "--notypesetting") == 0) + if (strcmp(argv[i], "--no-typesetting") == 0) { opt->notypesetting = 1; continue; } - if (strcmp(argv[i], "--timestamp-map") == 0 || strcmp(argv[i], "-tm") == 0) + if (strcmp(argv[i], "--timestamp-map") == 0) { opt->timestamp_map = 1; continue; } /* Input file formats */ - if (strcmp(argv[i], "-es") == 0 || - strcmp(argv[i], "-ts") == 0 || - strcmp(argv[i], "-ps") == 0 || - strcmp(argv[i], "-asf") == 0 || - strcmp(argv[i], "-wtv") == 0 || - strcmp(argv[i], "-mp4") == 0 || - strcmp(argv[i], "-mkv") == 0 || + if (strcmp(argv[i], "--es") == 0 || + strcmp(argv[i], "--ts") == 0 || + strcmp(argv[i], "--ps") == 0 || + strcmp(argv[i], "--asf") == 0 || + strcmp(argv[i], "--wtv") == 0 || + strcmp(argv[i], "--mp4") == 0 || + strcmp(argv[i], "--mkv") == 0 || strcmp(argv[i], "--dvr-ms") == 0) { set_input_format(opt, argv[i]); continue; } - if (strncmp(argv[i], "-in=", 4) == 0) + if (strncmp(argv[i], "--in=", 5) == 0) { - set_input_format(opt, argv[i] + 4); + set_input_format(opt, argv[i] + 5); continue; } /*user specified subtitle to be selected */ - if (strcmp(argv[i], "-codec") == 0) + if (strcmp(argv[i], "--codec") == 0) { if (i < argc - 1) { @@ -1578,12 +1583,12 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-codec has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--codec has no argument.\n"); } } /*user specified subtitle to be selected */ - if (strcmp(argv[i], "-nocodec") == 0) + if (strcmp(argv[i], "--no-codec") == 0) { if (i < argc - 1) { @@ -1599,18 +1604,18 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "Invalid option for nocodec.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "Invalid option for no-codec.\n"); } continue; } else { - fatal(EXIT_MALFORMED_PARAMETER, "-nocodec has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--no-codec has no argument.\n"); } } - if (strcmp(argv[i], "-dvblang") == 0) + if (strcmp(argv[i], "--dvblang") == 0) { if (i < argc - 1) { @@ -1627,11 +1632,11 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-dvblang has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--dvblang has no argument.\n"); } } - if (strcmp(argv[i], "-ocrlang") == 0) + if (strcmp(argv[i], "--ocrlang") == 0) { if (i++ < argc - 1) { @@ -1640,10 +1645,10 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-ocrlang has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--ocrlang has no argument.\n"); } } - if (strcmp(argv[i], "-quant") == 0) + if (strcmp(argv[i], "--quant") == 0) { if (i < argc - 1) { @@ -1653,15 +1658,15 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-quant has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--quant has no argument.\n"); } } - if (strcmp(argv[i], "-nospupngocr") == 0) + if (strcmp(argv[i], "--no-spupngocr") == 0) { opt->enc_cfg.nospupngocr = 1; continue; } - if (strcmp(argv[i], "-oem") == 0) + if (strcmp(argv[i], "--oem") == 0) { if (i < argc - 1) { @@ -1672,17 +1677,17 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) opt->ocr_oem = atoi(str); if (opt->ocr_oem < 0 || opt->ocr_oem > 2) { - fatal(EXIT_MALFORMED_PARAMETER, "-oem must be 0, 1 or 2\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--oem must be 0, 1 or 2\n"); } continue; } else { - fatal(EXIT_MALFORMED_PARAMETER, "-oem has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--oem has no argument.\n"); } } - if (strcmp(argv[i], "-mkvlang") == 0) + if (strcmp(argv[i], "--mkvlang") == 0) { if (i < argc - 1) { @@ -1696,24 +1701,24 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-mkvlang has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--mkvlang has no argument.\n"); } } /* Output file formats */ - if (strcmp(argv[i], "-srt") == 0 || - strcmp(argv[i], "-mcc") == 0 || strcmp(argv[i], "-dvdraw") == 0 || - strcmp(argv[i], "-smi") == 0 || strcmp(argv[i], "-sami") == 0 || - strcmp(argv[i], "-txt") == 0 || strcmp(argv[i], "--transcript") == 0 || - strcmp(argv[i], "-ttxt") == 0 || strcmp(argv[i], "--timedtranscript") == 0 || - strcmp(argv[i], "-webvtt") == 0 || strcmp(argv[i], "-null") == 0) + if (strcmp(argv[i], "--srt") == 0 || + strcmp(argv[i], "--mcc") == 0 || strcmp(argv[i], "--dvdraw") == 0 || + strcmp(argv[i], "--smi") == 0 || strcmp(argv[i], "--sami") == 0 || + strcmp(argv[i], "--txt") == 0 || strcmp(argv[i], "--transcript") == 0 || + strcmp(argv[i], "--ttxt") == 0 || strcmp(argv[i], "--timedtranscript") == 0 || + strcmp(argv[i], "--webvtt") == 0 || strcmp(argv[i], "--null") == 0) { set_output_format(opt, argv[i]); continue; } - if (strncmp(argv[i], "-out=", 5) == 0) + if (strncmp(argv[i], "--out=", 6) == 0) { - set_output_format(opt, argv[i] + 5); + set_output_format(opt, argv[i] + 6); continue; } @@ -1842,38 +1847,32 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } /* More stuff */ - if (strcmp(argv[i], "-ve") == 0 || strcmp(argv[i], "--videoedited") == 0) + if (strcmp(argv[i], "--videoedited") == 0) { opt->binary_concat = 0; continue; } - if (strcmp(argv[i], "-12") == 0) - { - opt->is_608_enabled = 1; - opt->extract = 12; - continue; - } - if (strcmp(argv[i], "-gt") == 0 || strcmp(argv[i], "--goptime") == 0) + if (strcmp(argv[i], "--goptime") == 0) { opt->use_gop_as_pts = 1; continue; } - if (strcmp(argv[i], "-nogt") == 0 || strcmp(argv[i], "--nogoptime") == 0) + if (strcmp(argv[i], "--no-goptime") == 0) { opt->use_gop_as_pts = -1; // Don't use even if we would want to continue; } - if (strcmp(argv[i], "-fp") == 0 || strcmp(argv[i], "--fixpadding") == 0) + if (strcmp(argv[i], "--fixpadding") == 0) { opt->fix_padding = 1; continue; } - if (strcmp(argv[i], "-90090") == 0) + if (strcmp(argv[i], "--90090") == 0) { MPEG_CLOCK_FREQ = 90090; continue; } - if (strcmp(argv[i], "--noscte20") == 0) + if (strcmp(argv[i], "--no-scte20") == 0) { opt->noscte20 = 1; continue; @@ -1883,34 +1882,34 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) opt->webvtt_create_css = 1; continue; } - if (strcmp(argv[i], "-noru") == 0 || strcmp(argv[i], "--norollup") == 0) + if (strcmp(argv[i], "--no-rollup") == 0) { opt->no_rollup = 1; opt->settings_608.no_rollup = 1; opt->settings_dtvcc.no_rollup = 1; continue; } - if (strcmp(argv[i], "-ru1") == 0) + if (strcmp(argv[i], "--ru1") == 0) { opt->settings_608.force_rollup = 1; continue; } - if (strcmp(argv[i], "-ru2") == 0) + if (strcmp(argv[i], "--ru2") == 0) { opt->settings_608.force_rollup = 2; continue; } - if (strcmp(argv[i], "-ru3") == 0) + if (strcmp(argv[i], "--ru3") == 0) { opt->settings_608.force_rollup = 3; continue; } - if (strcmp(argv[i], "-trim") == 0) + if (strcmp(argv[i], "--trim") == 0) { opt->enc_cfg.trim_subs = 1; continue; } - if (strcmp(argv[i], "-outinterval") == 0) + if (strcmp(argv[i], "--outinterval") == 0) { if (i < argc - 1) { @@ -1923,35 +1922,35 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) fatal(EXIT_MALFORMED_PARAMETER, "-outinterval has no argument.\n"); } } - if (strcmp(argv[i], "--segmentonkeyonly") == 0 || strcmp(argv[i], "-key") == 0) + if (strcmp(argv[i], "--segmentonkeyonly") == 0) { opt->segment_on_key_frames_only = 1; opt->analyze_video_stream = 1; continue; } - if (strcmp(argv[i], "--gui_mode_reports") == 0) + if (strcmp(argv[i], "--gui-mode-reports") == 0) { opt->gui_mode_reports = 1; continue; } - if (strcmp(argv[i], "--no_progress_bar") == 0) + if (strcmp(argv[i], "--no-progress-bar") == 0) { opt->no_progress_bar = 1; continue; } - if (strcmp(argv[i], "--splitbysentence") == 0 || strcmp(argv[i], "-sbs") == 0) + if (strcmp(argv[i], "--splitbysentence") == 0) { opt->enc_cfg.splitbysentence = 1; continue; } - if (strcmp(argv[i], "--sentencecap") == 0 || strcmp(argv[i], "-sc") == 0) + if (strcmp(argv[i], "--sentencecap") == 0) { opt->enc_cfg.sentence_cap = 1; continue; } - if ((strcmp(argv[i], "--capfile") == 0 || strcmp(argv[i], "-caf") == 0) && i < argc - 1) + if (strcmp(argv[i], "--capfile") == 0 && i < argc - 1) { opt->enc_cfg.sentence_cap = 1; opt->sentence_cap_file = argv[i + 1]; @@ -1975,7 +1974,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) continue; } - if (strcmp(argv[i], "--program-number") == 0 || strcmp(argv[i], "-pn") == 0) + if (strcmp(argv[i], "--program-number") == 0) { if (i < argc - 1 && isanumber(argv[i + 1])) { @@ -1990,12 +1989,12 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) continue; } } - if (strcmp(argv[i], "-autoprogram") == 0) + if (strcmp(argv[i], "--autoprogram") == 0) { opt->demux_cfg.ts_autoprogram = 1; continue; } - if (strcmp(argv[i], "-multiprogram") == 0) + if (strcmp(argv[i], "--multiprogram") == 0) { opt->multiprogram = 1; opt->demux_cfg.ts_allprogram = CCX_TRUE; @@ -2015,7 +2014,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) continue; } } - if (strcmp(argv[i], "--defaultcolor") == 0 || strcmp(argv[i], "-dc") == 0) + if (strcmp(argv[i], "--defaultcolor") == 0) { if (i < argc - 1) { @@ -2033,23 +2032,23 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) fatal(EXIT_MALFORMED_PARAMETER, "--defaultcolor has no argument.\n"); } } - if (strcmp(argv[i], "-delay") == 0) + if (strcmp(argv[i], "--delay") == 0) { if (i < argc - 1) { i++; if (parsedelay(opt, argv[i])) { - fatal(EXIT_MALFORMED_PARAMETER, "-delay only accept integers (such as -300 or 300)\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--delay only accept integers (such as --300 or 300)\n"); } continue; } else { - fatal(EXIT_MALFORMED_PARAMETER, "-delay has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--delay has no argument.\n"); } } - if (strcmp(argv[i], "-scr") == 0 || strcmp(argv[i], "--screenfuls") == 0) + if (strcmp(argv[i], "--screenfuls") == 0) { if (i < argc - 1) { @@ -2066,111 +2065,118 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) fatal(EXIT_MALFORMED_PARAMETER, "--screenfuls has no argument.\n"); } } - if (strcmp(argv[i], "-startat") == 0) + if (strcmp(argv[i], "--startat") == 0) { if (i < argc - 1) { i++; if (stringztoms(argv[i], &opt->extraction_start) == -1) { - fatal(EXIT_MALFORMED_PARAMETER, "-startat only accepts SS, MM:SS or HH:MM:SS\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--startat only accepts SS, MM:SS or HH:MM:SS\n"); } continue; } else { - fatal(EXIT_MALFORMED_PARAMETER, "-startat has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--startat has no argument.\n"); } } - if (strcmp(argv[i], "-endat") == 0) + if (strcmp(argv[i], "--endat") == 0) { if (i < argc - 1) { i++; if (stringztoms(argv[i], &opt->extraction_end) == -1) { - fatal(EXIT_MALFORMED_PARAMETER, "-endat only accepts SS, MM:SS or HH:MM:SS\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--endat only accepts SS, MM:SS or HH:MM:SS\n"); } continue; } else { - fatal(EXIT_MALFORMED_PARAMETER, "-endat has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--endat has no argument.\n"); } } - if (strcmp(argv[i], "-1") == 0) + if (strcmp(argv[i], "--output-field") == 0) { - opt->is_608_enabled = 1; - opt->extract = 1; - continue; - } - if (strcmp(argv[i], "-2") == 0) - { - opt->is_608_enabled = 1; - opt->extract = 2; - continue; + if (i < argc - 1) + { + i++; + opt->extract = strcmp(argv[i], "both") == 0 ? 12 : atoi_hex(argv[i]); + if (opt->extract != 1 && opt->extract != 2 && opt->extract != 12) + { + fatal(EXIT_MALFORMED_PARAMETER, "--output-field only accepts 1 or 2 or both.\n"); + } + opt->is_608_enabled = 1; + continue; + } + else + { + fatal(EXIT_MALFORMED_PARAMETER, "--output-field has no argument.\n"); + } } - if (strcmp(argv[i], "-cc2") == 0 || strcmp(argv[i], "-CC2") == 0) + + if (strcmp(argv[i], "--cc2") == 0 || strcmp(argv[i], "--CC2") == 0) { opt->cc_channel = 2; continue; } - if (strcmp(argv[i], "-stdout") == 0) + if (strcmp(argv[i], "--stdout") == 0) { - if (opt->messages_target == 1) // Only change this if still stdout. -quiet could set it to 0 for example + if (opt->messages_target == 1) // Only change this if still stdout. --quiet could set it to 0 for example { opt->messages_target = 2; // stderr } opt->cc_to_stdout = 1; continue; } - if (strcmp(argv[i], "-pesheader") == 0) + if (strcmp(argv[i], "--pesheader") == 0) { opt->pes_header_to_stdout = 1; continue; } - if (strcmp(argv[i], "-debugdvbsub") == 0) + if (strcmp(argv[i], "--debugdvbsub") == 0) { opt->debug_mask |= CCX_DMT_DVB; continue; } - if (strcmp(argv[i], "-ignoreptsjumps") == 0) + if (strcmp(argv[i], "--ignoreptsjumps") == 0) { opt->ignore_pts_jumps = 1; continue; } - // -ignoreptsjumps counterpart - if (strcmp(argv[i], "-fixptsjumps") == 0) + // --ignoreptsjumps counterpart + if (strcmp(argv[i], "--fixptsjumps") == 0) { opt->ignore_pts_jumps = 0; continue; } - if (strcmp(argv[i], "-quiet") == 0) + if (strcmp(argv[i], "--quiet") == 0) { opt->messages_target = 0; continue; } - if (strcmp(argv[i], "-debug") == 0) + if (strcmp(argv[i], "--debug") == 0) { opt->debug_mask |= CCX_DMT_VERBOSE; continue; } - if (strcmp(argv[i], "-608") == 0) + if (strcmp(argv[i], "--608") == 0) { opt->debug_mask |= CCX_DMT_DECODER_608; continue; } - if (strcmp(argv[i], "-deblev") == 0) + if (strcmp(argv[i], "--deblev") == 0) { opt->debug_mask |= CCX_DMT_LEVENSHTEIN; continue; } - if (strcmp(argv[i], "-nolevdist") == 0) + if (strcmp(argv[i], "--no-levdist") == 0) { opt->dolevdist = 0; continue; } - if (strcmp(argv[i], "-levdistmincnt") == 0) + if (strcmp(argv[i], "--levdistmincnt") == 0) { if (i < argc - 1) { @@ -2180,10 +2186,10 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-levdistmincnt has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--levdistmincnt has no argument.\n"); } } - if (strcmp(argv[i], "-levdistmaxpct") == 0) + if (strcmp(argv[i], "--levdistmaxpct") == 0) { if (i < argc - 1) { @@ -2193,10 +2199,10 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-levdistmaxpct has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--levdistmaxpct has no argument.\n"); } } - if (strcmp(argv[i], "-708") == 0) + if (strcmp(argv[i], "--708") == 0) { opt->debug_mask |= CCX_DMT_708; #ifndef DISABLE_RUST @@ -2204,133 +2210,133 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) #endif continue; } - if (strcmp(argv[i], "-goppts") == 0) + if (strcmp(argv[i], "--goppts") == 0) { opt->debug_mask |= CCX_DMT_TIME; continue; } - if (strcmp(argv[i], "-vides") == 0) + if (strcmp(argv[i], "--vides") == 0) { opt->debug_mask |= CCX_DMT_VIDES; opt->analyze_video_stream = 1; continue; } - if (strcmp(argv[i], "-anvid") == 0 || strcmp(argv[i], "--analyzevideo") == 0) + if (strcmp(argv[i], "--analyzevideo") == 0) { opt->analyze_video_stream = 1; continue; } - if (strcmp(argv[i], "-xds") == 0) + if (strcmp(argv[i], "--xds") == 0) { - // XDS can be set regardless of -UCLA (isFinal) usage. + // XDS can be set regardless of --UCLA (isFinal) usage. opt->transcript_settings.xds = 1; continue; } - if (strcmp(argv[i], "-xdsdebug") == 0) + if (strcmp(argv[i], "--xdsdebug") == 0) { opt->transcript_settings.xds = 1; opt->debug_mask |= CCX_DMT_DECODER_XDS; continue; } - if (strcmp(argv[i], "-parsedebug") == 0) + if (strcmp(argv[i], "--parsedebug") == 0) { opt->debug_mask |= CCX_DMT_PARSE; continue; } - if (strcmp(argv[i], "-parsePAT") == 0 || strcmp(argv[i], "-parsepat") == 0) + if (strcmp(argv[i], "--parsePAT") == 0 || strcmp(argv[i], "--parsepat") == 0) { opt->debug_mask |= CCX_DMT_PAT; continue; } - if (strcmp(argv[i], "-parsePMT") == 0 || strcmp(argv[i], "-parsepmt") == 0) + if (strcmp(argv[i], "--parsePMT") == 0 || strcmp(argv[i], "--parsepmt") == 0) { opt->debug_mask |= CCX_DMT_PMT; continue; } - if (strcmp(argv[i], "-dumpdef") == 0) + if (strcmp(argv[i], "--dumpdef") == 0) { opt->debug_mask |= CCX_DMT_DUMPDEF; continue; } - if (strcmp(argv[i], "-investigate_packets") == 0) + if (strcmp(argv[i], "--investigate-packets") == 0) { opt->investigate_packets = 1; continue; } - if (strcmp(argv[i], "-cbraw") == 0) + if (strcmp(argv[i], "--cbraw") == 0) { opt->debug_mask |= CCX_DMT_CBRAW; continue; } - if (strcmp(argv[i], "-tverbose") == 0) + if (strcmp(argv[i], "--tverbose") == 0) { opt->debug_mask |= CCX_DMT_TELETEXT; tlt_config.verbose = 1; continue; } #ifdef ENABLE_SHARING - if (strcmp(argv[i], "-sharing-debug") == 0) + if (strcmp(argv[i], "--sharing-debug") == 0) { opt->debug_mask |= CCX_DMT_SHARE; continue; } #endif // ENABLE_SHARING - if (strcmp(argv[i], "-fullbin") == 0) + if (strcmp(argv[i], "--fullbin") == 0) { opt->fullbin = 1; continue; } - if (strcmp(argv[i], "-nosync") == 0) + if (strcmp(argv[i], "--no-sync") == 0) { opt->nosync = 1; continue; } - if (strcmp(argv[i], "-haup") == 0 || strcmp(argv[i], "--hauppauge") == 0) + if (strcmp(argv[i], "--hauppauge") == 0) { opt->hauppauge_mode = 1; continue; } - if (strcmp(argv[i], "-mp4vidtrack") == 0) + if (strcmp(argv[i], "--mp4vidtrack") == 0) { opt->mp4vidtrack = 1; continue; } - if (strstr(argv[i], "-unicode") != NULL) + if (strstr(argv[i], "--unicode") != NULL) { opt->enc_cfg.encoding = CCX_ENC_UNICODE; continue; } - if (strstr(argv[i], "-utf8") != NULL) + if (strstr(argv[i], "--utf8") != NULL) { opt->enc_cfg.encoding = CCX_ENC_UTF_8; continue; } - if (strstr(argv[i], "-latin1") != NULL) + if (strstr(argv[i], "--latin1") != NULL) { opt->enc_cfg.encoding = CCX_ENC_LATIN_1; continue; } - if (strcmp(argv[i], "-poc") == 0 || strcmp(argv[i], "--usepicorder") == 0) + if (strcmp(argv[i], "--usepicorder") == 0) { opt->usepicorder = 1; continue; } - if (strstr(argv[i], "-myth") != NULL) + if (strstr(argv[i], "--myth") != NULL) { opt->auto_myth = 1; continue; } - if (strstr(argv[i], "-nomyth") != NULL) + if (strstr(argv[i], "--no-myth") != NULL) { opt->auto_myth = 0; continue; } - if (strstr(argv[i], "-wtvconvertfix") != NULL) + if (strstr(argv[i], "--wtvconvertfix") != NULL) { opt->wtvconvertfix = 1; continue; } - if (strstr(argv[i], "-wtvmpeg2") != NULL) + if (strstr(argv[i], "--wtvmpeg2") != NULL) { opt->wtvmpeg2 = 1; continue; @@ -2348,7 +2354,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) fatal(EXIT_MALFORMED_PARAMETER, "-o has no argument.\n"); } } - if (strcmp(argv[i], "-svc") == 0 || strcmp(argv[i], "--service") == 0) + if (strcmp(argv[i], "--service") == 0) { if (i < argc - 1) { @@ -2362,7 +2368,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) fatal(EXIT_MALFORMED_PARAMETER, "--service has no argument.\n"); } } - if (strcmp(argv[i], "-datapid") == 0) + if (strcmp(argv[i], "--datapid") == 0) { if (i < argc - 1) { @@ -2373,10 +2379,10 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-datapid has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--datapid has no argument.\n"); } } - if (strcmp(argv[i], "-datastreamtype") == 0) + if (strcmp(argv[i], "--datastreamtype") == 0) { if (i < argc - 1) { @@ -2386,10 +2392,10 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-datastreamtype has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--datastreamtype has no argument.\n"); } } - if (strcmp(argv[i], "-streamtype") == 0) + if (strcmp(argv[i], "--streamtype") == 0) { if (i < argc - 1) { @@ -2399,12 +2405,12 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-streamtype has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--streamtype has no argument.\n"); } } /* Teletext stuff */ - if (strcmp(argv[i], "-tpage") == 0) + if (strcmp(argv[i], "--tpage") == 0) { if (i < argc - 1) { @@ -2415,12 +2421,12 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-tpage has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--tpage has no argument.\n"); } } /* Red Hen/ UCLA Specific stuff */ - if (strcmp(argv[i], "-UCLA") == 0 || strcmp(argv[i], "-ucla") == 0) + if (strcmp(argv[i], "--UCLA") == 0 || strcmp(argv[i], "--ucla") == 0) { opt->ucla = 1; opt->millis_separator = '.'; @@ -2436,37 +2442,37 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } continue; } - if (strcmp(argv[i], "-latrusmap") == 0) + if (strcmp(argv[i], "--latrusmap") == 0) { tlt_config.latrusmap = 1; continue; } - if (strcmp(argv[i], "-tickertext") == 0 || strcmp(argv[i], "-tickertape") == 0) + if (strcmp(argv[i], "--tickertext") == 0 || strcmp(argv[i], "--tickertape") == 0) { opt->tickertext = 1; continue; } - if (strcmp(argv[i], "-lf") == 0 || strcmp(argv[i], "-LF") == 0) + if (strcmp(argv[i], "--lf") == 0 || strcmp(argv[i], "--LF") == 0) { opt->enc_cfg.line_terminator_lf = 1; continue; } - if (strcmp(argv[i], "-df") == 0 || strcmp(argv[i], "-DF") == 0) + if (strcmp(argv[i], "--df") == 0 || strcmp(argv[i], "--DF") == 0) { opt->enc_cfg.force_dropframe = 1; continue; } - if (strcmp(argv[i], "-noautotimeref") == 0) + if (strcmp(argv[i], "--no-autotimeref") == 0) { opt->noautotimeref = 1; continue; } - if (strcmp(argv[i], "-autodash") == 0 || strcmp(argv[i], "-sem") == 0) + if (strcmp(argv[i], "--autodash") == 0 || strcmp(argv[i], "--sem") == 0) { opt->enc_cfg.autodash = 1; continue; } - if (strcmp(argv[i], "-xmltv") == 0) + if (strcmp(argv[i], "--xmltv") == 0) { if (i < argc - 1 && isanumber(argv[i + 1])) { @@ -2480,7 +2486,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) continue; } } - if (strcmp(argv[i], "-xmltvliveinterval") == 0) + if (strcmp(argv[i], "--xmltvliveinterval") == 0) { if (i < argc - 1 && isanumber(argv[i + 1])) { @@ -2494,7 +2500,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) continue; } } - if (strcmp(argv[i], "-xmltvoutputinterval") == 0) + if (strcmp(argv[i], "--xmltvoutputinterval") == 0) { if (i < argc - 1 && isanumber(argv[i + 1])) { @@ -2508,13 +2514,13 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) continue; } } - if (strcmp(argv[i], "-xmltvonlycurrent") == 0) + if (strcmp(argv[i], "--xmltvonlycurrent") == 0) { opt->xmltvonlycurrent = 1; i++; // why do we skip next? continue; } - if (strcmp(argv[i], "-unixts") == 0) + if (strcmp(argv[i], "--unixts") == 0) { if (i < argc - 1) { @@ -2533,31 +2539,31 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-unixts has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--unixts has no argument.\n"); } } - if (strcmp(argv[i], "-sects") == 0) + if (strcmp(argv[i], "--sects") == 0) { opt->date_format = ODF_SECONDS; continue; } - if (strcmp(argv[i], "-datets") == 0) + if (strcmp(argv[i], "--datets") == 0) { opt->date_format = ODF_DATE; continue; } - if (strcmp(argv[i], "-teletext") == 0) + if (strcmp(argv[i], "--teletext") == 0) { opt->demux_cfg.codec = CCX_CODEC_TELETEXT; continue; } - if (strcmp(argv[i], "-noteletext") == 0) + if (strcmp(argv[i], "--no-teletext") == 0) { opt->demux_cfg.nocodec = CCX_CODEC_TELETEXT; continue; } /* Custom transcript */ - if (strcmp(argv[i], "-customtxt") == 0) + if (strcmp(argv[i], "--customtxt") == 0) { if (i < argc - 1) { @@ -2582,7 +2588,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_INCOMPATIBLE_PARAMETERS, "customtxt cannot be set after -UCLA is used!\n"); + fatal(EXIT_INCOMPATIBLE_PARAMETERS, "customtxt cannot be set after --UCLA is used!\n"); } } else @@ -2594,12 +2600,12 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-customtxt has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--customtxt has no argument.\n"); } } /* Network stuff */ - if (strcmp(argv[i], "-udp") == 0) + if (strcmp(argv[i], "--udp") == 0) { if (i < argc - 1) { @@ -2609,7 +2615,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) char *colon = strchr(argv[i], ':'); if (at && !colon) { - fatal(EXIT_MALFORMED_PARAMETER, "If -udp contains an '@', it must also contain a ':'\n"); + fatal(EXIT_MALFORMED_PARAMETER, "If --udp contains an '@', it must also contain a ':'\n"); } else if (at && colon) { @@ -2636,10 +2642,10 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-udp has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--udp has no argument.\n"); } } - if (strcmp(argv[i], "-sendto") == 0) + if (strcmp(argv[i], "--sendto") == 0) { if (i < argc - 1) { @@ -2688,10 +2694,10 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-sendto has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--sendto has no argument.\n"); } } - if (strcmp(argv[i], "-tcp") == 0) + if (strcmp(argv[i], "--tcp") == 0) { if (i < argc - 1) { @@ -2706,10 +2712,10 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-tcp has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--tcp has no argument.\n"); } } - if (strcmp(argv[i], "-tcppassword") == 0) + if (strcmp(argv[i], "--tcp-password") == 0) { if (i < argc - 1) { @@ -2719,10 +2725,10 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-tcppassword has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--tcp-password has no argument.\n"); } } - if (strcmp(argv[i], "-tcpdesc") == 0) + if (strcmp(argv[i], "--tcp-description") == 0) { if (i < argc - 1) { @@ -2732,11 +2738,11 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-tcpdesc has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--tcpdesc has no argument.\n"); } } - if (strcmp(argv[i], "-font") == 0) + if (strcmp(argv[i], "--font") == 0) { if (i < argc - 1) { @@ -2746,10 +2752,10 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-font has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--font has no argument.\n"); } } - if (strcmp(argv[i], "-italics") == 0 && i < argc - 1) + if (strcmp(argv[i], "--italics") == 0 && i < argc - 1) { opt->enc_cfg.render_font_italics = argv[i + 1]; i++; @@ -2757,7 +2763,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } #ifdef WITH_LIBCURL - if (strcmp(argv[i], "-curlposturl") == 0) + if (strcmp(argv[i], "--curlposturl") == 0) { if (i < argc - 1) { @@ -2767,18 +2773,18 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-curlposturl has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--curlposturl has no argument.\n"); } } #endif // WITH_LIBCURL #ifdef ENABLE_SHARING - if (strcmp(argv[i], "-enable-sharing") == 0) + if (strcmp(argv[i], "--enable-sharing") == 0) { opt->sharing_enabled = 1; continue; } - if (strcmp(argv[i], "-sharing-url") == 0) + if (strcmp(argv[i], "--sharing-url") == 0) { if (i < argc - 1) { @@ -2788,10 +2794,10 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-sharing-url has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--sharing-url has no argument.\n"); } } - if (strcmp(argv[i], "-translate") == 0) + if (strcmp(argv[i], "--translate") == 0) { if (i < argc - 1) { @@ -2803,10 +2809,10 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-translate has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--translate has no argument.\n"); } } - if (strcmp(argv[i], "-translate-auth") == 0) + if (strcmp(argv[i], "--translate-auth") == 0) { if (i < argc - 1) { @@ -2816,7 +2822,7 @@ int parse_parameters(struct ccx_s_options *opt, int argc, char *argv[]) } else { - fatal(EXIT_MALFORMED_PARAMETER, "-translate-auth has no argument.\n"); + fatal(EXIT_MALFORMED_PARAMETER, "--translate-auth has no argument.\n"); } } #endif // ENABLE_SHARING diff --git a/src/lib_ccx/stream_functions.c b/src/lib_ccx/stream_functions.c index dd1728d95..14d2372e8 100644 --- a/src/lib_ccx/stream_functions.c +++ b/src/lib_ccx/stream_functions.c @@ -99,7 +99,7 @@ void detect_stream_type(struct ccx_demuxer *ctx) while (idx < ctx->startbytes_avail - 8) { // Check if we have a valid box - if (isValidMP4Box(ctx->startbytes, idx, &nextBoxLocation, &boxScore)) + if (isValidMP4Box(ctx->startbytes, idx, &nextBoxLocation, &boxScore) && nextBoxLocation > idx) { idx = nextBoxLocation; // If the box is valid, a new box should be found on the next location... Not somewhere in between. if (boxScore > 7) diff --git a/src/lib_ccx/utility.c b/src/lib_ccx/utility.c index a950008c1..27c731fb4 100644 --- a/src/lib_ccx/utility.c +++ b/src/lib_ccx/utility.c @@ -84,6 +84,9 @@ static uint32_t crc32_table[] = { int verify_crc32(uint8_t *buf, int len) { +#ifndef DISABLE_RUST + return ccxr_verify_crc32(buf, len); +#endif /* ifndef DISABLE_RUST */ int i = 0; int32_t crc = -1; for (i = 0; i < len; i++) @@ -96,8 +99,8 @@ 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; + unsigned ss = 0, + mm = 0, hh = 0; int value = -1; int colons = 0; const char *c = s; @@ -170,6 +173,9 @@ void timestamp_to_vtttime(uint64_t timestamp, char *buffer) int levenshtein_dist(const uint64_t *s1, const uint64_t *s2, unsigned s1len, unsigned s2len) { +#ifndef DISABLE_RUST + return ccxr_levenshtein_dist(s1, s2, s1len, s2len); +#endif unsigned int x, y, v, lastdiag, olddiag; unsigned int *column = (unsigned *)malloc((s1len + 1) * sizeof(unsigned int)); for (y = 1; y <= s1len; y++) @@ -191,6 +197,9 @@ int levenshtein_dist(const uint64_t *s1, const uint64_t *s2, unsigned s1len, uns int levenshtein_dist_char(const char *s1, const char *s2, unsigned s1len, unsigned s2len) { +#ifndef DISABLE_RUST + return ccxr_levenshtein_dist_char(s1, s2, s1len, s2len); +#endif unsigned int x, y, v, lastdiag, olddiag; unsigned int *column = (unsigned *)malloc((s1len + 1) * sizeof(unsigned int)); for (y = 1; y <= s1len; y++) @@ -213,17 +222,7 @@ 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"); - } + return ccxr_millis_to_date(timestamp, buffer, date_format, millis_separator); #endif time_t secs; @@ -321,7 +320,6 @@ void mprint(const char *fmt, ...) va_list args; if (!ccx_options.messages_target) return; - activity_header(); // Brag about writing it :-) va_start(args, fmt); if (ccx_options.messages_target == CCX_MESSAGES_STDOUT) { diff --git a/src/lib_ccx/utility.h b/src/lib_ccx/utility.h index 11f138b26..6fc6eafa1 100644 --- a/src/lib_ccx/utility.h +++ b/src/lib_ccx/utility.h @@ -26,6 +26,16 @@ struct ccx_rational extern int temp_debug; volatile extern sig_atomic_t change_filename_requested; +#ifndef DISABLE_RUST +extern int ccxr_verify_crc32(uint8_t *buf, int len); +extern int ccxr_levenshtein_dist(const uint64_t *s1, const uint64_t *s2, unsigned s1len, unsigned s2len); +extern int ccxr_levenshtein_dist_char(const char *s1, const char *s2, unsigned s1len, unsigned s2len); +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 + int levenshtein_dist_char (const char *s1, const char *s2, unsigned s1len, unsigned s2len); void init_boundary_time (struct ccx_boundary_time *bt); void print_error (int mode, const char *fmt, ...); diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 30e6139b4..31d27850b 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -11,15 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "approx" version = "0.5.1" @@ -42,7 +33,7 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" @@ -88,7 +79,30 @@ dependencies = [ "rustc-hash", "shlex", "syn 1.0.109", - "which 4.4.0", + "which", +] + +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.71", + "which", ] [[package]] @@ -113,7 +127,7 @@ checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" name = "ccx_rust" version = "0.1.0" dependencies = [ - "bindgen 0.58.1", + "bindgen 0.64.0", "env_logger", "iconv", "leptonica-sys", @@ -139,7 +153,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom 7.1.3", + "nom", ] [[package]] @@ -160,10 +174,16 @@ dependencies = [ ] [[package]] -name = "clap" -version = "2.34.0" +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.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "ansi_term", "atty", @@ -269,6 +289,15 @@ dependencies = [ "libc", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys", +] + [[package]] name = "humantime" version = "2.1.0" @@ -315,7 +344,22 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" @@ -354,14 +398,20 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", - "winapi", + "windows-targets", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "log" version = "0.4.20" @@ -370,9 +420,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" @@ -600,9 +650,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rsmpeg" -version = "0.14.1+ffmpeg.6.0" +version = "0.14.2+ffmpeg.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "055510ebd8693dfe70570352b19a9147314da95e7435477526b976fc83b04455" +checksum = "927012cd6ae43519f519741f4a69602ce3a47cf84750784da124dffd03527cc0" dependencies = [ "libc", "paste", @@ -629,9 +679,31 @@ dependencies = [ name = "rusty_ffmpeg" version = "0.13.1+ffmpeg.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7c726b08ea3ed199e21f6f4b3a1c23fc56a154be731b4445e4ef9ee004cffc" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "bindgen 0.64.0", + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty_ffmpeg" +version = "0.13.3+ffmpeg.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "716adffa5f909c8533611b1dab9ab5666bece35687845865b75ed6a990fc239c" +dependencies = [ + "bindgen 0.69.4", "camino", "libc", "once_cell", @@ -667,9 +739,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "siphasher" @@ -707,9 +779,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] @@ -726,15 +798,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "thiserror" version = "1.0.47" @@ -868,36 +931,16 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "which" -version = "3.1.1" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" -dependencies = [ - "libc", -] - -[[package]] -name = "which" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -918,11 +961,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys", ] [[package]] diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index f736deb3a..93ca96dd4 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -15,14 +15,15 @@ log = "0.4.0" env_logger = "0.8.4" iconv = "0.1.1" 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} +rsmpeg = { version = "0.14.2", 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" +bindgen = "0.64.0" [features] hardsubx_ocr = ["rsmpeg", "tesseract-sys", "leptonica-sys"] - diff --git a/src/rust/build.rs b/src/rust/build.rs index e1636e2be..e4ca427b6 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -62,6 +62,8 @@ fn main() { } let bindings = builder + .derive_default(true) + .no_default("dtvcc_pen_attribs|dtvcc_pen_color|dtvcc_symbol") // Finish the builder and generate the bindings. .generate() // Unwrap the Result and panic on failure. diff --git a/src/rust/lib_ccxr/Cargo.lock b/src/rust/lib_ccxr/Cargo.lock index 6f9180d03..aecb788a5 100644 --- a/src/rust/lib_ccxr/Cargo.lock +++ b/src/rust/lib_ccxr/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "bitflags" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "convert_case" @@ -16,69 +16,31 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", - "syn 1.0.109", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "form_urlencoded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" - -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" -dependencies = [ - "equivalent", - "hashbrown", + "syn", ] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "lib_ccxr" @@ -86,75 +48,36 @@ version = "0.1.0" dependencies = [ "bitflags", "derive_more", - "num_enum", "thiserror", "time", - "url", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "num_enum" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.29", ] [[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "percent-encoding" -version = "2.3.0" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] -name = "proc-macro-crate" -version = "1.3.1" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit", -] +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -170,46 +93,35 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" 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" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn", ] [[package]] name = "syn" -version = "2.0.29" +version = "2.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" dependencies = [ "proc-macro2", "quote", @@ -218,32 +130,34 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn", ] [[package]] name = "time" -version = "0.3.28" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "num-conv", + "powerfmt", "serde", "time-core", "time-macros", @@ -251,88 +165,22 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" - -[[package]] -name = "toml_edit" -version = "0.19.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - [[package]] name = "unicode-ident" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "url" -version = "2.4.0" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "winnow" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" -dependencies = [ - "memchr", -] +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/src/rust/lib_ccxr/Cargo.toml b/src/rust/lib_ccxr/Cargo.toml index 8f46da750..e7a584b84 100644 --- a/src/rust/lib_ccxr/Cargo.toml +++ b/src/rust/lib_ccxr/Cargo.toml @@ -6,15 +6,19 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -num_enum = "0.6.1" -thiserror = "1.0.39" -time = { version = "0.3.27", features = ["macros", "formatting"] } -derive_more = "0.99.17" -bitflags = "2.3.1" -url = "2.4.0" +bitflags = "2.6.0" +thiserror = "1.0.61" +time = { version = "0.3.36", features = ["macros", "formatting"] } +derive_more = "0.99.18" [features] -default = ["enable_sharing", "wtv_debug", "enable_ffmpeg", "debug", "with_libcurl"] +default = [ + "enable_sharing", + "wtv_debug", + "enable_ffmpeg", + "debug", + "with_libcurl", +] enable_sharing = [] wtv_debug = [] enable_ffmpeg = [] diff --git a/src/rust/lib_ccxr/src/common/constants.rs b/src/rust/lib_ccxr/src/common/constants.rs index b310c95b9..5e26143f6 100644 --- a/src/rust/lib_ccxr/src/common/constants.rs +++ b/src/rust/lib_ccxr/src/common/constants.rs @@ -23,6 +23,134 @@ pub enum OutputFormat { Scc, Ccd, } +//! Provides common constant types throughout the codebase. +//! Rust equivalent for `ccx_common_constants.c` file in C. +//! +//! # Conversion Guide +//! +//! | From | To | +//! |--------------------------------|--------------------------------------------| +//! | `ccx_avc_nal_types` | [`AvcNalType`] | +//! | `ccx_stream_type` | [`StreamType`] | +//! | `ccx_mpeg_descriptor` | [`MpegDescriptor`] | +//! | `ccx_datasource` | [`DataSource`] | +//! | `ccx_output_format` | [`OutputFormat`] | +//! | `ccx_stream_mode_enum` | [`StreamMode`] | +//! | `ccx_bufferdata_type` | [`BufferdataType`] | +//! | `ccx_frame_type` | [`FrameType`] | +//! | `ccx_code_type` | [`Codec`] | +//! | `cdp_section_type` | [`CdpSectionType`] | +//! | `cc_types[4]` | [`CCTypes`] | +//! | `CCX_TXT_*` macros | [`CcxTxt`] | +//! | `language[NB_LANGUAGE]` | [`Language`] | +//! | `DEF_VAL_*` macros | [`CreditTiming`] | +//! | `IS_FEASIBLE` macro | [`Codec::is_feasible`] | +//! | `IS_VALID_TELETEXT_DESC` macro | [`MpegDescriptor::is_valid_teletext_desc`] | + +use std::ffi::OsStr; + +// RCWT header (11 bytes): +// byte(s) value description (All values below are hex numbers, not +// actual numbers or values +// 0-2 CCCCED magic number, for Closed Caption CC Extractor Data +// 3 CC Creating program. Legal values: CC = CC Extractor +// 4-5 0050 Program version number +// 6-7 0001 File format version +// 8-10 000000 Padding, required :-) +pub static mut RCWT_HEADER: [u8; 11] = [0xCC, 0xCC, 0xED, 0xCC, 0x00, 0x50, 0, 1, 0, 0, 0]; + +pub const BROADCAST_HEADER: [u8; 4] = [0xff, 0xff, 0xff, 0xff]; +pub const LITTLE_ENDIAN_BOM: [u8; 2] = [0xff, 0xfe]; +pub const UTF8_BOM: [u8; 3] = [0xef, 0xbb, 0xbf]; +pub const DVD_HEADER: [u8; 8] = [0x00, 0x00, 0x01, 0xb2, 0x43, 0x43, 0x01, 0xf8]; +pub const LC1: [u8; 1] = [0x8a]; +pub const LC2: [u8; 1] = [0x8f]; +pub const LC3: [u8; 2] = [0x16, 0xfe]; +pub const LC4: [u8; 2] = [0x1e, 0xfe]; +pub const LC5: [u8; 1] = [0xff]; +pub const LC6: [u8; 1] = [0xfe]; + +pub const FRAMERATES_VALUES: [f64; 16] = [ + 0.0, + 24000.0 / 1001.0, // 23.976 + 24.0, + 25.0, + 30000.0 / 1001.0, // 29.97 + 30.0, + 50.0, + 60000.0 / 1001.0, // 59.94 + 60.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, +]; + +pub const FRAMERATES_TYPES: [&str; 16] = [ + "00 - forbidden", + "01 - 23.976", + "02 - 24", + "03 - 25", + "04 - 29.97", + "05 - 30", + "06 - 50", + "07 - 59.94", + "08 - 60", + "09 - reserved", + "10 - reserved", + "11 - reserved", + "12 - reserved", + "13 - reserved", + "14 - reserved", + "15 - reserved", +]; + +pub const ASPECT_RATIO_TYPES: [&str; 16] = [ + "00 - forbidden", + "01 - 1:1", + "02 - 4:3", + "03 - 16:9", + "04 - 2.21:1", + "05 - reserved", + "06 - reserved", + "07 - reserved", + "08 - reserved", + "09 - reserved", + "10 - reserved", + "11 - reserved", + "12 - reserved", + "13 - reserved", + "14 - reserved", + "15 - reserved", +]; + +pub const PICT_TYPES: [&str; 8] = [ + "00 - illegal (0)", + "01 - I", + "02 - P", + "03 - B", + "04 - illegal (D)", + "05 - illegal (5)", + "06 - illegal (6)", + "07 - illegal (7)", +]; + +pub const SLICE_TYPES: [&str; 10] = [ + "0 - P", "1 - B", "2 - I", "3 - SP", "4 - SI", "5 - P", "6 - B", "7 - I", "8 - SP", "9 - SI", +]; + +pub const ONEPASS: usize = 120; // Bytes we can always look ahead without going out of limits +pub const BUFSIZE: usize = 2048 * 1024 + ONEPASS; // 2 Mb plus the safety pass +pub const MAX_CLOSED_CAPTION_DATA_PER_PICTURE: usize = 32; +pub const EIA_708_BUFFER_LENGTH: usize = 2048; // TODO: Find out what the real limit is +pub const TS_PACKET_PAYLOAD_LENGTH: usize = 184; // From specs +pub const SUBLINESIZE: usize = 2048; // Max. length of a .srt line - TODO: Get rid of this +pub const STARTBYTESLENGTH: usize = 1024 * 1024; +pub const UTF8_MAX_BYTES: usize = 6; +pub const XMLRPC_CHUNK_SIZE: usize = 64 * 1024; // 64 Kb per chunk, to avoid too many realloc() // AVC NAL types pub enum AvcNalType { @@ -104,6 +232,7 @@ pub enum MpegDescriptor { /* User defined */ CaptionService = 0x86, DataComp = 0xfd, + DataComp = 0xfd, // Consider to change DESC to DSC } pub enum DataSource { @@ -113,6 +242,28 @@ pub enum DataSource { Tcp, } +/// 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, +} + pub enum StreamMode { ElementaryOrNotFound = 0, Transport = 1, @@ -124,6 +275,11 @@ pub enum StreamMode { Mp4 = 7, // mp4, iso- #[cfg(feature = "wtv_debug")] HexDump = 8, // hexadecimal dump generated by wtvccdump + 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, @@ -144,6 +300,7 @@ pub enum BufferdataType { DvbSubtitle, IsdbSubtitle, /* BUffer where cc data contain 3 byte cc_valid ccdata 1 ccdata 2 */ + /* Buffer where cc data contain 3 byte cc_valid ccdata 1 ccdata 2 */ RawType, DvdSubtitle, } @@ -158,6 +315,9 @@ pub enum FrameType { } pub enum Codec { +#[derive(PartialEq, Eq)] +pub enum Codec { + Any, Teletext, Dvb, IsdbCc, @@ -181,6 +341,19 @@ pub enum CdpSectionType { Footer = 0x74, } +pub enum CCTypes { + NtscCCF1, + NtscCCF2, + DtvccPacketData, + DtvccPacketStart, +} + +pub enum CcxTxt { + Forbidden = 0, // Ignore teletext packets + AutoNotYetFound = 1, + InUse = 2, // Positive auto-detected, or forced, etc +} + pub enum Language { Und, // Undefined Eng, @@ -283,6 +456,15 @@ pub enum Language { Yid, } +pub enum CreditTiming { + StartCreditsNotBefore, + StartCreditsNotAfter, + StartCreditsForAtLeast, + StartCreditsForAtMost, + EndCreditsForAtLeast, + EndCreditsForAtMost, +} + impl OutputFormat { /// Returns the file extension for the output format if it is a file based format. pub fn file_extension(&self) -> Option<&OsStr> { @@ -308,6 +490,64 @@ impl OutputFormat { } } +impl Codec { + /// Determines whether a specific subtitle codec type should be parsed. + /// + /// # Arguments + /// + /// * `user_selected` - The codec selected by the user to be searched in all elementary streams. + /// * `user_not_selected` - The codec selected by the user not to be parsed. + /// * `feasible` - The codec being tested for feasibility to parse. + /// + /// # Returns + /// + /// Returns `true` if the codec should be parsed, `false` otherwise. + /// + /// # Description + /// + /// This function is used when you want to find out whether you should parse a specific + /// subtitle codec type or not. We ignore the stream if it's not selected, as setting + /// a stream as both selected and not selected doesn't make sense. + pub fn is_feasible(user_selected: &Codec, user_not_selected: &Codec, feasible: &Codec) -> bool { + (*user_selected == Codec::Any && user_not_selected != feasible) || user_selected == feasible + } +} + +impl MpegDescriptor { + pub fn is_valid_teletext_desc(&self) -> bool { + matches!( + self, + MpegDescriptor::VbiDataDescriptor + | MpegDescriptor::VbiTeletextDescriptor + | MpegDescriptor::TeletextDescriptor + ) + } +} + +impl CCTypes { + pub fn to_str(&self) -> &'static str { + match self { + CCTypes::NtscCCF1 => "NTSC line 21 field 1 closed captions", + CCTypes::NtscCCF2 => "NTSC line 21 field 2 closed captions", + CCTypes::DtvccPacketData => "DTVCC Channel Packet Data", + CCTypes::DtvccPacketStart => "DTVCC Channel Packet Start", + } + } +} + +impl CreditTiming { + pub fn value(&self) -> &str { + match self { + CreditTiming::StartCreditsNotBefore => "0", + CreditTiming::StartCreditsNotAfter => "5:00", + CreditTiming::StartCreditsForAtLeast => "2", + CreditTiming::StartCreditsForAtMost => "5", + CreditTiming::EndCreditsForAtLeast => "2", + CreditTiming::EndCreditsForAtMost => "5", + } + } +} + impl Language { pub fn to_str(&self) -> &'static str { match self { diff --git a/src/rust/lib_ccxr/src/lib.rs b/src/rust/lib_ccxr/src/lib.rs index bb3785121..15c1b3e68 100644 --- a/src/rust/lib_ccxr/src/lib.rs +++ b/src/rust/lib_ccxr/src/lib.rs @@ -1,3 +1,4 @@ pub mod common; pub mod hardsubx; +pub mod time; pub mod util; diff --git a/src/rust/lib_ccxr/src/time/mod.rs b/src/rust/lib_ccxr/src/time/mod.rs new file mode 100644 index 000000000..9cd41dac1 --- /dev/null +++ b/src/rust/lib_ccxr/src/time/mod.rs @@ -0,0 +1,23 @@ +//! 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`] | + +pub mod units; diff --git a/src/rust/lib_ccxr/src/time/units.rs b/src/rust/lib_ccxr/src/time/units.rs new file mode 100644 index 000000000..611806722 --- /dev/null +++ b/src/rust/lib_ccxr/src/time/units.rs @@ -0,0 +1,645 @@ +use std::convert::TryInto; +use std::fmt::Write; +use std::num::TryFromIntError; +use std::os::raw::c_int; +use std::time::{SystemTime, UNIX_EPOCH}; + +use derive_more::{Add, Neg, Sub}; +use thiserror::Error; +use time::{ + error::Format, + macros::{datetime, format_description}, + Duration, +}; + +extern "C" { + static mut MPEG_CLOCK_FREQ: c_int; +} + +/// 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 crate::time::units::{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 crate::time::units::{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 crate::time::units::{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 crate::time::units::{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 { + /// Returns the ratio to convert a clock tick to time duration. + pub fn mpeg_clock_freq() -> i64 { + unsafe { MPEG_CLOCK_FREQ.into() } + } + + /// 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/lib_ccxr/src/util/bits.rs b/src/rust/lib_ccxr/src/util/bits.rs new file mode 100644 index 000000000..0127e6a0d --- /dev/null +++ b/src/rust/lib_ccxr/src/util/bits.rs @@ -0,0 +1,224 @@ +//! This module provides various bit-level operations, including parity calculation, +//! bit reversal, and hamming code decoding. +//! +//! - [`get_parity`]: Calculate the parity of an 8-bit value. +//! - [`get_reverse_byte`]: Reverse the bits in an 8-bit value. +//! - [`decode_hamming_8_4`]: Decode a Hamming(8,4) encoded byte. +//! - [`decode_hamming_24_18`]: Decode a Hamming(24,18) encoded value. +//! +//! # Conversion Guide +//! +//! | From | To | +//! |--------------------------------------------|--------------------------------| +//! | `PARITY_8` | [`get_parity`] | +//! | `REVERSE_8` | [`get_reverse_byte`] | +//! | `UNHAM_8_4` const or `unham_8_4` funcn | [`decode_hamming_8_4`] | +//! | `unham_24_18` | [`decode_hamming_24_18`] | +//! | `crc32_table` | [`get_crc32_byte`] | +//! | `verify_crc32` | [`verify_crc32`] | + +/// Equivalent to `PARITY_8[256]` const in `hamming.h` C code. +/// Instead use [`get_parity`] function to get parity bit based on your input. +const PARITY_TABLE: [bool; 256] = [ + false, true, true, false, true, false, false, true, true, false, false, true, false, true, + true, false, true, false, false, true, false, true, true, false, false, true, true, false, + true, false, false, true, true, false, false, true, false, true, true, false, false, true, + true, false, true, false, false, true, false, true, true, false, true, false, false, true, + true, false, false, true, false, true, true, false, true, false, false, true, false, true, + true, false, false, true, true, false, true, false, false, true, false, true, true, false, + true, false, false, true, true, false, false, true, false, true, true, false, false, true, + true, false, true, false, false, true, true, false, false, true, false, true, true, false, + true, false, false, true, false, true, true, false, false, true, true, false, true, false, + false, true, true, false, false, true, false, true, true, false, false, true, true, false, + true, false, false, true, false, true, true, false, true, false, false, true, true, false, + false, true, false, true, true, false, false, true, true, false, true, false, false, true, + true, false, false, true, false, true, true, false, true, false, false, true, false, true, + true, false, false, true, true, false, true, false, false, true, false, true, true, false, + true, false, false, true, true, false, false, true, false, true, true, false, true, false, + false, true, false, true, true, false, false, true, true, false, true, false, false, true, + true, false, false, true, false, true, true, false, false, true, true, false, true, false, + false, true, false, true, true, false, true, false, false, true, true, false, false, true, + false, true, true, false, +]; + +/// Equivalent to `REVERSE_8[256]` const in `hamming.h` C code +/// Instead use [`get_reverse_byte`] function to get any reversed bit based on your input. +const BIT_REVERSE_TABLE: [u8; 256] = [ + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, +]; + +/// Equivalent to `UNHAM_8_4[256]` const in `hamming.h` C code +/// Instead use [`decode_hamming_8_4`] function to get decoded hamming code. +const HAMMING_8_4_DECODER_TABLE: [u8; 256] = [ + 0x01, 0xff, 0x01, 0x01, 0xff, 0x00, 0x01, 0xff, 0xff, 0x02, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x07, + 0xff, 0x00, 0x01, 0xff, 0x00, 0x00, 0xff, 0x00, 0x06, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x03, 0xff, + 0xff, 0x0c, 0x01, 0xff, 0x04, 0xff, 0xff, 0x07, 0x06, 0xff, 0xff, 0x07, 0xff, 0x07, 0x07, 0x07, + 0x06, 0xff, 0xff, 0x05, 0xff, 0x00, 0x0d, 0xff, 0x06, 0x06, 0x06, 0xff, 0x06, 0xff, 0xff, 0x07, + 0xff, 0x02, 0x01, 0xff, 0x04, 0xff, 0xff, 0x09, 0x02, 0x02, 0xff, 0x02, 0xff, 0x02, 0x03, 0xff, + 0x08, 0xff, 0xff, 0x05, 0xff, 0x00, 0x03, 0xff, 0xff, 0x02, 0x03, 0xff, 0x03, 0xff, 0x03, 0x03, + 0x04, 0xff, 0xff, 0x05, 0x04, 0x04, 0x04, 0xff, 0xff, 0x02, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x07, + 0xff, 0x05, 0x05, 0x05, 0x04, 0xff, 0xff, 0x05, 0x06, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x03, 0xff, + 0xff, 0x0c, 0x01, 0xff, 0x0a, 0xff, 0xff, 0x09, 0x0a, 0xff, 0xff, 0x0b, 0x0a, 0x0a, 0x0a, 0xff, + 0x08, 0xff, 0xff, 0x0b, 0xff, 0x00, 0x0d, 0xff, 0xff, 0x0b, 0x0b, 0x0b, 0x0a, 0xff, 0xff, 0x0b, + 0x0c, 0x0c, 0xff, 0x0c, 0xff, 0x0c, 0x0d, 0xff, 0xff, 0x0c, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x07, + 0xff, 0x0c, 0x0d, 0xff, 0x0d, 0xff, 0x0d, 0x0d, 0x06, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x0d, 0xff, + 0x08, 0xff, 0xff, 0x09, 0xff, 0x09, 0x09, 0x09, 0xff, 0x02, 0x0f, 0xff, 0x0a, 0xff, 0xff, 0x09, + 0x08, 0x08, 0x08, 0xff, 0x08, 0xff, 0xff, 0x09, 0x08, 0xff, 0xff, 0x0b, 0xff, 0x0e, 0x03, 0xff, + 0xff, 0x0c, 0x0f, 0xff, 0x04, 0xff, 0xff, 0x09, 0x0f, 0xff, 0x0f, 0x0f, 0xff, 0x0e, 0x0f, 0xff, + 0x08, 0xff, 0xff, 0x05, 0xff, 0x0e, 0x0d, 0xff, 0xff, 0x0e, 0x0f, 0xff, 0x0e, 0x0e, 0xff, 0x0e, +]; + +/// Equivalent to `crc32_table[256]` const in `utility.c` C code +/// Instead use [`get_crc32_byte`] function to get CRC32 bit. +const CRC32_TABLE: [u32; 256] = [ + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4, +]; + +/// Returns the parity of the given 8-bit unsigned integer. +/// +/// # Exmaples +/// ```rust +/// # use lib_ccxr::util::bits::*; +/// assert_eq!(get_parity(0), false); +/// assert_eq!(get_parity(1), true); +/// ``` +pub fn get_parity(value: u8) -> bool { + PARITY_TABLE[value as usize] +} + +/// Returns a byte with its bits flipped from given 8-bit unsigned integer. +/// +/// # Exmaples +/// ```rust +/// # use lib_ccxr::util::bits::*; +/// assert_eq!(get_reverse_byte(0), 0x00); +/// assert_eq!(get_reverse_byte(1), 0x80); +/// ``` +pub fn get_reverse_byte(value: u8) -> u8 { + BIT_REVERSE_TABLE[value as usize] +} + +/// Returns an Option of the decoded byte given a \[8,4\] hamming code byte. +/// (ETS 300 706, chapter 8.2) +/// +/// # Exmaples +/// ```rust +/// # use lib_ccxr::util::bits::*; +/// assert_eq!(decode_hamming_8_4(0x00), Some(0x01)); +/// assert_eq!(decode_hamming_8_4(0x01), None); +/// ``` +pub fn decode_hamming_8_4(value: u8) -> Option { + let decoded = HAMMING_8_4_DECODER_TABLE[value as usize]; + if decoded == 0xff { + None + } else { + Some(decoded & 0x0f) + } +} + +/// Returns an Option of the decoded byte given a \[24,18\] hamming code byte. +/// (ETS 300 706, chapter 8.3) +/// +/// # Exmaples +/// ```rust +/// # use lib_ccxr::util::bits::*; +/// assert_eq!(decode_hamming_24_18(0x00000000), Some(0x00000000)); +/// assert_eq!(decode_hamming_24_18(0x00000001), None); +/// ``` +pub fn decode_hamming_24_18(mut value: u32) -> Option { + let mut test: u8 = 0; + + // Tests A-F correspond to bits 0-6 respectively in 'test'. + for i in 0..23 { + test ^= (((value >> i) & 0x01) as u8) * (i + 33); + } + + // Only parity bit is tested for bit 24 + test ^= (((value >> 23) & 0x01) as u8) * 32u8; + + if (test & 0x1f) != 0x1f { + // Not all tests A-E correct + if (test & 0x20) == 0x20 { + // F correct: Double error + return None; + } + // Test F incorrect: Single error + value ^= 1 << (30 - test); + } + + Some( + (value & 0x000004) >> 2 + | (value & 0x000070) >> 3 + | (value & 0x007f00) >> 4 + | (value & 0x7f0000) >> 5, + ) +} + +/// Returns a crc 32-bit from given 8-bit unsigned integer. +/// +/// # Exmaples +/// ```rust +/// # use lib_ccxr::util::bits::*; +/// assert_eq!(get_crc32_byte(0), 0x00000000); +/// assert_eq!(get_crc32_byte(1), 0x04c11db7); +/// ``` +pub fn get_crc32_byte(value: u8) -> u32 { + CRC32_TABLE[value as usize] +} + +/// Verifies the CRC32-bit value +/// Rust equivalent for `verify_crc32` function in C. Uses Rust-native types as input and output. +pub fn verify_crc32(buf: &[u8]) -> bool { + let mut crc: i32 = -1; + for &byte in buf { + let expr = ((crc >> 24) ^ (byte & 0xff) as i32) & 0xff; + crc = (crc << 8) ^ get_crc32_byte(expr as u8) as i32; + } + crc == 0 +} diff --git a/src/rust/lib_ccxr/src/util/levenshtein.rs b/src/rust/lib_ccxr/src/util/levenshtein.rs new file mode 100644 index 000000000..3b23b6267 --- /dev/null +++ b/src/rust/lib_ccxr/src/util/levenshtein.rs @@ -0,0 +1,39 @@ +//! Provides function for calculating levenshtein distance. + +use std::cmp::min; + +/// Calculates the levenshtein distance between two slices. +/// +/// # Examples +/// ```rust +/// # use lib_ccxr::util::levenshtein::*; +/// assert_eq!(levenshtein(&[1,2,3,4,5], &[1,3,2,4,5,6]), 3); +/// ``` +pub fn levenshtein(a: &[T], b: &[T]) -> usize { + let mut column: Vec = (0..).take(a.len() + 1).collect(); + + for x in 1..=b.len() { + column[0] = x; + let mut lastdiag = x - 1; + for y in 1..=a.len() { + let olddiag = column[y]; + column[y] = min( + min(column[y] + 1, column[y - 1] + 1), + lastdiag + (if a[y - 1] == b[x - 1] { 0 } else { 1 }), + ); + lastdiag = olddiag; + } + } + + column[a.len()] +} + +/// Rust equivalent for `levenshtein_dist` function in C. Uses Rust-native types as input and output. +pub fn levenshtein_dist(s1: &[u64], s2: &[u64]) -> usize { + levenshtein(s1, s2) +} + +/// Rust equivalent for `levenshtein_dist_char` function in C. Uses Rust-native types as input and output. +pub fn levenshtein_dist_char(s1: &[T], s2: &[T]) -> usize { + levenshtein(s1, s2) +} diff --git a/src/rust/lib_ccxr/src/util/log.rs b/src/rust/lib_ccxr/src/util/log.rs index 13c7870c4..d1f167e58 100644 --- a/src/rust/lib_ccxr/src/util/log.rs +++ b/src/rust/lib_ccxr/src/util/log.rs @@ -29,6 +29,7 @@ //! | `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`] | +//! | `ccx_common_logging_gui` | [`GuiXdsMessage`] | //! | `dump` | [`hex_dump`] | //! | `dump` | [`hex_dump_with_start_idx`] | @@ -55,38 +56,38 @@ bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct DebugMessageFlag: u16 { /// Show information related to parsing the container - const PARSE = 0b0000000000000001; + const PARSE = 0x1; /// Show video stream related information - const VIDEO_STREAM = 0b0000000000000010; + const VIDEO_STREAM = 0x2; /// Show GOP and PTS timing information - const TIME = 0b0000000000000100; + const TIME = 0x4; /// Show lots of debugging output - const VERBOSE = 0b0000000000001000; + const VERBOSE = 0x8; /// Show CC-608 decoder debug - const DECODER_608 = 0b0000000000010000; + const DECODER_608 = 0x10; /// Show CC-708 decoder debug - const DECODER_708 = 0b0000000000100000; + const DECODER_708 = 0x20; /// Show XDS decoder debug - const DECODER_XDS = 0b0000000001000000; + const DECODER_XDS = 0x40; /// Show Caption blocks with FTS timing - const CB_RAW = 0b0000000010000000; + const CB_RAW = 0x80; /// Generic, always displayed even if no debug is selected - const GENERIC_NOTICE = 0b0000000100000000; + const GENERIC_NOTICE = 0x100; /// Show teletext debug - const TELETEXT = 0b0000001000000000; + const TELETEXT = 0x200; /// Show Program Allocation Table dump - const PAT = 0b0000010000000000; + const PAT = 0x400; /// Show Program Map Table dump - const PMT = 0b0000100000000000; + const PMT = 0x800; /// Show Levenshtein distance calculations - const LEVENSHTEIN = 0b0001000000000000; + const LEVENSHTEIN = 0x1000; /// Show DVB debug - const DVB = 0b0010000000000000; + const DVB = 0x2000; /// Dump defective TS packets - const DUMP_DEF = 0b0100000000000000; + const DUMP_DEF = 0x4000; /// Extracted captions sharing service #[cfg(feature = "enable_sharing")] - const SHARE = 0b1000000000000000; + const SHARE = 0x8000; } } @@ -176,7 +177,7 @@ impl DebugMessageMask { } /// Check if the current mode is set to Debug Mode. - pub fn debug_mode(&self) -> bool { + pub fn is_debug_mode(&self) -> bool { self.debug_mode } @@ -252,9 +253,9 @@ impl<'a> CCExtractorLogger { /// 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() + /// Similar to [`DebugMessageMask::is_debug_mode`]. + pub fn is_debug_mode(&self) -> bool { + self.debug_mask.is_debug_mode() } /// Returns the currently set target for logging messages. @@ -280,7 +281,8 @@ impl<'a> CCExtractorLogger { /// 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 + info!("Issues? Open a ticket here\n"); + info!("https://github.com/CCExtractor/ccextractor/issues\n"); std::process::exit(exit_cause.exit_code()) } @@ -301,8 +303,11 @@ impl<'a> CCExtractorLogger { /// /// 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)); + if self.target == OutputTarget::Quiet { + return; + } + + self.print(args); } /// Log a debug message. Use [`debug!`] instead. @@ -310,15 +315,36 @@ impl<'a> CCExtractorLogger { /// 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)); + self.print(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!() + pub fn send_gui(&self, message_type: GuiXdsMessage) { + if self.gui_mode { + match message_type { + GuiXdsMessage::ProgramName(program_name) => { + eprintln!("###XDSPROGRAMNAME#{}", program_name) + } + GuiXdsMessage::ProgramIdNr { + minute, + hour, + date, + month, + } => eprintln!( + "###XDSPROGRAMIDENTIFICATIONNUMBER#{}#{}#{}#{}", + minute, hour, date, month + ), + GuiXdsMessage::ProgramDescription { line_num, desc } => { + eprintln!("###XDSPROGRAMDESC#{}#{}", line_num, desc) + } + GuiXdsMessage::CallLetters(current_letters) => { + eprintln!("###XDSNETWORKCALLLETTERS#{}", current_letters) + } + } + } } /// Log a hex dump which is helpful for debugging purposes. @@ -337,34 +363,35 @@ impl<'a> CCExtractorLogger { 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)); - } + if !self.debug_mask.mask().intersects(message_type) { + return; + } + let chunked_data = data.chunks(16); - for _ in 0..(16 - chunk.len()) { - self.print(&format_args!(" ")); - } + 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)); + } - self.print(&format_args!(" | ")); + for _ in 0..(16 - chunk.len()) { + 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!(" | ")); - self.print(&format_args!("{}", c as char)); - } + 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!("\n")); + self.print(&format_args!("{}", c as char)); } + + self.print(&format_args!("\n")); } } } @@ -460,7 +487,7 @@ macro_rules! error { /// # Examples /// ```no_run /// # use lib_ccxr::util::log::*; -/// info!("Processing the header section"); +/// info!("Processing the header section\n"); /// ``` #[macro_export] macro_rules! info { diff --git a/src/rust/lib_ccxr/src/util/mod.rs b/src/rust/lib_ccxr/src/util/mod.rs index ce5fba646..cb62df05e 100644 --- a/src/rust/lib_ccxr/src/util/mod.rs +++ b/src/rust/lib_ccxr/src/util/mod.rs @@ -1,5 +1,20 @@ //! Provides basic utilities used throughout the program. +pub mod bits; pub mod encoding; +pub mod levenshtein; pub mod log; pub mod time; + +use std::os::raw::c_char; + +/// Helper function that converts a Rust-String (`string`) to C-String (`buffer`). +/// +/// # Safety +/// +/// `buffer` must have enough allocated space for `string` to fit. +pub fn write_string_into_pointer(buffer: *mut c_char, string: &str) { + let buffer = unsafe { 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'; +} diff --git a/src/rust/lib_ccxr/src/util/time.rs b/src/rust/lib_ccxr/src/util/time.rs new file mode 100644 index 000000000..990cbf3c0 --- /dev/null +++ b/src/rust/lib_ccxr/src/util/time.rs @@ -0,0 +1,35 @@ +//! Provides Rust equivalent code for functions in `utility.c` involves time operations. Uses Rust-native types as input and output. + +use crate::time::units::{Timestamp, TimestampError, TimestampFormat}; + +/// 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/src/decoder/encoding.rs b/src/rust/src/decoder/encoding.rs new file mode 100644 index 000000000..2671cedd9 --- /dev/null +++ b/src/rust/src/decoder/encoding.rs @@ -0,0 +1,45 @@ +/// 256 BYTES IS ENOUGH FOR ALL THE SUPPORTED CHARACTERS IN +/// EIA-708, SO INTERNALLY WE USE THIS TABLE (FOR CONVENIENCE) +/// +/// 00-1F -> Characters that are in the G2 group in 20-3F, +/// except for 06, which is used for the closed captions +/// sign "CC" which is defined in group G3 as 00. (this +/// is by the article 33). +/// 20-7F -> Group G0 as is - corresponds to the ASCII code +/// 80-9F -> Characters that are in the G2 group in 60-7F +/// (there are several blank characters here, that's OK) +/// A0-FF -> Group G1 as is - non-English characters and symbols + +// NOTE: Same as `lib_ccx/ccx_decoder_708_encoding.c` file + +#[no_mangle] +pub extern "C" fn dtvcc_get_internal_from_G0(g0_char: u8) -> u8 { + g0_char +} + +#[no_mangle] +pub extern "C" fn dtvcc_get_internal_from_G1(g1_char: u8) -> u8 { + g1_char +} + +/// G2: Extended Control Code Set 1 +#[no_mangle] +pub extern "C" fn dtvcc_get_internal_from_G2(g2_char: u8) -> u8 { + match g2_char { + 0x20..=0x3F => g2_char - 0x20, + 0x60..=0x7F => g2_char + 0x20, + _ => 0x20, + } +} + +/// G3: Future Characters and Icon Expansion +#[no_mangle] +pub extern "C" fn dtvcc_get_internal_from_G3(g3_char: u8) -> u8 { + if g3_char == 0xa0 { + // The "CC" (closed captions) sign + 0x06 + } else { + // Rest unmapped, so we return a blank space + 0x20 + } +} diff --git a/src/rust/src/decoder/mod.rs b/src/rust/src/decoder/mod.rs index e13c7ed2d..2ee5c26bc 100644 --- a/src/rust/src/decoder/mod.rs +++ b/src/rust/src/decoder/mod.rs @@ -3,6 +3,7 @@ //! Provides a CEA 708 decoder as defined by ANSI/CTA-708-E R-2018 mod commands; +mod encoding; mod output; mod service_decoder; mod timing; diff --git a/src/rust/src/decoder/output.rs b/src/rust/src/decoder/output.rs index fe13f6ff5..fd0c96dc3 100644 --- a/src/rust/src/decoder/output.rs +++ b/src/rust/src/decoder/output.rs @@ -20,6 +20,7 @@ pub struct Writer<'a> { pub no_font_color: bool, pub transcript_settings: &'a ccx_encoders_transcript_format, pub no_bom: i32, + pub old_cc_time_end: i32, } impl<'a> Writer<'a> { @@ -42,6 +43,7 @@ impl<'a> Writer<'a> { no_font_color: is_true(no_font_color), transcript_settings, no_bom, + old_cc_time_end: 0, } } /// Write subtitles to the file diff --git a/src/rust/src/decoder/timing.rs b/src/rust/src/decoder/timing.rs index e65aad9e2..5702359ef 100644 --- a/src/rust/src/decoder/timing.rs +++ b/src/rust/src/decoder/timing.rs @@ -47,3 +47,31 @@ pub fn get_time_str(time: LLONG) -> String { let ms = time - 1000 * (ss + 60 * (mm + 60 * hh)); format!("{:02}:{:02}:{:02},{:03}", hh, mm, ss, ms) } + +impl ccx_boundary_time { + /// Returns ccx_boundary_time from given time + pub fn get_time(time: LLONG) -> Self { + let hh = time / 1000 / 60 / 60; + let mm = time / 1000 / 60 - 60 * hh; + let ss = time / 1000 - 60 * (mm + 60 * hh); + + Self { + hh: hh as i32, + mm: mm as i32, + ss: ss as i32, + time_in_ms: time, + set: Default::default(), + } + } +} + +/// Returns a hh:mm:ss;frame string of time for SCC format +pub fn get_scc_time_str(time: ccx_boundary_time) -> String { + // Feel sorry for formatting:( + let frame: u8 = (((time.time_in_ms + - 1000 * ((time.ss as i64) + 60 * ((time.mm as i64) + 60 * (time.hh as i64)))) + as f64) + * 29.97 + / 1000.0) as u8; + format!("{:02}:{:02}:{:02};{:02}", time.hh, time.mm, time.ss, frame) +} diff --git a/src/rust/src/decoder/tv_screen.rs b/src/rust/src/decoder/tv_screen.rs index 71f3f2b2f..055f29739 100644 --- a/src/rust/src/decoder/tv_screen.rs +++ b/src/rust/src/decoder/tv_screen.rs @@ -3,6 +3,7 @@ //! TV screen contains the captions to be displayed. //! Captions are added to TV screen from a window when any of DSW, HDW, TGW, DLW or CR commands are received +use std::cmp::Ordering; #[cfg(unix)] use std::os::unix::prelude::IntoRawFd; #[cfg(windows)] @@ -10,7 +11,7 @@ use std::os::windows::io::IntoRawHandle; use std::{ffi::CStr, fs::File}; use super::output::{color_to_hex, write_char, Writer}; -use super::timing::get_time_str; +use super::timing::{get_scc_time_str, get_time_str}; use super::{CCX_DTVCC_SCREENGRID_COLUMNS, CCX_DTVCC_SCREENGRID_ROWS}; use crate::{ bindings::*, @@ -104,7 +105,8 @@ impl dtvcc_tv_screen { /// Returns the bounds in which captions are present pub fn get_write_interval(&self, row_index: usize) -> (usize, usize) { let mut first = 0; - let mut last = CCX_DTVCC_SCREENGRID_COLUMNS as usize - 1; + let mut last = 0; + for col in 0..CCX_DTVCC_SCREENGRID_COLUMNS as usize { if self.chars[row_index][col].is_set() { first = col; @@ -128,6 +130,7 @@ impl dtvcc_tv_screen { ccx_output_format::CCX_OF_SRT => self.write_srt(writer), ccx_output_format::CCX_OF_SAMI => self.write_sami(writer), ccx_output_format::CCX_OF_TRANSCRIPT => self.write_transcript(writer), + ccx_output_format::CCX_OF_SCC => self.write_scc(writer), _ => { self.write_debug(); Err("Unsupported write format".to_owned()) @@ -358,6 +361,149 @@ impl dtvcc_tv_screen { Ok(()) } + fn count_captions_lines_scc(&self) -> usize { + (0..CCX_DTVCC_SCREENGRID_ROWS) + .filter(|&row_index| !self.is_row_empty(row_index as usize)) + .count() + } + + /// Write captions in SCC format + pub fn write_scc(&self, writer: &mut Writer) -> Result<(), String> { + fn adjust_odd_parity(value: u8) -> u8 { + let mut ones = 0; + for i in 0..=7 { + if value & (1 << i) != 0 { + ones += 1; + } + } + if ones % 2 == 0 { + 0b10000000 | value + } else { + value + } + } + // This function is designed to assign appropriate SSC labels for positioning subtitles based on their length. + // In some scenarios where the video stream provides lengthy subtitles that cannot fit within a single line. + // Single-line subtitle can be placed in 15th row(most bottom row) + // 2 line length subtitles can be placed in 14th and 15th row + // 3 line length subtitles can be placed in 13th, 14th and 15th row + fn add_needed_scc_labels( + buf: &mut String, + total_subtitle_count: usize, + current_subtitle_count: usize, + ) { + match total_subtitle_count { + // row 15, column 00 + 1 => buf.push_str(" 94e0 94e0"), + 2 => { + if current_subtitle_count == 1 { + // row 14, column 00 + buf.push_str(" 9440 9440"); + } else { + // row 15, column 00 + buf.push_str(" 94e0 94e0") + } + } + _ => { + if current_subtitle_count == 1 { + // row 13, column 04 + buf.push_str(" 13e0 13e0"); + } else if current_subtitle_count == 2 { + // row 14, column 00 + buf.push_str(" 9440 9440"); + } else { + // row 15, column 00 + buf.push_str(" 94e0 94e0") + } + } + } + } + if self.is_screen_empty(writer) { + return Ok(()); + } + + if self.time_ms_show + writer.subs_delay < 0 { + return Ok(()); + } + + if self.cc_count == 2 { + writer.write_to_file(b"Scenarist_SCC V1.0\n\n")?; + } + + if writer.old_cc_time_end == 0 { + writer.old_cc_time_end = self.time_ms_show as i32; + } + + let mut buf = String::new(); + let mut time_show = ccx_boundary_time::get_time(self.time_ms_show); + let time_end = ccx_boundary_time::get_time(self.time_ms_hide); + + // Caption overlapping situation + match writer.old_cc_time_end.cmp(&(time_show.time_in_ms as i32)) { + Ordering::Greater => { + // Correct the frame delay + time_show.time_in_ms -= 1000 / 29.97 as i64; + buf.push_str(&(get_scc_time_str(time_show) + "\t942c 942c ").to_owned()); + time_show.time_in_ms += 1000 / 29.97 as i64; + // Clear the buffer and start pop on caption + buf.push_str("94ae 94ae 9420 9420"); + } + Ordering::Less => { + // Clear the screen for new caption + let time_to_display = ccx_boundary_time::get_time(writer.old_cc_time_end as i64); + buf.push_str(&(get_scc_time_str(time_to_display) + "\t942c 942c \n\n").to_owned()); + // Correct the frame delay + time_show.time_in_ms -= 1000 / 29.97 as i64; + // Clear the buffer and start pop on caption in new time + buf.push_str(&(get_scc_time_str(time_show) + "\t94ae 94ae 9420 9420").to_owned()); + time_show.time_in_ms += 1000 / 29.97 as i64; + } + Ordering::Equal => { + time_show.time_in_ms -= 1000 / 29.97 as i64; + buf.push_str( + &(get_scc_time_str(time_show) + "\t942c 942c 94ae 94ae 9420 9420").to_owned(), + ); + time_show.time_in_ms += 1000 / 29.97 as i64; + } + } + + let total_subtitle_count = self.count_captions_lines_scc(); + let mut current_subtitle_count = 0; + + for row_index in 0..CCX_DTVCC_SCREENGRID_ROWS as usize { + if !self.is_row_empty(row_index) { + current_subtitle_count += 1; + add_needed_scc_labels(&mut buf, total_subtitle_count, current_subtitle_count); + + let (first, last) = self.get_write_interval(row_index); + debug!("First: {}, Last: {}", first, last); + + let mut bytes_written = 0; + for i in 0..last + 1 { + if bytes_written % 2 == 0 { + buf.push(' '); + } + let adjusted_val = adjust_odd_parity(self.chars[row_index][i].sym as u8); + buf = format!("{}{:x}", buf, adjusted_val); + bytes_written += 1; + } + // add 0x80 padding and form byte pair if the last byte pair is not form + if bytes_written % 2 == 1 { + buf.push_str("80 "); + } else { + buf.push(' '); + } + } + } + + // Display caption (942f 942f) + buf.push_str("942f 942f \n\n"); + writer.write_to_file(buf.as_bytes())?; + + writer.old_cc_time_end = time_end.time_in_ms as i32; + Ok(()) + } + /// Write debug messages /// /// Write all characters,show and hide time as a debug log diff --git a/src/rust/src/decoder/window.rs b/src/rust/src/decoder/window.rs index fbd4d024c..868709193 100644 --- a/src/rust/src/decoder/window.rs +++ b/src/rust/src/decoder/window.rs @@ -441,13 +441,13 @@ impl PenStyle { }; let color = PenColor { - /// White(2,2,2) i.e 10,10,10 i.e 42 + // White(2,2,2) i.e 10,10,10 i.e 42 fg_color: 42, fg_opacity: Opacity::Solid, - /// Either N/A or black, still always 0 + // Either N/A or black, still always 0 bg_color: 0, bg_opacity, - /// Either N/A or black, still always 0 + // Either N/A or black, still always 0 edge_color: 0, }; diff --git a/src/rust/src/libccxr_exports/mod.rs b/src/rust/src/libccxr_exports/mod.rs index cff1990d3..de1e4b43c 100644 --- a/src/rust/src/libccxr_exports/mod.rs +++ b/src/rust/src/libccxr_exports/mod.rs @@ -1,27 +1,45 @@ //! 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; +pub mod time; -mod time; +use crate::ccx_s_options; +use lib_ccxr::util::log::*; +use lib_ccxr::util::{bits::*, levenshtein::*}; -pub use time::*; +use core::panic; +use std::convert::TryInto; +use std::os::raw::{c_char, c_int, c_uint}; /// Initializes the logger at the rust side. /// /// # Safety /// -/// `ccx_options` in C must initialized properly before calling this function. +/// `ccx_options` must not be null and 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(); +pub unsafe extern "C" fn ccxr_init_basic_logger(ccx_options: *const ccx_s_options) { + if ccx_options.is_null() { + panic!("ccx_s_options must not be null"); + } + + let debug_mask = DebugMessageFlag::from_bits( + (*ccx_options) + .debug_mask + .try_into() + .expect("Failed to convert debug_mask to an unsigned integer"), + ) + .expect("Failed to convert debug_mask to a DebugMessageFlag"); + + let debug_mask_on_debug = DebugMessageFlag::from_bits( + (*ccx_options) + .debug_mask_on_debug + .try_into() + .expect("Failed to convert debug_mask_on_debug to an unsigned integer"), + ) + .expect("Failed to convert debug_mask_on_debug to a DebugMessageFlag"); + 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 { + let gui_mode_reports = unsafe { *ccx_options }.gui_mode_reports != 0; + let messages_target = match unsafe { *ccx_options }.messages_target { 0 => OutputTarget::Stdout, 1 => OutputTarget::Stderr, 2 => OutputTarget::Quiet, @@ -32,5 +50,64 @@ pub unsafe extern "C" fn ccxr_init_basic_logger() { mask, gui_mode_reports, )) - .unwrap(); + .expect("Failed to initialize and setup the logger"); +} + +/// Rust equivalent for `verify_crc32` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `buf` should not be a NULL pointer and the length of buffer pointed by `buf` should be equal to +/// or less than `len`. +#[no_mangle] +pub unsafe extern "C" fn ccxr_verify_crc32(buf: *const u8, len: c_int) -> c_int { + let buf = std::slice::from_raw_parts(buf, len as usize); + if verify_crc32(buf) { + 1 + } else { + 0 + } +} + +/// Rust equivalent for `levenshtein_dist` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `s1` and `s2` must valid slices of data with lengths of `s1len` and `s2len` respectively. +#[no_mangle] +pub unsafe extern "C" fn ccxr_levenshtein_dist( + s1: *const u64, + s2: *const u64, + s1len: c_uint, + s2len: c_uint, +) -> c_int { + let s1 = std::slice::from_raw_parts(s1, s1len.try_into().unwrap()); + let s2 = std::slice::from_raw_parts(s2, s2len.try_into().unwrap()); + + let ans = levenshtein_dist(s1, s2); + + ans.try_into() + .expect("Failed to convert the levenshtein distance to C int") +} + +/// Rust equivalent for `levenshtein_dist_char` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// `s1` and `s2` must valid slices of data and therefore not be null. They must have lengths +/// of `s1len` and `s2len` respectively. +#[no_mangle] +pub unsafe extern "C" fn ccxr_levenshtein_dist_char( + s1: *const c_char, + s2: *const c_char, + s1len: c_uint, + s2len: c_uint, +) -> c_int { + let s1 = std::slice::from_raw_parts(s1, s1len.try_into().unwrap()); + let s2 = std::slice::from_raw_parts(s2, s2len.try_into().unwrap()); + + let ans = levenshtein_dist_char(s1, s2); + + ans.try_into() + .expect("Failed to convert the levenshtein distance to C int") } diff --git a/src/rust/src/libccxr_exports/time.rs b/src/rust/src/libccxr_exports/time.rs index 0938e936b..b83e109cc 100644 --- a/src/rust/src/libccxr_exports/time.rs +++ b/src/rust/src/libccxr_exports/time.rs @@ -1,22 +1,10 @@ -#![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'; -} +use lib_ccxr::time::units::*; +use lib_ccxr::util::{time::*, write_string_into_pointer}; /// Rust equivalent for `timestamp_to_srttime` function in C. Uses C-native types as input and /// output. @@ -25,11 +13,11 @@ unsafe fn write_string_into_pointer(buffer: *mut c_char, string: &str) { /// /// `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) { +pub 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); + let _ = timestamp_to_srttime(timestamp, &mut s); write_string_into_pointer(buffer, &s); } @@ -41,11 +29,11 @@ pub unsafe extern "C" fn ccxr_timestamp_to_srttime(timestamp: u64, buffer: *mut /// /// `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) { +pub 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); + let _ = timestamp_to_vtttime(timestamp, &mut s); write_string_into_pointer(buffer, &s); } @@ -56,7 +44,7 @@ pub unsafe extern "C" fn ccxr_timestamp_to_vtttime(timestamp: u64, buffer: *mut /// /// `buffer` must have enough allocated space for the formatted `timestamp` to fit. #[no_mangle] -pub unsafe extern "C" fn ccxr_millis_to_date( +pub extern "C" fn ccxr_millis_to_date( timestamp: u64, buffer: *mut c_char, date_format: ccx_output_date_format, @@ -65,18 +53,19 @@ pub unsafe extern "C" fn ccxr_millis_to_date( 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 { + 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 { + ccx_output_date_format_ODF_DATE => TimestampFormat::Date { millis_separator: millis_separator as u8 as char, }, + _ => TimestampFormat::None, }; - let _ = c::millis_to_date(timestamp, &mut s, date_format); + let _ = millis_to_date(timestamp, &mut s, date_format); write_string_into_pointer(buffer, &s); } @@ -85,12 +74,14 @@ pub unsafe extern "C" fn ccxr_millis_to_date( /// /// # Safety /// -/// `s` must contain valid utf-8 data and have a nul terminator at the end of the string. +/// `s` must contain valid utf-8 data and have a null 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 s = CStr::from_ptr(s) + .to_str() + .expect("Failed to convert buffer `s` into a &str"); - let option_timestamp = c::stringztoms(s); + let option_timestamp = stringztoms(s); if let Some(timestamp) = option_timestamp { if let Ok((h, m, s, _)) = timestamp.as_hms_millis() { @@ -105,3 +96,30 @@ pub unsafe extern "C" fn ccxr_stringztoms(s: *const c_char, bt: *mut ccx_boundar -1 } + +/// Rust equivalent for `millis_to_time` function in C. Uses C-native types as input and output. +/// +/// # Safety +/// +/// Must ensure all passed pointers are valid and non-null. +#[no_mangle] +pub unsafe extern "C" fn ccxr_millis_to_time( + milli: i64, + hours: *mut u32, + minutes: *mut u32, + seconds: *mut u32, + ms: *mut u32, +) { + let ms_value = (milli % 1000) as u32; // milliseconds + let mut remaining = (milli - ms_value as i64) / 1000; // Remainder, in seconds + let seconds_value = (remaining % 60) as u32; + remaining = (remaining - seconds_value as i64) / 60; // Remainder, in minutes + let minutes_value = (remaining % 60) as u32; + remaining = (remaining - minutes_value as i64) / 60; // Remainder, in hours + let hours_value = remaining as u32; + + *hours = hours_value; + *minutes = minutes_value; + *seconds = seconds_value; + *ms = ms_value; +} diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 000000000..6b7a5ab6f --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,2 @@ +ccextractor +vcpkg_installed \ No newline at end of file diff --git a/windows/ccextractor.vcxproj b/windows/ccextractor.vcxproj index d2190f3c2..74f29588f 100644 --- a/windows/ccextractor.vcxproj +++ b/windows/ccextractor.vcxproj @@ -62,10 +62,8 @@ - - + + @@ -188,21 +186,26 @@ - + {0F0063C4-BCBC-4379-A6D5-84A5669C940A} ccextractor Win32Proj - 10.0.19041.0 + 10.0.22621.0 + + + true + x64-windows-static + true Application - v142 + v143 Application - v142 + v143 @@ -278,14 +281,14 @@ xcopy /y "$(ProjectDir)\dll\vcruntime140_1.dll" "$(OutDir)" xcopy /y "C:\Program Files\GPAC\libgpac.dll" "$(OutDir)" xcopy /y "C:\Program Files\GPAC\OpenSVCDecoder.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\postproc-56.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\postproc-57.dll" "$(OutDir)" xcopy /y "C:\Program Files\GPAC\swresample-4.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\avfilter-8.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\swscale-6.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\avdevice-59.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\avcodec-59.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\avformat-59.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\avutil-57.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\avfilter-9.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\swscale-7.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\avdevice-60.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\avcodec-60.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\avformat-60.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\avutil-58.dll" "$(OutDir)" xcopy /y "C:\Program Files\GPAC\libsslMD.dll" "$(OutDir)" xcopy /y "C:\Program Files\GPAC\libcryptoMD.dll" "$(OutDir)" @@ -325,14 +328,14 @@ xcopy /y "$(ProjectDir)\dll\vcruntime140_1.dll" "$(OutDir)" xcopy /y "C:\Program Files\GPAC\libgpac.dll" "$(OutDir)" xcopy /y "C:\Program Files\GPAC\OpenSVCDecoder.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\postproc-56.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\postproc-57.dll" "$(OutDir)" xcopy /y "C:\Program Files\GPAC\swresample-4.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\avfilter-8.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\swscale-6.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\avdevice-59.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\avcodec-59.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\avformat-59.dll" "$(OutDir)" - xcopy /y "C:\Program Files\GPAC\avutil-57.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\avfilter-9.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\swscale-7.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\avdevice-60.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\avcodec-60.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\avformat-60.dll" "$(OutDir)" + xcopy /y "C:\Program Files\GPAC\avutil-58.dll" "$(OutDir)" xcopy /y "C:\Program Files\GPAC\libsslMD.dll" "$(OutDir)" xcopy /y "C:\Program Files\GPAC\libcryptoMD.dll" "$(OutDir)" diff --git a/windows/ccextractor.vcxproj.filters b/windows/ccextractor.vcxproj.filters index 27083f551..8a7029ba0 100644 --- a/windows/ccextractor.vcxproj.filters +++ b/windows/ccextractor.vcxproj.filters @@ -183,108 +183,6 @@ Header Files\lib_ccx\ccx_encoders - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - Header Files @@ -300,39 +198,6 @@ Header Files - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - Header Files @@ -381,603 +246,379 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + - - Source Files - - - Source Files\ccx_decoders - - - Source Files\ccx_decoders - - - Source Files\ccx_decoders - - - Source Files\ccx_decoders - - - Source Files\ccx_decoders - - - Source Files\ccx_decoders - - - Source Files\ccx_common - - - Source Files\ccx_common - - - Source Files\ccx_common - - - Source Files\ccx_common - - - Source Files\ccx_encoders - - - Source Files\ccx_encoders - - - Source Files\ccx_common - Source Files\ccx_decoders - - Source Files - - - Source Files - - - Source Files\ccx_decoders - - - Source Files\ccx_encoders - - - Source Files\ccx_encoders - - - Source Files\ccx_encoders - - - Source Files\ccx_encoders - - - Source Files\ccx_encoders - - - Source Files\ccx_encoders - - - Source Files\ccx_decoders - - - Source Files\ccx_decoders - - - Source Files\ccx_decoders - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\ccx_encoders - - - Source Files\ccx_encoders - - - Source Files\ccx_encoders - - - Source Files\ccx_encoders - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files\lib_ccx - - - Source Files - - - Source Files\ccx_encoders - - - Source Files\ccx_encoders - Source Files\ccx_decoders Source Files\ccx_decoders - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - + Source Files - - Source Files\zlib - - - Source Files - - + Source Files - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - + Source Files - - Header Files\lib_ccx - + \ No newline at end of file diff --git a/windows/vcpkg.json b/windows/vcpkg.json new file mode 100644 index 000000000..caef96365 --- /dev/null +++ b/windows/vcpkg.json @@ -0,0 +1,10 @@ +{ + "name": "ccextractor", + "version": "1.0.0", + "dependencies": [ + "leptonica", + "tesseract", + "ffmpeg" + ], + "builtin-baseline": "fba75d09065fcc76a25dcf386b1d00d33f5175af" +} \ No newline at end of file