246 lines
8.1 KiB
Lua
246 lines
8.1 KiB
Lua
local msg = require('mp.msg')
|
||
local utils = require("mp.utils")
|
||
|
||
local user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
|
||
|
||
local function build_curl_args(url, extra_headers)
|
||
local args = {
|
||
'curl', '-L', '-s', '--compressed', '--user-agent', user_agent
|
||
}
|
||
extra_headers = extra_headers or {}
|
||
for _, h in ipairs(extra_headers) do
|
||
table.insert(args, '-H')
|
||
table.insert(args, h)
|
||
end
|
||
if options.cookie_file and options.cookie_file ~= '' then
|
||
table.insert(args, '-b')
|
||
table.insert(args, mp.command_native({'expand-path', options.cookie_file}))
|
||
end
|
||
if options.proxy and options.proxy ~= '' then
|
||
table.insert(args, '-x')
|
||
table.insert(args, options.proxy)
|
||
end
|
||
table.insert(args, url)
|
||
return args
|
||
end
|
||
|
||
local function get_cid()
|
||
local cid, danmaku_id = nil, nil
|
||
local tracks = mp.get_property_native("track-list")
|
||
for _, track in ipairs(tracks) do
|
||
if track["lang"] == "danmaku" then
|
||
cid = track["external-filename"]:match("/(%d-)%.xml$")
|
||
danmaku_id = track["id"]
|
||
break
|
||
end
|
||
end
|
||
return cid, danmaku_id
|
||
end
|
||
|
||
local function get_bilibili_id_and_page(path)
|
||
local bvid, aid, page = nil, nil, nil
|
||
if not bvid then
|
||
bvid = path:match("/video/(BV[%w]+)")
|
||
or path:match("[?&]bvid=(BV[%w]+)")
|
||
end
|
||
if not aid then
|
||
aid = path:match("/video/av([%d]+)")
|
||
end
|
||
if not page then
|
||
page = tonumber(path:match("[?&]p=(%d+)"))
|
||
end
|
||
return bvid, aid, page or 1
|
||
end
|
||
|
||
local function get_bilibili_pagelist_args(bvid, aid)
|
||
local url
|
||
if bvid ~= nil then
|
||
url = "https://api.bilibili.com/x/player/pagelist?bvid=" .. bvid
|
||
else
|
||
url = "https://api.bilibili.com/x/player/pagelist?aid=" .. aid
|
||
end
|
||
local headers = {"Referer: https://www.bilibili.com/video/" .. (bvid or ("av" .. aid))}
|
||
return build_curl_args(url, headers)
|
||
end
|
||
|
||
local function resolve_bilibili_cid(path, callback)
|
||
-- 扩展支持:普通视频(BV/av)、番剧(ep)、课程(cheese)
|
||
local api_bangumi_season = "https://api.bilibili.com/pgc/view/web/season"
|
||
local api_cheese_season = "https://api.bilibili.com/pugv/view/web/season"
|
||
|
||
local q = path
|
||
-- 解析普通投稿视频
|
||
local bvid, aid, p = get_bilibili_id_and_page(q)
|
||
if bvid or aid then
|
||
local args = get_bilibili_pagelist_args(bvid, aid)
|
||
call_cmd_async(args, function(error, json)
|
||
if error then
|
||
msg.warn("Failed to request bilibili pagelist: " .. tostring(error))
|
||
callback(nil)
|
||
return
|
||
end
|
||
local data = utils.parse_json(json)
|
||
local pages = data and data["data"]
|
||
if type(pages) ~= "table" then
|
||
callback(nil)
|
||
return
|
||
end
|
||
local page_info = pages[p] or pages[1]
|
||
local cid = page_info and page_info["cid"]
|
||
local part = page_info and page_info["part"]
|
||
callback(cid and tostring(cid) or nil, part and tostring(part) or nil)
|
||
end)
|
||
return
|
||
end
|
||
|
||
-- 番剧、番外等(含 ep)
|
||
if q:find("bangumi/") and q:find("ep") then
|
||
local epid = q:match("ep(%d+)") or q:match("ep(%d+)$")
|
||
if not epid then
|
||
callback(nil)
|
||
return
|
||
end
|
||
local url = api_bangumi_season .. "?ep_id=" .. epid
|
||
local arg = build_curl_args(url)
|
||
call_cmd_async(arg, function(error, json)
|
||
if error then
|
||
msg.warn("Failed to request bilibili bangumi info: " .. tostring(error))
|
||
callback(nil)
|
||
return
|
||
end
|
||
local data = utils.parse_json(json)
|
||
if not data or data.code ~= 0 or not data.result then
|
||
msg.warn("bilibili bangumi api returned error")
|
||
callback(nil)
|
||
return
|
||
end
|
||
-- 查找正片
|
||
local episodes = data.result.episodes or {}
|
||
for _, ep in ipairs(episodes) do
|
||
if tostring(ep.id) == tostring(epid) then
|
||
callback(tostring(ep.cid), tostring(ep.share_copy or ep.title or ""))
|
||
return
|
||
end
|
||
end
|
||
-- 查找 section(花絮等)
|
||
if type(data.result.section) == "table" then
|
||
for _, sec in ipairs(data.result.section) do
|
||
if sec.episodes then
|
||
for _, ep in ipairs(sec.episodes) do
|
||
if tostring(ep.id) == tostring(epid) then
|
||
callback(tostring(ep.cid), tostring(ep.share_copy or ep.title or ""))
|
||
return
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
callback(nil)
|
||
end)
|
||
return
|
||
end
|
||
|
||
-- cheese 课程
|
||
if q:find("cheese/") and q:find("ep") then
|
||
local epid = q:match("ep(%d+)") or q:match("ep(%d+)$")
|
||
if not epid then
|
||
callback(nil)
|
||
return
|
||
end
|
||
local url = api_cheese_season .. "?ep_id=" .. epid
|
||
local arg = build_curl_args(url)
|
||
call_cmd_async(arg, function(error, json)
|
||
if error then
|
||
msg.warn("Failed to request bilibili cheese info: " .. tostring(error))
|
||
callback(nil)
|
||
return
|
||
end
|
||
local data = utils.parse_json(json)
|
||
if not data or data.code ~= 0 or not data.data then
|
||
msg.warn("bilibili cheese api returned error")
|
||
callback(nil)
|
||
return
|
||
end
|
||
local episodes = data.data.episodes or {}
|
||
for _, ep in ipairs(episodes) do
|
||
if tostring(ep.id) == tostring(epid) then
|
||
callback(tostring(ep.cid), tostring(ep.title or ""))
|
||
return
|
||
end
|
||
end
|
||
callback(nil)
|
||
end)
|
||
return
|
||
end
|
||
|
||
-- 其它情况返回 nil
|
||
callback(nil)
|
||
end
|
||
|
||
local function download_bilibili_danmaku(path, cid, from_menu, callback)
|
||
local url = "https://comment.bilibili.com/" .. cid .. ".xml"
|
||
local args = build_curl_args(url)
|
||
|
||
call_cmd_async(args, function(error, out)
|
||
if error then
|
||
show_message("HTTP request failed, see console for details", 5)
|
||
msg.error(error)
|
||
callback(false)
|
||
return
|
||
end
|
||
if not out or out == '' then
|
||
callback(false)
|
||
return
|
||
end
|
||
save_danmaku_xml(path, out)
|
||
load_danmaku(from_menu == nil and true or from_menu)
|
||
callback(true)
|
||
end)
|
||
end
|
||
|
||
-- 为 bilibli 网站的视频播放加载弹幕
|
||
function load_danmaku_for_bilibili(path, callback)
|
||
callback = callback or function() end
|
||
local cid, danmaku_id = get_cid()
|
||
if danmaku_id ~= nil then
|
||
mp.commandv('sub-remove', danmaku_id)
|
||
end
|
||
|
||
if cid == nil then
|
||
cid = mp.get_opt('cid')
|
||
if not cid then
|
||
local patterns = {
|
||
"bilivideo%.c[nom]+.*/resource/(%d+)%D+.*",
|
||
"bilivideo%.c[nom]+.*/(%d+)-%d+-%d+%..*%?",
|
||
}
|
||
local urls = {
|
||
path,
|
||
mp.get_property("stream-open-filename", ''),
|
||
}
|
||
|
||
for _, pattern in ipairs(patterns) do
|
||
for _, url in ipairs(urls) do
|
||
if url:find(pattern) then
|
||
cid = url:match(pattern)
|
||
break
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if cid == nil then
|
||
resolve_bilibili_cid(path, function(resolved_cid)
|
||
if resolved_cid then
|
||
download_bilibili_danmaku(path, resolved_cid, true, callback)
|
||
else
|
||
show_message("获取哔哩哔哩视频cid失败", 3)
|
||
msg.error("获取哔哩哔哩视频cid失败")
|
||
callback(false)
|
||
end
|
||
end)
|
||
return
|
||
end
|
||
if cid ~= nil then
|
||
download_bilibili_danmaku(path, cid, true, callback)
|
||
end
|
||
end |