PR #23021 opened by Vladimir Pantelic  (av500)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23021
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23021.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.

issues raised in https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22909 have been 
addressed:

- loop index is now `unsigned int`
- `has_titles` logic has been fixed
- cue IDs are now just the loop index `i`
- `dyn_buf` usage has been replaced by simple `strlen(...) + 1`

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


>From 3b8c8b63f5bb61a49927be6508af71efd5c143e1 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 | 48 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/libavformat/wavenc.c b/libavformat/wavenc.c
index 01fffaafe5..edd266f4f5 100644
--- a/libavformat/wavenc.c
+++ b/libavformat/wavenc.c
@@ -299,6 +299,51 @@ static int peak_write_chunk(AVFormatContext *s)
     return 0;
 }
 
+static void wav_write_cue_chunk(AVFormatContext *s)
+{
+    AVIOContext *pb = s->pb;
+    AVRational scale = {1, s->streams[0]->codecpar->sample_rate};
+    int has_titles = 0;
+
+    int64_t start = ff_start_tag(pb, "cue "); /* chunk ID */
+    avio_wl32(pb, s->nb_chapters); /* number of cue points */
+
+    /* write cue points */
+    for (unsigned 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 = 1;
+
+        avio_wl32(pb, i);         /* 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 */
+        start = ff_start_tag(pb, "LIST");
+        ffio_wfourcc(pb, "adtl"); /* associated data list chunk */
+
+        /* write cue point names, if they exist */
+        for (unsigned 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) {
+                int len = strlen(t->value);
+                int64_t labl = ff_start_tag(pb, "labl"); /* sub chunk ID */
+                avio_wl32(pb, i);                        /* cue point ID */
+                avio_write(s->pb, t->value, len + 1);    /* title string data 
*/
+                ff_end_tag(pb, labl);
+            }
+        }
+        ff_end_tag(pb, start);
+    }
+}
+
 static int wav_write_header(AVFormatContext *s)
 {
     WAVMuxContext *wav = s->priv_data;
@@ -354,6 +399,9 @@ static int wav_write_header(AVFormatContext *s)
     if (wav->write_bext)
         bwf_write_bext_chunk(s);
 
+    if (s->nb_chapters)
+        wav_write_cue_chunk(s);
+
     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