This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

The following commit(s) were added to refs/heads/master by this push:
     new 0e89d993c5 libavformat/oggenc.c: re-initialize stream on new metadata.
0e89d993c5 is described below

commit 0e89d993c5bc6c7ed4d9a05f750bd5a7130836ea
Author:     Romain Beauxis <[email protected]>
AuthorDate: Fri Mar 14 00:27:04 2025 -0500
Commit:     Lynne <[email protected]>
CommitDate: Wed Feb 18 16:25:06 2026 +0000

    libavformat/oggenc.c: re-initialize stream on new metadata.
---
 libavformat/oggenc.c      | 104 +++++++++++++++++++++++++++++++++++++++-------
 tests/fate-run.sh         |  10 +++++
 tests/fate/ogg-flac.mak   |   5 +++
 tests/fate/ogg-opus.mak   |   5 +++
 tests/fate/ogg-vorbis.mak |   5 +++
 5 files changed, 115 insertions(+), 14 deletions(-)

diff --git a/libavformat/oggenc.c b/libavformat/oggenc.c
index be85386a6a..605c9d20e1 100644
--- a/libavformat/oggenc.c
+++ b/libavformat/oggenc.c
@@ -66,6 +66,7 @@ typedef struct OGGStreamContext {
     OGGPage page; ///< current page
     unsigned serial_num; ///< serial number
     int64_t last_granule; ///< last packet granule
+    int packet_seen; ///< true when packets have been submitted
 } OGGStreamContext;
 
 typedef struct OGGPageList {
@@ -81,11 +82,14 @@ typedef struct OGGContext {
 #endif
     int64_t pref_duration;      ///< preferred page duration (0 => fill all 
segments)
     int serial_offset;
+    int failed; // if true all packet submission will fail.
 } OGGContext;
 
 #define OFFSET(x) offsetof(OGGContext, x)
 #define PARAM AV_OPT_FLAG_ENCODING_PARAM
 
