Commit 13ddd58210 for ffmpeg

commit 13ddd5821035bcc5cca57b813faecfede7ad6a98
Author: Romain Beauxis <romain.beauxis@gmail.com>
Date:   Fri Nov 6 16:25:03 2026 -0600

    avcodec/vorbisdec: attach the tags of a dropped frame to the first frame with samples

    The priming frame of a Vorbis stream is dropped whole, along with the tags it took from its packet, such as a chained Ogg link's title.

    Hand those tags on to the first frame with samples, so that they line up with the first sample played.

    The first link of the vorbis chained-meta test now reports its tags on its first frame.

diff --git a/libavcodec/vorbisdec.c b/libavcodec/vorbisdec.c
index aeea1b4908..111fe44318 100644
--- a/libavcodec/vorbisdec.c
+++ b/libavcodec/vorbisdec.c
@@ -30,6 +30,7 @@
 #include <math.h>

 #include "libavutil/avassert.h"
+#include "libavutil/dict.h"
 #include "libavutil/float_dsp.h"
 #include "libavutil/mem.h"
 #include "libavutil/tx.h"
@@ -158,6 +159,10 @@ typedef struct vorbis_context_s {
     int8_t       previous_window;
     float        *channel_residues;
     float        *saved;
+    /**
+     * Tags of a dropped priming frame, added to the first frame with samples.
+     */
+    AVDictionary *pending_metadata;
 } vorbis_context;

 /* Helper functions */
@@ -1859,6 +1864,22 @@ static int vorbis_decode_frame(AVCodecContext *avctx, AVFrame *frame,
     if (!vc->first_frame) {
         vc->first_frame = 1;
         avctx->internal->skip_samples = len;
+        if (!(avctx->flags2 & AV_CODEC_FLAG2_SKIP_MANUAL)) {
+            av_dict_free(&vc->pending_metadata);
+            vc->pending_metadata = frame->metadata;
+            frame->metadata      = NULL;
+        }
+    } else if (vc->pending_metadata) {
+        if (!frame->metadata) {
+            frame->metadata      = vc->pending_metadata;
+            vc->pending_metadata = NULL;
+        } else {
+            ret = av_dict_copy(&frame->metadata, vc->pending_metadata,
+                               AV_DICT_DONT_OVERWRITE);
+            av_dict_free(&vc->pending_metadata);
+            if (ret < 0)
+                return ret;
+        }
     }

     ff_dlog(NULL, "parsed %d bytes %d bits, returned %d samples (*ch*bits) \n",
@@ -1877,6 +1898,7 @@ static av_cold int vorbis_decode_close(AVCodecContext *avctx)
     vorbis_context *vc = avctx->priv_data;

     vorbis_free(vc);
+    av_dict_free(&vc->pending_metadata);

     return 0;
 }
@@ -1885,6 +1907,7 @@ static av_cold void vorbis_decode_flush(AVCodecContext *avctx)
 {
     vorbis_context *vc = avctx->priv_data;

+    av_dict_free(&vc->pending_metadata);
     if (vc->saved) {
         memset(vc->saved, 0, (vc->blocksize[1] / 4) * vc->audio_channels *
                              sizeof(*vc->saved));
diff --git a/tests/ref/fate/ogg-vorbis-chained-meta.txt b/tests/ref/fate/ogg-vorbis-chained-meta.txt
index 5a102fdd7e..84feb88ddd 100644
--- a/tests/ref/fate/ogg-vorbis-chained-meta.txt
+++ b/tests/ref/fate/ogg-vorbis-chained-meta.txt
@@ -2,7 +2,7 @@ Stream ID: 0, codec name: vorbis, metadata: encoder=Lavc61.19.100 libvorbis:titl
 Stream ID: 0, packet PTS: -128, packet DTS: -128
 Stream ID: 0, new metadata: encoder=Lavc61.19.100 libvorbis:title=First Stream
 Stream ID: 0, packet PTS: 0, packet DTS: 0
-Stream ID: 0, frame PTS: 0, metadata: N/A
+Stream ID: 0, frame PTS: 0, metadata: encoder=Lavc61.19.100 libvorbis:title=First Stream
 Stream ID: 0, packet PTS: 576, packet DTS: 576
 Stream ID: 0, frame PTS: 576, metadata: N/A
 Stream ID: 0, packet PTS: 1600, packet DTS: 1600