1
1
< head >
2
+ < meta charset ="UTF-8 ">
3
+ < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
2
4
< 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 >
3
30
</ head >
4
31
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 >
5
37
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 >
15
58
16
59
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
+
17
77
< br />
18
78
19
79
< hr />
20
80
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 >
21
98
< script >
22
99
var onlyXML = false ;
100
+ var delay = 0 ;
23
101
24
102
var importJs = async function ( jsUrl ) {
25
103
let jsCode = await fetch ( jsUrl ) . then ( resp => resp . text ( ) ) ;
37
115
//主要函数
38
116
async function handle ( f ) {
39
117
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 ( ) ;
42
126
//第三方API
43
127
else await handleRemote ( ) ;
44
128
}
45
129
46
130
function downloadDanmu ( ds , title , url ) {
131
+ // 去除出现时间为负数的弹幕
132
+ var filteredDs = ds . filter ( x => ! x . startsWith ( '<d p="-' ) ) ;
47
133
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>" ;
49
135
50
136
console . log ( xml ) ;
51
137
if ( onlyXML ) {
60
146
}
61
147
}
62
148
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
+
63
183
///////////////////////////IQY START
64
184
65
185
async function get_tvid ( url ) {
91
211
for ( let entry of enrties ) {
92
212
const [ bulletInfo1 , bulletInfo2 ] = entry . getElementsByTagName ( "bulletInfo" ) ;
93
213
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 ;
95
215
const content = bi . getElementsByTagName ( "content" ) [ 0 ] . textContent ;
96
216
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>` ) ;
98
218
}
99
219
}
100
220
return list . join ( '' ) ;
128
248
let tmp = JSON . parse ( x . content_style ) ;
129
249
if ( tmp . gradient_colors ) color = parseInt ( "0x" + tmp . gradient_colors [ 0 ] ) ;
130
250
}
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>` ) ;
133
253
} ) ;
134
254
return list . join ( '' ) ;
135
255
} ) ;
139
259
///////////////////////////QQ END
140
260
141
261
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
142
275
143
276
async function handleLocal ( ) {
144
277
var url = text . value ;
145
278
document . body . appendChild ( document . createTextNode ( "开始下载:" + url ) ) ;
146
- if ( url . indexOf ( "qq.com" ) > 0 ) {
279
+ if ( url . includes ( "qq.com" ) ) {
147
280
await invoke_qq ( url ) ;
148
- } else if ( url . indexOf ( "iqiyi.com" ) > 0 ) {
281
+ } else if ( url . includes ( "iqiyi.com" ) ) {
149
282
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 ) ;
150
286
} else {
151
287
alert ( '不支持' ) ;
152
288
}
160
296
var json = await fetch ( "https://dmku.thefilehosting.com/?ac=dm&url=" + url ) . then ( x => x . json ( ) ) ;
161
297
var list = [ ] ;
162
298
Array . from ( json . danmuku ) . forEach ( x => {
163
- let start = Number ( x [ 0 ] ) ;
299
+ let start = parseFloat ( x [ 0 ] ) + delay ;
164
300
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>` )
166
302
} ) ;
167
303
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>`
168
304
+ list . join ( '' ) + "</i>" ;
180
316
document . body . appendChild ( document . createTextNode ( " 完成" ) ) ;
181
317
document . body . appendChild ( document . createElement ( "hr" ) ) ;
182
318
}
183
- </ script >
319
+ </ script >
0 commit comments