summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2020-02-20 14:51:48 +0100
committerwm4 <wm4@nowhere>2020-02-20 14:54:09 +0100
commit89203cc994bb0f2009f8553f611da67486330cd2 (patch)
tree41efa0feb341758e2c9a406d7298d7b4a0858a3c
parentc2e62817f610d5b73cc918d77603b23d9feef671 (diff)
downloadmpv-89203cc994bb0f2009f8553f611da67486330cd2.tar.bz2
mpv-89203cc994bb0f2009f8553f611da67486330cd2.tar.xz
ytdl_hook: attempt to filter out muxed streams if all_formats is used
See manpage additions. We would have to extend delay_open to support multiple sub-tracks (for audio and video), and we'd still don't know (?) whether it might contain more than one stream each (thinking of HLS master streams). And if it's a true interleaved file (such as a "normal" mp4 file provided as fallback for more primitive players), we'd either have to signal such "bundled" tracks, or waste bandwidth. This restructures a lot. The if/else tree in add_single_video for format selection was a bit annoying, so it's split into separate if blocks, where it checks each time whether a URL was determined yet.
-rw-r--r--DOCS/man/options.rst12
-rw-r--r--player/lua/ytdl_hook.lua221
2 files changed, 159 insertions, 74 deletions
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 6720b34831..59cab84619 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -846,6 +846,18 @@ Program Behavior
purpose for various technical reasons. In general, this option is not
useful, and was only added to show that it's possible.
+ The ``skip_muxed`` script option is a boolean (default: yes). It is used
+ only if ``all_formats`` is set. If set to 'yes', it will skip formats that
+ have both audio and video streams. Some sites that provide multiple
+ qualities will do so with separated audio and video streams (which is what
+ ``all_formats`` is supposed to make use of), still provide formats that
+ include both audio and video. We assume that they do so for compatibility
+ reasons, and ``skip_muxed`` filters them out. This will make loading faster,
+ and potentially avoid wasting bandwidth by using only one stream of a muxed
+ stream. On the other hand, if muxed streams are present, but the separate
+ streams lack either video or audio or do not exist at all, then the stream
+ selection as done by youtube-dl (via ``--ytdl-format``) is used.
+
The ``use_manifests`` script option makes mpv use the master manifest URL for
formats like HLS and DASH, if available, allowing for video/audio selection
in runtime. It's disabled ("no") by default for performance reasons.
diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua
index e5089dedb6..0215ad024e 100644
--- a/player/lua/ytdl_hook.lua
+++ b/player/lua/ytdl_hook.lua
@@ -7,6 +7,7 @@ local o = {
try_ytdl_first = false,
use_manifests = false,
all_formats = false,
+ skip_muxed = true,
}
options.read_options(o)
@@ -324,79 +325,72 @@ local function as_integer(v, def)
return def
end
-local function add_single_video(json)
- local streamurl = ""
- local max_bitrate = 0
- local formats = json["requested_formats"]
- local duration = as_integer(json["duration"])
+-- Convert a format list from youtube-dl to an EDL URL, or plain URL.
+-- json: full json blob by youtube-dl
+-- formats: format list by youtube-dl
+-- use_all_formats: if=true, then formats is the full format list, and the
+-- function will attempt to return them as delay-loaded tracks
+-- See res table initialization in the function for result type.
+local function formats_to_edl(json, formats, use_all_formats)
+ local res = {
+ -- the media URL, which may be EDL
+ url = nil,
+ -- for use_all_formats=true: whether any muxed formats are present, and
+ -- at the same time the separate EDL parts don't have both audio/video
+ muxed_needed = false,
+ }
- local use_all_formats = o.all_formats
- local all_formats = json["formats"]
- if use_all_formats and all_formats and (#all_formats > 0) then
- formats = all_formats
- end
+ local duration = as_integer(json["duration"])
+ local single_url = nil
+ local streams = {}
+ local separate_present = {}
+ local muxed_present = false
- -- prefer manifest_url if present
- if o.use_manifests and valid_manifest(json) then
- local mpd_url = reqfmts and reqfmts[1]["manifest_url"] or
- json["manifest_url"]
- if not mpd_url then
- msg.error("No manifest URL found in JSON data.")
- return
- elseif not url_is_safe(mpd_url) then
- return
+ for index, track in ipairs(formats) do
+ local edl_track = nil
+ edl_track = edl_track_joined(track.fragments,
+ track.protocol, json.is_live,
+ track.fragment_base_url)
+ if not edl_track and not url_is_safe(track.url) then
+ return nil
end
- streamurl = mpd_url
-
- if reqfmts then
- for _, track in pairs(reqfmts) do
- max_bitrate = track.tbr > max_bitrate and
- track.tbr or max_bitrate
+ local media_type = nil
+ local codec = nil
+ local interlaved_streams = false
+ if track.vcodec and track.vcodec ~= "none" then
+ media_type = "video"
+ codec = track.vcodec
+ end
+ -- Tries to follow the strange logic that vcodec unset means it's
+ -- an audio stream, even if acodec is (supposedly) sometimes unset.
+ if (not codec) or (track.acodec and track.acodec ~= "none") then
+ if codec then
+ interlaved_streams = true
end
- elseif json.tbr then
- max_bitrate = json.tbr > max_bitrate and json.tbr or max_bitrate
+ media_type = "audio"
+ codec = track.acodec
+ end
+ if not media_type then
+ return nil
end
- -- DASH/split tracks
- elseif formats then
- local streams = {}
- local single_url = nil
+ local url = edl_track or track.url
+ local hdr = {"!new_stream", "!no_clip", "!no_chapters"}
+ local as_muxed = false
- for index, track in ipairs(formats) do
- local edl_track = nil
- edl_track = edl_track_joined(track.fragments,
- track.protocol, json.is_live,
- track.fragment_base_url)
- if not edl_track and not url_is_safe(track.url) then
- return
- end
- local media_type = nil
- local codec = nil
- local interlaved_streams = false
- if track.vcodec and track.vcodec ~= "none" then
- media_type = "video"
- codec = track.vcodec
- end
- -- Tries to follow the strange logic that vcodec unset means it's
- -- an audio stream, even if acodec is (supposedly) sometimes unset.
- if (not codec) or (track.acodec and track.acodec ~= "none") then
- if codec then
- interlaved_streams = true
- end
- media_type = "audio"
- codec = track.acodec
- end
- if not media_type then
- return
- end
- local url = edl_track or track.url
- local hdr = {"!new_stream", "!no_clip", "!no_chapters"}
- if use_all_formats and not interlaved_streams then
+ if use_all_formats then
+ if interlaved_streams then
+ hdr[#hdr + 1] = "!track_meta,title=muxed-" .. index
+ as_muxed = o.skip_muxed
+ else
+ -- A single track that is either audio or video. Delay load it.
local codec = map_codec_to_mpv(codec)
hdr[#hdr + 1] = "!delay_open,media_type=" .. media_type ..
",codec=" .. (codec or "null") .. ",w=" ..
as_integer(track.width) .. ",h=" .. as_integer(track.height)
+
+ -- Add bitrate information etc. for better user selection.
local size = as_integer(track["filesize"])
local byterate = 0
for _, f in ipairs({"tbr", "vbr", "abr"}) do
@@ -412,24 +406,100 @@ local function add_single_video(json)
hdr[#hdr + 1] = "!track_meta,title=" ..
edl_escape(track.format_note or "") ..
",byterate=" .. byterate
- elseif interlaved_streams then
- hdr[#hdr + 1] = "!track_meta,title=muxed-" .. index
+ separate_present[media_type] = true
end
- hdr[#hdr + 1] = edl_escape(url)
- streams[#streams + 1] = table.concat(hdr, ";")
- -- In case there is only 1 of these streams.
- -- Note: assumes it has no important EDL headers
- single_url = url
end
- if #streams > 1 then
- -- merge them via EDL
- streamurl = "edl://" .. table.concat(streams, ";")
+ hdr[#hdr + 1] = edl_escape(url)
+
+ if as_muxed then
+ muxed_present = true
else
- streamurl = single_url
+ streams[#streams + 1] = table.concat(hdr, ";")
+ end
+ -- In case there is only 1 of these streams.
+ -- Note: assumes it has no important EDL headers
+ single_url = url
+ end
+
+ -- If "skip_muxed" is enabled, we discard formats that have both audio
+ -- and video aka muxed (because it's a pain). But if the single-media
+ -- type formats do not provide both video and audio, then discard them
+ -- and use the muxed streams instead.
+ res.muxed_needed = muxed_present and (not (separate_present["video"] and
+ separate_present["audio"]))
+
+ -- Merge all tracks into a single virtual file, but avoid EDL if it's
+ -- only a single track (i.e. redundant).
+ if #streams == 1 and single_url then
+ res.url = single_url
+ elseif #streams > 0 then
+ res.url = "edl://" .. table.concat(streams, ";")
+ else
+ return nil
+ end
+
+ return res
+end
+
+local function add_single_video(json)
+ local streamurl = ""
+ local format_info = ""
+ local max_bitrate = 0
+ local requested_formats = json["requested_formats"]
+ local all_formats = json["formats"]
+
+ if o.use_manifests and valid_manifest(json) then
+ -- prefer manifest_url if present
+ format_info = "manifest"
+
+ local mpd_url = reqfmts and reqfmts[1]["manifest_url"] or
+ json["manifest_url"]
+ if not mpd_url then
+ msg.error("No manifest URL found in JSON data.")
+ return
+ elseif not url_is_safe(mpd_url) then
+ return
end
- elseif not (json.url == nil) then
+ streamurl = mpd_url
+
+ if reqfmts then
+ for _, track in pairs(reqfmts) do
+ max_bitrate = track.tbr > max_bitrate and
+ track.tbr or max_bitrate
+ end
+ elseif json.tbr then
+ max_bitrate = json.tbr > max_bitrate and json.tbr or max_bitrate
+ end
+ end
+
+ if streamurl == "" then
+ -- possibly DASH/split tracks
+ local res = nil
+
+ if all_formats and o.all_formats then
+ format_info = "all_formats (separate)"
+ res = formats_to_edl(json, all_formats, true)
+ -- Note: since we don't delay-load muxed streams, use normal stream
+ -- selection if we have to use muxed streams.
+ if res and res.muxed_needed then
+ res = nil
+ end
+ end
+
+ if (not res) and requested_formats and #requested_formats > 0 then
+ format_info = "youtube-dl (separate)"
+ res = formats_to_edl(json, requested_formats, false)
+ end
+
+ if res then
+ streamurl = res.url
+ end
+ end
+
+ if streamurl == "" and json.url then
+ format_info = "youtube-dl (single)"
local edl_track = nil
edl_track = edl_track_joined(json.fragments, json.protocol,
json.is_live, json.fragment_base_url)
@@ -440,11 +510,14 @@ local function add_single_video(json)
-- normal video or single track
streamurl = edl_track or json.url
set_http_headers(json.http_headers)
- else
+ end
+
+ if streamurl == "" then
msg.error("No URL found in JSON data.")
return
end
+ msg.verbose("format selection: " .. format_info)
msg.debug("streamurl: " .. streamurl)
mp.set_property("stream-open-filename", streamurl:gsub("^data:", "data://", 1))