PR #24413 opened by James Almer (jamrial)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24413
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24413.patch

When muxing a synthetic timecode (tmcd) track (e.g. via -timecode or 
-map_metadata 0) for video streams that use very high timescales (such as 
nanosecond VUI timing from GoPro cameras with a timescale of 500,000,000), 
mov_create_timecode_track() blindly copied the video track's timescale to the 
timecode track.
Unlike regular video tracks where each packet spans ~1 frame, a tmcd track 
contains a single sample covering the entire duration of the stream. With a 500 
MHz timescale, duration * timescale exceeds INT_MAX after just ~4.29 seconds 
(2,147,483,647 / 500,000,000 ~= 4.295s). When calculating sample duration in 
get_cluster_duration(), next_dts overflows INT_MAX and triggers 
av_assert0(next_dts <= INT_MAX), aborting container finalization.

derive the timecode (tmcd) track's timescale directly from the timecode rate 
across all files:
- For drop-frame timecode, use fps * 1000 (e.g. 30,000 for 29.97 DF).
- For fractional rates with den == 1001, use tc.rate.num (e.g. 24,000   for 
23.976, 30,000 for 29.97 NDF).
- Otherwise, use tc.fps (e.g. 24, 25, 30, 50, 60).

This eliminates the arbitrary 100,000 timescale threshold and ensures all 
timecode tracks adhere consistently to standard SMPE/QuickTime timecode 
timebases with integer frame durations.

Authored-by: Daniel Drake <[email protected]>


>From 7dcbcacc654d0ddf4f4b09f313f1c9b3c5236932 Mon Sep 17 00:00:00 2001
From: Daniel Drake <[email protected]>
Date: Sun, 6 Sep 2026 20:39:11 +0100
Subject: [PATCH 1/2] avformat/movenc: avoid timescale overflow in synthetic
 tmcd tracks

When muxing a synthetic timecode (tmcd) track (e.g. via -timecode or
-map_metadata 0) for video streams that use very high timescales (such
as nanosecond VUI timing from GoPro cameras with a timescale of
500,000,000), mov_create_timecode_track() blindly copied the video
track's timescale to the timecode track.

Unlike regular video tracks where each packet spans ~1 frame, a tmcd
track contains a single sample covering the entire duration of the
stream. With a 500 MHz timescale, duration * timescale exceeds INT_MAX
after just ~4.29 seconds (2,147,483,647 / 500,000,000 ~= 4.295s).
When calculating sample duration in get_cluster_duration(), next_dts
overflows INT_MAX and triggers av_assert0(next_dts <= INT_MAX),
aborting container finalization.

To maintain backward compatibility with existing muxing behavior and
keep FATE test outputs unchanged, source timescales <= 100,000 are
still copied directly. (At <= 100,000, single-sample durations can
safely span up to ~6 hours without 32-bit overflow, matching the high
timebase warning threshold in mov_init).

When the source timescale exceeds 100,000, derive an appropriate
standard timecode timescale from the timecode rate:
- For drop-frame timecode, use fps * 1000 (e.g. 30,000 for 29.97 DF)
  so frame duration is an integer (1001).
- For fractional rates with den == 1001, use tc.rate.num (e.g. 24,000
  or 30,000) to yield an integer frame duration of 1001.
- For integer rates with den == 1, use tc.rate.num (e.g. 25, 30, 60)
  for a 1:1 timescale with frame duration 1.
- Otherwise fallback to integer fps (or 30).

Signed-off-by: Daniel Drake <[email protected]>
---
 libavformat/movenc.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 4c7868c5f8..7c9e83cc3e 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -7922,7 +7922,16 @@ static int mov_create_timecode_track(AVFormatContext *s, 
int index, int src_inde
         return AVERROR(ENOMEM);
     *track->src_track = src_index;
     track->nb_src_track = 1;
-    track->timescale = mov->tracks[src_index].timescale;
+    if (mov->mode == MODE_ISM || mov->tracks[src_index].timescale <= 100000)
+        track->timescale = mov->tracks[src_index].timescale;
+    else if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME)
+        track->timescale = tc.fps * 1000;
+    else if (tc.rate.den == 1001 && tc.rate.num > 0 && tc.rate.num <= 100000)
+        track->timescale = tc.rate.num;
+    else if (tc.rate.den == 1 && tc.rate.num > 0 && tc.rate.num <= 1000)
+        track->timescale = tc.rate.num;
+    else
+        track->timescale = tc.fps > 0 ? tc.fps : 30;
     if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME)
         track->timecode_flags |= MOV_TIMECODE_FLAG_DROPFRAME;
 
-- 
2.52.0


>From e9fca153b74956dce8de97b875c5114290a12d43 Mon Sep 17 00:00:00 2001
From: Daniel Drake <[email protected]>
Date: Sun, 6 Sep 2026 20:51:01 +0100
Subject: [PATCH 2/2] avformat/movenc: always derive timecode timescale from
 timecode rate

