PR #25365 opened by Forgejo_Fairy
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25365
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25365.patch

Copying artwork into an M4A/M4B can make ten-minute QuickTime chapters 
disappear. With 22050 Hz audio and a cover stream using a 90 kHz time base, 
automatic movie-timescale selection chooses 4410000 ticks per second. Chapter 
durations then exceed the muxer's INT_MAX packet-duration limit and are 
rejected.

Exclude cover images from the common-timescale calculation using the existing 
is_cover_image() predicate. Artwork has no presentation timeline. Timed 
thumbnails still participate, and explicit movie_timescale settings retain 
their behavior.

Add fate-mov-cover-long-chapters using generated short audio/artwork and a 
staged metadata fixture containing two ten-minute chapters. The reference 
checks audio, artwork, the text chapter track and chapter boundaries. Update 
the existing cover-image checksum for the changed movie timing fields; its 
packet checksums and stream information are unchanged.

Fixes #25317. The regression was verified at 51f5f60443ee against its direct 
parent 2153c6795cf9.

Validation on Linux/AArch64:

- The supplied reproduction script fails on master 303510f9d1f and passes all 
five conversions with this patch.
- The new FATE test fails with the source fix removed and passes with it 
applied.
- All 11 tests enabled in the focused build's fate-mov group pass, including 
mov-mp4-chapters and mov-cover-image.
- Release/9.0 reproduces the issue; release/8.1 does not.

This fixes the cover-induced timescale inflation. The existing packet-duration 
limit and chapter-write error handling are outside this patch's scope. No new 
external FATE sample is needed.


>From 4734bb08fc5142d40ec339f96d3835a0216eb68e Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Thu, 22 Oct 2026 17:10:37 +0000
Subject: [PATCH] avformat/movenc: exclude cover images from the movie
 timescale

Cover images are stored as metadata and do not have a presentation
timeline. Including their time base in the common movie timescale can
raise it unnecessarily: 22050 Hz audio and a 90 kHz cover stream select
4410000 ticks per second.

QuickTime chapter packets use the movie timescale. At that rate a
ten-minute chapter exceeds the INT_MAX packet duration limit and is
rejected, leaving the output without its chapter track.

Exclude cover images using the same predicate as the artwork writer,
while retaining timed thumbnail streams and explicit movie_timescale
settings. Add a generated-media FATE regression test checking the audio,
artwork, chapter track and chapter times. Update the existing cover-image
checksum for the changed movie timing fields.

Fixes: 51f5f60443eec9944c9d0cf24e0766edf403d7ef
Fixes: https://code.ffmpeg.org/FFmpeg/FFmpeg/issues/25317
Assisted-by: Fairy
---
 libavformat/movenc.c                   |  4 +++-
 tests/fate/mov.mak                     | 23 +++++++++++++++++++++
 tests/mov-cover-long-chapters.ffmeta   | 11 ++++++++++
 tests/ref/fate/mov-cover-image         |  2 +-
 tests/ref/fate/mov-cover-long-chapters | 28 ++++++++++++++++++++++++++
 5 files changed, 66 insertions(+), 2 deletions(-)
 create mode 100644 tests/mov-cover-long-chapters.ffmeta
 create mode 100644 tests/ref/fate/mov-cover-long-chapters

diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index debe1acb7e0..438dc60b9e5 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -8640,7 +8640,9 @@ static int mov_init(AVFormatContext *s)
             track->par = st->codecpar;
         }
 
-        movie_timescale = av_gcd_q(movie_timescale, st->time_base, INT_MAX, 
(AVRational){1,0});
+        /* Cover images are metadata and have no presentation timeline. */
+        if (!is_cover_image(st))
+            movie_timescale = av_gcd_q(movie_timescale, st->time_base, 
INT_MAX, (AVRational){1,0});
     }
     if (!movie_timescale.den)
         movie_timescale = MOV_TIMESCALE_Q;
diff --git a/tests/fate/mov.mak b/tests/fate/mov.mak
index 9aa879fc390..325e8b479dd 100644
--- a/tests/fate/mov.mak
+++ b/tests/fate/mov.mak
@@ -575,6 +575,29 @@ fate-mov-stereo3d-sbs-mp4: CMD = transcode rawvideo 
$(TARGET_PATH)/tests/data/vs
   "-c:v mpeg4 -qscale:v 10 -stereo3d:v sbsl -strict unofficial -frames:v 2" \
   "-c:v copy" "-show_entries stream_side_data_list" "" "" "-s 352x288 -pix_fmt 
yuv420p"
 
