From c0fb6f963fd7ee555ba2fdc254444e208bb0bc46 Mon Sep 17 00:00:00 2001 From: Paul B Mahol Date: Sun, 16 Dec 2018 21:50:08 +0100 Subject: [PATCH] avformat/vorbiscomment: add support for writing chapters Fixes #7532. --- libavformat/flacenc.c | 4 +-- libavformat/matroskaenc.c | 4 +-- libavformat/oggenc.c | 22 +++++++------- libavformat/vorbiscomment.c | 60 +++++++++++++++++++++++++++++++++++-- libavformat/vorbiscomment.h | 8 +++-- 5 files changed, 79 insertions(+), 19 deletions(-) diff --git a/libavformat/flacenc.c b/libavformat/flacenc.c index 617bccdc84..a07260f426 100644 --- a/libavformat/flacenc.c +++ b/libavformat/flacenc.c @@ -65,7 +65,7 @@ static int flac_write_block_comment(AVIOContext *pb, AVDictionary **m, ff_metadata_conv(m, ff_vorbiscomment_metadata_conv, NULL); - len = ff_vorbiscomment_length(*m, vendor); + len = ff_vorbiscomment_length(*m, vendor, NULL, 0); if (len >= ((1<<24) - 4)) return AVERROR(EINVAL); p0 = av_malloc(len+4); @@ -75,7 +75,7 @@ static int flac_write_block_comment(AVIOContext *pb, AVDictionary **m, bytestream_put_byte(&p, last_block ? 0x84 : 0x04); bytestream_put_be24(&p, len); - ff_vorbiscomment_write(&p, m, vendor); + ff_vorbiscomment_write(&p, m, vendor, NULL, 0); avio_write(pb, p0, len+4); av_freep(&p0); diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index aed83aef70..f0e4c60b93 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -693,7 +693,7 @@ static int put_flac_codecpriv(AVFormatContext *s, snprintf(buf, sizeof(buf), "0x%"PRIx64, par->channel_layout); av_dict_set(&dict, "WAVEFORMATEXTENSIBLE_CHANNEL_MASK", buf, 0); - len = ff_vorbiscomment_length(dict, vendor); + len = ff_vorbiscomment_length(dict, vendor, NULL, 0); if (len >= ((1<<24) - 4)) return AVERROR(EINVAL); @@ -707,7 +707,7 @@ static int put_flac_codecpriv(AVFormatContext *s, AV_WB24(data + 1, len); p = data + 4; - ff_vorbiscomment_write(&p, &dict, vendor); + ff_vorbiscomment_write(&p, &dict, vendor, NULL, 0); avio_write(pb, data, len + 4); diff --git a/libavformat/oggenc.c b/libavformat/oggenc.c index 10c4eda062..06021c4f4b 100644 --- a/libavformat/oggenc.c +++ b/libavformat/oggenc.c @@ -291,7 +291,8 @@ static int ogg_buffer_data(AVFormatContext *s, AVStream *st, } static uint8_t *ogg_write_vorbiscomment(int64_t offset, int bitexact, - int *header_len, AVDictionary **m, int framing_bit) + int *header_len, AVDictionary **m, int framing_bit, + AVChapter **chapters, unsigned int nb_chapters) { const char *vendor = bitexact ? "ffmpeg" : LIBAVFORMAT_IDENT; int64_t size; @@ -299,7 +300,7 @@ static uint8_t *ogg_write_vorbiscomment(int64_t offset, int bitexact, ff_metadata_conv(m, ff_vorbiscomment_metadata_conv, NULL); - size = offset + ff_vorbiscomment_length(*m, vendor) + framing_bit; + size = offset + ff_vorbiscomment_length(*m, vendor, chapters, nb_chapters) + framing_bit; if (size > INT_MAX) return NULL; p = av_mallocz(size); @@ -308,7 +309,7 @@ static uint8_t *ogg_write_vorbiscomment(int64_t offset, int bitexact, p0 = p; p += offset; - ff_vorbiscomment_write(&p, m, vendor); + ff_vorbiscomment_write(&p, m, vendor, chapters, nb_chapters); if (framing_bit) bytestream_put_byte(&p, 1); @@ -342,7 +343,7 @@ static int ogg_build_flac_headers(AVCodecParameters *par, bytestream_put_buffer(&p, par->extradata, FLAC_STREAMINFO_SIZE); // second packet: VorbisComment - p = ogg_write_vorbiscomment(4, bitexact, &oggstream->header_len[1], m, 0); + p = ogg_write_vorbiscomment(4, bitexact, &oggstream->header_len[1], m, 0, NULL, 0); if (!p) return AVERROR(ENOMEM); oggstream->header[1] = p; @@ -373,7 +374,7 @@ static int ogg_build_speex_headers(AVCodecParameters *par, AV_WL32(&oggstream->header[0][68], 0); // set extra_headers to 0 // second packet: VorbisComment - p = ogg_write_vorbiscomment(0, bitexact, &oggstream->header_len[1], m, 0); + p = ogg_write_vorbiscomment(0, bitexact, &oggstream->header_len[1], m, 0, NULL, 0); if (!p) return AVERROR(ENOMEM); oggstream->header[1] = p; @@ -385,7 +386,8 @@ static int ogg_build_speex_headers(AVCodecParameters *par, static int ogg_build_opus_headers(AVCodecParameters *par, OGGStreamContext *oggstream, int bitexact, - AVDictionary **m) + AVDictionary **m, AVChapter **chapters, + unsigned int nb_chapters) { uint8_t *p; @@ -401,7 +403,7 @@ static int ogg_build_opus_headers(AVCodecParameters *par, bytestream_put_buffer(&p, par->extradata, par->extradata_size); /* second packet: VorbisComment */ - p = ogg_write_vorbiscomment(8, bitexact, &oggstream->header_len[1], m, 0); + p = ogg_write_vorbiscomment(8, bitexact, &oggstream->header_len[1], m, 0, chapters, nb_chapters); if (!p) return AVERROR(ENOMEM); oggstream->header[1] = p; @@ -446,7 +448,7 @@ static int ogg_build_vp8_headers(AVFormatContext *s, AVStream *st, /* optional second packet: VorbisComment */ if (av_dict_get(st->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX)) { - p = ogg_write_vorbiscomment(7, bitexact, &oggstream->header_len[1], &st->metadata, 0); + p = ogg_write_vorbiscomment(7, bitexact, &oggstream->header_len[1], &st->metadata, 0, NULL, 0); if (!p) return AVERROR(ENOMEM); oggstream->header[1] = p; @@ -560,7 +562,7 @@ static int ogg_init(AVFormatContext *s) } else if (st->codecpar->codec_id == AV_CODEC_ID_OPUS) { int err = ogg_build_opus_headers(st->codecpar, oggstream, s->flags & AVFMT_FLAG_BITEXACT, - &st->metadata); + &st->metadata, s->chapters, s->nb_chapters); if (err) { av_log(s, AV_LOG_ERROR, "Error writing Opus headers\n"); av_freep(&st->priv_data); @@ -590,7 +592,7 @@ static int ogg_init(AVFormatContext *s) p = ogg_write_vorbiscomment(7, s->flags & AVFMT_FLAG_BITEXACT, &oggstream->header_len[1], &st->metadata, - framing_bit); + framing_bit, NULL, 0); oggstream->header[1] = p; if (!p) return AVERROR(ENOMEM); diff --git a/libavformat/vorbiscomment.c b/libavformat/vorbiscomment.c index 575dd13328..fb5c655a23 100644 --- a/libavformat/vorbiscomment.c +++ b/libavformat/vorbiscomment.c @@ -38,10 +38,21 @@ const AVMetadataConv ff_vorbiscomment_metadata_conv[] = { { 0 } }; -int64_t ff_vorbiscomment_length(AVDictionary *m, const char *vendor_string) +int64_t ff_vorbiscomment_length(AVDictionary *m, const char *vendor_string, + AVChapter **chapters, unsigned int nb_chapters) { int64_t len = 8; len += strlen(vendor_string); + if (chapters && nb_chapters) { + for (int i = 0; i < nb_chapters; i++) { + AVDictionaryEntry *tag = NULL; + len += 4 + 12 + 1 + 10; + while ((tag = av_dict_get(chapters[i]->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + int64_t len1 = !strcmp(tag->key, "title") ? 4 : strlen(tag->key); + len += 4 + 10 + len1 + 1 + strlen(tag->value); + } + } + } if (m) { AVDictionaryEntry *tag = NULL; while ((tag = av_dict_get(m, "", tag, AV_DICT_IGNORE_SUFFIX))) { @@ -52,12 +63,19 @@ int64_t ff_vorbiscomment_length(AVDictionary *m, const char *vendor_string) } int ff_vorbiscomment_write(uint8_t **p, AVDictionary **m, - const char *vendor_string) + const char *vendor_string, + AVChapter **chapters, unsigned int nb_chapters) { + int cm_count = 0; bytestream_put_le32(p, strlen(vendor_string)); bytestream_put_buffer(p, vendor_string, strlen(vendor_string)); + if (chapters && nb_chapters) { + for (int i = 0; i < nb_chapters; i++) { + cm_count += av_dict_count(chapters[i]->metadata) + 1; + } + } if (*m) { - int count = av_dict_count(*m); + int count = av_dict_count(*m) + cm_count; AVDictionaryEntry *tag = NULL; bytestream_put_le32(p, count); while ((tag = av_dict_get(*m, "", tag, AV_DICT_IGNORE_SUFFIX))) { @@ -70,6 +88,42 @@ int ff_vorbiscomment_write(uint8_t **p, AVDictionary **m, bytestream_put_byte(p, '='); bytestream_put_buffer(p, tag->value, len2); } + for (int i = 0; i < nb_chapters; i++) { + AVChapter *chp = chapters[i]; + char chapter_time[13]; + char chapter_number[4]; + int h, m, s, ms; + + s = av_rescale(chp->start, chp->time_base.num, chp->time_base.den); + h = s / 3600; + m = (s / 60) % 60; + ms = av_rescale_q(chp->start, chp->time_base, av_make_q( 1, 1000)) % 1000; + s = s % 60; + snprintf(chapter_number, sizeof(chapter_number), "%03d", i); + snprintf(chapter_time, sizeof(chapter_time), "%02d:%02d:%02d.%03d", h, m, s, ms); + bytestream_put_le32(p, 10+1+12); + bytestream_put_buffer(p, "CHAPTER", 7); + bytestream_put_buffer(p, chapter_number, 3); + bytestream_put_byte(p, '='); + bytestream_put_buffer(p, chapter_time, 12); + + tag = NULL; + while ((tag = av_dict_get(chapters[i]->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + int64_t len1 = !strcmp(tag->key, "title") ? 4 : strlen(tag->key); + int64_t len2 = strlen(tag->value); + if (len1+1+len2+10 > UINT32_MAX) + return AVERROR(EINVAL); + bytestream_put_le32(p, 10+len1+1+len2); + bytestream_put_buffer(p, "CHAPTER", 7); + bytestream_put_buffer(p, chapter_number, 3); + if (!strcmp(tag->key, "title")) + bytestream_put_buffer(p, "NAME", 4); + else + bytestream_put_buffer(p, tag->key, len1); + bytestream_put_byte(p, '='); + bytestream_put_buffer(p, tag->value, len2); + } + } } else bytestream_put_le32(p, 0); return 0; diff --git a/libavformat/vorbiscomment.h b/libavformat/vorbiscomment.h index e0d30b14a7..4ff3dd6c27 100644 --- a/libavformat/vorbiscomment.h +++ b/libavformat/vorbiscomment.h @@ -34,7 +34,8 @@ * For no string, set to an empty string. * @return The length in bytes. */ -int64_t ff_vorbiscomment_length(AVDictionary *m, const char *vendor_string); +int64_t ff_vorbiscomment_length(AVDictionary *m, const char *vendor_string, + AVChapter **chapters, unsigned int nb_chapters); /** * Write a VorbisComment into a buffer. The buffer, p, must have enough @@ -45,9 +46,12 @@ int64_t ff_vorbiscomment_length(AVDictionary *m, const char *vendor_string); * @param p The buffer in which to write. * @param m The metadata struct to write. * @param vendor_string The vendor string to write. + * @param chapters The chapters to write. + * @param nb_chapters The number of chapters to write. */ int ff_vorbiscomment_write(uint8_t **p, AVDictionary **m, - const char *vendor_string); + const char *vendor_string, + AVChapter **chapters, unsigned int nb_chapters); extern const AVMetadataConv ff_vorbiscomment_metadata_conv[];