Skip to content

Commit cb8f7e9

Browse files
authored
支持多多视频弹幕转换; 支持调整已有弹幕
1 parent b9d188e commit cb8f7e9

File tree

1 file changed

+157
-21
lines changed

1 file changed

+157
-21
lines changed

HTML/tools/弹幕下载.html

+157-21
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,103 @@
11
<head>
2+
<meta charset="UTF-8">
3+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
24
<title>弹幕下载</title>
5+
<style>
6+
.tabs {
7+
display: flex;
8+
cursor: pointer;
9+
border-bottom: 2px solid #ddd;
10+
margin-bottom: 1rem;
11+
}
12+
.tab {
13+
padding: 10px 20px;
14+
margin-right: 5px;
15+
border: 1px solid #ddd;
16+
border-bottom: none;
17+
background: #f9f9f9;
18+
}
19+
.tab.active {
20+
background: #fff;
21+
font-weight: bold;
22+
}
23+
.tab-content {
24+
display: none;
25+
}
26+
.tab-content.active {
27+
display: block;
28+
}
29+
</style>
330
</head>
431

32+
<!-- Tabs Navigation -->
33+
<div class="tabs">
34+
<div class="tab active" data-tab="download">弹幕下载</div>
35+
<div class="tab" data-tab="adjust">XML 弹幕调整</div>
36+
</div>
537

6-
<form id="form" onsubmit="return false;">
7-
<!--<input id="text" style="width:600px" value="https://v.qq.com/x/cover/mzc002007knmh3g/q0045g3rwbv.html">-->
8-
<input id="text" style="width:600px" value="https://www.iqiyi.com/v_2bh3doltkq4.html">
9-
</input>
10-
<input type="radio" name="api" checked>官方API
11-
<input type="radio" name="api">第三方API
12-
<button onclick="handle(false)">下载ASS</button>
13-
<button onclick="handle(true)">下载XML</button>
14-
</form>
38+
<!-- Tab Content: 弹幕下载 -->
39+
<div id="download" class="tab-content active">
40+
<form id="form" onsubmit="return false;" style="display: flex; flex-direction: column; gap: 0.5rem;">
41+
<div>
42+
<input id="text" style="width:600px;" value="https://v.qq.com/x/cover/mzc002007knmh3g/q0045g3rwbv.html">
43+
<!--<input id="text" style="width:600px" value="https://www.iqiyi.com/v_2bh3doltkq4.html">-->
44+
</div>
45+
<div>
46+
<input type="radio" name="api" checked>官方API
47+
<input type="radio" name="api">第三方API
48+
<input type="radio" name="api">多多视频弹幕转换
49+
</div>
50+
<div>
51+
延迟时间(秒)
52+
<input id="delaySec" style="width:100px" value="0">
53+
<button onclick="handle(false)">下载ASS</button>
54+
<button onclick="handle(true)">下载XML</button>
55+
</div>
56+
</form>
57+
</div>
1558

1659

60+
<!-- Tab Content: XML 弹幕调整 -->
61+
<div id="adjust" class="tab-content">
62+
<form id="adjustForm" onsubmit="return false;" style="display: flex; flex-direction: column; gap: 0.5rem;">
63+
<div>
64+
<label for="xmlFile">上传 XML 弹幕文件:</label>
65+
<input type="file" id="xmlFile" accept=".xml">
66+
</div>
67+
<div>
68+
<label for="adjustTime">延迟时间(秒):</label>
69+
<input id="adjustTime" style="width:100px" value="0">
70+
</div>
71+
<div>
72+
<button onclick="adjustXML()">调整并下载</button>
73+
</div>
74+
</form>
75+
</div>
76+
1777
<br />
1878

1979
<hr />
2080

