Commit 111ff3f736 for ffmpeg

commit 111ff3f736d238ba3ffd01361820e9121cdfd2c8
Author: Romain Beauxis <romain.beauxis@gmail.com>
Date:   Thu Nov 5 10:15:42 2026 -0600

    avformat/vorbis/comment: move the Vorbis comment reader out of the Ogg demuxer

    The reader moves from oggparsevorbis.c to vorbis/comment/reader.c and its declarations to vorbis/comment.h, next to the writer.

    The FLAC and Matroska demuxers link the reader instead of the Ogg Vorbis mapping.

diff --git a/.forgejo/CODEOWNERS b/.forgejo/CODEOWNERS
index 380cb4f797..b07b4617be 100644
--- a/.forgejo/CODEOWNERS
+++ b/.forgejo/CODEOWNERS
@@ -170,6 +170,7 @@ libavformat/rm.* @pross
 libavformat/sauce.* @pross
 libavformat/scd.* @zane
 libavformat/tty.* @pross
+libavformat/vorbis/comment/reader[.]c @toots
 libavformat/whip.c @JackLau
 libavformat/wsd.* @pross
 libavformat/wtv.* @pross
diff --git a/MAINTAINERS b/MAINTAINERS
index ef62381933..dfba5da3d4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -506,6 +506,7 @@ Muxers/Demuxers:
   tta.c                                 Alex Beregszaszi
   txd.c                                 Ivo van Poorten
   voc.c                                 Aurelien Jacobs
+  vorbis/comment/reader.c               David Conrad, Romain Beauxis
   wav.c                                 Michael Niedermayer
   wc3movie.c                            Mike Melanson
   webm dash (matroskaenc.c)             Vignesh Venkatasubramanian
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 044eab9534..29db52477f 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -227,8 +227,8 @@ OBJS-$(CONFIG_FITS_DEMUXER)              += fitsdec.o
 OBJS-$(CONFIG_FITS_MUXER)                += fitsenc.o
 OBJS-$(CONFIG_FLAC_DEMUXER)              += flacdec.o rawdec.o \
                                             flac_picture.o   \
-                                            oggparsevorbis.o \
                                             replaygain.o     \
+                                            vorbis/comment/reader.o \
                                             vorbis/comment/writer.o
 OBJS-$(CONFIG_FLAC_MUXER)                += flacenc.o flacenc_header.o \
                                             vorbis/comment/writer.o
@@ -373,7 +373,7 @@ OBJS-$(CONFIG_M4V_DEMUXER)               += m4vdec.o rawdec.o
 OBJS-$(CONFIG_M4V_MUXER)                 += rawenc.o
 OBJS-$(CONFIG_MATROSKA_DEMUXER)          += matroskadec.o matroska.o  \
                                             flac_picture.o rmsipr.o \
-                                            oggparsevorbis.o vorbis/comment/writer.o \
+                                            vorbis/comment/reader.o vorbis/comment/writer.o \
                                             qtpalette.o replaygain.o dovi_isom.o
 OBJS-$(CONFIG_MATROSKA_MUXER)            += matroskaenc.o matroska.o \
                                             flacenc_header.o vorbis/comment/writer.o \
@@ -457,7 +457,8 @@ OBJS-$(CONFIG_OGG_DEMUXER)               += oggdec.o         \
                                             oggparsevorbis.o \
                                             oggparsevp8.o    \
                                             replaygain.o     \
-                                            vorbis/comment/writer.o  \
+                                            vorbis/comment/reader.o \
+                                            vorbis/comment/writer.o \
                                             flac_picture.o
 OBJS-$(CONFIG_OGA_MUXER)                 += oggenc.o \
                                             vorbis/comment/writer.o
diff --git a/libavformat/flacdec.c b/libavformat/flacdec.c
index 1f5cfeed71..a07c8bacba 100644
--- a/libavformat/flacdec.c
+++ b/libavformat/flacdec.c
@@ -30,7 +30,7 @@
 #include "flac_picture.h"
 #include "internal.h"
 #include "rawdec.h"
-#include "oggdec.h"
+#include "libavformat/vorbis/comment.h"
 #include "replaygain.h"

 #define SEEKPOINT_SIZE 18
diff --git a/libavformat/matroskadec.c b/libavformat/matroskadec.c
index 706a0566bc..2aa4f83e78 100644
--- a/libavformat/matroskadec.c
+++ b/libavformat/matroskadec.c
@@ -66,7 +66,7 @@
 #include "internal.h"
 #include "isom.h"
 #include "matroska.h"
