From 07fbba3935e9d4327d3ae946b747486693d9d0f7 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sun, 6 Oct 2013 20:49:12 -0400 Subject: matroska: store segment/edition uids in a single structure To support edition references in matroska chapters, editions need to be remembered for each chapter and source. To facilitate easier management of these now-paired uids, a single structure is used. --- demux/demux.h | 14 +++++++++++--- demux/demux_mkv.c | 23 ++++++++++++++-------- mpvcore/timeline/tl_matroska.c | 44 +++++++++++++++++++++++------------------- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/demux/demux.h b/demux/demux.h index 1e0546a6d0..0c8f9cb3f8 100644 --- a/demux/demux.h +++ b/demux/demux.h @@ -128,14 +128,19 @@ typedef struct demux_chapter uint64_t demuxer_id; // for mapping to internal demuxer data structures } demux_chapter_t; +struct matroska_segment_uid { + unsigned char segment[16]; + uint64_t edition; +}; + struct matroska_data { - unsigned char segment_uid[16]; + struct matroska_segment_uid uid; // Ordered chapter information if any struct matroska_chapter { uint64_t start; uint64_t end; bool has_segment_uid; - unsigned char segment_uid[16]; + struct matroska_segment_uid uid; char *name; } *ordered_chapters; int num_ordered_chapters; @@ -151,7 +156,7 @@ typedef struct demux_attachment struct demuxer_params { int matroska_num_wanted_uids; - unsigned char (*matroska_wanted_uids)[16]; + struct matroska_segment_uid *matroska_wanted_uids; int matroska_wanted_segment; bool *matroska_was_valid; struct ass_library *ass_library; @@ -301,4 +306,7 @@ void mp_tags_set_bstr(struct mp_tags *tags, bstr key, bstr value); char *mp_tags_get_str(struct mp_tags *tags, const char *key); char *mp_tags_get_bstr(struct mp_tags *tags, bstr key); +bool demux_matroska_uid_cmp(struct matroska_segment_uid *a, + struct matroska_segment_uid *b); + #endif /* MPLAYER_DEMUXER_H */ diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c index bbbe88d903..9ca4e62ecb 100644 --- a/demux/demux_mkv.c +++ b/demux/demux_mkv.c @@ -377,24 +377,24 @@ static int demux_mkv_read_info(demuxer_t *demuxer) } if (info.n_segment_uid) { int len = info.segment_uid.len; - if (len != sizeof(demuxer->matroska_data.segment_uid)) { + if (len != sizeof(demuxer->matroska_data.uid.segment)) { mp_msg(MSGT_DEMUX, MSGL_INFO, "[mkv] segment uid invalid length %d\n", len); } else { - memcpy(demuxer->matroska_data.segment_uid, info.segment_uid.start, + memcpy(demuxer->matroska_data.uid.segment, info.segment_uid.start, len); mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] | + segment uid"); for (int i = 0; i < len; i++) mp_msg(MSGT_DEMUX, MSGL_V, " %02x", - demuxer->matroska_data.segment_uid[i]); + demuxer->matroska_data.uid.segment[i]); mp_msg(MSGT_DEMUX, MSGL_V, "\n"); } } if (demuxer->params && demuxer->params->matroska_wanted_uids) { - unsigned char (*uids)[16] = demuxer->params->matroska_wanted_uids; if (info.n_segment_uid) { for (int i = 0; i < demuxer->params->matroska_num_wanted_uids; i++) { - if (!memcmp(info.segment_uid.start, uids[i], 16)) + struct matroska_segment_uid *uid = demuxer->params->matroska_wanted_uids + i; + if (!memcmp(info.segment_uid.start, uid->segment, 16)) goto out; } } @@ -847,7 +847,7 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer) if (ca->n_chapter_segment_uid) { chapter.has_segment_uid = true; int len = ca->chapter_segment_uid.len; - if (len != sizeof(chapter.segment_uid)) + if (len != sizeof(chapter.uid.segment)) mp_msg(MSGT_DEMUX, warn_level, "[mkv] Chapter segment uid bad length %d\n", len); else if (ca->n_chapter_segment_edition_uid) { @@ -855,12 +855,12 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer) "unsupported edition recursion in chapter; " "will skip on playback!\n"); } else { - memcpy(chapter.segment_uid, ca->chapter_segment_uid.start, + memcpy(chapter.uid.segment, ca->chapter_segment_uid.start, len); mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] Chapter segment uid "); for (int n = 0; n < len; n++) mp_msg(MSGT_DEMUX, MSGL_V, "%02x ", - chapter.segment_uid[n]); + chapter.uid.segment[n]); mp_msg(MSGT_DEMUX, MSGL_V, "\n"); } } @@ -2737,3 +2737,10 @@ const demuxer_desc_t demuxer_desc_matroska = { .seek = demux_mkv_seek, .control = demux_mkv_control }; + +bool demux_matroska_uid_cmp(struct matroska_segment_uid *a, + struct matroska_segment_uid *b) +{ + return (!memcmp(a->segment, b->segment, 16) && + a->edition == b->edition); +} diff --git a/mpvcore/timeline/tl_matroska.c b/mpvcore/timeline/tl_matroska.c index 4c3b705f20..220aa34ece 100644 --- a/mpvcore/timeline/tl_matroska.c +++ b/mpvcore/timeline/tl_matroska.c @@ -150,13 +150,13 @@ static int enable_cache(struct MPContext *mpctx, struct stream **stream, // segment = get Nth segment of a multi-segment file static bool check_file_seg(struct MPContext *mpctx, struct demuxer **sources, - int num_sources, unsigned char uid_map[][16], + int num_sources, struct matroska_segment_uid *uids, char *filename, int segment) { bool was_valid = false; struct demuxer_params params = { .matroska_num_wanted_uids = num_sources, - .matroska_wanted_uids = uid_map, + .matroska_wanted_uids = uids, .matroska_wanted_segment = segment, .matroska_was_valid = &was_valid, }; @@ -170,10 +170,12 @@ static bool check_file_seg(struct MPContext *mpctx, struct demuxer **sources, return was_valid; } if (d->type == DEMUXER_TYPE_MATROSKA) { + struct matroska_data *m = &d->matroska_data; for (int i = 1; i < num_sources; i++) { + struct matroska_segment_uid *uid = uids + i; if (sources[i]) continue; - if (!memcmp(uid_map[i], d->matroska_data.segment_uid, 16)) { + if (!memcmp(uid->segment, m->uid.segment, 16)) { mp_msg(MSGT_CPLAYER, MSGL_INFO, "Match for source %d: %s\n", i, d->filename); @@ -191,12 +193,12 @@ static bool check_file_seg(struct MPContext *mpctx, struct demuxer **sources, } static void check_file(struct MPContext *mpctx, struct demuxer **sources, - int num_sources, unsigned char uid_map[][16], + int num_sources, struct matroska_segment_uid *uids, char *filename, int first) { for (int segment = first; ; segment++) { - if (!check_file_seg(mpctx, sources, num_sources, uid_map, - filename, segment)) + if (!check_file_seg(mpctx, sources, num_sources, + uids, filename, segment)) break; } } @@ -213,7 +215,7 @@ static bool missing(struct demuxer **sources, int num_sources) static int find_ordered_chapter_sources(struct MPContext *mpctx, struct demuxer **sources, int num_sources, - unsigned char uid_map[][16]) + struct matroska_segment_uid *uids) { int num_filenames = 0; char **filenames = NULL; @@ -231,14 +233,14 @@ static int find_ordered_chapter_sources(struct MPContext *mpctx, num_filenames = MP_TALLOC_ELEMS(filenames); } // Possibly get further segments appended to the first segment - check_file(mpctx, sources, num_sources, uid_map, main_filename, 1); + check_file(mpctx, sources, num_sources, uids, main_filename, 1); } for (int i = 0; i < num_filenames; i++) { if (!missing(sources, num_sources)) break; mp_msg(MSGT_CPLAYER, MSGL_INFO, "Checking file %s\n", filenames[i]); - check_file(mpctx, sources, num_sources, uid_map, filenames[i], 0); + check_file(mpctx, sources, num_sources, uids, filenames[i], 0); } talloc_free(filenames); @@ -248,8 +250,10 @@ static int find_ordered_chapter_sources(struct MPContext *mpctx, int j = 1; for (int i = 1; i < num_sources; i++) if (sources[i]) { + struct matroska_segment_uid *source_uid = uids + i; + struct matroska_segment_uid *target_uid = uids + j; sources[j] = sources[i]; - memcpy(uid_map[j], uid_map[i], 16); + memcpy(target_uid, source_uid, sizeof(*source_uid)); j++; } num_sources = j; @@ -278,20 +282,21 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx) struct demuxer **sources = talloc_array_ptrtype(NULL, sources, m->num_ordered_chapters+1); sources[0] = mpctx->demuxer; - unsigned char (*uid_map)[16] = talloc_array_ptrtype(NULL, uid_map, - m->num_ordered_chapters + 1); + struct matroska_segment_uid *uids = talloc_array_ptrtype(NULL, uids, + m->num_ordered_chapters + 1); int num_sources = 1; - memcpy(uid_map[0], m->segment_uid, 16); + memcpy(uids[0].segment, m->uid.segment, 16); + uids[0].edition = 0; for (int i = 0; i < m->num_ordered_chapters; i++) { struct matroska_chapter *c = m->ordered_chapters + i; if (!c->has_segment_uid) - memcpy(c->segment_uid, m->segment_uid, 16); + memcpy(c->uid.segment, m->uid.segment, 16); for (int j = 0; j < num_sources; j++) - if (!memcmp(c->segment_uid, uid_map[j], 16)) + if (!memcmp(c->uid.segment, uids[j].segment, 16)) goto found1; - memcpy(uid_map[num_sources], c->segment_uid, 16); + memcpy(uids + num_sources, &c->uid, sizeof(c->uid)); sources[num_sources] = NULL; num_sources++; found1: @@ -299,8 +304,7 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx) } num_sources = find_ordered_chapter_sources(mpctx, sources, num_sources, - uid_map); - + uids); // +1 for terminating chapter with start time marking end of last real one struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline, @@ -317,7 +321,7 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx) int j; for (j = 0; j < num_sources; j++) { - if (!memcmp(c->segment_uid, uid_map[j], 16)) + if (!memcmp(c->uid.segment, uids[j].segment, 16)) goto found2; } missing_time += c->end - c->start; @@ -353,7 +357,7 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx) num_chapters++; } timeline[part_count].start = starttime / 1e9; - talloc_free(uid_map); + talloc_free(uids); if (!part_count) { // None of the parts come from the file itself??? -- cgit v1.2.3 From 4f287f17929e4448688266105caa2c939720d01f Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Thu, 26 Sep 2013 02:21:19 -0400 Subject: matroska: parse the requested edition for the segment reference --- demux/demux_mkv.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c index 9ca4e62ecb..8b497a1402 100644 --- a/demux/demux_mkv.c +++ b/demux/demux_mkv.c @@ -850,13 +850,13 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer) if (len != sizeof(chapter.uid.segment)) mp_msg(MSGT_DEMUX, warn_level, "[mkv] Chapter segment uid bad length %d\n", len); - else if (ca->n_chapter_segment_edition_uid) { - mp_tmsg(MSGT_DEMUX, warn_level, "[mkv] Warning: " - "unsupported edition recursion in chapter; " - "will skip on playback!\n"); - } else { + else { memcpy(chapter.uid.segment, ca->chapter_segment_uid.start, len); + if (ca->n_chapter_segment_edition_uid) + chapter.uid.edition = ca->chapter_segment_edition_uid; + else + chapter.uid.edition = 0; mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] Chapter segment uid "); for (int n = 0; n < len; n++) mp_msg(MSGT_DEMUX, MSGL_V, "%02x ", -- cgit v1.2.3 From 069a2d047d458d980181de9b5b9654d3dc2c1bdf Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Thu, 26 Sep 2013 02:54:25 -0400 Subject: matroska: set the edition uid when reading a chapter reference --- demux/demux_mkv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c index 8b497a1402..8c827f4131 100644 --- a/demux/demux_mkv.c +++ b/demux/demux_mkv.c @@ -394,8 +394,10 @@ static int demux_mkv_read_info(demuxer_t *demuxer) if (info.n_segment_uid) { for (int i = 0; i < demuxer->params->matroska_num_wanted_uids; i++) { struct matroska_segment_uid *uid = demuxer->params->matroska_wanted_uids + i; - if (!memcmp(info.segment_uid.start, uid->segment, 16)) + if (!memcmp(info.segment_uid.start, uid->segment, 16)) { + demuxer->matroska_data.uid.edition = uid->edition; goto out; + } } } mp_tmsg(MSGT_DEMUX, MSGL_INFO, -- cgit v1.2.3 From 2fe2be4df36c17b460bd9588e9276a2e14e4aca9 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Thu, 26 Sep 2013 02:53:54 -0400 Subject: matroska: select the edition using the requested edition uid --- demux/demux_mkv.c | 23 +++++++++++++++++++++-- mpvcore/timeline/tl_matroska.c | 13 ++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c index 8c827f4131..88e91a3717 100644 --- a/demux/demux_mkv.c +++ b/demux/demux_mkv.c @@ -785,6 +785,12 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer) struct MPOpts *opts = demuxer->opts; stream_t *s = demuxer->stream; int wanted_edition = opts->edition_id; + uint64_t wanted_edition_uid = demuxer->matroska_data.uid.edition; + + /* A specific edition UID was requested; ignore the user option which is + * only applicable to the top-level file. */ + if (wanted_edition_uid) + wanted_edition = -1; mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] /---- [ parsing chapters ] ---------\n"); struct ebml_chapters file_chapters = {}; @@ -793,7 +799,7 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer) &ebml_chapters_desc) < 0) return -1; - int selected_edition = 0; + int selected_edition = -1; int num_editions = file_chapters.n_edition_entry; struct ebml_edition_entry *editions = file_chapters.edition_entry; if (wanted_edition >= 0 && wanted_edition < num_editions) { @@ -802,11 +808,24 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer) selected_edition); } else for (int i = 0; i < num_editions; i++) - if (editions[i].edition_flag_default) { + if (wanted_edition_uid && + editions[i].edition_uid == wanted_edition_uid) { + selected_edition = i; + break; + } else if (editions[i].edition_flag_default) { selected_edition = i; mp_msg(MSGT_DEMUX, MSGL_V, "[mkv] Default edition: %d\n", i); break; } + if (selected_edition < 0) { + if (wanted_edition_uid) { + mp_msg(MSGT_DEMUX, MSGL_ERR, + "[mkv] Unable to find expected edition uid: %"PRIu64"\n", + wanted_edition_uid); + return -1; + } else + selected_edition = 0; + } struct matroska_chapter *m_chapters = NULL; if (editions[selected_edition].edition_flag_ordered) { int count = editions[selected_edition].n_chapter_atom; diff --git a/mpvcore/timeline/tl_matroska.c b/mpvcore/timeline/tl_matroska.c index 220aa34ece..51ee657e2a 100644 --- a/mpvcore/timeline/tl_matroska.c +++ b/mpvcore/timeline/tl_matroska.c @@ -175,7 +175,10 @@ static bool check_file_seg(struct MPContext *mpctx, struct demuxer **sources, struct matroska_segment_uid *uid = uids + i; if (sources[i]) continue; - if (!memcmp(uid->segment, m->uid.segment, 16)) { + /* Accept the source if the segment uid matches and the edition + * either matches or isn't specified. */ + if (!memcmp(uid->segment, m->uid.segment, 16) && + (!uid->edition || uid->edition == m->uid.edition)) { mp_msg(MSGT_CPLAYER, MSGL_INFO, "Match for source %d: %s\n", i, d->filename); @@ -294,7 +297,11 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx) memcpy(c->uid.segment, m->uid.segment, 16); for (int j = 0; j < num_sources; j++) - if (!memcmp(c->uid.segment, uids[j].segment, 16)) + /* If there isn't a segment uid, we are the source. If the segment + * uid is our segment uid and the edition matches. We can't accept + * the "don't care" edition value of 0 since the user may have + * requested a non-default edition. */ + if (demux_matroska_uid_cmp(&c->uid, uids + j)) goto found1; memcpy(uids + num_sources, &c->uid, sizeof(c->uid)); sources[num_sources] = NULL; @@ -321,7 +328,7 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx) int j; for (j = 0; j < num_sources; j++) { - if (!memcmp(c->uid.segment, uids[j].segment, 16)) + if (demux_matroska_uid_cmp(&c->uid, uids + j)) goto found2; } missing_time += c->end - c->start; -- cgit v1.2.3 From d8e5ac00bb5c21b29e13ea701a21c16dee7d8615 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sun, 6 Oct 2013 21:42:30 -0400 Subject: matroska: refactor ordered chapter timeline building This will need to be recursive to support chapters referencing ordered editions in other files. --- mpvcore/timeline/tl_matroska.c | 166 +++++++++++++++++++++++++---------------- 1 file changed, 103 insertions(+), 63 deletions(-) diff --git a/mpvcore/timeline/tl_matroska.c b/mpvcore/timeline/tl_matroska.c index 51ee657e2a..ce4092adfe 100644 --- a/mpvcore/timeline/tl_matroska.c +++ b/mpvcore/timeline/tl_matroska.c @@ -264,6 +264,88 @@ static int find_ordered_chapter_sources(struct MPContext *mpctx, return num_sources; } +static void add_timeline_part(struct MPOpts *opts, + struct demuxer *source, + struct timeline_part **timeline, + int *part_count, + uint64_t start, + uint64_t *last_end_time, + uint64_t *starttime) +{ + /* Only add a separate part if the time or file actually changes. + * Matroska files have chapter divisions that are redundant from + * timeline point of view because the same chapter structure is used + * both to specify the timeline and for normal chapter information. + * Removing a missing inserted external chapter can also cause this. + * We allow for a configurable fudge factor because of files which + * specify chapter end times that are one frame too early; + * we don't want to try seeking over a one frame gap. */ + int64_t join_diff = start - *last_end_time; + if (*part_count == 0 + || FFABS(join_diff) > opts->chapter_merge_threshold * 1e6 + || source != (*timeline)[*part_count - 1].source) { + struct timeline_part new = { + .start = *starttime / 1e9, + .source_start = start / 1e9, + .source = source, + }; + MP_TARRAY_APPEND(NULL, *timeline, *part_count, new); + } else if (*part_count > 0 && join_diff) { + /* Chapter was merged at an inexact boundary; + * adjust timestamps to match. */ + mp_msg(MSGT_CPLAYER, MSGL_V, "Merging timeline part %d with " + "offset %g ms.\n", *part_count, join_diff / 1e6); + *starttime += join_diff; + } +} + +static void build_timeline_loop(struct MPOpts *opts, + struct demuxer **sources, + int num_sources, + int current_source, + uint64_t *starttime, + uint64_t *missing_time, + uint64_t *last_end_time, + struct timeline_part **timeline, + struct chapter *chapters, + int *part_count) +{ + struct demuxer *source = sources[current_source]; + struct matroska_data *m = &source->matroska_data; + + for (int i = 0; i < m->num_ordered_chapters; i++) { + struct matroska_chapter *c = m->ordered_chapters + i; + uint64_t chapter_length = c->end - c->start; + + /* Fill in the segment uid with the current one if one isn't requested. */ + if (!c->has_segment_uid) + memcpy(&c->uid, &m->uid, sizeof(c->uid)); + + /* Look for the source for this chapter. */ + for (int j = 0; j < num_sources; j++) { + struct demuxer *linked_source = sources[j]; + struct matroska_data *linked_m = &linked_source->matroska_data; + + /* Skip if the segment or edition isn't acceptable. */ + if (!demux_matroska_uid_cmp(&c->uid, &linked_m->uid)) + continue; + + chapters[i].start = *starttime / 1e9; + chapters[i].name = talloc_strdup(chapters, c->name); + + add_timeline_part(opts, linked_source, timeline, part_count, + c->start, last_end_time, starttime); + *last_end_time = c->end; + goto found; + } + + /* We're missing a part of the chapter, so add it to the accounting. */ + *missing_time += chapter_length; + found:; + *starttime += chapter_length; + } +} + void build_ordered_chapter_timeline(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; @@ -293,78 +375,31 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx) for (int i = 0; i < m->num_ordered_chapters; i++) { struct matroska_chapter *c = m->ordered_chapters + i; - if (!c->has_segment_uid) - memcpy(c->uid.segment, m->uid.segment, 16); - - for (int j = 0; j < num_sources; j++) - /* If there isn't a segment uid, we are the source. If the segment - * uid is our segment uid and the edition matches. We can't accept - * the "don't care" edition value of 0 since the user may have - * requested a non-default edition. */ - if (demux_matroska_uid_cmp(&c->uid, uids + j)) - goto found1; + /* If there isn't a segment uid, we are the source. If the segment uid + * is our segment uid and the edition matches. We can't accept the + * "don't care" edition value of 0 since the user may have requested a + * non-default edition. */ + if (!c->has_segment_uid || demux_matroska_uid_cmp(&c->uid, &m->uid)) + continue; + memcpy(uids + num_sources, &c->uid, sizeof(c->uid)); sources[num_sources] = NULL; num_sources++; - found1: - ; } num_sources = find_ordered_chapter_sources(mpctx, sources, num_sources, uids); + talloc_free(uids); - // +1 for terminating chapter with start time marking end of last real one - struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline, - m->num_ordered_chapters + 1); - struct chapter *chapters = talloc_array_ptrtype(NULL, chapters, - m->num_ordered_chapters); + struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline, 0); + struct chapter *chapters = talloc_array_ptrtype(NULL, chapters, m->num_ordered_chapters); uint64_t starttime = 0; uint64_t missing_time = 0; + uint64_t last_end_time = 0; int part_count = 0; - int num_chapters = 0; - uint64_t prev_part_offset = 0; - for (int i = 0; i < m->num_ordered_chapters; i++) { - struct matroska_chapter *c = m->ordered_chapters + i; - - int j; - for (j = 0; j < num_sources; j++) { - if (demux_matroska_uid_cmp(&c->uid, uids + j)) - goto found2; - } - missing_time += c->end - c->start; - continue; - found2:; - /* Only add a separate part if the time or file actually changes. - * Matroska files have chapter divisions that are redundant from - * timeline point of view because the same chapter structure is used - * both to specify the timeline and for normal chapter information. - * Removing a missing inserted external chapter can also cause this. - * We allow for a configurable fudge factor because of files which - * specify chapter end times that are one frame too early; - * we don't want to try seeking over a one frame gap. */ - int64_t join_diff = c->start - starttime - prev_part_offset; - if (part_count == 0 - || FFABS(join_diff) > opts->chapter_merge_threshold * 1000000 - || sources[j] != timeline[part_count - 1].source) { - timeline[part_count].source = sources[j]; - timeline[part_count].start = starttime / 1e9; - timeline[part_count].source_start = c->start / 1e9; - prev_part_offset = c->start - starttime; - part_count++; - } else if (part_count > 0 && join_diff) { - /* Chapter was merged at an inexact boundary; - * adjust timestamps to match. */ - mp_msg(MSGT_CPLAYER, MSGL_V, "Merging timeline part %d with " - "offset %g ms.\n", i, join_diff / 1e6); - starttime += join_diff; - } - chapters[num_chapters].start = starttime / 1e9; - chapters[num_chapters].name = talloc_strdup(chapters, c->name); - starttime += c->end - c->start; - num_chapters++; - } - timeline[part_count].start = starttime / 1e9; - talloc_free(uids); + build_timeline_loop(opts, sources, num_sources, 0, &starttime, + &missing_time, &last_end_time, &timeline, + chapters, &part_count); if (!part_count) { // None of the parts come from the file itself??? @@ -374,6 +409,11 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx) return; } + struct timeline_part new = { + .start = starttime / 1e9, + }; + MP_TARRAY_APPEND(NULL, timeline, part_count, new); + if (missing_time) mp_msg(MSGT_CPLAYER, MSGL_ERR, "There are %.3f seconds missing " "from the timeline!\n", missing_time / 1e9); @@ -381,7 +421,7 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx) mpctx->sources = sources; mpctx->num_sources = num_sources; mpctx->timeline = timeline; - mpctx->num_timeline_parts = part_count; - mpctx->num_chapters = num_chapters; + mpctx->num_timeline_parts = part_count - 1; + mpctx->num_chapters = m->num_ordered_chapters; mpctx->chapters = chapters; } -- cgit v1.2.3 From 5cd33853f20382580a82eaa16c0a9c6d04ab7153 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sat, 5 Oct 2013 03:01:49 -0400 Subject: matroska: recursively search for referenced segments When playing a Matroska file with ordered chapters, it may reference another file by edition uid. When this edition is also ordered, it may reference other files. When this occurs, the new segment/edition pair is added to the list of sources to search for. --- mpvcore/timeline/tl_matroska.c | 80 ++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/mpvcore/timeline/tl_matroska.c b/mpvcore/timeline/tl_matroska.c index ce4092adfe..259da3a3aa 100644 --- a/mpvcore/timeline/tl_matroska.c +++ b/mpvcore/timeline/tl_matroska.c @@ -149,14 +149,14 @@ static int enable_cache(struct MPContext *mpctx, struct stream **stream, } // segment = get Nth segment of a multi-segment file -static bool check_file_seg(struct MPContext *mpctx, struct demuxer **sources, - int num_sources, struct matroska_segment_uid *uids, +static bool check_file_seg(struct MPContext *mpctx, struct demuxer ***sources, + int *num_sources, struct matroska_segment_uid **uids, char *filename, int segment) { bool was_valid = false; struct demuxer_params params = { - .matroska_num_wanted_uids = num_sources, - .matroska_wanted_uids = uids, + .matroska_num_wanted_uids = *num_sources, + .matroska_wanted_uids = *uids, .matroska_wanted_segment = segment, .matroska_was_valid = &was_valid, }; @@ -171,9 +171,10 @@ static bool check_file_seg(struct MPContext *mpctx, struct demuxer **sources, } if (d->type == DEMUXER_TYPE_MATROSKA) { struct matroska_data *m = &d->matroska_data; - for (int i = 1; i < num_sources; i++) { - struct matroska_segment_uid *uid = uids + i; - if (sources[i]) + + for (int i = 1; i < *num_sources; i++) { + struct matroska_segment_uid *uid = *uids + i; + if ((*sources)[i]) continue; /* Accept the source if the segment uid matches and the edition * either matches or isn't specified. */ @@ -185,7 +186,21 @@ static bool check_file_seg(struct MPContext *mpctx, struct demuxer **sources, if (enable_cache(mpctx, &s, &d, ¶ms) < 0) continue; - sources[i] = d; + for (int j = 0; j < m->num_ordered_chapters; j++) { + struct matroska_chapter *c = m->ordered_chapters + j; + + if (!c->has_segment_uid) + continue; + + /* Set the requested segment. */ + MP_TARRAY_GROW(NULL, *uids, *num_sources); + memcpy((*uids) + *num_sources, &c->uid, sizeof(c->uid)); + + /* Add a new source slot. */ + MP_TARRAY_APPEND(NULL, *sources, *num_sources, NULL); + } + + (*sources)[i] = d; return true; } } @@ -195,8 +210,8 @@ static bool check_file_seg(struct MPContext *mpctx, struct demuxer **sources, return was_valid; } -static void check_file(struct MPContext *mpctx, struct demuxer **sources, - int num_sources, struct matroska_segment_uid *uids, +static void check_file(struct MPContext *mpctx, struct demuxer ***sources, + int *num_sources, struct matroska_segment_uid **uids, char *filename, int first) { for (int segment = first; ; segment++) { @@ -216,13 +231,13 @@ static bool missing(struct demuxer **sources, int num_sources) } static int find_ordered_chapter_sources(struct MPContext *mpctx, - struct demuxer **sources, - int num_sources, - struct matroska_segment_uid *uids) + struct demuxer ***sources, + int *num_sources, + struct matroska_segment_uid **uids) { int num_filenames = 0; char **filenames = NULL; - if (num_sources > 1) { + if (*num_sources > 1) { char *main_filename = mpctx->demuxer->filename; mp_msg(MSGT_CPLAYER, MSGL_INFO, "This file references data from " "other sources.\n"); @@ -239,29 +254,34 @@ static int find_ordered_chapter_sources(struct MPContext *mpctx, check_file(mpctx, sources, num_sources, uids, main_filename, 1); } - for (int i = 0; i < num_filenames; i++) { - if (!missing(sources, num_sources)) - break; - mp_msg(MSGT_CPLAYER, MSGL_INFO, "Checking file %s\n", filenames[i]); - check_file(mpctx, sources, num_sources, uids, filenames[i], 0); - } + int old_source_count; + do { + old_source_count = *num_sources; + for (int i = 0; i < num_filenames; i++) { + if (!missing(*sources, *num_sources)) + break; + mp_msg(MSGT_CPLAYER, MSGL_INFO, "Checking file %s\n", filenames[i]); + check_file(mpctx, sources, num_sources, uids, filenames[i], 0); + } + /* Loop while we have new sources to look for. */ + } while (old_source_count != *num_sources); talloc_free(filenames); - if (missing(sources, num_sources)) { + if (missing(*sources, *num_sources)) { mp_msg(MSGT_CPLAYER, MSGL_ERR, "Failed to find ordered chapter part!\n" "There will be parts MISSING from the video!\n"); int j = 1; - for (int i = 1; i < num_sources; i++) - if (sources[i]) { - struct matroska_segment_uid *source_uid = uids + i; - struct matroska_segment_uid *target_uid = uids + j; - sources[j] = sources[i]; + for (int i = 1; i < *num_sources; i++) + if ((*sources)[i]) { + struct matroska_segment_uid *source_uid = *uids + i; + struct matroska_segment_uid *target_uid = *uids + j; + (*sources)[j] = (*sources)[i]; memcpy(target_uid, source_uid, sizeof(*source_uid)); j++; } - num_sources = j; + *num_sources = j; } - return num_sources; + return *num_sources; } static void add_timeline_part(struct MPOpts *opts, @@ -387,8 +407,8 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx) num_sources++; } - num_sources = find_ordered_chapter_sources(mpctx, sources, num_sources, - uids); + num_sources = find_ordered_chapter_sources(mpctx, &sources, &num_sources, + &uids); talloc_free(uids); struct timeline_part *timeline = talloc_array_ptrtype(NULL, timeline, 0); -- cgit v1.2.3 From f72a900892aa8371a16b844b89f7e2369b45b449 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Sun, 6 Oct 2013 23:22:19 -0400 Subject: matroska: support chapter references to ordered editions Since ordered editions can reference external files, when an ordered chapter references another ordered edition, we have to go and figure out what is is referencing as well. --- mpvcore/timeline/tl_matroska.c | 58 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/mpvcore/timeline/tl_matroska.c b/mpvcore/timeline/tl_matroska.c index 259da3a3aa..b4af848fa9 100644 --- a/mpvcore/timeline/tl_matroska.c +++ b/mpvcore/timeline/tl_matroska.c @@ -328,8 +328,11 @@ static void build_timeline_loop(struct MPOpts *opts, uint64_t *last_end_time, struct timeline_part **timeline, struct chapter *chapters, - int *part_count) + int *part_count, + uint64_t skip, + uint64_t limit) { + uint64_t local_starttime = 0; struct demuxer *source = sources[current_source]; struct matroska_data *m = &source->matroska_data; @@ -337,10 +340,18 @@ static void build_timeline_loop(struct MPOpts *opts, struct matroska_chapter *c = m->ordered_chapters + i; uint64_t chapter_length = c->end - c->start; - /* Fill in the segment uid with the current one if one isn't requested. */ + /* Fill in the uid with the current one if one isn't requested. */ if (!c->has_segment_uid) memcpy(&c->uid, &m->uid, sizeof(c->uid)); + /* "Seek" to the end of the chapter. */ + local_starttime += chapter_length; + + /* If we're before the start time for the chapter, skip to the next + * one. */ + if (local_starttime <= skip) + continue; + /* Look for the source for this chapter. */ for (int j = 0; j < num_sources; j++) { struct demuxer *linked_source = sources[j]; @@ -350,11 +361,34 @@ static void build_timeline_loop(struct MPOpts *opts, if (!demux_matroska_uid_cmp(&c->uid, &linked_m->uid)) continue; - chapters[i].start = *starttime / 1e9; - chapters[i].name = talloc_strdup(chapters, c->name); + /* TODO: Add option to support recursive chapters when loading + * recursive ordered chapter editions? If so, more code will be + * needed to add chapters for external non-ordered segment loading + * as well since that part is not recursive. */ + if (!limit) { + chapters[i].start = *starttime / 1e9; + chapters[i].name = talloc_strdup(chapters, c->name); + } - add_timeline_part(opts, linked_source, timeline, part_count, - c->start, last_end_time, starttime); + /* If we're the source or it's a non-ordered edition reference, + * just add a timeline part from the source. */ + if (current_source == j || !linked_m->num_ordered_chapters) { + add_timeline_part(opts, linked_source, timeline, part_count, + c->start, last_end_time, starttime); + /* Otherwise, we have an ordered edition as the source. Since this + * can jump around all over the place, we need to build up the + * timeline parts for each of its chapters, but not add them as + * chapters. */ + } else { + build_timeline_loop(opts, sources, num_sources, j, starttime, + missing_time, last_end_time, timeline, + chapters, part_count, c->start, c->end); + /* The loop call has added time as needed (we can't add it here + * due to 'join_diff' in the add_timeline_part function. Since + * the time has already been added as needed, the chapter has + * an effective 0 length at this point. */ + chapter_length = 0; + } *last_end_time = c->end; goto found; } @@ -363,7 +397,17 @@ static void build_timeline_loop(struct MPOpts *opts, *missing_time += chapter_length; found:; *starttime += chapter_length; + /* If we're after the limit on this chapter, stop here. */ + if (limit && local_starttime >= limit) { + /* Back up the global start time by the overflow. */ + *starttime -= local_starttime - limit; + break; + } } + + /* If we stopped before the limit, add up the missing time. */ + if (local_starttime < limit) + *missing_time += limit - local_starttime; } void build_ordered_chapter_timeline(struct MPContext *mpctx) @@ -419,7 +463,7 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx) int part_count = 0; build_timeline_loop(opts, sources, num_sources, 0, &starttime, &missing_time, &last_end_time, &timeline, - chapters, &part_count); + chapters, &part_count, 0, 0); if (!part_count) { // None of the parts come from the file itself??? -- cgit v1.2.3 From 47345219a342eeec822583110553ae41884b7f8f Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 8 Oct 2013 00:33:42 -0400 Subject: matroska: avoid requesting the same source multiple times --- mpvcore/timeline/tl_matroska.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mpvcore/timeline/tl_matroska.c b/mpvcore/timeline/tl_matroska.c index b4af848fa9..8ff5ae2074 100644 --- a/mpvcore/timeline/tl_matroska.c +++ b/mpvcore/timeline/tl_matroska.c @@ -148,6 +148,18 @@ static int enable_cache(struct MPContext *mpctx, struct stream **stream, return 1; } +static bool has_source_request(struct matroska_segment_uid *uids, + int num_sources, + struct matroska_segment_uid *new_uid) +{ + for (int i = 0; i < num_sources; ++i) { + if (demux_matroska_uid_cmp(uids + i, new_uid)) + return true; + } + + return false; +} + // segment = get Nth segment of a multi-segment file static bool check_file_seg(struct MPContext *mpctx, struct demuxer ***sources, int *num_sources, struct matroska_segment_uid **uids, @@ -192,6 +204,9 @@ static bool check_file_seg(struct MPContext *mpctx, struct demuxer ***sources, if (!c->has_segment_uid) continue; + if (has_source_request(*uids, *num_sources, &c->uid)) + continue; + /* Set the requested segment. */ MP_TARRAY_GROW(NULL, *uids, *num_sources); memcpy((*uids) + *num_sources, &c->uid, sizeof(c->uid)); @@ -446,6 +461,9 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx) if (!c->has_segment_uid || demux_matroska_uid_cmp(&c->uid, &m->uid)) continue; + if (has_source_request(uids, num_sources, &c->uid)) + continue; + memcpy(uids + num_sources, &c->uid, sizeof(c->uid)); sources[num_sources] = NULL; num_sources++; -- cgit v1.2.3 From 67eb187826b0e25ddef108b06c7e3c4cc31784a7 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 8 Oct 2013 00:35:04 -0400 Subject: matroska: use memmove when collapsing the source list down --- mpvcore/timeline/tl_matroska.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mpvcore/timeline/tl_matroska.c b/mpvcore/timeline/tl_matroska.c index 8ff5ae2074..43857f1e59 100644 --- a/mpvcore/timeline/tl_matroska.c +++ b/mpvcore/timeline/tl_matroska.c @@ -291,7 +291,7 @@ static int find_ordered_chapter_sources(struct MPContext *mpctx, struct matroska_segment_uid *source_uid = *uids + i; struct matroska_segment_uid *target_uid = *uids + j; (*sources)[j] = (*sources)[i]; - memcpy(target_uid, source_uid, sizeof(*source_uid)); + memmove(target_uid, source_uid, sizeof(*source_uid)); j++; } *num_sources = j; -- cgit v1.2.3 From e80fd3f9e12a455114624b7bc5d4a3eee16907bd Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Mon, 14 Oct 2013 18:51:03 -0400 Subject: matroska: modify chapter limits by join_diff When adding or removing frames to avoid 1-frame seeks on chapter boundaries, the end of the chapter needs to be pushed or pulled by the same amount to keep the intended end frame the same. --- mpvcore/timeline/tl_matroska.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/mpvcore/timeline/tl_matroska.c b/mpvcore/timeline/tl_matroska.c index 43857f1e59..06ab5a774b 100644 --- a/mpvcore/timeline/tl_matroska.c +++ b/mpvcore/timeline/tl_matroska.c @@ -299,13 +299,13 @@ static int find_ordered_chapter_sources(struct MPContext *mpctx, return *num_sources; } -static void add_timeline_part(struct MPOpts *opts, - struct demuxer *source, - struct timeline_part **timeline, - int *part_count, - uint64_t start, - uint64_t *last_end_time, - uint64_t *starttime) +static int64_t add_timeline_part(struct MPOpts *opts, + struct demuxer *source, + struct timeline_part **timeline, + int *part_count, + uint64_t start, + uint64_t *last_end_time, + uint64_t *starttime) { /* Only add a separate part if the time or file actually changes. * Matroska files have chapter divisions that are redundant from @@ -331,7 +331,10 @@ static void add_timeline_part(struct MPOpts *opts, mp_msg(MSGT_CPLAYER, MSGL_V, "Merging timeline part %d with " "offset %g ms.\n", *part_count, join_diff / 1e6); *starttime += join_diff; + return join_diff; } + + return 0; } static void build_timeline_loop(struct MPOpts *opts, @@ -388,8 +391,19 @@ static void build_timeline_loop(struct MPOpts *opts, /* If we're the source or it's a non-ordered edition reference, * just add a timeline part from the source. */ if (current_source == j || !linked_m->num_ordered_chapters) { - add_timeline_part(opts, linked_source, timeline, part_count, - c->start, last_end_time, starttime); + int64_t join_diff = add_timeline_part(opts, linked_source, timeline, part_count, + c->start, last_end_time, starttime); + + /* If we merged two chapters into a single part due to them + * being off by a few frames, we need to change the limit to + * avoid chopping the end of the intended chapter (the adding + * frames case) or showing extra content (the removing frames + * case). Also update chapter_length to incorporate the extra + * time. */ + if (limit) { + limit += join_diff; + chapter_length += join_diff; + } /* Otherwise, we have an ordered edition as the source. Since this * can jump around all over the place, we need to build up the * timeline parts for each of its chapters, but not add them as -- cgit v1.2.3 From abee328f20c89472e00fa877a0859ecfe572ed8f Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Mon, 14 Oct 2013 19:28:47 -0400 Subject: matroska: account for missing information from short sources If a source is shorter than the requested chapter length, account for the time missing. --- mpvcore/timeline/tl_matroska.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/mpvcore/timeline/tl_matroska.c b/mpvcore/timeline/tl_matroska.c index 06ab5a774b..9505fe06a4 100644 --- a/mpvcore/timeline/tl_matroska.c +++ b/mpvcore/timeline/tl_matroska.c @@ -391,8 +391,37 @@ static void build_timeline_loop(struct MPOpts *opts, /* If we're the source or it's a non-ordered edition reference, * just add a timeline part from the source. */ if (current_source == j || !linked_m->num_ordered_chapters) { - int64_t join_diff = add_timeline_part(opts, linked_source, timeline, part_count, - c->start, last_end_time, starttime); + double source_full_length_seconds = demuxer_get_time_length(linked_source); + /* Some accuracy lost, but not enough to care. (Over one + * million parts, a nanosecond off here could add up to a + * millisecond and trigger a false-positive error message, but + * if that's your biggest problem at that point, + * congratulations. */ + uint64_t source_full_length = source_full_length_seconds * 1e9; + uint64_t source_length = source_full_length - c->start; + int64_t join_diff = 0; + + /* If the chapter starts after the end of a source, there's + * nothing we can get from it. Instead, mark the entire chapter + * as missing and make the chapter length 0. */ + if (source_full_length <= c->start) { + *missing_time += chapter_length; + chapter_length = 0; + goto found; + } + + /* If the source length starting at the chapter start is + * shorter than the chapter it is supposed to fill, add the gap + * to missing_time. Also, modify the chapter length to be what + * we actually have to avoid playing off the end of the file + * and not switching to the next source. */ + if (source_length < chapter_length) { + *missing_time += chapter_length - source_length; + chapter_length = source_length; + } + + join_diff = add_timeline_part(opts, linked_source, timeline, part_count, + c->start, last_end_time, starttime); /* If we merged two chapters into a single part due to them * being off by a few frames, we need to change the limit to -- cgit v1.2.3 From 78545fee421f4e194d988b93d970e61f57841c61 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Mon, 14 Oct 2013 19:29:42 -0400 Subject: matroska: only error if at least a millisecond is missing --- mpvcore/timeline/tl_matroska.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mpvcore/timeline/tl_matroska.c b/mpvcore/timeline/tl_matroska.c index 9505fe06a4..11d73f314e 100644 --- a/mpvcore/timeline/tl_matroska.c +++ b/mpvcore/timeline/tl_matroska.c @@ -539,7 +539,10 @@ void build_ordered_chapter_timeline(struct MPContext *mpctx) }; MP_TARRAY_APPEND(NULL, timeline, part_count, new); - if (missing_time) + /* Ignore anything less than a millisecond when reporting missing time. If + * users really notice less than a millisecond missing, maybe this can be + * revisited. */ + if (missing_time >= 1e6) mp_msg(MSGT_CPLAYER, MSGL_ERR, "There are %.3f seconds missing " "from the timeline!\n", missing_time / 1e9); talloc_free(mpctx->sources); -- cgit v1.2.3 From 648e8fbeeddeb8e490d2007546556844bc1d29f8 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 15 Oct 2013 21:04:16 -0400 Subject: matroska: log about where missing time comes from --- mpvcore/timeline/tl_matroska.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/mpvcore/timeline/tl_matroska.c b/mpvcore/timeline/tl_matroska.c index 11d73f314e..e5276b2a75 100644 --- a/mpvcore/timeline/tl_matroska.c +++ b/mpvcore/timeline/tl_matroska.c @@ -337,6 +337,19 @@ static int64_t add_timeline_part(struct MPOpts *opts, return 0; } +static void account_missing_time(uint64_t *total_time, + uint64_t new_time, + const char *message) +{ + if (!new_time) + return; + + *total_time += new_time; + mp_msg(MSGT_CPLAYER, MSGL_HINT, + "missing %"PRIu64" nanoseconds: %s\n", + new_time, message); +} + static void build_timeline_loop(struct MPOpts *opts, struct demuxer **sources, int num_sources, @@ -405,7 +418,8 @@ static void build_timeline_loop(struct MPOpts *opts, * nothing we can get from it. Instead, mark the entire chapter * as missing and make the chapter length 0. */ if (source_full_length <= c->start) { - *missing_time += chapter_length; + account_missing_time(missing_time, chapter_length, + "referenced segment ends before the requested start time"); chapter_length = 0; goto found; } @@ -416,7 +430,8 @@ static void build_timeline_loop(struct MPOpts *opts, * we actually have to avoid playing off the end of the file * and not switching to the next source. */ if (source_length < chapter_length) { - *missing_time += chapter_length - source_length; + account_missing_time(missing_time, chapter_length - source_length, + "referenced segment ends before the requested end time"); chapter_length = source_length; } @@ -452,7 +467,8 @@ static void build_timeline_loop(struct MPOpts *opts, } /* We're missing a part of the chapter, so add it to the accounting. */ - *missing_time += chapter_length; + account_missing_time(missing_time, chapter_length, + "the source for a chapter could not be found"); found:; *starttime += chapter_length; /* If we're after the limit on this chapter, stop here. */ @@ -465,7 +481,8 @@ static void build_timeline_loop(struct MPOpts *opts, /* If we stopped before the limit, add up the missing time. */ if (local_starttime < limit) - *missing_time += limit - local_starttime; + account_missing_time(missing_time, limit - local_starttime, + "nested ordered chapter segment is shorter than the requested end time"); } void build_ordered_chapter_timeline(struct MPContext *mpctx) -- cgit v1.2.3 From 1b30a0bbf901c04140770d37c58cb63ef676406e Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 15 Oct 2013 21:16:05 -0400 Subject: matroska: don't add time for chapters without a source --- mpvcore/timeline/tl_matroska.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mpvcore/timeline/tl_matroska.c b/mpvcore/timeline/tl_matroska.c index e5276b2a75..e8a855a233 100644 --- a/mpvcore/timeline/tl_matroska.c +++ b/mpvcore/timeline/tl_matroska.c @@ -469,6 +469,9 @@ static void build_timeline_loop(struct MPOpts *opts, /* We're missing a part of the chapter, so add it to the accounting. */ account_missing_time(missing_time, chapter_length, "the source for a chapter could not be found"); + /* We don't have the source, but don't leave a gap in the timeline for + * the source. */ + chapter_length = 0; found:; *starttime += chapter_length; /* If we're after the limit on this chapter, stop here. */ -- cgit v1.2.3