summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--demux/demux_cue.c20
-rw-r--r--demux/demux_edl.c128
-rw-r--r--demux/demux_mkv_timeline.c44
-rw-r--r--demux/demux_timeline.c37
-rw-r--r--demux/timeline.c9
-rw-r--r--demux/timeline.h49
-rw-r--r--player/lua/ytdl_hook.lua4
7 files changed, 160 insertions, 131 deletions
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 ::= <entry> ( (';' | '\n') <entry> )*
* entry ::= <param> ( <param> ',' )*
* param ::= [<string> '='] (<string> | '%' <number> '%' <bytes>)
*/
-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, &params, tl->cancel, tl->global);
+ struct demuxer *d = demux_open_url(filename, &params, 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://", &params, tl->cancel,
- tl->global);
+ tl->track_layout = demux_open_url("memory://", &params, 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]