From 6d8b4ca742d931634be19fb01d4310d7b7fb8de0 Mon Sep 17 00:00:00 2001 From: wm4 Date: Wed, 19 Feb 2020 16:33:48 +0100 Subject: [PATCH] ytdl_hook: add all_formats option Pretty worthless I guess. I only tested one site (and 2 videos), it's somewhat likely that it will break with other sites. Even if you leave the option disabled (the default). Slightly related to #3548. This will allows you to use the bitrate stream selection mechanism, that was added for HLS, with normal videos. --- DOCS/man/options.rst | 17 +++++++ player/lua/ytdl_hook.lua | 103 ++++++++++++++++++++++++++++++++------- 2 files changed, 102 insertions(+), 18 deletions(-) diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index f0543c7275..6ef710137f 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -823,6 +823,19 @@ Program Behavior should use ``%`` before any of the characters ``^$()%|,.[]*+-?`` to match that character. + The ``all_formats`` script option accepts a boolean 'yes' or 'no', + and if 'yes' will attempt to add all formats found reported by youtube-dl + (default: no). Each format is added as a separate track. In addition, they + are delay-loaded, and actually opened only when a track is selected (this + should keep load times as low as without this option). It also adds average + bitrate metadata, if available, which means ``--hls-bitrate`` will decide + which track to select. (HLS used to be the only format whose alternative + quality streams were exposed in a similar way, thus the option name.) The + ``--ytdl-format`` option is essentially ignored. Although this mechanism + makes it possible to switch streams at runtime, it's not suitable for this + purpose for various technical reasons. In general, this option is not + useful, and was only added to show that it's possible. + .. admonition:: Examples - ``--script-opts=ytdl_hook-exclude='^youtube%.com'`` @@ -837,6 +850,10 @@ Program Behavior formats like HLS and DASH, if available, allowing for video/audio selection in runtime. It's disabled ("no") by default for performance reasons. + .. admonition:: Why do the option names mix ``_`` and ``-``? + + I have no idea. + ``--ytdl-format=`` Video format/quality that is directly passed to youtube-dl. The possible values are specific to the website and the video, for a given url the diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua index 3526d907ea..37136dc8d5 100644 --- a/player/lua/ytdl_hook.lua +++ b/player/lua/ytdl_hook.lua @@ -5,7 +5,8 @@ local options = require 'mp.options' local o = { exclude = "", try_ytdl_first = false, - use_manifests = false + use_manifests = false, + all_formats = false, } options.read_options(o) @@ -29,11 +30,38 @@ local safe_protos = Set { "data" } --- Codec name as reported by youtube-dl mapped to mpv internal codec names. -local map_codec_to_mpv = { - ["vtt"] = "webvtt", +local codec_map = { + ["vtt"] = "webvtt", + ["opus"] = "opus", + ["vp9"] = "vp9", } +-- Codec name as reported by youtube-dl mapped to mpv internal codec names. +-- Fun fact: mpv will not really use the codec, but will still try to initialize +-- the codec on track selection (just to scrap it), meaning it's only a hint, +-- but one that may make initialization fail. On the other hand, if the codec +-- is valid but completely different from the actual media, nothing bad happens. +local function map_codec_to_mpv(codec) + if codec == nil then + return nil + end + local mc = codec_map[codec] + if mc then + return mc + end + if codec:sub(1, 5) == "avc1." then + return "h264" + end + if codec:sub(1, 5) == "av01." then + return "av1" + end + if codec:sub(1, 5) == "mp4a." then + return "aac" + end + print("unknoiwn codec " .. codec) + return nil +end + local function exec(args) local ret = mp.command_native({name = "subprocess", args = args, @@ -288,10 +316,26 @@ local function valid_manifest(json) proto:find("^m3u8") end +local function as_integer(v, def) + def = def or 0 + local num = math.floor(tonumber(v) or def) + if num > -math.huge and num < math.huge then + return num + end + return def +end + local function add_single_video(json) local streamurl = "" local max_bitrate = 0 - local reqfmts = json["requested_formats"] + local formats = json["requested_formats"] + local duration = as_integer(json["duration"]) + + 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 -- prefer manifest_url if present if o.use_manifests and valid_manifest(json) then @@ -316,10 +360,11 @@ local function add_single_video(json) end -- DASH/split tracks - elseif reqfmts then + elseif formats then local streams = {} + local single_url = nil - for _, track in pairs(reqfmts) do + for _, track in pairs(formats) do local edl_track = nil edl_track = edl_track_joined(track.fragments, track.protocol, json.is_live, @@ -327,24 +372,46 @@ local function add_single_video(json) if not edl_track and not url_is_safe(track.url) then return end + local media_type = nil + local codec = nil if track.vcodec and track.vcodec ~= "none" then - -- video track - streams[#streams + 1] = edl_track or track.url + media_type = "video" + codec = track.vcodec elseif track.vcodec == "none" then - -- audio track - streams[#streams + 1] = edl_track or track.url + 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 then + 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) + local size = as_integer(track["filesize"]) + local byterate = 0 + if size > 0 and duration > 0 then + byterate = as_integer(size / duration) + end + hdr[#hdr + 1] = "!track_meta,title=" .. + edl_escape(track.format_note or "") .. + ",byterate=" .. byterate + 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 - for i = 1, #streams do - streams[i] = "!no_clip;!no_chapters;" .. edl_escape(streams[i]) - end - streamurl = "edl://" .. - table.concat(streams, ";!new_stream;") .. ";" + streamurl = "edl://" .. table.concat(streams, ";") else - streamurl = streams[1] + streamurl = single_url end elseif not (json.url == nil) then @@ -392,7 +459,7 @@ local function add_single_video(json) if not (sub == nil) then local edl = "edl://!no_clip;!delay_open,media_type=sub" - local codec = map_codec_to_mpv[sub_info.ext] + local codec = map_codec_to_mpv(sub_info.ext) if codec then edl = edl .. ",codec=" .. codec end