Rather than preserving the heuristic threshold of copying the video
track's timescale for <= 100,000 to maintain backward compatibility,
cleanly derive the timecode (tmcd) track's timescale directly from the
timecode rate across all files:
- For drop-frame timecode, use fps * 1000 (e.g. 30,000 for 29.97 DF).
- For fractional rates with den == 1001, use tc.rate.num (e.g. 24,000
  for 23.976, 30,000 for 29.97 NDF).
- Otherwise, use tc.fps (e.g. 24, 25, 30, 50, 60).

This eliminates the arbitrary 100,000 timescale threshold and ensures
all timecode tracks adhere consistently to standard SMPTE/QuickTime
timecode timebases with integer frame durations.

Update FATE reference checksums for affected MOV, MP4, and ISMV tests.

Signed-off-by: Daniel Drake <[email protected]>
---
 libavformat/movenc.c | 10 +++-------
 tests/ref/lavf/ismv  |  4 ++--
 tests/ref/lavf/mov   |  2 +-
 tests/ref/lavf/mp4   |  2 +-
 4 files changed, 7 insertions(+), 11 deletions(-)

diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 7c9e83cc3e..3e8ccd9272 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -7922,16 +7922,12 @@ static int mov_create_timecode_track(AVFormatContext 
*s, int index, int src_inde
         return AVERROR(ENOMEM);
     *track->src_track = src_index;
     track->nb_src_track = 1;
-    if (mov->mode == MODE_ISM || mov->tracks[src_index].timescale <= 100000)
-        track->timescale = mov->tracks[src_index].timescale;
-    else if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME)
+    if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME)
         track->timescale = tc.fps * 1000;
-    else if (tc.rate.den == 1001 && tc.rate.num > 0 && tc.rate.num <= 100000)
-        track->timescale = tc.rate.num;
-    else if (tc.rate.den == 1 && tc.rate.num > 0 && tc.rate.num <= 1000)
+    else if (tc.rate.den == 1001)
         track->timescale = tc.rate.num;
     else
-        track->timescale = tc.fps > 0 ? tc.fps : 30;
+        track->timescale = tc.fps;
     if (tc.flags & AV_TIMECODE_FLAG_DROPFRAME)
         track->timecode_flags |= MOV_TIMECODE_FLAG_DROPFRAME;
 
diff --git a/tests/ref/lavf/ismv b/tests/ref/lavf/ismv
index 12f5c8c960..86aaa39f1c 100644
--- a/tests/ref/lavf/ismv
+++ b/tests/ref/lavf/ismv
@@ -1,7 +1,7 @@
-48fb8d7a5d19bd60f3a49ccf4b7d6593 *tests/data/lavf/lavf.ismv
+43fac7feb5f11c226e5b409e19c7fdb6 *tests/data/lavf/lavf.ismv
 313169 tests/data/lavf/lavf.ismv
 tests/data/lavf/lavf.ismv CRC=0x9d9a638a
-ba53902fb100d5d1d0603114802df3a5 *tests/data/lavf/lavf.ismv
+6c2b3ec373d68d028b44fbcf0cae58c2 *tests/data/lavf/lavf.ismv
 322247 tests/data/lavf/lavf.ismv
 tests/data/lavf/lavf.ismv CRC=0xc5569484
 3b6023766845b51b075aed474c00f73c *tests/data/lavf/lavf.ismv
diff --git a/tests/ref/lavf/mov b/tests/ref/lavf/mov
index 0ce413a666..1d3c1e1f2f 100644
--- a/tests/ref/lavf/mov
+++ b/tests/ref/lavf/mov
@@ -1,4 +1,4 @@
-bcaddabe56a6517cc3e1130e0e0a31fc *tests/data/lavf/lavf.mov
+a95d0651532051ce24bda4e9c750d1e5 *tests/data/lavf/lavf.mov
 357371 tests/data/lavf/lavf.mov
 tests/data/lavf/lavf.mov CRC=0xbb2b949b
 554d925e6fb9babdaadc474ebe1c5761 *tests/data/lavf/lavf.mov
diff --git a/tests/ref/lavf/mp4 b/tests/ref/lavf/mp4
index 89143af524..ec20ab5d55 100644
--- a/tests/ref/lavf/mp4
+++ b/tests/ref/lavf/mp4
@@ -1,4 +1,4 @@
-a6e44724cab1c4b50c49b0fd227b87d3 *tests/data/lavf/lavf.mp4
+2fa2e05e8685530ca952293542d0c783 *tests/data/lavf/lavf.mp4
 312477 tests/data/lavf/lavf.mp4
 tests/data/lavf/lavf.mp4 CRC=0x9d9a638a
 e2876a5a13c2e3c5570f114ff9ba0d41 *tests/data/lavf/lavf.mp4
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to