local cachePath = "/tmp/ytdl-preload" local nextIndex local caught = true local pop = false local ytdl = "yt-dlp" local utils = require 'mp.utils' local chapter_list = {} local json = "" local function exists(file) local ok, err, code = os.rename(file, file) if not ok then if code == 13 then -- Permission denied, but it exists return true end end return ok, err end -- from ytdl_hook 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 chapters() if json.chapters then 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 end -- end ytdl_hook local title = "" local function listener(event) if not caught and event.prefix == mp.get_script_name() then local destination = string.match(event.text, "%[download%] Destination: (.+).mkv") or string.match(event.text, "%[download%] (.+).mkv has already been downloaded") if destination and string.find(destination, string.gsub(cachePath, '~/', '')) then _, title = utils.split_path(destination) local audio = "" if exists(destination .. ".mka") then audio = "audio-file=" .. destination .. '.mka,' end mp.commandv("loadfile", destination .. ".mkv", "append", audio .. 'force-media-title="' .. title:gsub("-" .. ("[%w_-]"):rep(11) .. "$", "") .. '",demuxer-max-back-bytes=1MiB,demuxer-max-bytes=3MiB,ytdl=no') -- ,sub-file="..destination..".en.vtt") --in case they are not set up to autoload mp.commandv("playlist_move", mp.get_property("playlist-count") - 1, nextIndex) mp.commandv("playlist_remove", nextIndex + 1) mp.unregister_event(listener) caught = true title = "" pop = true end end end -- from ytdl_hook mp.add_hook("on_preloaded", 10, function() if string.find(mp.get_property("path"), cachePath) then chapters() if next(chapter_list) ~= nil then mp.set_property_native("chapter-list", chapter_list) chapter_list = {} json = "" end end end) -- end ytdl_hook local function DL() -- mp.add_timeout(1, function() if tonumber(mp.get_property("playlist-pos-1")) > 0 and mp.get_property("playlist-pos-1") ~= mp.get_property("playlist-count") then nextIndex = tonumber(mp.get_property("playlist-pos")) + 1 local nextFile = mp.get_property( "playlist/" .. tostring(nextIndex) .. "/filename") if nextFile and caught and nextFile:find("://", 0, false) then caught = false mp.enable_messages("info") mp.register_event("log-message", listener) local ytFormat = mp.get_property("ytdl-format") local fVideo = string.match(ytFormat, '(.+)%+.+//?') or 'bestvideo' local fAudio = string.match(ytFormat, '.+%+(.+)//?') or 'bestaudio' json = mp.command_native({ name = "subprocess", args = { ytdl, "--dump-single-json", nextFile }, capture_stdout = true, capture_stderr = true }) if json then json = json.stdout if json:find("audio only") then mp.command_native_async({ name = "subprocess", args = { ytdl, "-q", "-f", fAudio, "--restrict-filenames", "--no-playlist", "--sub-lang", "en", "--write-sub", "--no-part", "-o", cachePath .. "/%(title)s-%(id)s.mka", nextFile }, playback_only = false }, function() end) else if fVideo:find("bestvideo") then fVideo = fVideo:gsub("bestvideo", "best") end end json = utils.parse_json(json) end mp.command_native_async({ name = "subprocess", -- args = {ytdl, "-f", fVideo..'/best', "--restrict-filenames", "--no-part", "-N","2","-o", cachePath.."/%(title)s-%(id)s.mkv", nextFile}, args = { ytdl, "-f", fVideo .. '/best', "--restrict-filenames", "--no-playlist", "--no-part", "-o", cachePath .. "/%(title)s-%(id)s.mkv", nextFile }, playback_only = false }, function() end) end end -- end) end local function clearCache() if pop == true then if package.config:sub(1, 1) ~= '/' then os.execute('rd /s/q "' .. cachePath .. '"') else os.execute('rm -rd ' .. cachePath) end print('clear') mp.command("quit") end end local skipInitial mp.observe_property("playlist-count", "number", function() if skipInitial then DL() else skipInitial = true end end) -- from ytdl_hook local platform_is_windows = (package.config:sub(1, 1) == "\\") local o = { exclude = "", try_ytdl_first = false, use_manifests = false, all_formats = false, force_all_formats = true, ytdl_path = "" } local paths_to_search = { "yt-dlp", "yt-dlp_x86", "youtube-dl" } local options = require 'mp.options' options.read_options(o, "ytdl_hook") local separator = platform_is_windows and ";" or ":" if o.ytdl_path:match("[^" .. separator .. "]") then paths_to_search = {} for path in o.ytdl_path:gmatch("[^" .. separator .. "]+") do table.insert(paths_to_search, path) end 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 end local msg = require 'mp.msg' local command = {} for _, path in pairs(paths_to_search) do -- search for youtube-dl in mpv's config dir local exesuf = platform_is_windows 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 = ytdl_cmd break else msg.verbose("No youtube-dl found with path " .. path .. exesuf .. " in config directories") -- search in PATH command[1] = path es, json, result, aborted = exec(command) if result.error_string == "init" then msg.verbose("youtube-dl with path " .. path .. exesuf .. " not found in PATH or not enough permissions") else msg.verbose("Found youtube-dl with path " .. path .. exesuf .. " in PATH") ytdl = path break end end end -- end ytdl_hook mp.register_event("start-file", DL) mp.register_event("shutdown", clearCache)