Skip to content

[FIX] Use packet timestamps for tx3g subtitle times on the FFmpeg MP4 path - #2378

Merged
cfsmp3 merged 1 commit into
CCExtractor:masterfrom
pantha704:fix/mp4-tx3g-subtitle-end-time
Oct 18, 2026
Merged

cfsmp3 merged 1 commit into
CCExtractor:masterfrom
pantha704:fix/mp4-tx3g-subtitle-end-time

Conversation

@pantha704

Copy link
Copy Markdown
Contributor

Fixes #2198.

Problem

On current master (97a0d24) the FFmpeg MP4 demuxer (ENABLE_FFMPEG_MP4 /
cmake -DWITH_FFMPEG=ON) extracts tx3g subtitles with wrong times. A tx3g MP4
with subtitles at 1s-3s and 4s-6s produces:

1
00:00:00,000 --> 00:00:03,999
Hello one

2
00:00:04,000 --> 00:00:09,959
Hello two

The first subtitle starts at zero and ends at the next subtitle's start; the
last subtitle ends at the end of the video. The GPAC path extracts the same
file correctly.

Root cause

ccx_mp4_process_tx3g_packet derived sub->start_time from
dec_ctx->timing->fts_now, and ccx_mp4_flush_tx3g derived sub->end_time
the same way. That timing pointer is shared by every track: when a tx3g packet
comes around it can already point past that subtitle (packets from all tracks
are interleaved), and for the very first subtitle the first set_fts() call
leaves it at zero. Empty lead-in tx3g samples (16-bit length 0) also move the
pointer without producing a subtitle.

Fix

Take the times from the packet timestamps directly:

  • start = packet PTS converted to milliseconds,
  • end = start + sample duration when the sample has one; otherwise the
    previous behaviour applies (end at the next subtitle's start, or the usual
    5 second default for the last sample).

The bridge functions now take/return explicit millisecond values, the same
convention the VobSub bridge (ccx_mp4_vobsub_process) already uses. The
timing pointer is still set from the explicit start, it is just no longer used
to derive the output times.

Verification

Same file after the fix — and diff-identical to the GPAC path output across
subtitle layouts (plain, gapped, overlapping, contiguous) and output formats
(srt, ass, webvtt):

1
00:00:01,000 --> 00:00:02,999
Hello one

2
00:00:04,000 --> 00:00:05,999
Hello two

Sample — small and deterministic; generate with:

printf '1\n00:00:01,000 --> 00:00:03,000\nHello one\n\n2\n00:00:04,000 --> 00:00:06,000\nHello two\n' > test.srt
ffmpeg -y -f lavfi -i color=c=black:s=320x240:r=25 \
       -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 \
       -i test.srt -map 0:v -map 1:a -map 2:s \
       -c:v libx264 -pix_fmt yuv420p -preset ultrafast -c:a aac -c:s mov_text \
       -t 10 out.mp4

Then:

mkdir build && cd build
cmake -DWITH_FFMPEG=ON ../src && make -j$(nproc)
./ccextractor out.mp4 -out=srt -o out && cat out

(The GPAC path and the default build are not touched by this change; happy to
attach the generated 14 KB sample file if that helps.)

… path

The tx3g start/end times came from the global timing pointer, which every
track shares: it can already be past the subtitle being processed, and on
the very first subtitle it is still at zero. Result on a tx3g MP4
with subtitles at 1s-3s and 4s-6s:

  1: 00:00:00,000 --> 00:00:03,999
  2: 00:00:04,000 --> 00:00:09,959

Take the times from the packet timestamps directly instead: the start is
the packet PTS in milliseconds and the end is the sample duration when
present, with the previous behaviour as fallback (next subtitle's start,
or the usual 5 second default for the last sample). The timing pointer is
still set from the explicit start, it is just no longer used to derive
the output times.

Output now matches the GPAC path exactly:

  1: 00:00:01,000 --> 00:00:02,999
  2: 00:00:04,000 --> 00:00:05,999
@pantha704

Copy link
Copy Markdown
Contributor Author

CI note: the two red jobs are infra-only — the Windows build failed in 21s because Chocolatey returned a 503 fetching the gpac package, and the macOS runner is currently broken for multiple branches (the same failures appear on #2376 since yesterday). All Linux builds, cmake_ffmpeg_mp4, build_rust, test_rust, the format checks and the three Docker builds are green.

@cfsmp3
cfsmp3 merged commit a6922e1 into CCExtractor:master Oct 18, 2026
20 of 27 checks passed
cfsmp3 added a commit that referenced this pull request Oct 18, 2026
#2378 derives the end of a tx3g cue as start_ms + duration * 1000 /
timescale. Both terms are truncated separately, so the end can land
1 ms before the GPAC path's, which converts pts + duration at once.
On a real m4v with 797 cues, 99 ended 1 ms early (e.g. timescale
600, pts 135725, duration 2725: 230749 instead of 230750).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] tx3g subtitle timestamps broken — integer overflow on GPAC path, off-by-one on FFmpeg path

2 participants