avformat/mov: Fix integer overflows related to sample_duration
[ffmpeg.git] / libavformat / mov.c
index fdba34c..39ad697 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "libavutil/attributes.h"
 #include "libavutil/channel_layout.h"
+#include "libavutil/internal.h"
 #include "libavutil/intreadwrite.h"
 #include "libavutil/intfloat.h"
 #include "libavutil/mathematics.h"
@@ -418,6 +419,12 @@ retry:
             snprintf(key2, sizeof(key2), "%s-%s", key, language);
             av_dict_set(&c->fc->metadata, key2, str, 0);
         }
+        if (!strcmp(key, "encoder")) {
+            int major, minor, micro;
+            if (sscanf(str, "HandBrake %d.%d.%d", &major, &minor, &micro) == 3) {
+                c->handbrake_version = 1000000*major + 1000*minor + micro;
+            }
+        }
     }
     av_log(c->fc, AV_LOG_TRACE, "lang \"%3s\" ", language);
     av_log(c->fc, AV_LOG_TRACE, "tag \"%s\" value \"%s\" atom \"%.4s\" %d %"PRId64"\n",
@@ -628,6 +635,8 @@ static int mov_read_hdlr(MOVContext *c, AVIOContext *pb, MOVAtom atom)
 
     title_size = atom.size - 24;
     if (title_size > 0) {
+        if (title_size > FFMIN(INT_MAX, SIZE_MAX-1))
+            return AVERROR_INVALIDDATA;
         title_str = av_malloc(title_size + 1); /* Add null terminator */
         if (!title_str)
             return AVERROR(ENOMEM);
@@ -837,9 +846,9 @@ static int mov_read_adrm(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     }
 
     /* drm blob processing */
-    avio_read(pb, output, 8); // go to offset 8, absolute postion 0x251
+    avio_read(pb, output, 8); // go to offset 8, absolute position 0x251
     avio_read(pb, input, DRM_BLOB_SIZE);
-    avio_read(pb, output, 4); // go to offset 4, absolute postion 0x28d
+    avio_read(pb, output, 4); // go to offset 4, absolute position 0x28d
     avio_read(pb, file_checksum, 20);
 
     av_log(c->fc, AV_LOG_INFO, "[aax] file checksum == "); // required by external tools
@@ -848,9 +857,9 @@ static int mov_read_adrm(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     av_log(c->fc, AV_LOG_INFO, "\n");
 
     /* verify activation data */
-    if (!activation_bytes || c->activation_bytes_size != 4) {
-        av_log(c->fc, AV_LOG_FATAL, "[aax] activation_bytes option is missing!\n");
-        ret = AVERROR(EINVAL);
+    if (!activation_bytes) {
+        av_log(c->fc, AV_LOG_WARNING, "[aax] activation_bytes option is missing!\n");
+        ret = 0;  /* allow ffprobe to continue working on .aax files */
         goto fail;
     }
     if (c->activation_bytes_size != 4) {
@@ -1393,7 +1402,7 @@ static int mov_read_aclr(MOVContext *c, AVIOContext *pb, MOVAtom atom)
                         av_log(c, AV_LOG_WARNING, "ignored unknown aclr value (%d)\n", range_value);
                         break;
                     }
-                    av_dlog(c, "color_range: %d\n", codec->color_range);
+                    ff_dlog(c, "color_range: %d\n", codec->color_range);
                 } else {
                   /* For some reason the whole atom was not added to the extradata */
                   av_log(c, AV_LOG_ERROR, "aclr not decoded - incomplete atom\n");
@@ -1435,6 +1444,32 @@ static int mov_read_wave(MOVContext *c, AVIOContext *pb, MOVAtom atom)
         if (ret < 0)
             return ret;
     } else if (atom.size > 8) { /* to read frma, esds atoms */
+        if (st->codec->codec_id == AV_CODEC_ID_ALAC && atom.size >= 24) {
+            uint64_t buffer;
+            ret = ffio_ensure_seekback(pb, 8);
+            if (ret < 0)
+                return ret;
+            buffer = avio_rb64(pb);
+            atom.size -= 8;
+            if (  (buffer & 0xFFFFFFFF) == MKBETAG('f','r','m','a')
+                && buffer >> 32 <= atom.size
+                && buffer >> 32 >= 8) {
+                avio_skip(pb, -8);
+                atom.size += 8;
+            } else if (!st->codec->extradata_size) {
+#define ALAC_EXTRADATA_SIZE 36
+                st->codec->extradata = av_mallocz(ALAC_EXTRADATA_SIZE + AV_INPUT_BUFFER_PADDING_SIZE);
+                if (!st->codec->extradata)
+                    return AVERROR(ENOMEM);
+                st->codec->extradata_size = ALAC_EXTRADATA_SIZE;
+                AV_WB32(st->codec->extradata    , ALAC_EXTRADATA_SIZE);
+                AV_WB32(st->codec->extradata + 4, MKTAG('a','l','a','c'));
+                AV_WB64(st->codec->extradata + 12, buffer);
+                avio_read(pb, st->codec->extradata + 20, 16);
+                avio_skip(pb, atom.size - 24);
+                return 0;
+            }
+        }
         if ((ret = mov_read_default(c, pb, atom)) < 0)
             return ret;
     } else
@@ -1683,7 +1718,7 @@ static void mov_parse_stsd_video(MOVContext *c, AVIOContext *pb,
     if ((color_depth == 2) || (color_depth == 4) || (color_depth == 8)) {
         /* for palette traversal */
         unsigned int color_start, color_count, color_end;
-        unsigned char a, r, g, b;
+        unsigned int a, r, g, b;
 
         if (color_greyscale) {
             int color_index, color_dec;
@@ -2171,6 +2206,8 @@ static int mov_read_stsc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     avio_rb24(pb); /* flags */
 
     entries = avio_rb32(pb);
+    if ((uint64_t)entries * 12 + 4 > atom.size)
+        return AVERROR_INVALIDDATA;
 
     av_log(c->fc, AV_LOG_TRACE, "track[%i].stsc.entries = %i\n", c->fc->nb_streams-1, entries);
 
@@ -2417,14 +2454,19 @@ static int mov_read_stts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
             && total_sample_count > 100
             && sample_duration/10 > duration / total_sample_count)
             sample_duration = duration / total_sample_count;
-        duration+=(int64_t)sample_duration*sample_count;
+        duration+=(int64_t)sample_duration*(uint64_t)sample_count;
         total_sample_count+=sample_count;
     }
 
     sc->stts_count = i;
 
-    sc->duration_for_fps  += duration;
-    sc->nb_frames_for_fps += total_sample_count;
+    if (duration > 0 &&
+        duration <= INT64_MAX - sc->duration_for_fps &&
+        total_sample_count <= INT64_MAX - sc->nb_frames_for_fps
+    ) {
+        sc->duration_for_fps  += duration;
+        sc->nb_frames_for_fps += total_sample_count;
+    }
 
     if (pb->eof_reached)
         return AVERROR_EOF;
@@ -2479,7 +2521,7 @@ static int mov_read_ctts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
         av_log(c->fc, AV_LOG_TRACE, "count=%d, duration=%d\n",
                 count, duration);
 
-        if (FFABS(duration) > (1<<28) && i+2<entries) {
+        if (FFNABS(duration) < -(1<<28) && i+2<entries) {
             av_log(c->fc, AV_LOG_WARNING, "CTTS invalid\n");
             av_freep(&sc->ctts_data);
             sc->ctts_count = 0;
@@ -2671,7 +2713,12 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
                 sample_size = sc->stsz_sample_size > 0 ? sc->stsz_sample_size : sc->sample_sizes[current_sample];
                 if (sc->pseudo_stream_id == -1 ||
                    sc->stsc_data[stsc_index].id - 1 == sc->pseudo_stream_id) {
-                    AVIndexEntry *e = &st->index_entries[st->nb_index_entries++];
+                    AVIndexEntry *e;
+                    if (sample_size > 0x3FFFFFFF) {
+                        av_log(mov->fc, AV_LOG_ERROR, "Sample size %u is too large\n", sample_size);
+                        return;
+                    }
+                    e = &st->index_entries[st->nb_index_entries++];
                     e->pos = current_offset;
                     e->timestamp = current_dts;
                     e->size = sample_size;
@@ -2796,6 +2843,10 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
                     av_log(mov->fc, AV_LOG_ERROR, "wrong chunk count %d\n", total);
                     return;
                 }
+                if (size > 0x3FFFFFFF) {
+                    av_log(mov->fc, AV_LOG_ERROR, "Sample size %u is too large\n", size);
+                    return;
+                }
                 e = &st->index_entries[st->nb_index_entries++];
                 e->pos = current_offset;
                 e->timestamp = current_dts;
@@ -2969,13 +3020,23 @@ static int mov_read_trak(MOVContext *c, AVIOContext *pb, MOVAtom atom)
 
     if (sc->dref_id-1 < sc->drefs_count && sc->drefs[sc->dref_id-1].path) {
         MOVDref *dref = &sc->drefs[sc->dref_id - 1];
-        if (mov_open_dref(c, &sc->pb, c->fc->filename, dref,
-                          &c->fc->interrupt_callback) < 0)
-            av_log(c->fc, AV_LOG_ERROR,
-                   "stream %d, error opening alias: path='%s', dir='%s', "
-                   "filename='%s', volume='%s', nlvl_from=%d, nlvl_to=%d\n",
+        if (c->enable_drefs) {
+            if (mov_open_dref(c, &sc->pb, c->fc->filename, dref,
+                              &c->fc->interrupt_callback) < 0)
+                av_log(c->fc, AV_LOG_ERROR,
+                       "stream %d, error opening alias: path='%s', dir='%s', "
+                       "filename='%s', volume='%s', nlvl_from=%d, nlvl_to=%d\n",
+                       st->index, dref->path, dref->dir, dref->filename,
+                       dref->volume, dref->nlvl_from, dref->nlvl_to);
+        } else {
+            av_log(c->fc, AV_LOG_WARNING,
+                   "Skipped opening external track: "
+                   "stream %d, alias: path='%s', dir='%s', "
+                   "filename='%s', volume='%s', nlvl_from=%d, nlvl_to=%d."
+                   "Set enable_drefs to allow this.\n",
                    st->index, dref->path, dref->dir, dref->filename,
                    dref->volume, dref->nlvl_from, dref->nlvl_to);
+        }
     } else {
         sc->pb = c->fc->pb;
         sc->pb_is_copied = 1;
@@ -3267,7 +3328,7 @@ static int mov_read_tfhd(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     MOVFragment *frag = &c->fragment;
     MOVTrackExt *trex = NULL;
     MOVFragmentIndex* index = NULL;
-    int flags, track_id, i;
+    int flags, track_id, i, found = 0;
 
     avio_r8(pb); /* version */
     flags = avio_rb24(pb);
@@ -3285,15 +3346,6 @@ static int mov_read_tfhd(MOVContext *c, AVIOContext *pb, MOVAtom atom)
         av_log(c->fc, AV_LOG_ERROR, "could not find corresponding trex\n");
         return AVERROR_INVALIDDATA;
     }
-    for (i = 0; i < c->fragment_index_count; i++) {
-        MOVFragmentIndex* candidate = c->fragment_index_data[i];
-        if (candidate->track_id == frag->track_id) {
-            av_log(c->fc, AV_LOG_DEBUG,
-                   "found fragment index for track %u\n", frag->track_id);
-            index = candidate;
-            break;
-        }
-    }
 
     frag->base_data_offset = flags & MOV_TFHD_BASE_DATA_OFFSET ?
                              avio_rb64(pb) : flags & MOV_TFHD_DEFAULT_BASE_IS_MOOF ?
@@ -3307,23 +3359,32 @@ static int mov_read_tfhd(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     frag->flags    = flags & MOV_TFHD_DEFAULT_FLAGS ?
                      avio_rb32(pb) : trex->flags;
     frag->time     = AV_NOPTS_VALUE;
-    if (index) {
-        int i, found = 0;
-        for (i = index->current_item; i < index->item_count; i++) {
-            if (frag->implicit_offset == index->items[i].moof_offset) {
-                av_log(c->fc, AV_LOG_DEBUG, "found fragment index entry "
-                        "for track %u and moof_offset %"PRId64"\n",
-                        frag->track_id, index->items[i].moof_offset);
-                frag->time = index->items[i].time;
-                index->current_item = i + 1;
-                found = 1;
+    for (i = 0; i < c->fragment_index_count; i++) {
+        int j;
+        MOVFragmentIndex* candidate = c->fragment_index_data[i];
+        if (candidate->track_id == frag->track_id) {
+            av_log(c->fc, AV_LOG_DEBUG,
+                   "found fragment index for track %u\n", frag->track_id);
+            index = candidate;
+            for (j = index->current_item; j < index->item_count; j++) {
+                if (frag->implicit_offset == index->items[j].moof_offset) {
+                    av_log(c->fc, AV_LOG_DEBUG, "found fragment index entry "
+                            "for track %u and moof_offset %"PRId64"\n",
+                            frag->track_id, index->items[j].moof_offset);
+                    frag->time = index->items[j].time;
+                    index->current_item = j + 1;
+                    found = 1;
+                    break;
+                }
             }
+            if (found)
+                break;
         }
-        if (!found) {
-            av_log(c->fc, AV_LOG_WARNING, "track %u has a fragment index "
-                   "but it doesn't have an (in-order) entry for moof_offset "
-                   "%"PRId64"\n", frag->track_id, frag->implicit_offset);
-        }
+    }
+    if (index && !found) {
+        av_log(c->fc, AV_LOG_DEBUG, "track %u has a fragment index but "
+               "it doesn't have an (in-order) entry for moof_offset "
+               "%"PRId64"\n", frag->track_id, frag->implicit_offset);
     }
     av_log(c->fc, AV_LOG_TRACE, "frag flags 0x%x\n", frag->flags);
     return 0;
@@ -3479,7 +3540,7 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom)
                 }
                 av_log(c->fc, AV_LOG_DEBUG, "calculated into dts %"PRId64"\n", dts);
             } else {
-                dts = frag->time;
+                dts = frag->time - sc->time_offset;
                 av_log(c->fc, AV_LOG_DEBUG, "found frag time %"PRId64
                         ", using it for dts\n", dts);
             }
@@ -3506,15 +3567,119 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom)
         dts += sample_duration;
         offset += sample_size;
         sc->data_size += sample_size;
-        sc->duration_for_fps += sample_duration;
-        sc->nb_frames_for_fps ++;
+
+        if (sample_duration <= INT64_MAX - sc->duration_for_fps &&
+            1 <= INT64_MAX - sc->nb_frames_for_fps
+        ) {
+            sc->duration_for_fps += sample_duration;
+            sc->nb_frames_for_fps ++;
+        }
     }
 
     if (pb->eof_reached)
         return AVERROR_EOF;
 
     frag->implicit_offset = offset;
-    st->duration = sc->track_end = dts + sc->time_offset;
+
+    sc->track_end = dts + sc->time_offset;
+    if (st->duration < sc->track_end)
+        st->duration = sc->track_end;
+
+    return 0;
+}
+
+static int mov_read_sidx(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+    int64_t offset = avio_tell(pb) + atom.size, pts;
+    uint8_t version;
+    unsigned i, track_id;
+    AVStream *st = NULL;
+    MOVStreamContext *sc;
+    MOVFragmentIndex *index = NULL;
+    MOVFragmentIndex **tmp;
+    AVRational timescale;
+
+    version = avio_r8(pb);
+    if (version > 1) {
+        avpriv_request_sample(c->fc, "sidx version %u", version);
+        return 0;
+    }
+
+    avio_rb24(pb); // flags
+
+    track_id = avio_rb32(pb); // Reference ID
+    for (i = 0; i < c->fc->nb_streams; i++) {
+        if (c->fc->streams[i]->id == track_id) {
+            st = c->fc->streams[i];
+            break;
+        }
+    }
+    if (!st) {
+        av_log(c->fc, AV_LOG_WARNING, "could not find corresponding track id %d\n", track_id);
+        return 0;
+    }
+
+    sc = st->priv_data;
+
+    timescale = av_make_q(1, avio_rb32(pb));
+
+    if (version == 0) {
+        pts = avio_rb32(pb);
+        offset += avio_rb32(pb);
+    } else {
+        pts = avio_rb64(pb);
+        offset += avio_rb64(pb);
+    }
+
+    avio_rb16(pb); // reserved
+
+    index = av_mallocz(sizeof(MOVFragmentIndex));
+    if (!index)
+        return AVERROR(ENOMEM);
+
+    index->track_id = track_id;
+
+    index->item_count = avio_rb16(pb);
+    index->items = av_mallocz_array(index->item_count, sizeof(MOVFragmentIndexItem));
+
+    if (!index->items) {
+        av_freep(&index);
+        return AVERROR(ENOMEM);
+    }
+
+    for (i = 0; i < index->item_count; i++) {
+        uint32_t size = avio_rb32(pb);
+        uint32_t duration = avio_rb32(pb);
+        if (size & 0x80000000) {
+            avpriv_request_sample(c->fc, "sidx reference_type 1");
+            av_freep(&index->items);
+            av_freep(&index);
+            return AVERROR_PATCHWELCOME;
+        }
+        avio_rb32(pb); // sap_flags
+        index->items[i].moof_offset = offset;
+        index->items[i].time = av_rescale_q(pts, st->time_base, timescale);
+        offset += size;
+        pts += duration;
+    }
+
+    st->duration = sc->track_end = pts;
+
+    tmp = av_realloc_array(c->fragment_index_data,
+                           c->fragment_index_count + 1,
+                           sizeof(MOVFragmentIndex*));
+    if (!tmp) {
+        av_freep(&index->items);
+        av_freep(&index);
+        return AVERROR(ENOMEM);
+    }
+
+    c->fragment_index_data = tmp;
+    c->fragment_index_data[c->fragment_index_count++] = index;
+
+    if (offset == avio_size(pb))
+        c->fragment_index_complete = 1;
+
     return 0;
 }
 
@@ -3657,7 +3822,7 @@ static int mov_read_uuid(MOVContext *c, AVIOContext *pb, MOVAtom atom)
         0xba, 0x2f, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66
     };
 
-    if (atom.size < sizeof(uuid) || atom.size == INT64_MAX)
+    if (atom.size < sizeof(uuid) || atom.size >= FFMIN(INT_MAX, SIZE_MAX))
         return AVERROR_INVALIDDATA;
 
     ret = avio_read(pb, uuid, sizeof(uuid));
@@ -3772,6 +3937,7 @@ static const MOVParseTableEntry mov_default_parse_table[] = {
 { MKTAG('a','l','a','c'), mov_read_alac }, /* alac specific atom */
 { MKTAG('a','v','c','C'), mov_read_glbl },
 { MKTAG('p','a','s','p'), mov_read_pasp },
+{ MKTAG('s','i','d','x'), mov_read_sidx },
 { MKTAG('s','t','b','l'), mov_read_default },
 { MKTAG('s','t','c','o'), mov_read_stco },
 { MKTAG('s','t','p','s'), mov_read_stps },
@@ -3825,7 +3991,7 @@ static int mov_read_default(MOVContext *c, AVIOContext *pb, MOVAtom atom)
 
     if (atom.size < 0)
         atom.size = INT64_MAX;
-    while (total_size + 8 <= atom.size && !avio_feof(pb)) {
+    while (total_size <= atom.size - 8 && !avio_feof(pb)) {
         int (*parse)(MOVContext*, AVIOContext*, MOVAtom) = NULL;
         a.size = atom.size;
         a.type=0;
@@ -3895,9 +4061,9 @@ static int mov_read_default(MOVContext *c, AVIOContext *pb, MOVAtom atom)
                 return err;
             }
             if (c->found_moov && c->found_mdat &&
-                ((!pb->seekable || c->fc->flags & AVFMT_FLAG_IGNIDX) ||
+                ((!pb->seekable || c->fc->flags & AVFMT_FLAG_IGNIDX || c->fragment_index_complete) ||
                  start_pos + a.size == avio_size(pb))) {
-                if (!pb->seekable || c->fc->flags & AVFMT_FLAG_IGNIDX)
+                if (!pb->seekable || c->fc->flags & AVFMT_FLAG_IGNIDX || c->fragment_index_complete)
                     c->next_root_atom = start_pos + a.size;
                 c->atom_depth --;
                 return 0;
@@ -4254,6 +4420,13 @@ static int read_tfra(MOVContext *mov, AVIOContext *f)
     }
     for (i = 0; i < index->item_count; i++) {
         int64_t time, offset;
+
+        if (avio_feof(f)) {
+            index->item_count = 0;
+            av_freep(&index->items);
+            return AVERROR_INVALIDDATA;
+        }
+
         if (version == 1) {
             time   = avio_rb64(f);
             offset = avio_rb64(f);
@@ -4401,6 +4574,13 @@ static int mov_read_header(AVFormatContext *s)
                     return err;
             }
         }
+        if (mov->handbrake_version &&
+            mov->handbrake_version <= 1000000*0 + 1000*10 + 2 &&  // 0.10.2
+            st->codec->codec_id == AV_CODEC_ID_MP3
+        ) {
+            av_log(s, AV_LOG_VERBOSE, "Forcing full parsing for mp3 stream\n");
+            st->need_parsing = AVSTREAM_PARSE_FULL;
+        }
     }
 
     if (mov->trex_data) {
@@ -4502,6 +4682,55 @@ static int should_retry(AVIOContext *pb, int error_code) {
     return 1;
 }
 
+static int mov_switch_root(AVFormatContext *s, int64_t target)
+{
+    int ret;
+    MOVContext *mov = s->priv_data;
+    int i, j;
+    int already_read = 0;
+
+    if (avio_seek(s->pb, target, SEEK_SET) != target) {
+        av_log(mov->fc, AV_LOG_ERROR, "root atom offset 0x%"PRIx64": partial file\n", target);
+        return AVERROR_INVALIDDATA;
+    }
+
+    mov->next_root_atom = 0;
+
+    for (i = 0; i < mov->fragment_index_count; i++) {
+        MOVFragmentIndex *index = mov->fragment_index_data[i];
+        int found = 0;
+        for (j = 0; j < index->item_count; j++) {
+            MOVFragmentIndexItem *item = &index->items[j];
+            if (found) {
+                mov->next_root_atom = item->moof_offset;
+                break; // Advance to next index in outer loop
+            } else if (item->moof_offset == target) {
+                index->current_item = FFMIN(j, index->current_item);
+                if (item->headers_read)
+                    already_read = 1;
+                item->headers_read = 1;
+                found = 1;
+            }
+        }
+        if (!found)
+            index->current_item = 0;
+    }
+
+    if (already_read)
+        return 0;
+
+    mov->found_mdat = 0;
+
+    ret = mov_read_default(mov, s->pb, (MOVAtom){ AV_RL32("root"), INT64_MAX });
+    if (ret < 0)
+        return ret;
+    if (avio_feof(s->pb))
+        return AVERROR_EOF;
+    av_log(s, AV_LOG_TRACE, "read fragments, offset 0x%"PRIx64"\n", avio_tell(s->pb));
+
+    return 1;
+}
+
 static int mov_read_packet(AVFormatContext *s, AVPacket *pkt)
 {
     MOVContext *mov = s->priv_data;
@@ -4512,19 +4741,11 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt)
     mov->fc = s;
  retry:
     sample = mov_find_next_sample(s, &st);
-    if (!sample) {
-        mov->found_mdat = 0;
+    if (!sample || (mov->next_root_atom && sample->pos > mov->next_root_atom)) {
         if (!mov->next_root_atom)
             return AVERROR_EOF;
-        if (avio_seek(s->pb, mov->next_root_atom, SEEK_SET) != mov->next_root_atom) {
-            av_log(mov->fc, AV_LOG_ERROR, "next root atom offset 0x%"PRIx64": partial file\n", mov->next_root_atom);
-            return AVERROR_INVALIDDATA;
-        }
-        mov->next_root_atom = 0;
-        if (mov_read_default(mov, s->pb, (MOVAtom){ AV_RL32("root"), INT64_MAX }) < 0 ||
-            avio_feof(s->pb))
-            return AVERROR_EOF;
-        av_log(s, AV_LOG_TRACE, "read fragments, offset 0x%"PRIx64"\n", avio_tell(s->pb));
+        if ((ret = mov_switch_root(s, mov->next_root_atom)) < 0)
+            return ret;
         goto retry;
     }
     sc = st->priv_data;
@@ -4602,12 +4823,41 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt)
     return 0;
 }
 
+static int mov_seek_fragment(AVFormatContext *s, AVStream *st, int64_t timestamp)
+{
+    MOVContext *mov = s->priv_data;
+    int i, j;
+
+    if (!mov->fragment_index_complete)
+        return 0;
+
+    for (i = 0; i < mov->fragment_index_count; i++) {
+        if (mov->fragment_index_data[i]->track_id == st->id) {
+            MOVFragmentIndex *index = index = mov->fragment_index_data[i];
+            for (j = index->item_count - 1; j >= 0; j--) {
+                if (index->items[j].time <= timestamp) {
+                    if (index->items[j].headers_read)
+                        return 0;
+
+                    return mov_switch_root(s, index->items[j].moof_offset);
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
 static int mov_seek_stream(AVFormatContext *s, AVStream *st, int64_t timestamp, int flags)
 {
     MOVStreamContext *sc = st->priv_data;
     int sample, time_sample;
     int i;
 
+    int ret = mov_seek_fragment(s, st, timestamp);
+    if (ret < 0)
+        return ret;
+
     sample = av_index_search_timestamp(st, timestamp, flags);
     av_log(s, AV_LOG_TRACE, "stream %d, timestamp %"PRId64", sample %d\n", st->index, timestamp, sample);
     if (sample < 0 && st->nb_index_entries && timestamp < st->index_entries[0].timestamp)
@@ -4689,17 +4939,17 @@ static int mov_read_seek(AVFormatContext *s, int stream_index, int64_t sample_ti
 static const AVOption mov_options[] = {
     {"use_absolute_path",
         "allow using absolute path when opening alias, this is a possible security issue",
-        OFFSET(use_absolute_path), FF_OPT_TYPE_INT, {.i64 = 0},
+        OFFSET(use_absolute_path), AV_OPT_TYPE_INT, {.i64 = 0},
         0, 1, FLAGS},
     {"seek_streams_individually",
         "Seek each stream individually to the to the closest point",
         OFFSET(seek_individually), AV_OPT_TYPE_INT, { .i64 = 1 },
         0, 1, FLAGS},
-    {"ignore_editlist", "", OFFSET(ignore_editlist), FF_OPT_TYPE_INT, {.i64 = 0},
+    {"ignore_editlist", "", OFFSET(ignore_editlist), AV_OPT_TYPE_INT, {.i64 = 0},
         0, 1, FLAGS},
     {"use_mfra_for",
         "use mfra for fragment timestamps",
-        OFFSET(use_mfra_for), FF_OPT_TYPE_INT, {.i64 = FF_MOV_FLAG_MFRA_AUTO},
+        OFFSET(use_mfra_for), AV_OPT_TYPE_INT, {.i64 = FF_MOV_FLAG_MFRA_AUTO},
         -1, FF_MOV_FLAG_MFRA_PTS, FLAGS,
         "use_mfra_for"},
     {"auto", "auto", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_MFRA_AUTO}, 0, 0,
@@ -4718,6 +4968,8 @@ static const AVOption mov_options[] = {
         "Fixed key used for handling Audible AAX files", OFFSET(audible_fixed_key),
         AV_OPT_TYPE_BINARY, {.str="77214d4b196a87cd520045fd20a51d67"},
         .flags = AV_OPT_FLAG_DECODING_PARAM },
+    { "enable_drefs", "Enable external track support.", OFFSET(enable_drefs), AV_OPT_TYPE_INT,
+        {.i64 = 0}, 0, 1, FLAGS },
     { NULL },
 };