From a9acfa82c35c4d5a98737d1cfda5d56e622f9674 Mon Sep 17 00:00:00 2001 From: wm4 Date: Sat, 15 Feb 2020 02:19:20 +0100 Subject: some shit --- DOCS/edl-mpv.rst | 65 +++++++++++++++++++++++++++++++++ demux/demux_edl.c | 51 ++++++++++++++++++++++++-- demux/demux_timeline.c | 87 +++++++++++++++++++++++++++++---------------- demux/timeline.h | 8 +++++ filters/f_decoder_wrapper.c | 8 ++++- player/lua/ytdl_hook.lua | 4 ++- sub/sd_ass.c | 6 +++- 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, ¶ms, @@ -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); -- cgit v1.2.3