146 lines
4.8 KiB
Lua
146 lines
4.8 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'
|
||
|
||
-- 解析多种 time 表示为秒数(支持数字、带小数、HH:MM:SS、MM:SS)
|
||
local function parse_mgtv_time(time_str)
|
||
if time_str == nil then return 0 end
|
||
if type(time_str) == 'number' then return time_str end
|
||
local s = tostring(time_str):gsub('^%s*(.-)%s*$', '%1')
|
||
local n = tonumber(s)
|
||
if n then return n end
|
||
local h, m, sec = s:match('^(%d+):(%d+):([%d%.]+)$')
|
||
if h and m and sec then
|
||
return tonumber(h) * 3600 + tonumber(m) * 60 + tonumber(sec)
|
||
end
|
||
local mm, ss = s:match('^(%d+):([%d%.]+)$')
|
||
if mm and ss then
|
||
return tonumber(mm) * 60 + tonumber(ss)
|
||
end
|
||
return 0
|
||
end
|
||
|
||
-- 生成分段请求列表(每段以 ms 为单位的起点)
|
||
local function generate_mgtv_segments(api_base, total_seconds, step_ms)
|
||
local segments = {}
|
||
local end_time_ms = math.floor(total_seconds * 1000)
|
||
for i = 0, math.max(0, end_time_ms - 1), step_ms do
|
||
table.insert(segments, api_base .. tostring(i))
|
||
end
|
||
return segments
|
||
end
|
||
|
||
-- 构建通用 curl 请求参数
|
||
local function build_mgtv_curl_args(target_url)
|
||
local args = {
|
||
'curl', '-s', '-L', '--compressed',
|
||
'--user-agent', user_agent,
|
||
'-H', 'Accept: application/json',
|
||
target_url,
|
||
}
|
||
return args
|
||
end
|
||
|
||
-- 解析单个分段返回并把弹幕追加到 output_table
|
||
local function parse_mgtv_segment(out, output_table)
|
||
if not out then return end
|
||
local j = utils.parse_json(out)
|
||
if not j or not j.data or not j.data.items then return end
|
||
for _, item in ipairs(j.data.items) do
|
||
local t_ms = tonumber(item.time) or 0
|
||
local time_s = t_ms / 1000
|
||
local content = item.content or ''
|
||
local mode = 1
|
||
local color = 16777215
|
||
local c_param = string.format('%.2f,%d,%d,25,,,', time_s, color, mode)
|
||
table.insert(output_table, { c = c_param, m = content })
|
||
end
|
||
end
|
||
|
||
local function extract_mgtv_ids(path)
|
||
if not path then return nil, nil end
|
||
-- 常见格式: /b/<cid>/<vid>.html
|
||
local cid, vid = path:match('/b/(%d+)/([%w%._-]+)%.html')
|
||
if cid and vid then
|
||
vid = vid:match('([^.]+)') or vid
|
||
return cid, vid
|
||
end
|
||
|
||
-- 回退:取最后两个 path segment
|
||
local segs = {}
|
||
for seg in path:gmatch('/([^/]+)') do table.insert(segs, seg) end
|
||
if #segs >= 2 then
|
||
cid = segs[#segs - 1]
|
||
vid = segs[#segs]
|
||
vid = vid and vid:match('([^.]+)') or nil
|
||
return cid, vid
|
||
end
|
||
return nil, nil
|
||
end
|
||
|
||
-- 为 芒果TV 加载弹幕
|
||
function load_danmaku_for_mgtv(path, callback)
|
||
callback = callback or function() end
|
||
local url = path or mp.get_property('stream-open-filename', '')
|
||
if not url or url == '' then
|
||
msg.error('mgtv: 无效的 url')
|
||
return
|
||
end
|
||
|
||
local cid, vid = extract_mgtv_ids(url)
|
||
if not cid or not vid then
|
||
msg.error('mgtv: 无法解析 cid/vid: ' .. tostring(url))
|
||
return
|
||
end
|
||
|
||
local info_api = 'https://pcweb.api.mgtv.com/video/info?cid=' .. cid .. '&vid=' .. vid
|
||
local api_danmaku_base = 'https://galaxy.bz.mgtv.com/rdbarrage?vid=' .. vid .. '&cid=' .. cid .. '&time='
|
||
|
||
local args = build_mgtv_curl_args(info_api)
|
||
|
||
call_cmd_async(args, function(err, out)
|
||
if err then
|
||
msg.error('mgtv: 请求 video/info 失败: ' .. tostring(err))
|
||
callback(false)
|
||
return
|
||
end
|
||
local data = utils.parse_json(out)
|
||
if not data or data.code ~= 200 or not data.data or not data.data.info then
|
||
msg.info('mgtv: video/info 返回无效')
|
||
callback(false)
|
||
return
|
||
end
|
||
local time_str = data.data.info.time
|
||
local total_seconds = parse_mgtv_time(time_str)
|
||
|
||
local step = 60 * 1000
|
||
local segments = generate_mgtv_segments(api_danmaku_base, total_seconds, step)
|
||
|
||
if #segments == 0 then
|
||
msg.info('mgtv: 未生成任何弹幕分段请求')
|
||
callback(false)
|
||
return
|
||
end
|
||
|
||
local output_table = {}
|
||
|
||
local function per_response_cb(server, err, out)
|
||
if err then
|
||
msg.debug('mgtv segment request failed: ' .. tostring(server) .. ' err: ' .. tostring(err))
|
||
return
|
||
end
|
||
parse_mgtv_segment(out, output_table)
|
||
end
|
||
|
||
local function final_cb()
|
||
local ok = #output_table > 0
|
||
local final_json_str = utils.format_json(output_table)
|
||
save_danmaku_json(url, final_json_str)
|
||
load_danmaku(true)
|
||
callback(ok)
|
||
end
|
||
|
||
parallel_requests(segments, build_mgtv_curl_args, per_response_cb, final_cb, { concurrency = 6, per_request_timeout = 10 })
|
||
end)
|
||
end |