This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

commit 90dd8673ce3a6ad50f0872e6606d578840e7b1c4
Author:     James Almer <[email protected]>
AuthorDate: Tue Mar 17 09:33:49 2026 -0300
Commit:     James Almer <[email protected]>
CommitDate: Sun May 17 11:16:56 2026 -0300

    avformat/mov: handle all references in tref boxes
    
    tref types can have more than one value, as is the case of tmcd in
    fcp_export8-236.mov, where the single video track references all timecode
    tracks.
    Handle them in a generic and extensible way.
    
    Signed-off-by: James Almer <[email protected]>
---
 libavformat/isom.h                       |  13 ++-
 libavformat/mov.c                        | 168 +++++++++++++++++++++++++------
 tests/ref/fate/mov-mp4-with-mov-in24-ver |   5 +
 3 files changed, 150 insertions(+), 36 deletions(-)

diff --git a/libavformat/isom.h b/libavformat/isom.h
index d7e138585a..9b5437fb16 100644
--- a/libavformat/isom.h
+++ b/libavformat/isom.h
@@ -91,6 +91,12 @@ typedef struct MOVDref {
     int16_t nlvl_to, nlvl_from;
 } MOVDref;
 
+typedef struct MovTref {
+    uint32_t    name;
+    int         nb_id;
+    uint32_t   *id; ///< trackID of the referenced track
+} MovTref;
+
 typedef struct MOVAtom {
     uint32_t type;
     int64_t size; /* total size (excluding the size and type fields) */
@@ -222,9 +228,8 @@ typedef struct MOVStreamContext {
     unsigned drefs_count;
     MOVDref *drefs;
     int dref_id;
-    unsigned tref_flags;
-    int tref_id;
-    int timecode_track;
+    int nb_tref_tags;
+    MovTref *tref_tags;
     int width;            ///< tkhd width
     int height;           ///< tkhd height
     int h_spacing;        ///< pasp hSpacing
@@ -434,8 +439,6 @@ void ff_mp4_parse_es_descr(AVIOContext *pb, int *es_id);
 #define MOV_SAMPLE_DEPENDENCY_YES     0x1
 #define MOV_SAMPLE_DEPENDENCY_NO      0x2
 
-#define MOV_TREF_FLAG_ENHANCEMENT     0x1
-
 #define TAG_IS_AVCI(tag)                    \
     ((tag) == MKTAG('a', 'i', '5', 'p') ||  \
      (tag) == MKTAG('a', 'i', '5', 'q') ||  \
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 0d982e5a79..93e419113d 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -2547,6 +2547,58 @@ static int mov_read_dvc1(MOVContext *c, AVIOContext *pb, 
MOVAtom atom)
     return 0;
 }
 
+static int mov_find_tref_id(const MovTref *tag, uint32_t id)
+{
+    for (int i = 0; i < tag->nb_id; i++) {
+        if (tag->id[i] == id)
+            return 1;
+    }
+    return 0;
+}
+
+static int mov_add_tref_id(MovTref *tag, uint32_t id)
+{
+    int ret = mov_find_tref_id(tag, id);
+
+    if (!ret) {
+        uint32_t *tmp = av_realloc_array(tag->id, tag->nb_id + 1, 
sizeof(*tag->id));
+        if (!tmp)
+            return AVERROR(ENOMEM);
+        tag->id = tmp;
+        tag->id[tag->nb_id++] = id;
+    }
+
+    return 0;
+}
+
+static MovTref *mov_find_tref_tag(const MOVStreamContext *sc, uint32_t name)
+{
+    for (int i = 0; i < sc->nb_tref_tags; i++) {
+        MovTref *entry = &sc->tref_tags[i];
+
+        if (entry->name == name)
+            return entry;
+    }
+    return NULL;
+}
+
+static MovTref *mov_add_tref_tag(MOVStreamContext *sc, uint32_t name)
+{
+    MovTref *tag = mov_find_tref_tag(sc, name);
+
+    if (!tag) {
+        MovTref *tmp = av_realloc_array(sc->tref_tags, sc->nb_tref_tags + 1,
+                                       sizeof(*sc->tref_tags));
+        if (!tmp)
+            return NULL;
+        sc->tref_tags = tmp;
+        tag = &sc->tref_tags[sc->nb_tref_tags++];
+        *tag = (MovTref){ .name = name };
+    }
+
+    return tag;
+}
+
 static int mov_read_sbas(MOVContext* c, AVIOContext* pb, MOVAtom atom)
 {
     AVStream* st;
@@ -2562,11 +2614,19 @@ static int mov_read_sbas(MOVContext* c, AVIOContext* 
pb, MOVAtom atom)
         av_log(c->fc, AV_LOG_ERROR, "Only a single tref of type sbas is 
supported\n");
         return AVERROR_PATCHWELCOME;
     }
+    if (atom.size < 4)
+        return AVERROR_INVALIDDATA;
 
     st = c->fc->streams[c->fc->nb_streams - 1];
     sc = st->priv_data;
-    sc->tref_id = avio_rb32(pb);
-    sc->tref_flags |= MOV_TREF_FLAG_ENHANCEMENT;
+
+    MovTref *tag = mov_add_tref_tag(sc, atom.type);
+    if (!tag)
+        return AVERROR(ENOMEM);
+
+    int ret = mov_add_tref_id(tag, avio_rb32(pb));
+    if (ret < 0)
+        return ret;
 
     return 0;
 }
@@ -5210,8 +5270,6 @@ static int mov_read_trak(MOVContext *c, AVIOContext *pb, 
MOVAtom atom)
     st->codecpar->codec_type = AVMEDIA_TYPE_DATA;
     sc->ffindex = st->index;
     c->trak_index = st->index;
-    sc->tref_flags = 0;
-    sc->tref_id = -1;
     sc->refcount = 1;
 
     if ((ret = mov_read_default(c, pb, atom)) < 0)
@@ -6403,11 +6461,28 @@ static int mov_read_elst(MOVContext *c, AVIOContext 
*pb, MOVAtom atom)
 static int mov_read_tmcd(MOVContext *c, AVIOContext *pb, MOVAtom atom)
 {
     MOVStreamContext *sc;
+    int err;
 
     if (c->fc->nb_streams < 1)
         return AVERROR_INVALIDDATA;
     sc = c->fc->streams[c->fc->nb_streams - 1]->priv_data;
-    sc->timecode_track = avio_rb32(pb);
+
+    if (atom.size % sizeof(uint32_t))
+        return AVERROR_INVALIDDATA;
+
+    MovTref *tag = mov_add_tref_tag(sc, atom.type);
+    if (!tag)
+        return AVERROR(ENOMEM);
+
+    int nb_timecode_track = atom.size >> 2;
+    for (int i = 0; i < nb_timecode_track; i++) {
+        if (avio_feof(pb))
+            return AVERROR_INVALIDDATA;
+        err = mov_add_tref_id(tag, avio_rb32(pb));
+        if (err < 0)
+            return err;
+    }
+
     return 0;
 }
 
@@ -10104,6 +10179,9 @@ static void mov_free_stream_context(AVFormatContext *s, 
AVStream *st)
     av_freep(&sc->open_key_samples);
     av_freep(&sc->display_matrix);
     av_freep(&sc->index_ranges);
+    for (int i = 0; i < sc->nb_tref_tags; i++)
+        av_freep(&sc->tref_tags[i].id);
+    av_freep(&sc->tref_tags);
 
     if (sc->extradata)
         for (int i = 0; i < sc->stsd_count; i++)
@@ -10190,9 +10268,11 @@ static int tmcd_is_referenced(AVFormatContext *s, int 
tmcd_id)
     for (i = 0; i < s->nb_streams; i++) {
         AVStream *st = s->streams[i];
         MOVStreamContext *sc = st->priv_data;
+        MovTref *tag = mov_find_tref_tag(sc, MKTAG('t','m','c','d'));
 
-        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
-            sc->timecode_track == tmcd_id)
+        if (!tag)
+            continue;
+        if (mov_find_tref_id(tag, tmcd_id))
             return 1;
     }
     return 0;
@@ -10207,7 +10287,7 @@ static void export_orphan_timecode(AVFormatContext *s)
         AVStream *st = s->streams[i];
 
         if (st->codecpar->codec_tag  == MKTAG('t','m','c','d') &&
-            !tmcd_is_referenced(s, i + 1)) {
+            !tmcd_is_referenced(s, st->id)) {
             AVDictionaryEntry *tcr = av_dict_get(st->metadata, "timecode", 
NULL, 0);
             if (tcr) {
                 av_dict_set(&s->metadata, "timecode", tcr->value, 0);
@@ -10799,17 +10879,16 @@ static int mov_parse_heif_items(AVFormatContext *s)
 }
 
 static AVStream *mov_find_reference_track(AVFormatContext *s, AVStream *st,
-                                          int first_index)
+                                          uint32_t *tref_id, int nb_tref_id, 
int first_index)
 {
-    MOVStreamContext *sc = st->priv_data;
-
-    if (sc->tref_id < 0)
+    if (!nb_tref_id)
         return NULL;
 
     for (int i = first_index; i < s->nb_streams; i++)
-        if (s->streams[i]->index != st->index &&
-            s->streams[i]->id == sc->tref_id)
-            return s->streams[i];
+        for (int j = 0; j < nb_tref_id; j++)
+            if (s->streams[i]->index != st->index &&
+                s->streams[i]->id == tref_id[j])
+                return s->streams[i];
 
     return NULL;
 }
@@ -10827,11 +10906,11 @@ static int mov_parse_lcevc_streams(AVFormatContext *s)
         AVStream *st = s->streams[i];
         AVStream *st_base;
         MOVStreamContext *sc = st->priv_data;
+        MovTref *tag = mov_find_tref_tag(sc, MKTAG('s','b','a','s'));
         int j = 0;
 
         /* Find an enhancement stream. */
-        if (st->codecpar->codec_id != AV_CODEC_ID_LCEVC ||
-            !(sc->tref_flags & MOV_TREF_FLAG_ENHANCEMENT))
+        if (st->codecpar->codec_id != AV_CODEC_ID_LCEVC || !tag)
             continue;
 
         stg = avformat_stream_group_create(s, AV_STREAM_GROUP_PARAMS_LCEVC, 
NULL);
@@ -10842,7 +10921,7 @@ static int mov_parse_lcevc_streams(AVFormatContext *s)
         stg->params.lcevc->width  = st->codecpar->width;
         stg->params.lcevc->height = st->codecpar->height;
 
-        while (st_base = mov_find_reference_track(s, st, j)) {
+        while (st_base = mov_find_reference_track(s, st, tag->id, tag->nb_id, 
j)) {
             err = avformat_stream_group_add_stream(stg, st_base);
             if (err < 0)
                 return err;
@@ -10963,24 +11042,51 @@ static int mov_read_header(AVFormatContext *s)
 
     /* copy timecode metadata from tmcd tracks to the related video streams */
     for (i = 0; i < s->nb_streams; i++) {
+        AVStreamGroup *stg;
+        const AVDictionaryEntry *tcr;
         AVStream *st = s->streams[i];
-        MOVStreamContext *sc = st->priv_data;
-        if (sc->timecode_track > 0) {
-            AVDictionaryEntry *tcr;
-            int tmcd_st_id = -1;
 
-            for (j = 0; j < s->nb_streams; j++) {
-                MOVStreamContext *sc2 = s->streams[j]->priv_data;
-                if (sc2->id == sc->timecode_track)
-                    tmcd_st_id = j;
-            }
+        if (st->codecpar->codec_tag != MKTAG('t','m','c','d'))
+            continue;
+
+        stg = avformat_stream_group_create(s, AV_STREAM_GROUP_PARAMS_TREF, 
NULL);
+        if (!stg)
+            return AVERROR(ENOMEM);
+
+        stg->id = st->id;
+        tcr = av_dict_get(st->metadata, "timecode", NULL, 0);
+
+        for (int j = 0; j < s->nb_streams; j++) {
+            AVStream *st2 = s->streams[j];
+            MOVStreamContext *sc2 = st2->priv_data;
+            MovTref *tag = mov_find_tref_tag(sc2, MKTAG('t','m','c','d'));
 
-            if (tmcd_st_id < 0 || tmcd_st_id == i)
+            if (!tag)
                 continue;
-            tcr = av_dict_get(s->streams[tmcd_st_id]->metadata, "timecode", 
NULL, 0);
-            if (tcr)
-                av_dict_set(&st->metadata, "timecode", tcr->value, 0);
+
+            for (int k = 0; k < tag->nb_id; k++) {
+                if (tag->id[k] != st->id)
+                    continue;
+
+                err = avformat_stream_group_add_stream(stg, st2);
+                if (err < 0)
+                    return err;
+
+                if (tcr)
+                    av_dict_set(&st2->metadata, "timecode", tcr->value, 
AV_DICT_DONT_OVERWRITE);
+            }
         }
+
+        if (!stg->nb_streams) {
+            ff_remove_stream_group(s, stg);
+            continue;
+        }
+
+        err = avformat_stream_group_add_stream(stg, st);
+        if (err < 0)
+            return err;
+
+        stg->params.tref->metadata_index = stg->nb_streams - 1;
     }
     export_orphan_timecode(s);
 
diff --git a/tests/ref/fate/mov-mp4-with-mov-in24-ver 
b/tests/ref/fate/mov-mp4-with-mov-in24-ver
index b5522454df..450560b149 100644
--- a/tests/ref/fate/mov-mp4-with-mov-in24-ver
+++ b/tests/ref/fate/mov-mp4-with-mov-in24-ver
@@ -1,3 +1,8 @@
+[STREAM_GROUP]
+[STREAM]
+codec_name=pcm_s24le
+[/STREAM]
+[/STREAM_GROUP]
 [STREAM]
 codec_name=pcm_s24le
 [/STREAM]

_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to