Files
mpv-config/scripts/uosc_danmaku/sites/tencentvideo.lua
T
2026-08-15 17:50:48 +02:00

157 lines
4.9 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'
-- 将 URL 中的百分号编码解码为字符
local function normalize_url(path)
if not path then return '' end
return (path:gsub('%%(%x%x)', hex_to_char))
end
-- 从 URL 提取 vid
local function extract_vid(url)
if not url then return nil end
local vid = url:match('[?&]vid=([^&?#]+)')
if not vid then
local last = nil
for seg in url:gmatch('/([^/?#]+)') do
last = seg
end
if last then
vid = last:match('([^%.]+)')
end
end
return vid
end
-- 构造 curl 请求参数(通用)
local function build_curl_args(target_url)
local args = {
'curl',
'-L',
'-s',
'--compressed',
'--user-agent',
user_agent,
target_url,
}
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
return args
end
-- 解析单个 segment 返回并把弹幕追加到 output_table
local function parse_segment_to_output(seg_json, output_table)
if not seg_json or not seg_json['barrage_list'] then return end
for _, item in ipairs(seg_json['barrage_list']) do
local time = tonumber(item['time_offset']) and tonumber(item['time_offset']) / 1000 or 0
local color = 16777215
if item['content_style'] and item['content_style']['color'] then
local col = item['content_style']['color']
if type(col) == 'string' and col:match('^#') then
color = hex_to_int_color(col)
end
end
local mode = 1
local c_param = string.format('%s,%s,%s,25,,,', time, color, mode)
table.insert(output_table, {c = c_param, m = item['content'] or ''})
end
end
-- 保存并加载最终弹幕 JSON
local function save_output_and_load(output_table, source_url)
if #output_table == 0 then
show_message('未获取到任何弹幕', 3)
return
end
local final_json_str = utils.format_json(output_table)
save_danmaku_json(source_url, final_json_str)
load_danmaku(true)
end
-- 为 腾讯视频 加载弹幕
function load_danmaku_for_tencent(path, callback)
callback = callback or function() end
local url = normalize_url(path)
if not url or url == '' then
url = mp.get_property('stream-open-filename', '')
end
local vid = extract_vid(url)
if not vid then
msg.error('无法从 URL 中解析 vid: ' .. tostring(url))
callback(false)
return
end
local api_base = 'https://dm.video.qq.com/barrage/base/' .. vid
local api_segment_base = 'https://dm.video.qq.com/barrage/segment/' .. vid .. '/'
local base_args = build_curl_args(api_base)
call_cmd_async(base_args, function(err, out)
if err then
msg.error('请求腾讯弹幕 base 失败: ' .. tostring(err))
callback(false)
return
end
local base_json = utils.parse_json(out)
if not base_json or not base_json['segment_index'] then
show_message('好像没有弹幕哦', 3)
callback(false)
return
end
-- 构造 segment 请求列表
local segments = {}
local seg_index = base_json['segment_index']
if type(seg_index) == 'table' then
for k, v in pairs(seg_index) do
local seg_name = nil
if type(v) == 'table' and v['segment_name'] then
seg_name = v['segment_name']
elseif type(k) == 'string' then
seg_name = k
end
if seg_name then
table.insert(segments, api_segment_base .. seg_name)
end
end
end
if #segments == 0 then
show_message('没有找到弹幕分段', 3)
callback(false)
return
end
local output_table = {}
local function build_args_fn(server)
return build_curl_args(server)
end
local function per_response_cb(server, err, out)
if err then
msg.warn('请求段失败: ' .. tostring(server) .. ' 错误: ' .. tostring(err))
return
end
local seg_json = utils.parse_json(out)
parse_segment_to_output(seg_json, output_table)
end
local function final_cb()
local ok = #output_table > 0
save_output_and_load(output_table, url)
callback(ok)
end
-- 并行请求 segments
parallel_requests(segments, build_args_fn, per_response_cb, final_cb, {concurrency = 6, per_request_timeout = 15})
end)
end