-- protocol_hook.lua -- handlers.json: https://searchfox.org/mozilla-central/source/uriloader/exthandler/tests/unit/handlers.json -- named pipe: mpv.exe --input-ipc-server=\\.\pipe\mpvsocket local utils = require 'mp.utils' local msg = require 'mp.msg' local opts = require "mp.options" local options = { cwd = '', ipcMode = false, proxy = '', nogeometry = true, stream_quality = '720p,best,worst', } opts.read_options(options, "protocol_hook") -- MPV folder local cwd = options.cwd --Beta feature, Windows only (for now), true = on, false = off. check -- named pipe local ipcMode = options.ipcMode local proxy = options.proxy print(proxy) if proxy == '' then proxy = false end print(proxy) local nogeometry = options.nogeometry local stream_quality = options.stream_quality print(cwd) local function getOS() local BinaryFormat = package.cpath --print(BinaryFormat) if BinaryFormat:match("dll$") then return "Windows" elseif BinaryFormat:match("so$") then if BinaryFormat:match("homebrew") then return "MacOS" else return "Linux" end elseif BinaryFormat:match("dylib$") then return "MacOS" end end local function exedir() --local path1 = debug.getinfo(1).source local path = mp.command_native({'expand-path', '~~home/'}) print(path) path = path:gsub('/portable_config.*', '') print(path) return path end local osv = getOS() if cwd == '' then cwd = exedir() print(cwd) end --[[function getOS(cmd, raw) local f = assert(io.popen(cmd, 'r')) local s = assert(f:read('*a')) f:close() if raw then return s end s = string.gsub(s, '^%s+', '') s = string.gsub(s, '%s+$', '') s = string.gsub(s, '[\n\r]+', ' ') return s end function getOS() -- ask LuaJIT first if jit then return jit.os end -- Unix, Linux variants local fh,err = assert(io.popen("uname -o 2>/dev/null","r")) if fh then osname = fh:read() end return osname or "Windows" end print(getOS())]]-- --print(getOS()) local function parseqs(url) -- return 0-based index to use with --playlist-start 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 return args end local function dump(o) if type(o) == 'table' then local s = '{ ' for k,v in pairs(o) do if type(k) ~= 'number' then k = '"'..k..'"' end s = s .. '['..k..'] = ' .. dump(v) .. ',' end return s .. '} ' else return tostring(o) end end local function split(text, delim) -- returns an array of fields based on text and delimiter (one character only) local result = {} local magic = "().%+-*?[]^$" if delim == nil then delim = "%s" elseif string.find(delim, magic, 1, true) then -- escape magic delim = "%"..delim end local pattern = "[^"..delim.."]+" for w in string.gmatch(text, pattern) do table.insert(result, w) end return result end local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' -- You will need this for encoding/decoding -- encoding local function enc(data) return ((data:gsub('.', function(x) local r,b='',x:byte() for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end return r; end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) if (#x < 6) then return '' end local c=0 for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end return b:sub(c+1,c+1) end)..({ '', '==', '=' })[#data%3+1]) end -- decoding local function dec(data) data = string.gsub(data, '[^'..b..'=]', '') return (data:gsub('.', function(x) if (x == '=') then return '' end local r,f='',(b:find(x)-1) for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end return r; end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) if (#x ~= 8) then return '' end local c=0 for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end return string.char(c) end)) end local function atobUrl(url) url = string.gsub(url, '_', '/') url = string.gsub(url, '-', '+') url = dec(url) return url end local function regexEscape(str) return str:gsub("[%(%)%.%%%+%-%*%?%[%^%$%]]", "%%%1") end -- you can use return and set your own name if you do require() or dofile() -- like this: str_replace = require("string-replace") -- return function (str, this, that) -- modify the line below for the above to work local function replace (str, this, that) return str:gsub(regexEscape(this), that:gsub("%%", "%%%%")) -- only % needs to be escaped for 'that' end local function exec(args) print("Running: " .. table.concat(args, " ")) return mp.command_native({ name = "subprocess", args = args, capture_stdout = true, capture_stderr = true, }) end local function stripqs(s) return string.gsub(s, '&.*', '') end local function escapeqs(s) return string.gsub(s, '&', '^&') end function magiclines(s) if s:sub(-1)~="\n" then s=s.."\n" end return s:gmatch("(.-)\n") end local function getDomain(url) return split(url, '/')[2] end local function livestreamer(url, referer, proxy, hls) print('Streamlink: '..url) local url2 = '"'..url..'"' local cmd = '' local cmd2 = url2..' '..stream_quality..' ' --local cmdconfig = '' local cmdconfig = ' --config='..cwd..'/streamlink.conf' if osv == 'Windows' then cmd = 'run '..cwd..'/streamlink/bin/streamlink.exe ' else cmd = 'run streamlink ' end --if (url:find("hls://") == 1) then if (hls == true) then cmdconfig = cmdconfig..' --player-args=--demuxer-lavf-format=mpegts ' end if proxy ~= false then cmd2 = cmd2..'--http-proxy='..proxy..'/' end print(referer) cmd = cmd..cmd2..cmdconfig if referer ~= '' then cmd = cmd..' --http-header=Referer='..referer end print(cmd) mp.command(cmd) --mp.command('quit') end -- Thank to pTalent: https://voz.vn/t/tong-hop-nhung-addon-chat-cho-firefox-pc-mobile.682181/post-27975348 local function iptv(url, referer, proxy, hls) print('IPTV: '..url) local url2 = '"'..url..'"' local playlist = false --if (url:find("hls://") == 1) then if (hls == true) then playlist = true --url = string.gsub(url, 'hls:', 'https:') end local curlpath = '' local mpvpath = '' local cmdcurl = '' local cmdmpv = '' local stdout = '' local stdfin = '' local domain = getDomain(url) print(domain) print(dump(split(url, '/'))) if osv == 'Windows' then curlpath = cwd..'/curl.exe ' else curlpath = 'curl ' end if osv == 'Windows' then mpvpath = 'run '..cwd..'/mpv.exe ' else mpvpath = 'mpv curl ' end local args = {curlpath, url, "-L", '-s'} print(dump(args)) local p = mp.command_native{ name = "subprocess", capture_stdout = true, playback_only = false, args = args } if p.stdout then for line in magiclines(p.stdout) do if (line:find('/') == 1) then stdout = stdout..'https://'..domain..line..'\n' else stdout = stdout..line..'\n' end end --stdout = string.gsub(stdout, '/hls/', 'https://'..domain..'/hls/') print(stdout) f = io.open(cwd..'/dummy.m3u8', 'w') f:write(stdout) f:close() if playlist == true then mp.commandv('set', 'ytdl', 'no') mp.commandv('set', 'prefetch-playlist', 'yes') mp.commandv('set', 'demuxer-lavf-format', 'mpegts') mp.commandv('loadlist', cwd..'/dummy.m3u8') else mp.commandv('loadfile', cwd..'/dummy.m3u8') end end --cmdcurl = curlpath..' -L '..url2..' -o magicList.m3u8' --cmdmpv = mpvpath..' magicList.m3u8' --mp.command(cmdcurl) --mp.command(cmdmpv) --mp.commandv('loadfile', 'magicList.m3u8') --mp.command('quit') end local function mpv(url, referer, proxy, command) print('MPV: '..url) local url2 = '"'..url..'"' local cmd = '' if osv == 'Windows' then cmd = 'run '..cwd..'/mpv.exe ' else cmd = 'run mpv ' end if proxy ~= false then cmd = cmd..'--http-proxy='..proxy..'/' end if command then url2 = string.gsub(url2, '?geometry', '?geometry2') cmd = cmd..' '..command end cmd = cmd..' '..url2 --print(cmd) mp.command(cmd) --mp.command('quit') end local function EA(url, referer, app) print('EA: '..url) local url2 = '"'..url..'"' local cmd = 'run '..app..' '..url2 mp.command(cmd) --mp.command('quit') end local function ytdl(url, referer, mode) print('YTDL: '..url) local url2 = '"'..url..'"' local cmd = '' local cmd2 = '' if osv == 'Windows' then cmd = 'run cmd /c cd /d '..cwd..' && start yt-dlp.exe ' else cmd = 'run yt-dlp ' end if mode == 'audio' then cmd2 = ' -f ba --extract-audio ' --else -- cmd2 = ' -f bv+ba/b ' end cmd = cmd..cmd2..url2 mp.command(cmd) --mp.command('quit') end local function exec2(args) local ret = utils.subprocess({args = args}) return ret.status, ret.stdout, ret end local function gallery(url, referer) local es, urls, result = exec2({"gallery-dl", "-g", url}) print(urls) if (es < 0) or (urls == nil) or (urls == "") then msg.error("failed to get album list.") end msg.info(urls) mp.commandv("loadlist", "memory://" .. urls) end local function ipc(url, mode) local cmd = 'run cmd /c echo loadfile '..url..' '..mode..' >\\\\.\\pipe\\mpvsocket' --print(cmd) mp.command(cmd) end local function piper(url, mode, proxy, hls) --yt-dlp -o - https://www.douyu.com/5092355 print(proxy) local url = escapeqs(url) local playlist = false --if (url:find("hls://") == 1) then if (hls == true) then playlist = true --url = string.gsub(url, 'hls:', 'https:') end local cmd = '' local audiourl = '' local audiocmd = '' local args = {'yt-dlp', '-f', 'ba', '-g', url} local quality = '-f bv[height^>=?1080][vcodec*=?avc1]' local mpvcmd ='' local ytdlcmd = '' --print(dump(args)) if string.find(url, 'youtube.com') then local p = mp.command_native{ name = "subprocess", capture_stdout = true, playback_only = false, args = args } if p.stdout then audiourl = p.stdout end audiourl = escapeqs(audiourl) audiocmd = ' --audio-file='..audiourl else quality = '' audiocmd = '' end if audiocmd ~= '' then mpvcmd = mpvcmd..' '..audiocmd end if proxy ~= false then mpvcmd = mpvcmd..' '..'--http-proxy='..proxy ytdlcmd = ytdlcmd..' '..'--proxy='..proxy end if playlist ~= false then mpvcmd = mpvcmd..' '..'--demuxer-lavf-format=mpegts' end if quality ~= '' then ytdlcmd = ytdlcmd..' '..quality end if osv == 'Windows' then cmd = 'run cmd /c cd /d '..cwd..' && yt-dlp.exe '..ytdlcmd..' -o - '..url..' | mpv.exe -'..mpvcmd else cmd = 'run yt-dlp '..ytdlcmd..' -o - '..url..' | mpv -'..mpvcmd end print(cmd) print(cwd) mp.command(cmd) end --print(dump(mp)) --print(mp.find_config_file('.')) --print(utils.join_path(mp.find_config_file('.'),"streamlink")) --print(dump(debug.getinfo(1))) --print(debug.getinfo(1).source) --current_dir=io.popen"cd":read'*l' --print(current_dir) --print(package.path) --print(package.cpath) --print(os.getenv('PATH')) print(dump(options)) if ipcMode == true then local ipc = mp.get_property("input-ipc-server", "") if ipc ~= "" then ipcMode = false end end local hook_count = 0 mp.add_hook("on_load", 1, function() local mode = 'append-play' local referer = '' local ourl = mp.get_property("stream-open-filename", "") local url = ourl local qs = {} local autopip = false local start = false local audio = false local playlist = false local pipe = false local hls = false if osv == 'MacOS' then url = 'mpv://'..url end if (url:find("mpv://") ~= 1) then print("not a mpv url: " .. url) return end --mp.set_property("stream-open-filename", "memory://") --mp.commandv('playlist-clear') --mp.commandv('playlist-remove', 'current') local arr = split(url, '/') if arr[1] == 'mpv:' then url = atobUrl(arr[3]) if (url:find("data:") == 1) then url = atobUrl(split(url, ',')[2]) --print(url) end if arr[4] then qs = parseqs(arr[4]) end local function subadd() local subs = qs['subs'] subs = atobUrl(subs) mp.commandv('sub-add', subs) end local function cleanup() mp.commandv("playlist-remove", "0") end --mp.register_event("file-loaded", cleanup) if qs['subs'] then mp.register_event("file-loaded", subadd) end if qs['referer'] then referer = qs['referer'] referer = atobUrl(referer) print(referer) end if qs['autopip'] then autopip = true ourl = string.gsub(ourl, '?autopip=1', '') end if qs['hls'] then hls = true end if qs['start'] then start = qs['start'] end if arr[2] == 'mpv69' then arr[2] = 'play' proxy = 'http://127.0.0.1:9966' end if arr[2] == 'ls69' then arr[2] = 'stream' proxy = 'http://127.0.0.1:9966' end if arr[2] == 'mpva' then arr[2] = 'play' audio = true end if arr[2] == 'mpvp' or arr[2] == 'list' then arr[2] = 'play' mp.commandv('set', 'ytdl', 'no') playlist = true end if arr[2] == 'mpvi' then arr[2] = 'play' autopip = true end if arr[2] == 'mpvy' then arr[2] = 'play' pipe = true end if arr[2] == 'ytdla' then arr[2] = 'ytdl' audio = true end if referer ~= '' then mp.commandv('set', 'http-header-fields', 'Referer: '..referer) end if proxy ~= false then local ytdlrawoptions = mp.get_property_native('ytdl-raw-options', '') --print(dump(ytdlrawoptions)) ytdlrawoptions['proxy'] = proxy --mp.commandv('set', 'http-proxy', proxy..'/') --mp.commandv('set', 'ytdl-raw-options', 'proxy='..proxy..','..ytdlrawoptions) mp.set_property('http-proxy', proxy..'/') --mp.set_property('ytdl-raw-options', 'proxy='..proxy..','..ytdlrawoptions) mp.set_property_native('ytdl-raw-options', ytdlrawoptions) --local ytdlrawoptions = mp.get_property_native('ytdl-raw-options', '') --print(dump(ytdlrawoptions)) end if arr[2] == 'play' then --local pp = mp.get_property('playlist-pos') --mp.commandv('playlist-remove', pp) if audio == true then mp.commandv('set', 'video', 'no') end if start ~= false then mp.commandv('set', 'start', start) end if ipcMode == false then if pipe == true then piper(url, false, proxy, hls) return end if qs['geometry'] and nogeometry == false then mpv(ourl, referer, proxy, '--geometry='..qs['geometry']) return end if autopip == true then ipc(ourl, 'replace') else for link in string.gmatch(url, "[^%s]+") do if hook_count > 0 then mode = 'replace' end if playlist == false then mp.commandv('loadfile', link, mode) else --mp.commandv('loadlist', link, mode) iptv(url, referer, proxy, hls) end if hook_count > 0 then mp.commandv('loadfile', ourl, 'append') end end end else ipc(ourl, 'append-play') end elseif arr[2] == 'stream' then for link in string.gmatch(url, "[^%s]+") do livestreamer(link, referer, proxy, hls) end --livestreamer(url, referer) elseif arr[2] == 'ytdl' then for link in string.gmatch(url, "[^%s]+") do if audio == false then ytdl(link, referer, 'video') else ytdl(link, referer, 'audio') end end --ytdl(url, referer, 'video') elseif arr[2] == 'mg' then for link in string.gmatch(url, "[^%s]+") do gallery(link, referer) end --ytdl(url, referer, 'audio') elseif qs['app'] then for link in string.gmatch(url, "[^%s]+") do local app = qs['app'] app = atobUrl(app) EA(link, referer, app) end --local app = qs['app'] --app = atobUrl(app) --EA(url, referer, app) end hook_count = hook_count + 1 end end)