+static int ogg_write_trailer(AVFormatContext *s);
+
 static const AVOption options[] = {
     { "serial_offset", "serial number offset",
         OFFSET(serial_offset), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, 
PARAM },
@@ -107,6 +111,20 @@ static const AVClass ogg_muxer_class = {
     .version    = LIBAVUTIL_VERSION_INT,
 };
 
+static void ogg_cleanup_stream(AVStream *st)
+{
+    OGGStreamContext *oggstream = st->priv_data;
+
+    if (st->codecpar->codec_id == AV_CODEC_ID_FLAC ||
+        st->codecpar->codec_id == AV_CODEC_ID_SPEEX ||
+        st->codecpar->codec_id == AV_CODEC_ID_OPUS ||
+        st->codecpar->codec_id == AV_CODEC_ID_VP8) {
+        av_freep(&oggstream->header[0]);
+    }
+
+    av_freep(&oggstream->header[1]);
+}
+
 static void ogg_write_page(AVFormatContext *s, OGGPage *page, int extra_flags)
 {
     OGGStreamContext *oggstream = s->streams[page->stream_index]->priv_data;
@@ -482,6 +500,8 @@ static void ogg_write_pages(AVFormatContext *s, int flush)
     ogg->page_list = p;
 }
 
+// This function can be used on an initialized context to reinitialize the
+// streams.
 static int ogg_init(AVFormatContext *s)
 {
     OGGContext *ogg = s->priv_data;
@@ -515,9 +535,17 @@ static int ogg_init(AVFormatContext *s)
             av_log(s, AV_LOG_ERROR, "No extradata present\n");
             return AVERROR_INVALIDDATA;
         }
-        oggstream = av_mallocz(sizeof(*oggstream));
-        if (!oggstream)
-            return AVERROR(ENOMEM);
+        oggstream = st->priv_data;
+
+        if (!oggstream) {
+            oggstream = av_mallocz(sizeof(*oggstream));
+            if (!oggstream)
+                return AVERROR(ENOMEM);
+            st->priv_data = oggstream;
+        } else {
+            ogg_cleanup_stream(st);
+            memset(oggstream, 0, sizeof(*oggstream));
+        }
 
         oggstream->page.stream_index = i;
 
@@ -534,7 +562,6 @@ static int ogg_init(AVFormatContext *s)
 
         av_dict_copy(&st->metadata, s->metadata, AV_DICT_DONT_OVERWRITE);
 
-        st->priv_data = oggstream;
         if (st->codecpar->codec_id == AV_CODEC_ID_FLAC) {
             int err = ogg_build_flac_headers(st->codecpar, oggstream,
                                              s->flags & AVFMT_FLAG_BITEXACT,
@@ -642,13 +669,69 @@ static int ogg_write_header(AVFormatContext *s)
     return 0;
 }
 
+static int ogg_check_new_metadata(AVFormatContext *s, AVPacket *pkt)
+{
+    int ret = 0;
+    size_t size;
+    OGGContext *oggcontext = s->priv_data;
+    AVStream *st = s->streams[pkt->stream_index];
+    OGGStreamContext *oggstream = st->priv_data;
+    const uint8_t *side_metadata = av_packet_get_side_data(pkt, 
AV_PKT_DATA_STRINGS_METADATA, &size);
+
+    if (!side_metadata)
+        return 0;
+
+    // Don't restart on first packet.
+    if (!oggstream->packet_seen)
+        return 0;
+
+    if (s->nb_streams > 1) {
+        av_log(s, AV_LOG_WARNING, "Multiple streams present: cannot insert new 
metadata!\n");
+        return 0;
+    }
+
+    if (st->codecpar->codec_id != AV_CODEC_ID_VORBIS &&
+        st->codecpar->codec_id != AV_CODEC_ID_FLAC &&
+        st->codecpar->codec_id != AV_CODEC_ID_OPUS) {
+        av_log(s, AV_LOG_WARNING, "Inserting metadata is only supported for 
vorbis, flac and opus streams!\n");
+        return 0;
+    }
+
+    ret = ogg_write_trailer(s);
+    if (ret < 0)
+        goto end;
+
+    av_dict_free(&st->metadata);
+    ret = av_packet_unpack_dictionary(side_metadata, size, &st->metadata);
+    if (ret < 0)
+        goto end;
+
+    ret = ogg_init(s);
+    if (ret < 0)
+        goto end;
+
+    ret = ogg_write_header(s);
+
+end:
+    oggcontext->failed = ret < 0;
+    return ret;
+}
+
 static int ogg_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
 {
     AVStream *st = s->streams[pkt->stream_index];
+    OGGContext *oggcontext = s->priv_data;
     OGGStreamContext *oggstream = st->priv_data;
     int ret;
     int64_t granule;
 
+    if (oggcontext->failed)
+        return AVERROR_INVALIDDATA;
+
+    ret = ogg_check_new_metadata(s, pkt);
+    if (ret < 0)
+        return ret;
+
     if (st->codecpar->codec_id == AV_CODEC_ID_THEORA) {
         int64_t pts = oggstream->vrev < 1 ? pkt->pts : pkt->pts + 
pkt->duration;
         int pframe_count;
@@ -690,6 +773,7 @@ static int ogg_write_packet_internal(AVFormatContext *s, 
AVPacket *pkt)
     ogg_write_pages(s, 0);
 
     oggstream->last_granule = granule;
+    oggstream->packet_seen = 1;
 
     return 0;
 }
@@ -736,16 +820,8 @@ static void ogg_free(AVFormatContext *s)
 
     for (i = 0; i < s->nb_streams; i++) {
         AVStream *st = s->streams[i];
-        OGGStreamContext *oggstream = st->priv_data;
-        if (!oggstream)
-            continue;
-        if (st->codecpar->codec_id == AV_CODEC_ID_FLAC ||
-            st->codecpar->codec_id == AV_CODEC_ID_SPEEX ||
-            st->codecpar->codec_id == AV_CODEC_ID_OPUS ||
-            st->codecpar->codec_id == AV_CODEC_ID_VP8) {
-            av_freep(&oggstream->header[0]);
-        }
-        av_freep(&oggstream->header[1]);
+        if (st->priv_data)
+            ogg_cleanup_stream(st);
     }
 
     while (p) {
diff --git a/tests/fate-run.sh b/tests/fate-run.sh
index 0e0c11ac3b..cd66cd059c 100755
--- a/tests/fate-run.sh
+++ b/tests/fate-run.sh
@@ -103,6 +103,16 @@ runecho(){
     $target_exec $target_path/"$@" >&3
 }
 
+run_with_temp(){
+    create_tmp=$1
+    process_tmp=$2
+    filext=$3
+    tmpfile=${outdir}/$test.$filext
+    cleanfiles="$cleanfiles $tmpfile"
+    run $create_tmp $tmpfile || return 1
+    run $process_tmp $tmpfile
+}
+
 probefmt(){
     run ffprobe${PROGSUF}${EXECSUF} -bitexact -threads $threads -show_entries 
format=format_name -print_format default=nw=1:nk=1 "$@"
 }
diff --git a/tests/fate/ogg-flac.mak b/tests/fate/ogg-flac.mak
index 07bdb232cb..3b48f32d2d 100644
--- a/tests/fate/ogg-flac.mak
+++ b/tests/fate/ogg-flac.mak
@@ -2,6 +2,11 @@ FATE_OGG_FLAC += fate-ogg-flac-chained-meta
 fate-ogg-flac-chained-meta: REF = 
$(SRC_PATH)/tests/ref/fate/ogg-flac-chained-meta.txt
 fate-ogg-flac-chained-meta: CMD = run 
$(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF) 
$(TARGET_SAMPLES)/ogg-flac/chained-meta.ogg
 
+FATE_OGG_FLAC += fate-ogg-flac-copy-chained-meta
+fate-ogg-flac-copy-chained-meta: 
$(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF) $(FFMPEG)
+fate-ogg-flac-copy-chained-meta: REF = 
$(SRC_PATH)/tests/ref/fate/ogg-flac-chained-meta.txt
+fate-ogg-flac-copy-chained-meta: CMD = run_with_temp "$(FFMPEG) -nostdin 
-hide_banner -loglevel quiet -i $(TARGET_SAMPLES)/ogg-flac/chained-meta.ogg -c 
copy -f ogg -y" "$(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF)" ogg
+
 FATE_OGG_FLAC-$(call DEMDEC, OGG, FLAC, FLAC_PARSER) += $(FATE_OGG_FLAC)
 
 FATE_SAMPLES_DUMP_STREAM_META += $(FATE_OGG_FLAC-yes)
diff --git a/tests/fate/ogg-opus.mak b/tests/fate/ogg-opus.mak
index 54b6fbabde..d8a30dd058 100644
--- a/tests/fate/ogg-opus.mak
+++ b/tests/fate/ogg-opus.mak
@@ -2,6 +2,11 @@ FATE_OGG_OPUS += fate-ogg-opus-chained-meta
 fate-ogg-opus-chained-meta: REF = 
$(SRC_PATH)/tests/ref/fate/ogg-opus-chained-meta.txt
 fate-ogg-opus-chained-meta: CMD = run 
$(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF) 
$(TARGET_SAMPLES)/ogg-opus/chained-meta.ogg
 
+FATE_OGG_OPUS += fate-ogg-opus-copy-chained-meta
+fate-ogg-opus-copy-chained-meta: 
$(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF) $(FFMPEG)
+fate-ogg-opus-copy-chained-meta: REF = 
$(SRC_PATH)/tests/ref/fate/ogg-opus-chained-meta.txt
+fate-ogg-opus-copy-chained-meta: CMD = run_with_temp "$(FFMPEG) -nostdin 
-hide_banner -loglevel quiet -i $(TARGET_SAMPLES)/ogg-opus/chained-meta.ogg -c 
copy -f ogg -y" "$(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF)" ogg
+
 FATE_OGG_OPUS-$(call DEMDEC, OGG, OPUS) += $(FATE_OGG_OPUS)
 
 FATE_SAMPLES_DUMP_STREAM_META += $(FATE_OGG_OPUS-yes)
diff --git a/tests/fate/ogg-vorbis.mak b/tests/fate/ogg-vorbis.mak
index 74805d591e..1bca373fea 100644
--- a/tests/fate/ogg-vorbis.mak
+++ b/tests/fate/ogg-vorbis.mak
@@ -2,6 +2,11 @@ FATE_OGG_VORBIS += fate-ogg-vorbis-chained-meta
 fate-ogg-vorbis-chained-meta: REF = 
$(SRC_PATH)/tests/ref/fate/ogg-vorbis-chained-meta.txt
 fate-ogg-vorbis-chained-meta: CMD = run 
$(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF) 
$(TARGET_SAMPLES)/ogg-vorbis/chained-meta.ogg
 
+FATE_OGG_VORBIS += fate-ogg-vorbis-copy-chained-meta
+fate-ogg-vorbis-copy-chained-meta: 
$(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF) $(FFMPEG)
+fate-ogg-vorbis-copy-chained-meta: REF = 
$(SRC_PATH)/tests/ref/fate/ogg-vorbis-chained-meta.txt
+fate-ogg-vorbis-copy-chained-meta: CMD = run_with_temp "$(FFMPEG) -nostdin 
-hide_banner -loglevel quiet -i $(TARGET_SAMPLES)/ogg-vorbis/chained-meta.ogg 
-c copy -f ogg -y" "$(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF)" ogg
+
 FATE_OGG_VORBIS-$(call DEMDEC, OGG, VORBIS) += $(FATE_OGG_VORBIS)
 
 FATE_SAMPLES_DUMP_STREAM_META += $(FATE_OGG_VORBIS-yes)

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

Reply via email to