From cf9c9fdd91521b2ee230f153c8c0b6361c95420d Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Mon, 30 Mar 2026 20:33:44 +0100
Subject: [PATCH 01/14] avformat/assdec: reject subtitles with negative
 duration

Return error for dialogue lines where end time is before start time,
which would result in negative duration. This prevents undefined
behavior in downstream processing.

Fixes ticket #5291.
---
 libavformat/assdec.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/libavformat/assdec.c b/libavformat/assdec.c
index 3a8353f4d3..61edf927dc 100644
--- a/libavformat/assdec.c
+++ b/libavformat/assdec.c
@@ -72,6 +72,11 @@ static int read_dialogue(ASSContext *ass, AVBPrint *dst, const uint8_t *p,
         *start = (hh1*3600LL + mm1*60LL + ss1) * 100LL + ms1;
         *duration = end - *start;
 
+        if (*duration < 0) {
+            *duration = 0;
+            return -1;
+        }
+
         av_bprint_clear(dst);
         av_bprintf(dst, "%u,%d,%s", ass->readorder++, layer, p + pos);
         if (!av_bprint_is_complete(dst))
-- 
2.39.5 (Apple Git-154)

From 180fe6bfa98fce33650b82556787e434902bbee8 Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Mon, 30 Mar 2026 20:35:17 +0100
Subject: [PATCH 02/14] avformat/movenc: snap near-1:1 SAR to exactly 1:1

Snap sample aspect ratios within 0.1% of 1:1 to exactly 1:1. This
improves compatibility with players like Windows Media Player that
mishandle pasp atoms with very small deviations from square pixels.

Fixes ticket #3674.
---
 libavformat/movenc.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 8157b5e277..971da25629 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -2563,6 +2563,12 @@ static int mov_write_pasp_tag(AVIOContext *pb, MOVTrack *track)
     av_reduce(&sar.num, &sar.den, track->par->sample_aspect_ratio.num,
               track->par->sample_aspect_ratio.den, INT_MAX);
 
+    /* Snap near-1:1 SAR to exactly 1:1 for player compatibility */
+    if (sar.num && sar.den &&
+        sar.num > sar.den * 999 / 1000 && sar.num < sar.den * 1001 / 1000) {
+        sar.num = sar.den = 1;
+    }
+
     avio_wb32(pb, 16);
     ffio_wfourcc(pb, "pasp");
     avio_wb32(pb, sar.num);
-- 
2.39.5 (Apple Git-154)

From a63c197e71b440740704e820b135a6d3955798bc Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Mon, 30 Mar 2026 20:35:26 +0100
Subject: [PATCH 03/14] avformat/tee,fifo: copy programs to output context

Copy AVProgram structures from source to slave contexts with proper
stream index remapping. This preserves multi-program MPEG-TS structure
when using tee or fifo muxers.

Fixes ticket #11501.
---
 libavformat/fifo.c | 22 ++++++++++++++++++++++
 libavformat/tee.c  | 27 +++++++++++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/libavformat/fifo.c b/libavformat/fifo.c
index e66002b902..2bab67fb76 100644
--- a/libavformat/fifo.c
+++ b/libavformat/fifo.c
@@ -533,6 +533,28 @@ static int fifo_mux_init(AVFormatContext *avf, const AVOutputFormat *oformat,
             return AVERROR(ENOMEM);
     }
 
+    /* Copy programs from source to fifo context */
+    for (i = 0; i < avf->nb_programs; ++i) {
+        AVProgram *prog = avf->programs[i];
+        AVProgram *prog2 = av_new_program(avf2, prog->id);
+        if (!prog2)
+            return AVERROR(ENOMEM);
+        ret = av_dict_copy(&prog2->metadata, prog->metadata, 0);
+        if (ret < 0)
+            return ret;
+        prog2->program_num = prog->program_num;
+        prog2->pmt_pid     = prog->pmt_pid;
+        prog2->pcr_pid     = prog->pcr_pid;
+        prog2->pmt_version = prog->pmt_version;
+
+        /* Copy stream indexes (1:1 mapping since all streams are cloned) */
+        for (unsigned j = 0; j < prog->nb_stream_indexes; j++) {
+            unsigned idx = prog->stream_index[j];
+            if (idx < avf->nb_streams)
+                av_program_add_stream_index(avf2, prog->id, idx);
+        }
+    }
+
     return 0;
 }
 
