PR #22909 opened by av500
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22909
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22909.patch

avformat/wavenc: add support for writing chapters as 'cue ' chunks, also write 
chapter titles as 'adtl' chunks if they exist

since 2a14d55a there is support for reading 'cue ' and 'adtl' chunks into 
chapters and titles from WAV files, this commit adds support for writing them 
back.

Signed-off-by: Vladimir Pantelic <[email protected]>


>From 6ab02051def82680b4d17a46d48f80ce990c9da1 Mon Sep 17 00:00:00 2001
From: Vladimir Pantelic <[email protected]>
Date: Fri, 24 Apr 2026 16:01:00 +0200
Subject: [PATCH] avformat/wavenc: add support for writing chapters as 'cue '
 chunks

also write chapter titles as 'adtl' chunks if they exist

Signed-off-by: Vladimir Pantelic <[email protected]>
---
 libavformat/wavenc.c | 64 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/libavformat/wavenc.c b/libavformat/wavenc.c
index 01fffaafe5..596551c3ed 100644
--- a/libavformat/wavenc.c
+++ b/libavformat/wavenc.c
@@ -299,6 +299,64 @@ static int peak_write_chunk(AVFormatContext *s)
     return 0;
 }
 
+static int wav_write_cue_chunk(AVFormatContext *s)
+{
+    AVIOContext *pb = s->pb;
+    AVRational scale = {1, s->streams[0]->codecpar->sample_rate};
+    int has_titles = 1;
+
+    int64_t start = ff_start_tag(pb, "cue "); /* chunk ID */
+    avio_wl32(pb, s->nb_chapters); /* number of cue points */
+
+    /* write cue points */
+    for (int i = 0; i < s->nb_chapters; i++) {
+        AVChapter *c = s->chapters[i];
+        int64_t samples = av_rescale_q(c->start, c->time_base, scale);
+        if(!av_dict_get(c->metadata, "title", NULL, 0))
+            has_titles = 0;
+
+        avio_wl32(pb, c->id);     /* cue point ID */
+        avio_wl32(pb, 0);         /* position, only relevant with PLST chunk */
+        ffio_wfourcc(pb, "data"); /* data chunk ID, only used with WAVL chunk 
*/
+        avio_wl32(pb, 0);         /* chunk start, only used with WAVL chunk */
+        avio_wl32(pb, 0);         /* block start, only used with WAVL chunk*/
+        avio_wl32(pb, samples);   /* sample start (in # of samples) */
+    }
+
+    ff_end_tag(pb, start);
+
+    if(has_titles) { /* do not write an empty list */
+        AVIOContext *dyn_buf;
+        int ret;
+        if ((ret = avio_open_dyn_buf(&dyn_buf)) < 0)
+            return ret;
+
+        start = ff_start_tag(pb, "LIST");
+        ffio_wfourcc(pb, "adtl"); /* associated data list chunk */
+
+        /* write cue point names, if they exist */
+        for (int i = 0; i < s->nb_chapters; i++) {
+            AVChapter *c = s->chapters[i];
+            AVDictionaryEntry *t = av_dict_get(c->metadata, "title", NULL, 0);
+            if(t) {
+                uint8_t *buf;
+                int len;
+                int64_t label = ff_start_tag(pb, "labl"); /* sub chunk ID */
+                avio_wl32(pb, c->id);                     /* cue point ID */
+                avio_put_str(dyn_buf, t->value);
+                len = avio_get_dyn_buf(dyn_buf, &buf);
+                avio_write(pb, buf, len);                 /* title string data 
*/
+                ffio_reset_dyn_buf(dyn_buf);
+                ff_end_tag(pb, label);
+            }
+        }
+        ff_end_tag(pb, start);
+        ffio_free_dyn_buf(&dyn_buf);
+    }
+
+    return 0;
+}
+
 static int wav_write_header(AVFormatContext *s)
 {
     WAVMuxContext *wav = s->priv_data;
@@ -354,6 +412,12 @@ static int wav_write_header(AVFormatContext *s)
     if (wav->write_bext)
         bwf_write_bext_chunk(s);
 
+    if (s->nb_chapters) {
+        int ret;
+        if((ret = wav_write_cue_chunk(s)) < 0)
+            return ret;
+    }
+
     if (wav->write_peak) {
         int ret;
         if ((ret = peak_init_writer(s)) < 0)
-- 
2.52.0

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

Reply via email to