+# Including artwork's 90 kHz time base would make the movie/chapter timescale
+# so large that ten-minute chapter durations no longer fit in INT_MAX.
+FATE_MOV_FFMPEG_FFPROBE-$(call ALLYES, LAVFI_INDEV SINE_FILTER COLOR_FILTER \
+                                      PCM_S16$(if $(filter 
yes,$(HAVE_BIGENDIAN)),BE,LE)_DECODER \
+                                      WRAPPED_AVFRAME_DECODER AAC_ENCODER 
MJPEG_ENCODER \
+                                      ARESAMPLE_FILTER SCALE_FILTER 
FFMETADATA_DEMUXER \
+                                      IPOD_MUXER MOV_DEMUXER FILE_PROTOCOL) \
+                                      += fate-mov-cover-long-chapters
+
+tests/data/mov-cover-long-chapters.ffmeta: TAG = COPY
+tests/data/mov-cover-long-chapters.ffmeta: 
$(SRC_PATH)/tests/mov-cover-long-chapters.ffmeta | tests/data
+       $(M)cp $< $@
+fate-mov-cover-long-chapters: tests/data/mov-cover-long-chapters.ffmeta
+fate-mov-cover-long-chapters: CMD = run_with_temp \
+    "$(FFMPEG) -nostdin -v error \
+    -f lavfi -i sine=r=22050:d=0.2 -f lavfi -i color=c=red:s=16x16:d=0.04 \
+    -i $(TARGET_PATH)/tests/data/mov-cover-long-chapters.ffmeta \
+    -map 0:a -map 1:v -map_chapters 2 -c:a aac -c:v mjpeg \
+    -fps_mode:v passthrough -enc_time_base:v 1/90000 -disposition:v 
attached_pic \
+    -threads 1 -f ipod -y" \
+    "ffprobe$(PROGSSUF)$(EXESUF) -v error \
+    -show_entries 
stream=codec_name,codec_tag_string,time_base:stream_disposition=attached_pic:chapter=start_time,end_time:chapter_tags=title"
 m4b
+
 FATE_FFMPEG += $(FATE_MOV_FFMPEG-yes)
 FATE_FFMPEG_FFPROBE += $(FATE_MOV_FFMPEG_FFPROBE-yes)
 
diff --git a/tests/mov-cover-long-chapters.ffmeta 
b/tests/mov-cover-long-chapters.ffmeta
new file mode 100644
index 00000000000..49e100fdf7e
--- /dev/null
+++ b/tests/mov-cover-long-chapters.ffmeta
@@ -0,0 +1,11 @@
+;FFMETADATA1
+[CHAPTER]
+TIMEBASE=1/1000
+START=0
+END=600000
+title=CH 1
+[CHAPTER]
+TIMEBASE=1/1000
+START=600000
+END=1200000
+title=CH 2
diff --git a/tests/ref/fate/mov-cover-image b/tests/ref/fate/mov-cover-image
index 6c6607c39c1..0811b5594a9 100644
--- a/tests/ref/fate/mov-cover-image
+++ b/tests/ref/fate/mov-cover-image
@@ -1,4 +1,4 @@
-124d92893a4a6dc8e0b5a6c384536e2e *tests/data/fate/mov-cover-image.mp4
+863c657c1ac377cada881745c3220eb8 *tests/data/fate/mov-cover-image.mp4
 1024065 tests/data/fate/mov-cover-image.mp4
 #extradata 0:        2, 0x00340022
 #tb 0: 1/44100
diff --git a/tests/ref/fate/mov-cover-long-chapters 
b/tests/ref/fate/mov-cover-long-chapters
new file mode 100644
index 00000000000..c70b4fd690f
--- /dev/null
+++ b/tests/ref/fate/mov-cover-long-chapters
@@ -0,0 +1,28 @@
+[STREAM]
+codec_name=aac
+codec_tag_string=mp4a
+time_base=1/22050
+DISPOSITION:attached_pic=0
+[/STREAM]
+[STREAM]
+codec_name=bin_data
+codec_tag_string=text
+time_base=1/22050
+DISPOSITION:attached_pic=0
+[/STREAM]
+[STREAM]
+codec_name=mjpeg
+codec_tag_string=[0][0][0][0]
+time_base=1/90000
+DISPOSITION:attached_pic=1
+[/STREAM]
+[CHAPTER]
+start_time=0.000000
+end_time=600.000000
+TAG:title=CH 1
+[/CHAPTER]
+[CHAPTER]
+start_time=600.000000
+end_time=1200.000000
+TAG:title=CH 2
+[/CHAPTER]
-- 
2.54.0

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

Reply via email to