81+
<script>
82+
// Tab Switching Logic
83+
const tabs = document.querySelectorAll('.tab');
84+
const tabContents = document.querySelectorAll('.tab-content');
85+
86+
tabs.forEach(tab => {
87+
tab.addEventListener('click', () => {
88+
// Remove active class from all tabs and contents
89+
tabs.forEach(t => t.classList.remove('active'));
90+
tabContents.forEach(tc => tc.classList.remove('active'));
91+
92+
// Add active class to the clicked tab and corresponding content
93+
tab.classList.add('active');
94+
document.getElementById(tab.dataset.tab).classList.add('active');
95+
});
96+
});
97+
</script>
2198
<script>
2299
var onlyXML = false;
100+
var delay = 0;
23101

24102
var importJs = async function (jsUrl) {
25103
let jsCode = await fetch(jsUrl).then(resp => resp.text());
@@ -37,15 +115,23 @@
37115
//主要函数
38116
async function handle(f) {
39117
onlyXML = f;
40-
//官方API
41-
if (form.api[0].checked) await handleLocal();
118+
// 检查是否为浮点数
119+
if (isNaN(delaySec.value)) {
120+
alert('请提供有效的时间值!');
121+
return;
122+
}
123+
delay = parseFloat(delaySec.value);
124+
//官方API || 多多视频转换
125+
if (form.api[0].checked || form.api[2].checked) await handleLocal();
42126
//第三方API
43127
else await handleRemote();
44128
}
45129

46130
function downloadDanmu(ds, title, url) {
131+
// 去除出现时间为负数的弹幕
132+
var filteredDs = ds.filter(x => !x.startsWith('<d p="-'));
47133
var xml = String.raw`<?xml version="1.0" encoding="UTF-8"?><i><chatserver>chat.bilibili.com</chatserver><chatid>52175602</chatid><mission>0</mission><maxlimit>10000</maxlimit><state>0</state><real_name>0</real_name><source>k-v</source>`
48-
+ ds.join('') + "</i>";
134+
+ filteredDs.join('') + "</i>";
49135

50136
console.log(xml);
51137
if (onlyXML) {
@@ -60,6 +146,40 @@
60146
}
61147
}
62148

149+
function adjustXML() {
150+
const file = document.getElementById('xmlFile').files[0];
151+
const timeAdjustment = parseFloat(adjustTime.value);
152+
if (!file) {
153+
alert('请上传一个 XML 文件!');
154+
return;
155+
}
156+
// 检查是否为浮点数
157+
if (isNaN(timeAdjustment)) {
158+
alert('请提供有效的时间值!');
159+
return;
160+
}
161+
const reader = new FileReader();
162+
reader.onload = function (event) {
163+
const xmlContent = event.target.result;
164+
// 使用正则表达式调整时间,删除负数记录
165+
const updatedXML = xmlContent.replace(
166+
/<d p="([\d.]+),.*?<\/d>/g,
167+
(match, time) => {
168+
const adjustedTime = parseFloat(time) + timeAdjustment;
169+
return adjustedTime >= 0 ? match.replace(time, adjustedTime.toFixed(3)) : '';
170+
}
171+
);
172+
// 生成下载文件名
173+
const originalName = file.name;
174+
const fileNameWithoutExtension = originalName.substring(0, originalName.lastIndexOf('.'));
175+
const adjustedFileName = `${fileNameWithoutExtension}_adjusted.xml`;
176+
// 下载修改后的 XML
177+
startDownload('\ufeff' + updatedXML, adjustedFileName);
178+
};
179+
// 读取文件内容
180+
reader.readAsText(file);
181+
}
182+
63183
///////////////////////////IQY START
64184

65185
async function get_tvid(url) {
@@ -91,10 +211,10 @@
91211
for (let entry of enrties) {
92212
const [bulletInfo1, bulletInfo2] = entry.getElementsByTagName("bulletInfo");
93213
for (let bi of [bulletInfo1, bulletInfo2].filter(x => x)) {
94-
const start = bi.getElementsByTagName("showTime")[0].textContent;
214+
const start = parseFloat(bi.getElementsByTagName("showTime")[0].textContent) + delay;
95215
const content = bi.getElementsByTagName("content")[0].textContent;
96216
const color = parseInt("0x" + bi.getElementsByTagName("color")[0].textContent);
97-
list.push(`<d p="${start},1,25,${color},1659282294,0,8b53b65c,1108899274487246080,10"><![CDATA[${content}]]></d>`);
217+
list.push(`<d p="${start.toFixed(3)},1,25,${color},1659282294,0,8b53b65c,1108899274487246080,10"><![CDATA[${content}]]></d>`);
98218
}
99219
}
100220
return list.join('');
@@ -128,8 +248,8 @@
128248
let tmp = JSON.parse(x.content_style);
129249
if (tmp.gradient_colors) color = parseInt("0x" + tmp.gradient_colors[0]);
130250
}
131-
let start = Number(x.time_offset) / 1000.0;
132-
list.push(`<d p="${start},1,25,${color},1659282294,0,8b53b65c,1108899274487246080,10"><![CDATA[${content}]]></d>`);
251+
let start = (parseFloat(x.time_offset) / 1000.0) + delay;
252+
list.push(`<d p="${start.toFixed(3)},1,25,${color},1659282294,0,8b53b65c,1108899274487246080,10"><![CDATA[${content}]]></d>`);
133253
});
134254
return list.join('');
135255
});
@@ -139,14 +259,30 @@
139259
///////////////////////////QQ END
140260

