summaryrefslogtreecommitdiffstats
path: root/player/lua/ytdl_hook.lua
diff options
context:
space:
mode:
Diffstat (limited to 'player/lua/ytdl_hook.lua')
-rw-r--r--player/lua/ytdl_hook.lua459
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