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]
