dotfiles/.config/mpv/scripts/webtorrent-hook/main.lua
2025-05-09 22:52:11 +02:00

249 lines
7.6 KiB
Lua

-- TODO start webtorrent instance immediately when pasting into playlist instead
-- of waiting for load?
-- TODO what happens with --prefetch-playlist?
local settings = {
close_webtorrent = true,
remove_files = true,
download_directory = "/tmp/webtorrent-hook",
webtorrent_flags = [[]],
show_speed = true,
remember_last_played = true,
remember_directory = "/tmp/webtorrent-hook-last-played"
}
(require "mp.options").read_options(settings, "webtorrent-hook")
local utils = require "mp.utils";
local webtorrent_instances = {}
local webtorrent_files = {}
local script_dir = mp.get_script_directory()
local printer_pid = nil
-- * Helpers
-- http://lua-users.org/wiki/StringRecipes
function ends_with(str, ending)
return ending == "" or str:sub(-#ending) == ending
end
function read_file(file)
local fh = assert(io.open(file, "rb"))
local contents = fh:read("*all")
fh:close()
return contents
end
function write_file(file, text)
local fh = io.open(file, "w")
fh:write(text)
fh:close()
end
function is_handled_url(url, load_failed)
if load_failed then
-- info hash
return (load_failed and string.match(url, "%w+"))
else
return (url:find("magnet:") == 1 or url:find("peerflix://") == 1
or url:find("webtorrent://") == 1 or ends_with(url, "torrent"))
end
end
function load_file_after_current(url, option_table, num_entries)
mp.command_native({
"loadfile", url, "append", -1, option_table
})
local index = mp.get_property("playlist-pos")
mp.command_native({
"playlist-move",
mp.get_property("playlist-count") - 1,
index + 1 + num_entries
})
end
-- * Store Last Played Files
function file_info_hash(filename)
return webtorrent_files[filename]
end
function get_remember_file_path(info_hash)
return utils.join_path(settings.remember_directory, info_hash)
end
function maybe_store_last_played_torrent_file(_)
if settings.remember_last_played then
mp.commandv("run", "mkdir", "-p", settings.remember_directory)
local filename = mp.get_property("media-title")
local info_hash = file_info_hash(filename)
if info_hash ~= nil then
local remember_file = get_remember_file_path(info_hash)
write_file(remember_file, filename)
end
end
end
mp.register_event("file-loaded", maybe_store_last_played_torrent_file)
function get_last_played_filename_for_torrent(info_hash)
local remember_file = get_remember_file_path(info_hash)
if utils.file_info(remember_file) then
return read_file(remember_file)
end
end
-- * Play Torrents
function load_webtorrent_files(info_hash, webtorrent_info)
local first = true
local found_last_played = false
local last_played_filename = ""
if settings.remember_last_played then
last_played_filename = get_last_played_filename_for_torrent(info_hash)
end
local should_remember = settings.remember_last_played
and last_played_filename
local file_index = 0
local file_play_index = 0
for _, file in pairs(webtorrent_info["files"]) do
local title = file["title"]
webtorrent_files[title] = info_hash
local option_table = {}
-- TODO is it actually necessary to set force-media-title for sub
-- plugins? it seems to be correctly set by default for what I've
-- tried
option_table["force-media-title"] = title
local url = file["url"]
if first then
load_file_after_current(url, option_table, 0)
mp.command_native({"playlist-remove", mp.get_property("playlist-pos")})
else
load_file_after_current(url, option_table,
file_index - (file_play_index + 1))
if should_remember and not found_last_played then
file_play_index = file_play_index + 1
mp.set_property("playlist-pos", mp.get_property("playlist-pos") + 1)
if title == last_played_filename then
found_last_played = true
end
end
end
file_index = file_index + 1
first = false
end
end
function maybe_kill_printer()
if printer_pid then
mp.commandv("run", "kill", printer_pid)
printer_pid = nil
end
end
mp.register_event("file-loaded", maybe_kill_printer)
function start_speed_printer(out_dir)
if utils.file_info(utils.join_path(out_dir, "webtorrent-output")) then
local speed_printer_path =
utils.join_path(script_dir, "webtorrent-speed-printer.sh")
os.execute(speed_printer_path .. ' "' .. out_dir .. '"')
printer_pid = read_file(utils.join_path(out_dir, "printer.pid"))
end
end
function start_webtorrent(url, torrent_info)
local base_dir = mp.command_native({
"expand-path", settings.download_directory
})
local info_hash = torrent_info["infoHash"]
local out_dir = utils.join_path(base_dir, info_hash)
local wrapper_path =
utils.join_path(script_dir, "webtorrent-wrap.sh")
local webtorrent_args = {wrapper_path, out_dir, url}
local flags = utils.parse_json(settings.webtorrent_flags)
if flags ~= nil then
for _, flag in pairs(flags) do
table.insert(webtorrent_args, flag)
end
end
mp.msg.info("Waiting for webtorrent server")
local webtorrent_result = mp.command_native({
name = "subprocess",
playback_only = false,
capture_stdout = true,
args = webtorrent_args
})
if webtorrent_result.status == 0 then
mp.msg.info("Webtorrent server is up")
local webtorrent_info = utils.parse_json(webtorrent_result.stdout)
local pid = webtorrent_info["pid"]
mp.msg.debug(webtorrent_info)
local name = "Unknown name"
if torrent_info["name"] ~= nil then
name = torrent_info["name"]
end
table.insert(webtorrent_instances,
{download_dir=out_dir,pid=pid,name=name})
if settings.show_speed then
start_speed_printer(out_dir)
end
load_webtorrent_files(info_hash, webtorrent_info)
else
mp.msg.info("Failed to start webtorrent")
end
end
-- check if the url is a torrent and play it if it is
function maybe_play_torrent(load_failed)
local url = mp.get_property("stream-open-filename")
if is_handled_url(url, load_failed) then
if url:find("webtorrent://") == 1 then
url = url:sub(14)
end
if url:find("peerflix://") == 1 then
url = url:sub(12)
end
local torrent_info_command = mp.command_native({
name = "subprocess",
playback_only = false,
capture_stdout = true,
args = {"webtorrent", "info", url},
})
if torrent_info_command.status == 0 then
local torrent_info = utils.parse_json(torrent_info_command.stdout)
local info_hash = torrent_info["infoHash"]
if info_hash ~= nil then
start_webtorrent(url, torrent_info)
end
end
end
end
function check_if_torrent_on_load()
maybe_play_torrent(false)
end
function check_if_torrent_on_load_fail()
maybe_play_torrent(true)
end
function webtorrent_cleanup()
if settings.close_webtorrent then
for _, instance in pairs(webtorrent_instances) do
mp.msg.verbose("Killing WebTorrent pid " .. instance.pid)
mp.commandv("run", "kill", instance.pid)
if settings.remove_files then
mp.msg.verbose("Removing files for torrent " .. instance.name)
mp.commandv("run", "rm", "-r", instance.download_dir)
end
end
end
end
mp.add_hook("on_load", 50, check_if_torrent_on_load)
mp.add_hook("on_load_fail", 50, check_if_torrent_on_load_fail)
mp.register_event("shutdown", webtorrent_cleanup)