diff --git a/libavformat/tee.c b/libavformat/tee.c
index 3a2ee92c3c..233bf45cc8 100644
--- a/libavformat/tee.c
+++ b/libavformat/tee.c
@@ -294,6 +294,33 @@ static int open_slave(AVFormatContext *avf, char *slave, TeeSlave *tee_slave)
         }
     }
 
+    /* Copy programs from source to slave context */
+    for (unsigned i = 0; i < avf->nb_programs; i++) {
+        AVProgram *prog = avf->programs[i];
+        AVProgram *prog2 = av_new_program(avf2, prog->id);
+        if (!prog2) {
+            ret = AVERROR(ENOMEM);
+            goto end;
+        }
+        ret = av_dict_copy(&prog2->metadata, prog->metadata, 0);
+        if (ret < 0)
+            goto end;
+        prog2->program_num = prog->program_num;
+        prog2->pmt_pid     = prog->pmt_pid;
+        prog2->pcr_pid     = prog->pcr_pid;
+        prog2->pmt_version = prog->pmt_version;
+
+        /* Map stream indexes using the stream_map */
+        for (unsigned j = 0; j < prog->nb_stream_indexes; j++) {
+            unsigned src_idx = prog->stream_index[j];
+            if (src_idx < avf->nb_streams) {
+                int mapped_idx = tee_slave->stream_map[src_idx];
+                if (mapped_idx >= 0)
+                    av_program_add_stream_index(avf2, prog->id, (unsigned)mapped_idx);
+            }
+        }
+    }
+
     ret = ff_format_output_open(avf2, filename, &options);
     if (ret < 0) {
         av_log(avf, AV_LOG_ERROR, "Slave '%s': error opening: %s\n", slave,
-- 
2.39.5 (Apple Git-154)

From 20cc97983744003cb86ec6424eae71409d74bca5 Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Mon, 30 Mar 2026 20:36:04 +0100
Subject: [PATCH 04/14] avformat/mpegts: detect keyframes in video packets

Parse H.264/HEVC/MPEG-1/2 start codes to properly set AV_PKT_FLAG_KEY
on video packets. This enables better seeking behavior and accurate
stream analysis.

Supported keyframe detection:
- H.264: NAL type 5 (IDR slice)
- HEVC: NAL types 16-21 (BLA/IDR/CRA)
- MPEG-1/2: Picture type 1 (I-frame)

Fixes ticket #3690.
---
 libavformat/mpegts.c | 49 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c
index 8eb3bae720..b114f16007 100644
--- a/libavformat/mpegts.c
+++ b/libavformat/mpegts.c
@@ -1019,6 +1019,50 @@ static void reset_pes_packet_state(PESContext *pes)
     av_buffer_unref(&pes->buffer);
 }
 
+/* Detect if video packet contains a keyframe by parsing start codes */
+static int detect_keyframe(const uint8_t *data, int size, enum AVCodecID codec_id)
+{
+    const uint8_t *p = data;
+    const uint8_t *end = data + size - 4;
+    int nal_type;
+
+    /* Only supported codecs */
+    if (codec_id != AV_CODEC_ID_H264 &&
+        codec_id != AV_CODEC_ID_HEVC &&
+        codec_id != AV_CODEC_ID_MPEG1VIDEO &&
+        codec_id != AV_CODEC_ID_MPEG2VIDEO)
+        return 0;
+
+    while (p < end) {
+        if (p[0] == 0 && p[1] == 0 && p[2] == 1) {
+            switch (codec_id) {
+            case AV_CODEC_ID_H264:
+                /* NAL type 5 = IDR slice */
+                if ((p[3] & 0x1F) == 5)
+                    return 1;
+                break;
+            case AV_CODEC_ID_HEVC:
+                /* NAL types 16-21 = BLA/IDR/CRA */
+                nal_type = (p[3] >> 1) & 0x3F;
+                if (nal_type >= 16 && nal_type <= 21)
+                    return 1;
+                break;
+            case AV_CODEC_ID_MPEG2VIDEO:
+            case AV_CODEC_ID_MPEG1VIDEO:
+                /* Picture start code 0x00, pic type in bits 3-5: 1=I */
+                if (p[3] == 0x00 && p + 5 < data + size &&
+                    ((p[5] >> 3) & 0x07) == 1)
+                    return 1;
+                break;
+            }
+            p += 4;
+        } else {
+            p++;
+        }
+    }
+    return 0;
+}
+
 static void new_data_packet(const uint8_t *buffer, int len, AVPacket *pkt)
 {
     av_packet_unref(pkt);
@@ -1072,6 +1116,11 @@ static int new_pes_packet(PESContext *pes, AVPacket *pkt)
     pkt->pos   = pes->ts_packet_pos;
     pkt->flags = pes->flags;
 
+    /* Detect keyframes in video streams for better seeking */
+    if (pes->st && pes->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
+        detect_keyframe(pkt->data, pkt->size, pes->st->codecpar->codec_id))
+        pkt->flags |= AV_PKT_FLAG_KEY;
+
     pes->buffer = NULL;
     reset_pes_packet_state(pes);
 
-- 
2.39.5 (Apple Git-154)

From 7b11c9dcbed8e48dcfe7af9936fa67c6075f3fca Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Mon, 30 Mar 2026 20:36:26 +0100
Subject: [PATCH 05/14] avformat/concatdec: add probe_duration option

Add option to probe each file's duration during header read when
explicit durations are not provided in the concat script. This enables
accurate total duration reporting.

Usage: -f concat -probe_duration 1 -i list.txt

Fixes ticket #5672.
---
 libavformat/concatdec.c | 41 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 40 insertions(+), 1 deletion(-)

diff --git a/libavformat/concatdec.c b/libavformat/concatdec.c
index c57d1b649a..a8113f2895 100644
--- a/libavformat/concatdec.c
+++ b/libavformat/concatdec.c
@@ -73,6 +73,7 @@ typedef struct {
     ConcatMatchMode stream_match_mode;
     unsigned auto_convert;
     int segment_time_metadata;
+    int probe_duration;
 } ConcatContext;
 
 static int concat_probe(const AVProbeData *probe)
@@ -658,6 +659,32 @@ fail:
     return ret == AVERROR_EOF ? 0 : ret;
 }
 
+/* Probe a file to get its duration without fully opening it */
+static int64_t probe_file_duration(AVFormatContext *avf, ConcatFile *file)
+{
+    AVFormatContext *probe_avf = NULL;
+    int64_t duration = AV_NOPTS_VALUE;
+    int ret;
+
+    probe_avf = avformat_alloc_context();
+    if (!probe_avf)
+        return AV_NOPTS_VALUE;
+
+    probe_avf->interrupt_callback = avf->interrupt_callback;
+    ret = avformat_open_input(&probe_avf, file->url, NULL, NULL);
+    if (ret < 0) {
+        av_log(avf, AV_LOG_WARNING, "Could not probe duration for '%s'\n", file->url);
+        return AV_NOPTS_VALUE;
+    }
+
+    ret = avformat_find_stream_info(probe_avf, NULL);
+    if (ret >= 0 && probe_avf->duration > 0)
+        duration = probe_avf->duration;
+
+    avformat_close_input(&probe_avf);
+    return duration;
+}
+
 static int concat_read_header(AVFormatContext *avf)
 {
     ConcatContext *cat = avf->priv_data;
@@ -681,10 +708,20 @@ static int concat_read_header(AVFormatContext *avf)
         if (cat->files[i].user_duration == AV_NOPTS_VALUE) {
             if (cat->files[i].inpoint == AV_NOPTS_VALUE || cat->files[i].outpoint == AV_NOPTS_VALUE ||
                 cat->files[i].outpoint - (uint64_t)cat->files[i].inpoint != av_sat_sub64(cat->files[i].outpoint, cat->files[i].inpoint)
-            )
+            ) {
+                /* Probe duration if option enabled */
+                if (cat->probe_duration) {
+                    int64_t probed = probe_file_duration(avf, &cat->files[i]);
+                    if (probed != AV_NOPTS_VALUE) {
+                        cat->files[i].user_duration = probed;
+                        goto have_duration;
+                    }
+                }
                 break;
+            }
             cat->files[i].user_duration = cat->files[i].outpoint - cat->files[i].inpoint;
         }
+have_duration:
         cat->files[i].duration = cat->files[i].user_duration;
         if (time + (uint64_t)cat->files[i].user_duration > INT64_MAX)
             return AVERROR_INVALIDDATA;
@@ -940,6 +977,8 @@ static const AVOption options[] = {
       OFFSET(auto_convert), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DEC },
     { "segment_time_metadata", "output file segment start time and duration as packet metadata",
       OFFSET(segment_time_metadata), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
+    { "probe_duration", "probe file durations for accurate total duration",
+      OFFSET(probe_duration), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
     { NULL }
 };
 
-- 
2.39.5 (Apple Git-154)

From 2cad6aa7bb9d499f3e8d9465d2d313f329f76f41 Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Mon, 30 Mar 2026 20:36:53 +0100
Subject: [PATCH 06/14] avdevice/gdigrab: cache cursor to reduce flickering

Cache cursor handle and icon info instead of recreating them every
frame. Only update when the system cursor actually changes. This
reduces GDI calls and cursor flickering in screen recordings.

Fixes ticket #11599.
---
 libavdevice/gdigrab.c | 77 ++++++++++++++++++++++++++++---------------
 1 file changed, 50 insertions(+), 27 deletions(-)

diff --git a/libavdevice/gdigrab.c b/libavdevice/gdigrab.c
index fa9ba27db0..5923af4327 100644
--- a/libavdevice/gdigrab.c
+++ b/libavdevice/gdigrab.c
@@ -67,6 +67,10 @@ struct gdigrab {
     HWND       region_hwnd; /**< Handle of the region border window */
 
     int cursor_error_printed;
+
+    HCURSOR    last_cursor;  /**< Last system cursor handle for caching */
+    HICON      cached_icon;  /**< Cached cursor icon copy */
+    ICONINFO   cached_info;  /**< Cached icon info (contains bitmaps) */
 };
 
 #define WIN32_API_ERROR(str)                                            \
@@ -480,8 +484,7 @@ static void paint_mouse_pointer(AVFormatContext *s1, struct gdigrab *gdigrab)
     ci.cbSize = sizeof(ci);
 
     if (GetCursorInfo(&ci)) {
-        HCURSOR icon = CopyCursor(ci.hCursor);
-        ICONINFO info;
+        ICONINFO *info = &gdigrab->cached_info;
         POINT pos;
         RECT clip_rect = gdigrab->clip_rect;
         HWND hwnd = gdigrab->hwnd;
@@ -489,42 +492,62 @@ static void paint_mouse_pointer(AVFormatContext *s1, struct gdigrab *gdigrab)
         int vertres = GetDeviceCaps(gdigrab->source_hdc, VERTRES);
         int desktophorzres = GetDeviceCaps(gdigrab->source_hdc, DESKTOPHORZRES);
         int desktopvertres = GetDeviceCaps(gdigrab->source_hdc, DESKTOPVERTRES);
-        info.hbmMask = NULL;
-        info.hbmColor = NULL;
 
         if (ci.flags != CURSOR_SHOWING)
             return;
 
-        if (!icon) {
-            /* Use the standard arrow cursor as a fallback.
-             * You'll probably only hit this in Wine, which can't fetch
-             * the current system cursor. */
-            icon = CopyCursor(LoadCursor(NULL, IDC_ARROW));
-        }
+        /* Cache cursor to avoid flickering from repeated CopyCursor/GetIconInfo */
+        if (ci.hCursor != gdigrab->last_cursor) {
+            /* Free old cached resources */
+            if (gdigrab->cached_info.hbmMask)
+                DeleteObject(gdigrab->cached_info.hbmMask);
+            if (gdigrab->cached_info.hbmColor)
+                DeleteObject(gdigrab->cached_info.hbmColor);
+            if (gdigrab->cached_icon)
+                DestroyCursor(gdigrab->cached_icon);
+
+            gdigrab->cached_info.hbmMask = NULL;
+            gdigrab->cached_info.hbmColor = NULL;
+            gdigrab->cached_icon = CopyCursor(ci.hCursor);
+            gdigrab->last_cursor = ci.hCursor;
+
+            if (!gdigrab->cached_icon) {
+                /* Use the standard arrow cursor as a fallback.
+                 * You'll probably only hit this in Wine, which can't fetch
+                 * the current system cursor. */
+                gdigrab->cached_icon = CopyCursor(LoadCursor(NULL, IDC_ARROW));
+            }
 
-        if (!GetIconInfo(icon, &info)) {
-            CURSOR_ERROR("Could not get icon info");
-            goto icon_error;
+            if (gdigrab->cached_icon && !GetIconInfo(gdigrab->cached_icon, info)) {
+                CURSOR_ERROR("Could not get icon info");
+                DestroyCursor(gdigrab->cached_icon);
+                gdigrab->cached_icon = NULL;
+                gdigrab->last_cursor = NULL;
+                return;
+            }
         }
 
+        if (!gdigrab->cached_icon)
+            return;
+
         if (hwnd) {
             RECT rect;
 
             if (GetWindowRect(hwnd, &rect)) {
-                pos.x = ci.ptScreenPos.x - clip_rect.left - info.xHotspot - rect.left;
-                pos.y = ci.ptScreenPos.y - clip_rect.top - info.yHotspot - rect.top;
+                pos.x = ci.ptScreenPos.x - clip_rect.left - info->xHotspot - rect.left;
+                pos.y = ci.ptScreenPos.y - clip_rect.top - info->yHotspot - rect.top;
 
                 //that would keep the correct location of mouse with hidpi screens
                 pos.x = pos.x * desktophorzres / horzres;
                 pos.y = pos.y * desktopvertres / vertres;
             } else {
                 CURSOR_ERROR("Couldn't get window rectangle");
-                goto icon_error;
+                return;
             }
         } else {
             //that would keep the correct location of mouse with hidpi screens
-            pos.x = ci.ptScreenPos.x * desktophorzres / horzres - clip_rect.left - info.xHotspot;
-            pos.y = ci.ptScreenPos.y * desktopvertres / vertres - clip_rect.top - info.yHotspot;
+            pos.x = ci.ptScreenPos.x * desktophorzres / horzres - clip_rect.left - info->xHotspot;
+            pos.y = ci.ptScreenPos.y * desktopvertres / vertres - clip_rect.top - info->yHotspot;
         }
 
         av_log(s1, AV_LOG_DEBUG, "Cursor pos (%li,%li) -> (%li,%li)\n",
@@ -532,17 +555,9 @@ static void paint_mouse_pointer(AVFormatContext *s1, struct gdigrab *gdigrab)
 
         if (pos.x >= 0 && pos.x <= clip_rect.right - clip_rect.left &&
                 pos.y >= 0 && pos.y <= clip_rect.bottom - clip_rect.top) {
-            if (!DrawIcon(gdigrab->dest_hdc, pos.x, pos.y, icon))
+            if (!DrawIcon(gdigrab->dest_hdc, pos.x, pos.y, gdigrab->cached_icon))
                 CURSOR_ERROR("Couldn't draw icon");
         }
-
-icon_error:
-        if (info.hbmMask)
-            DeleteObject(info.hbmMask);
-        if (info.hbmColor)
-            DeleteObject(info.hbmColor);
-        if (icon)
-            DestroyCursor(icon);
     } else {
         CURSOR_ERROR("Couldn't get cursor info");
     }
@@ -655,6 +670,14 @@ static int gdigrab_read_close(AVFormatContext *s1)
     if (s->source_hdc)
         DeleteDC(s->source_hdc);
 
+    /* Free cached cursor resources */
+    if (s->cached_info.hbmMask)
+        DeleteObject(s->cached_info.hbmMask);
+    if (s->cached_info.hbmColor)
+        DeleteObject(s->cached_info.hbmColor);
+    if (s->cached_icon)
+        DestroyCursor(s->cached_icon);
+
     return 0;
 }
 
-- 
2.39.5 (Apple Git-154)

From 13da1634c840a7063fcd584930fc6bba5171dfc9 Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Mon, 30 Mar 2026 20:37:57 +0100
Subject: [PATCH 07/14] avdevice/libcdio: validate seek position

Add bounds checking to ensure seek position is within disc boundaries
and check cdio_paranoia_seek return value to properly report errors.

Fixes ticket #3815.
---
 libavdevice/libcdio.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/libavdevice/libcdio.c b/libavdevice/libcdio.c
index 5e97c25b89..e34ba7d31b 100644
--- a/libavdevice/libcdio.c
+++ b/libavdevice/libcdio.c
@@ -153,8 +153,16 @@ static int read_seek(AVFormatContext *ctx, int stream_index, int64_t timestamp,
 {
     CDIOContext *s = ctx->priv_data;
     AVStream *st = ctx->streams[0];
+    lsn_t ret;
+
+    /* Validate seek position is within disc bounds */
+    if (timestamp < 0 || timestamp > s->last_sector)
+        return AVERROR(EINVAL);
+
+    ret = cdio_paranoia_seek(s->paranoia, timestamp, SEEK_SET);
+    if (ret < 0)
+        return AVERROR(EIO);
 
-    cdio_paranoia_seek(s->paranoia, timestamp, SEEK_SET);
     avpriv_update_cur_dts(ctx, st, timestamp);
     return 0;
 }
-- 
2.39.5 (Apple Git-154)

From 6c9a84db9e6d59ea8862fc670118fd17d498ce35 Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Mon, 30 Mar 2026 20:58:07 +0100
Subject: [PATCH 08/14] avformat/spdifenc: fix TrueHD frame timing sanity check
 threshold

The sanity check for padding_remaining was using MAT_FRAME_SIZE / 2
(30712 bytes) which is too restrictive for variable bitrate TrueHD
content. Change to MAT_FRAME_SIZE * 2 to accommodate larger inter-frame
gaps without triggering false "Unusual frame timing" errors.

This fixes audio cutouts during TrueHD passthrough via S/PDIF.

Fixes ticket #10948.
---
 libavformat/spdifenc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c
index ab3f73da0d..c6e1fc83d0 100644
--- a/libavformat/spdifenc.c
+++ b/libavformat/spdifenc.c
@@ -474,7 +474,7 @@ static int spdif_header_truehd(AVFormatContext *s, AVPacket *pkt)
                delta_samples, delta_bytes);
 
         /* sanity check */
-        if (padding_remaining < 0 || padding_remaining >= MAT_FRAME_SIZE / 2) {
+        if (padding_remaining < 0 || padding_remaining >= MAT_FRAME_SIZE * 2) {
             avpriv_request_sample(s, "Unusual frame timing: %"PRIu16" => %"PRIu16", %d samples/frame",
                                   ctx->truehd_prev_time, input_timing, ctx->truehd_samples_per_frame);
             padding_remaining = 0;
-- 
2.39.5 (Apple Git-154)

From 47b3b83bd38b4366212326c14bfe1d11c5393efa Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Mon, 30 Mar 2026 21:06:17 +0100
Subject: [PATCH 09/14] avformat/movenc: only enable first default track per
 media type

When multiple tracks of the same type have AV_DISPOSITION_DEFAULT set,
only mark the first one as enabled in the tkhd atom. This prevents
QuickTime and Apple TV from treating multiple audio tracks as alternates
that play simultaneously.

Fixes ticket #3622.
---
 libavformat/movenc.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 971da25629..9d50f4d262 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -7742,7 +7742,9 @@ static void enable_tracks(AVFormatContext *s)
         if (first[st->codecpar->codec_type] < 0)
             first[st->codecpar->codec_type] = i;
         if (st->disposition & AV_DISPOSITION_DEFAULT) {
-            mov->tracks[i].flags |= MOV_TRACK_ENABLED;
+            /* Only enable the first track of each type with default flag */
+            if (!enabled[st->codecpar->codec_type])
+                mov->tracks[i].flags |= MOV_TRACK_ENABLED;
             enabled[st->codecpar->codec_type]++;
         }
     }
-- 
2.39.5 (Apple Git-154)

From 0080fbc2096e47b4bad126387601a8fd4ac9e133 Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Mon, 30 Mar 2026 21:14:10 +0100
Subject: [PATCH 10/14] avformat/mux: allow non-strict timestamp check for
 subtitles

Add subtitle and data type exclusions to the second DTS validation
check, matching the first check's behavior. This allows subtitle
streams like PGS to have equal (non-decreasing) DTS values without
triggering errors, while still rejecting strictly decreasing timestamps.

Fixes ticket #5474.
---
 libavformat/mux.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/libavformat/mux.c b/libavformat/mux.c
index db3b6c2bfe..dd25d9a0d5 100644
--- a/libavformat/mux.c
+++ b/libavformat/mux.c
@@ -800,7 +800,10 @@ static int prepare_input_packet(AVFormatContext *s, AVStream *st, AVPacket *pkt)
         /* check that the dts are increasing (or at least non-decreasing,
          * if the format allows it */
         if (sti->cur_dts != AV_NOPTS_VALUE &&
-            ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && sti->cur_dts >= pkt->dts) ||
+            ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) &&
+              st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE &&
+              st->codecpar->codec_type != AVMEDIA_TYPE_DATA &&
+              sti->cur_dts >= pkt->dts) ||
              sti->cur_dts > pkt->dts)) {
             av_log(s, AV_LOG_ERROR,
                    "Application provided invalid, non monotonically increasing "
-- 
2.39.5 (Apple Git-154)

From f88e06f0558a648e0ce75f0a238e785f0ad08b46 Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Tue, 31 Mar 2026 09:16:30 +0100
Subject: [PATCH 11/14] avformat/hls: handle EXT-X-DISCONTINUITY timestamp
 resets

Parse the EXT-X-DISCONTINUITY tag and adjust packet timestamps
to ensure monotonic DTS when crossing discontinuity boundaries.

When a segment is preceded by EXT-X-DISCONTINUITY, the timestamps
may reset to a different base. This causes "Non-monotonous DTS"
warnings when muxing. Track the expected timestamp based on
cumulative segment durations and apply an offset to maintain
continuous timestamps.

Fixes ticket #5419
---
 libavformat/hls.c | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/libavformat/hls.c b/libavformat/hls.c
index 28c883097a..06696b5637 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -84,6 +84,7 @@ struct segment {
     uint8_t iv[16];
     /* associated Media Initialization Section, treated as a segment */
     struct segment *init_section;
+    int discontinuity; /* EXT-X-DISCONTINUITY before this segment */
 };
 
 struct rendition;
@@ -174,6 +175,12 @@ struct playlist {
     int n_init_sections;
     struct segment **init_sections;
     int is_subtitle; /* Indicates if it's a subtitle playlist */
+
+    /* EXT-X-DISCONTINUITY handling: track timestamp offset when
+     * crossing discontinuity boundaries */
+    int64_t discontinuity_ts_offset; /* offset to add to packet timestamps */
+    int64_t last_seg_end_ts;         /* expected timestamp at end of last segment */
+    int in_discontinuity;            /* currently in a discontinuity segment */
 };
 
 /*
@@ -336,6 +343,9 @@ static struct playlist *new_playlist(HLSContext *c, const char *url,
     pls->is_id3_timestamped = -1;
     pls->id3_mpegts_timestamp = AV_NOPTS_VALUE;
 
+    /* Discontinuity handling */
+    pls->last_seg_end_ts = AV_NOPTS_VALUE;
+
     dynarray_add(&c->playlists, &c->n_playlists, pls);
     return pls;
 }
@@ -786,6 +796,7 @@ static int parse_playlist(HLSContext *c, const char *url,
                           struct playlist *pls, AVIOContext *in)
 {
     int ret = 0, is_segment = 0, is_variant = 0;
+    int is_discontinuity = 0;
     int64_t duration = 0;
     enum KeyType key_type = KEY_NONE;
     uint8_t iv[16] = "";
@@ -969,6 +980,8 @@ static int parse_playlist(HLSContext *c, const char *url,
         } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) {
             if (pls)
                 pls->finished = 1;
+        } else if (av_strstart(line, "#EXT-X-DISCONTINUITY", &ptr)) {
+            is_discontinuity = 1;
         } else if (av_strstart(line, "#EXTINF:", &ptr)) {
             double d = atof(ptr) * AV_TIME_BASE;
             if (d < 0 || d > INT64_MAX || isnan(d)) {
@@ -1063,8 +1076,10 @@ static int parse_playlist(HLSContext *c, const char *url,
                 }
                 seg->duration = duration;
                 seg->key_type = key_type;
+                seg->discontinuity = is_discontinuity;
                 dynarray_add(&pls->segments, &pls->n_segments, seg);
                 is_segment = 0;
+                is_discontinuity = 0;
 
                 seg->size = seg_size;
                 if (seg_size >= 0) {
@@ -1746,6 +1761,12 @@ restart:
     } else {
         ff_format_io_close(v->parent, &v->input);
     }
+
+    /* Track expected end timestamp for discontinuity handling */
+    if (v->last_seg_end_ts == AV_NOPTS_VALUE)
+        v->last_seg_end_ts = 0;
+    v->last_seg_end_ts += seg->duration;
+
     v->cur_seq_no++;
 
     c->cur_seq_no = v->cur_seq_no;
@@ -2555,6 +2576,27 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
                 }
 
                 seg = current_segment(pls);
+
+                /* Handle EXT-X-DISCONTINUITY: adjust timestamps to be continuous */
+                if (seg && seg->discontinuity && !pls->in_discontinuity) {
+                    /* Entering a discontinuity segment - calculate timestamp offset */
+                    pls->in_discontinuity = 1;
+                    if (pls->pkt->dts != AV_NOPTS_VALUE && pls->last_seg_end_ts != AV_NOPTS_VALUE) {
+                        AVRational tb = get_timebase(pls);
+                        int64_t expected_ts = av_rescale_q(pls->last_seg_end_ts, AV_TIME_BASE_Q, tb);
+                        pls->discontinuity_ts_offset = expected_ts - pls->pkt->dts;
+                    }
+                } else if (seg && !seg->discontinuity) {
+                    pls->in_discontinuity = 0;
+                }
+
+                /* Apply discontinuity timestamp offset */
+                if (pls->discontinuity_ts_offset && pls->pkt->dts != AV_NOPTS_VALUE) {
+                    pls->pkt->dts += pls->discontinuity_ts_offset;
+                    if (pls->pkt->pts != AV_NOPTS_VALUE)
+                        pls->pkt->pts += pls->discontinuity_ts_offset;
+                }
+
                 if (seg && seg->key_type == KEY_SAMPLE_AES && !strstr(pls->ctx->iformat->name, "mov")) {
                     enum AVCodecID codec_id = pls->ctx->streams[pls->pkt->stream_index]->codecpar->codec_id;
                     memcpy(c->crypto_ctx.iv, seg->iv, sizeof(seg->iv));
-- 
2.39.5 (Apple Git-154)

From 5ca110bc5eb22c724cf5084107473ab11e183948 Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Tue, 31 Mar 2026 09:27:42 +0100
Subject: [PATCH 12/14] avformat/matroskaenc: fall back to alternate timestamp
 when primary is missing

When the preferred timestamp (DTS or PTS based on track settings)
is missing, try the other timestamp field before failing. This
allows MPEG-TS streams with missing timestamps to be remuxed to
Matroska without aborting.

Fixes ticket #3369
---
 libavformat/matroskaenc.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c
index cb19584b2b..c51c36ceaf 100644
--- a/libavformat/matroskaenc.c
+++ b/libavformat/matroskaenc.c
@@ -3065,9 +3065,15 @@ static int mkv_write_packet_internal(AVFormatContext *s, const AVPacket *pkt)
     int64_t ts = track->write_dts ? pkt->dts : pkt->pts;
     int64_t relative_packet_pos;
 
+    /* Fall back to other timestamp field if preferred one is missing */
     if (ts == AV_NOPTS_VALUE) {
-        av_log(s, AV_LOG_ERROR, "Can't write packet with unknown timestamp\n");
-        return AVERROR(EINVAL);
+        ts = track->write_dts ? pkt->pts : pkt->dts;
+        if (ts == AV_NOPTS_VALUE) {
+            av_log(s, AV_LOG_ERROR, "Can't write packet with unknown timestamp\n");
+            return AVERROR(EINVAL);
+        }
+        av_log(s, AV_LOG_WARNING, "Using %s as timestamp for stream %d\n",
+               track->write_dts ? "pts" : "dts", pkt->stream_index);
     }
     ts += track->ts_offset;
 
-- 
2.39.5 (Apple Git-154)

From bb88a166ed984de962fbc84b8e87b3aa1f5b2eef Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Tue, 31 Mar 2026 10:27:58 +0100
Subject: [PATCH 13/14] avcodec/decode: reset faulty timestamp counters on
 flush

Reset pts_correction_num_faulty_pts and pts_correction_num_faulty_dts
in ff_decode_flush_buffers() to prevent incorrect PTS guessing after
buffer flushes. Without this reset, historical fault counts carry over
to subsequent decode operations.

Fixes ticket #11645
---
 libavcodec/decode.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/libavcodec/decode.c b/libavcodec/decode.c
index 6ddf469663..4a5fd64cbc 100644
--- a/libavcodec/decode.c
+++ b/libavcodec/decode.c
@@ -2343,6 +2343,8 @@ av_cold void ff_decode_flush_buffers(AVCodecContext *avctx)
     av_packet_unref(avci->last_pkt_props);
     av_packet_unref(avci->in_pkt);
 
+    dc->pts_correction_num_faulty_pts =
+    dc->pts_correction_num_faulty_dts = 0;
     dc->pts_correction_last_pts =
     dc->pts_correction_last_dts = INT64_MIN;
 
-- 
2.39.5 (Apple Git-154)

From ad0445dbea8b854885be1b339a6fbc5e1c7a8f75 Mon Sep 17 00:00:00 2001
From: jlima8900 <jlima8900@hotmail.com>
Date: Tue, 31 Mar 2026 10:43:38 +0100
Subject: [PATCH 14/14] avformat/movenc: preserve DVD subtitle dimensions from
 source

When copying DVD subtitles to MP4, use the existing codecpar
dimensions if valid, rather than always defaulting to 720x480.
The explicit "size:" in extradata still takes precedence.

This fixes subtitle dimension loss when copying from sources
with non-NTSC resolutions (e.g., 720x576 PAL or HD).

Fixes ticket #11534
---
 libavformat/movenc.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 9d50f4d262..0d21c8e88e 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -7836,7 +7836,9 @@ static uint32_t rgb_to_yuv(uint32_t rgb)
 static int mov_create_dvd_sub_decoder_specific_info(MOVTrack *track,
                                                     AVStream *st)
 {
-    int i, width = 720, height = 480;
+    /* Use existing dimensions if valid, otherwise default to DVD NTSC */
+    int i, width = st->codecpar->width > 0 ? st->codecpar->width : 720;
+    int height = st->codecpar->height > 0 ? st->codecpar->height : 480;
     int have_palette = 0, have_size = 0;
     uint32_t palette[16];
     char *cur = track->extradata[track->last_stsd_index];
-- 
2.39.5 (Apple Git-154)

