[FFmpeg-devel] avformat/vorbiscomment: add support for writing chapters

Submitted by Paul B Mahol on Dec. 16, 2018, 9:05 p.m.

Details

Message ID 20181216210527.22377-1-onemda@gmail.com
State New
Headers show

Commit Message

Paul B Mahol Dec. 16, 2018, 9:05 p.m.
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 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(-)

Patch hide | download patch | download mbox

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[];