930 lines
29 KiB
Lua
930 lines
29 KiB
Lua
VERSION = "2.0.0"
|
||
|
||
mp.commandv('script-message', 'uosc_danmaku-version', VERSION)
|
||
|
||
local msg = require('mp.msg')
|
||
local utils = require("mp.utils")
|
||
|
||
AES = require("modules/aes")
|
||
Base64 = require("modules/base64")
|
||
MD5 = require("modules/md5")
|
||
Sha256 = require("modules/hash")
|
||
|
||
require("modules/options")
|
||
require("modules/utils")
|
||
require("modules/parse")
|
||
require("modules/guess")
|
||
require('modules/render')
|
||
require('modules/menu')
|
||
require("modules/update")
|
||
|
||
require("apis/dandanplay")
|
||
require('apis/extra')
|
||
|
||
DANMAKU_PATH = os.getenv("TEMP") or "/tmp/"
|
||
HISTORY_PATH = mp.command_native({"expand-path", options.history_path})
|
||
PID = utils.getpid()
|
||
DANMAKU = {sources = {}, count = 1}
|
||
DELAYS = {}
|
||
ENABLED, COMMENTS, DELAY = false, nil, 0
|
||
DELAY_PROPERTY = string.format("user-data/%s/danmaku-delay", mp.get_script_name())
|
||
mp.set_property_native(DELAY_PROPERTY, 0)
|
||
|
||
KEY = table_to_zero_indexed({
|
||
0x00,0x01,0x02,0x03,0x04,
|
||
0x05,0x06,0x07,0x08,0x09,
|
||
0x0a,0x0b,0x0c,0x0d,0x0e,
|
||
0x0f,0x10,0x11,0x12,0x13,
|
||
0x14,0x15,0x16,0x17,0x18,
|
||
0x19,0x1a,0x1b,0x1c,0x1d,
|
||
0x1e,0x1f
|
||
})
|
||
|
||
PLATFORM = (function()
|
||
local platform = mp.get_property_native("platform")
|
||
if platform then
|
||
if itable_index_of({ "windows", "darwin" }, platform) then
|
||
return platform
|
||
end
|
||
else
|
||
if os.getenv("windir") ~= nil then
|
||
return "windows"
|
||
end
|
||
local homedir = os.getenv("HOME")
|
||
if homedir ~= nil and string.sub(homedir, 1, 6) == "/Users" then
|
||
return "darwin"
|
||
end
|
||
end
|
||
return "linux"
|
||
end)()
|
||
|
||
function get_danmaku_visibility()
|
||
local history_json = read_file(HISTORY_PATH)
|
||
local history
|
||
if history_json ~= nil then
|
||
history = utils.parse_json(history_json) or {}
|
||
local flag = history["show_danmaku"]
|
||
if flag == nil then
|
||
history["show_danmaku"] = false
|
||
write_json_file(HISTORY_PATH, history)
|
||
else
|
||
return flag
|
||
end
|
||
else
|
||
history = {}
|
||
history["show_danmaku"] = false
|
||
write_json_file(HISTORY_PATH, history)
|
||
end
|
||
return false
|
||
end
|
||
|
||
function set_danmaku_visibility(flag)
|
||
local history = {}
|
||
local history_json = read_file(HISTORY_PATH)
|
||
if history_json ~= nil then
|
||
history = utils.parse_json(history_json) or {}
|
||
end
|
||
history["show_danmaku"] = flag
|
||
write_json_file(HISTORY_PATH, history)
|
||
end
|
||
|
||
function set_danmaku_button()
|
||
if get_danmaku_visibility() then
|
||
mp.commandv("script-message-to", "uosc", "set", "show_danmaku", "on")
|
||
end
|
||
end
|
||
|
||
function show_loaded(init)
|
||
if DANMAKU.anime and DANMAKU.episode then
|
||
show_message("匹配内容:" .. DANMAKU.anime .. "-" .. DANMAKU.episode .. "\\N弹幕加载成功,共计" .. #COMMENTS .. "条弹幕", 3)
|
||
if init then
|
||
msg.info(DANMAKU.anime .. "-" .. DANMAKU.episode .. " 弹幕加载成功,共计" .. #COMMENTS .. "条弹幕")
|
||
end
|
||
else
|
||
show_message("弹幕加载成功,共计" .. #COMMENTS .. "条弹幕", 3)
|
||
end
|
||
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 extract_between_colons(input_string)
|
||
local start_index = 0
|
||
local end_index = 0
|
||
local count = 0
|
||
for i = 1, #input_string do
|
||
if input_string:sub(i, i) == ":" then
|
||
count = count + 1
|
||
if count == 2 then
|
||
start_index = i
|
||
elseif count == 3 then
|
||
end_index = i
|
||
break
|
||
end
|
||
end
|
||
end
|
||
if start_index > 0 and end_index > 0 then
|
||
return input_string:sub(start_index + 1, end_index - 1)
|
||
else
|
||
return nil
|
||
end
|
||
end
|
||
|
||
local function hex_to_int_color(hex_color)
|
||
-- 移除颜色代码中的'#'字符
|
||
hex_color = hex_color:sub(2) -- 只保留颜色代码部分
|
||
|
||
-- 提取R, G, B的十六进制值并转为整数
|
||
local r = tonumber(hex_color:sub(1, 2), 16)
|
||
local g = tonumber(hex_color:sub(3, 4), 16)
|
||
local b = tonumber(hex_color:sub(5, 6), 16)
|
||
|
||
-- 计算32位整数值
|
||
local color_int = (r * 256 * 256) + (g * 256) + b
|
||
|
||
return color_int
|
||
end
|
||
|
||
local function get_type_from_position(position)
|
||
if position == 0 then
|
||
return 1
|
||
end
|
||
if position == 1 then
|
||
return 4
|
||
end
|
||
return 5
|
||
end
|
||
|
||
-- 获取指定时间的延迟
|
||
-- 返回该时间点之前所有延迟段的总和
|
||
function get_delay_for_time(delay_segments, time)
|
||
if not delay_segments or #delay_segments == 0 then return 0 end
|
||
|
||
table.sort(delay_segments, function(a, b) return a.start < b.start end)
|
||
|
||
local applied_delay = 0
|
||
for i = 1, #delay_segments do
|
||
local seg = delay_segments[i]
|
||
local delay = tonumber(seg.delay)
|
||
if time >= seg.start and delay then
|
||
applied_delay = applied_delay + delay
|
||
else
|
||
break
|
||
end
|
||
end
|
||
return applied_delay
|
||
end
|
||
|
||
local function merge_delay_segments(segments)
|
||
if not segments or #segments == 0 then return {} end
|
||
|
||
local NEAREST_THRESHOLD = 10 -- 最邻近段合并阈值
|
||
local MERGE_THRESHOLD = 30 -- 跨段合并阈值
|
||
local EPSILON = 1e-6 -- 判断接近 0 的阈值
|
||
|
||
table.sort(segments, function(a, b) return a.start < b.start end)
|
||
|
||
local partially_merged = {}
|
||
local i = 1
|
||
while i <= #segments do
|
||
local cur = segments[i]
|
||
local next_seg = segments[i + 1]
|
||
|
||
if next_seg and (next_seg.start - cur.start) <= NEAREST_THRESHOLD then
|
||
local combined_delay = tonumber(cur.delay) + tonumber(next_seg.delay)
|
||
if math.abs(combined_delay) > EPSILON then
|
||
table.insert(partially_merged, {
|
||
start = cur.start,
|
||
delay = combined_delay
|
||
})
|
||
end
|
||
i = i + 2
|
||
else
|
||
if math.abs(tonumber(cur.delay)) > EPSILON then
|
||
table.insert(partially_merged, cur)
|
||
end
|
||
i = i + 1
|
||
end
|
||
end
|
||
|
||
local merged = {}
|
||
for _, seg in ipairs(partially_merged) do
|
||
local merged_flag = false
|
||
for idx, m in ipairs(merged) do
|
||
if math.abs(seg.start - m.start) <= MERGE_THRESHOLD then
|
||
m.delay = tonumber(m.delay) + tonumber(seg.delay)
|
||
if math.abs(m.delay) <= EPSILON then
|
||
table.remove(merged, idx)
|
||
end
|
||
merged_flag = true
|
||
break
|
||
end
|
||
end
|
||
if not merged_flag then
|
||
if math.abs(tonumber(seg.delay)) > EPSILON then
|
||
table.insert(merged, {
|
||
start = seg.start,
|
||
delay = seg.delay
|
||
})
|
||
end
|
||
end
|
||
end
|
||
|
||
table.sort(merged, function(a, b) return a.start < b.start end)
|
||
return merged
|
||
end
|
||
|
||
local function set_danmaku_delay(dly, time)
|
||
for url, source in pairs(DANMAKU.sources) do
|
||
if source.fname and not source.blocked then
|
||
source.delay_segments = source.delay_segments or {}
|
||
if dly == 0 then
|
||
source.delay_segments = {}
|
||
elseif time then
|
||
table.insert(source.delay_segments, {start = time, delay = dly})
|
||
else
|
||
table.insert(source.delay_segments, {start = 0, delay = dly})
|
||
end
|
||
|
||
source.delay = nil
|
||
table.sort(source.delay_segments, function(a, b) return a.start < b.start end)
|
||
add_source_to_history(url, source)
|
||
end
|
||
end
|
||
|
||
if time then
|
||
table.insert(DELAYS, {start = time, delay = dly})
|
||
else
|
||
table.insert(DELAYS, {start = 0, delay = dly})
|
||
end
|
||
|
||
if dly == 0 then
|
||
DELAY = 0
|
||
DELAYS = {}
|
||
else
|
||
DELAY = DELAY + dly
|
||
end
|
||
|
||
DELAYS = merge_delay_segments(DELAYS)
|
||
|
||
if ENABLED and COMMENTS ~= nil then
|
||
render()
|
||
end
|
||
|
||
show_message('设置弹幕延迟: ' .. string.format("%.1f", DELAY + 1e-10) .. ' s')
|
||
mp.set_property_native(DELAY_PROPERTY, DELAY)
|
||
end
|
||
|
||
local function clear_source()
|
||
local path = mp.get_property("path")
|
||
local history_json = read_file(HISTORY_PATH)
|
||
|
||
if not path or not history_json then return end
|
||
|
||
local history = utils.parse_json(history_json) or {}
|
||
if history[path] == nil then return end
|
||
|
||
history[path] = nil
|
||
write_json_file(HISTORY_PATH, history)
|
||
|
||
for url, source in pairs(DANMAKU.sources) do
|
||
if source.from == "user_custom" then
|
||
if source.fname and file_exists(source.fname) then
|
||
os.remove(source.fname)
|
||
end
|
||
DANMAKU.sources[url] = nil
|
||
end
|
||
end
|
||
|
||
load_danmaku(false)
|
||
|
||
show_message("已重置当前视频所有弹幕源更改", 3)
|
||
msg.verbose("已重置当前视频所有弹幕源更改")
|
||
end
|
||
|
||
function write_history(episodeid)
|
||
local history = {}
|
||
local path = mp.get_property("path")
|
||
local dir = get_parent_directory(path)
|
||
local fname = mp.get_property('filename/no-ext')
|
||
local episodeNumber = 0
|
||
if episodeid then
|
||
episodeNumber = tonumber(episodeid) % 1000
|
||
elseif DANMAKU.extra then
|
||
episodeNumber = DANMAKU.extra.episodenum
|
||
end
|
||
|
||
if is_protocol(path) then
|
||
local title, season_num, episod_num = parse_title()
|
||
if title and episod_num then
|
||
if season_num then
|
||
dir = title .." Season".. season_num
|
||
else
|
||
dir = title
|
||
end
|
||
fname = url_decode(mp.get_property("media-title"))
|
||
episodeNumber = episod_num
|
||
end
|
||
end
|
||
|
||
if dir ~= nil then
|
||
local history_json = read_file(HISTORY_PATH)
|
||
if history_json ~= nil then
|
||
history = utils.parse_json(history_json) or {}
|
||
end
|
||
history[dir] = {}
|
||
history[dir].fname = fname
|
||
history[dir].source = DANMAKU.source
|
||
history[dir].animeTitle = DANMAKU.anime
|
||
history[dir].episodeTitle = DANMAKU.episode
|
||
history[dir].episodeNumber = episodeNumber
|
||
if episodeid then
|
||
history[dir].episodeId = episodeid
|
||
elseif DANMAKU.extra then
|
||
history[dir].extra = DANMAKU.extra
|
||
end
|
||
write_json_file(HISTORY_PATH, history)
|
||
end
|
||
end
|
||
|
||
function remove_source_from_history(rm_source)
|
||
local history_json = read_file(HISTORY_PATH)
|
||
local path = mp.get_property("path")
|
||
|
||
if is_protocol(path) then
|
||
path = remove_query(path)
|
||
end
|
||
|
||
if history_json then
|
||
local history = utils.parse_json(history_json) or {}
|
||
|
||
if history[path] ~= nil and history[path]["sources"] ~= nil then
|
||
for source in pairs(history[path]["sources"]) do
|
||
if source == rm_source then
|
||
history[path]["sources"][source] = nil
|
||
break
|
||
end
|
||
end
|
||
end
|
||
|
||
write_json_file(HISTORY_PATH, history)
|
||
end
|
||
end
|
||
|
||
function add_source_to_history(add_url, add_source)
|
||
local history_json = read_file(HISTORY_PATH)
|
||
local path = mp.get_property("path")
|
||
|
||
if is_protocol(path) then
|
||
path = remove_query(path)
|
||
end
|
||
|
||
local history = {}
|
||
if history_json then
|
||
history = utils.parse_json(history_json) or {}
|
||
end
|
||
|
||
history[path] = history[path] or {}
|
||
history[path]["sources"] = history[path]["sources"] or {}
|
||
history[path]["sources"][add_url] = history[path]["sources"][add_url] or {}
|
||
|
||
local record = history[path]["sources"][add_url]
|
||
record.from = add_source.from or "user_custom"
|
||
record.blocked = add_source.blocked or false
|
||
|
||
local delay_segments = shallow_copy(add_source.delay_segments or {})
|
||
if #delay_segments > 0 then
|
||
record.delay_segments = merge_delay_segments(delay_segments)
|
||
if #record.delay_segments == 0 then
|
||
record.delay_segments = nil
|
||
end
|
||
else
|
||
record.delay_segments = nil
|
||
end
|
||
|
||
record.delay = nil
|
||
write_json_file(HISTORY_PATH, history)
|
||
end
|
||
|
||
function read_danmaku_source_record(path)
|
||
if is_protocol(path) then
|
||
path = remove_query(path)
|
||
end
|
||
|
||
local history_json = read_file(HISTORY_PATH)
|
||
if not history_json then return end
|
||
|
||
local history = utils.parse_json(history_json) or {}
|
||
local record = history[path]
|
||
if not record or not record.sources then return end
|
||
|
||
local sources = record.sources
|
||
local upgraded_sources = {}
|
||
|
||
if is_nested_table(sources) then
|
||
for source, data in pairs(sources) do
|
||
local from = data.from or "user_custom"
|
||
local blocked = data.blocked or false
|
||
local delay_segments = shallow_copy(data.delay_segments or {})
|
||
if data.delay ~= nil then
|
||
for i = #delay_segments, 1, -1 do
|
||
if delay_segments[i].start == 0 then
|
||
table.remove(delay_segments, i)
|
||
end
|
||
end
|
||
table.insert(delay_segments, 1, { start = 0, delay = tonumber(data.delay) })
|
||
end
|
||
if #delay_segments > 0 then
|
||
delay_segments = merge_delay_segments(delay_segments)
|
||
else
|
||
delay_segments = nil
|
||
end
|
||
|
||
DANMAKU.sources[source] = {
|
||
from = from,
|
||
blocked = blocked,
|
||
delay_segments = delay_segments,
|
||
from_history = true,
|
||
}
|
||
end
|
||
else
|
||
for _, raw in ipairs(sources) do
|
||
local source = raw
|
||
local blocked = false
|
||
local from = raw:match("<(.-)>")
|
||
local delay = raw:match("{{(.-)}}")
|
||
|
||
source = source:gsub("<.->", ""):gsub("{{.-}}", "")
|
||
|
||
if source:match("^%-") then
|
||
source = source:sub(2)
|
||
blocked = true
|
||
from = from or "api_server"
|
||
end
|
||
|
||
local delay_segments = nil
|
||
if delay ~= nil then
|
||
delay_segments = {
|
||
{ start = 0, delay = tonumber(delay) }
|
||
}
|
||
end
|
||
|
||
DANMAKU.sources[source] = {
|
||
from = from or "user_custom",
|
||
blocked = blocked,
|
||
delay_segments = delay_segments,
|
||
from_history = true,
|
||
}
|
||
|
||
upgraded_sources[source] = shallow_copy(DANMAKU.sources[source])
|
||
end
|
||
|
||
if next(upgraded_sources) then
|
||
record.sources = upgraded_sources
|
||
write_json_file(HISTORY_PATH, history)
|
||
end
|
||
end
|
||
end
|
||
|
||
-- 收集现有的弹幕文件和延迟记录
|
||
local function collect_danmaku_sources()
|
||
local danmaku_input = {}
|
||
local delays = {}
|
||
|
||
for _, source in pairs(DANMAKU.sources) do
|
||
if not source.blocked and source.fname then
|
||
if not file_exists(source.fname) then
|
||
show_message("未找到弹幕文件", 3)
|
||
msg.info("未找到弹幕文件")
|
||
return
|
||
end
|
||
table.insert(danmaku_input, source.fname)
|
||
|
||
if source.delay_segments and #source.delay_segments > 0 then
|
||
table.insert(delays, source.delay_segments)
|
||
end
|
||
end
|
||
end
|
||
|
||
return danmaku_input, delays
|
||
end
|
||
|
||
-- 视频播放时保存弹幕
|
||
function save_danmaku(not_forced)
|
||
local danmaku_input, delays = collect_danmaku_sources()
|
||
if #danmaku_input == 0 then
|
||
show_message("弹幕内容为空,无法保存", 3)
|
||
msg.verbose("弹幕内容为空,无法保存")
|
||
COMMENTS = {}
|
||
return
|
||
end
|
||
|
||
local path = mp.get_property("path")
|
||
local dir = get_parent_directory(path) or ""
|
||
local filename = mp.get_property('filename/no-ext')
|
||
local danmaku_out = utils.join_path(dir, filename .. ".xml")
|
||
-- 排除网络播放场景
|
||
if not path or is_protocol(path) or (not file_exists(danmaku_out)
|
||
and not is_writable(danmaku_out)) then
|
||
show_message("此弹幕文件不支持保存至本地")
|
||
msg.warn("此弹幕文件不支持保存至本地")
|
||
else
|
||
if not_forced and file_exists(danmaku_out) then
|
||
show_message("已存在同名弹幕文件:" .. danmaku_out)
|
||
msg.info("已存在同名弹幕文件:" .. danmaku_out)
|
||
return
|
||
else
|
||
convert_danmaku_to_xml(danmaku_input, danmaku_out, delays)
|
||
end
|
||
end
|
||
end
|
||
|
||
-- 加载弹幕
|
||
function load_danmaku(from_menu, no_osd)
|
||
if not ENABLED then return end
|
||
local temp_file = "danmaku-" .. PID .. ".ass"
|
||
local danmaku_file = utils.join_path(DANMAKU_PATH, temp_file)
|
||
local danmaku_input, delays = collect_danmaku_sources()
|
||
-- 如果没有弹幕文件,退出加载
|
||
if #danmaku_input == 0 then
|
||
show_message("该集弹幕内容为空,结束加载", 3)
|
||
msg.verbose("该集弹幕内容为空,结束加载")
|
||
COMMENTS = {}
|
||
return
|
||
end
|
||
|
||
convert_danmaku_format(danmaku_input, danmaku_file, delays)
|
||
parse_danmaku(danmaku_file, from_menu, no_osd)
|
||
end
|
||
|
||
-- 为 bilibli 网站的视频播放加载弹幕
|
||
function load_danmaku_for_bilibili(path)
|
||
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 and path:match("/video/BV.-") then
|
||
if path:match("video/BV.-/.*") then
|
||
path = path:gsub("/[^/]+$", "")
|
||
end
|
||
add_danmaku_source_online(path, true)
|
||
return
|
||
end
|
||
if cid ~= nil then
|
||
local url = "https://comment.bilibili.com/" .. cid .. ".xml"
|
||
local temp_file = "danmaku-" .. PID .. DANMAKU.count .. ".xml"
|
||
local danmaku_xml = utils.join_path(DANMAKU_PATH, temp_file)
|
||
DANMAKU.count = DANMAKU.count + 1
|
||
local arg = {
|
||
"curl",
|
||
"-L",
|
||
"-s",
|
||
"--compressed",
|
||
"--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 Edg/124.0.0.0",
|
||
"--output",
|
||
danmaku_xml,
|
||
url,
|
||
}
|
||
|
||
call_cmd_async(arg, function(error)
|
||
async_running = false
|
||
if error then
|
||
show_message("HTTP 请求失败,打开控制台查看详情", 5)
|
||
msg.error(error)
|
||
return
|
||
end
|
||
if file_exists(danmaku_xml) then
|
||
save_danmaku_downloaded(path, danmaku_xml)
|
||
load_danmaku(true)
|
||
end
|
||
end)
|
||
end
|
||
end
|
||
|
||
-- 为 bahamut 网站的视频播放加载弹幕
|
||
function load_danmaku_for_bahamut(path)
|
||
local path = path:gsub('%%(%x%x)', hex_to_char)
|
||
local sn = extract_between_colons(path)
|
||
if sn == nil then
|
||
return
|
||
end
|
||
local url = "https://ani.gamer.com.tw/ajax/danmuGet.php"
|
||
local temp_file = "bahamut-" .. PID .. ".json"
|
||
local danmaku_json = utils.join_path(DANMAKU_PATH, temp_file)
|
||
local arg = {
|
||
"curl",
|
||
"-X",
|
||
"POST",
|
||
"-d",
|
||
"sn=" .. sn,
|
||
"-L",
|
||
"-s",
|
||
"--user-agent",
|
||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36",
|
||
"--header",
|
||
"Origin: https://ani.gamer.com.tw",
|
||
"--header",
|
||
"Content-Type: application/x-www-form-urlencoded;charset=utf-8",
|
||
"--header",
|
||
"Accept: application/json",
|
||
"--header",
|
||
"Authority: ani.gamer.com.tw",
|
||
"--output",
|
||
danmaku_json,
|
||
url,
|
||
}
|
||
|
||
if options.proxy ~= "" then
|
||
table.insert(arg, '-x')
|
||
table.insert(arg, options.proxy)
|
||
end
|
||
|
||
call_cmd_async(arg, function(error)
|
||
async_running = false
|
||
if error then
|
||
show_message("HTTP 请求失败,打开控制台查看详情", 5)
|
||
msg.error(error)
|
||
return
|
||
end
|
||
if not file_exists(danmaku_json) then
|
||
url = "https://ani.gamer.com.tw/animeVideo.php?sn=" .. sn
|
||
ENABLED = true
|
||
add_danmaku_source_online(url, true)
|
||
return
|
||
end
|
||
|
||
local comments_json = read_file(danmaku_json)
|
||
local comments = utils.parse_json(comments_json)
|
||
if not comments then
|
||
return
|
||
end
|
||
|
||
temp_file = "danmaku-" .. PID .. DANMAKU.count .. ".json"
|
||
local json_filename = utils.join_path(DANMAKU_PATH, temp_file)
|
||
DANMAKU.count = DANMAKU.count + 1
|
||
local json_file = io.open(json_filename, "w")
|
||
|
||
if json_file then
|
||
json_file:write("[\n")
|
||
for _, comment in ipairs(comments) do
|
||
local m = comment["text"]
|
||
local color = hex_to_int_color(comment["color"])
|
||
local mode = get_type_from_position(comment["position"])
|
||
local time = tonumber(comment["time"]) / 10
|
||
local c = time .. "," .. color .. "," .. mode .. ",25,,,"
|
||
|
||
-- Write the JSON object as a single line, no spaces or extra formatting
|
||
local json_entry = string.format('{"c":"%s","m":"%s"},\n', c, m)
|
||
json_file:write(json_entry)
|
||
end
|
||
json_file:write("]")
|
||
json_file:close()
|
||
end
|
||
|
||
if file_exists(json_filename) then
|
||
save_danmaku_downloaded(
|
||
"https://ani.gamer.com.tw/animeVideo.php?sn=" .. sn,
|
||
json_filename)
|
||
load_danmaku(true)
|
||
end
|
||
end)
|
||
end
|
||
|
||
function load_danmaku_for_url(path)
|
||
if path:find('bilibili.com') or path:find('bilivideo.c[nom]+') then
|
||
load_danmaku_for_bilibili(path)
|
||
return
|
||
end
|
||
|
||
if path:find('bahamut.akamaized.net') then
|
||
load_danmaku_for_bahamut(path)
|
||
return
|
||
end
|
||
|
||
local title, season_num, episod_num = parse_title()
|
||
local filename = url_decode(mp.get_property("media-title"))
|
||
local episod_number = nil
|
||
if title and episod_num then
|
||
if season_num then
|
||
dir = title .." Season".. season_num
|
||
episod_number = episod_num
|
||
else
|
||
dir = title
|
||
end
|
||
auto_load_danmaku(path, dir, filename, episod_number)
|
||
addon_danmaku(dir, false)
|
||
return
|
||
end
|
||
get_danmaku_with_hash(filename, path)
|
||
addon_danmaku()
|
||
end
|
||
|
||
-- 自动加载上次匹配的弹幕
|
||
function auto_load_danmaku(path, dir, filename, number)
|
||
if dir ~= nil then
|
||
local history_json = read_file(HISTORY_PATH)
|
||
if history_json ~= nil then
|
||
local history = utils.parse_json(history_json) or {}
|
||
-- 1.判断父文件名是否存在
|
||
local history_dir = history[dir]
|
||
if history_dir ~= nil then
|
||
--2.如果存在,则获取number和id
|
||
DANMAKU.anime = history_dir.animeTitle
|
||
local episode_number = history_dir.episodeTitle and history_dir.episodeTitle:match("%d+")
|
||
local history_number = history_dir.episodeNumber
|
||
local history_id = history_dir.episodeId
|
||
local history_fname = history_dir.fname
|
||
local history_extra = history_dir.extra
|
||
local playing_number = nil
|
||
|
||
if history_fname then
|
||
if filename ~= history_fname then
|
||
if number then
|
||
playing_number = number
|
||
else
|
||
history_number, playing_number = get_episode_number(filename, history_fname)
|
||
end
|
||
else
|
||
playing_number = history_number
|
||
end
|
||
else
|
||
playing_number = get_episode_number(filename)
|
||
end
|
||
if playing_number ~= nil then
|
||
local x = playing_number - history_number --获取集数差值
|
||
DANMAKU.episode = episode_number and string.format("第%s话", episode_number + x) or history_dir.episodeTitle
|
||
show_message("自动加载上次匹配的弹幕", 3)
|
||
msg.verbose("自动加载上次匹配的弹幕")
|
||
if history_id then
|
||
local tmp_id = tostring(x + history_id)
|
||
set_episode_id(tmp_id)
|
||
elseif history_extra then
|
||
local episodenum = history_extra.episodenum + x
|
||
get_details(history_extra.class, history_extra.id, history_extra.site,
|
||
history_extra.title, history_extra.year, history_extra.number, episodenum)
|
||
end
|
||
else
|
||
get_danmaku_with_hash(filename, path)
|
||
end
|
||
else
|
||
get_danmaku_with_hash(filename, path)
|
||
end
|
||
else
|
||
get_danmaku_with_hash(filename, path)
|
||
end
|
||
end
|
||
end
|
||
|
||
function init(path)
|
||
if not path then return end
|
||
local dir = get_parent_directory(path)
|
||
local filename = mp.get_property('filename/no-ext')
|
||
local video = mp.get_property_native("current-tracks/video")
|
||
local duration = mp.get_property_number("duration", 0)
|
||
if not video or video["image"] or video["albumart"] or duration < 60 then
|
||
msg.info("不支持的播放内容(非视频)")
|
||
return
|
||
end
|
||
if is_protocol(path) then
|
||
load_danmaku_for_url(path)
|
||
end
|
||
if dir and filename then
|
||
local danmaku_xml = utils.join_path(dir, filename .. ".xml")
|
||
if file_exists(danmaku_xml) then
|
||
add_danmaku_source_local(danmaku_xml, true)
|
||
else
|
||
auto_load_danmaku(path, dir, filename)
|
||
addon_danmaku(dir, true)
|
||
end
|
||
end
|
||
end
|
||
|
||
mp.register_event("file-loaded", function()
|
||
local path = mp.get_property("path")
|
||
local dir = get_parent_directory(path)
|
||
local filename = mp.get_property('filename/no-ext')
|
||
local video = mp.get_property_native("current-tracks/video")
|
||
local fps = mp.get_property_number("container-fps", 0)
|
||
local duration = mp.get_property_number("duration", 0)
|
||
if not video or video["image"] or video["albumart"] or fps < 23 or duration < 60 then
|
||
return
|
||
end
|
||
|
||
read_danmaku_source_record(path)
|
||
|
||
if not get_danmaku_visibility() then
|
||
return
|
||
end
|
||
|
||
if options.autoload_for_url and is_protocol(path) then
|
||
ENABLED = true
|
||
load_danmaku_for_url(path)
|
||
end
|
||
|
||
if filename == nil or dir == nil then
|
||
return
|
||
end
|
||
local danmaku_xml = utils.join_path(dir, filename .. ".xml")
|
||
if options.autoload_local_danmaku then
|
||
if file_exists(danmaku_xml) then
|
||
ENABLED = true
|
||
add_danmaku_source_local(danmaku_xml)
|
||
return
|
||
end
|
||
end
|
||
|
||
if options.auto_load then
|
||
ENABLED = true
|
||
auto_load_danmaku(path, dir, filename)
|
||
addon_danmaku(dir, false)
|
||
return
|
||
end
|
||
|
||
if ENABLED and COMMENTS == nil and not async_running then
|
||
init(path)
|
||
end
|
||
end)
|
||
|
||
-------------- 键位绑定 --------------
|
||
mp.add_key_binding(options.open_search_danmaku_menu_key, "open_search_danmaku_menu", function()
|
||
mp.commandv("script-message", "open_search_danmaku_menu")
|
||
end)
|
||
mp.add_key_binding(options.show_danmaku_keyboard_key, "show_danmaku_keyboard", function()
|
||
mp.commandv("script-message", "show_danmaku_keyboard")
|
||
end)
|
||
|
||
mp.register_script_message("danmaku-delay", function(...)
|
||
local commands = {...}
|
||
local delay_str, time_str = commands[1], commands[2]
|
||
local dly = tonumber(delay_str)
|
||
local time = time_str and tonumber(time_str)
|
||
if type(dly) ~= "number" then
|
||
show_message("参数错误:缺少有效的延迟秒数", 3)
|
||
return
|
||
end
|
||
set_danmaku_delay(dly, time)
|
||
end)
|
||
|
||
mp.register_script_message("show_danmaku_keyboard", function()
|
||
ENABLED = not ENABLED
|
||
if ENABLED then
|
||
mp.commandv("script-message-to", "uosc", "set", "show_danmaku", "on")
|
||
set_danmaku_visibility(true)
|
||
if COMMENTS == nil then
|
||
show_message("加载弹幕初始化...", 3)
|
||
local path = mp.get_property("path")
|
||
init(path)
|
||
else
|
||
show_loaded()
|
||
show_danmaku_func()
|
||
end
|
||
else
|
||
show_message("关闭弹幕", 2)
|
||
mp.commandv("script-message-to", "uosc", "set", "show_danmaku", "off")
|
||
set_danmaku_visibility(false)
|
||
hide_danmaku_func()
|
||
end
|
||
end)
|
||
|
||
mp.register_script_message("check-update", check_for_update)
|
||
mp.register_script_message("clear-source", clear_source)
|
||
mp.register_script_message("immediately_save_danmaku", save_danmaku)
|
||
mp.register_script_message("open_source_delay_menu", danmaku_delay_setup)
|
||
mp.register_script_message("open_search_danmaku_menu", open_input_menu)
|
||
mp.register_script_message("open_add_source_menu", open_add_menu)
|
||
mp.register_script_message("open_add_total_menu", open_add_total_menu)
|