141261

262+
///////////////////////////DuoDuo START
263+
async function invoke_duoduo(url) {
264+
const color = "16777215";
265+
const jsonArray = await fetch(url).then(x => x.json());
266+
const vid = url.split('/').pop();
267+
const ds = jsonArray.map(x => {
268+
const start = parseFloat(x.p.split(',').shift()) + delay;
269+
const content = x.d;
270+
return `<d p="${start.toFixed(3)},1,25,${color},1659282294,0,8b53b65c,1108899274487246080,10"><![CDATA[${content}]]></d>`;
271+
});
272+
downloadDanmu(ds, vid, url);
273+
}
274+
///////////////////////////DuoDuo END
142275

143276
async function handleLocal() {
144277
var url = text.value;
145278
document.body.appendChild(document.createTextNode("开始下载:" + url));
146-
if (url.indexOf("qq.com") > 0) {
279+
if (url.includes("qq.com")) {
147280
await invoke_qq(url);
148-
} else if (url.indexOf("iqiyi.com") > 0) {
281+
} else if (url.includes("iqiyi.com")) {
149282
await invoke_iqiyi(url);
283+
} else if (url.includes("static-dm.frogsport.top/v1/produce/danmu")) {
284+
// https://static-dm.frogsport.top/v1/produce/danmu/EPISODE/346213
285+
await invoke_duoduo(url);
150286
} else {
151287
alert('不支持');
152288
}
@@ -160,9 +296,9 @@
160296
var json = await fetch("https://dmku.thefilehosting.com/?ac=dm&url=" + url).then(x => x.json());
161297
var list = [];
162298
Array.from(json.danmuku).forEach(x => {
163-
let start = Number(x[0]);
299+
let start = parseFloat(x[0]) + delay;
164300
let content = x[4];
165-
list.push(`<d p="${start},1,25,16777215,1659282294,0,8b53b65c,1108899274487246080,10"><![CDATA[${content}]]></d>`)
301+
list.push(`<d p="${start.toFixed(3)},1,25,16777215,1659282294,0,8b53b65c,1108899274487246080,10"><![CDATA[${content}]]></d>`)
166302
});
167303
var xml = String.raw`<?xml version="1.0" encoding="UTF-8"?><i><chatserver>chat.bilibili.com</chatserver><chatid>52175602</chatid><mission>0</mission><maxlimit>1000</maxlimit><state>0</state><real_name>0</real_name><source>k-v</source>`
168304
+ list.join('') + "</i>";
@@ -180,4 +316,4 @@
180316
document.body.appendChild(document.createTextNode(" 完成"));
181317
document.body.appendChild(document.createElement("hr"));
182318
}
183-
</script>
319+
</script>

0 commit comments

Comments
 (0)