--- a/player/lua/ytdl_hook.lua 2020-01-26 21:31:54.000000000 +0100 +++ /dev/null 2023-02-08 11:37:05.880016690 +0100 @@ -1,735 +0,0 @@ -local utils = require 'mp.utils' -local msg = require 'mp.msg' -local options = require 'mp.options' - -local o = { - exclude = "", - try_ytdl_first = false, - use_manifests = false -} -options.read_options(o) - -local ytdl = { - path = "youtube-dl", - searched = false, - blacklisted = {} -} - -local chapter_list = {} - -function Set (t) - local set = {} - for _, v in pairs(t) do set[v] = true end - return set -end - -local safe_protos = Set { - "http", "https", "ftp", "ftps", - "rtmp", "rtmps", "rtmpe", "rtmpt", "rtmpts", "rtmpte", - "data" -} - -local function exec(args) - local ret = mp.command_native({name = "subprocess", - args = args, - capture_stdout = true, - capture_stderr = true}) - return ret.status, ret.stdout, ret, ret.killed_by_us -end - --- return true if it was explicitly set on the command line -local function option_was_set(name) - return mp.get_property_bool("option-info/" ..name.. "/set-from-commandline", - false) -end - --- return true if the option was set locally -local function option_was_set_locally(name) - return mp.get_property_bool("option-info/" ..name.. "/set-locally", false) -end - --- youtube-dl may set special http headers for some sites (user-agent, cookies) -local function set_http_headers(http_headers) - if not http_headers then - return - end - local headers = {} - local useragent = http_headers["User-Agent"] - if useragent and not option_was_set("user-agent") then - mp.set_property("file-local-options/user-agent", useragent) - end - local additional_fields = {"Cookie", "Referer", "X-Forwarded-For"} - for idx, item in pairs(additional_fields) do - local field_value = http_headers[item] - if field_value then - headers[#headers + 1] = item .. ": " .. field_value - end - end - if #headers > 0 and not option_was_set("http-header-fields") then - mp.set_property_native("file-local-options/http-header-fields", headers) - end -end - -local function append_libav_opt(props, name, value) - if not props then - props = {} - end - - if name and value and not props[name] then - props[name] = value - end - - return props -end - -local function edl_escape(url) - return "%" .. string.len(url) .. "%" .. url -end - -local function url_is_safe(url) - local proto = type(url) == "string" and url:match("^(.+)://") or nil - local safe = proto and safe_protos[proto] - if not safe then - msg.error(("Ignoring potentially unsafe url: '%s'"):format(url)) - end - return safe -end - -local function time_to_secs(time_string) - local ret - - local a, b, c = time_string:match("(%d+):(%d%d?):(%d%d)") - if a ~= nil then - ret = (a*3600 + b*60 + c) - else - a, b = time_string:match("(%d%d?):(%d%d)") - if a ~= nil then - ret = (a*60 + b) - end - end - - return ret -end - -local function extract_chapters(data, video_length) - local ret = {} - - for line in data:gmatch("[^\r\n]+") do - local time = time_to_secs(line) - if time and (time < video_length) then - table.insert(ret, {time = time, title = line}) - end - end - table.sort(ret, function(a, b) return a.time < b.time end) - return ret -end - -local function is_blacklisted(url) - if o.exclude == "" then return false end - if #ytdl.blacklisted == 0 then - local joined = o.exclude - while joined:match('%|?[^|]+') do - local _, e, substring = joined:find('%|?([^|]+)') - table.insert(ytdl.blacklisted, substring) - joined = joined:sub(e+1) - end - end - if #ytdl.blacklisted > 0 then - url = url:match('https?://(.+)') - for _, exclude in ipairs(ytdl.blacklisted) do - if url:match(exclude) then - msg.verbose('URL matches excluded substring. Skipping.') - return true - end - end - end - return false -end - -local function parse_yt_playlist(url, json) - -- return 0-based index to use with --playlist-start - - if not json.extractor or json.extractor ~= "youtube:playlist" then - return nil - end - - local query = url:match("%?.+") - if not query then return nil end - - local args = {} - for arg, param in query:gmatch("(%a+)=([^&?]+)") do - if arg and param then - args[arg] = param - end - end - - local maybe_idx = tonumber(args["index"]) - - -- if index matches v param it's probably the requested item - if maybe_idx and #json.entries >= maybe_idx and - json.entries[maybe_idx].id == args["v"] then - msg.debug("index matches requested video") - return maybe_idx - 1 - end - - -- if there's no index or it doesn't match, look for video - for i = 1, #json.entries do - if json.entries[i] == args["v"] then - msg.debug("found requested video in index " .. (i - 1)) - return i - 1 - end - end - - msg.debug("requested video not found in playlist") - -- if item isn't on the playlist, give up - return nil -end - -local function make_absolute_url(base_url, url) - if url:find("https?://") == 1 then return url end - - local proto, domain, rest = - base_url:match("(https?://)([^/]+/)(.*)/?") - local segs = {} - rest:gsub("([^/]+)", function(c) table.insert(segs, c) end) - url:gsub("([^/]+)", function(c) table.insert(segs, c) end) - local resolved_url = {} - for i, v in ipairs(segs) do - if v == ".." then - table.remove(resolved_url) - elseif v ~= "." then - table.insert(resolved_url, v) - end - end - return proto .. domain .. - table.concat(resolved_url, "/") -end - -local function join_url(base_url, fragment) - local res = "" - if base_url and fragment.path then - res = make_absolute_url(base_url, fragment.path) - elseif fragment.url then - res = fragment.url - end - return res -end - -local function edl_track_joined(fragments, protocol, is_live, base) - if not (type(fragments) == "table") or not fragments[1] then - msg.debug("No fragments to join into EDL") - return nil - end - - local edl = "edl://" - local offset = 1 - local parts = {} - - if (protocol == "http_dash_segments") and not is_live then - msg.debug("Using dash") - local args = "" - - -- assume MP4 DASH initialization segment - if not fragments[1].duration then - msg.debug("Using init segment") - args = args .. ",init=" .. edl_escape(join_url(base, fragments[1])) - offset = 2 - end - - table.insert(parts, "!mp4_dash" .. args) - - -- Check remaining fragments for duration; - -- if not available in all, give up. - for i = offset, #fragments do - if not fragments[i].duration then - msg.error("EDL doesn't support fragments" .. - "without duration with MP4 DASH") - return nil - end - end - end - - for i = offset, #fragments do - local fragment = fragments[i] - if not url_is_safe(join_url(base, fragment)) then - return nil - end - table.insert(parts, edl_escape(join_url(base, fragment))) - if fragment.duration then - parts[#parts] = - parts[#parts] .. ",length="..fragment.duration - end - end - return edl .. table.concat(parts, ";") .. ";" -end - -local function has_native_dash_demuxer() - local demuxers = mp.get_property_native("demuxer-lavf-list", {}) - for _, v in ipairs(demuxers) do - if v == "dash" then - return true - end - end - return false -end - -local function valid_manifest(json) - local reqfmt = json["requested_formats"] and json["requested_formats"][1] or {} - if not reqfmt["manifest_url"] and not json["manifest_url"] then - return false - end - local proto = reqfmt["protocol"] or json["protocol"] or "" - return (proto == "http_dash_segments" and has_native_dash_demuxer()) or - proto:find("^m3u8") -end - -local function add_single_video(json) - local streamurl = "" - local max_bitrate = 0 - local reqfmts = json["requested_formats"] - - -- 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 - 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 - end - elseif json.tbr then - max_bitrate = json.tbr > max_bitrate and json.tbr or max_bitrate - end - - -- DASH/split tracks - elseif reqfmts then - local streams = {} - - for _, track in pairs(reqfmts) 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 - if track.vcodec and track.vcodec ~= "none" then - -- video track - streams[#streams + 1] = edl_track or track.url - elseif track.vcodec == "none" then - -- audio track - streams[#streams + 1] = edl_track or track.url - end - 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;") .. ";" - else - streamurl = streams[1] - end - - elseif not (json.url == nil) then - local edl_track = nil - edl_track = edl_track_joined(json.fragments, json.protocol, - json.is_live, json.fragment_base_url) - - if not edl_track and not url_is_safe(json.url) then - return - end - -- normal video or single track - streamurl = edl_track or json.url - set_http_headers(json.http_headers) - else - msg.error("No URL found in JSON data.") - return - end - - msg.debug("streamurl: " .. streamurl) - - mp.set_property("stream-open-filename", streamurl:gsub("^data:", "data://", 1)) - - mp.set_property("file-local-options/force-media-title", json.title) - - -- set hls-bitrate for dash track selection - if max_bitrate > 0 and - not option_was_set("hls-bitrate") and - not option_was_set_locally("hls-bitrate") then - mp.set_property_native('file-local-options/hls-bitrate', max_bitrate*1000) - end - - -- add subtitles - if not (json.requested_subtitles == nil) then - for lang, sub_info in pairs(json.requested_subtitles) do - msg.verbose("adding subtitle ["..lang.."]") - - local sub = nil - - if not (sub_info.data == nil) then - sub = "memory://"..sub_info.data - elseif not (sub_info.url == nil) and - url_is_safe(sub_info.url) then - sub = sub_info.url - end - - if not (sub == nil) then - mp.commandv("sub-add", sub, - "auto", sub_info.ext, lang) - else - msg.verbose("No subtitle data/url for ["..lang.."]") - end - end - end - - -- add chapters - if json.chapters then - msg.debug("Adding pre-parsed chapters") - for i = 1, #json.chapters do - local chapter = json.chapters[i] - local title = chapter.title or "" - if title == "" then - title = string.format('Chapter %02d', i) - end - table.insert(chapter_list, {time=chapter.start_time, title=title}) - end - elseif not (json.description == nil) and not (json.duration == nil) then - chapter_list = extract_chapters(json.description, json.duration) - end - - -- set start time - if not (json.start_time == nil) and - not option_was_set("start") and - not option_was_set_locally("start") then - msg.debug("Setting start to: " .. json.start_time .. " secs") - mp.set_property("file-local-options/start", json.start_time) - end - - -- set aspect ratio for anamorphic video - if not (json.stretched_ratio == nil) and - not option_was_set("video-aspect-override") then - mp.set_property('file-local-options/video-aspect-override', json.stretched_ratio) - end - - local stream_opts = mp.get_property_native("file-local-options/stream-lavf-o", {}) - - -- for rtmp - if (json.protocol == "rtmp") then - stream_opts = append_libav_opt(stream_opts, - "rtmp_tcurl", streamurl) - stream_opts = append_libav_opt(stream_opts, - "rtmp_pageurl", json.page_url) - stream_opts = append_libav_opt(stream_opts, - "rtmp_playpath", json.play_path) - stream_opts = append_libav_opt(stream_opts, - "rtmp_swfverify", json.player_url) - stream_opts = append_libav_opt(stream_opts, - "rtmp_swfurl", json.player_url) - stream_opts = append_libav_opt(stream_opts, - "rtmp_app", json.app) - end - - if json.proxy and json.proxy ~= "" then - stream_opts = append_libav_opt(stream_opts, - "http_proxy", json.proxy) - end - - mp.set_property_native("file-local-options/stream-lavf-o", stream_opts) -end - -local function check_version(ytdl_path) - local command = { - name = "subprocess", - capture_stdout = true, - args = {ytdl_path, "--version"} - } - local version_string = mp.command_native(command).stdout - local year, month, day = string.match(version_string, "(%d+).(%d+).(%d+)") - - -- sanity check - if (tonumber(year) < 2000) or (tonumber(month) > 12) or - (tonumber(day) > 31) then - return - end - local version_ts = os.time{year=year, month=month, day=day} - if (os.difftime(os.time(), version_ts) > 60*60*24*90) then - msg.warn("It appears that your youtube-dl version is severely out of date.") - end -end - -function run_ytdl_hook(url) - local start_time = os.clock() - - -- check for youtube-dl in mpv's config dir - if not (ytdl.searched) then - local exesuf = (package.config:sub(1,1) == '\\') and '.exe' or '' - local ytdl_mcd = mp.find_config_file("youtube-dl" .. exesuf) - if not (ytdl_mcd == nil) then - msg.verbose("found youtube-dl at: " .. ytdl_mcd) - ytdl.path = ytdl_mcd - end - ytdl.searched = true - end - - -- strip ytdl:// - if (url:find("ytdl://") == 1) then - url = url:sub(8) - end - - local format = mp.get_property("options/ytdl-format") - local raw_options = mp.get_property_native("options/ytdl-raw-options") - local allsubs = true - local proxy = nil - local use_playlist = false - - local command = { - ytdl.path, "--no-warnings", "-J", "--flat-playlist", - "--sub-format", "ass/srt/best" - } - - -- Checks if video option is "no", change format accordingly, - -- but only if user didn't explicitly set one - if (mp.get_property("options/vid") == "no") and (#format == 0) then - format = "bestaudio/best" - msg.verbose("Video disabled. Only using audio") - end - - if (format == "") then - format = "bestvideo+bestaudio/best" - end - table.insert(command, "--format") - table.insert(command, format) - - for param, arg in pairs(raw_options) do - table.insert(command, "--" .. param) - if (arg ~= "") then - table.insert(command, arg) - end - if (param == "sub-lang") and (arg ~= "") then - allsubs = false - elseif (param == "proxy") and (arg ~= "") then - proxy = arg - elseif (param == "yes-playlist") then - use_playlist = true - end - end - - if (allsubs == true) then - table.insert(command, "--all-subs") - end - if not use_playlist then - table.insert(command, "--no-playlist") - end - table.insert(command, "--") - table.insert(command, url) - msg.debug("Running: " .. table.concat(command,' ')) - local es, json, result, aborted = exec(command) - - if aborted then - return - end - - if (es < 0) or (json == nil) or (json == "") then - -- trim our stderr to avoid spurious newlines - ytdl_err = result.stderr:gsub("^%s*(.-)%s*$", "%1") - msg.error(ytdl_err) - local err = "youtube-dl failed: " - if result.error_string and result.error_string == "init" then - err = err .. "not found or not enough permissions" - elseif not result.killed_by_us then - err = err .. "unexpected error ocurred" - else - err = string.format("%s returned '%d'", err, es) - end - msg.error(err) - if string.find(ytdl_err, "yt%-dl%.org/bug") then - check_version(ytdl.path) - end - return - end - - local json, err = utils.parse_json(json) - - if (json == nil) then - msg.error("failed to parse JSON data: " .. err) - check_version(ytdl.path) - return - end - - msg.verbose("youtube-dl succeeded!") - msg.debug('ytdl parsing took '..os.clock()-start_time..' seconds') - - json["proxy"] = json["proxy"] or proxy - - -- what did we get? - if json["direct"] then - -- direct URL, nothing to do - msg.verbose("Got direct URL") - return - elseif (json["_type"] == "playlist") - or (json["_type"] == "multi_video") then - -- a playlist - - if (#json.entries == 0) then - msg.warn("Got empty playlist, nothing to play.") - return - end - - local self_redirecting_url = - json.entries[1]["_type"] ~= "url_transparent" and - json.entries[1]["webpage_url"] and - json.entries[1]["webpage_url"] == json["webpage_url"] - - - -- some funky guessing to detect multi-arc videos - if self_redirecting_url and #json.entries > 1 - and json.entries[1].protocol == "m3u8_native" - and json.entries[1].url then - msg.verbose("multi-arc video detected, building EDL") - - local playlist = edl_track_joined(json.entries) - - msg.debug("EDL: " .. playlist) - - if not playlist then - return - end - - -- can't change the http headers for each entry, so use the 1st - set_http_headers(json.entries[1].http_headers) - - mp.set_property("stream-open-filename", playlist) - if not (json.title == nil) then - mp.set_property("file-local-options/force-media-title", - json.title) - end - - -- there might not be subs for the first segment - local entry_wsubs = nil - for i, entry in pairs(json.entries) do - if not (entry.requested_subtitles == nil) then - entry_wsubs = i - break - end - end - - if not (entry_wsubs == nil) and - not (json.entries[entry_wsubs].duration == nil) then - for j, req in pairs(json.entries[entry_wsubs].requested_subtitles) do - local subfile = "edl://" - for i, entry in pairs(json.entries) do - if not (entry.requested_subtitles == nil) and - not (entry.requested_subtitles[j] == nil) and - url_is_safe(entry.requested_subtitles[j].url) then - subfile = subfile..edl_escape(entry.requested_subtitles[j].url) - else - subfile = subfile..edl_escape("memory://WEBVTT") - end - subfile = subfile..",length="..entry.duration..";" - end - msg.debug(j.." sub EDL: "..subfile) - mp.commandv("sub-add", subfile, "auto", req.ext, j) - end - end - - elseif self_redirecting_url and #json.entries == 1 then - msg.verbose("Playlist with single entry detected.") - add_single_video(json.entries[1]) - else - local playlist_index = parse_yt_playlist(url, json) - local playlist = {"#EXTM3U"} - for i, entry in pairs(json.entries) do - local site = entry.url - local title = entry.title - - if not (title == nil) then - title = string.gsub(title, '%s+', ' ') - table.insert(playlist, "#EXTINF:0," .. title) - end - - --[[ some extractors will still return the full info for - all clips in the playlist and the URL will point - directly to the file in that case, which we don't - want so get the webpage URL instead, which is what - we want, but only if we aren't going to trigger an - infinite loop - --]] - if entry["webpage_url"] and not self_redirecting_url then - site = entry["webpage_url"] - end - - -- links without protocol as returned by --flat-playlist - if not site:find("://") then - -- youtube extractor provides only IDs, - -- others come prefixed with the extractor name and ":" - local prefix = site:find(":") and "ytdl://" or - "https://youtu.be/" - table.insert(playlist, prefix .. site) - elseif url_is_safe(site) then - table.insert(playlist, site) - end - - end - - if use_playlist and - not option_was_set("playlist-start") and playlist_index then - mp.set_property_number("playlist-start", playlist_index) - end - - mp.set_property("stream-open-filename", "memory://" .. table.concat(playlist, "\n")) - - -- This disables mpv's mushy playlist security code, which will - -- break links that will be resolved to EDL later (because EDL is - -- not considered "safe", and the playlist entries got tagged as - -- network originating due to the playlist redirection). - mp.set_property_native("file-local-options/load-unsafe-playlists", true) - end - - else -- probably a video - add_single_video(json) - end - msg.debug('script running time: '..os.clock()-start_time..' seconds') -end - -if (not o.try_ytdl_first) then - mp.add_hook("on_load", 10, function () - msg.verbose('ytdl:// hook') - local url = mp.get_property("stream-open-filename", "") - if not (url:find("ytdl://") == 1) then - msg.verbose('not a ytdl:// url') - return - end - run_ytdl_hook(url) - end) -end - -mp.add_hook(o.try_ytdl_first and "on_load" or "on_load_fail", 10, function() - msg.verbose('full hook') - local url = mp.get_property("stream-open-filename", "") - if not (url:find("ytdl://") == 1) and - not ((url:find("https?://") == 1) and not is_blacklisted(url)) then - return - end - run_ytdl_hook(url) -end) - -mp.add_hook("on_preloaded", 10, function () - if next(chapter_list) ~= nil then - msg.verbose("Setting chapters") - - mp.set_property_native("chapter-list", chapter_list) - chapter_list = {} - end -end) --- a/player/lua.c 2023-02-23 11:26:34.000000000 +0100 +++ b/player/lua.c 2023-02-23 11:29:02.217705575 +0100 @@ -67,9 +67,6 @@ {"@osc.lua", # include "player/lua/osc.inc" }, - {"@ytdl_hook.lua", -# include "player/lua/ytdl_hook.inc" - }, {"@stats.lua", # include "player/lua/stats.inc" }, --- a/player/scripting.c 2020-01-26 21:31:54.000000000 +0100 +++ b/player/scripting.c 2023-02-23 11:31:42.691479373 +0100 @@ -209,7 +209,6 @@ void mp_load_builtin_scripts(struct MPContext *mpctx) { load_builtin_script(mpctx, mpctx->opts->lua_load_osc, "@osc.lua"); - load_builtin_script(mpctx, mpctx->opts->lua_load_ytdl, "@ytdl_hook.lua"); load_builtin_script(mpctx, mpctx->opts->lua_load_stats, "@stats.lua"); load_builtin_script(mpctx, mpctx->opts->lua_load_console, "@console.lua"); } --- a/libmpv/client.h 2020-01-26 21:31:54.000000000 +0100 +++ b/libmpv/client.h 2023-02-23 11:30:05.579195622 +0100 @@ -138,8 +138,7 @@ * - The FPU precision must be set at least to double precision. * - On Windows, mpv will call timeBeginPeriod(1). * - On memory exhaustion, mpv will kill the process. - * - In certain cases, mpv may start sub processes (such as with the ytdl - * wrapper script). + * - In certain cases, mpv may start sub processes. * - Using UNIX IPC (off by default) will override the SIGPIPE signal handler, * and set it to SIG_IGN. * - mpv will reseed the legacy C random number generator by calling srand() at --- a/options/m_option.h 2020-01-26 21:31:54.000000000 +0100 +++ b/options/m_option.h 2023-02-23 11:38:21.710862952 +0100 @@ -418,7 +418,7 @@ #define UPDATE_OPT_FIRST (1 << 8) #define UPDATE_TERM (1 << 8) // terminal options #define UPDATE_OSD (1 << 10) // related to OSD rendering -#define UPDATE_BUILTIN_SCRIPTS (1 << 11) // osc/ytdl/stats +#define UPDATE_BUILTIN_SCRIPTS (1 << 11) // osc/stats #define UPDATE_IMGPAR (1 << 12) // video image params overrides #define UPDATE_INPUT (1 << 13) // mostly --input-* options #define UPDATE_AUDIO (1 << 14) // --audio-channels etc. --- a/options/options.h 2020-01-26 21:31:54.000000000 +0100 +++ b/options/options.h 2023-02-23 11:37:14.879291301 +0100 @@ -132,9 +132,6 @@ char **script_files; char **script_opts; int lua_load_osc; - int lua_load_ytdl; - char *lua_ytdl_format; - char **lua_ytdl_raw_options; int lua_load_stats; int lua_load_console; --- a/options/options.c 2020-01-26 21:31:54.000000000 +0100 +++ b/options/options.c 2023-02-23 11:37:57.970304655 +0100 @@ -392,9 +392,6 @@ #endif #if HAVE_LUA OPT_FLAG("osc", lua_load_osc, UPDATE_BUILTIN_SCRIPTS), - OPT_FLAG("ytdl", lua_load_ytdl, UPDATE_BUILTIN_SCRIPTS), - OPT_STRING("ytdl-format", lua_ytdl_format, 0), - OPT_KEYVALUELIST("ytdl-raw-options", lua_ytdl_raw_options, 0), OPT_FLAG("load-stats-overlay", lua_load_stats, UPDATE_BUILTIN_SCRIPTS), OPT_FLAG("load-osd-console", lua_load_console, UPDATE_BUILTIN_SCRIPTS), #endif @@ -916,9 +913,6 @@ .osd_duration = 1000, #if HAVE_LUA .lua_load_osc = 1, - .lua_load_ytdl = 1, - .lua_ytdl_format = NULL, - .lua_ytdl_raw_options = NULL, .lua_load_stats = 1, .lua_load_console = 1, #endif --- a/wscript_build.py 2020-01-26 21:31:54.000000000 +0100 +++ b/wscript_build.py 2023-02-23 11:41:25.935195282 +0100 @@ -100,7 +100,7 @@ ) lua_files = ["defaults.lua", "assdraw.lua", "options.lua", "osc.lua", - "ytdl_hook.lua", "stats.lua", "console.lua"] + "stats.lua", "console.lua"] for fn in lua_files: fn = "player/lua/" + fn --- a/DOCS/edl-mpv.rst 2020-01-26 21:31:54.000000000 +0100 +++ b/DOCS/edl-mpv.rst 2023-02-23 11:49:35.806715395 +0100 @@ -112,46 +112,6 @@ !no_chapters -MP4 DASH -======== - -This is a header that helps implementing DASH, although it only provides a low -level mechanism. - -If this header is set, the given url designates an mp4 init fragment. It's -downloaded, and every URL in the EDL is prefixed with the init fragment on the -byte stream level. This is mostly for use by mpv's internal ytdl support. The -ytdl script will call youtube-dl, which in turn actually processes DASH -manifests. It may work only for this very specific purpose and fail to be -useful in other scenarios. It can be removed or changed in incompatible ways -at any times. - -Example:: - - !mp4_dash,init=url - -The ``url`` is encoded as parameter value as defined in the general EDL syntax. -It's expected to point to an "initialization fragment", which will be prefixed -to every entry in the EDL on the byte stream level. - -The current implementation will - -- ignore stream start times -- use durations as hint for seeking only -- not adjust source timestamps -- open and close segments (i.e. fragments) as needed -- not add segment boundaries as chapter points -- require full compatibility between all segments (same codec etc.) - -Another header part of this mechanism is ``no_clip``. This header is similar -to ``mp4_dash``, but does not include on-demand opening/closing of segments, -and does not support init segments. It also exists solely to support internal -ytdl requirements. - -The ``mp4_dash`` and ``no_clip`` headers are not part of the core EDL format. -They may be changed or removed at any time, depending on mpv's internal -requirements. - Separate files for tracks ========================= --- a/DOCS/man/input.rst 2020-01-26 21:31:54.000000000 +0100 +++ b/DOCS/man/input.rst 2023-02-23 11:50:49.908458016 +0100 @@ -1493,11 +1493,7 @@ ``stream-open-filename`` The full path to the currently played media. This is different only from - ``path`` in special cases. In particular, if ``--ytdl=yes`` is used, and - the URL is detected by ``youtube-dl``, then the script will set this - property to the actual media URL. This property should be set only during - the ``on_load`` or ``on_load_fail`` hooks, otherwise it will have no effect - (or may do something implementation defined in the future). The property is + ``path`` in special cases. The property is reset if playback of the current media ends. ``media-title`` --- a/DOCS/man/mpv.rst 2020-01-26 21:31:54.000000000 +0100 +++ b/DOCS/man/mpv.rst 2023-02-23 11:55:29.105023770 +0100 @@ -879,16 +879,6 @@ format ``data://``. This is done to avoid ambiguity with filenames. You can also prefix it with ``lavf://`` or ``ffmpeg://``. -``ytdl://...`` - - By default, the youtube-dl hook script only looks at http(s) URLs. Prefixing - an URL with ``ytdl://`` forces it to be always processed by the script. This - can also be used to invoke special youtube-dl functionality like playing a - video by ID or invoking search. - - Keep in mind that you can't pass youtube-dl command line options by this, - and you have to use ``--ytdl-raw-options`` instead. - ``-`` Play data from stdin. --- a/DOCS/man/options.rst 2020-01-26 21:31:54.000000000 +0100 +++ b/DOCS/man/options.rst 2023-02-23 11:54:53.714191498 +0100 @@ -66,10 +66,6 @@ ``--vid=no`` or ``--video=no`` or ``--no-video`` disables video playback. (The latter variant does not work with the client API.) - If video is disabled, mpv will try to download the audio only if media is - streamed with youtube-dl, because it saves bandwidth. This is done by - setting the ytdl_format to "bestaudio/best" in the ytdl_hook.lua script. - ``--edition=`` (Matroska files only) Specify the edition (set of chapters) to use, where 0 is the first. If set @@ -791,71 +787,6 @@ May be dangerous if playing from untrusted media. -``--ytdl``, ``--no-ytdl`` - Enable the youtube-dl hook-script. It will look at the input URL, and will - play the video located on the website. This works with many streaming sites, - not just the one that the script is named after. This requires a recent - version of youtube-dl to be installed on the system. (Enabled by default.) - - If the script can't do anything with an URL, it will do nothing. - - The ``try_ytdl_first`` script option accepts a boolean 'yes' or 'no', and if - 'yes' will try parsing the URL with youtube-dl first, instead of the default - where it's only after mpv failed to open it. This mostly depends on whether - most of your URLs need youtube-dl parsing. - - The ``exclude`` script option accepts a ``|``-separated list of URL patterns - which mpv should not use with youtube-dl. The patterns are matched after - the ``http(s)://`` part of the URL. - - ``^`` matches the beginning of the URL, ``$`` matches its end, and you - should use ``%`` before any of the characters ``^$()%|,.[]*+-?`` to match - that character. - - .. admonition:: Examples - - - ``--script-opts=ytdl_hook-exclude='^youtube%.com'`` - will exclude any URL that starts with ``http://youtube.com`` or - ``https://youtube.com``. - - ``--script-opts=ytdl_hook-exclude='%.mkv$|%.mp4$'`` - will exclude any URL that ends with ``.mkv`` or ``.mp4``. - - See more lua patterns here: https://www.lua.org/manual/5.1/manual.html#5.4.1 - - 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. - -``--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 - available formats can be found with the command - ``youtube-dl --list-formats URL``. See youtube-dl's documentation for - available aliases. - (Default: youtube-dl's default, currently ``bestvideo+bestaudio/best``) - -``--ytdl-raw-options==[,=[,...]]`` - Pass arbitrary options to youtube-dl. Parameter and argument should be - passed as a key-value pair. Options without argument must include ``=``. - - There is no sanity checking so it's possible to break things (i.e. - passing invalid parameters to youtube-dl). - - A proxy URL can be passed for youtube-dl to use it in parsing the website. - This is useful for geo-restricted URLs. After youtube-dl parsing, some - URLs also require a proxy for playback, so this can pass that proxy - information to mpv. Take note that SOCKS proxies aren't supported and - https URLs also bypass the proxy. This is a limitation in FFmpeg. - - This is a key/value list option. See `List Options`_ for details. - - .. admonition:: Example - - - ``--ytdl-raw-options=username=user,password=pass`` - - ``--ytdl-raw-options=force-ipv6=`` - - ``--ytdl-raw-options=proxy=[http://127.0.0.1:3128]`` - - ``--ytdl-raw-options-append=proxy=http://127.0.0.1:3128`` - ``--load-stats-overlay=`` Enable the builtin script that shows useful playback information on a key binding (default: yes). By default, the ``i`` key is used (``I`` to make @@ -4374,8 +4305,7 @@ URL of the HTTP/HTTPS proxy. If this is set, the ``http_proxy`` environment is ignored. The ``no_proxy`` environment variable is still respected. This option is silently ignored if it does not start with ``http://``. Proxies - are not used for https URLs. Setting this option does not try to make the - ytdl script use the proxy. + are not used for https URLs. ``--tls-ca-file=`` Certificate authority database file for use with TLS. (Silently fails with --- a/DOCS/interface-changes.rst 2020-01-26 21:31:54.000000000 +0100 +++ b/DOCS/interface-changes.rst 2023-02-23 12:01:57.044146779 +0100 @@ -177,7 +177,6 @@ --ad-spdif-dtshd, --softvol options - fix --external-files: strictly never select any tracks from them, unless explicitly selected (this may or may not be expected) - - --ytdl is now always enabled, even for libmpv - add a number of --audio-resample-* options, which should from now on be used instead of --af-defaults=lavrresample:... - deprecate --vf-defaults and --af-defaults. These didn't work with the @@ -552,7 +551,7 @@ between options and properties) - implement changing sub-speed during playback - make many previously fixed options changeable at runtime (for example - --terminal, --osc, --ytdl, can all be enable/disabled after + --terminal, --osc, can all be enable/disabled after mpv_initialize() - this can be extended to other still fixed options on user requests) --- mpv 0.20.0 --- --- a/DOCS/mplayer-changes.rst 2020-01-26 21:31:54.000000000 +0100 +++ b/DOCS/mplayer-changes.rst 2023-02-23 12:01:17.673220909 +0100 @@ -128,10 +128,6 @@ * Better MKV support (e.g. ordered chapters, 3D metadata). * Matroska edition switching at runtime. -* Support for playing URLs of popular streaming sites directly. - (e.g. ``mpv https://www.youtube.com/watch?v=...``). - Requires a recent version of ``youtube-dl`` to be installed. Can be - disabled with ``ytdl=no`` in the mpv config file. * Support for precise scrolling which scales the parameter of commands. If the input doesn't support precise scrolling the scale factor stays 1. * Allow changing/adjusting video filters at runtime. (This is also used to make