From patchwork Sun Dec 16 21:05:27 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul B Mahol X-Patchwork-Id: 11442 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id A2BB644D285 for ; Sun, 16 Dec 2018 23:05:46 +0200 (EET) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id C7A2368A9C7; Sun, 16 Dec 2018 23:05:46 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wr1-f65.google.com (mail-wr1-f65.google.com [209.85.221.65]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 2BBA268A991 for ; Sun, 16 Dec 2018 23:05:40 +0200 (EET) Received: by mail-wr1-f65.google.com with SMTP id v13so10303631wrw.5 for ; Sun, 16 Dec 2018 13:05:41 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id; bh=w3ZDZMrZcZrVrxZ4rVNqebd9edduiaI9jQgA7hfqAQY=; b=J2lTbivpQojYkfJLt7d24oo4MQvLAErsjjLvq8XJPlxYWH8HAnJjvV+lGoCYHtlnBu C8SAz4QkGrBya4tGuhtEixTp78lZuPvfzNE4g+XBa2PpIh3MVirTNNPvu/pPLSUKh36e Oh/dIlo9XV6y/K9+1gEeDVdW76+dCwlEXBQeDPPA72St3P0mKoRD5pEZJYaDQYhbPI3B FuPddj8+rQ094oYbehbUBPwfkIx6XWH4K1kPY8ak3AhAVFnDHuPXlUFMY7Ayucm/1Yvr FgxtDumfg4ZPQvfalVTeZShJVBbi4o7GGijVf8Qgi4TK2BOh8bCzV713Bdkm3p/YbkGf n01g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id; bh=w3ZDZMrZcZrVrxZ4rVNqebd9edduiaI9jQgA7hfqAQY=; b=dR7tUij8Db/PIJvg1Np70kI5gMKfLMySQZyDiOR/9kyqzk/NZIdQpW26j+MqYlNrHv uKdfNPuqmwxXaf4pzQZ9E93+t2xfHGhoGKdq97Hh7Shs0cvBdh9Iki5VMuJ7NLOFxCeU uSVoogcV+ku5nzjLN1LtQoW9qkumaQ9+Rg5PxNPuUoAF+KmAS8b9tTq2nq9Hh0jluX2q PiJCE559C7vrhjZT1BuifRQlJAJfsVBOObeRCwJau3dnjr4kdmIKIvtPcL1+nVsfBnsU 1zb7U2uJDkZk8XHe6MxN6n3/9+OaDS/n+XkV3qsnYqQz34OhdE7DDhiQzM3VuriZSWY9 /TvA== X-Gm-Message-State: AA+aEWYg0w5ic1YmG04D7IOWvGqErXv+zoqUtRAW76J/wzr955i2IuCO 3HT7a3IpdDdXB5lm01Im/O2LDcT5 X-Google-Smtp-Source: AFSGD/X4xJie3/AhRpQGMHhaK3oDidJpDQ0ot7xwOD1ef7cDMWwwlf8A2fNFEDAidDKiUaHpup5TMQ== X-Received: by 2002:adf:e1c3:: with SMTP id l3mr8837195wri.36.1544994340463; Sun, 16 Dec 2018 13:05:40 -0800 (PST) Received: from localhost.localdomain ([94.250.174.60]) by smtp.gmail.com with ESMTPSA id a1sm8741837wrw.76.2018.12.16.13.05.39 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 16 Dec 2018 13:05:40 -0800 (PST) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Sun, 16 Dec 2018 22:05:27 +0100 Message-Id: <20181216210527.22377-1-onemda@gmail.com> X-Mailer: git-send-email 2.17.1 Subject: [FFmpeg-devel] [PATCH] avformat/vorbiscomment: add support for writing chapters X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Paul B Mahol --- libavformat/flacenc.c | 4 +-- libavformat/matroskaenc.c | 4 +-- libavformat/oggenc.c | 22 +++++++------- libavformat/vorbiscomment.c | 59 +++++++++++++++++++++++++++++++++++-- libavformat/vorbiscomment.h | 8 +++-- 5 files changed, 78 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..5c22c05d6a 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,41 @@ 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; + + h = av_rescale_q(chp->start, chp->time_base, av_make_q(3600, 1)); + m = (av_rescale_q(chp->start, chp->time_base, av_make_q(1, 1)) / 60) % 60; + s = av_rescale_q(chp->start, chp->time_base, av_make_q(1, 1)) % 60; + ms = av_rescale_q(chp->start, chp->time_base, av_make_q(1, 1000)) % 1000; + 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[];