summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2020-02-15 18:09:28 +0100
committerwm4 <wm4@nowhere>2020-02-15 18:29:44 +0100
commitc92c08edc671e64c31d98584e709b0f712864772 (patch)
tree7895910f80f81c11493fcf546c9fdbf77f4856e4
parent1d53a8f2e725cb9e3e71a9ca70feb880747d782f (diff)
downloadmpv-c92c08edc671e64c31d98584e709b0f712864772.tar.bz2
mpv-c92c08edc671e64c31d98584e709b0f712864772.tar.xz
edl: add mechanism for delay loading streams
Add something that will access an URL embedded in EDL only when the track it corresponds to is actually selected. This is meant to help with ytdl_hook.lua and to improve loading speeds. In theory, all this stuff is available to any mpv user, but discourage using it, as it's so specialized towards ytdl_hook.lua, that there's danger we'll just break this once ytdl_hook.lua stops using it, or similar. Mostly untested.
-rw-r--r--DOCS/edl-mpv.rst72
-rw-r--r--demux/demux.c1
-rw-r--r--demux/demux_edl.c64
-rw-r--r--demux/demux_timeline.c107
-rw-r--r--demux/timeline.h8
5 files changed, 219 insertions, 33 deletions
diff --git a/DOCS/edl-mpv.rst b/DOCS/edl-mpv.rst
index 495b27279e..ec60d09ccc 100644
--- a/DOCS/edl-mpv.rst
+++ b/DOCS/edl-mpv.rst
@@ -195,6 +195,78 @@ example as above::
Note that ``!new_stream`` must be the first header. Whether the parser accepts
(i.e. ignores) or rejects other headers before that is implementation specific.
+Track metadata
+==============
+
+The special ``track_meta`` header can set some specific metadata fields of the
+current ``!new_stream`` partition. The tags are applied to all tracks within
+the partition. It is not possible to set the metadata for individual tracks (the
+feature was needed only for single-track media).
+
+It provides following parameters change track metadata:
+
+``lang``
+ Set the language tag.
+
+``title``
+ Set the title tag.
+
+``byterate``
+ Number of bytes per second this stream uses. (Purely informational.)
+
+Example::
+
+ # mpv EDL v0
+ !track_meta,lang=bla,title=blabla
+ file.mkv
+ !new_stream
+ !track_meta,title=ducks
+ sub.srt
+
+If ``file.mkv`` has an audio and a video stream, both will use ``blabla`` as
+title. The subtitle stream will use ``ducks`` as title.
+
+The ``track_meta`` header is not part of the core EDL format. It may be changed
+or removed at any time, depending on mpv's internal requirements.
+
+Delayed media opening
+=====================
+
+The special ``delay_open`` header can be used to open the media URL of the
+stream only when the track is selected for the first time. This is supposed to
+be an optimization to speed up opening of a remote stream if there are many
+tracks for whatever reasons.
+
+This has various tricky restrictions, and also will defer failure to open a
+stream to "later". By design, it's supposed to be used for single-track streams.
+
+Using multiple segments requires you to specify all offsets and durations (also
+it was never tested whether it works at all). Interaction with ``mp4_dash`` may
+be strange.
+
+This has the following parameters:
+
+``media_type``
+ Required. Must be set to ``video``, ``audio``, or ``sub``. (Other tracks in
+ the opened URL are ignored.)
+
+``codec``
+ The mpv codec name that is expected. Although mpv tries to initialize a
+ decoder with it currently (and will fail track selection if it does not
+ initialize successfully), it is not used for decoding - decoding still uses
+ the information retrieved from opening the actual media information, and may
+ be a different codec (you should try to avoid this, of course). Defaults to
+ ``null``.
+
+ Above also applies for similar fields such as ``w``. These fields are
+ mostly to help with user track pre-selection.
+
+``w``, ``h``
+ For video codecs: expected video size. See ``codec`` for details.
+
+The ``delay_open`` header is not part of the core EDL format. It may be changed
+or removed at any time, depending on mpv's internal requirements.
+
Timestamp format
================
diff --git a/demux/demux.c b/demux/demux.c
index 2ecfc96ba2..88c4374e63 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -953,6 +953,7 @@ static void add_missing_streams(struct demux_internal *in,
// Allocate a new sh_stream of the given type. It either has to be released
// with talloc_free(), or added to a demuxer with demux_add_sh_stream(). You
// cannot add or read packets from the stream before it has been added.
+// type may be changed later, but only before demux_add_sh_stream().
struct sh_stream *demux_alloc_sh_stream(enum stream_type type)
{
struct sh_stream *sh = talloc_ptrtype(NULL, sh);
diff --git a/demux/demux_edl.c b/demux/demux_edl.c
index 6e1af373ee..e71c1dfd99 100644
--- a/demux/demux_edl.c
+++ b/demux/demux_edl.c
@@ -21,6 +21,7 @@
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
+#include <limits.h>
#include <math.h>
#include "mpv_talloc.h"
@@ -47,8 +48,9 @@ struct tl_part {
struct tl_parts {
bool disable_chapters;
- bool dash, no_clip;
+ bool dash, no_clip, delay_open;
char *init_fragment_url;
+ struct sh_stream *sh_meta;
struct tl_part *parts;
int num_parts;
struct tl_parts *next;
@@ -102,6 +104,24 @@ static char *get_param0(struct parse_ctx *ctx, void *ta_ctx, const char *name)
return bstrdup0(ta_ctx, get_param(ctx, name));
}
+// Optional int parameter. Returns the parsed integer, or def if the parameter
+// is missing or on error (sets ctx.error on error).
+static int get_param_int(struct parse_ctx *ctx, const char *name, int def)
+{
+ bstr val = get_param(ctx, name);
+ if (val.start) {
+ bstr rest;
+ long long ival = bstrtoll(val, &rest, 0);
+ if (!val.len || rest.len || ival < INT_MIN || ival > INT_MAX) {
+ MP_ERR(ctx, "Invalid integer: '%.*s'\n", BSTR_P(val));
+ ctx->error = true;
+ return def;
+ }
+ return ival;
+ }
+ return def;
+}
+
// Optional time parameter. Currently a number.
// Returns true: parameter was present and valid, *t is set
// Returns false: parameter was not present (or broken => ctx.error set)
@@ -125,6 +145,8 @@ static bool get_param_time(struct parse_ctx *ctx, const char *name, double *t)
static struct tl_parts *add_part(struct tl_root *root)
{
struct tl_parts *tl = talloc_zero(root, struct tl_parts);
+ tl->sh_meta = demux_alloc_sh_stream(STREAM_TYPE_COUNT);
+ talloc_steal(tl, tl->sh_meta);
MP_TARRAY_APPEND(root, root->pars, root->num_pars, tl);
return tl;
}
@@ -200,6 +222,30 @@ static struct tl_root *parse_edl(bstr str, struct mp_log *log)
tl = add_part(root);
} else if (bstr_equals0(f_type, "no_chapters")) {
tl->disable_chapters = true;
+ } else if (bstr_equals0(f_type, "track_meta")) {
+ struct sh_stream *sh = tl->sh_meta;
+ sh->lang = get_param0(&ctx, sh, "lang");
+ sh->title = get_param0(&ctx, sh, "title");
+ sh->hls_bitrate = get_param_int(&ctx, "byterate", 0) * 8;
+ } else if (bstr_equals0(f_type, "delay_open")) {
+ struct sh_stream *sh = tl->sh_meta;
+ bstr mt = get_param(&ctx, "media_type");
+ if (bstr_equals0(mt, "video")) {
+ sh->type = sh->codec->type = STREAM_VIDEO;
+ } else if (bstr_equals0(mt, "audio")) {
+ sh->type = sh->codec->type = STREAM_AUDIO;
+ } else if (bstr_equals0(mt, "sub")) {
+ sh->type = sh->codec->type = STREAM_SUB;
+ } else {
+ mp_err(log, "Invalid or missing !delay_open media type.\n");
+ goto error;
+ }
+ sh->codec->codec = get_param0(&ctx, sh, "codec");
+ if (!sh->codec->codec)
+ sh->codec->codec = "null";
+ sh->codec->disp_w = get_param_int(&ctx, "w", 0);
+ sh->codec->disp_h = get_param_int(&ctx, "h", 0);
+ tl->delay_open = true;
} else {
mp_err(log, "Unknown header: '%.*s'\n", BSTR_P(f_type));
goto error;
@@ -322,6 +368,11 @@ static struct timeline_par *build_timeline(struct timeline *root,
tl->track_layout = NULL;
tl->dash = parts->dash;
tl->no_clip = parts->no_clip;
+ tl->delay_open = parts->delay_open;
+
+ // There is no copy function for sh_stream, so just steal it.
+ tl->sh_meta = talloc_steal(tl, parts->sh_meta);
+ parts->sh_meta = NULL;
if (parts->init_fragment_url && parts->init_fragment_url[0]) {
MP_VERBOSE(root, "Opening init fragment...\n");
@@ -366,6 +417,15 @@ static struct timeline_par *build_timeline(struct timeline *root,
if (!tl->track_layout)
tl->track_layout = open_source(root, tl, part->filename);
+ } else if (tl->delay_open) {
+ if (n == 0 && !part->offset_set) {
+ part->offset = starttime;
+ part->offset_set = true;
+ }
+ if (part->chapter_ts || (part->length < 0 && !tl->no_clip)) {
+ MP_ERR(root, "Invalid specification for delay_open stream.\n");
+ goto error;
+ }
} else {
MP_VERBOSE(root, "Opening segment %d...\n", n);
@@ -444,7 +504,7 @@ static struct timeline_par *build_timeline(struct timeline *root,
}
}
- if (!tl->track_layout)
+ if (!tl->track_layout && !tl->delay_open)
goto error;
if (!root->meta)
root->meta = tl->track_layout;
diff --git a/demux/demux_timeline.c b/demux/demux_timeline.c
index cdad24f02d..a9a762d3e4 100644
--- a/demux/demux_timeline.c
+++ b/demux/demux_timeline.c
@@ -56,7 +56,7 @@ struct virtual_stream {
struct virtual_source {
struct timeline_par *tl;
- bool dash, no_clip;
+ bool dash, no_clip, delay_open;
struct segment **segments;
int num_segments;
@@ -204,11 +204,15 @@ static void reopen_lazy_segments(struct demuxer *demuxer,
if (src->current->d)
return;
- close_lazy_segments(demuxer, src);
+ // Note: in delay_open mode, we must _not_ close segments during demuxing,
+ // because demuxed packets have demux_packet.codec set to objects owned
+ // by the segments. Closing them would create dangling pointers.
+ if (!src->delay_open)
+ close_lazy_segments(demuxer, src);
struct demuxer_params params = {
.init_fragment = src->tl->init_fragment,
- .skip_lavf_probing = true,
+ .skip_lavf_probing = src->tl->dash,
.stream_flags = demuxer->stream_origin,
};
src->current->d = demux_open_url(src->current->url, &params,
@@ -309,10 +313,12 @@ static bool do_read_next_packet(struct demuxer *demuxer,
if (pkt->stream < 0 || pkt->stream >= seg->num_stream_map)
goto drop;
- if (!src->no_clip) {
+ if (!src->no_clip || src->delay_open) {
pkt->segmented = true;
if (!pkt->codec)
pkt->codec = demux_get_stream(seg->d, pkt->stream)->codec;
+ }
+ if (!src->no_clip) {
if (pkt->start == MP_NOPTS_VALUE || pkt->start < seg->start)
pkt->start = seg->start;
if (pkt->end == MP_NOPTS_VALUE || pkt->end > seg->end)
@@ -495,6 +501,27 @@ static void print_timeline(struct demuxer *demuxer)
MP_VERBOSE(demuxer, "Total duration: %f\n", p->duration);
}
+// Copy various (not all) metadata fields from src to dst, but try not to
+// overwrite fields in dst that are unset in src.
+// May keep data from src by reference.
+// Imperfect and arbitrary, only suited for EDL stuff.
+static void apply_meta(struct sh_stream *dst, struct sh_stream *src)
+{
+ if (src->demuxer_id >= 0)
+ dst->demuxer_id = src->demuxer_id;
+ if (src->title)
+ dst->title = src->title;
+ if (src->lang)
+ dst->lang = src->lang;
+ dst->default_track = src->default_track;
+ dst->forced_track = src->forced_track;
+ if (src->hls_bitrate)
+ dst->hls_bitrate = src->hls_bitrate;
+ dst->missing_timestamps = src->missing_timestamps;
+ if (src->attached_picture)
+ dst->attached_picture = src->attached_picture;
+}
+
static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
{
struct priv *p = demuxer->priv;
@@ -503,11 +530,12 @@ static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
*src = (struct virtual_source){
.tl = tl,
.dash = tl->dash,
+ .delay_open = tl->delay_open,
.no_clip = tl->no_clip || tl->dash,
.dts = MP_NOPTS_VALUE,
};
- if (!tl->num_parts || !tl->track_layout)
+ if (!tl->num_parts)
return false;
MP_TARRAY_APPEND(p, p->sources, p->num_sources, src);
@@ -516,19 +544,32 @@ static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
struct demuxer *meta = tl->track_layout;
- int num_streams = demux_get_num_stream(meta);
+ // delay_open streams normally have meta==NULL, and 1 virtual stream
+ int num_streams = 0;
+ if (tl->delay_open) {
+ num_streams = 1;
+ } else if (meta) {
+ num_streams = demux_get_num_stream(meta);
+ }
for (int n = 0; n < num_streams; n++) {
- struct sh_stream *sh = demux_get_stream(meta, n);
- struct sh_stream *new = demux_alloc_sh_stream(sh->type);
- new->demuxer_id = sh->demuxer_id;
- new->codec = sh->codec;
- new->title = sh->title;
- new->lang = sh->lang;
- new->default_track = sh->default_track;
- new->forced_track = sh->forced_track;
- new->hls_bitrate = sh->hls_bitrate;
- new->missing_timestamps = sh->missing_timestamps;
- new->attached_picture = sh->attached_picture;
+ struct sh_stream *new = NULL;
+
+ if (tl->delay_open) {
+ assert(tl->sh_meta);
+ new = demux_alloc_sh_stream(tl->sh_meta->type);
+ new->codec = tl->sh_meta->codec;
+ demuxer->is_network = true;
+ demuxer->is_streaming = true;
+ } else {
+ struct sh_stream *sh = demux_get_stream(meta, n);
+ new = demux_alloc_sh_stream(sh->type);
+ apply_meta(new, sh);
+ new->codec = sh->codec;
+ }
+
+ if (tl->sh_meta)
+ apply_meta(new, tl->sh_meta);
+
demux_add_sh_stream(demuxer, new);
struct virtual_stream *vs = talloc_ptrtype(p, vs);
*vs = (struct virtual_stream){
@@ -550,6 +591,9 @@ static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
demuxer->is_streaming |= part->source->is_streaming;
}
+ if (!part->source)
+ assert(tl->dash || tl->delay_open);
+
struct segment *seg = talloc_ptrtype(src, seg);
*seg = (struct segment){
.d = part->source,
@@ -566,8 +610,10 @@ static bool add_tl(struct demuxer *demuxer, struct timeline_par *tl)
MP_TARRAY_APPEND(src, src->segments, src->num_segments, seg);
}
- demuxer->is_network |= tl->track_layout->is_network;
- demuxer->is_streaming |= tl->track_layout->is_streaming;
+ if (tl->track_layout) {
+ demuxer->is_network |= tl->track_layout->is_network;
+ demuxer->is_streaming |= tl->track_layout->is_streaming;
+ }
return true;
}
@@ -582,14 +628,14 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
demuxer->num_chapters = p->tl->num_chapters;
struct demuxer *meta = p->tl->meta;
- if (!meta)
- return -1;
- demuxer->metadata = meta->metadata;
- demuxer->attachments = meta->attachments;
- demuxer->num_attachments = meta->num_attachments;
- demuxer->editions = meta->editions;
- demuxer->num_editions = meta->num_editions;
- demuxer->edition = meta->edition;
+ if (meta) {
+ demuxer->metadata = meta->metadata;
+ demuxer->attachments = meta->attachments;
+ demuxer->num_attachments = meta->num_attachments;
+ demuxer->editions = meta->editions;
+ demuxer->num_editions = meta->num_editions;
+ demuxer->edition = meta->edition;
+ }
for (int n = 0; n < p->tl->num_pars; n++) {
if (!add_tl(demuxer, p->tl->pars[n]))
@@ -609,9 +655,10 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
demuxer->seekable = true;
demuxer->partially_seekable = false;
- demuxer->filetype = talloc_asprintf(p, "%s/%s",
- p->tl->format,
- meta->filetype ? meta->filetype : meta->desc->name);
+ const char *format_name = "unknown";
+ if (meta)
+ format_name = meta->filetype ? meta->filetype : meta->desc->name;
+ demuxer->filetype = talloc_asprintf(p, "%s/%s", p->tl->format, format_name);
reselect_streams(demuxer);
diff --git a/demux/timeline.h b/demux/timeline.h
index c8151a0606..faeec53b32 100644
--- a/demux/timeline.h
+++ b/demux/timeline.h
@@ -1,6 +1,8 @@
#ifndef MP_TIMELINE_H_
#define MP_TIMELINE_H_
+#include "common/common.h"
+
// Single segment in a timeline.
struct timeline_part {
// (end time must match with start time of the next part)
@@ -20,7 +22,11 @@ struct timeline_part {
// "par" is short for parallel stream.
struct timeline_par {
bstr init_fragment;
- bool dash, no_clip;
+ bool dash, no_clip, delay_open;
+
+ // If non-NULL, _some_ fields are used. If delay_open==true, this must be
+ // set, and the codec info is used.
+ struct sh_stream *sh_meta;
// Segments to play, ordered by time.
struct timeline_part *parts;