summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2020-02-15 02:19:20 +0100
committerwm4 <wm4@nowhere>2020-02-15 02:19:20 +0100
commita9acfa82c35c4d5a98737d1cfda5d56e622f9674 (patch)
tree10cf2bb9c86fdd5c5c13a08a8c9f8a5cb3ced140
parent641d102101087a3453398800febce2f65f104203 (diff)
downloadmpv-ytdl_sub_delayload.tar.bz2
mpv-ytdl_sub_delayload.tar.xz
-rw-r--r--DOCS/edl-mpv.rst65
-rw-r--r--demux/demux_edl.c51
-rw-r--r--demux/demux_timeline.c87
-rw-r--r--demux/timeline.h8
-rw-r--r--filters/f_decoder_wrapper.c8
-rw-r--r--player/lua/ytdl_hook.lua4
-rw-r--r--sub/sd_ass.c6
7 files changed, 194 insertions, 35 deletions
diff --git a/DOCS/edl-mpv.rst b/DOCS/edl-mpv.rst
index 46a25293aa..60936f9cb5 100644
--- a/DOCS/edl-mpv.rst
+++ b/DOCS/edl-mpv.rst
@@ -182,6 +182,71 @@ this will use a unified cache for all streams.
The ``new_stream`` header is not part of the core EDL format. It may be changed
or removed at any time, depending on mpv's internal requirements.
+Ff the first ``!new_stream`` is redundant, it is ignored. This is the same
+example as above::
+
+ # mpv EDL v0
+ !new_stream
+ video.mkv
+ !new_stream
+ audio.mkv
+
+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.
+
+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 requires specifying the ``media_type`` parameter, which has to be set to
+``video``, ``audio``, or ``sub``. Other tracks in the opened URL are ignored.
+This is the minimum metadata that must be provided.
+
+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_edl.c b/demux/demux_edl.c
index 9d276732fe..afa14cc084 100644
--- a/demux/demux_edl.c
+++ b/demux/demux_edl.c
@@ -49,6 +49,9 @@ struct tl_parts {
bool disable_chapters;
bool dash, no_clip;
char *init_fragment_url;
+ char *title, *lang;
+ bool delay_open;
+ enum stream_type delay_open_st;
struct tl_part *parts;
int num_parts;
struct tl_parts *next;
@@ -102,6 +105,9 @@ static struct tl_root *parse_edl(bstr str)
bool is_header = bstr_eatstart0(&str, "!");
bstr f_type = {0};
bstr f_init = {0};
+ bstr f_lang = {0};
+ bstr f_title = {0};
+ bstr f_mt = {0};
struct tl_part p = { .length = -1 };
int nparam = 0;
while (1) {
@@ -135,6 +141,12 @@ static struct tl_root *parse_edl(bstr str)
f_type = val;
} else if (bstr_equals0(name, "init")) {
f_init = val;
+ } else if (bstr_equals0(name, "lang")) {
+ f_lang = val;
+ } else if (bstr_equals0(name, "title")) {
+ f_title = val;
+ } else if (bstr_equals0(name, "media_type")) {
+ f_mt = val;
}
} else {
if (bstr_equals0(name, "file")) {
@@ -168,9 +180,28 @@ static struct tl_root *parse_edl(bstr str)
} else if (bstr_equals0(f_type, "no_clip")) {
tl->no_clip = true;
} else if (bstr_equals0(f_type, "new_stream")) {
- tl = add_part(root);
+ // (Special case: ignore "redundant" headers at the start for
+ // general symmetry.)
+ if (root->num_pars > 1 || tl->num_parts)
+ tl = add_part(root);
} else if (bstr_equals0(f_type, "no_chapters")) {
tl->disable_chapters = true;
+ } else if (bstr_equals0(f_type, "track_meta")) {
+ if (f_lang.start)
+ tl->lang = bstrto0(tl, f_lang);
+ if (f_title.start)
+ tl->title = bstrto0(tl, f_title);
+ } else if (bstr_equals0(f_type, "delay_open")) {
+ if (bstr_equals0(f_mt, "video")) {
+ tl->delay_open_st = STREAM_VIDEO;
+ } else if (bstr_equals0(f_mt, "audio")) {
+ tl->delay_open_st = STREAM_AUDIO;
+ } else if (bstr_equals0(f_mt, "sub")) {
+ tl->delay_open_st = STREAM_SUB;
+ } else {
+ goto error;
+ }
+ tl->delay_open = true;
} else {
goto error;
}
@@ -265,6 +296,10 @@ static struct timeline_par *build_timeline(struct timeline *root,
tl->track_layout = NULL;
tl->dash = parts->dash;
tl->no_clip = parts->no_clip;
+ tl->title = talloc_strdup(tl, parts->title);
+ tl->lang = talloc_strdup(tl, parts->lang);
+ tl->delay_open = parts->delay_open;
+ tl->delay_open_st = parts->delay_open_st;
if (parts->init_fragment_url && parts->init_fragment_url[0]) {
MP_VERBOSE(root, "Opening init fragment...\n");
@@ -309,6 +344,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, "Incomplete specification for delay_open stream.\n");
+ goto error;
+ }
} else {
MP_VERBOSE(root, "Opening segment %d...\n", n);
@@ -372,6 +416,9 @@ static struct timeline_par *build_timeline(struct timeline *root,
tl->num_parts++;
}
+ if (tl->no_clip && tl->num_parts > 1)
+ MP_WARN(root, "Multiple parts with no_clip. Undefined behavior ahead.\n");
+
if (!tl->track_layout) {
// Use a heuristic to select the "broadest" part as layout.
for (int n = 0; n < parts->num_parts; n++) {
@@ -384,7 +431,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 ecce06bcb2..362e113402 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;
@@ -207,11 +207,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,
@@ -418,10 +422,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)
@@ -509,14 +515,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]))
@@ -536,9 +542,11 @@ 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);
@@ -553,11 +561,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);
@@ -566,19 +575,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==0, and 1 virtual stream
+ int num_streams = meta ? demux_get_num_stream(meta) : tl->delay_open;
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 *sh = meta ? demux_get_stream(meta, n) : NULL;
+ struct sh_stream *new = NULL;
+
+ if (sh) {
+ new = demux_alloc_sh_stream(sh->type);
+ new->demuxer_id = sh->demuxer_id;
+ new->codec = sh->codec;
+ new->title = tl->title ? tl->title : sh->title;
+ new->lang = tl->lang ? tl->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;
+ } else {
+ assert(tl->delay_open);
+ new = demux_alloc_sh_stream(tl->delay_open_st);
+ new->codec->type = new->type;
+ new->codec->codec = "null";
+ new->title = tl->title ? tl->title : NULL;
+ new->lang = tl->lang ? tl->lang : NULL;
+ }
+
demux_add_sh_stream(demuxer, new);
struct virtual_stream *vs = talloc_ptrtype(p, vs);
*vs = (struct virtual_stream){
@@ -600,6 +622,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,
@@ -616,8 +641,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;
}
diff --git a/demux/timeline.h b/demux/timeline.h
index c8151a0606..04ded0ad64 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)
@@ -22,6 +24,12 @@ struct timeline_par {
bstr init_fragment;
bool dash, no_clip;
+ bool delay_open;
+ enum stream_type delay_open_st; // valid if delay_open=true, promised type
+
+ char *lang;
+ char *title;
+
// Segments to play, ordered by time.
struct timeline_part *parts;
int num_parts;
diff --git a/filters/f_decoder_wrapper.c b/filters/f_decoder_wrapper.c
index 7ce13606b6..6f90df04ad 100644
--- a/filters/f_decoder_wrapper.c
+++ b/filters/f_decoder_wrapper.c
@@ -199,13 +199,16 @@ bool mp_decoder_wrapper_reinit(struct mp_decoder_wrapper *d)
const struct mp_decoder_fns *driver = NULL;
struct mp_decoder_list *list = NULL;
char *user_list = NULL;
+ char *fallback = NULL;
if (p->codec->type == STREAM_VIDEO) {
driver = &vd_lavc;
user_list = opts->video_decoders;
+ fallback = "h264";
} else if (p->codec->type == STREAM_AUDIO) {
driver = &ad_lavc;
user_list = opts->audio_decoders;
+ fallback = "aac";
if (p->public.try_spdif && p->codec->codec) {
struct mp_decoder_list *spdif =
@@ -223,7 +226,10 @@ bool mp_decoder_wrapper_reinit(struct mp_decoder_wrapper *d)
struct mp_decoder_list *full = talloc_zero(NULL, struct mp_decoder_list);
if (driver)
driver->add_decoders(full);
- list = mp_select_decoders(p->log, full, p->codec->codec, user_list);
+ const char *codec = p->codec->codec;
+ if (codec && strcmp(codec, "null") == 0)
+ codec = fallback;
+ list = mp_select_decoders(p->log, full, codec, user_list);
talloc_free(full);
}
diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua
index 7e77eb01ba..67425c9210 100644
--- a/player/lua/ytdl_hook.lua
+++ b/player/lua/ytdl_hook.lua
@@ -386,7 +386,9 @@ local function add_single_video(json)
end
if not (sub == nil) then
- mp.commandv("sub-add", sub,
+ local edl = "edl://!no_clip;!delay_open,media_type=sub;"
+ .. edl_escape(sub)
+ mp.commandv("sub-add", edl,
"auto", sub_info.ext, lang)
else
msg.verbose("No subtitle data/url for ["..lang.."]")
diff --git a/sub/sd_ass.c b/sub/sd_ass.c
index eb80230f44..5443a688d4 100644
--- a/sub/sd_ass.c
+++ b/sub/sd_ass.c
@@ -155,7 +155,11 @@ static int init(struct sd *sd)
char *extradata = sd->codec->extradata;
int extradata_size = sd->codec->extradata_size;
- if (strcmp(sd->codec->codec, "ass") != 0) {
+ // Note: accept "null" as alias for "ass", so EDL delay_open subtitle
+ // streams work.
+ if (strcmp(sd->codec->codec, "ass") != 0 &&
+ strcmp(sd->codec->codec, "null") != 0)
+ {
ctx->is_converted = true;
ctx->converter = lavc_conv_create(sd->log, sd->codec->codec, extradata,
extradata_size);