-#include "oggdec.h"
+#include "libavformat/vorbis/comment.h"
 /* For ff_codec_get_id(). */
 #include "riff.h"
 #include "rmsipr.h"
diff --git a/libavformat/oggdec.h b/libavformat/oggdec.h
index b7b008cc1f..107ba12f02 100644
--- a/libavformat/oggdec.h
+++ b/libavformat/oggdec.h
@@ -139,28 +139,6 @@ extern const struct ogg_codec ff_theora_codec;
 extern const struct ogg_codec ff_vorbis_codec;
 extern const struct ogg_codec ff_vp8_codec;

-/**
- * Parse Vorbis comments
- *
- * @note  The buffer will be temporarily modified by this function,
- *        so it needs to be writable. Furthermore it must be padded
- *        by a single byte (not counted in size).
- *        All changes will have been reverted upon return.
- */
-int ff_vorbis_comment(AVFormatContext *ms, AVDictionary **m,
-                      const uint8_t *buf, int size, int parse_picture);
-
-/**
- * Parse Vorbis comments and add metadata to an AVStream
- *
- * @note  The buffer will be temporarily modified by this function,
- *        so it needs to be writable. Furthermore it must be padded
- *        by a single byte (not counted in size).
- *        All changes will have been reverted upon return.
- */
-int ff_vorbis_stream_comment(AVFormatContext *as, AVStream *st,
-                             const uint8_t *buf, int size);
-
 /**
  * Parse Vorbis comments, add metadata to an AVStream
  *
diff --git a/libavformat/oggparseflac.c b/libavformat/oggparseflac.c
index 31dd1439da..9926c5ebbb 100644
--- a/libavformat/oggparseflac.c
+++ b/libavformat/oggparseflac.c
@@ -26,6 +26,7 @@
 #include "avformat.h"
 #include "internal.h"
 #include "oggdec.h"
+#include "libavformat/vorbis/comment.h"

 #define OGG_FLAC_METADATA_TYPE_STREAMINFO 0x7F
 #define OGG_FLAC_MAGIC "\177FLAC"
diff --git a/libavformat/oggparseogm.c b/libavformat/oggparseogm.c
index 1e794745ab..371a05a2c6 100644
--- a/libavformat/oggparseogm.c
+++ b/libavformat/oggparseogm.c
@@ -31,6 +31,7 @@
 #include "avformat.h"
 #include "internal.h"
 #include "oggdec.h"
+#include "libavformat/vorbis/comment.h"
 #include "riff.h"

 static int
diff --git a/libavformat/oggparseopus.c b/libavformat/oggparseopus.c
index 6dde828abf..a9ef988c45 100644
--- a/libavformat/oggparseopus.c
+++ b/libavformat/oggparseopus.c
@@ -26,6 +26,7 @@
 #include "avformat.h"
 #include "internal.h"
 #include "oggdec.h"
+#include "libavformat/vorbis/comment.h"

 struct oggopus_private {
     int need_comments;
diff --git a/libavformat/oggparsespeex.c b/libavformat/oggparsespeex.c
index 0a4b32de15..79ec881835 100644
--- a/libavformat/oggparsespeex.c
+++ b/libavformat/oggparsespeex.c
@@ -29,6 +29,7 @@
 #include "avformat.h"
 #include "internal.h"
 #include "oggdec.h"
+#include "libavformat/vorbis/comment.h"

 struct speex_params {
     int packet_size;
diff --git a/libavformat/oggparsetheora.c b/libavformat/oggparsetheora.c
index e104d63229..c0ddbff8a3 100644
--- a/libavformat/oggparsetheora.c
+++ b/libavformat/oggparsetheora.c
@@ -29,6 +29,7 @@
 #include "avformat.h"
 #include "internal.h"
 #include "oggdec.h"
+#include "libavformat/vorbis/comment.h"

 typedef struct TheoraParams {
     int gpshift;
diff --git a/libavformat/oggparsevorbis.c b/libavformat/oggparsevorbis.c
index f00dbfb517..d50a8e01f7 100644
--- a/libavformat/oggparsevorbis.c
+++ b/libavformat/oggparsevorbis.c
@@ -24,8 +24,6 @@

 #include <stdlib.h>

-#include "libavutil/avstring.h"
-#include "libavutil/base64.h"
 #include "libavutil/dict.h"
 #include "libavutil/mem.h"

@@ -34,166 +32,11 @@

 #include "avformat.h"
 #include "demux.h"
-#include "flac_picture.h"
 #include "internal.h"
 #include "oggdec.h"
 #include "libavformat/vorbis/comment.h"
 #include "replaygain.h"

-static int ogm_chapter(AVFormatContext *as, const uint8_t *key, const uint8_t *val)
-{
-    int i, cnum, h, m, s, ms, keylen = strlen(key);
-    AVChapter *chapter = NULL;
-
-    if (keylen < 9 || av_strncasecmp(key, "CHAPTER", 7) || sscanf(key+7, "%03d", &cnum) != 1)
-        return 0;
-
-    if (keylen <= 10) {
-        if (sscanf(val, "%02d:%02d:%02d.%03d", &h, &m, &s, &ms) < 4)
-            return 0;
-
-        avpriv_new_chapter(as, cnum, (AVRational) { 1, 1000 },
-                           ms + 1000 * (s + 60 * (m + 60 * h)),
-                           AV_NOPTS_VALUE, NULL);
-    } else if (!av_strcasecmp(key + keylen - 4, "NAME")) {
-        for (i = 0; i < as->nb_chapters; i++)
-            if (as->chapters[i]->id == cnum) {
-                chapter = as->chapters[i];
-                break;
-            }
-        if (!chapter)
-            return 0;
-
-        av_dict_set(&chapter->metadata, "title", val, 0);
-    } else
-        return 0;
-
-    return 1;
-}
-
-int ff_vorbis_stream_comment(AVFormatContext *as, AVStream *st,
-                             const uint8_t *buf, int size)
-{
-    int updates = ff_vorbis_comment(as, &st->metadata, buf, size, 1);
-
-    if (updates > 0) {
-        st->event_flags |= AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
-    }
-
-    return updates;
-}
-
-/**
- * This function temporarily modifies the (const qualified) input buffer
- * and reverts its changes before return. The input buffer needs to have
- * at least one byte of padding.
- */
-static int vorbis_parse_single_comment(AVFormatContext *as, AVDictionary **m,
-                                       const uint8_t *buf, uint32_t size,
-                                       int *updates, int parse_picture)
-{
-    char *t = (char*)buf, *v = memchr(t, '=', size);
-    int tl, vl;
-    char backup;
-
-    if (!v)
-        return 0;
-
-    tl = v - t;
-    vl = size - tl - 1;
-    v++;
-
-    if (!tl || !vl)
-        return 0;
-
-    t[tl]  = 0;
-
-    backup = v[vl];
-    v[vl]  = 0;
-
-    /* The format in which the pictures are stored is the FLAC format.
-     * Xiph says: "The binary FLAC picture structure is base64 encoded
-     * and placed within a VorbisComment with the tag name
-     * 'METADATA_BLOCK_PICTURE'. This is the preferred and
-     * recommended way of embedding cover art within VorbisComments."
-     */
-    if (!av_strcasecmp(t, "METADATA_BLOCK_PICTURE") && parse_picture) {
-        int ret, len = AV_BASE64_DECODE_SIZE(vl);
-        uint8_t *pict = av_malloc(len + AV_INPUT_BUFFER_PADDING_SIZE);
-
-        if (!pict) {
-            av_log(as, AV_LOG_WARNING, "out-of-memory error. Skipping cover art block.\n");
-            goto end;
-        }
-        ret = av_base64_decode(pict, v, len);
-        if (ret > 0)
-            ret = ff_flac_parse_picture(as, &pict, ret, 0);
-        av_freep(&pict);
-        if (ret < 0) {
-            av_log(as, AV_LOG_WARNING, "Failed to parse cover art block.\n");
-            goto end;
-        }
-    } else if (!ogm_chapter(as, t, v)) {
-        (*updates)++;
-        if (av_dict_get(*m, t, NULL, 0))
-            av_dict_set(m, t, ";", AV_DICT_APPEND);
-        av_dict_set(m, t, v, AV_DICT_APPEND);
-    }
-end:
-    t[tl] = '=';
-    v[vl] = backup;
-
-    return 0;
-}
-
-int ff_vorbis_comment(AVFormatContext *as, AVDictionary **m,
-                      const uint8_t *buf, int size,
-                      int parse_picture)
-{
-    const uint8_t *p   = buf;
-    const uint8_t *end = buf + size;
-    int updates        = 0;
-    unsigned n;
-    int s, ret;
-
-    /* must have vendor_length and user_comment_list_length */
-    if (size < 8)
-        return AVERROR_INVALIDDATA;
-
-    s = bytestream_get_le32(&p);
-
-    if (end - p - 4 < s || s < 0)
-        return AVERROR_INVALIDDATA;
-
-    p += s;
-
-    n = bytestream_get_le32(&p);
-
-    while (end - p >= 4 && n > 0) {
-        s = bytestream_get_le32(&p);
-
-        if (end - p < s || s < 0)
-            break;
-
-        ret = vorbis_parse_single_comment(as, m, p, s, &updates, parse_picture);
-        if (ret < 0)
-            return ret;
-        p += s;
-        n--;
-    }
-
-    if (p != end)
-        av_log(as, AV_LOG_INFO,
-               "%td bytes of comment header remain\n", end - p);
-    if (n > 0)
-        av_log(as, AV_LOG_INFO,
-               "truncated comment header, %i comments not found\n", n);
-
-    ff_metadata_conv(m, NULL, ff_vorbiscomment_metadata_conv);
-
-    return updates;
-}
-
 /*
  * Parse the vorbis header
  *
diff --git a/libavformat/oggparsevp8.c b/libavformat/oggparsevp8.c
index 676b3a0f8e..39ab927500 100644
--- a/libavformat/oggparsevp8.c
+++ b/libavformat/oggparsevp8.c
@@ -24,6 +24,7 @@
 #include "avformat.h"
 #include "internal.h"
 #include "oggdec.h"
+#include "libavformat/vorbis/comment.h"

 #define VP8_HEADER_SIZE 26

diff --git a/libavformat/vorbis/comment.h b/libavformat/vorbis/comment.h
index 2762d63135..2256632348 100644
--- a/libavformat/vorbis/comment.h
+++ b/libavformat/vorbis/comment.h
@@ -54,4 +54,26 @@ int ff_vorbiscomment_write(AVIOContext *pb, const AVDictionary *m,

 extern const AVMetadataConv ff_vorbiscomment_metadata_conv[];

+/**
+ * Parse Vorbis comments
+ *
+ * @note  The buffer will be temporarily modified by this function,
+ *        so it needs to be writable. Furthermore it must be padded
+ *        by a single byte (not counted in size).
+ *        All changes will have been reverted upon return.
+ */
+int ff_vorbis_comment(AVFormatContext *ms, AVDictionary **m,
+                      const uint8_t *buf, int size, int parse_picture);
+
+/**
+ * Parse Vorbis comments and add metadata to an AVStream
+ *
+ * @note  The buffer will be temporarily modified by this function,
+ *        so it needs to be writable. Furthermore it must be padded
+ *        by a single byte (not counted in size).
+ *        All changes will have been reverted upon return.
+ */
+int ff_vorbis_stream_comment(AVFormatContext *as, AVStream *st,
+                             const uint8_t *buf, int size);
+
 #endif /* AVFORMAT_VORBIS_COMMENT_H */
