diff --git a/demux/demux_cue.c b/demux/demux_cue.c index f54c3f3e4a..8518d928d1 100644 --- a/demux/demux_cue.c +++ b/demux/demux_cue.c @@ -210,6 +210,7 @@ static void build_timeline(struct timeline *tl) } timeline[i] = (struct timeline_part) { .start = starttime, + .end = starttime + duration, .source_start = tracks[i].start, .source = source, }; @@ -217,23 +218,20 @@ static void build_timeline(struct timeline *tl) .pts = timeline[i].start, .metadata = mp_tags_dup(tl, tracks[i].tags), }; - starttime += duration; + starttime = timeline[i].end; } - // apparently we need this to give the last part a non-zero length - timeline[track_count] = (struct timeline_part) { - .start = starttime, - // perhaps unused by the timeline code - .source_start = 0, - .source = timeline[0].source, + struct timeline_par *par = talloc_ptrtype(tl, par); + *par = (struct timeline_par){ + .parts = timeline, + .num_parts = track_count, + .track_layout = timeline[0].source, }; - tl->parts = timeline; - // the last part is not included it in the count - tl->num_parts = track_count + 1 - 1; tl->chapters = chapters; tl->num_chapters = track_count; - tl->track_layout = tl->parts[0].source; + MP_TARRAY_APPEND(tl, tl->pars, tl->num_pars, par); + tl->meta = par->track_layout; out: talloc_free(ctx); diff --git a/demux/demux_edl.c b/demux/demux_edl.c index bfd5cb6dad..1f27a971a9 100644 --- a/demux/demux_edl.c +++ b/demux/demux_edl.c @@ -53,6 +53,11 @@ struct tl_parts { struct tl_parts *next; }; +struct tl_root { + struct tl_parts **pars; + int num_pars; +}; + struct priv { bstr data; bool allow_any; @@ -70,16 +75,23 @@ static bool parse_time(bstr str, double *out_time) return true; } +static struct tl_parts *add_part(struct tl_root *root) +{ + struct tl_parts *tl = talloc_zero(root, struct tl_parts); + MP_TARRAY_APPEND(root, root->pars, root->num_pars, tl); + return tl; +} + /* Returns a list of parts, or NULL on parse error. * Syntax (without file header or URI prefix): * url ::= ( (';' | '\n') )* * entry ::= ( ',' )* * param ::= [ '='] ( | '%' '%' ) */ -static struct tl_parts *parse_edl(bstr str) +static struct tl_root *parse_edl(bstr str) { - struct tl_parts *tl = talloc_zero(NULL, struct tl_parts); - struct tl_parts *root = tl; + struct tl_root *root = talloc_zero(NULL, struct tl_root); + struct tl_parts *tl = add_part(root); while (str.len) { if (bstr_eatstart0(&str, "#")) { bstr_split_tok(str, "\n", &(bstr){0}, &str); @@ -153,9 +165,7 @@ static struct tl_parts *parse_edl(bstr str) } else if (bstr_equals0(f_type, "no_clip")) { tl->no_clip = true; } else if (bstr_equals0(f_type, "new_stream")) { - struct tl_parts *ntl = talloc_zero(tl, struct tl_parts); - tl->next = ntl; - tl = ntl; + tl = add_part(root); } else if (bstr_equals0(f_type, "no_chapters")) { tl->disable_chapters = true; } else { @@ -167,29 +177,35 @@ static struct tl_parts *parse_edl(bstr str) MP_TARRAY_APPEND(tl, tl->parts, tl->num_parts, p); } } - if (!root->num_parts) + if (!root->num_pars) goto error; + for (int n = 0; n < root->num_pars; n++) { + if (root->pars[n]->num_parts < 1) + goto error; + } return root; error: talloc_free(root); return NULL; } -static struct demuxer *open_source(struct timeline *tl, char *filename) +static struct demuxer *open_source(struct timeline *root, + struct timeline_par *tl, char *filename) { - for (int n = 0; n < tl->num_sources; n++) { - struct demuxer *d = tl->sources[n]; - if (strcmp(d->stream->url, filename) == 0) + for (int n = 0; n < tl->num_parts; n++) { + struct demuxer *d = tl->parts[n].source; + if (d && strcmp(d->stream->url, filename) == 0) return d; } struct demuxer_params params = { .init_fragment = tl->init_fragment, }; - struct demuxer *d = demux_open_url(filename, ¶ms, tl->cancel, tl->global); + struct demuxer *d = demux_open_url(filename, ¶ms, root->cancel, + root->global); if (d) { - MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, d); + MP_TARRAY_APPEND(root, root->sources, root->num_sources, d); } else { - MP_ERR(tl, "EDL: Could not open source file '%s'.\n", filename); + MP_ERR(root, "EDL: Could not open source file '%s'.\n", filename); } return d; } @@ -236,35 +252,38 @@ static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer) part->offset = demuxer->start_time; } -static void build_timeline(struct timeline *tl, struct tl_parts *parts) +static bool build_timeline(struct timeline *root, struct tl_parts *parts) { + struct timeline_par *tl = talloc_zero(root, struct timeline_par); + MP_TARRAY_APPEND(root, root->pars, root->num_pars, tl); + tl->track_layout = NULL; tl->dash = parts->dash; tl->no_clip = parts->no_clip; if (parts->init_fragment_url && parts->init_fragment_url[0]) { - MP_VERBOSE(tl, "Opening init fragment...\n"); + MP_VERBOSE(root, "Opening init fragment...\n"); stream_t *s = stream_create(parts->init_fragment_url, STREAM_READ, - tl->cancel, tl->global); + root->cancel, root->global); if (s) tl->init_fragment = stream_read_complete(s, tl, 1000000); free_stream(s); if (!tl->init_fragment.len) { - MP_ERR(tl, "Could not read init fragment.\n"); + MP_ERR(root, "Could not read init fragment.\n"); goto error; } struct demuxer_params params = { .init_fragment = tl->init_fragment, }; - tl->track_layout = demux_open_url("memory://", ¶ms, tl->cancel, - tl->global); + tl->track_layout = demux_open_url("memory://", ¶ms, root->cancel, + root->global); if (!tl->track_layout) { - MP_ERR(tl, "Could not demux init fragment.\n"); + MP_ERR(root, "Could not demux init fragment.\n"); goto error; } } - tl->parts = talloc_array_ptrtype(tl, tl->parts, parts->num_parts + 1); + tl->parts = talloc_array_ptrtype(tl, tl->parts, parts->num_parts); double starttime = 0; for (int n = 0; n < parts->num_parts; n++) { struct tl_part *part = &parts->parts[n]; @@ -273,21 +292,16 @@ static void build_timeline(struct timeline *tl, struct tl_parts *parts) if (tl->dash) { part->offset = starttime; if (part->length <= 0) - MP_WARN(tl, "Segment %d has unknown duration.\n", n); + MP_WARN(root, "Segment %d has unknown duration.\n", n); if (part->offset_set) - MP_WARN(tl, "Offsets are ignored.\n"); - if (tl->demuxer) - tl->demuxer->is_network = true; + MP_WARN(root, "Offsets are ignored.\n"); - if (!tl->track_layout) { - tl->track_layout = open_source(tl, part->filename); - if (!tl->track_layout) - goto error; - } + if (!tl->track_layout) + tl->track_layout = open_source(root, tl, part->filename); } else { - MP_VERBOSE(tl, "Opening segment %d...\n", n); + MP_VERBOSE(root, "Opening segment %d...\n", n); - source = open_source(tl, part->filename); + source = open_source(root, tl, part->filename); if (!source) goto error; @@ -301,7 +315,7 @@ static void build_timeline(struct timeline *tl, struct tl_parts *parts) // something up. if (part->length < 0) { if (end_time < 0) { - MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n", + MP_WARN(root, "EDL: source file '%s' has unknown duration.\n", part->filename); end_time = 1; } @@ -309,7 +323,7 @@ static void build_timeline(struct timeline *tl, struct tl_parts *parts) } else if (end_time >= 0) { double end_part = part->offset + part->length; if (end_part > end_time) { - MP_WARN(tl, "EDL: entry %d uses %f " + MP_WARN(root, "EDL: entry %d uses %f " "seconds, but file has only %f seconds.\n", n, end_part, end_time); } @@ -323,33 +337,39 @@ static void build_timeline(struct timeline *tl, struct tl_parts *parts) }; mp_tags_set_str(ch.metadata, "title", part->title ? part->title : part->filename); - MP_TARRAY_APPEND(tl, tl->chapters, tl->num_chapters, ch); + MP_TARRAY_APPEND(root, root->chapters, root->num_chapters, ch); // Also copy the source file's chapters for the relevant parts - copy_chapters(&tl->chapters, &tl->num_chapters, source, + copy_chapters(&root->chapters, &root->num_chapters, source, part->offset, part->length, starttime); } } tl->parts[n] = (struct timeline_part) { .start = starttime, + .end = starttime + part->length, .source_start = part->offset, .source = source, .url = talloc_strdup(tl, part->filename), }; - starttime += part->length; + starttime = tl->parts[n].end; if (source && !tl->track_layout) tl->track_layout = source; } - tl->parts[parts->num_parts] = (struct timeline_part) {.start = starttime}; + + if (!tl->track_layout) + goto error; + if (!root->meta) + root->meta = tl->track_layout; + tl->num_parts = parts->num_parts; - return; + return true; error: - tl->num_parts = 0; - tl->num_chapters = 0; + root->num_pars = 0; + return false; } // For security, don't allow relative or absolute paths, only plain filenames. @@ -368,30 +388,18 @@ static void build_mpv_edl_timeline(struct timeline *tl) { struct priv *p = tl->demuxer->priv; - struct timeline *root_tl = tl; - struct tl_parts *root = parse_edl(p->data); + struct tl_root *root = parse_edl(p->data); if (!root) { MP_ERR(tl, "Error in EDL.\n"); return; } - for (struct tl_parts *parts = root; parts; parts = parts->next) { - if (tl->demuxer) - MP_TARRAY_APPEND(tl, tl->sources, tl->num_sources, tl->demuxer); + for (int n = 0; n < root->num_pars; n++) { + struct tl_parts *parts = root->pars[n]; if (!p->allow_any) - fix_filenames(parts, root_tl->demuxer->filename); - build_timeline(tl, parts); - - if (parts->next) { - struct timeline *ntl = talloc_zero(tl, struct timeline); - *ntl = (struct timeline) { - .global = tl->global, - .log = tl->log, - .cancel = tl->cancel, - }; - tl->next = ntl; - tl = ntl; - } + fix_filenames(parts, tl->demuxer->filename); + if (!build_timeline(tl, parts)) + break; } talloc_free(root); } diff --git a/demux/demux_mkv_timeline.c b/demux/demux_mkv_timeline.c index 1408ddf8f5..6d52995e26 100644 --- a/demux/demux_mkv_timeline.c +++ b/demux/demux_mkv_timeline.c @@ -455,12 +455,10 @@ static void build_timeline_loop(struct tl_ctx *ctx, ctx->missing_time += info->limit - local_starttime; } -static void check_track_compatibility(struct timeline *tl) +static void check_track_compatibility(struct tl_ctx *tl, struct demuxer *mainsrc) { - struct demuxer *mainsrc = tl->track_layout; - for (int n = 0; n < tl->num_parts; n++) { - struct timeline_part *p = &tl->parts[n]; + struct timeline_part *p = &tl->timeline[n]; if (p->source == mainsrc) continue; @@ -593,10 +591,11 @@ void build_ordered_chapter_timeline(struct timeline *tl) MP_TARRAY_APPEND(NULL, ctx->timeline, ctx->num_parts, new); } - struct timeline_part new = { - .start = ctx->start_time / 1e9, + for (int n = 0; n < ctx->num_parts; n++) { + ctx->timeline[n].end = n == ctx->num_parts - 1 + ? ctx->start_time / 1e9 + : ctx->timeline[n + 1].start; }; - MP_TARRAY_APPEND(NULL, ctx->timeline, ctx->num_parts, new); /* Ignore anything less than a millisecond when reporting missing time. If * users really notice less than a millisecond missing, maybe this can be @@ -606,22 +605,29 @@ void build_ordered_chapter_timeline(struct timeline *tl) ctx->missing_time / 1e9); } - tl->sources = ctx->sources; - tl->num_sources = ctx->num_sources; - tl->parts = ctx->timeline; - tl->num_parts = ctx->num_parts - 1; // minus termination - tl->chapters = chapters; - tl->num_chapters = m->num_ordered_chapters; - // With Matroska, the "master" file usually dictates track layout etc., // except maybe with playlist-like files. - tl->track_layout = tl->parts[0].source; - for (int n = 0; n < tl->num_parts; n++) { - if (tl->parts[n].source == ctx->demuxer) { - tl->track_layout = ctx->demuxer; + struct demuxer *track_layout = ctx->timeline[0].source; + for (int n = 0; n < ctx->num_parts; n++) { + if (ctx->timeline[n].source == ctx->demuxer) { + track_layout = ctx->demuxer; break; } } - check_track_compatibility(tl); + check_track_compatibility(ctx, track_layout); + + tl->sources = ctx->sources; + tl->num_sources = ctx->num_sources; + + struct timeline_par *par = talloc_ptrtype(tl, par); + *par = (struct timeline_par){ + .parts = ctx->timeline, + .num_parts = ctx->num_parts, + .track_layout = track_layout, + }; + MP_TARRAY_APPEND(tl, tl->pars, tl->num_pars, par); + tl->chapters = chapters; + tl->num_chapters = m->num_ordered_chapters; + tl->meta = track_layout; } diff --git a/demux/demux_timeline.c b/demux/demux_timeline.c index b61fb11c28..1d692d8387 100644 --- a/demux/demux_timeline.c +++ b/demux/demux_timeline.c @@ -54,7 +54,7 @@ struct virtual_stream { // This represents a single timeline source. (See timeline.next. For each // timeline struct there is a virtual_source.) struct virtual_source { - struct timeline *tl; + struct timeline_par *tl; bool dash, no_clip; @@ -87,7 +87,7 @@ struct priv { int num_sources; }; -static void add_tl(struct demuxer *demuxer, struct timeline *tl); +static bool add_tl(struct demuxer *demuxer, struct timeline_par *par); static void update_slave_stats(struct demuxer *demuxer, struct demuxer *slave) { @@ -415,13 +415,14 @@ static void print_timeline(struct demuxer *demuxer) break; } } - MP_VERBOSE(demuxer, " %2d: %12f [%12f] (", n, seg->start, seg->d_start); + MP_VERBOSE(demuxer, " %2d: %12f - %12f [%12f] (", + n, seg->start, seg->end, seg->d_start); for (int i = 0; i < seg->num_stream_map; i++) { struct virtual_stream *vs = seg->stream_map[i]; MP_VERBOSE(demuxer, "%s%d", i ? " " : "", vs ? vs->sh->index : -1); } - MP_VERBOSE(demuxer, ") %d:'%s'\n", src_num, seg->url); + MP_VERBOSE(demuxer, ")\n source %d:'%s'\n", src_num, seg->url); } if (src->dash) @@ -434,13 +435,15 @@ static int d_open(struct demuxer *demuxer, enum demux_check check) { struct priv *p = demuxer->priv = talloc_zero(demuxer, struct priv); p->tl = demuxer->params ? demuxer->params->timeline : NULL; - if (!p->tl || p->tl->num_parts < 1) + if (!p->tl || p->tl->num_pars < 1) return -1; demuxer->chapters = p->tl->chapters; demuxer->num_chapters = p->tl->num_chapters; - struct demuxer *meta = p->tl->track_layout; + struct demuxer *meta = p->tl->meta; + if (!meta) + return -1; demuxer->metadata = meta->metadata; demuxer->attachments = meta->attachments; demuxer->num_attachments = meta->num_attachments; @@ -448,8 +451,13 @@ static int d_open(struct demuxer *demuxer, enum demux_check check) demuxer->num_editions = meta->num_editions; demuxer->edition = meta->edition; - for (struct timeline *tl = p->tl; tl; tl = tl->next) - add_tl(demuxer, tl); + for (int n = 0; n < p->tl->num_pars; n++) { + if (!add_tl(demuxer, p->tl->pars[n])) + return -1; + } + + if (!p->num_sources) + return -1; demuxer->duration = p->duration; @@ -459,7 +467,7 @@ static int d_open(struct demuxer *demuxer, enum demux_check check) demuxer->partially_seekable = false; demuxer->filetype = talloc_asprintf(p, "edl/%s%s", - p->num_sources && p->sources[0]->dash ? "dash/" : "", + p->sources[0]->dash ? "dash/" : "", meta->filetype ? meta->filetype : meta->desc->name); reselect_streams(demuxer); @@ -467,7 +475,7 @@ static int d_open(struct demuxer *demuxer, enum demux_check check) return 0; } -static void add_tl(struct demuxer *demuxer, struct timeline *tl) +static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl) { struct priv *p = demuxer->priv; @@ -479,9 +487,12 @@ static void add_tl(struct demuxer *demuxer, struct timeline *tl) .dts = MP_NOPTS_VALUE, }; + if (!tl->num_parts || !tl->track_layout) + return false; + MP_TARRAY_APPEND(p, p->sources, p->num_sources, src); - p->duration = MPMAX(p->duration, tl->parts[tl->num_parts].start); + p->duration = MPMAX(p->duration, tl->parts[tl->num_parts - 1].end); struct demuxer *meta = tl->track_layout; @@ -511,7 +522,6 @@ static void add_tl(struct demuxer *demuxer, struct timeline *tl) for (int n = 0; n < tl->num_parts; n++) { struct timeline_part *part = &tl->parts[n]; - struct timeline_part *next = &tl->parts[n + 1]; // demux_timeline already does caching, doing it for the sub-demuxers // would be pointless and wasteful. @@ -527,7 +537,7 @@ static void add_tl(struct demuxer *demuxer, struct timeline *tl) .lazy = !part->source, .d_start = part->source_start, .start = part->start, - .end = next->start, + .end = part->end, }; associate_streams(demuxer, src, seg); @@ -537,6 +547,7 @@ static void add_tl(struct demuxer *demuxer, struct timeline *tl) } demuxer->is_network |= tl->track_layout->is_network; + return true; } static void d_close(struct demuxer *demuxer) diff --git a/demux/timeline.c b/demux/timeline.c index 86e4921280..f771155e18 100644 --- a/demux/timeline.c +++ b/demux/timeline.c @@ -16,12 +16,11 @@ struct timeline *timeline_load(struct mpv_global *global, struct mp_log *log, .log = log, .cancel = demuxer->cancel, .demuxer = demuxer, - .track_layout = demuxer, }; demuxer->desc->load_timeline(tl); - if (tl->num_parts) + if (tl->num_pars) return tl; timeline_destroy(tl); return NULL; @@ -31,14 +30,10 @@ void timeline_destroy(struct timeline *tl) { if (!tl) return; - // (Sub timeline elements may depend on allocations in the parent one.) - timeline_destroy(tl->next); for (int n = 0; n < tl->num_sources; n++) { struct demuxer *d = tl->sources[n]; - if (d != tl->demuxer && d != tl->track_layout) + if (d != tl->demuxer) demux_free(d); } - if (tl->track_layout && tl->track_layout != tl->demuxer) - demux_free(tl->track_layout); talloc_free(tl); } diff --git a/demux/timeline.h b/demux/timeline.h index 0940592aea..544220358a 100644 --- a/demux/timeline.h +++ b/demux/timeline.h @@ -1,44 +1,55 @@ #ifndef MP_TIMELINE_H_ #define MP_TIMELINE_H_ +// Single segment in a timeline. struct timeline_part { - double start; + // (end time must match with start time of the next part) + double start, end; double source_start; char *url; struct demuxer *source; }; +// Timeline formed by a single demuxer. Multiple pars are used to get tracks +// that require a separate opened demuxer, such as separate audio tracks. (For +// example, for ordered chapters there is only a single par, because all streams +// demux from the same file at a given time, while for DASH-style video+audio, +// each track would have its own timeline.) +// Note that demuxer instances must not be shared across timeline_pars. This +// would conflict in demux_timeline.c. +// "par" is short for parallel stream. +struct timeline_par { + bstr init_fragment; + bool dash, no_clip; + + // Segments to play, ordered by time. + struct timeline_part *parts; + int num_parts; + + // Which source defines the overall track list (over the full timeline). + struct demuxer *track_layout; +}; + struct timeline { struct mpv_global *global; struct mp_log *log; struct mp_cancel *cancel; - // main source + // main source, and all other sources (this usually only has special meaning + // for memory management; mostly compensates for the lack of refcounting) struct demuxer *demuxer; - - bstr init_fragment; - bool dash, no_clip; - - // All referenced files. struct demuxer **sources; int num_sources; - // Segments to play, ordered by time. parts[num_parts] must be valid; its - // start field sets the duration, and source must be NULL. - struct timeline_part *parts; - int num_parts; + // Description of timeline ranges, possibly multiple parallel ones. + struct timeline_par **pars; + int num_pars; struct demux_chapter *chapters; int num_chapters; - // Which source defines the overall track list (over the full timeline). - struct demuxer *track_layout; - - // For tracks which require a separate opened demuxer, such as separate - // audio tracks. (For example, for ordered chapters this would be NULL, - // because all streams demux from the same file at a given time, while - // for DASH-style video+audio, each track would have its own timeline.) - struct timeline *next; + // global tags, attachments, editions + struct demuxer *meta; }; struct timeline *timeline_load(struct mpv_global *global, struct mp_log *log, diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua index df0a0195f8..ab29610f0c 100644 --- a/player/lua/ytdl_hook.lua +++ b/player/lua/ytdl_hook.lua @@ -331,9 +331,9 @@ local function add_single_video(json) if #streams > 1 then -- merge them via EDL for i = 1, #streams do - streams[i] = "!no_clip;" .. edl_escape(streams[i]) + streams[i] = "!no_clip;!no_chapters;" .. edl_escape(streams[i]) end - streamurl = "edl://!no_chapters;" .. + streamurl = "edl://" .. table.concat(streams, ";!new_stream;") .. ";" else streamurl = streams[1]