Commit e9dc8fd4d6 for ffmpeg
commit e9dc8fd4d6c4f46a02fe9d739141efb32596f7fe
Author: Michael Niedermayer <michael@niedermayer.cc>
Date: Sun Sep 20 12:44:33 2026 +0000
avformat/mov: allow explicit zero sample sizes in trun
03d81a044ad587ea83567f75dc36bc3d64278199 rejected every zero-sized
sample to avoid pathological runs of empty samples. This also prevents
opening fragmented recordings with occasional explicit zero sizes.
Allow explicit zero sample sizes, whose count is bounded by the trun
payload checks. Continue rejecting nonempty runs that inherit a zero
default size, but do so before allocating or updating the sample index.
Keep empty samples in the index to preserve sample numbering and timing.
Add a generated FATE test with an empty sample at the end of one fragment
followed by another fragment. No external sample is needed.
Fixes #23807.
Assisted-by: Fairy
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 24b178649a..71c4e38c88 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -6255,6 +6255,11 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom)
entries = avio_rb32(pb);
av_log(c->fc, AV_LOG_TRACE, "flags 0x%x entries %u\n", flags, entries);
+ /* Explicit zero sizes are bounded by the trun payload. Reject a zero
+ * default size before allocating or updating the sample index. */
+ if (entries && !frag->size && !(flags & MOV_TRUN_SAMPLE_SIZE))
+ return AVERROR_INVALIDDATA;
+
if ((uint64_t)entries+sc->tts_count >= UINT_MAX/sizeof(*sc->tts_data))
return AVERROR_INVALIDDATA;
if (flags & MOV_TRUN_DATA_OFFSET) data_offset = avio_rb32(pb);
@@ -6465,8 +6470,6 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom)
distance++;
if (av_sat_add64(dts, sample_duration) != dts + (uint64_t)sample_duration)
return AVERROR_INVALIDDATA;
- if (!sample_size)
- return AVERROR_INVALIDDATA;
dts += sample_duration;
offset += sample_size;
sc->data_size += sample_size;
diff --git a/tests/fate/mov.mak b/tests/fate/mov.mak
index 63a90ac0c3..979d8533e9 100644
--- a/tests/fate/mov.mak
+++ b/tests/fate/mov.mak
@@ -371,7 +371,8 @@ FATE_MOV_FFMPEG_FFPROBE-$(call ALLYES, COLOR_FILTER SETPTS_FILTER MPEG4_ENCODER
FATE_MOV_FFMPEG_FFPROBE-$(call ALLYES, COLOR_FILTER SETPTS_FILTER MPEG4_ENCODER \
MP4_MUXER MOV_DEMUXER FILE_PROTOCOL) \
- += fate-mov-trun-large-sample-duration
+ += fate-mov-trun-large-sample-duration \
+ fate-mov-trun-zero-sample-size
# Generate a fragmented VFR file, then replace the last sample duration in
# its first trun with UINT32_MAX.
@@ -385,6 +386,19 @@ fate-mov-trun-large-sample-duration: CMD = run_with_patched_temp \
-show_entries packet=pts,dts,duration -print_format compact \
-select_streams v -v 0" mp4 923 "\\0377\\0377\\0377\\0377"
+# An explicitly empty sample must not prevent reading the following fragment.
+# Set the last sample size in the first trun to zero; its unreferenced payload
+# remains in mdat. Disable parsing to observe the empty sample and its duration.
+fate-mov-trun-zero-sample-size: CMD = run_with_patched_temp \
+ "$(FFMPEG) -nostdin -v error \
+ -filter_complex color=c=black:s=2x2:r=1,setpts=N*N \
+ -frames:v 10 -fps_mode vfr -c:v mpeg4 -g 5 -bf 0 -q:v 2 -threads 1 \
+ -flags +bitexact -fflags +bitexact \
+ -movflags empty_moov+frag_keyframe+default_base_moof -f mp4 -y" \
+ "ffprobe$(PROGSSUF)$(EXESUF) -fflags +noparse+nofillin -show_packets \
+ -show_entries packet=pts,dts,duration,size,pos -print_format compact \
+ -select_streams v -v 0" mp4 927 "\\0000\\0000\\0000\\0000"
+
# Create VFR B-frames whose presentation durations are not a permutation of
# the STTS sample deltas.
tests/data/mov-vfr-bframes-derived-duration.mov: TAG = GEN
diff --git a/tests/ref/fate/mov-trun-zero-sample-size b/tests/ref/fate/mov-trun-zero-sample-size
new file mode 100644
index 0000000000..7838fa6b93
--- /dev/null
+++ b/tests/ref/fate/mov-trun-zero-sample-size
@@ -0,0 +1,10 @@
+packet|pts=0|dts=0|duration=16384|size=17|pos=939
+packet|pts=16384|dts=16384|duration=49152|size=7|pos=956
+packet|pts=65536|dts=65536|duration=81920|size=7|pos=963
+packet|pts=147456|dts=147456|duration=114688|size=8|pos=970
+packet|pts=262144|dts=262144|duration=147456|size=0|pos=978
+packet|pts=409600|dts=409600|duration=180224|size=17|pos=1138
+packet|pts=589824|dts=589824|duration=212992|size=8|pos=1155
+packet|pts=802816|dts=802816|duration=245760|size=9|pos=1163
+packet|pts=1048576|dts=1048576|duration=278528|size=9|pos=1172
+packet|pts=1327104|dts=1327104|duration=16384|size=9|pos=1181