3325 Commits

Author SHA1 Message Date
dependabot[bot]
fd3cdc57b1 chore(deps): bump actions/cache from 5 to 6 (#2285)
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 22:31:54 -07:00
Ren yiwei
84bf4cc414 docs: fix typo in tests README (#2281) 2026-07-06 17:48:39 -07:00
Chandragupt Singh
c0ec804823 ci: add manual SHA256 checksum workflow for release artifacts (#2277) 2026-06-12 22:00:31 -07:00
dependabot[bot]
0632bff4e3 chore(deps): bump AButler/upload-release-assets from 3.0 to 4.0 (#2272)
Bumps [AButler/upload-release-assets](https://github.com/abutler/upload-release-assets) from 3.0 to 4.0.
- [Release notes](https://github.com/abutler/upload-release-assets/releases)
- [Commits](https://github.com/abutler/upload-release-assets/compare/v3.0...v4.0)

---
updated-dependencies:
- dependency-name: AButler/upload-release-assets
  dependency-version: '4.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-31 13:00:04 -07:00
Chandragupt Singh
8876d21062 fix(snap): derive snapcraft version from release tag safely (#2276) 2026-05-31 12:15:00 -07:00
Chandragupt Singh
576c727af8 [IMPROVEMENT]: Add macOS binary to GitHub Releases (#2274)
* ci: upload macOS binary to GitHub release on publish

* ci: version macOS release artifact as ccextractor-<version>-macos
2026-05-31 12:14:38 -07:00
Chandragupt Singh
9440749cbe ci: align release.yml trigger with Linux workflows (created → published) (#2275) 2026-05-31 12:14:18 -07:00
dependabot[bot]
2feb09a142 chore(deps): bump rand from 0.8.5 to 0.8.6 in /src/rust (#2271)
Bumps [rand](https://github.com/rust-random/rand) from 0.8.5 to 0.8.6.
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/0.8.6/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.8.5...0.8.6)

---
updated-dependencies:
- dependency-name: rand
  dependency-version: 0.8.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 18:42:54 -07:00
Gaurav karmakar
689b27ce2f fix(608): handle pop-on to roll-up transition ending at EOF (#2268)
When a stream switches from pop-on to roll-up mode and ends before
check_roll_up() reports changes=1 (fewer lines than the roll-up window
requires), the CR handler never runs the backfill at line 836 and
current_visible_start_ms is still zero when flush_608_context fires
EraseDisplayedMemory. Result: the first emitted caption is timestamped
at 00:00:00,000.

Capture the FTS of the first character after the transition in a new
field ts_first_char_rollup_transition. Unlike ts_start_of_current_line,
this field is not overwritten by intermediate changes=0 CRs, so the
first-char time survives to the end-of-stream flush path.
write_cc_buffer uses it to backfill current_visible_start_ms when
rollup_from_popon is still set at emit time, and clears it together
with the flag. The normal popon->roll-up flow (where a scrolling CR
eventually fires) and every other caption path are unchanged.

Verified on c4dd893cb9d6...ts: first subtitle now starts at
00:00:13,913 (was 00:00:00,000). Regression tested against six master
samples the mentor flagged (0069dffd.mpg 03,603; 5cbb21ad.dvr-ms
00,534; 132d7df7.mov 12,812; 6395b281.asf 02,436; 8849331d.mp4 03,771;
b22260d0.ts 04,838) - all match the reference timings unchanged.

Co-authored-by: GAURAV KARMAKAR <gaurav.k@graeon.ai>
2026-04-21 22:53:37 -07:00
Carlos Fernandez Sanz
283bfd9896 feat(mp4): add dvdsub/VobSub support to FFmpeg MP4 demuxer (#2269)
* feat(mp4): add dvdsub/VobSub support to FFmpeg MP4 demuxer

Wire the existing vobsub_decoder (OCR-based bitmap subtitle decoder)
into the FFmpeg MP4 demuxer path. Previously, dvdsub tracks in MP4
containers were documented as unsupported — GPAC could extract them
but the FFmpeg path could not.

Changes:
- Add DvdSub track type detection (AV_CODEC_ID_DVD_SUBTITLE) in mp4.rs
- Add C bridge functions (ccx_mp4_vobsub_init/process/free) that call
  the existing vobsub_decoder module
- Buffer dvdsub packets to compute end times from next-packet PTS
- Add --undefined linker flags for bridge symbols

Tested on sample 1f3e951d516b.mp4 (dvdsub in MP4):
- GPAC: 5405 bytes output
- FFmpeg + this patch: 5405 bytes, byte-identical to GPAC

With this change, the FFmpeg path extracts captions from every sample
that GPAC can, plus one additional sample (ad9f9e03240e.m4v) that
GPAC cannot handle.

* style: apply clang-format to vobsub bridge

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 17:01:32 -07:00
Gaurav karmakar
e4443a74ba [IMPROVEMENT] feat(mp4): add FFmpeg/libavformat backend for MP4 demuxing (#2191)
* ci: add Linux and macOS workflows for FFmpeg MP4 build

Adds a cmake_ffmpeg_mp4 job to both build_linux.yml and build_mac.yml
that configures CCExtractor with -DWITH_FFMPEG=ON -DWITH_OCR=ON
-DWITH_HARDSUBX=ON and builds it, so the FFmpeg-based MP4 demuxer
path is exercised on every PR. Linux job pulls the full set of
ffmpeg/tesseract/leptonica dev packages (including libavdevice-dev);
macOS job installs the corresponding Homebrew bottles.

* build: wire FFmpeg libs into ccx_rust link order

Add the compile-time option to build CCExtractor's MP4 demuxer on top
of libavformat: set -DENABLE_FFMPEG_MP4 and pull libswresample as a
required dependency alongside libavformat/libavutil/libavcodec/
libavfilter/libswscale when -DWITH_FFMPEG=ON.

Link-order handling. Corrosion places ccx_rust at the end of the
ccextractor link line. On Linux (GNU ld), ccx_rust contains rsmpeg
which references FFmpeg symbols like swr_get_out_samples, so the
FFmpeg shared libs must appear after ccx_rust. Collect them into a
separate EXTRA_FFMPEG_LIBS variable and attach them as
INTERFACE_LINK_LIBRARIES on the ccx_rust target so CMake emits them
right after ccx_rust. Make ccx's own link dependencies PRIVATE so
the same libs don't propagate earlier and get deduplicated against
the INTERFACE copy.

Force bridge symbols to be pulled from libccx. GNU ld only pulls
object files from a static archive when they resolve currently-needed
symbols. Bridge functions in libccx.a (ccx_mp4_process_nal_sample,
ccx_mp4_process_cc_packet, etc.) aren't needed until libccx_rust.a
is processed, but libccx.a precedes it on the command line. Use
-Wl,--undefined=<symbol> on ccextractor for each bridge entry
point so the linker pulls them early — same pattern already used
for decode_vbi/do_cb/store_hdcc.

Rust crate wiring. Add the optional rsmpeg dependency guarded behind
the enable_mp4_ffmpeg feature, with platform-specific feature flags
(link_system_ffmpeg on Linux + macOS, link_vcpkg_ffmpeg on Windows,
all using the ffmpeg7 bindings). Extend bindgen's wrapper.h to
expose the bridge headers so Rust can call back into ccx from
mp4_rust_bridge.c.

* feat(mp4): FFmpeg MP4 demuxer with GPAC-parity caption extraction

Alternative MP4 demuxing backend built on rsmpeg (Rust FFmpeg bindings)
that matches GPAC's caption output on every sample reviewed. Activated
via -DWITH_FFMPEG=ON at compile time; default build keeps the GPAC
path untouched.

Architecture
  - src/rust/src/demuxer/mp4.rs: opens the MP4 with rsmpeg, classifies
    tracks (AVC, HEVC, c608, c708, tx3g), and drives packet dispatch.
  - src/rust/src/mp4_ffmpeg_exports.rs: #[no_mangle] entry points
    (ccxr_processmp4, ccxr_dumpchapters) called from ccextractor.c.
  - src/lib_ccx/mp4_rust_bridge.c/.h: thin C shim around do_NAL,
    process608, process_cc_data, ccdp_find_data, store_hdcc, and
    encode_sub so the Rust side can feed decoded payloads into
    CCExtractor's existing CEA-608/708 pipeline.

Caption parity
GPAC-equivalent output on all six samples from the Apr 18 review:

  - 132d7df7e993.mov  108 290 B   byte-identical
  - 1974a299f050.mov  127 828 B   byte-identical
  - 99e5eaafdc55.mov  164 099 B   byte-identical
  - 8849331ddae9.mp4   48 485 B   identical size, content, caption
                                  count; uniform ~2 ms timing shift
  - b2771c84c2a3.mp4    2 607 B   byte-identical
  - 5df914ce773d.mp4    1 164 B   byte-identical

SEI-embedded CEA-608 in H.264 video
The last caption finishing on the final sample was never encoded
because the interleaved av_read_frame loop exited without an
equivalent of GPAC's per-track encode_sub. Drain sub.got_output at
EOF. Intentionally avoid calling process_hdcc there: the last IDR's
slice_header already flushed the HD-CC buffer, and re-running
process_hdcc re-emits partial post-IDR caption state as trailing
garbage.

c608 payload handling
libavformat delivers c608/c708 samples in two shapes:
  1) atom-wrapped raw 608 pairs — [u32 length][4cc cdat|cdt2|ccdp]
     [payload]. Strip the 8-byte header to match GPAC's process_clcp.
  2) bare cc_data triplets — [cc_info][b1][b2]. Detect via len % 3 == 0
     and (payload[0] & 0xF8) == 0xF8. For c608 tracks, extract each
     field-1/field-2 pair from the triplet, set dec_ctx->current_field
     so process608 picks the right decoder context, and call
     process608 directly. Routing through do_cb would hit its
     CCX_H264 guard (set by interleaved H.264 packets) and suppress
     the cb_field increments process608 relies on for
     caption-boundary timing, which merged short captions into their
     successors. For c708, keep ccdp_find_data + process_cc_data.

Bridge design
Single unified entry point ccx_mp4_process_nal_sample(..., int
is_hevc, ...) replaces the separate AVC/HEVC helpers — their NAL
iteration was ~90% identical. Uses utility.h's RB16/RB32 macros
instead of hand-rolled byte-swap helpers. Handles HEVC's
end-of-sample cc_data flush inline.

Supported and unsupported tracks are documented in the mp4.rs
module header. Known limitation: dvdsub/bitmap subtitles in MP4
are not decoded (neither GPAC nor this backend handles them).

* chore(rust): fix clippy 1.95 warnings blocking CI

The format_rust CI job runs `cargo clippy --lib -- -D warnings` and
rust-1.95.0 promoted a handful of lints that hadn't been tripped on
master before this branch went through CI. Address all six:

collapsible_match (4 sites):
  - src/demuxer/demux.rs: fold the nested `if matches!(...)` inside
    the `Some(false) =>` arm into a match guard on the arm itself.
  - src/encoder/common.rs: three header-write arms (Ccd, Scc, Raw)
    each had a nested `if write_raw(...) == -1 { return -1; }`.
    Collapse each into a match guard; the body now just logs and
    returns.

unnecessary_cast (2 sites):
  - src/libccxr_exports/demuxer.rs: drop `as i64` from
    demux_ctx.get_filesize() — it already returns i64.
  - src/parser.rs: drop `as u64` from `t as u64` when writing to
    UTC_REFVALUE — t is already u64.

Also two lints in the new MP4 demuxer file from this branch:
  - if_same_then_else at src/demuxer/mp4.rs:198 — the FOURCC_TX3G
    and AV_CODEC_ID_MOV_TEXT arms both produced TrackType::Tx3g;
    same for FOURCC_C608 and AV_CODEC_ID_EIA_608 → TrackType::Cea608.
    Fold each pair into a single `||` branch.
  - manual_is_multiple_of at src/demuxer/mp4.rs:399 —
    `packet_count % 100 == 0` → `packet_count.is_multiple_of(100)`.

No behavior change. Caption parity against GPAC on all six mentor
samples verified post-fix.

---------

Co-authored-by: GAURAV KARMAKAR <gaurav02081@users.noreply.github.com>
2026-04-19 10:54:45 -07:00
Abir Hassan
c8932dabf7 fix: use c_char instead of i8 for CapInfo.lang to fix ARM compilation (#2267)
When building with Docker on an Apple Silicon Mac (M4), the Rust build
fails with type mismatch errors in common.rs and ctorust.rs.

The lang field in CapInfo is hardcoded as [i8; 4], which only matches the
bindgen-generated c_char on x86 (where c_char = i8). On ARM platforms like
Apple Silicon, c_char = u8, so the types don't match and compilation fails
with:

  error[E0308]: mismatched types - expected `[u8; 4]`, found `[i8; 4]`...

Change to [c_char; 4] which resolves to the correct type on both architectures.
2026-04-18 12:16:46 -07:00
Dhanush
10d9230836 Fix/cea 708 c1 bounds check (#2258)
* fix: move C0 bounds check before match, improve C1 warn message

- C0 handler: bounds check was after the match, meaning process_p16(&block[1..])
  could panic with an index out of bounds before the guard ran. Moved the check
  before the match to prevent this.
- C1 handler: improved the warn message to include command code, name, and
  lengths for easier debugging.

Fixes #1407

* docs: update CHANGES.TXT for #1407 fix

* style: cargo fmt

---------

Co-authored-by: Dhanush Varma <your@email.com>
2026-04-18 11:58:10 -07:00
Chandragupt Singh
a3ac701c1a fix(build): support Git commit hash injection for tarball-based package builds (#2266) 2026-04-18 10:59:54 -07:00
dependabot[bot]
600cc5f96d chore(deps): bump softprops/action-gh-release from 2 to 3 (#2265)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 11:01:53 -07:00
Chandragupt Singh
edee69726a chore: remove redundant Homebrew bump workflow (#2262) 2026-04-12 09:41:02 -07:00
ujjwalr27
ad4886e719 feat: auto-extract multi-language DVB subtitles into per-language files (#447) (#2243)
* feat: auto-extract multi-language DVB subtitles into per-language files

* style: apply clang-format to multi-DVB subtitle extraction changes

* fix: only add lang suffix when 2+ DVB PID

* fix segfault from uninitialized dvb_lang

* docs: add CHANGES.TXT entry for multi-language DVB subtitle extraction

* fix: skip non-DVB encoders in lookup
2026-04-10 22:53:16 -07:00
Carlos Fernandez Sanz
5fdd9b8626 fix(build): decouple WITH_FFMPEG from ENABLE_HARDSUBX, fix FFmpeg 5.0+ API (#2259)
* fix(build): decouple WITH_FFMPEG from ENABLE_HARDSUBX, fix FFmpeg 5.0+ API

Since 2017 (commit 4f5f564b), WITH_FFMPEG=ON automatically defined
ENABLE_HARDSUBX. In 2018 (PR #966), WITH_HARDSUBX was added as a
separate option but the old coupling was never removed. This made
-DWITH_FFMPEG=ON fail to build because it compiled hardsubx code
without linking tesseract/leptonica.

Fixes:
- Remove ENABLE_HARDSUBX from both WITH_FFMPEG blocks (src/CMakeLists.txt
  and src/lib_ccx/CMakeLists.txt). ENABLE_HARDSUBX is now only defined
  by the WITH_HARDSUBX block, where it belongs.
- WITH_HARDSUBX now auto-enables WITH_OCR and WITH_FFMPEG since it
  requires both. Previously -DWITH_HARDSUBX=ON without -DWITH_OCR=ON
  also failed to link.
- Fix const AVCodec* in ffmpeg_intgr.c for FFmpeg 5.0+ API (the
  av_find_best_stream signature changed in libavcodec 59).

Tested all 7 build combinations:
  1. Default                              OK (was OK)
  2. WITH_FFMPEG                          OK (was BROKEN)
  3. WITH_HARDSUBX + WITH_OCR             OK (was OK)
  4. WITH_FFMPEG + WITH_HARDSUBX + OCR    OK (was BROKEN)
  5. WITH_FFMPEG + WITH_OCR               OK (was BROKEN)
  6. WITH_OCR                             OK (was OK)
  7. WITH_HARDSUBX only                   OK (was BROKEN)
2026-04-06 22:16:20 -07:00
Dhanush
395f9b3213 fix: MKV subtitle track .(null) extension for KATE and unknown codec IDs (#2250)
* fix: MKV subtitle track .(null) extension for KATE and unknown codec IDs

The matroska_track_text_subtitle_id_extensions array had 7 entries for
an 8-value enum, leaving MATROSKA_TRACK_SUBTITLE_CODEC_ID_KATE (index 7)
out of bounds. On most platforms this read NULL, which then caused
strlen(NULL) UB and snprintf to emit .(null) in the output filename.

Two fixes:
- Add "kate" at index 7 in the extensions array so KATE tracks
  produce correct .kate output filenames
- Add a NULL guard in generate_filename_from_track() so any future
  unknown codec ID safely falls back to .bin instead of crashing or
  producing .(null)

Fixes #972

* fix: MKV subtitle track .(null) extension for KATE and unknown codec IDs

The matroska_track_text_subtitle_id_extensions array had 7 entries for
an 8-value enum, leaving MATROSKA_TRACK_SUBTITLE_CODEC_ID_KATE (index 7)
out of bounds. On most platforms this read NULL, which then caused
strlen(NULL) UB and snprintf to emit .(null) in the output filename.

Two fixes:
- Add "kate" at index 7 in the extensions array so KATE tracks
  produce correct .kate output filenames
- Add a NULL guard in generate_filename_from_track() so any future
  unknown codec ID safely falls back to .bin instead of crashing or
  producing .(null)

Fixes #972

* fix: MKV subtitle track .(null) extension for KATE and unknown codec IDs

The matroska_track_text_subtitle_id_extensions array had 7 entries for
an 8-value enum, leaving MATROSKA_TRACK_SUBTITLE_CODEC_ID_KATE (index 7)
out of bounds. On most platforms this read NULL, which then caused
strlen(NULL) UB and snprintf to emit .(null) in the output filename.

Two fixes:
- Add "kate" at index 7 in the extensions array so KATE tracks
  produce correct .kate output filenames
- Add a NULL guard in generate_filename_from_track() so any future
  unknown codec ID safely falls back to .bin instead of crashing or
  producing .(null)

Fixes #972

---------

Co-authored-by: Dhanush Varma <your@email.com>
2026-04-04 14:28:48 -07:00
Abhijeet Kumar
65df24e6bc Fix integer overflow in ccxr_process_cc_data(): cast cc_count to usize before multiply (#2241)
cc_count * 3 used i32 arithmetic with no upper-bound check. For cc_count
> i32::MAX / 3 (~715 million), debug builds panic on overflow detection
and release builds silently wrap around to a negative range, discarding
all CC data for the frame. Both are triggerable from malformed media files.

Fix:
1. Cast cc_count to usize immediately after the existing <= 0 guard,
   before any arithmetic — eliminates the overflow entirely
2. Add MAX_CC_COUNT = 31 upper-bound guard — CEA-708/ATSC A/53 encodes
   cc_count in a 5-bit bitstream field (0x1F mask in avc_functions.c:514),
   making 31 the spec-defined per-frame maximum; this value is also
   independently documented in es/userdata.rs ("Maximum cc_count is 31").
   Returns -1 with a warn!() log for out-of-range values, consistent with
   existing error-handling style in the function.
3. Remove the now-redundant `x as usize` cast in the map closure since
   the range is already usize..usize

Fixes #2234
2026-04-03 20:04:24 -07:00
Abhijeet Kumar
92dc785435 Fix panic in process_page() on negative teletext PTS timestamps (#2240)
show_timestamp.to_srt_time().expect() and hide_timestamp.to_srt_time().expect()
in TeletextContext::process_page() panicked for any negative Timestamp value.
Negative timestamps are common in broadcast captures with wrap-around or
uninitialized PTS — crashing after potentially processing an entire file.

to_srt_time() → as_hms_millis() → i64::try_into::<u64>() returns
OutOfRangeError for negative values; .expect() made this fatal.

Fix: process_page() already returns Option<Subtitle>, so replace both
.expect() calls with .ok()? — silently skipping the subtitle when the
timestamp is out of range, matching the function's existing None-on-empty
contract.

Fixes #2233
2026-04-03 19:50:14 -07:00
ahmedbektic
d56a6be9e4 [FIX] clean up rust TODO fix bad AVC SEI payload run (#2235)
* fix avc sei payload

* fix failed formatting test
2026-03-28 13:03:31 -07:00
Rizky Mirzaviandy Priambodo
47ad8388b1 [FIX] Route --hardsubx --tickertext through subtitle encoder (#2230)
* fix(hardsubx): route --tickertext through subtitle encoder (#2229)

* chore(pr): drop changelog entry for bug fix
2026-03-28 13:00:20 -07:00
Abhijeet Kumar
1b4123b302 Fix heap OOB read in switch_to_next_file(): sync inputfile array with num_input_files (#2218)
The Rust FFI function copy_from_rust() computed num_input_files by filtering
empty strings from the inputfiles Vec, but passed an unfiltered clone to
string_to_c_chars() to build the C inputfile[] array. This mismatch made the
C array length and num_input_files disagree: switch_to_next_file() could index
inputfile[current_file] where current_file < num_input_files but >= array size,
reading one slot past the end of the allocated array — confirmed by
AddressSanitizer (heap-buffer-overflow at file_functions.c:183).

The same count/size mismatch also caused free_rust_c_string_array() to
reconstruct the Vec with an incorrect capacity, producing heap corruption on
every clean shutdown.

Fix: filter empty strings into a single Vec<String> first, then derive both
num_input_files (filtered.len()) and the C array (string_to_c_chars(filtered))
from that same source, eliminating the mismatch entirely.

Fixes #2182

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 12:32:41 -07:00
Carlos Fernandez Sanz
c4573bdced style: add missing braces to strdup NULL checks (#2213 follow-up) (#2237) 2026-03-28 12:08:31 -07:00
Abhijeet Kumar
8e4bcfc0cb Fix unchecked strdup() return values in lib_ccx (#2213)
Two related strdup bugs across multiple lib_ccx files:

1. strdup(variable) return not checked for NULL — use after potential
   NULL dereference causes undefined behavior / segfault on OOM.
   Fixed by adding NULL check + fatal(EXIT_NOT_ENOUGH_MEMORY, ...).

2. strdup("literal") in get_buffer_type_str returned directly as
   function result — unchecked and leaks memory on every call since
   the function has no callers that free it.  Fixed by removing strdup
   and returning string literals directly; return type changed from
   char * to const char * (no callers exist, no header declaration).

Files changed:
  src/lib_ccx/ccx_common_common.c
  src/lib_ccx/ccx_encoders_common.c
  src/lib_ccx/ccx_encoders_helpers.c
  src/lib_ccx/configuration.c
  src/lib_ccx/hardsubx.c
  src/lib_ccx/hardsubx_decoder.c
  src/lib_ccx/ocr.c
  src/lib_ccx/output.c
  src/lib_ccx/ts_functions.c

Fixes #2194

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 11:59:35 -07:00
Carlos Fernandez Sanz
159b2193f4 style: add missing braces in general_loop.c (#2210 follow-up) (#2236)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 11:42:02 -07:00
Dhanush
ab109cf316 fix: crash when reading from stdin with no data (#2210)
Two bugs: (1) general_loop.c dereferences NULL dec_ctx in the
live_stream progress code when no data was processed. (2) Rust
switch_to_next_file calls demux_ctx.open() for stdin, triggering
null pointer dereference via ptr::null().

Fix: add NULL check for dec_ctx in general_loop.c, and return
early from switch_to_next_file for stdin/network/tcp sources
instead of calling demux_ctx.open().

Co-authored-by: Dhanush Varma <your@email.com>
2026-03-28 11:40:48 -07:00
Navdeep Kaur
c20b408527 [FIX] Add NULL checks for fopen() and alloc_demuxer_data() in process_hex() (#2202)
* fix(general_loop): Add NULL checks for fopen() and alloc_demuxer_data() in process_hex()

* docs: update changelog for process_hex NULL checks fix

* fix: use EXIT_READ_ERROR for fopen failure and remove CHANGES.TXT entry
2026-03-28 11:25:40 -07:00
dependabot[bot]
74e3842ed0 chore(deps): bump microsoft/setup-msbuild from 2.0.0 to 3.0.0 (#2224)
Bumps [microsoft/setup-msbuild](https://github.com/microsoft/setup-msbuild) from 2.0.0 to 3.0.0.
- [Release notes](https://github.com/microsoft/setup-msbuild/releases)
- [Commits](https://github.com/microsoft/setup-msbuild/compare/v2.0.0...v3.0.0)

---
updated-dependencies:
- dependency-name: microsoft/setup-msbuild
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-24 17:16:41 -07:00
pszemus
03ad9e8e02 [FEATURE] Allow output \0 terminated frames (for WebSocket streaming support) (#2105)
* [feat] Allow output \0 terminated frames

* Fix rust `FromCType`

* use encoded_end_frame for text-based captions

* add changelog entry

* fix CEA-708 Rust decoder

* fix Rust formating

* remove unused `crlf` field - satisfy clippy function argument limit

* silence clippy function argument limit in `Writer`

* Fix writing frame end with multiline captions

* fix formatting errors
2026-03-18 18:16:43 -07:00
Atul Chahar
9f250b144d fix(cea708): use dynamic current_fps instead of hardcoded 29.97 in SCC frame delays (#2173)
Replace all 6 hardcoded 1000/29.97 frame delay calculations in
dtvcc_write_scc() with 1000/current_fps so that CEA-708 SCC output
uses the actual stream framerate instead of assuming NTSC 29.97.

Fixes #2172
2026-03-17 20:20:17 -07:00
Carlos Fernandez Sanz
0b1a967b73 fix: prevent stream detection from corrupting current_file index (#2209)
detect_stream_type() reads up to 1MB (STARTBYTESLENGTH) via
buffered_read_opt() for format detection. For input files smaller
than 1MB, the read hits EOF and—because binary_concat defaults to
enabled—buffered_read_opt() calls switch_to_next_file(). This
increments current_file past the valid range and closes the file
descriptor, leaving format-specific handlers (matroska_loop, MP4,
etc.) to crash when they access inputfile[current_file].

Fix: temporarily disable binary_concat around detect_stream_type()
so that hitting EOF during detection never triggers file switching.

Fixes the root cause of the crash reported in PR #2206 (which
proposed a band-aid of using current_file-1).

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 20:12:20 -07:00
Carlos Fernandez Sanz
52b5385c2a ci: add dual build artifacts for Windows (min-rust vs migrations) (#2208)
Add $(ExtraDefines) to PreprocessorDefinitions in all 4 configurations
of the vcxproj. This allows passing /p:ExtraDefines=DISABLE_RUST from
the MSBuild command line to use C code paths for switchable modules.

The Windows CI now produces two Release artifacts per architecture:
- "CCExtractor Windows x64 Release build" — min Rust (DISABLE_RUST)
- "CCExtractor Windows x64 Release build (with migrations)" — max Rust

The migrations build uses /t:Rebuild to do a clean rebuild without
DISABLE_RUST after the min-rust build completes.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 08:48:02 -07:00
Carlos Fernandez Sanz
92389cff84 ci: add dual build artifacts to compare C vs Rust code paths (#2207)
* fix: flush pending EIT sections in EPG_free() before freeing buffers

* ci: add dual build artifacts to compare C vs Rust code paths

Add -min-rust flag to linux/build that passes -DDISABLE_RUST to gcc,
causing switchable modules (DTVCC, demuxer, AVC, networking, hex utils)
to use their C implementations instead of Rust. The Rust library still
compiles since many modules are Rust-only.

The Linux CI now produces two artifacts:
- "CCExtractor Linux build" — min Rust (C paths where available)
- "CCExtractor Linux build (with migrations)" — max Rust

Both should produce identical output on the sample platform. If they
diverge, it means a Rust port introduced a behavioral difference.

The sample platform will need a corresponding update to recognize and
test the new "with migrations" artifact.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Varadraj75 <agrawalvaradraj2007@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 21:47:59 -07:00
Abhijith
578abcaf3b [FIX] Fix ownership of default Matroska track language (#2193)
* Fix ownership of default Matroska track language

* matroska: restore lang_ietf preference in filename gen and lang matching

Restore the working IETF language preference that was accidentally
removed in the previous commit:

- generate_filename_from_track(): use lang_ietf ? lang_ietf : lang
  for buffer sizing and both snprintf calls
- save_vobsub_track(): same lang_tag pattern for base filename
- matroska_save_all(): check lang_ietf first (BCP-47), fall back to
  lang (ISO-639-2) when --mkvlang filter is active; remove now-unused
  char *match variable

The strdup NULL check and broken switch-case removal from the prior
commit are unchanged.

* matroska: fix clang-format style

* matroska: fix filename underscores, LLD macro, braces, param name
2026-03-15 20:09:52 -07:00
Dhanush
5c87a33a8a fix: add link_directories for tesseract/leptonica on macOS (#2186)
pkg_check_modules provides library names without paths.
Without link_directories, the linker cannot find tesseract
and leptonica on systems where they are not in default
search paths (e.g. Homebrew on macOS arm64).

Co-authored-by: Dhanush Varma <your@email.com>
2026-03-15 11:05:57 -07:00
Carlos Fernandez Sanz
6281a481d0 chore: update PR template to set clearer expectations (#2203)
Require repro instructions and samples, discourage AI-generated PRs
for theoretical issues, clarify changelog and C code policies.
2026-03-14 12:58:41 -07:00
Chandragupt Singh
ed7f544e10 [FEATURE] Add guarded ASS/SSA \pos positioning for CEA-608 captions (#1885)
* feat(ssa): add guarded ASS \pos positioning for CEA-608 captions

* fix(ssa): correct ASS positioning anchor, validate row adjacency, and clean up variable placement

* fix(ssa): adjust top margin to prevent clipping of top-positioned CEA-608 captions

* ssa: map CEA-608 row+col to ASS coords using FFmpeg safe-area formula and fix \an2→\an7 anchor
2026-03-14 10:13:40 -07:00
Apoorv Darshan
538e39db67 Fix null pointer dereference in Matroska parser on file open failure (#2171)
create_file() returns the result of fopen() which can be NULL if the
file cannot be opened. matroska_loop() never checked this, passing
the NULL pointer into matroska_parse() where it is immediately used
in feof(), causing a crash.

Add a NULL check that calls fatal(EXIT_READ_ERROR, ...) on failure,
consistent with other file-open error handling in the codebase.
2026-03-14 10:03:39 -07:00
Arun
af53968611 Fix: Heap OOB buffer over-read in MP4 atom parsing (#2179) (#2180)
* Fix: Heap OOB buffer over-read in MP4 atom parsing (#2179)

* formatting

---------

Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local>
2026-03-12 20:54:28 -07:00
Carlos Fernandez Sanz
0f41b70a6e style: add missing braces to if blocks in lib_ccx.c (#2147 follow-up) (#2197)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-12 18:56:52 -07:00
Atul Chahar
58a8ded621 Fix MSVC cross-CRT invalid free on output_filename (#2147) 2026-03-12 18:54:47 -07:00
Carlos Fernandez Sanz
ee57fb46f3 chore: clean up #2168 merge — drop internal CHANGES.TXT entry, fix whitespace (#2196)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-12 18:49:44 -07:00
Varad Raj Agrawal
dc1d8d9592 fix: move VBI_DEBUG to CMake opt-in, fix MSVC empty struct error (#2168)
- Remove unconditional #define VBI_DEBUG from ccx_decoders_vbi.h
- Add CMake option VBI_DEBUG (OFF by default) in src/CMakeLists.txt
- Use #ifdef VBI_DEBUG / #else for debug_file_name vs reserved member,
  preventing MSVC C2016 empty struct error in non-debug builds
- Add changelog entry in docs/CHANGES.TXT under 0.96.7 unreleased

Fixes #2167
2026-03-12 18:47:43 -07:00
dependabot[bot]
d0c73362ed chore(deps): bump docker/build-push-action from 6 to 7 (#2181)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-08 13:28:04 -07:00
dependabot[bot]
80ed678f98 chore(deps): bump docker/setup-buildx-action from 3 to 4 (#2178)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-07 23:16:15 -08:00
Varad Raj Agrawal
90128d8c28 fix: memory leaks and invalid CSS in WebVTT encoder (#2164)
* fix: memory leaks and invalid CSS in WebVTT encoder

- Remove 6 unnecessary strdup() calls on string literals in
  write_cc_buffer_as_webvtt() — literals are passed directly to
  write_wrapped() which takes void*, no heap allocation needed.
  This runs in a per-character inner loop and leaked on every
  styled subtitle in a broadcast.
- Fix invalid CSS: rgba(0, 256, 0, 0.5) -> rgba(0, 255, 0, 0.5)
  CSS color channels are 0-255; 256 is out of range.
- Fix missing free(unescaped) on write-error path in
  write_stringz_as_webvtt() — matched the existing pattern on
  the adjacent error path which correctly freed both el and unescaped.

Fixes #2154

* fix: move WebVTT changelog entry to unreleased 0.96.7 section
2026-03-07 00:37:12 -08:00
Pranav Sharma
b2c1babf90 Feat(rust): Implement WebVTT-specific timestamp format and layout anchor (#2135)
* Feat(rust): Implement WebVTT-specific timestamp format and layout anchor

* style: apply rustfmt to g608.rs

* fix(rust): use WebVTT-spec dot separator for milliseconds in timestamp line
2026-03-07 00:26:55 -08:00
Anayo Anyafulu
a44db9f617 Port hex_string_to_int from C to Rust and support uppercase hex (#2141) 2026-03-06 23:55:33 -08:00