diff --git a/libavformat/vorbis/comment/reader.c b/libavformat/vorbis/comment/reader.c
new file mode 100644
index 0000000000..2e7215b9a8
--- /dev/null
+++ b/libavformat/vorbis/comment/reader.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2005  Michael Ahlberg, Måns Rullgård
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ *  The above copyright notice and this permission notice shall be
+ *  included in all copies or substantial portions of the Software.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ *  DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "libavutil/avstring.h"
+#include "libavutil/base64.h"
+#include "libavutil/dict.h"
+#include "libavutil/mem.h"
+
+#include "libavcodec/bytestream.h"
+
+#include "libavformat/avformat.h"
+#include "libavformat/demux.h"
+#include "libavformat/flac_picture.h"
+#include "libavformat/internal.h"
+#include "libavformat/metadata.h"
+#include "libavformat/vorbis/comment.h"
+
+static int ogm_chapter(AVFormatContext *as, const uint8_t *key, const uint8_t *val)
+{
+    int i, cnum, h, m, s, ms, keylen = strlen(key);
+    AVChapter *chapter = NULL;
+
+    if (keylen < 9 || av_strncasecmp(key, "CHAPTER", 7) || sscanf(key+7, "%03d", &cnum) != 1)
+        return 0;
+
+    if (keylen <= 10) {
+        if (sscanf(val, "%02d:%02d:%02d.%03d", &h, &m, &s, &ms) < 4)
+            return 0;
+
+        avpriv_new_chapter(as, cnum, (AVRational) { 1, 1000 },
+                           ms + 1000 * (s + 60 * (m + 60 * h)),
+                           AV_NOPTS_VALUE, NULL);
+    } else if (!av_strcasecmp(key + keylen - 4, "NAME")) {
+        for (i = 0; i < as->nb_chapters; i++)
+            if (as->chapters[i]->id == cnum) {
+                chapter = as->chapters[i];
+                break;
+            }
+        if (!chapter)
+            return 0;
+
+        av_dict_set(&chapter->metadata, "title", val, 0);
+    } else
+        return 0;
+
+    return 1;
+}
+
+int ff_vorbis_stream_comment(AVFormatContext *as, AVStream *st,
+                             const uint8_t *buf, int size)
+{
+    int updates = ff_vorbis_comment(as, &st->metadata, buf, size, 1);
+
+    if (updates > 0) {
+        st->event_flags |= AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
+    }
+
+    return updates;
+}
+
+/**
+ * This function temporarily modifies the (const qualified) input buffer
+ * and reverts its changes before return. The input buffer needs to have
+ * at least one byte of padding.
+ */
+static int vorbis_parse_single_comment(AVFormatContext *as, AVDictionary **m,
+                                       const uint8_t *buf, uint32_t size,
+                                       int *updates, int parse_picture)
+{
+    char *t = (char*)buf, *v = memchr(t, '=', size);
+    int tl, vl;
+    char backup;
+
+    if (!v)
+        return 0;
+
+    tl = v - t;
+    vl = size - tl - 1;
+    v++;
+
+    if (!tl || !vl)
+        return 0;
+
+    t[tl]  = 0;
+
+    backup = v[vl];
+    v[vl]  = 0;
+
+    /* The format in which the pictures are stored is the FLAC format.
+     * Xiph says: "The binary FLAC picture structure is base64 encoded
+     * and placed within a VorbisComment with the tag name
+     * 'METADATA_BLOCK_PICTURE'. This is the preferred and
+     * recommended way of embedding cover art within VorbisComments."
+     */
+    if (!av_strcasecmp(t, "METADATA_BLOCK_PICTURE") && parse_picture) {
+        int ret, len = AV_BASE64_DECODE_SIZE(vl);
+        uint8_t *pict = av_malloc(len + AV_INPUT_BUFFER_PADDING_SIZE);
+
+        if (!pict) {
+            av_log(as, AV_LOG_WARNING, "out-of-memory error. Skipping cover art block.\n");
+            goto end;
+        }
+        ret = av_base64_decode(pict, v, len);
+        if (ret > 0)
+            ret = ff_flac_parse_picture(as, &pict, ret, 0);
+        av_freep(&pict);
+        if (ret < 0) {
+            av_log(as, AV_LOG_WARNING, "Failed to parse cover art block.\n");
+            goto end;
+        }
+    } else if (!ogm_chapter(as, t, v)) {
+        (*updates)++;
+        if (av_dict_get(*m, t, NULL, 0))
+            av_dict_set(m, t, ";", AV_DICT_APPEND);
+        av_dict_set(m, t, v, AV_DICT_APPEND);
+    }
+end:
+    t[tl] = '=';
+    v[vl] = backup;
+
+    return 0;
+}
+
+int ff_vorbis_comment(AVFormatContext *as, AVDictionary **m,
+                      const uint8_t *buf, int size,
+                      int parse_picture)
+{
+    const uint8_t *p   = buf;
+    const uint8_t *end = buf + size;
+    int updates        = 0;
+    unsigned n;
+    int s, ret;
+
+    /* must have vendor_length and user_comment_list_length */
+    if (size < 8)
+        return AVERROR_INVALIDDATA;
+
+    s = bytestream_get_le32(&p);
+
+    if (end - p - 4 < s || s < 0)
+        return AVERROR_INVALIDDATA;
+
+    p += s;
+
+    n = bytestream_get_le32(&p);
+
+    while (end - p >= 4 && n > 0) {
+        s = bytestream_get_le32(&p);
+
+        if (end - p < s || s < 0)
+            break;
+
+        ret = vorbis_parse_single_comment(as, m, p, s, &updates, parse_picture);
+        if (ret < 0)
+            return ret;
+        p += s;
+        n--;
+    }
+
+    if (p != end)
+        av_log(as, AV_LOG_INFO,
+               "%td bytes of comment header remain\n", end - p);
+    if (n > 0)
+        av_log(as, AV_LOG_INFO,
+               "truncated comment header, %i comments not found\n", n);
+
+    ff_metadata_conv(m, NULL, ff_vorbiscomment_metadata_conv);
+
+    return updates;
+}