From 8f7c53e54877d1964b9e9aa7cdb623bd7226708c Mon Sep 17 00:00:00 2001
From: Jerome Martinez <jerome@mediaarea.net>
Date: Thu, 4 Dec 2025 17:31:32 +0100
Subject: [PATCH 19/19] decklink_dec: support labels

---
 fftools/ffprobe.c            |  27 +++++++--
 libavdevice/decklink_dec.cpp |  75 +++++++++++------------
 libavformat/avformat.c       |  81 +++++++++++++++++++++++++
 libavformat/avformat.h       |  25 ++++++++
 libavformat/dump.c           |  45 +++++++++++---
 libavformat/matroska.h       |   1 +
 libavformat/matroskadec.c    |  79 +++++++++++++++++--------
 libavformat/matroskaenc.c    | 111 ++++++++++++++++++++++++++++++++---
 libavutil/timecode.h         |   2 +
 9 files changed, 363 insertions(+), 83 deletions(-)

diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
index f4ad270..81c5e4f 100644
--- a/fftools/ffprobe.c
+++ b/fftools/ffprobe.c
@@ -1015,6 +1015,7 @@ static void print_pkt_side_data(AVTextFormatContext *tfc,
     const char *name = av_packet_side_data_name(sd->type);
 
     avtext_print_section_header(tfc, sd, id_data);
+    av_log(NULL, AV_LOG_ERROR, "%s \n", name ? name : "unknown");
     print_str("side_data_type", name ? name : "unknown");
     if (sd->type == AV_PKT_DATA_DISPLAYMATRIX && sd->size >= 9*4) {
         print_displaymatrix(tfc, (const int32_t*)sd->data);
@@ -1363,14 +1364,32 @@ static void print_frame_side_data(AVTextFormatContext *tfc,
             av_timecode_make_mpeg_tc_string(tcbuf, *(int64_t *)(sd->data));
             print_str("timecode", tcbuf);
         } else if (sd->type == AV_FRAME_DATA_S12M_TIMECODE && sd->size >= 8) {
-            uint64_t *tc = (uint64_t*)sd->data;
-            int m = FFMIN(tc[0],3);
+            uint8_t *sd_count = (uint8_t*) (sd->data + 4);
+            uint8_t count = *sd_count;
             avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_TIMECODE_LIST);
-            for (int j = 1; j <= m ; j++) {
+            for (int j = 0; j < count ; j++) {
+                uint8_t *sd_base = sd->data + 8 + (8 + 4 + 16) * j;
+                uint64_t *sd_tc = (uint64_t*)sd_base;
+                uint32_t *sd_id = (uint32_t*)(sd_base + 8);
+                char* sd_title = (char*)(sd_base + 8 + 4);
                 char tcbuf[AV_TIMECODE_STR_SIZE];
-                av_timecode_make_smpte_tc_string2(tcbuf, stream->avg_frame_rate, tc[j], 0, 0);
+                uint64_t id = *sd_id;
+                uint64_t tc = *sd_tc;
+                av_timecode_make_smpte_tc_string2(tcbuf, stream->avg_frame_rate, av_timecode_parse_from_64bit(tc), 0, 0);
                 avtext_print_section_header(tfc, NULL, SECTION_ID_FRAME_SIDE_DATA_TIMECODE);
                 print_str("value", tcbuf);
+                print_int("id", id);
+                char tctitlebuf[17];
+                int k = 0;
+                for (; k < 16; k++)
+                    if (!sd_title[k])
+                        break;
+                if (k >= 16) {
+                    memcpy(tctitlebuf, sd_title, k);
+                    tctitlebuf[k] = '\0';
+                    sd_title = tctitlebuf;
+                }
+                print_str("title", sd_title);
                 avtext_print_section_footer(tfc);
             }
             avtext_print_section_footer(tfc);
diff --git a/libavdevice/decklink_dec.cpp b/libavdevice/decklink_dec.cpp
index 370efa9..539482b 100644
--- a/libavdevice/decklink_dec.cpp
+++ b/libavdevice/decklink_dec.cpp
@@ -712,7 +712,24 @@ static int64_t get_pkt_pts(IDeckLinkVideoInputFrame_v14_2_1 *videoFrame,
     return pts;
 }
 
-static int get_bmd_timecode(AVFormatContext *avctx, AVTimecode *tc, AVRational frame_rate, BMDTimecodeFormat tc_format, IDeckLinkVideoInputFrame_v14_2_1 *videoFrame)
+static const char *BMDTimecodeFormat_to_label(BMDTimecodeFormat tc_format)
+{
+    switch(tc_format) {
+        case bmdTimecodeRP188VITC1: return "ATC_VITC";
+        case bmdTimecodeRP188VITC2: return "ATC_VITC2";
+        case bmdTimecodeRP188LTC: return "ATC_LTC";
+        case bmdTimecodeRP188Any: return "VITC";
+        case bmdTimecodeVITC: return "VITC";
+        case bmdTimecodeVITCField2: return "VITC2";
+        case bmdTimecodeSerial: return "9PIN";
+#if BLACKMAGIC_DECKLINK_API_VERSION >= 0x0b000000
+        case bmdTimecodeRP188HighFrameRate: return "HFRTC";
+#endif
+        default: return NULL;
+    }
+}
+
+static int get_bmd_timecode(AVFormatContext *avctx, AVTimecode *tc, const char **tc_kind, AVRational frame_rate, BMDTimecodeFormat tc_format, IDeckLinkVideoInputFrame_v14_2_1 *videoFrame)
 {
     IDeckLinkTimecode *timecode;
     int ret = AVERROR(ENOENT);
@@ -722,6 +739,7 @@ static int get_bmd_timecode(AVFormatContext *avctx, AVTimecode *tc, AVRational f
     int hfr = 0;
 #endif
     if (videoFrame->GetTimecode(tc_format, &timecode) == S_OK) {
+        *tc_kind = BMDTimecodeFormat_to_label(tc_format);
         uint8_t hh, mm, ss, ff;
         if (timecode->GetComponents(&hh, &mm, &ss, &ff) == S_OK) {
             int flags = (timecode->GetFlags() & bmdTimecodeIsDropFrame) ? AV_TIMECODE_FLAG_DROPFRAME : 0;
@@ -734,53 +752,39 @@ static int get_bmd_timecode(AVFormatContext *avctx, AVTimecode *tc, AVRational f
     return ret;
 }
 
-static int get_frame_timecode(AVFormatContext *avctx, decklink_ctx *ctx, AVTimecode *tc, IDeckLinkVideoInputFrame_v14_2_1 *videoFrame)
+static int get_frame_timecode(AVFormatContext *avctx, decklink_ctx *ctx, AVTimecode *tc, const char **tc_kind, IDeckLinkVideoInputFrame_v14_2_1 *videoFrame)
 {
     AVRational frame_rate = ctx->video_st->r_frame_rate;
     int ret;
     if (ctx->tc_format == (BMDTimecodeFormat)1) {
         int count = 0;
-        ret = get_bmd_timecode(avctx, tc + count, frame_rate, bmdTimecodeRP188VITC1, videoFrame);
-        if (ret != AVERROR(ENOENT))
-            count++;
-        ret = get_bmd_timecode(avctx, tc + count, frame_rate, bmdTimecodeRP188VITC2, videoFrame);
-        if (ret != AVERROR(ENOENT))
-            count++;
-        ret = get_bmd_timecode(avctx, tc + count, frame_rate, bmdTimecodeRP188LTC, videoFrame);
-        if (ret != AVERROR(ENOENT))
-            count++;
+        count += get_bmd_timecode(avctx, tc + count, tc_kind + count, frame_rate, bmdTimecodeRP188VITC1, videoFrame) != AVERROR(ENOENT);
+        count += get_bmd_timecode(avctx, tc + count, tc_kind + count, frame_rate, bmdTimecodeRP188VITC2, videoFrame) != AVERROR(ENOENT);
+        count += get_bmd_timecode(avctx, tc + count, tc_kind + count, frame_rate, bmdTimecodeRP188LTC, videoFrame) != AVERROR(ENOENT);
+        count += get_bmd_timecode(avctx, tc + count, tc_kind + count, frame_rate, bmdTimecodeVITC, videoFrame) != AVERROR(ENOENT);
+        count += get_bmd_timecode(avctx, tc + count, tc_kind + count, frame_rate, bmdTimecodeVITCField2, videoFrame) != AVERROR(ENOENT);
+        count += get_bmd_timecode(avctx, tc + count, tc_kind + count, frame_rate, bmdTimecodeSerial, videoFrame) != AVERROR(ENOENT);
 #if BLACKMAGIC_DECKLINK_API_VERSION >= 0x0b000000
-        ret = get_bmd_timecode(avctx, tc + count, frame_rate, bmdTimecodeRP188HighFrameRate, videoFrame);
-        if (ret != AVERROR(ENOENT))
-            count++;
+        count += get_bmd_timecode(avctx, tc + count, tc_kind + count, frame_rate, bmdTimecodeRP188HighFrameRate, videoFrame) != AVERROR(ENOENT);
 #endif
-        ret = get_bmd_timecode(avctx, tc + count, frame_rate, bmdTimecodeVITC, videoFrame);
-        if (ret != AVERROR(ENOENT))
-            count++;
-        ret = get_bmd_timecode(avctx, tc + count, frame_rate, bmdTimecodeVITCField2, videoFrame);
-        if (ret != AVERROR(ENOENT))
-            count++;
-        ret = get_bmd_timecode(avctx, tc + count, frame_rate, bmdTimecodeSerial, videoFrame);
-        if (ret != AVERROR(ENOENT))
-            count++;
        return count;
     }
     /* 50/60 fps content has alternating VITC1 and VITC2 timecode (see SMPTE ST
      * 12-2, section 7), so the native ordering of RP188Any (HFR, VITC1, LTC,
      * VITC2) would not work because LTC might not contain the field flag.
      * Therefore we query the types manually. */
-    if (ctx->tc_format == bmdTimecodeRP188Any && av_cmp_q(frame_rate, av_make_q(30, 1)) == 1) {
+    if (ctx->tc_format == bmdTimecodeRP188Any /*&& av_cmp_q(frame_rate, av_make_q(30, 1)) == 1*/) {
 #if BLACKMAGIC_DECKLINK_API_VERSION >= 0x0b000000
-       ret = get_bmd_timecode(avctx, tc, frame_rate, bmdTimecodeRP188HighFrameRate, videoFrame);
+       ret = get_bmd_timecode(avctx, tc, tc_kind, frame_rate, bmdTimecodeRP188HighFrameRate, videoFrame);
        if (ret == AVERROR(ENOENT))
 #endif
-           ret = get_bmd_timecode(avctx, tc, frame_rate, bmdTimecodeRP188VITC1, videoFrame);
+           ret = get_bmd_timecode(avctx, tc, tc_kind, frame_rate, bmdTimecodeRP188VITC1, videoFrame);
        if (ret == AVERROR(ENOENT))
-           ret = get_bmd_timecode(avctx, tc, frame_rate, bmdTimecodeRP188VITC2, videoFrame);
+           ret = get_bmd_timecode(avctx, tc, tc_kind, frame_rate, bmdTimecodeRP188VITC2, videoFrame);
        if (ret == AVERROR(ENOENT))
-           ret = get_bmd_timecode(avctx, tc, frame_rate, bmdTimecodeRP188LTC, videoFrame);
+           ret = get_bmd_timecode(avctx, tc, tc_kind, frame_rate, bmdTimecodeRP188LTC, videoFrame);
     } else {
-       ret = get_bmd_timecode(avctx, tc, frame_rate, ctx->tc_format, videoFrame);
+       ret = get_bmd_timecode(avctx, tc, tc_kind, frame_rate, ctx->tc_format, videoFrame);
     }
     return ret >= 0;
 }
@@ -878,7 +882,8 @@ HRESULT decklink_input_callback::VideoInputFrameArrived(
             // Handle Timecode (if requested)
             if (ctx->tc_format) {
                 AVTimecode tcr[8];
-                int count = get_frame_timecode(avctx, ctx, &tcr[0], videoFrame);
+                const char *tcr_kind[8];
+                int count = get_frame_timecode(avctx, ctx, &tcr[0], &tcr_kind[0], videoFrame);
                 if (count > 0) {
                     char tcstr[AV_TIMECODE_STR_SIZE];
                     const char *tc = av_timecode_make_string(&tcr[0], tcstr, 0);
@@ -887,13 +892,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) {
-                            int size = sizeof(uint64_t) * (1 + count);
-                            uint64_t *sd = (uint64_t *)av_packet_new_side_data(&pkt, AV_PKT_DATA_S12M_TIMECODE, size);
-
-                            if (sd) {
-                                *sd = count;
-                                for (int i = 0; i < count; i++)
-                                    *(sd + 1 + i) = av_timecode_expand_to_64bit(av_timecode_get_smpte_from_framenum(&tcr[i], 0));
+                            for (int i = 0; i < count; i++) {
+                                uint64_t tc = av_timecode_expand_to_64bit(av_timecode_get_smpte_from_framenum(&tcr[i], 0));
+                                av_packet_add_s12m_timecode_to_side_data(avctx, &pkt, S12M_TIMECODE_FLAG_ID_PRESENT | S12M_TIMECODE_FLAG_TITLE_PRESENT, tc, 0, tcr_kind[i]);
                             }
                         }
 
diff --git a/libavformat/avformat.c b/libavformat/avformat.c
index 8a98f2e..b062b28 100644
--- a/libavformat/avformat.c
+++ b/libavformat/avformat.c
@@ -908,3 +908,84 @@ int ff_format_io_close(AVFormatContext *s, AVIOContext **pb)
     *pb = NULL;
     return ret;
 }
+
+#define add_block_timecode_count 4 //TODO
+static size_t s12m_timecode_get_size_per_item(unsigned flags)
+{
+    return 8 + 4 + 16;
+}
+
+static size_t s12m_timecode_get_size(unsigned flags)
+{
+    return add_block_timecode_count * s12m_timecode_get_size_per_item(flags);
+}
+
+static void s12m_timecode_init(uint8_t *sd, size_t sd_size, unsigned flags)
+{
+    uint32_t *sd_allocated = (uint32_t*)sd;
+    *sd_allocated = sd_size;
+    uint8_t *sd_count = (uint8_t*) (sd + 4);
+    *sd_count = 0;
+    uint8_t *item_size = sd + 5;
+    *item_size = s12m_timecode_get_size_per_item(flags);
+}
+
+static int av_packet_add_s12m_timecode_to_side_data_internal(AVFormatContext *ctx, uint8_t *sd, size_t sd_size, unsigned flags, uint64_t tc, uint32_t id, const char *title)
+{
+    // header: 0-3 allocated size; 4 count; 5 byte size per item; 6 flags; 7 reserved
+    // flags: 0 tc present; 1 id present; 2 title present
+    // content: 8 bytes tc; 4 bytes id; 16 bytes title
+
+    uint8_t *sd_count = (uint8_t*) (sd + 4);
+    uint8_t count = *sd_count;
+    uint8_t *item_size = sd + 5;
+
+    if (count >= add_block_timecode_count) {
+        av_log(ctx, AV_LOG_DEBUG, "There are more timecodes in the block than the count supported, extra timecodes are ignored.\n");
+        return 0; // not an actual error, just ignored, TODO: manage more tc
+    }
+    av_log(ctx, AV_LOG_DEBUG, "Reading SMPTE timecode from BlockAdditional: 0x%016lX (RFC 5484)\n", tc);
+
+    *sd_count = count + 1;
+    uint8_t *sd_base = sd + 8 + *item_size * count;
+    uint64_t *sd_tc = (uint64_t*)sd_base;
+    uint32_t *sd_id = (uint32_t*)(sd_base + 8);
+    char* sd_title = (char*)(sd_base + 8 + 4);
+    *sd_tc = tc;
+    *sd_id = id;
+    size_t title_size = title ? strlen(title) : 0;
+    if (title_size)
+        memcpy(sd_title, title, title_size);
+    memset(sd_title + title_size, 0, 16 - title_size);
+    
+    return 0;
+}
+
+int av_packet_add_s12m_timecode_to_side_data(AVFormatContext *ctx, AVPacket *pkt, unsigned flags, uint64_t tc, uint32_t id, const char *title)
+{
+    size_t sd_size = 0;
+    uint8_t *sd = av_packet_get_side_data(pkt, AV_PKT_DATA_S12M_TIMECODE, &sd_size);
+    if (!sd) {
+        sd_size = s12m_timecode_get_size(flags);
+        sd = av_packet_new_side_data(pkt, AV_PKT_DATA_S12M_TIMECODE, sd_size);
+        if (!sd)
+            return 1;
+        s12m_timecode_init(sd, sd_size, flags);
+    }
+    return av_packet_add_s12m_timecode_to_side_data_internal(ctx, sd, sd_size, flags, tc, id, title);
+}
+
+// Add timecode track information to track information (no actual data)
+int av_packet_side_data_add_s12m_timecode_to(AVFormatContext *ctx, AVPacketSideData **psd, int *pnb_sd, unsigned flags, uint64_t tc, uint32_t id, const char *title)
+{
+    size_t sd_size = 0;
+    const AVPacketSideData *sd = av_packet_side_data_get(*psd, *pnb_sd, AV_PKT_DATA_S12M_TIMECODE);
+    if (!sd) {
+        sd_size = s12m_timecode_get_size(flags);
+        sd = av_packet_side_data_new(psd, pnb_sd, AV_PKT_DATA_S12M_TIMECODE, sd_size, 0);
+        if (!sd || !sd->data)
+            return 1;
+        s12m_timecode_init(sd->data, sd_size, flags);
+    }
+    return av_packet_add_s12m_timecode_to_side_data_internal(ctx, sd->data, sd->size, flags, tc, id, title);
+}
\ No newline at end of file
diff --git a/libavformat/avformat.h b/libavformat/avformat.h
index ee0dddd..6342bb6 100644
--- a/libavformat/avformat.h
+++ b/libavformat/avformat.h
@@ -3110,6 +3110,31 @@ attribute_deprecated
 AVRational av_stream_get_codec_timebase(const AVStream *st);
 #endif
 
+/**
+ *
+ */
+#define S12M_TIMECODE_FLAG_TC_PRESENT 1
+#define S12M_TIMECODE_FLAG_ID_PRESENT 2
+#define S12M_TIMECODE_FLAG_TITLE_PRESENT 4
+typedef struct S12MTimecodeData {
+    unsigned flags;
+    uint32_t id;
+    uint64_t tc;
+    const char *title; /**< title may NOT be null terminated */
+    unsigned title_size;   /**< Size of title */
+} S12MTimecodeData;
+
+/**
+ *
+ */
+int av_packet_add_s12m_timecode_to_side_data(AVFormatContext *ctx, AVPacket *pkt, unsigned flags, uint64_t tc, uint32_t id, const char *title);
+S12MTimecodeData av_packet_get_s12m_timecode(AVFormatContext *ctx, const AVPacket *pkt, unsigned pos);
+
+/**
+ *
+ */
+int av_packet_side_data_add_s12m_timecode_to(AVFormatContext *ctx, AVPacketSideData **psd, int *pnb_sd, unsigned flags, uint64_t tc, uint32_t id, const char *title);
+S12MTimecodeData av_packet_s12m_timecode_get(AVFormatContext *ctx, const AVPacketSideData *psd, int pnb_sd, unsigned pos);
 
 /**
  * @}
diff --git a/libavformat/dump.c b/libavformat/dump.c
index 66abc2a..0ab2614 100644
--- a/libavformat/dump.c
+++ b/libavformat/dump.c
@@ -427,19 +427,46 @@ static void dump_dovi_conf(void *ctx, const AVPacketSideData *sd,
 }
 
 static void dump_s12m_timecode(void *ctx, AVRational avg_frame_rate, const AVPacketSideData *sd,
-                               int log_level)
+                               int log_level, const char *indent)
 {
-    const uint32_t *tc = (const uint32_t *)sd->data;
-
-    if ((sd->size != sizeof(uint32_t) * 4) || (tc[0] > 3)) {
+    if (sd->size < sizeof(uint32_t)) {
         av_log(ctx, AV_LOG_ERROR, "invalid data\n");
         return;
     }
-
-    for (int j = 1; j <= tc[0]; j++) {
+  
+    uint8_t *sd_count = (uint8_t*) (sd->data + 4);
+    uint8_t count = *sd_count;
+    for (int j = 0; j < count ; j++) {
+        uint8_t *sd_base = sd->data + 8 + (8 + 4 + 16) * j;
+        uint64_t *sd_tc = (uint64_t*)sd_base;
+        uint32_t *sd_id = (uint32_t*)(sd_base + 8);
+        char* sd_title = (char*)(sd_base + 8 + 4);
         char tcbuf[AV_TIMECODE_STR_SIZE];
-        av_timecode_make_smpte_tc_string2(tcbuf, avg_frame_rate, tc[j], 0, 0);
-        av_log(ctx, log_level, "timecode - %s%s", tcbuf, j != tc[0] ? ", " : "");
+        uint64_t id = *sd_id;
+        uint64_t tc = *sd_tc;
+        av_timecode_make_smpte_tc_string2(tcbuf, avg_frame_rate, av_timecode_parse_from_64bit(tc), 0, 0);
+        char tctitlebuf[17];
+        int k = 0;
+        for (; k < 16; k++)
+            if (!sd_title[k])
+                break;
+        if (k >= 16) {
+            memcpy(tctitlebuf, sd_title, k);
+            tctitlebuf[k] = '\0';
+            sd_title = tctitlebuf;
+        }
+        if (j) {
+            const char *name = av_packet_side_data_name(AV_PKT_DATA_S12M_TIMECODE);
+            av_log(ctx, log_level, "\n%s  %s: ", indent, name ? name : "");
+        }
+        char id_string[33];
+        if (id) {
+            snprintf(id_string, sizeof(id_string), "%" PRIu64, id);
+        }
+        else {
+            id_string[0] = '\0';
+        }
+        av_log(ctx, log_level, "%s%s%s%s%s", id ? "id: " : "", id_string, *sd_title && id ? " " : "", *sd_title ? "title: " : "", *sd_title ? sd_title : "");
     }
 }
 
@@ -521,7 +548,7 @@ static void dump_sidedata(void *ctx, const AVPacketSideData *side_data, int nb_s
             dump_dovi_conf(ctx, sd, log_level);
             break;
         case AV_PKT_DATA_S12M_TIMECODE:
-            dump_s12m_timecode(ctx, avg_frame_rate, sd, log_level);
+            dump_s12m_timecode(ctx, avg_frame_rate, sd, log_level, indent);
             break;
         case AV_PKT_DATA_AMBIENT_VIEWING_ENVIRONMENT:
             dump_ambient_viewing_environment_metadata(ctx, sd, log_level);
diff --git a/libavformat/matroska.h b/libavformat/matroska.h
index d78b33d..84debcf 100644
--- a/libavformat/matroska.h
+++ b/libavformat/matroska.h
@@ -223,6 +223,7 @@
 #define MATROSKA_ID_TAGTARGETS_TRACKUID   0x63C5
 #define MATROSKA_ID_TAGTARGETS_CHAPTERUID 0x63C4
 #define MATROSKA_ID_TAGTARGETS_ATTACHUID  0x63C6
+#define MATROSKA_ID_TAGTARGETS_BLOCKADDID 0x63C7
 
 /* IDs in the seekhead master */
 #define MATROSKA_ID_SEEKENTRY  0x4DBB
diff --git a/libavformat/matroskadec.c b/libavformat/matroskadec.c
index ea1216b..3cdfe91 100644
--- a/libavformat/matroskadec.c
+++ b/libavformat/matroskadec.c
@@ -49,6 +49,7 @@
 #include "libavutil/opt.h"
 #include "libavutil/pixdesc.h"
 #include "libavutil/time_internal.h"
+#include "libavutil/timecode.h"
 #include "libavutil/spherical.h"
 
 #include "libavcodec/bytestream.h"
@@ -259,6 +260,7 @@ typedef struct MatroskaBlockAdditionMapping {
     char *name;
     uint64_t type;
     EbmlBin extradata;
+    char *title;
 } MatroskaBlockAdditionMapping;
 
 typedef struct MatroskaTrack {
@@ -341,6 +343,7 @@ typedef struct MatroskaTagTarget {
     uint64_t trackuid;
     uint64_t chapteruid;
     uint64_t attachuid;
+    uint64_t blockaddid;
 } MatroskaTagTarget;
 
 typedef struct MatroskaTags {
@@ -449,7 +452,7 @@ typedef struct MatroskaDemuxContext {
 // Removing the sizes breaks MSVC.
 static EbmlSyntax ebml_syntax[3], matroska_segment[9], matroska_track_video_color[15], matroska_track_video[19],
                   matroska_track[33], matroska_track_encoding[6], matroska_track_encodings[2],
-                  matroska_track_combine_planes[2], matroska_track_operation[2], matroska_block_addition_mapping[5], matroska_tracks[2],
+                  matroska_track_combine_planes[2], matroska_track_operation[2], matroska_block_addition_mapping[6], matroska_tracks[2],
                   matroska_attachments[2], matroska_chapter_entry[9], matroska_chapter[6], matroska_chapters[2],
                   matroska_index_entry[3], matroska_index[2], matroska_tag[3], matroska_tags[2], matroska_seekhead[2],
                   matroska_blockadditions[2], matroska_blockgroup[8], matroska_cluster_parsing[8];
@@ -604,6 +607,7 @@ static EbmlSyntax matroska_block_addition_mapping[] = {
     { MATROSKA_ID_BLKADDIDNAME,       EBML_STR,  0, 0, offsetof(MatroskaBlockAdditionMapping, name) },
     { MATROSKA_ID_BLKADDIDTYPE,       EBML_UINT, 0, 0, offsetof(MatroskaBlockAdditionMapping, type), { .u = MATROSKA_BLOCK_ADD_ID_TYPE_DEFAULT } },
     { MATROSKA_ID_BLKADDIDEXTRADATA,  EBML_BIN,  0, 0, offsetof(MatroskaBlockAdditionMapping, extradata) },
+    { MATROSKA_ID_BLKADDIDTYPE,       EBML_UTF8, 0, 0, offsetof(MatroskaBlockAdditionMapping, title) }, //TODO: fake ID, used here for freeing the char* automatically
     CHILD_OF(matroska_track)
 };
 
@@ -731,6 +735,7 @@ static EbmlSyntax matroska_tagtargets[] = {
     { MATROSKA_ID_TAGTARGETS_TRACKUID,   EBML_UINT, 0, 0, offsetof(MatroskaTagTarget, trackuid),   { .u = 0 } },
     { MATROSKA_ID_TAGTARGETS_CHAPTERUID, EBML_UINT, 0, 0, offsetof(MatroskaTagTarget, chapteruid), { .u = 0 } },
     { MATROSKA_ID_TAGTARGETS_ATTACHUID,  EBML_UINT, 0, 0, offsetof(MatroskaTagTarget, attachuid),  { .u = 0 } },
+    { MATROSKA_ID_TAGTARGETS_BLOCKADDID, EBML_UINT, 0, 0, offsetof(MatroskaTagTarget, blockaddid), { .u = 0 } },
     CHILD_OF(matroska_tag)
 };
 
@@ -1846,6 +1851,35 @@ static void matroska_convert_tag(AVFormatContext *s, EbmlList *list,
     ff_metadata_conv(metadata, NULL, ff_mkv_metadata_conv);
 }
 
+static void matroska_convert_blockaddid_tag(AVFormatContext *s, EbmlList *list,
+                                 const EbmlList *mappings_list, uint64_t mappings_id)
+{
+    MatroskaTag *tags = list->elem;
+    MatroskaBlockAdditionMapping *mappings = mappings_list->elem;
+    int i;
+
+    for (i = 0; i < list->nb_elem; i++) {
+        const char *lang = tags[i].lang &&
+                           strcmp(tags[i].lang, "und") ? tags[i].lang : NULL;
+                           
+        if (lang)
+            continue; // Only the default one
+
+        if (!tags[i].name) {
+            av_log(s, AV_LOG_WARNING, "Skipping invalid tag with no TagName.\n");
+            continue;
+        }
+        for (int j = 0; j < mappings_list->nb_elem; j++) {
+            MatroskaBlockAdditionMapping *mapping = &mappings[j];
+            uint64_t id = mapping->value;
+            if (mappings_id != id)
+                continue;
+            av_log(s, AV_LOG_INFO, "blockaddid %"PRId64", title is %s\n", id, tags[i].string);
+            mapping->title = av_strdup(tags[i].string);
+        }
+    }
+}
+
 static void matroska_convert_tags(AVFormatContext *s)
 {
     MatroskaDemuxContext *matroska = s->priv_data;
@@ -1893,7 +1927,12 @@ static void matroska_convert_tags(AVFormatContext *s)
             for (j = 0; j < matroska->tracks.nb_elem; j++) {
                 if (track[j].uid == tags[i].target.trackuid &&
                     track[j].stream) {
-                    matroska_convert_tag(s, &tags[i].tag,
+                    if (tags[i].target.blockaddid) {
+                        matroska_convert_blockaddid_tag(s, &tags[i].tag,
+                                         &track->block_addition_mappings, tags[i].target.blockaddid);
+                    }
+                    else 
+                        matroska_convert_tag(s, &tags[i].tag,
                                          &track[j].stream->metadata, NULL);
                     found = 1;
                }
@@ -3467,6 +3506,17 @@ static int matroska_read_header(AVFormatContext *s)
 
     matroska_convert_tags(s);
 
+    MatroskaTrack *tracks = matroska->tracks.elem;
+    for (i = 0; i < matroska->tracks.nb_elem; i++) {
+        MatroskaTrack *track = &tracks[i];
+        const EbmlList *mappings_list = &track->block_addition_mappings;
+        MatroskaBlockAdditionMapping *mappings = mappings_list->elem;
+
+        for (int i = 0; i < mappings_list->nb_elem; i++) {
+            av_packet_side_data_add_s12m_timecode_to(s, &track->stream->codecpar->coded_side_data, &track->stream->codecpar->nb_coded_side_data, S12M_TIMECODE_FLAG_ID_PRESENT | S12M_TIMECODE_FLAG_TITLE_PRESENT, 0, mappings[i].value, mappings[i].title);
+        }
+    }
+
     return 0;
 }
 
@@ -3978,29 +4028,8 @@ static int matroska_parse_block_additional(MatroskaDemuxContext *matroska,
             break;
         }
 
-        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) * (1 + track->add_block_timecode_count);
-            sd = av_packet_new_side_data(pkt, AV_PKT_DATA_S12M_TIMECODE, sd_size);
-            count = 0;
-        }
-
-        if (count >= track->add_block_timecode_count) {
-            av_log(matroska->ctx, AV_LOG_DEBUG, "There are more timecodes in the block than the count indicated in the track header, extra timecodes are ignored.\n");
-        }
-        else if (sd) {
-            uint64_t tc = *((uint64_t*)data);
-            av_log(matroska->ctx, AV_LOG_DEBUG, "Reading SMPTE timecode from BlockAdditional: 0x%016lX (RFC 5484)\n", tc);
-
-            uint64_t *sd_64 = (uint64_t*)sd;
-            count++;
-            *sd_64 = count;
-            AV_WB64(sd_64 + count, tc);
-        }
-        
-        return 0;
+        uint64_t tc = AV_RB64(data);
+        return av_packet_add_s12m_timecode_to_side_data(matroska->ctx, pkt, S12M_TIMECODE_FLAG_ID_PRESENT | S12M_TIMECODE_FLAG_TITLE_PRESENT, tc, id, mapping->title);
     }
     default:
         break;
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c
index bbc80e1..d35a37d 100644
--- a/libavformat/matroskaenc.c
+++ b/libavformat/matroskaenc.c
@@ -203,9 +203,11 @@ typedef struct mkv_track {
     int64_t         last_timestamp;
     int64_t         duration;
     int64_t         duration_offset;
+    int64_t         more_tags_offset;
     uint64_t        max_blockaddid;
     int             itu_t_t35_count;
     int             timecode_count;
+    char            timecode_label[8][16];
     int64_t         blockadditionmapping_offset;
     int             codecpriv_offset;
     unsigned        codecpriv_size;     ///< size reserved for CodecPrivate excluding header+length field
@@ -3524,6 +3526,46 @@ static int mkv_write_tags(AVFormatContext *s)
             return ret;
         if (seekable)
             track->duration_offset = avio_tell(mkv->tags.bc) - DURATION_SIMPLETAG_SIZE;
+
+        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+        {
+            /*
+            AVIOContext *pb = mkv->tmp_bc;
+            ebml_master targets;
+
+            targets = start_ebml_master(pb, MATROSKA_ID_TAGTARGETS, 4 + 1 + 8);
+            put_ebml_uid(pb, MATROSKA_ID_TAGTARGETS_TRACKUID, track->uid);
+            put_ebml_uint(pb, MATROSKA_ID_TAGTARGETS_BLOCKADDID, 101);
+            end_ebml_master(pb, targets);
+            
+            targets = start_ebml_master(pb, MATROSKA_ID_SIMPLETAG, 4 + 1 + 8);
+            put_ebml_string(pb, MATROSKA_ID_TAGNAME, "VITC");
+            end_ebml_master(pb, targets);
+
+            
+
+            uint8_t *buf;
+            int ret = 0, size, tag_written = 0;
+
+            AVIOContext *const tmp_bc = pb;
+            size = avio_get_dyn_buf(tmp_bc, &buf);
+            if (tmp_bc->error) {
+                ret = tmp_bc->error;
+                //goto end;
+            }
+            if (!pb) {
+                ret = start_ebml_master_crc32(&pb, mkv);
+                //if (ret < 0)
+                    //goto end;
+            }
+            ffio_reset_dyn_buf(pb);
+            put_ebml_binary(mkv->tags.bc, MATROSKA_ID_TAG, buf, size);
+            */
+
+            track->more_tags_offset = avio_tell(mkv->tags.bc);
+            put_ebml_void(mkv->tags.bc, 8 * 32);
+
+        }
     }
 
     if (mkv->nb_attachments && !IS_WEBM(mkv)) {
@@ -4145,12 +4187,16 @@ static int mkv_write_block(void *logctx, MatroskaMuxContext *mkv,
     uint64_t count = 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;
-            count = side_data_64[0];
-            if (side_data_size / sizeof(uint64_t) - 1 >= count ) {
+        if (side_data && side_data_size >= 8) {
+
+            // header: 0-3 allocated size; 4 count; 5 byte size per item; 6 flags; 7 reserved
+            // flags: 0 id present; 1 title present
+            // content: 8 bytes tc; 4 bytes id; 16 bytes 0 terminated or 16 bytes UTF8 title
+
+            uint8_t count = *(side_data + 4);
+            uint8_t item_size = *(side_data + 5);
+            if (item_size && (side_data_size - 8) / item_size >= count ) {
                 uint64_t written_count = count;
-                side_data_64++;
                 if (count > MAX_MATROSKA_BLOCK_ADD_SMPTE_12M) {
                     if (count > track->timecode_count) {
                         av_log(logctx, AV_LOG_WARNING, "Too many SMPTE timecode streams in side data, discarding %"PRIu64" timecode streams.\n", count - MAX_MATROSKA_BLOCK_ADD_SMPTE_12M);
@@ -4158,10 +4204,15 @@ static int mkv_write_block(void *logctx, MatroskaMuxContext *mkv,
                     written_count = MAX_MATROSKA_BLOCK_ADD_SMPTE_12M;
                 }
                 for (uint64_t i = 0; i < written_count; i++) {
-                    uint64_t tc = side_data_64[i];
+                    uint8_t *sd_base = side_data + 8 + item_size * i;
+                    uint64_t *sd_tc = (uint64_t*)sd_base;
+                    uint32_t *sd_id = (uint32_t*)(sd_base + 8);
+                    char* sd_title = (char*)(sd_base + 8 + 4);
                     uint8_t *payload = timecode_buf[i];
-                    AV_WB64(payload, tc);
-                    av_log(logctx, AV_LOG_DEBUG, "Writing SMPTE timecode from side data, pos %"PRIu64", to BlockAdditional: 0x%016lX (RFC 5484)\n", i + 1, tc);
+                    AV_WB64(payload, *sd_tc);
+                    memcpy(track->timecode_label[i], sd_title, 16);
+                    
+                    av_log(logctx, AV_LOG_DEBUG, "Writing SMPTE timecode from side data, pos %"PRIu64", to BlockAdditional: 0x%016lX (RFC 5484)\n", i + 1, *sd_tc);
 
                     int blockaddid = MATROSKA_BLOCK_ADD_ID_SMPTE_12M + i;
                     mkv_write_blockadditional(&writer, payload, 8, blockaddid);
@@ -4639,6 +4690,50 @@ after_cues:
                 if (remaining_video_track_space > 1) {
                     put_ebml_void(track_bc, remaining_video_track_space);
                 }
+                
+                int remaining_bytes = 8 * 32;
+                AVIOContext *pb = mkv->tmp_bc;
+                
+                avio_seek(mkv->tags.bc, track->more_tags_offset, SEEK_SET);
+
+                for (int i = 0; i < track->timecode_count; i++) {
+                    if (!*track->timecode_label[i])
+                        continue;
+                    
+                    ebml_master targets;
+
+                    targets = start_ebml_master(pb, MATROSKA_ID_TAGTARGETS, 4 + 1 + 8);
+                    put_ebml_uid(pb, MATROSKA_ID_TAGTARGETS_TRACKUID, track->uid);
+                    put_ebml_uint(pb, MATROSKA_ID_TAGTARGETS_BLOCKADDID, 101 + i);
+                    end_ebml_master(pb, targets);
+
+                    targets = start_ebml_master(pb, MATROSKA_ID_SIMPLETAG, 4 + 1 + 8);
+                    put_ebml_string(pb, MATROSKA_ID_TAGNAME, "TITLE");
+                    put_ebml_string(pb, MATROSKA_ID_TAGSTRING, track->timecode_label[i]);
+                    end_ebml_master(pb, targets);
+
+
+
+                    uint8_t *buf;
+                    int ret = 0, size, tag_written = 0;
+
+                    AVIOContext *const tmp_bc = pb;
+                    size = avio_get_dyn_buf(tmp_bc, &buf);
+                    if (tmp_bc->error) {
+                        ret = tmp_bc->error;
+                        //goto end;
+                    }
+                    if (!pb) {
+                        ret = start_ebml_master_crc32(&pb, mkv);
+                        //if (ret < 0)
+                            //goto end;
+                    }
+                    ffio_reset_dyn_buf(pb);
+                    
+                    put_ebml_binary(mkv->tags.bc, MATROSKA_ID_TAG, buf, size);
+                    remaining_bytes -= 3 + size;
+                }
+                put_ebml_void(mkv->tags.bc, remaining_bytes);
             }
         }
 
diff --git a/libavutil/timecode.h b/libavutil/timecode.h
index 81713a9..6c4af1a 100644
--- a/libavutil/timecode.h
+++ b/libavutil/timecode.h
@@ -28,6 +28,8 @@
 #define AVUTIL_TIMECODE_H
 
 #include <stdint.h>
+#include "libavformat/avformat.h"
+#include "libavcodec/packet.h"
 #include "rational.h"
 
 #define AV_TIMECODE_STR_SIZE 23
-- 
2.52.0

