[FFmpeg-devel] [PATCH] avformat/mov: parse 3gpp titl from media or track udta

Jan Ekström jeebjp at gmail.com
Wed Jul 21 00:10:03 EEST 2021


Seems to be:
* Utilized by Handbrake for track titling
* Actually defined as "title for the media"

Definition from 3GPP TS 26.244 follows:

Field               Type                Details                             Value
BoxHeader.Size      Unsigned int(32)                                        BOX_SIZE
BoxHeader.Type      Unsigned int(32)                                        'titl'
BoxHeader.Version   Unsigned int(8)                                         0
BoxHeader.Flags     Bit(24)                                                 0
Pad                 Bit(1)                                                  0
Language            Unsigned int(5)[3]  Packed ISO-639-2/T language code
Title               String              Text of title

Semantics:

Language: declares the language code for the following text. See
ISO 639-2/T for the set of three character codes. Each character
is packed as the difference between its ASCII value and 0x60.

The code is confined to being three lower-case letters, so these
values are strictly positive.

Title: null-terminated string in either UTF-8 or UTF-16 characters,
giving a title information. If UTF-16 is used, the string shall
start with the BYTE ORDER MARK (0xFEFF).
---
 libavformat/mov.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 107 insertions(+)

diff --git a/libavformat/mov.c b/libavformat/mov.c
index 040babed95..9edb3d6596 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -291,6 +291,111 @@ static int mov_metadata_hmmt(MOVContext *c, AVIOContext *pb, unsigned len)
     return 0;
 }
 
+// 3GPP TS 26.244, 8.2 3GPP asset meta data
+static int mov_metadata_titl(MOVContext *c, AVIOContext *pb, unsigned len)
+{
+    AVFormatContext *s = c->fc;
+    int version = -1, ret = AVERROR_BUG;
+    unsigned left_bytes = len, langcode = 0, flags = 100, bom = 0, buf_size = 0;
+    char language[4] = { 0 };
+    AVStream *st = NULL;
+    char *title_buf = NULL;
+    const char key[] = "title";
+
+    // 4 byte FullBox header, 2 byte lang. code, at least 1 byte for string
+    if (len < 4 + 2 + 1) {
+        av_log(s, AV_LOG_ERROR, "3GPP titl box too short!\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (s->nb_streams >= 1)
+        st = s->streams[s->nb_streams-1];
+
+    // FullBox header
+    version = avio_r8(pb);
+    flags   = avio_rb24(pb);
+    left_bytes -= 4;
+
+    if (version != 0 || flags != 0) {
+        av_log(s, AV_LOG_ERROR,
+               "Invalid nonzero version (%d) or flags (%x) for 3GPP titl!\n",
+               version, flags);
+        return AVERROR_INVALIDDATA;
+    }
+
+    langcode = avio_rb16(pb) & ~(1 << 15);
+    if ((ret = ff_mov_lang_to_iso639(langcode, language)) < 0) {
+        av_log(s, AV_LOG_ERROR,
+               "Failed to parse 3GPP titl language code %x: %s!\n",
+               langcode, av_err2str(ret));
+        return ret;
+    }
+
+    left_bytes -= 2;
+
+    if (left_bytes <= 1)
+        // no contents (just null)
+        return 0;
+
+    buf_size = left_bytes + 1;
+    if (!(title_buf = av_mallocz(buf_size))) {
+        av_log(s, AV_LOG_ERROR,
+               "Could not allocate buffer of length %u for parsed 3GPP titl "
+               "title string!\n",
+               left_bytes);
+        return AVERROR(ENOMEM);
+    }
+
+    bom = avio_rb16(pb);
+    left_bytes -= 2;
+
+    if (bom == 0xfeff)
+        avio_get_str16be(pb, left_bytes, title_buf, buf_size);
+    else if (bom == 0xfffe)
+        avio_get_str16le(pb, left_bytes, title_buf, buf_size);
+    else {
+        AV_WB16(title_buf, bom);
+        if (!left_bytes)
+            title_buf[2] = 0;
+        else
+            avio_get_str(pb, left_bytes, title_buf + 2, buf_size - 2);
+    }
+
+    av_log(s, AV_LOG_TRACE, "%s TitlBox(lang: %s, title: %s)\n",
+           st ? "track" : "media",
+           language, title_buf);
+
+    s->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED;
+
+    if (*language && strcmp(language, "und")) {
+        char lang_key[sizeof(key) + 1 + sizeof(language)] = { 0 };
+        snprintf(lang_key, sizeof(lang_key), "%s-%s", key, language);
+
+        if ((ret = av_dict_set(st ? &st->metadata : &s->metadata,
+                               lang_key, title_buf, 0)) < 0) {
+            av_log(s, AV_LOG_ERROR,
+                   "Failed to set %s metadata key %s to value %s: %s!\n",
+                   st ? "track" : "media",
+                   lang_key, title_buf,
+                   av_err2str(ret));
+            goto cleanup;
+        }
+    }
+
+    ret = av_dict_set(st ? &st->metadata : &s->metadata, key, title_buf, 0);
+    if (ret < 0)
+        av_log(s, AV_LOG_ERROR,
+               "Failed to set %s metadata key %s to value %s: %s!\n",
+               st ? "track" : "media",
+               key, title_buf,
+               av_err2str(ret));
+
+cleanup:
+    av_freep(&title_buf);
+
+    return ret;
+}
+
 static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom)
 {
     char tmp_key[AV_FOURCC_MAX_STRING_SIZE] = {0};
@@ -349,6 +454,8 @@ static int mov_read_udta_string(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     case MKTAG( 's','o','s','n'): key = "sort_show";    break;
     case MKTAG( 's','t','i','k'): key = "media_type";
         parse = mov_metadata_int8_no_padding; break;
+    case MKTAG( 't','i','t','l'):
+        return mov_metadata_titl(c, pb, atom.size);
     case MKTAG( 't','r','k','n'): key = "track";
         parse = mov_metadata_track_or_disc_number; break;
     case MKTAG( 't','v','e','n'): key = "episode_id"; break;
-- 
2.31.1



More information about the ffmpeg-devel mailing list