diff options
Diffstat (limited to 'player/lua/ytdl_hook.lua')
-rw-r--r-- | player/lua/ytdl_hook.lua | 459 |
1 files changed, 349 insertions, 110 deletions
diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua index e9a7b3aac7..3161da6194 100644 --- a/player/lua/ytdl_hook.lua +++ b/player/lua/ytdl_hook.lua @@ -8,11 +8,13 @@ local o = { use_manifests = false, all_formats = false, force_all_formats = true, - ytdl_path = "youtube-dl", + thumbnails = "none", + ytdl_path = "", } local ytdl = { - path = nil, + path = "", + paths_to_search = {"yt-dlp", "yt-dlp_x86", "youtube-dl"}, searched = false, blacklisted = {} } @@ -23,6 +25,7 @@ options.read_options(o, nil, function() end) local chapter_list = {} +local playlist_cookies = {} function Set (t) local set = {} @@ -38,6 +41,17 @@ function iif(cond, if_true, if_false) return if_false end +-- youtube-dl JSON name to mpv tag name +local tag_list = { + ["uploader"] = "uploader", + ["channel_url"] = "channel_url", + -- these titles tend to be a bit too long, so hide them on the terminal + -- (default --display-tags does not include this name) + ["description"] = "ytdl_description", + -- "title" is handled by force-media-title + -- tags don't work with all_formats=yes +} + local safe_protos = Set { "http", "https", "ftp", "ftps", "rtmp", "rtmps", "rtmpe", "rtmpt", "rtmpts", "rtmpte", @@ -78,12 +92,19 @@ local function map_codec_to_mpv(codec) return nil end +local function platform_is_windows() + return mp.get_property_native("platform") == "windows" +end + 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 + msg.debug("Running: " .. table.concat(args, " ")) + + return mp.command_native({ + name = "subprocess", + args = args, + capture_stdout = true, + capture_stderr = true, + }) end -- return true if it was explicitly set on the command line @@ -119,6 +140,78 @@ local function set_http_headers(http_headers) end end +local special_cookie_field_names = Set { + "expires", "max-age", "domain", "path" +} + +-- parse single-line Set-Cookie syntax +local function parse_cookies(cookies_line) + if not cookies_line then + return {} + end + local cookies = {} + local cookie = {} + for stem in cookies_line:gmatch('[^;]+') do + stem = stem:gsub("^%s*(.-)%s*$", "%1") + local name, value = stem:match('^(.-)=(.+)$') + if name and name ~= "" and value then + local cmp_name = name:lower() + if special_cookie_field_names[cmp_name] then + cookie[cmp_name] = value + else + if cookie.name and cookie.value then + table.insert(cookies, cookie) + end + cookie = { + name = name, + value = value, + } + end + end + end + if cookie.name and cookie.value then + local cookie_key = cookie.domain .. ":" .. cookie.name + cookies[cookie_key] = cookie + end + return cookies +end + +-- serialize cookies for avformat +local function serialize_cookies_for_avformat(cookies) + local result = '' + for _, cookie in pairs(cookies) do + local cookie_str = ('%s=%s; '):format(cookie.name, cookie.value) + for k, v in pairs(cookie) do + if k ~= "name" and k ~= "value" then + cookie_str = cookie_str .. ('%s=%s; '):format(k, v) + end + end + result = result .. cookie_str .. '\r\n' + end + return result +end + +-- set file-local cookies, preserving existing ones +local function set_cookies(cookies) + if not cookies or cookies == "" then + return + end + + local option_key = "file-local-options/stream-lavf-o" + local stream_opts = mp.get_property_native(option_key, {}) + local existing_cookies = parse_cookies(stream_opts["cookies"]) + + local new_cookies = parse_cookies(cookies) + for cookie_key, cookie in pairs(new_cookies) do + if not existing_cookies[cookie_key] then + existing_cookies[cookie_key] = cookie + end + end + + stream_opts["cookies"] = serialize_cookies_for_avformat(existing_cookies) + mp.set_property_native(option_key, stream_opts) +end + local function append_libav_opt(props, name, value) if not props then props = {} @@ -136,7 +229,7 @@ local function edl_escape(url) end local function url_is_safe(url) - local proto = type(url) == "string" and url:match("^(.+)://") or nil + local proto = type(url) == "string" and url:match("^(%a[%w+.-]*):") or nil local safe = proto and safe_protos[proto] if not safe then msg.error(("Ignoring potentially unsafe url: '%s'"):format(url)) @@ -176,11 +269,8 @@ 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) + for match in o.exclude:gmatch('%|?([^|]+)') do + ytdl.blacklisted[#ytdl.blacklisted + 1] = match end end if #ytdl.blacklisted > 0 then @@ -198,7 +288,9 @@ 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 + if not json.extractor or + (json.extractor ~= "youtube:tab" and + json.extractor ~= "youtube:playlist") then return nil end @@ -223,7 +315,7 @@ local function parse_yt_playlist(url, json) -- 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 + if json.entries[i].id == args["v"] then msg.debug("found requested video in index " .. (i - 1)) return i - 1 end @@ -265,7 +357,7 @@ local function join_url(base_url, fragment) end local function edl_track_joined(fragments, protocol, is_live, base) - if not (type(fragments) == "table") or not fragments[1] then + if type(fragments) ~= "table" or not fragments[1] then msg.debug("No fragments to join into EDL") return nil end @@ -274,12 +366,12 @@ local function edl_track_joined(fragments, protocol, is_live, base) local offset = 1 local parts = {} - if (protocol == "http_dash_segments") and not is_live then + 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 + if not fragments[1].duration and #fragments > 1 then msg.debug("Using init segment") args = args .. ",init=" .. edl_escape(join_url(base, fragments[1])) offset = 2 @@ -291,7 +383,7 @@ local function edl_track_joined(fragments, protocol, is_live, base) -- 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" .. + msg.verbose("EDL doesn't support fragments " .. "without duration with MP4 DASH") return nil end @@ -341,6 +433,20 @@ local function as_integer(v, def) return def end +local function tags_to_edl(json) + local tags = {} + for json_name, mp_name in pairs(tag_list) do + local v = json[json_name] + if v then + tags[#tags + 1] = mp_name .. "=" .. edl_escape(tostring(v)) + end + end + if #tags == 0 then + return nil + end + return "!global_tags," .. table.concat(tags, ",") +end + -- 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 @@ -357,7 +463,7 @@ local function formats_to_edl(json, formats, use_all_formats) } local default_formats = {} - local requested_formats = json["requested_formats"] + local requested_formats = json["requested_formats"] or json["requested_downloads"] if use_all_formats and requested_formats then for _, track in ipairs(requested_formats) do local id = track["format_id"] @@ -377,38 +483,51 @@ local function formats_to_edl(json, formats, use_all_formats) (not track["abr"]) and (not track["vbr"]) end - for index, track in ipairs(formats) do + local has_requested_video = false + local has_requested_audio = false + -- Web players with quality selection always show the highest quality + -- option at the top. Since tracks are usually listed with the first + -- track at the top, that should also be the highest quality track. + -- yt-dlp/youtube-dl sorts it's formats from worst to best. + -- Iterate in reverse to get best track first. + for index = #formats, 1, -1 do + local track = formats[index] 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 + msg.error("No safe URL or supported fragmented stream available") return nil end + local is_default = default_formats[track["format_id"]] local tracks = {} - if track.vcodec and track.vcodec ~= "none" then + -- "none" means it is not a video + -- nil means it is unknown + if (o.force_all_formats or track.vcodec) and track.vcodec ~= "none" then tracks[#tracks + 1] = { media_type = "video", codec = map_codec_to_mpv(track.vcodec), } + if is_default then + has_requested_video = true + end end - -- Tries to follow the strange logic that vcodec unset means it's - -- an audio stream, even if acodec is sometimes unset. - if (#tracks == 0) or (track.acodec and track.acodec ~= "none") then + if (o.force_all_formats or track.acodec) and track.acodec ~= "none" then tracks[#tracks + 1] = { media_type = "audio", codec = map_codec_to_mpv(track.acodec) or ext_map[track.ext], } - end - if #tracks == 0 then - return nil + if is_default then + has_requested_audio = true + end end local url = edl_track or track.url local hdr = {"!new_stream", "!no_clip", "!no_chapters"} - local skip = false + local skip = #tracks == 0 local params = "" if use_all_formats then @@ -449,7 +568,7 @@ local function formats_to_edl(json, formats, use_all_formats) title = title .. "muxed-" .. index end local flags = {} - if default_formats[track["format_id"]] then + if is_default then flags[#flags + 1] = "default" end hdr[#hdr + 1] = "!track_meta,title=" .. @@ -462,24 +581,39 @@ local function formats_to_edl(json, formats, use_all_formats) end end - hdr[#hdr + 1] = edl_escape(url) .. params + if not skip then + hdr[#hdr + 1] = edl_escape(url) .. params - 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 + 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 end + local tags = tags_to_edl(json) + -- 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 + -- only a single track without metadata (i.e. redundant). + if #streams == 1 and single_url and not tags then res.url = single_url elseif #streams > 0 then + if tags then + -- not a stream; just for the sake of concatenating the EDL string + streams[#streams + 1] = tags + end res.url = "edl://" .. table.concat(streams, ";") else return nil end + if has_requested_audio ~= has_requested_video then + local not_req_prop = has_requested_video and "aid" or "vid" + if mp.get_property(not_req_prop) == "auto" then + mp.set_property("file-local-options/" .. not_req_prop, "no") + end + end + return res end @@ -487,8 +621,15 @@ local function add_single_video(json) local streamurl = "" local format_info = "" local max_bitrate = 0 - local requested_formats = json["requested_formats"] + local requested_formats = json["requested_formats"] or json["requested_downloads"] local all_formats = json["formats"] + local has_requested_formats = requested_formats and #requested_formats > 0 + local http_headers = has_requested_formats + and requested_formats[1].http_headers + or json.http_headers + local cookies = has_requested_formats + and requested_formats[1].cookies + or json.cookies if o.use_manifests and valid_manifest(json) then -- prefer manifest_url if present @@ -507,7 +648,7 @@ local function add_single_video(json) if requested_formats then for _, track in pairs(requested_formats) do - max_bitrate = track.tbr > max_bitrate and + max_bitrate = (track.tbr and track.tbr > max_bitrate) and track.tbr or max_bitrate end elseif json.tbr then @@ -518,7 +659,6 @@ local function add_single_video(json) if streamurl == "" then -- possibly DASH/split tracks local res = nil - local has_requested_formats = requested_formats and #requested_formats > 0 -- Not having requested_formats usually hints to HLS master playlist -- usage, which we don't want to split off, at least not yet. @@ -534,7 +674,7 @@ local function add_single_video(json) end end - if (not res) and has_requested_formats then + if not res and has_requested_formats then format_info = "youtube-dl (separate)" res = formats_to_edl(json, requested_formats, false) end @@ -555,7 +695,6 @@ local function add_single_video(json) end -- normal video or single track streamurl = edl_track or json.url - set_http_headers(json.http_headers) end if streamurl == "" then @@ -563,12 +702,16 @@ local function add_single_video(json) return end + set_http_headers(http_headers) + msg.verbose("format selection: " .. format_info) 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) + if mp.get_property("force-media-title", "") == "" then + mp.set_property("file-local-options/force-media-title", json.title) + end -- set hls-bitrate for dash track selection if max_bitrate > 0 and @@ -578,7 +721,7 @@ local function add_single_video(json) end -- add subtitles - if not (json.requested_subtitles == nil) then + if json.requested_subtitles ~= nil then local subs = {} for lang, info in pairs(json.requested_subtitles) do subs[#subs + 1] = {lang = lang or "-", info = info} @@ -590,27 +733,58 @@ local function add_single_video(json) local sub = nil - if not (sub_info.data == nil) then + if sub_info.data ~= nil then sub = "memory://"..sub_info.data - elseif not (sub_info.url == nil) and + elseif sub_info.url ~= nil and url_is_safe(sub_info.url) then sub = sub_info.url end - if not (sub == nil) then + if sub ~= nil then local edl = "edl://!no_clip;!delay_open,media_type=sub" local codec = map_codec_to_mpv(sub_info.ext) if codec then edl = edl .. ",codec=" .. codec end edl = edl .. ";" .. edl_escape(sub) - mp.commandv("sub-add", edl, "auto", sub_info.ext, lang) + local title = sub_info.name or sub_info.ext + mp.commandv("sub-add", edl, "auto", title, lang) else msg.verbose("No subtitle data/url for ["..lang.."]") end end end + -- add thumbnails + if (o.thumbnails == 'all' or o.thumbnails == 'best') and json.thumbnails ~= nil then + local thumb = nil + local thumb_height = -1 + local thumb_preference = nil + + for i = #json.thumbnails, 1, -1 do + local thumb_info = json.thumbnails[i] + if thumb_info.url ~= nil then + if o.thumbnails == 'all' then + msg.verbose("adding thumbnail") + mp.commandv("video-add", thumb_info.url, "auto") + thumb_height = 0 + elseif (thumb_preference ~= nil and (thumb_info.preference or -math.huge) > thumb_preference) or + (thumb_preference == nil and ((thumb_info.height or 0) > thumb_height)) then + thumb = thumb_info.url + thumb_height = thumb_info.height or 0 + thumb_preference = thumb_info.preference + end + end + end + + if thumb ~= nil then + msg.verbose("adding thumbnail") + mp.commandv("video-add", thumb, "auto") + elseif thumb_height == -1 then + msg.verbose("No thumbnail url") + end + end + -- add chapters if json.chapters then msg.debug("Adding pre-parsed chapters") @@ -622,20 +796,30 @@ local function add_single_video(json) end table.insert(chapter_list, {time=chapter.start_time, title=title}) end - elseif not (json.description == nil) and not (json.duration == nil) then + elseif json.description ~= nil and json.duration ~= nil then chapter_list = extract_chapters(json.description, json.duration) end -- set start time - if not (json.start_time == nil) and + if json.start_time or json.section_start 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) + local start_time = json.start_time or json.section_start + msg.debug("Setting start to: " .. start_time .. " secs") + mp.set_property("file-local-options/start", start_time) + end + + -- set end time + if json.end_time or json.section_end and + not option_was_set("end") and + not option_was_set_locally("end") then + local end_time = json.end_time or json.section_end + msg.debug("Setting end to: " .. end_time .. " secs") + mp.set_property("file-local-options/end", end_time) end -- set aspect ratio for anamorphic video - if not (json.stretched_ratio == nil) and + if 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 @@ -643,7 +827,7 @@ local function add_single_video(json) local stream_opts = mp.get_property_native("file-local-options/stream-lavf-o", {}) -- for rtmp - if (json.protocol == "rtmp") then + if json.protocol == "rtmp" then stream_opts = append_libav_opt(stream_opts, "rtmp_tcurl", streamurl) stream_opts = append_libav_opt(stream_opts, @@ -663,6 +847,15 @@ local function add_single_video(json) "http_proxy", json.proxy) end + if cookies and cookies ~= "" then + local existing_cookies = parse_cookies(stream_opts["cookies"]) + local new_cookies = parse_cookies(cookies) + for cookie_key, cookie in pairs(new_cookies) do + existing_cookies[cookie_key] = cookie + end + stream_opts["cookies"] = serialize_cookies_for_avformat(existing_cookies) + end + mp.set_property_native("file-local-options/stream-lavf-o", stream_opts) end @@ -676,12 +869,12 @@ local function check_version(ytdl_path) 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 + 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 + 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 @@ -689,22 +882,8 @@ 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(o.ytdl_path .. exesuf) - if ytdl_mcd == nil then - msg.verbose("No youtube-dl found with path "..o.ytdl_path..exesuf.." in config directories") - ytdl.path = o.ytdl_path - else - 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 + if url:find("ytdl://") == 1 then url = url:sub(8) end @@ -721,12 +900,12 @@ function run_ytdl_hook(url) -- 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 + 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 + if format == "" then format = "bestvideo+bestaudio/best" end @@ -737,19 +916,19 @@ function run_ytdl_hook(url) for param, arg in pairs(raw_options) do table.insert(command, "--" .. param) - if (arg ~= "") then + if arg ~= "" then table.insert(command, arg) end - if (param == "sub-lang") and (arg ~= "") then + if (param == "sub-lang" or param == "sub-langs" or param == "srt-lang") and (arg ~= "") then allsubs = false - elseif (param == "proxy") and (arg ~= "") then + elseif param == "proxy" and arg ~= "" then proxy = arg - elseif (param == "yes-playlist") then + elseif param == "yes-playlist" then use_playlist = true end end - if (allsubs == true) then + if allsubs == true then table.insert(command, "--all-subs") end if not use_playlist then @@ -757,40 +936,83 @@ function run_ytdl_hook(url) end table.insert(command, "--") table.insert(command, url) - msg.debug("Running: " .. table.concat(command,' ')) - local es, json, result, aborted = exec(command) - if aborted then + local result + if ytdl.searched then + result = exec(command) + else + local separator = platform_is_windows() and ";" or ":" + if o.ytdl_path:match("[^" .. separator .. "]") then + ytdl.paths_to_search = {} + for path in o.ytdl_path:gmatch("[^" .. separator .. "]+") do + table.insert(ytdl.paths_to_search, path) + end + end + + for _, path in pairs(ytdl.paths_to_search) do + -- search for youtube-dl in mpv's config dir + local exesuf = platform_is_windows() and not path:lower():match("%.exe$") and ".exe" or "" + local ytdl_cmd = mp.find_config_file(path .. exesuf) + if ytdl_cmd then + msg.verbose("Found youtube-dl at: " .. ytdl_cmd) + ytdl.path = ytdl_cmd + command[1] = ytdl.path + result = exec(command) + break + else + msg.verbose("No youtube-dl found with path " .. path .. exesuf .. " in config directories") + command[1] = path + result = exec(command) + if result.error_string == "init" then + msg.verbose("youtube-dl with path " .. path .. " not found in PATH or not enough permissions") + else + msg.verbose("Found youtube-dl with path " .. path .. " in PATH") + ytdl.path = path + break + end + end + end + + ytdl.searched = true + end + + if result.killed_by_us then return end - if (es < 0) or (json == nil) or (json == "") then + local json = result.stdout + local parse_err = nil + + if result.status ~= 0 or json == "" then + json = nil + elseif json then + json, parse_err = utils.parse_json(json) + end + + if json == nil then + msg.verbose("status:", result.status) + msg.verbose("reason:", result.error_string) + msg.verbose("stdout:", result.stdout) + msg.verbose("stderr:", result.stderr) + -- 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 occurred" + elseif parse_err then + err = err .. "failed to parse JSON data: " .. parse_err else - err = string.format("%s returned '%d'", err, es) + err = err .. "unexpected error occurred" end msg.error(err) - if string.find(ytdl_err, "yt%-dl%.org/bug") then + if parse_err or 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') @@ -801,11 +1023,11 @@ function run_ytdl_hook(url) -- direct URL, nothing to do msg.verbose("Got direct URL") return - elseif (json["_type"] == "playlist") - or (json["_type"] == "multi_video") then + elseif json["_type"] == "playlist" or + json["_type"] == "multi_video" then -- a playlist - if (#json.entries == 0) then + if #json.entries == 0 then msg.warn("Got empty playlist, nothing to play.") return end @@ -832,9 +1054,10 @@ function run_ytdl_hook(url) -- can't change the http headers for each entry, so use the 1st set_http_headers(json.entries[1].http_headers) + set_cookies(json.entries[1].cookies or json.cookies) mp.set_property("stream-open-filename", playlist) - if not (json.title == nil) then + if json.title and mp.get_property("force-media-title", "") == "" then mp.set_property("file-local-options/force-media-title", json.title) end @@ -842,19 +1065,19 @@ function run_ytdl_hook(url) -- 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 + if 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 + if entry_wsubs ~= nil and + 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 + if entry.requested_subtitles ~= nil and + 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 @@ -877,7 +1100,7 @@ function run_ytdl_hook(url) local site = entry.url local title = entry.title - if not (title == nil) then + if title ~= nil then title = string.gsub(title, '%s+', ' ') table.insert(playlist, "#EXTINF:0," .. title) end @@ -893,15 +1116,23 @@ function run_ytdl_hook(url) site = entry["webpage_url"] end + local playlist_url = nil + -- 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) + playlist_url = prefix .. site elseif url_is_safe(site) then - table.insert(playlist, site) + playlist_url = site + end + + if playlist_url then + table.insert(playlist, playlist_url) + -- save the cookies in a table for the playlist hook + playlist_cookies[playlist_url] = entry.cookies or json.cookies end end @@ -920,11 +1151,11 @@ function run_ytdl_hook(url) msg.debug('script running time: '..os.clock()-start_time..' seconds') end -if (not o.try_ytdl_first) then +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 + if url:find("ytdl://") ~= 1 then msg.verbose('not a ytdl:// url') return end @@ -932,10 +1163,18 @@ if (not o.try_ytdl_first) then end) end +mp.add_hook("on_load", 20, function () + msg.verbose('playlist hook') + local url = mp.get_property("stream-open-filename", "") + if playlist_cookies[url] then + set_cookies(playlist_cookies[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 + if url:find("ytdl://") ~= 1 and not ((url:find("https?://") == 1) and not is_blacklisted(url)) then return end |