[bTSstream-devel] [Git][videolan/bitstream][master] dvb/si: add the TTML subtitling descriptor

Christophe Massiot (@cmassiot) gitlab at videolan.org
Thu Aug 27 10:56:20 UTC 2026



Christophe Massiot pushed to branch master at VideoLAN / bitstream


Commits:
d4dd59d3 by Kieran Kunhya at 2026-08-27T10:21:52+00:00
dvb/si: add the TTML subtitling descriptor

Extension descriptor 0x20 of ETSI EN 303 560, which signals a DVB TTML
subtitle stream in the PMT: language, subtitle purpose, text-to-speech
suitability, the TTML processor profiles the stream conforms to, and the
optional presentation qualifier.

Only the profile list sits at a fixed offset; the qualifier, the essential
font list and the service description text each move with a count read out of
the descriptor, so validate() walks them rather than checking a single length.

Co-Authored-By: Claude Opus 5 (1M context) <noreply at anthropic.com>

- - - - -


3 changed files:

- + dvb/si/desc_7f20.h
- dvb/si/descs_list.h
- mpeg/psi/descs_print.h


Changes:

=====================================
dvb/si/desc_7f20.h
=====================================
@@ -0,0 +1,237 @@
+/*****************************************************************************
+ * desc_7f20.h: ETSI EN 303 560 Extension descriptor 0x20: TTML subtitling
+ * descriptor
+ *****************************************************************************
+ * Copyright (C) 2026 Open Broadcast Systems Ltd
+ *
+ * Authors: Kieran Kunhya <kierank at obe.tv>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject
+ * to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+/*
+ * Normative references:
+ *  - ETSI EN 303 560 V1.1.1 (2018-05) (TTML subtitling systems)
+ */
+
+#ifndef __BITSTREAM_DVB_DESC_7F20_H__
+#define __BITSTREAM_DVB_DESC_7F20_H__
+
+#include <bitstream/common.h>
+#include <bitstream/mpeg/psi/descriptors.h>
+#include <bitstream/dvb/si/desc_7f.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/*****************************************************************************
+ * Extension descriptor 0x20: TTML subtitling descriptor
+ *****************************************************************************/
+#define DESC7F20_HEADER_SIZE            (DESC7F_HEADER_SIZE + 5)
+#define DESC7F20_PROFILE_SIZE           1
+#define DESC7F20_QUALIFIER_SIZE         4
+#define DESC7F20_FONT_COUNT_SIZE        1
+#define DESC7F20_FONT_SIZE              1
+#define DESC7F20_TEXT_LENGTH_SIZE       1
+
+/* subtitle_purpose, EN 303 560 table 2 */
+#define DESC7F20_PURPOSE_SAME_LANG_DIALOGUE         0x00
+#define DESC7F20_PURPOSE_OTHER_LANG_DIALOGUE        0x01
+#define DESC7F20_PURPOSE_ALL_DIALOGUE               0x02
+#define DESC7F20_PURPOSE_HARD_OF_HEARING            0x10
+#define DESC7F20_PURPOSE_OTHER_LANG_DIALOGUE_HOH    0x11
+#define DESC7F20_PURPOSE_ALL_DIALOGUE_HOH           0x12
+#define DESC7F20_PURPOSE_AUDIO_DESCRIPTION          0x30
+#define DESC7F20_PURPOSE_CONTENT_RELATED_COMMENTARY 0x31
+
+/* TTS_suitability, EN 303 560 table 3 */
+#define DESC7F20_TTS_UNKNOWN                        0x0
+#define DESC7F20_TTS_SUITABLE                       0x1
+#define DESC7F20_TTS_NOT_SUITABLE                   0x2
+
+/* dvb_ttml_profile, EN 303 560 table 5 */
+#define DESC7F20_PROFILE_DEFAULT                    0x00
+#define DESC7F20_PROFILE_IMSC1_TEXT                 0x01
+#define DESC7F20_PROFILE_EBUTTD                     0x02
+
+static inline void desc7f20_init(uint8_t *p_desc)
+{
+    desc7f_init(p_desc);
+    desc_set_length(p_desc, (DESC7F20_HEADER_SIZE - DESC_HEADER_SIZE));
+    desc7f_set_tag_extension(p_desc, DESC7F_TAG_TTML_SUBTITLING);
+    p_desc[7] = 0x30; /* reserved_zero_future_use */
+}
+
+static inline const uint8_t *desc7f20_get_code(const uint8_t *p_desc)
+{
+    return p_desc + DESC7F_HEADER_SIZE;
+}
+
+static inline void desc7f20_set_code(uint8_t *p_desc, const uint8_t p_code[3])
+{
+    memcpy(p_desc + DESC7F_HEADER_SIZE, p_code, 3);
+}
+
+BITSTREAM_GET_SET(desc7f20, subtitle_purpose, uint8, 48, 6);
+BITSTREAM_GET_SET(desc7f20, tts_suitability, uint8, 54, 2);
+BITSTREAM_GET_SET(desc7f20, essential_font_usage_flag, uint8, 56, 1);
+BITSTREAM_GET_SET(desc7f20, qualifier_present_flag, uint8, 57, 1);
+BITSTREAM_GET_SET(desc7f20, profile_count, uint8, 60, 4);
+
+/* Only the profile list is at a fixed offset; every field after it moves with
+ * counts read out of the descriptor itself. */
+static inline uint8_t *desc7f20_get_profile(const uint8_t *p_desc, uint8_t n)
+{
+    if (n >= desc7f20_get_profile_count(p_desc))
+        return NULL;
+    return (uint8_t *)p_desc + DESC7F20_HEADER_SIZE + n * DESC7F20_PROFILE_SIZE;
+}
+
+static inline uint8_t *desc7f20_get_qualifier(const uint8_t *p_desc)
+{
+    if (!desc7f20_get_qualifier_present_flag(p_desc))
+        return NULL;
+    return (uint8_t *)p_desc + DESC7F20_HEADER_SIZE +
+        desc7f20_get_profile_count(p_desc) * DESC7F20_PROFILE_SIZE;
+}
+
+BITSTREAM_GET_SET(desc7f20_qualifier, size, uint8, 0, 4);
+BITSTREAM_GET_SET(desc7f20_qualifier, cadence, uint8, 4, 4);
+BITSTREAM_GET_SET(desc7f20_qualifier, monochrome_flag, uint8, 8, 1);
+BITSTREAM_GET_SET(desc7f20_qualifier, enhanced_accessibility_contrast_flag,
+                  uint8, 9, 1);
+BITSTREAM_GET_SET(desc7f20_qualifier, position, uint8, 10, 4);
+
+static inline uint8_t *desc7f20_get_font_count(const uint8_t *p_desc)
+{
+    if (!desc7f20_get_essential_font_usage_flag(p_desc))
+        return NULL;
+    return (uint8_t *)p_desc + DESC7F20_HEADER_SIZE +
+        desc7f20_get_profile_count(p_desc) * DESC7F20_PROFILE_SIZE +
+        (desc7f20_get_qualifier_present_flag(p_desc) ?
+         DESC7F20_QUALIFIER_SIZE : 0);
+}
+
+static inline uint8_t *desc7f20_get_font(const uint8_t *p_desc, uint8_t n)
+{
+    uint8_t *p_font_count = desc7f20_get_font_count(p_desc);
+    if (!p_font_count || n >= *p_font_count)
+        return NULL;
+    return p_font_count + DESC7F20_FONT_COUNT_SIZE + n * DESC7F20_FONT_SIZE;
+}
+
+BITSTREAM_GET_SET(desc7f20_font, font_id, uint8, 1, 7);
+
+static inline uint8_t *desc7f20_get_text_length(const uint8_t *p_desc)
+{
+    uint8_t *p_font_count = desc7f20_get_font_count(p_desc);
+    if (p_font_count)
+        return p_font_count + DESC7F20_FONT_COUNT_SIZE +
+            *p_font_count * DESC7F20_FONT_SIZE;
+    return (uint8_t *)p_desc + DESC7F20_HEADER_SIZE +
+        desc7f20_get_profile_count(p_desc) * DESC7F20_PROFILE_SIZE +
+        (desc7f20_get_qualifier_present_flag(p_desc) ?
+         DESC7F20_QUALIFIER_SIZE : 0);
+}
+
+static inline uint8_t *desc7f20_get_text(const uint8_t *p_desc,
+                                         uint8_t *pi_length)
+{
+    uint8_t *p_text_length = desc7f20_get_text_length(p_desc);
+    *pi_length = *p_text_length;
+    return p_text_length + DESC7F20_TEXT_LENGTH_SIZE;
+}
+
+static inline bool desc7f20_validate(const uint8_t *p_desc)
+{
+    uint16_t i_desc_length = desc_get_length(p_desc);
+    if (i_desc_length + DESC_HEADER_SIZE < DESC7F20_HEADER_SIZE)
+        return false;
+    /* dvb_ttml_profile_count shall not be set to zero */
+    if (!desc7f20_get_profile_count(p_desc))
+        return false;
+
+    uint16_t i_size = DESC7F20_HEADER_SIZE - DESC_HEADER_SIZE +
+        desc7f20_get_profile_count(p_desc) * DESC7F20_PROFILE_SIZE +
+        (desc7f20_get_qualifier_present_flag(p_desc) ?
+         DESC7F20_QUALIFIER_SIZE : 0);
+    if (desc7f20_get_essential_font_usage_flag(p_desc)) {
+        if (i_size + DESC7F20_FONT_COUNT_SIZE > i_desc_length)
+            return false;
+        i_size += DESC7F20_FONT_COUNT_SIZE +
+            *desc7f20_get_font_count(p_desc) * DESC7F20_FONT_SIZE;
+    }
+    if (i_size + DESC7F20_TEXT_LENGTH_SIZE > i_desc_length)
+        return false;
+    i_size += DESC7F20_TEXT_LENGTH_SIZE + *desc7f20_get_text_length(p_desc);
+    /* trailing reserved_zero_future_use bytes are allowed */
+    return i_size <= i_desc_length;
+}
+
+static inline void desc7f20_print(const uint8_t *p_desc, f_print pf_print,
+                                  void *opaque, print_type_t i_print_type)
+{
+    char psz_code[4];
+    memcpy(psz_code, desc7f20_get_code(p_desc), 3);
+    psz_code[3] = '\0';
+
+    uint8_t i_profile_count = desc7f20_get_profile_count(p_desc);
+    char psz_profiles[i_profile_count * 3 + 1];
+    psz_profiles[0] = '\0';
+    for (uint8_t i = 0; i < i_profile_count; i++)
+        sprintf(psz_profiles + i * 3, i + 1 < i_profile_count ? "%02x " : "%02x",
+                *desc7f20_get_profile(p_desc, i));
+
+    uint8_t i_text_length;
+    const uint8_t *p_text = desc7f20_get_text(p_desc, &i_text_length);
+    char psz_text[i_text_length + 1];
+    memcpy(psz_text, p_text, i_text_length);
+    psz_text[i_text_length] = '\0';
+
+    switch (i_print_type) {
+        case PRINT_XML:
+            pf_print(opaque,
+                     "<TTML_SUBTITLING_DESC language=\"%s\""
+                     " subtitle_purpose=\"%u\" tts_suitability=\"%u\""
+                     " profiles=\"%s\" text=\"%s\"/>",
+                     psz_code,
+                     desc7f20_get_subtitle_purpose(p_desc),
+                     desc7f20_get_tts_suitability(p_desc),
+                     psz_profiles, psz_text);
+            break;
+        default:
+            pf_print(opaque,
+                     "    - desc 7f20 ttml_subtitling language=%s"
+                     " subtitle_purpose=%u tts_suitability=%u"
+                     " profiles=\"%s\" text=\"%s\"",
+                     psz_code,
+                     desc7f20_get_subtitle_purpose(p_desc),
+                     desc7f20_get_tts_suitability(p_desc),
+                     psz_profiles, psz_text);
+    }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif


=====================================
dvb/si/descs_list.h
=====================================
@@ -90,6 +90,7 @@
 #include <bitstream/dvb/si/desc_7f06.h>
 #include <bitstream/dvb/si/desc_7f15.h>
 #include <bitstream/dvb/si/desc_7f19.h>
+#include <bitstream/dvb/si/desc_7f20.h>
 #include <bitstream/dvb/si/desc_83p28.h>
 #include <bitstream/dvb/si/desc_88p28.h>
 


=====================================
mpeg/psi/descs_print.h
=====================================
@@ -209,6 +209,7 @@ static inline void descl_print(uint8_t *p_descl, uint16_t i_length,
                     CASE_DESC(7f06)
                     CASE_DESC(7f15)
                     CASE_DESC(7f19)
+                    CASE_DESC(7f20)
                     default:
                         desc7f_print(p_desc, pf_print, print_opaque,
                                      i_print_type);



View it on GitLab: https://code.videolan.org/videolan/bitstream/-/commit/d4dd59d374a082e789c7db0c0716dc7c5a1d4cb8

-- 
View it on GitLab: https://code.videolan.org/videolan/bitstream/-/commit/d4dd59d374a082e789c7db0c0716dc7c5a1d4cb8
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the biTStream-devel mailing list