From a231d14e52debf5a7e2462a718e407bbbb7d7d6d Mon Sep 17 00:00:00 2001
From: Jerome Martinez <jerome@mediaarea.net>
Date: Sun, 6 Jul 2025 15:12:21 -0400
Subject: [PATCH 3/6] matroskadec/enc: support of TimeCodeXml input, add mk tc
 read (20250710)

---
 libavdevice/decklink_dec.cpp |    6 +-
 libavformat/matroska.h       |    2 +
 libavformat/matroskadec.c    |   27 +
 libavformat/matroskaenc.c    | 1264 +++++++++++++++++++++++++++++++++-
 libavutil/timecode.c         |  104 +++
 libavutil/timecode.h         |   22 +
 6 files changed, 1412 insertions(+), 13 deletions(-)

diff --git a/libavdevice/decklink_dec.cpp b/libavdevice/decklink_dec.cpp
index 418701e4e0..1dacf1b129 100644
--- a/libavdevice/decklink_dec.cpp
+++ b/libavdevice/decklink_dec.cpp
@@ -826,9 +826,9 @@ HRESULT decklink_input_callback::VideoInputFrameArrived(
                         uint8_t* packed_metadata;
 
                         if (av_cmp_q(ctx->video_st->r_frame_rate, av_make_q(60, 1)) < 1) {
-                            uint32_t tc_data = av_timecode_get_smpte_from_framenum(&tcr, 0);
-                            int size = sizeof(uint32_t) * 4;
-                            uint32_t *sd = (uint32_t *)av_packet_new_side_data(&pkt, AV_PKT_DATA_S12M_TIMECODE, size);
+                            uint64_t tc_data = av_timecode_expand_to_64bit(av_timecode_get_smpte_from_framenum(&tcr, 0));
+                            int size = sizeof(uint64_t) * 4;
+                            uint64_t *sd = (uint64_t *)av_packet_new_side_data(&pkt, AV_PKT_DATA_S12M_TIMECODE, size);
 
                             if (sd) {
                                 *sd       = 1;       // one TC
diff --git a/libavformat/matroska.h b/libavformat/matroska.h
index 719f2ef796..d228fed704 100644
--- a/libavformat/matroska.h
+++ b/libavformat/matroska.h
@@ -361,12 +361,14 @@ typedef enum {
   MATROSKA_BLOCK_ADD_ID_TYPE_DEFAULT                = 0,
   MATROSKA_BLOCK_ADD_ID_TYPE_OPAQUE                 = 1,
   MATROSKA_BLOCK_ADD_ID_TYPE_ITU_T_T35              = 4,
+  MATROSKA_BLOCK_ADD_ID_TYPE_SMPTE_12M              = 121,
   MATROSKA_BLOCK_ADD_ID_TYPE_DVCC                   = 0x64766343, // MKBETAG('d','v','c','C')
   MATROSKA_BLOCK_ADD_ID_TYPE_DVVC                   = 0x64767643, // MKBETAG('d','v','v','C')
 } MatroskaBlockAddIDType;
 
 #define MATROSKA_BLOCK_ADD_ID_OPAQUE 1
 #define MATROSKA_BLOCK_ADD_ID_ITU_T_T35 4
+#define MATROSKA_BLOCK_ADD_ID_SMPTE_12M 101
 
 /*
  * Matroska Codec IDs, strings
diff --git a/libavformat/matroskadec.c b/libavformat/matroskadec.c
index da5166319e..b07c92df5a 100644
--- a/libavformat/matroskadec.c
+++ b/libavformat/matroskadec.c
@@ -2530,6 +2530,8 @@ static int mkv_parse_block_addition_mappings(AVFormatContext *s, AVStream *st, M
                     return AVERROR_INVALIDDATA;
             }
             break;
+        case MATROSKA_BLOCK_ADD_ID_TYPE_SMPTE_12M:
+                break;
         case MATROSKA_BLOCK_ADD_ID_TYPE_DVCC:
         case MATROSKA_BLOCK_ADD_ID_TYPE_DVVC:
             if ((ret = mkv_parse_dvcc_dvvc(s, st, track, &mapping->extradata)) < 0)
@@ -3954,6 +3956,31 @@ static int matroska_parse_block_additional(MatroskaDemuxContext *matroska,
 
         return 0;
     }
+    case MATROSKA_BLOCK_ADD_ID_TYPE_SMPTE_12M: {
+        if (size < 8)
+            break; //ignore
+
+        size_t sd_size = 0;
+        uint8_t *sd = av_packet_get_side_data(pkt, AV_PKT_DATA_S12M_TIMECODE, &sd_size);
+        uint64_t count = sd ? *((uint64_t*)sd) : 0;
+        if (!count) {
+            sd_size = sizeof(uint64_t) * 4;
+            sd = av_packet_new_side_data(pkt, AV_PKT_DATA_S12M_TIMECODE, sd_size);
+            count = 0;
+        }
+
+        if (sd) {
+            uint64_t tc = *((uint64_t*)data);
+            av_log(matroska->ctx, AV_LOG_ERROR, "Reading SMPTE timecode from BlockAdditional: 0x%016lX\n", tc);
+
+            uint64_t *sd_64 = (uint64_t*)sd;
+            count++;
+            *sd_64 = count;
+            AV_WB64(sd_64 + count, tc);
+        }
+        
+        return 0;
+    }
     default:
         break;
     }
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c
index 408890fa89..3083dfa3c2 100644
--- a/libavformat/matroskaenc.c
+++ b/libavformat/matroskaenc.c
@@ -60,6 +60,7 @@
 #include "libavutil/rational.h"
 #include "libavutil/samplefmt.h"
 #include "libavutil/stereo3d.h"
+#include "libavutil/timecode.h"
 
 #include "libavcodec/av1.h"
 #include "libavcodec/bytestream.h"
@@ -202,6 +203,7 @@ typedef struct mkv_track {
     int64_t         ts_offset;
     uint64_t        default_duration_low;
     uint64_t        default_duration_high;
+    uint64_t*       timecodes[100];
     /* This callback will be called twice: First with a NULL AVIOContext
      * to return the size of the (Simple)Block's data via size
      * and a second time with the AVIOContext set when the data
@@ -260,6 +262,8 @@ typedef struct MatroskaMuxContext {
     int                 flipped_raw_rgb;
     int                 default_mode;
     int                 move_cues_to_front;
+    
+    const char*         mediatimecode;
 
     uint32_t            segment_uid[4];
 } MatroskaMuxContext;
@@ -1698,6 +1702,1038 @@ fail:
     return AVERROR(EINVAL);
 }
 
+/* Below is the license of the XML parser, and only it */
+
+/* Copyright (c) MediaArea.net SARL
+
+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.
+
+(MIT License)
+
+*/
+
+/*
+ * Tiny Fast Streamable XML parser
+ */
+
+#ifndef TFSXML_H
+#define TFSXML_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+/** -------------------------------------------------------------------------
+        Splitting of the XML content in blocks
+------------------------------------------------------------------------- **/
+
+/** Base structure for the parser
+ *
+ * @param buf  pointer to the buffer which has the data
+ * @param len  length of the buffer
+ * @param flags  private data, can change between versions of the library
+ *
+ * @note after init, priv should not be directly used (data may be something else than a buf/len pair)
+ */
+typedef struct tfsxml_string {
+    const char* buf;
+    unsigned    len;
+    unsigned    flags;
+} tfsxml_string;
+
+/** Initialize the parser
+ *
+ * @param priv      pointer to a tfsxml_string dedicated instance, private use by the parser
+ * @param buf       pointer to start of the buffer
+ * @param len       length of the buffer
+ * @param version   API version supported by the client
+ *
+ * @note after init, priv should not be directly used (data may be something else than a buf/len pair)
+ */
+int tfsxml_init(tfsxml_string* priv, const void* buf, unsigned len, unsigned version);
+
+/** Get next element or other content except an element value
+ *
+ * @param priv  pointer to a tfsxml_string dedicated instance, private use by the parser
+ * @param n  pointer to tfsxml_string instance receiving the element name or other content
+ *
+ * @return  0 if an element is available at the current level
+ *          -1 if no element is available at the current level
+ *          1 if need of more data
+ */
+int tfsxml_next(tfsxml_string* priv, tfsxml_string* n);
+
+/** Get next attribute of an element
+ *
+ * @param priv  pointer to a tfsxml_string dedicated instance, private use by the parser
+ * @param n  pointer to tfsxml_string instance receiving the attribute name
+ * @param v  pointer to tfsxml_string instance receiving the attribute value
+ *
+ * @return  0 if an attribute is available for the current element
+ *          -1 if no more attribute is available for the current element
+ *          1 if need of more data
+ */
+int tfsxml_attr(tfsxml_string* priv, tfsxml_string* n, tfsxml_string* v);
+
+/** Get next value of an element
+ *
+ * @param priv  pointer to a tfsxml_string dedicated instance, private use by the parser
+ * @param v  pointer to tfsxml_string instance receiving the value
+ *
+ * @return  0 if a value is available for the current element
+ *          -1 if no value is available for the current element
+ *          1 if need of more data
+ *
+ * @note if this element has sub-elements, the whole content (all sub-elements) are provided
+ */
+int tfsxml_value(tfsxml_string* priv, tfsxml_string* v);
+
+/** Enter in the element in order to get sub-elements
+ *
+ * @param priv  pointer to a tfsxml_string dedicated instance, private use by the parser
+ *
+ * @return  0 if it is possible to enter in an element
+ *          -1 if it is not possible to enter in an element
+ *          1 if need of more data
+ */
+int tfsxml_enter(tfsxml_string* priv);
+
+/** Leave the current parsed element (going to upper level)
+ *
+ * @param priv  pointer to a tfsxml_string dedicated instance, private use by the parser
+ *
+ * @return  0 if it is possible to leave an element
+ *          -1 if it is not possible to leave an element
+ *          1 if need of more data
+ */
+int tfsxml_leave(tfsxml_string* priv);
+
+/** -------------------------------------------------------------------------
+        Handling the encoded XML block
+------------------------------------------------------------------------- **/
+
+/** Convert encoded XML block (attribute or value) to real content (encoded in UTF-8)
+ *
+ * @param s         opaque data transmitted to func
+ * @param v         XML content to decode
+ * @param func      append function that will receive blocks of decoded content
+ *
+ * @param func_s    string to append to
+ * @param func_buf  decoded content
+ * @param func_len  length of the decoded content
+ *
+ * @note see tfsxml_decode_string C++ function for an example of usage
+ */
+void tfsxml_decode(void* s, const tfsxml_string* v, void (*func)(void* func_s, const char* func_buf, unsigned func_len));
+
+/** -------------------------------------------------------------------------
+        Helper functions related to tfsxml_string
+------------------------------------------------------------------------- **/
+
+/** Compare two strings
+ *
+ * @param a  string to compare
+ * @param b  string to compare
+ *
+ * @note similar to C strcmp function
+ */
+int tfsxml_strcmp_charp(tfsxml_string a, const char* b);
+
+/** Locate substring
+ *
+ * @param a  string to be scanned
+ * @param b  substring to match
+ *
+ * @note similar to C strstr function
+ */
+tfsxml_string tfsxml_strstr_charp(tfsxml_string a, const char* b);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#ifdef __cplusplus
+#include <string>
+
+static void tfsxml_decode_string(void* d, const char* buf, unsigned len) { ((std::string*)d)->append(buf, len); }
+
+/** Convert encoded XML block (attribute or value) to real content (encoded in UTF-8)
+ *
+ * @param s  string which will be appended with the decoded content
+ * @param b  XML content to decode
+ */
+static void tfsxml_decode(std::string& s, const tfsxml_string& b) { tfsxml_decode(&s, &b, tfsxml_decode_string); }
+
+/** Convert encoded XML block (attribute or value) to real content (encoded in UTF-8)
+ *
+ * @param b  XML content to decode
+ * @return  decoded content
+ */
+static std::string tfsxml_decode(const tfsxml_string& b) { std::string s; tfsxml_decode(&s, &b, tfsxml_decode_string); return s; }
+
+#endif /* __cplusplus */
+
+#endif
+
+#ifndef NULL
+    #ifdef __cplusplus
+        #define NULL 0
+    #else
+        #define NULL ((void *)0)
+    #endif
+#endif
+#if !defined(inline)
+    #define inline
+#endif
+
+/*
+ * priv flags :
+ * 0: is inside an element header
+ * 1: previous element is open (potentially with sub elements)
+ */
+
+/*
+ * attribute / value flags :
+ * 0: must be decoded
+ */
+
+static inline void set_flag(tfsxml_string* priv, int offset) {
+    priv->flags |= (1 << offset);
+}
+
+static inline int get_flag(tfsxml_string* priv, int offset) {
+    return priv->flags & (1 << offset);
+}
+
+static inline void unset_flag(tfsxml_string* priv, int offset) {
+    priv->flags &= ~(1 << offset);
+}
+
+#define set_is_in_element_header() set_flag(priv, 0)
+#define get_is_in_element_header() get_flag(priv, 0)
+#define unset_is_in_element_header() unset_flag(priv, 0)
+#define set_previous_element_is_open() set_flag(priv, 1)
+#define get_previous_element_is_open() get_flag(priv, 1)
+#define unset_previous_element_is_open() unset_flag(priv, 1)
+
+#define set_must_be_decoded() set_flag(v, 0)
+
+static inline void set_level(tfsxml_string* priv, int level)
+{
+    const int offset = (sizeof(priv->flags) - 1) * 8;
+    priv->flags <<= 8;
+    priv->flags >>= 8;
+    priv->flags |= level << offset;
+}
+
+static inline void get_level(tfsxml_string* priv, int* level)
+{
+    const int offset = (sizeof(priv->flags) - 1) * 8;
+    *level = priv->flags >> offset;
+}
+
+static inline void next_char(tfsxml_string* priv) {
+    priv->buf++;
+    priv->len--;
+}
+
+static int tfsxml_leave_element_header(tfsxml_string* priv) {
+    /* Skip attributes */
+    tfsxml_string n, v;
+    for (;;) {
+        int result = tfsxml_attr(priv, &n, &v);
+        switch (result) {
+            case -1: {
+                return 0;
+            }
+            case 1: {
+                return 1;
+            }
+            default: {
+            }
+        }
+    }
+}
+
+int tfsxml_strcmp_charp(tfsxml_string a, const char* b) {
+    /* Compare char per char and return the difference if chars are no equal */
+    for (; a.len && *b; a.buf++, a.len--, b++) {
+        int c = *a.buf - *b;
+        if (c)
+            return c;
+    }
+
+    if (!a.len && !*b)
+        return 0; /* All equal */
+    else if (*b)
+        return -*b; /* b is longer than a */
+    else
+        return *a.buf; /* a is longer than b */
+}
+
+tfsxml_string tfsxml_strstr_charp(tfsxml_string a, const char* b) {
+    /* Iterate string to be scanned */
+    for (; a.len; a.buf++, a.len--) {
+        const char* buf = a.buf;
+        int len = a.len;
+        const char* bb = b;
+        /* Compare char per char */
+        for (; len && *bb; buf++, len--, bb++) {
+            char c = *buf - *bb;
+            if (c)
+                break;
+        }
+        if (!len || *bb) {
+            return a;
+        }
+    }
+    a.buf = NULL;
+    a.len = 0;
+    return a;
+}
+
+int tfsxml_init(tfsxml_string* priv, const void* buf, unsigned len, unsigned version) {
+    const char* buf_8 = (const char*)buf;
+
+    if (version != 0) {
+        return -1;
+    }
+
+    /* BOM detection */
+    if (len > 3
+        && (unsigned char)buf_8[0] == 0xEF
+        && (unsigned char)buf_8[1] == 0xBB
+        && (unsigned char)buf_8[2] == 0xBF) {
+        buf_8 += 3;
+        len -= 3;
+    }
+
+    /* Start detection */
+    if (len < 1
+        || buf_8[0] != '<') {
+        return -1;
+    }
+
+    /* Init */
+    priv->buf = (const char*)buf;
+    priv->len = len;
+    priv->flags = 0;
+
+    return 0;
+}
+
+int tfsxml_next(tfsxml_string* priv, tfsxml_string* n) {
+    tfsxml_string priv_bak;
+    int level;
+
+    get_level(priv, &level);
+
+    /* Exiting previous element header analysis if needed */
+    if (!level && get_is_in_element_header()) {
+        int result = tfsxml_leave_element_header(priv);
+        if (result) {
+            return result;
+        }
+    }
+
+    /* Leaving previous element content if needed */
+    if (level || get_previous_element_is_open()) {
+        if (!level && get_previous_element_is_open()) {
+            level++;
+            set_level(priv, level);
+        }
+        int result = tfsxml_leave(priv);
+        if (result) {
+            return result;
+        }
+        get_level(priv, &level);
+    }
+
+    priv_bak = *priv;
+    while (priv->len) {
+        switch (*priv->buf) {
+            case '<': {
+                if (priv->len == 1) {
+                    *priv = priv_bak;
+                    set_level(priv, level);
+                    return 1;
+                }
+                if (priv->buf[1] == '/') {
+                    while (priv->len && *priv->buf != '>') {
+                        next_char(priv);
+                    }
+                    if (!priv->len) {
+                        *priv = priv_bak;
+                        set_level(priv, level);
+                        return 1;
+                    }
+                    if (!level) {
+                        next_char(priv);
+                        unset_previous_element_is_open();
+                        set_level(priv, level);
+                        return -1;
+                    }
+                    level--;
+                    break;
+                }
+                if (priv->buf[1] == '?') {
+                    next_char(priv);
+                    n->buf = priv->buf;
+                    while (priv->len && *priv->buf != '>') {
+                        next_char(priv);
+                    }
+                    if (!priv->len) {
+                        *priv = priv_bak;
+                        set_level(priv, level);
+                        return 1;
+                    }
+                    n->len = priv->buf - n->buf;
+                    next_char(priv);
+                    unset_previous_element_is_open();
+                    set_level(priv, 0);
+                    return 0;
+                }
+                if (priv->buf[1] == '!') {
+                    unsigned long long probe = 0;
+                    int i;
+                    if (priv->len <= 8) {
+                        *priv = priv_bak;
+                        set_level(priv, level);
+                        return 1;
+                    }
+                    for (i = 2; i <= 8; i++) {
+                        probe <<= 8;
+                        probe |= priv->buf[i];
+                    }
+                    if (probe == 0x5B43444154415BULL) { /* "<![CDATA[" */
+                        probe = 0;
+                        priv->buf += 9;
+                        priv->len -= 9;
+                        while (priv->len) {
+                            probe &= 0xFFFF;
+                            probe <<= 8;
+                            probe |= *priv->buf;
+                            if (probe == 0x5D5D3EULL) { /* "]]>" */
+                                break;
+                            }
+                            next_char(priv);
+                        }
+                        if (!priv->len) {
+                            *priv = priv_bak;
+                            set_level(priv, level);
+                            return 1;
+                        }
+                        break;
+                    }
+                    if ((probe >> 40) == 0x2D2D) { /* "<!--" */
+                        n->buf = priv->buf;
+                        probe = 0;
+                        priv->buf += 4;
+                        priv->len -= 4;
+                        while (priv->len) {
+                            probe &= 0xFFFF;
+                            probe <<= 8;
+                            probe |= *priv->buf;
+                            if (probe == 0x2D2D3EULL) /* "-->" */
+                                break;
+                            next_char(priv);
+                        }
+                        if (!priv->len) {
+                            *priv = priv_bak;
+                            set_level(priv, level);
+                            return 1;
+                        }
+                        n->len = priv->buf - n->buf;
+                        next_char(priv);
+                        unset_previous_element_is_open();
+                        set_level(priv, 0);
+                        return 0;
+                    }
+                    while (priv->len && *priv->buf != '>') {
+                        next_char(priv);
+                    }
+                    if (!priv->len) {
+                        *priv = priv_bak;
+                        set_level(priv, level);
+                        return 1;
+                    }
+                    n->buf = priv_bak.buf;
+                    n->len = priv->buf - n->buf;
+                    next_char(priv);
+                    unset_previous_element_is_open();
+                    set_level(priv, 0);
+                    return 0;
+                }
+                if (!level) {
+                    next_char(priv);
+                    n->buf = priv->buf;
+                    for (;;) {
+                        if (!priv->len) {
+                            *priv = priv_bak;
+                            set_level(priv, level);
+                            return 1;
+                        }
+
+                        switch (*priv->buf) {
+                            case '\n':
+                            case '\t':
+                            case '\r':
+                            case ' ':
+                            case '/':
+                            case '>':
+                                n->len = priv->buf - n->buf;
+                                set_is_in_element_header();
+                                set_previous_element_is_open();
+                                set_level(priv, 0);
+                                return 0;
+                            default: {
+                            }
+                        }
+                        next_char(priv);
+                    }
+                }
+                level++;
+                break;
+            }
+            default: {
+            }
+        }
+        next_char(priv);
+    }
+    set_level(priv, level);
+    return 1;
+}
+
+int tfsxml_attr(tfsxml_string* priv, tfsxml_string* n, tfsxml_string* v) {
+    if (!get_is_in_element_header()) {
+        return -1;
+    }
+
+    v->flags = 0;
+    tfsxml_string priv_bak = *priv;
+    while (priv->len) {
+        switch (*priv->buf) {
+            case '/': {
+                unset_previous_element_is_open();
+                //fall through
+            }
+            case '\n':
+            case '\t':
+            case '\r':
+            case ' ': {
+                break;
+            }
+            case '>': {
+                next_char(priv);
+                unset_is_in_element_header();
+                return -1;
+            }
+            default: {
+                if (!get_previous_element_is_open()) {
+                    break; // Junk after "/", ignoring
+                }
+
+                /* Attribute */
+                n->buf = priv->buf;
+                while (priv->len && *priv->buf != '=') {
+                    next_char(priv);
+                }
+                if (!priv->len) {
+                    *priv = priv_bak;
+                    return 1;
+                }
+                n->len = priv->buf - n->buf;
+                next_char(priv);
+
+                /* Value */
+                const char quote = *priv->buf;
+                if (!priv->len) {
+                    *priv = priv_bak;
+                    return 1;
+                }
+                next_char(priv);
+                v->buf = priv->buf;
+                while (priv->len && *priv->buf != quote) {
+                    if (*priv->buf == '&') {
+                        set_flag(v, 0);
+                    }
+                    next_char(priv);
+                }
+                v->len = priv->buf - v->buf;
+                if (!priv->len) {
+                    *priv = priv_bak;
+                    return 1;
+                }
+                next_char(priv);
+                return 0;
+            }
+        }
+        next_char(priv);
+    }
+    *priv = priv_bak;
+    return 1;
+}
+
+int tfsxml_value(tfsxml_string* priv, tfsxml_string* v) {
+    tfsxml_string priv_bak;
+    unsigned len_sav;
+
+    /* Exiting previous element header analysis if needed */
+    if (get_is_in_element_header()) {
+        int result = tfsxml_leave_element_header(priv);
+        if (result) {
+            return result;
+        }
+
+        /* Previous element must not be finished */
+        if (!get_previous_element_is_open()) {
+            return -1;
+        }
+    }
+
+    priv_bak = *priv;
+    len_sav = priv->len;
+    v->flags = 0;
+    while (priv->len) {
+        switch (*priv->buf) {
+            case '&': {
+                set_must_be_decoded();
+                break;
+            }
+            case '<': {
+                if (priv->len == 1) {
+                    *priv = priv_bak;
+                    return 1;
+                }
+                if (priv->buf[1] == '!') {
+                    if (priv->len <= 8) {
+                        *priv = priv_bak;
+                        return 1;
+                    }
+                    unsigned long long probe = 0;
+                    int i;
+                    for (i = 2; i <= 8; i++) {
+                        probe <<= 8;
+                        probe |= priv->buf[i];
+                    }
+                    if (probe == 0x5B43444154415BULL) { /* "<![CDATA[" */
+                        set_must_be_decoded();
+                        probe = 0;
+                        priv->buf += 9;
+                        priv->len -= 9;
+                        while (priv->len) {
+                            probe &= 0xFFFF;
+                            probe <<= 8;
+                            probe |= *priv->buf;
+                            if (probe == 0x5D5D3EULL) { /* "]]>" */
+                                break;
+                            }
+                            next_char(priv);
+                        }
+                        if (!priv->len) {
+                            *priv = priv_bak;
+                            return 1;
+                        }
+                        break;
+                    }
+                }
+                v->len = len_sav - priv->len;
+                v->buf = priv->buf - v->len;
+                set_previous_element_is_open();
+                set_level(priv, 1);
+                int result = tfsxml_leave(priv);
+                if (result) {
+                    *priv = priv_bak;
+                    return result;
+                }
+                return 0;
+            }
+            default: {
+            }
+        }
+        next_char(priv);
+    }
+    *priv = priv_bak;
+    return 1;
+}
+
+int tfsxml_enter(tfsxml_string* priv) {
+    /* Exiting previous element header if needed */
+    if (get_is_in_element_header()) {
+        int result = tfsxml_leave_element_header(priv);
+        if (result) {
+            return result;
+        }
+    }
+
+    /* Previous element must not be finished */
+    if (!get_previous_element_is_open()) {
+        return -1;
+    }
+
+    unset_previous_element_is_open();
+    return 0;
+}
+
+int tfsxml_leave(tfsxml_string* priv) {
+    tfsxml_string priv_bak;
+    unsigned level;
+
+    get_level(priv, &level);
+
+    /* Exiting previous element header analysis if needed */
+    if (get_is_in_element_header()) {
+        int result = tfsxml_leave_element_header(priv);
+        if (result) {
+            return result;
+        }
+        if (get_previous_element_is_open()) {
+            level++;
+        }
+    }
+
+    set_previous_element_is_open();
+    priv_bak = *priv;
+    while (priv->len) {
+        switch (*priv->buf) {
+            case '<': {
+                priv_bak = *priv;
+                if (priv->len == 1) {
+                    *priv = priv_bak;
+                    set_level(priv, level);
+                    return 1;
+                }
+                if (priv->buf[1] == '/') {
+                    while (priv->len && *priv->buf != '>') {
+                        next_char(priv);
+                    }
+                    if (!priv->len) {
+                        *priv = priv_bak;
+                        set_level(priv, level);
+                        return 1;
+                    }
+                    level--;
+                    if (!level) {
+                        next_char(priv);
+                        unset_previous_element_is_open();
+                        set_level(priv, 0);
+                        return 0;
+                    }
+                    priv_bak = *priv;
+                    break;
+                }
+                if (priv->buf[1] == '?') {
+                    while (priv->len && *priv->buf != '>') {
+                        next_char(priv);
+                    }
+                    if (!priv->len) {
+                        *priv = priv_bak;
+                        set_level(priv, level);
+                        return 1;
+                    }
+                    unset_previous_element_is_open();
+                    break;
+                }
+                if (priv->buf[1] == '!') {
+                    unsigned long long probe = 0;
+                    int i;
+                    if (priv->len <= 8) {
+                        *priv = priv_bak;
+                        set_level(priv, level);
+                        return 1;
+                    }
+                    for (i = 2; i <= 8; i++) {
+                        probe <<= 8;
+                        probe |= priv->buf[i];
+                    }
+                    if (probe == 0x5B43444154415BULL) { /* "<![CDATA[" */
+                        probe = 0;
+                        priv->buf += 9;
+                        priv->len -= 9;
+                        while (priv->len) {
+                            probe &= 0xFFFF;
+                            probe <<= 8;
+                            probe |= *priv->buf;
+                            if (probe == 0x5D5D3EULL) { /* "]]>" */
+                                break;
+                            }
+                            next_char(priv);
+                        }
+                        if (!priv->len) {
+                            *priv = priv_bak;
+                            set_level(priv, level);
+                            return 1;
+                        }
+                        break;
+                    }
+                    if ((probe >> 40) == 0x2D2D) { /* "<!--" */
+                        probe = 0;
+                        priv->buf += 4;
+                        priv->len -= 4;
+                        while (priv->len) {
+                            probe &= 0xFFFF;
+                            probe <<= 8;
+                            probe |= *priv->buf;
+                            if (probe == 0x2D2D3EULL) /* "-->" */
+                                break;
+                            next_char(priv);
+                        }
+                        if (!priv->len) {
+                            *priv = priv_bak;
+                            set_level(priv, level);
+                            return 1;
+                        }
+                        break;
+                    }
+                    while (priv->len && *priv->buf != '>') {
+                        next_char(priv);
+                    }
+                    if (!priv->len) {
+                        *priv = priv_bak;
+                        set_level(priv, level);
+                        return 1;
+                    }
+                    next_char(priv);
+                    break;
+                }
+                for (;;) {
+                    int split;
+
+                    if (!priv->len) {
+                        *priv = priv_bak;
+                        set_level(priv, level);
+                        return 1;
+                    }
+
+                    split = 0;
+                    switch (*priv->buf) {
+                        case '\n':
+                        case '\t':
+                        case '\r':
+                        case ' ':
+                        case '/':
+                        case '>': {
+                            set_is_in_element_header();
+                            set_previous_element_is_open();
+                            split = 1;
+                            break;
+                        }
+                        default: {
+                        }
+                    }
+                    if (split)
+                        break;
+                    next_char(priv);
+                }
+                int result = tfsxml_leave_element_header(priv);
+                if (result) {
+                    set_is_in_element_header();
+                    set_previous_element_is_open();
+                    set_level(priv, level);
+                    return result;
+                }
+                if (get_previous_element_is_open()) {
+                    level++;
+                    set_previous_element_is_open();
+                }
+                continue;
+            }
+            default: {
+            }
+        }
+        next_char(priv);
+    }
+    set_level(priv, level);
+    return 1;
+}
+
+static const char* const tfsxml_decode_markup[2] = {
+    "amp\0apos\0gt\0lt\0quot",
+    "&'><\"",
+};
+
+void tfsxml_decode(void* s, const tfsxml_string* v, void (*func)(void*, const char*, unsigned)) {
+    const char* buf_begin;
+    const char* buf = v->buf;
+    unsigned len = v->len;
+
+    if (!(v->flags & 1)) {
+        func(s, buf, len);
+        return;
+    }
+
+    buf_begin = buf;
+    while (len) {
+        if (*buf == '&') {
+            const char* buf_end = buf;
+            int len_end = len;
+            while (len_end && *buf_end != ';') {
+                buf_end++;
+                len_end--;
+            }
+            if (len_end) {
+                const char* buf_beg = buf + 1;
+                unsigned len_beg = buf_end - buf_beg;
+                if (len_beg && *buf_beg == '#') {
+                    unsigned long value = 0;
+                    buf_beg++;
+                    len_beg--;
+                    if (*buf_beg == 'x' || *buf_beg == 'X') {
+                        buf_beg++;
+                        len_beg--;
+                        while (len_beg) {
+                            char c = *buf_beg++;
+                            len_beg--;
+                            value <<= 4;
+                            if (value >= 0x110000) {
+                                value = -1;
+                                break;
+                            }
+                            if (c >= '0' && c <= '9')
+                                value |= c - '0';
+                            else if (c >= 'A' && c <= 'F')
+                                value |= c - ('A' - 10);
+                            else if (c >= 'a' && c <= 'f')
+                                value |= c - ('a' - 10);
+                            else {
+                                value = -1;
+                                break;
+                            }
+                        }
+                    }
+                    else {
+                        while (len_beg) {
+                            char c = *buf_beg++;
+                            len_beg--;
+                            if (c < '0' || c > '9') {
+                                value = -1;
+                                break;
+                            }
+                            value *= 10;
+                            value += c - '0';
+                            if (value >= 0x110000) {
+                                value = -1;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (value != (unsigned long)-1) {
+                        char utf8[4];
+                        func(s, buf_begin, buf - buf_begin);
+                        if (value < 0x0080) {
+                            utf8[0] = (char)value;
+                            func(s, utf8, 1);
+                        }
+                        else if (value < 0x0800) {
+                            utf8[0] = 0xC0 | (char)(value >> 6);
+                            utf8[1] = 0x80 | (char)(value & 0x3F);
+                            func(s, utf8, 2);
+                        }
+                        else if (value < 0x10000) {
+                            utf8[0] = 0xE0 | (char)((value >> 12));
+                            utf8[1] = 0x80 | (char)((value >> 6) & 0x3F);
+                            utf8[2] = 0x80 | (char)((value & 0x3F));
+                            func(s, utf8, 3);
+                        }
+                        else if (value < 0x110000) {
+                            utf8[0] = 0xF0 | (char)((value >> 18));
+                            utf8[1] = 0x80 | (char)((value >> 12) & 0x3F);
+                            utf8[2] = 0x80 | (char)((value >> 6) & 0x3F);
+                            utf8[3] = 0x80 | (char)((value & 0x3F));
+                            func(s, utf8, 4);
+                        }
+
+                        len -= buf_end - buf;
+                        buf = buf_end;
+                        buf_begin = buf_end + 1;
+                    }
+                }
+                else {
+                    const char* const buf_beg_sav = buf_beg;
+                    const char* replaced = tfsxml_decode_markup[0];
+                    const char* replace_bys = tfsxml_decode_markup[1];
+                    for (;;) {
+                        char replace_by = *replace_bys++;
+                        if (!replace_by) {
+                            break;
+                        }
+
+                        while (*replaced) {
+                            if (buf_beg == buf_end || !*replaced || *replaced != *buf_beg)
+                                break;
+                            replaced++;
+                            buf_beg++;
+                        }
+                        if (buf_beg == buf_end && !*replaced) {
+                            func(s, buf_begin, buf - buf_begin);
+                            func(s, &replace_by, 1);
+                            len -= buf_end - buf;
+                            buf = buf_end;
+                            buf_begin = buf_end + 1;
+                            break;
+                        }
+                        buf_beg = buf_beg_sav;
+                        while (*replaced) {
+                            replaced++;
+                        }
+                        replaced++;
+                    }
+                }
+            }
+        }
+        if (*buf == '<' && len > 8) {
+            unsigned long long probe = 0;
+            int i;
+            for (i = 1; i <= 8; i++) {
+                probe <<= 8;
+                probe |= buf[i];
+            }
+            if (probe == 0x215B43444154415BULL) { /* "<![CDATA[" */
+                func(s, buf_begin, buf - buf_begin);
+                probe = 0;
+                buf += 9;
+                len -= 9;
+                buf_begin = buf;
+                while (len) {
+                    probe &= 0xFFFF;
+                    probe <<= 8;
+                    probe |= *buf;
+                    if (probe == 0x5D5D3EULL) { /* "]]>" */
+                        break;
+                    }
+                    buf++;
+                    len--;
+                }
+                func(s, buf_begin, buf - 2 - buf_begin);
+                buf_begin = buf + 1;
+            }
+        }
+        buf++;
+        len--;
+    }
+    func(s, buf_begin, buf - buf_begin);
+}
+
+
+
 static void mkv_write_blockadditionmapping(AVFormatContext *s, const MatroskaMuxContext *mkv,
                                            const AVCodecParameters *par, AVIOContext *pb,
                                            mkv_track *track, const AVStream *st)
@@ -1705,7 +2741,143 @@ static void mkv_write_blockadditionmapping(AVFormatContext *s, const MatroskaMux
 #if CONFIG_MATROSKA_MUXER
     const AVDOVIDecoderConfigurationRecord *dovi;
     const AVPacketSideData *sd;
-
+    
+    size_t timecodes_pos = 0;
+    if (mkv->mediatimecode && par->codec_type == AVMEDIA_TYPE_VIDEO) {
+        if (s->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) {
+            av_log(s, AV_LOG_ERROR, "mediatimecode not enabled, -strict experimental needed for this experimental feature.\n");
+        } else {
+            FILE* f;
+            f = fopen (mkv->mediatimecode, "rb");
+            if (f == NULL) {
+                av_log(s, AV_LOG_ERROR, "mediatimecode not enabled, can not open %s.\n", mkv->mediatimecode);
+            } else {
+                int size, size_read;
+                char* buffer;
+                tfsxml_string p, v, a;
+                fseek(f, 0L, SEEK_END);
+                size=ftell(f);
+                fseek(f, 0, SEEK_SET);
+                buffer = (char*) malloc(size);
+                size_read = fread((void*)buffer, 1, size, f);
+                if (size_read != size) {
+                    av_log(s, AV_LOG_ERROR, "mediatimecode not enabled, can not read %s, %d bytes read.\n", mkv->mediatimecode, size_read);
+                }
+                if (!tfsxml_init(&p, buffer, size, 0)) {
+                    for (;;) {
+                        if (tfsxml_next(&p, &v)) 
+                            break;
+                        if (!tfsxml_strcmp_charp(v, "MediaTimecode")) {
+                            if (tfsxml_enter(&p))
+                                continue;
+                            for (;;) {
+                                if (tfsxml_next(&p, &v)) 
+                                    break;
+                                if (!tfsxml_strcmp_charp(v, "media")) {
+                                    if (tfsxml_enter(&p)) {
+                                        continue;
+                                    }
+                                    for (;;) {
+                                        if (timecodes_pos >= 100 || tfsxml_next(&p, &v)) 
+                                            break;
+                                        if (!tfsxml_strcmp_charp(v, "timecode_stream")) {
+                                            uint64_t* values;
+                                            values = (uint64_t*) malloc(sizeof(uint64_t) * (2 + size));
+                                            values[0] = 2;
+                                            values[1] = 2;
+                                            for (;;) {
+                                                if (tfsxml_attr(&p, &a, &v)) 
+                                                    break;
+                                                if (!tfsxml_strcmp_charp(a, "start_tc")) {
+                                                    if (v.len == 11) {
+                                                        values[values[0]]=
+                                                          ((uint64_t)v.buf[0] - '0') << 56
+                                                        | ((uint64_t)v.buf[1] - '0') << 48
+                                                        | ((uint64_t)v.buf[3] - '0') << 40
+                                                        | ((uint64_t)v.buf[4] - '0') << 32
+                                                        | ((uint64_t)v.buf[6] - '0') << 24
+                                                        | ((uint64_t)v.buf[7] - '0') << 16
+                                                        | ((uint64_t)v.buf[8] == '?' ? 1 : 0) << 10
+                                                        | ((uint64_t)v.buf[9] - '0') <<  8
+                                                        | ((uint64_t)v.buf[10] - '0')     ;
+                                                        values[0]++;
+                                                    }
+                                                }
+                                            }
+                                            if (!tfsxml_enter(&p)) {
+                                                for (;;) {
+                                                    if (tfsxml_next(&p, &v)) 
+                                                        break;
+                                                    if (!tfsxml_strcmp_charp(v, "tc")) {
+                                                        for (;;) {
+                                                            if (tfsxml_attr(&p, &a, &v)) 
+                                                                break;
+                                                            if (!tfsxml_strcmp_charp(a, "v")) {
+                                                                if (v.len == 11) {
+                                                                    values[values[0]]=
+                                                                      ((uint64_t)v.buf[0] - '0') << 56
+                                                                    | ((uint64_t)v.buf[1] - '0') << 48
+                                                                    | ((uint64_t)v.buf[3] - '0') << 40
+                                                                    | ((uint64_t)v.buf[4] - '0') << 32
+                                                                    | ((uint64_t)v.buf[6] - '0') << 24
+                                                                    | ((uint64_t)v.buf[7] - '0') << 16
+                                                                    | ((uint64_t)v.buf[8] == '?' ? 1 : 0) << 10
+                                                                    | ((uint64_t)v.buf[9] - '0') <<  8
+                                                                    | ((uint64_t)v.buf[10] - '0')     ;
+                                                                    values[0]++;
+                                                                }
+                                                            }
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                            track->timecodes[timecodes_pos++] = values;
+                                            static const char* tc_string_template = "00:00:00:00";
+                                            if (values[0] > 2) {
+                                                char tc_string[12];
+                                                char tc_string2[12];
+                                                uint64_t value2 = values[values[0] - 1];
+                                                memcpy (tc_string, tc_string_template, 12);
+                                                memcpy (tc_string2, tc_string_template, 12);
+                                                tc_string[ 0] += (values[2] >> 56) & 0xFF;
+                                                tc_string[ 1] += (values[2] >> 48) & 0xFF;
+                                                tc_string[ 3] += (values[2] >> 40) & 0xFF;
+                                                tc_string[ 4] += (values[2] >> 32) & 0xFF;
+                                                tc_string[ 6] += (values[2] >> 24) & 0xFF;
+                                                tc_string[ 7] += (values[2] >> 16) & 0xFF;
+                                                if ((values[2] >> 10) & 1)
+                                                    tc_string[ 8] = ';';
+                                                tc_string[ 9] += (values[2] >>  8) & 0x3F;
+                                                tc_string[10] += (values[2]      ) & 0xFF;
+                                                tc_string2[ 0] += (value2 >> 56) & 0xFF;
+                                                tc_string2[ 1] += (value2 >> 48) & 0xFF;
+                                                tc_string2[ 3] += (value2 >> 40) & 0xFF;
+                                                tc_string2[ 4] += (value2 >> 32) & 0xFF;
+                                                tc_string2[ 6] += (value2 >> 24) & 0xFF;
+                                                tc_string2[ 7] += (value2 >> 16) & 0xFF;
+                                                if ((value2 >> 10) & 1)
+                                                    tc_string2[ 8] = ';';
+                                                tc_string2[ 9] += (value2 >>  8) & 0x3F;
+                                                tc_string2[10] += (value2      ) & 0xFF;
+                                                av_log(s, AV_LOG_INFO, "Media time code %i: %s%s%s\n", (int)timecodes_pos, tc_string, values[0] == 3 ? " (stripped)" : " - ", values[0] == 3 ? "" : tc_string2);
+                                            } else {
+                                                av_log(s, AV_LOG_WARNING, "Media time code %i: stream detected but no time code detected.\n", (int)timecodes_pos);
+                                            }
+                                        }
+                                    }
+                                    tfsxml_leave(&p);
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+                track->timecodes[timecodes_pos] = NULL;
+                free(buffer);
+                fclose (f);
+            }
+        }
+    }
     if (IS_SEEKABLE(s->pb, mkv)) {
         track->blockadditionmapping_offset = avio_tell(pb);
         // We can't know at this point if there will be a block with BlockAdditions, so
@@ -1713,11 +2885,26 @@ static void mkv_write_blockadditionmapping(AVFormatContext *s, const MatroskaMux
         // be overwritten when finishing the track.
         put_ebml_uint(pb, MATROSKA_ID_TRACKMAXBLKADDID, 0);
         if (par->codec_type == AVMEDIA_TYPE_VIDEO) {
-            // Similarly, reserve space for an eventual
-            // HDR10+ ITU T.35 metadata BlockAdditionMapping.
-            put_ebml_void(pb, 3 /* BlockAdditionMapping */
-                            + 4 /* BlockAddIDValue */
-                            + 4 /* BlockAddIDType */);
+            if (1) {//IS_SEEKABLE(s->pb, mkv)) {
+                // Similarly, reserve space for an eventual
+                // HDR10+ ITU T.35 metadata BlockAdditionMapping.
+                put_ebml_void(pb, 3 /* BlockAdditionMapping */
+                                + 4 /* BlockAddIDValue */
+                                + 4 /* BlockAddIDType */);
+            }
+            /*for (size_t i = 0; i < timecodes_pos; i++) {
+                ebml_master mapping_master = start_ebml_master(pb, MATROSKA_ID_TRACKBLKADDMAPPING, 11);
+                put_ebml_uint(pb, MATROSKA_ID_BLKADDIDTYPE,
+                              MATROSKA_BLOCK_ADD_ID_TYPE_SMPTE_12M);
+                put_ebml_uint(pb, MATROSKA_ID_BLKADDIDVALUE,
+                              101 + i);
+                end_ebml_master(pb, mapping_master);
+            }*/
+            if (IS_SEEKABLE(s->pb, mkv)) {
+                if (timecodes_pos < 100) {
+                    put_ebml_void(pb, 11 * (100)); // for MATROSKA_ID_TRACKBLKADDMAPPING timecode
+                }
+            }
         }
     }
 
@@ -2101,9 +3288,6 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv,
         return AVERROR(EINVAL);
     }
 
-    if (!IS_WEBM(mkv))
-        mkv_write_blockadditionmapping(s, mkv, par, pb, track, st);
-
     if (!IS_WEBM(mkv) || par->codec_id != AV_CODEC_ID_WEBVTT) {
         uint8_t *codecpriv;
         int codecpriv_size, max_payload_size;
@@ -2118,6 +3302,9 @@ static int mkv_write_track(AVFormatContext *s, MatroskaMuxContext *mkv,
         track->codecpriv_size = max_payload_size;
     }
 
+    if (!IS_WEBM(mkv))
+        mkv_write_blockadditionmapping(s, mkv, par, pb, track, st);
+
     end_ebml_master(pb, track_master);
     ret = 0;
 fail:
@@ -2803,11 +3990,12 @@ static int mkv_write_block(void *logctx, MatroskaMuxContext *mkv,
                            int force_blockgroup, int64_t relative_packet_pos)
 {
     uint8_t t35_buf[6 + AV_HDR_PLUS_MAX_PAYLOAD_SIZE];
+    uint8_t timecode_buf[100][8];
     uint8_t *side_data;
     size_t side_data_size;
     uint64_t additional_id;
     unsigned track_number = track->track_num;
-    EBML_WRITER(12);
+    EBML_WRITER(12+100);
     int ret;
 
     mkv->cur_block.track  = track;
@@ -2884,6 +4072,47 @@ static int mkv_write_block(void *logctx, MatroskaMuxContext *mkv,
                                           MATROSKA_BLOCK_ADD_ID_ITU_T_T35);
         }
     }
+    
+    // Extract timecode from side data and write as BlockAdditional
+    size_t timecode_buf_pos = 0;
+    if (par->codec_type == AVMEDIA_TYPE_VIDEO) {
+        side_data = av_packet_get_side_data(pkt, AV_PKT_DATA_S12M_TIMECODE, &side_data_size);
+        if (side_data && side_data_size >= sizeof(uint64_t)) {
+            uint64_t *side_data_64 = (uint64_t*)side_data;
+            uint64_t count = side_data_64[0];
+            if (side_data_size / sizeof(uint64_t) - 1 >= count ) {
+                side_data_64++;
+                for (uint64_t i = 0; i < count; i++) {
+                    uint64_t tc = side_data_64[i];
+                    uint8_t *payload = malloc(sizeof(uint64_t));
+                    AV_WB64(payload, tc);
+                    av_log(logctx, AV_LOG_ERROR, "Writing SMPTE timecode from side data, pos %li, to BlockAdditional: 0x%016lX\n", i + 1, tc);
+
+                    int blockaddid = 101 + timecode_buf_pos++;
+                    mkv_write_blockadditional(&writer, payload, 8, blockaddid);
+                    track->max_blockaddid = FFMAX(track->max_blockaddid, blockaddid);
+                }
+            }
+        }
+    }
+
+    // Extract timecode from external file and write as BlockAdditional
+    if (track->timecodes) {
+        for (uint64_t i = 0; i < 100; i++) {
+            if (!track->timecodes[i])
+                continue;
+            if (track->timecodes[i][1]<track->timecodes[i][0]) {
+                uint8_t *payload = timecode_buf[i];
+                uint64_t tc = track->timecodes[i][track->timecodes[i][1]++];
+                AV_WB64(payload, tc);
+                av_log(logctx, AV_LOG_ERROR, "Writing SMPTE timecode from external file, pos %li, to BlockAdditional: 0x%016lX\n", i + 1, tc);
+
+                int blockaddid = 101 + timecode_buf_pos++;
+                mkv_write_blockadditional(&writer, payload, 8, blockaddid);
+                track->max_blockaddid = FFMAX(track->max_blockaddid, blockaddid);
+            }
+        }
+    }
 
     ebml_writer_close_or_discard_master(&writer);
 
@@ -3312,6 +4541,20 @@ after_cues:
                                   MATROSKA_BLOCK_ADD_ID_ITU_T_T35);
                     end_ebml_master(track_bc, mapping_master);
                 }
+                else {
+                    put_ebml_void(track_bc, 11);
+                }
+                for (int i = 101; i <= track->max_blockaddid; i++) {
+                    ebml_master mapping_master = start_ebml_master(track_bc, MATROSKA_ID_TRACKBLKADDMAPPING, 8);
+                    put_ebml_uint(track_bc, MATROSKA_ID_BLKADDIDTYPE,
+                                  MATROSKA_BLOCK_ADD_ID_TYPE_SMPTE_12M);
+                    put_ebml_uint(track_bc, MATROSKA_ID_BLKADDIDVALUE,
+                                  i);
+                    end_ebml_master(track_bc, mapping_master);
+                }
+                if (track->max_blockaddid >= 101 && track->max_blockaddid < 200) {
+                    put_ebml_void(track_bc, 11 * (200 - track->max_blockaddid));
+                }
             }
         }
 
@@ -3558,6 +4801,7 @@ static const AVOption options[] = {
     { "infer", "for each track type, mark each track of disposition default as default; if none exists, mark the first track as default", 0, AV_OPT_TYPE_CONST, { .i64 = DEFAULT_MODE_INFER }, 0, 0, FLAGS, .unit = "default_mode" },
     { "infer_no_subs", "for each track type, mark each track of disposition default as default; for audio and video: if none exists, mark the first track as default", 0, AV_OPT_TYPE_CONST, { .i64 = DEFAULT_MODE_INFER_NO_SUBS }, 0, 0, FLAGS, .unit = "default_mode" },
     { "passthrough", "use the disposition flag as-is", 0, AV_OPT_TYPE_CONST, { .i64 = DEFAULT_MODE_PASSTHROUGH }, 0, 0, FLAGS, .unit = "default_mode" },
+    { "mediatimecode", "MediaTimecode XML file name", OFFSET(mediatimecode), AV_OPT_TYPE_STRING, { .str = NULL },  0, 0, FLAGS },
     { NULL },
 };
 
diff --git a/libavutil/timecode.c b/libavutil/timecode.c
index bca16b6ac2..81fb66a5e4 100644
--- a/libavutil/timecode.c
+++ b/libavutil/timecode.c
@@ -243,3 +243,107 @@ int av_timecode_init_from_string(AVTimecode *tc, AVRational rate, const char *st
 
     return av_timecode_init_from_components(tc, rate, flags, hh, mm, ss, ff, log_ctx);
 }
+
+static int bcd_to_int(int bcd) {
+    return ((bcd >> 4) & 0xF) * 10 + (bcd & 0xF);
+}
+
+uint64_t av_timecode_expand_to_64bit(uint32_t tc32)
+{
+    uint64_t tc64 = 0;
+
+    int hours_bcd   =  tc32        & 0x3F;  // bits 0-5
+    int bgf1        = (tc32 >> 6)  & 0x1;   // bit 6
+    int bgf2        = (tc32 >> 7)  & 0x1;   // bit 7
+    int minutes_bcd = (tc32 >> 8)  & 0x7F;  // bits 8-14
+    int bgf0        = (tc32 >> 15) & 0x1;   // bit 15
+    int seconds_bcd = (tc32 >> 16) & 0x7F;  // bits 16-22
+    int frames_bcd  = (tc32 >> 24) & 0x3F;  // bits 24-29
+    int drop        = (tc32 >> 30) & 0x1;   // bit 30
+    int color       = (tc32 >> 31) & 0x1;   // bit 31
+
+    int hours   = bcd_to_int(hours_bcd);
+    int minutes = bcd_to_int(minutes_bcd);
+    int seconds = bcd_to_int(seconds_bcd);
+    int frames  = bcd_to_int(frames_bcd);
+
+    // Units and tens
+    int uh = hours   % 10, th = hours   / 10;
+    int um = minutes % 10, tm = minutes / 10;
+    int us = seconds % 10, ts = seconds / 10;
+    int uf = frames  % 10, tf = frames  / 10;
+
+    // Assign bits as per RFC 5484 layout
+    tc64 |= (uint64_t)(uf & 0xF) << 0;   // Units of frames
+    tc64 |= 0                    << 4;   // First binary group
+    tc64 |= (uint64_t)(tf & 0x3) << 8;   // Tens of frames (2 bits)
+    tc64 |= (uint64_t)drop       << 10;  // Drop frame flag
+    tc64 |= (uint64_t)color      << 11;  // Color frame flag
+    tc64 |= 0                    << 12;  // Second binary group
+
+    tc64 |= (uint64_t)(us & 0xF) << 16;  // Units of seconds
+    tc64 |= 0                    << 20;  // Third binary group
+    tc64 |= (uint64_t)(ts & 0x7) << 24;  // Tens of seconds
+    tc64 |= 0                    << 27;  // Polarity correction
+    tc64 |= 0                    << 28;  // Fourth binary group
+
+    tc64 |= (uint64_t)(um & 0xF) << 32;  // Units of minutes
+    tc64 |= (uint64_t)0          << 36;  // Fifth binary group
+    tc64 |= (uint64_t)(tm & 0x7) << 40;  // Tens of minutes
+    tc64 |= (uint64_t)bgf0       << 43;  // BGF0
+    tc64 |= (uint64_t)0          << 44;  // Sixth binary group
+
+    tc64 |= (uint64_t)(uh & 0xF) << 48;  // Units of hours
+    tc64 |= (uint64_t)0          << 52;  // Seventh binary group
+    tc64 |= (uint64_t)(th & 0x3) << 56;  // Tens of hours
+    tc64 |= (uint64_t)bgf1       << 58;  // BGF1
+    tc64 |= (uint64_t)bgf2       << 59;  // BGF2
+    tc64 |= (uint64_t)0          << 60;  // Eighth binary group
+
+    return tc64;
+}
+
+static int int_to_bcd(int val) {
+    return ((val / 10) << 4) | (val % 10);
+}
+
+uint32_t av_timecode_parse_from_64bit(uint64_t tc64)
+{
+    uint32_t tc32 = 0;
+
+    int uf   = (tc64 >> 0)  & 0xF;   // Ones of frames
+    int tf   = (tc64 >> 8)  & 0x3;   // Tens of frames
+    int drop = (tc64 >> 10) & 0x1;
+    int color= (tc64 >> 11) & 0x1;
+
+    int us   = (tc64 >> 16) & 0xF;   // Ones of seconds
+    int ts   = (tc64 >> 24) & 0x7;   // Tens of seconds
+
+    int um   = (tc64 >> 32) & 0xF;   // Ones of minutes
+    int tm   = (tc64 >> 40) & 0x7;   // Tens of minutes
+    int bgf0 = (tc64 >> 43) & 0x1;
+
+    int uh   = (tc64 >> 48) & 0xF;   // Ones of hours
+    int th   = (tc64 >> 56) & 0x3;   // Tens of hours
+    int bgf1 = (tc64 >> 58) & 0x1;
+    int bgf2 = (tc64 >> 59) & 0x1;
+
+    int hours   = int_to_bcd(th * 10 + uh);
+    int minutes = int_to_bcd(tm * 10 + um);
+    int seconds = int_to_bcd(ts * 10 + us);
+    int frames  = int_to_bcd(tf * 10 + uf);
+
+    // Assemble tc32 using the FFmpeg SMPTE 32-bit format
+    tc32 |= (hours & 0x3F)        << 0;   // bits 0-5: hours (BCD)
+    tc32 |= (bgf1  & 0x1)         << 6;   // bit 6: BGF1
+    tc32 |= (bgf2  & 0x1)         << 7;   // bit 7: BGF2 (or FIELD)
+    tc32 |= (minutes & 0x7F)      << 8;   // bits 8-14: minutes (BCD)
+    tc32 |= (bgf0  & 0x1)         << 15;  // bit 15: BGF0
+    tc32 |= (seconds & 0x7F)      << 16;  // bits 16-22: seconds (BCD)
+    tc32 |= 0                     << 23;  // bit 23: FIELD (set to 0)
+    tc32 |= (frames  & 0x3F)      << 24;  // bits 24-29: frames (BCD)
+    tc32 |= (drop    & 0x1)       << 30;  // bit 30: drop frame flag
+    tc32 |= (color   & 0x1)       << 31;  // bit 31: color frame flag
+
+    return tc32;
+}
diff --git a/libavutil/timecode.h b/libavutil/timecode.h
index fe0fc83576..81713a9adf 100644
--- a/libavutil/timecode.h
+++ b/libavutil/timecode.h
@@ -196,4 +196,26 @@ int av_timecode_init_from_string(AVTimecode *tc, AVRational rate, const char *st
  */
 int av_timecode_check_frame_rate(AVRational rate);
 
+/**
+ * Convert a 32-bit SMPTE 12M timecode to 64-bit SMPTE 12M/RFC 5484 format.
+ *
+ * This maps the timecode as described in RFC 5484 Section 6.2,
+ * expanding BCD-encoded time values into bit fields without the sync word.
+ *
+ * @param tc32  The 32-bit SMPTE timecode (from DeckLink or av_timecode_get_smpte)
+ * @return      The 64-bit SMPTE 12M timecode format (without sync word)
+ */
+uint64_t av_timecode_expand_to_64bit(uint32_t tc32);
+
+/**
+ * Convert a 64-bit SMPTE 12M/RFC 5484 timecode to 32-bit SMPTE format.
+ *
+ * This reconstructs the packed 32-bit SMPTE timecode (DeckLink-style or FFmpeg output)
+ * from a full 64-bit representation.
+ *
+ * @param tc64 The 64-bit timecode value in RFC 5484 / SMPTE 12M format (without sync word)
+ * @return     A 32-bit SMPTE timecode
+ */
+uint32_t av_timecode_parse_from_64bit(uint64_t tc64);
+
 #endif /* AVUTIL_TIMECODE_H */
-- 
2.46.0.windows.1

