QuadraV commited on
Commit
e778aa7
·
verified ·
1 Parent(s): 61e0ec3

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +976 -18
index.html CHANGED
@@ -1,19 +1,977 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <link rel="icon" href="static/logo_whisper.png" type="image/x-icon">
8
+ <title>Funsound Multilingual Speech Translator</title>
9
+ <style>
10
+ body {
11
+ display: flex;
12
+ flex-direction: column;
13
+ justify-content: center;
14
+ align-items: center;
15
+ height: 100vh;
16
+ margin: 0;
17
+ background-color: #1c1c1c;
18
+ color: #eaeaea;
19
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
20
+ }
21
+
22
+ .container {
23
+ display: flex;
24
+ flex-direction: column;
25
+ width: 80%;
26
+ max-width: 1800px; /* 添加最大宽度以避免过宽 */
27
+ min-width: 300px; /* 确保最小宽度以保持可读性 */
28
+ height: auto; /* 高度自适应内容 */
29
+ border: 1px solid #444;
30
+ border-radius: 8px;
31
+ padding: 20px;
32
+ box-sizing: border-box;
33
+ background-color: #282828;
34
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
35
+ overflow: hidden;
36
+ }
37
+
38
+ .title {
39
+ text-align: center;
40
+ font-size: 2.5rem;
41
+ margin-bottom: 20px;
42
+ color: #ffffff;
43
+ border-bottom: 2px solid #444;
44
+ padding-bottom: 15px;
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ }
49
+
50
+ .title img {
51
+ width: 40px;
52
+ height: 40px;
53
+ margin-right: 15px;
54
+ }
55
+
56
+ .content {
57
+ display: flex;
58
+ flex-grow: 1;
59
+ overflow: hidden;
60
+ }
61
+
62
+ .video-container, .asr-container {
63
+ margin: 10px;
64
+ border: 1px solid #444;
65
+ border-radius: 8px;
66
+ padding: 15px;
67
+ box-sizing: border-box;
68
+ background-color: #333;
69
+ overflow: hidden;
70
+ }
71
+
72
+ .video-container {
73
+ flex: 0 0 30%;
74
+ }
75
+
76
+ .asr-container {
77
+ flex: 0 0 70%;
78
+ overflow-y: auto;
79
+ position: relative;
80
+ display: flex;
81
+ flex-direction: column;
82
+ }
83
+
84
+ video {
85
+ width: 100%;
86
+ height: 30%; /* 自适应视频高度 */
87
+ background-color: #000;
88
+ border-radius: 8px;
89
+ margin-bottom: 0px;
90
+ }
91
+
92
+ label {
93
+ margin-bottom: 5px;
94
+ font-size: 1rem;
95
+ color: #aaa;
96
+ display: block;
97
+ }
98
+
99
+ .asr-list {
100
+ flex-grow: 1;
101
+ overflow-y: auto;
102
+ background-color: #1c1c1c;
103
+ padding: 10px;
104
+ border-radius: 8px;
105
+ border: 1px solid #444;
106
+ color: #eaeaea;
107
+ margin-bottom: 10px;
108
+ }
109
+
110
+ .asr-item {
111
+ display: flex;
112
+ align-items: center;
113
+ padding: 10px;
114
+ border-bottom: 1px solid #444;
115
+ box-sizing: border-box;
116
+ color: #eaeaea;
117
+ justify-content: space-between;
118
+ }
119
+
120
+ .asr-item label,
121
+ .asr-item input,
122
+ .asr-item select,
123
+ .asr-item button {
124
+ margin: 0 5px;
125
+ }
126
+
127
+ .asr-item .timestamp {
128
+ display: flex;
129
+ align-items: center;
130
+ flex: 0 0 200px;
131
+ text-align: center;
132
+ }
133
+
134
+ .asr-item .timestamp input {
135
+ width: 60px;
136
+ text-align: center;
137
+ background-color: #444;
138
+ color: #eaeaea;
139
+ border: 1px solid #555;
140
+ border-radius: 4px;
141
+ }
142
+
143
+ .asr-item input[type="text"],
144
+ .asr-item select {
145
+ padding: 5px;
146
+ border: 1px solid #555;
147
+ background-color: #444;
148
+ color: #eaeaea;
149
+ width: 100%;
150
+ border-radius: 4px;
151
+ flex: 1;
152
+ }
153
+
154
+ .asr-item input.role-field {
155
+ width: 70px;
156
+ padding: 5px;
157
+ border: 1px solid #555;
158
+ background-color: #444;
159
+ color: #eaeaea;
160
+ border-radius: 4px;
161
+ flex: 0 0 70px; /* 保持宽度固定为 100px */
162
+ text-align: center;
163
+ }
164
+
165
+ .asr-item input[type="checkbox"] {
166
+ margin-right: 5px;
167
+ transform: scale(0.8);
168
+ }
169
+
170
+ .play-button {
171
+ margin: 0 5px;
172
+ padding: 3px 8px;
173
+ background-color: #ff8c00; /* 使用橙色 */
174
+ color: #000;
175
+ border: none;
176
+ cursor: pointer;
177
+ font-size: 0.8rem;
178
+ border-radius: 4px;
179
+ transition: background-color 0.3s;
180
+ white-space: nowrap;
181
+ }
182
+
183
+ .play-button:hover {
184
+ background-color: #e67e00;
185
+ }
186
+
187
+ .upload-button {
188
+ padding: 10px;
189
+ background-color: #007bff; /* 使用蓝色 */
190
+ color: #fff;
191
+ border: none;
192
+ cursor: pointer;
193
+ font-size: 1rem;
194
+ margin-right: 10px;
195
+ border-radius: 4px;
196
+ transition: background-color 0.3s;
197
+ }
198
+
199
+ .upload-button:hover {
200
+ background-color: #0056b3;
201
+ }
202
+
203
+ .file-input-wrapper {
204
+ position: relative;
205
+ overflow: hidden;
206
+ display: inline-block;
207
+ }
208
+
209
+ .file-input {
210
+ font-size: 1rem;
211
+ font-weight: bold;
212
+ color: white;
213
+ background-color: #17a2b8; /* 使用青色 */
214
+ border: none;
215
+ padding: 10px 20px;
216
+ border-radius: 4px;
217
+ cursor: pointer;
218
+ transition: background-color 0.3s;
219
+ }
220
+
221
+ .file-input:hover {
222
+ background-color: #138496;
223
+ }
224
+
225
+ .file-input-wrapper input[type="file"] {
226
+ font-size: 100px;
227
+ position: absolute;
228
+ left: 0;
229
+ top: 0;
230
+ opacity: 0;
231
+ cursor: pointer;
232
+ }
233
+
234
+ .export-button {
235
+ padding: 10px;
236
+ background-color: #28a745; /* 使用绿色 */
237
+ color: #fff;
238
+ border: none;
239
+ cursor: pointer;
240
+ font-size: 1rem;
241
+ border-radius: 4px;
242
+ transition: background-color 0.3s;
243
+ }
244
+
245
+ .export-button:hover {
246
+ background-color: #218838;
247
+ }
248
+
249
+ .buttons-container {
250
+ display: flex;
251
+ justify-content: center;
252
+ margin-bottom: 10px;
253
+ }
254
+ /* 新增合成按钮样式,使用橙色 */
255
+ .subtitle-button {
256
+ padding: 10px;
257
+ background-color: #ff8c00; /* 使用橙色 */
258
+ color: #000;
259
+ border: none;
260
+ cursor: pointer;
261
+ font-size: 1rem;
262
+ border-radius: 4px;
263
+ transition: background-color 0.3s;
264
+ }
265
+
266
+ .subtitle-button:hover {
267
+ background-color: #e67e00;
268
+ }
269
+
270
+ .progress-bar {
271
+ width: 100%;
272
+ background-color: #444;
273
+ margin-top: 0px;
274
+ border-radius: 4px;
275
+ }
276
+
277
+ .progress-bar div {
278
+ width: 0%;
279
+ background-color: #00ff84; /* 使用绿色 */
280
+ color: #000;
281
+ text-align: center;
282
+ padding: 2px 0;
283
+ border-radius: 4px;
284
+ transition: width 0.3s ease;
285
+ }
286
+
287
+ .center-buttons {
288
+ display: flex;
289
+ justify-content: center;
290
+ gap: 10px;
291
+ margin-top: 20px;
292
+ position: sticky;
293
+ bottom: 0;
294
+ background-color: #333;
295
+ padding: 10px 0;
296
+ border-top: 1px solid #444;
297
+ }
298
+
299
+ footer {
300
+ text-align: center;
301
+ padding: 10px;
302
+ background-color: #1c1c1c;
303
+ color: #888;
304
+ font-size: 0.9rem;
305
+ margin-top: 20px;
306
+ border-top: 1px solid #444;
307
+ width: 100%;
308
+ }
309
+
310
+ footer a {
311
+ color: #00ff84;
312
+ text-decoration: none;
313
+ transition: color 0.3s;
314
+ }
315
+
316
+ footer a:hover {
317
+ color: #00d473;
318
+ }
319
+
320
+ @media (max-width: 768px) {
321
+ .container {
322
+ width: 95%;
323
+ padding: 10px; /* 减小内边距以增加可用空间 */
324
+ }
325
+
326
+ .content {
327
+ flex-direction: column;
328
+ }
329
+
330
+ .video-container, .asr-container {
331
+ flex: 0 0 100%;
332
+ }
333
+
334
+ .title {
335
+ font-size: 1.5rem; /* 缩小标题字体以适应小屏幕 */
336
+ }
337
+
338
+ .asr-item input[type="text"],
339
+ .asr-item select,
340
+ .asr-item input.role-field {
341
+ width: auto; /* 使输入框在小屏幕上更灵活 */
342
+ }
343
+ }
344
+ .server-url-input {
345
+ width: 70%;
346
+ padding: 10px;
347
+ font-size: 1rem;
348
+ color: #eaeaea;
349
+ background-color: #333;
350
+ border: 1px solid #555;
351
+ border-radius: 4px;
352
+ outline: none;
353
+ transition: all 0.3s ease;
354
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
355
+ }
356
+
357
+ .server-url-input:focus {
358
+ border-color: #00ff84; /* 聚焦时边框颜色 */
359
+ box-shadow: 0 0 8px rgba(0, 255, 132, 0.8); /* 添加绿色光晕效果 */
360
+ }
361
+
362
+ .options-container {
363
+ display: flex;
364
+ justify-content: space-around;
365
+ align-items: center;
366
+ }
367
+
368
+
369
+ </style>
370
+ </head>
371
+
372
+ <body>
373
+ <div class="container">
374
+ <div class="title">
375
+ <img src="static/logo_whisper.png" alt="Funsound Logo">
376
+ Funsound Multilingual Speech Translator
377
+ </div>
378
+ <div class="content">
379
+ <!-- 左列 -->
380
+ <div class="video-container">
381
+
382
+ <div class="file-input-wrapper">
383
+ <button class="file-input">Select File</button>
384
+ <input type="file" id="videoInput" accept=".wav, .mp3, .m4a, .mp4, .aac">
385
+ </div>
386
+
387
+ <video id="videoPlayer" controls>
388
+ 您的浏览器不支持 video 标签。
389
+ </video>
390
+
391
+ <div class="options-container">
392
+ <!-- Input Language Selection -->
393
+ <div style="margin: 10px;">
394
+ <label for="language_src">Select language (source):</label>
395
+ <select id="language_src" name="language_src">
396
+ <!-- Options will be populated by JavaScript -->
397
+ </select>
398
+ </div>
399
+
400
+ <!-- Output Language Selection -->
401
+ <div style="margin: 10px;">
402
+ <label for="language_dst">Select language (target):</label>
403
+ <select id="language_dst" name="language_dst">
404
+ <!-- Options will be populated by JavaScript -->
405
+ </select>
406
+ </div>
407
+
408
+ <!-- Speaker Recognition -->
409
+ <div style="margin: 10px;">
410
+ <label><input type="checkbox" id="speakerRecognition">Speaker Identification</label>
411
+ </div>
412
+ </div>
413
+
414
+ <div class="buttons-container">
415
+ <button id="uploadBtn" class="upload-button" onclick="uploadFile()">Recognize</button>
416
+ </div>
417
+ <label>Upload:</label>
418
+ <div id="uploadProgress" class="progress-bar">
419
+ <div>0%</div>
420
+ </div>
421
+ <label>Decoding:</label>
422
+ <div id="recognitionProgress" class="progress-bar">
423
+ <div>0%</div>
424
+ </div>
425
+ <label>Subtitles:</label>
426
+ <div id="subtitleProgress" class="progress-bar">
427
+ <div>0%</div>
428
+ </div>
429
+ <div id="logContent" style="margin-top: 10px; color: #fff;"></div>
430
+ </div>
431
+ <div class="asr-container">
432
+ <label>Results:</label>
433
+ <div id="asrList" class="asr-list"></div>
434
+ <div class="center-buttons">
435
+ <button id="exportJsonBtn" class="export-button" onclick="exportAsrData('json')">Export JSON</button>
436
+ <button id="exportSrtBtn" class="export-button" onclick="exportAsrData('srt')">Export SRT</button>
437
+ <button id="generateSubtitleBtn" class="subtitle-button" onclick="generateSubtitle()">Synthetic Subtitles</button>
438
+ <button id="downloadVideoBtn" style="display: none;" onclick="downloadVideo()">Download Video</button>
439
+ </div>
440
+ </div>
441
+ </div>
442
+ </div>
443
+
444
+ <footer>
445
+ EMAIL: <a href="mailto:[email protected]">[email protected]</a> |
446
+ Modelscope: <a href="https://modelscope.cn/studios/QuadraV/FunSound">Funsound</a>
447
+ </footer>
448
+
449
+ <script>
450
+ let currentTaskId = null; // 当前任务ID
451
+ let asrData = []; // 存储识别结果数据
452
+ let serverUrl = "https://www.funsound.cn/st"; // 服务器地址
453
+ const chunkSize = 1 * 1024 * 1024; // 分块大小,5MB
454
+
455
+ // 监听文件输入更改,预览视频
456
+ document.getElementById('videoInput').addEventListener('change', function (event) {
457
+ const file = event.target.files[0];
458
+ log('Selected file: ' + file.name);
459
+ document.getElementById('uploadBtn').disabled = file.size > 300 * 1024 * 1024;
460
+
461
+ const videoPlayer = document.getElementById('videoPlayer');
462
+ videoPlayer.src = URL.createObjectURL(file);
463
+ log('Video URL: ' + videoPlayer.src);
464
+ });
465
+
466
+
467
+ // 更新支持的语言
468
+ document.addEventListener("DOMContentLoaded", function() {
469
+ // Initialize language dropdowns
470
+ fetch(`${serverUrl}/get_languages`)
471
+ .then(response => response.json())
472
+ .then(data => {
473
+ if (data.code === 0) {
474
+ const languageSrcSelect = document.getElementById('language_src');
475
+ const languageDstSelect = document.getElementById('language_dst');
476
+
477
+ // Populate source languages
478
+ data.content.input_languages.forEach(lang => {
479
+ const option = document.createElement('option');
480
+ option.value = lang;
481
+ option.textContent = lang;
482
+ languageSrcSelect.appendChild(option);
483
+ });
484
+
485
+ // Populate destination languages
486
+ data.content.dst_languages.forEach(lang => {
487
+ const option = document.createElement('option');
488
+ option.value = lang;
489
+ option.textContent = lang;
490
+ languageDstSelect.appendChild(option);
491
+ });
492
+ } else {
493
+ console.error(data.message);
494
+ }
495
+ })
496
+ .catch(error => console.error('Error fetching languages:', error));
497
+
498
+ // Initialize other components if necessary
499
+ initOtherComponents();
500
+ });
501
+
502
+ // 上传文件并提交任务
503
+ function uploadFile() {
504
+ const fileInput = document.getElementById('videoInput');
505
+ const file = fileInput.files[0];
506
+
507
+ if (!file) {
508
+ alert('Select one file please');
509
+ return;
510
+ }
511
+
512
+ // 附加参数
513
+ const languageSrc = document.getElementById('language_src').value;
514
+ const languageDst = document.getElementById('language_dst').value;
515
+ const speakerRecognition = document.getElementById('speakerRecognition').checked;
516
+ log('languageSrc: ' + languageSrc);
517
+ log('languageDst: ' + languageSrc);
518
+ log('Speaker Recognition: ' + speakerRecognition);
519
+
520
+
521
+ document.getElementById('uploadBtn').disabled = true;
522
+ document.getElementById('uploadBtn').innerText = 'Decoding ..';
523
+ resetProgress();
524
+ log('Server URL: ' + serverUrl);
525
+
526
+ initializeTask(file, languageSrc, languageDst, speakerRecognition);
527
+ }
528
+
529
+ // 初始化任务
530
+ function initializeTask(file, languageSrc, languageDst, speakerRecognition) {
531
+ log("Initializing task with filename: " + file.name);
532
+ const formData = new FormData();
533
+ formData.append('status', 'init');
534
+ formData.append('filename', file.name);
535
+ formData.append('totalChunks', Math.ceil(file.size / chunkSize));
536
+ formData.append('language_src', languageSrc);
537
+ formData.append('language_dst', languageDst);
538
+ formData.append('speakerRecognition', speakerRecognition);
539
+
540
+
541
+ const xhr = new XMLHttpRequest();
542
+ xhr.open('POST', `${serverUrl}/submit`, true);
543
+ xhr.onload = function () {
544
+ if (xhr.status === 200) {
545
+ const response = JSON.parse(xhr.responseText);
546
+ log('Initialization response: ' + JSON.stringify(response));
547
+ if (response.code === 0) {
548
+ currentTaskId = response.content;
549
+ log('Task ID: ' + currentTaskId);
550
+ uploadChunks(file); // 开始分块上传
551
+ } else {
552
+ alert('Initialization failed, please try again');
553
+ resetUploadButton();
554
+ }
555
+ } else {
556
+ alert('Initialization failed, please try again');
557
+ resetUploadButton();
558
+ }
559
+ };
560
+ xhr.onerror = handleUploadError;
561
+ xhr.send(formData);
562
+ }
563
+
564
+ // 上传文件块
565
+ function uploadChunks(file) {
566
+ log("Uploading ..");
567
+ const totalChunks = Math.ceil(file.size / chunkSize);
568
+ let chunkIndex = 0;
569
+
570
+ function uploadNextChunk() {
571
+ if (chunkIndex >= totalChunks) {
572
+ log("After uploading, start transcribing..");
573
+ submitASRTask();
574
+ return;
575
+ }
576
+
577
+ const start = chunkIndex * chunkSize;
578
+ const end = Math.min(file.size, start + chunkSize);
579
+ const chunk = file.slice(start, end);
580
+
581
+ const formData = new FormData();
582
+ formData.append('status', 'upload');
583
+ formData.append('task_id', currentTaskId);
584
+ formData.append('ChunkId', chunkIndex);
585
+ formData.append('file', chunk);
586
+
587
+ const xhr = new XMLHttpRequest();
588
+ xhr.open('POST', `${serverUrl}/submit`, true);
589
+ xhr.onload = function () {
590
+ if (xhr.status === 200) {
591
+ const response = JSON.parse(xhr.responseText);
592
+ log(`Chunk ${chunkIndex} upload response: ` + JSON.stringify(response));
593
+ if (response.code === 0) {
594
+ updateUploadProgress(chunkIndex, totalChunks);
595
+ chunkIndex++;
596
+ uploadNextChunk(); // 上传下一个块
597
+ } else {
598
+ alert(`Failed to upload chunk ${chunkIndex + 1}, please try again`);
599
+ resetUploadButton();
600
+ }
601
+ } else {
602
+ alert(`Failed to upload chunk ${chunkIndex + 1}, please try again`);
603
+ resetUploadButton();
604
+ }
605
+ };
606
+ xhr.onerror = handleUploadError;
607
+ xhr.send(formData);
608
+ }
609
+
610
+ uploadNextChunk(); // 开始上传第一个块
611
+ }
612
+
613
+ // 更新上传进度
614
+ function updateUploadProgress(chunkIndex, totalChunks) {
615
+ const totalProgress = ((chunkIndex + 1) / totalChunks) * 100;
616
+ log('Upload progress: ' + totalProgress.toFixed(2) + '%');
617
+ document.getElementById('uploadProgress').firstElementChild.style.width = `${totalProgress}%`;
618
+ document.getElementById('uploadProgress').firstElementChild.innerText = `${totalProgress.toFixed(2)}%`;
619
+ }
620
+
621
+ // 提交 ASR 任务
622
+ function submitASRTask() {
623
+ const formData = new FormData();
624
+ formData.append('status', 'asr');
625
+ formData.append('task_id', currentTaskId);
626
+
627
+ const xhr = new XMLHttpRequest();
628
+ xhr.open('POST', `${serverUrl}/submit`, true);
629
+ xhr.onload = function () {
630
+ if (xhr.status === 200) {
631
+ const response = JSON.parse(xhr.responseText);
632
+ log('ASR submission response: ' + JSON.stringify(response));
633
+ if (response.code === 0) {
634
+ monitorTaskProgress(currentTaskId);
635
+ } else {
636
+ alert('ASR submission failed, please try again');
637
+ resetUploadButton();
638
+ }
639
+ } else {
640
+ alert('ASR submission failed, please try again');
641
+ resetUploadButton();
642
+ }
643
+ };
644
+ xhr.onerror = handleUploadError;
645
+ xhr.send(formData);
646
+ }
647
+
648
+ // 监控任务进度
649
+ function monitorTaskProgress(taskId) {
650
+ log('Monitoring task progress for Task ID: ' + taskId);
651
+ let failedRequests = 0;
652
+ const maxFailedRequests = 10;
653
+
654
+ const intervalId = setInterval(function () {
655
+ const xhr = new XMLHttpRequest();
656
+ xhr.open('GET', `${serverUrl}/task_asr_prgs/${taskId}`, true);
657
+ xhr.onload = function () {
658
+ if (xhr.status === 200) {
659
+ const response = JSON.parse(xhr.responseText);
660
+ log('Progress response: ' + JSON.stringify(response));
661
+ const status = response.content.status;
662
+ const progress = response.content.prgs;
663
+ failedRequests = 0;
664
+
665
+ if (progress) {
666
+ updateRecognitionProgress((progress.cur / progress.total) * 100, progress.msg);
667
+ }
668
+
669
+ if (status === "SUCCESS") {
670
+ clearInterval(intervalId);
671
+ asrData = response.content.result;
672
+ log('Recognition successful, ASR data: ' + JSON.stringify(asrData));
673
+ displayResults(asrData);
674
+ resetUploadButton();
675
+ } else if (status === "FAIL") {
676
+ clearInterval(intervalId);
677
+ alert('识别任务失败');
678
+ resetUploadButton();
679
+ }
680
+ } else {
681
+ handleProgressError();
682
+ }
683
+ };
684
+ xhr.onerror = handleProgressError;
685
+ xhr.send();
686
+ }, 2000);
687
+ }
688
+
689
+ // 处理进度请求错误
690
+ function handleProgressError() {
691
+ failedRequests++;
692
+ log('Request failed, current number of reconnections: ' + failedRequests);
693
+ if (failedRequests >= maxFailedRequests) {
694
+ clearInterval(intervalId);
695
+ alert('Continuous requests failed and the task was not completed.');
696
+ resetUploadButton();
697
+ }
698
+ }
699
+
700
+ // 更新识别进度
701
+ function updateRecognitionProgress(progress, msg) {
702
+ log('Recognition progress: ' + progress + '%, Message: ' + msg);
703
+ document.getElementById('recognitionProgress').firstElementChild.style.width = `${progress}%`;
704
+ document.getElementById('recognitionProgress').firstElementChild.innerText = `${progress.toFixed(2)}%`;
705
+ document.getElementById('logContent').innerText = `progress: ${progress.toFixed(2)}%, status: ${msg}`;
706
+ }
707
+
708
+ // 显示识别结果
709
+ function displayResults(results) {
710
+ log('Displaying ASR results');
711
+ const asrList = document.getElementById('asrList');
712
+ asrList.innerHTML = "";
713
+ results.forEach((entry) => {
714
+ const div = document.createElement('div');
715
+ div.className = 'asr-item';
716
+
717
+ div.innerHTML = `
718
+ <div class="timestamp">
719
+ <button class="play-button">Play</button>
720
+ <input type="number" value="${entry.start.toFixed(1)}" step="0.1" min="0" class="start-time">
721
+ -
722
+ <input type="number" value="${entry.end.toFixed(1)}" step="0.1" min="0" class="end-time">
723
+ </div>
724
+ <input type="text" value="${entry.role}" placeholder="Role" class="role-field">
725
+ <input type="text" value="${entry.text}" placeholder="Text" class="text-field">
726
+ <input type="text" value="${entry.trans}" placeholder="Translation" class="trans-field">
727
+ <label>
728
+ <input type="checkbox" ${entry.drop ? 'checked' : ''}> Drop
729
+ </label>
730
+ `;
731
+
732
+ setupASREventHandlers(div, entry);
733
+ asrList.appendChild(div);
734
+ });
735
+ }
736
+
737
+ // 导出识别结果为 JSON 或 SRT 格式
738
+ function exportAsrData(format) {
739
+ if (asrData.length === 0) {
740
+ alert('No ASR data');
741
+ return;
742
+ }
743
+
744
+ // 过滤掉被标记为丢弃的条目
745
+ const filteredData = asrData.filter(entry => !entry.drop);
746
+
747
+ let content = '';
748
+ if (format === 'json') {
749
+ content = JSON.stringify(filteredData, null, 2);
750
+ } else if (format === 'srt') {
751
+ filteredData.forEach((entry, index) => {
752
+ content += `${index + 1}\n`;
753
+ const start = formatTime(entry.start);
754
+ const end = formatTime(entry.end);
755
+ content += `${start} --> ${end}\n`;
756
+ content += `${entry.text}\n${entry.trans}\n\n`;
757
+ });
758
+ }
759
+
760
+ const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
761
+ const url = URL.createObjectURL(blob);
762
+ const a = document.createElement('a');
763
+ a.href = url;
764
+ a.download = `result.${format}`;
765
+ document.body.appendChild(a);
766
+ a.click();
767
+ document.body.removeChild(a);
768
+ }
769
+
770
+
771
+
772
+ // 设置 ASR 事件处理
773
+ function setupASREventHandlers(div, entry) {
774
+ const startInput = div.querySelector('.start-time');
775
+ const endInput = div.querySelector('.end-time');
776
+ const roleInput = div.querySelector('input.role-field');
777
+ const textInput = div.querySelector('input.text-field');
778
+ const transInput = div.querySelector('input.trans-field');
779
+ const dropCheckbox = div.querySelector('input[type="checkbox"]');
780
+ const playButton = div.querySelector('.play-button');
781
+
782
+ startInput.addEventListener('input', () => entry.start = parseFloat(startInput.value));
783
+ endInput.addEventListener('input', () => entry.end = parseFloat(endInput.value));
784
+ roleInput.addEventListener('input', () => entry.role = roleInput.value);
785
+ textInput.addEventListener('input', () => entry.text = textInput.value);
786
+ transInput.addEventListener('input', () => entry.trans = transInput.value);
787
+ dropCheckbox.addEventListener('change', () => entry.drop = dropCheckbox.checked);
788
+
789
+ playButton.addEventListener('click', () => {
790
+ const video = document.getElementById('videoPlayer');
791
+ video.currentTime = entry.start;
792
+ video.play();
793
+ const interval = setInterval(() => {
794
+ if (video.currentTime >= entry.end) {
795
+ video.pause();
796
+ clearInterval(interval);
797
+ }
798
+ }, 100);
799
+ });
800
+ }
801
+
802
+ // 生成字幕
803
+ function generateSubtitle() {
804
+ if (!currentTaskId || asrData.length === 0) {
805
+ alert('Please make sure the video file is selected and recognized');
806
+ return;
807
+ }
808
+
809
+ const formData = new FormData();
810
+ formData.append('status', "subtitle");
811
+ formData.append('task_id', currentTaskId);
812
+ formData.append('asr_results', JSON.stringify(asrData)); // 将 ASR 数据转为字符串
813
+
814
+ document.getElementById('generateSubtitleBtn').disabled = true;
815
+ document.getElementById('generateSubtitleBtn').innerText = 'generating ...';
816
+
817
+ const xhr = new XMLHttpRequest();
818
+ xhr.open('POST', `${serverUrl}/submit`, true);
819
+ xhr.onload = function () {
820
+ if (xhr.status === 200) {
821
+ const response = JSON.parse(xhr.responseText);
822
+ log('Subtitle generation response: ' + JSON.stringify(response));
823
+ if (response.code === 0) {
824
+ monitorSubtitleGeneration(response.content);
825
+ } else {
826
+ alert('Subtitle generation failed, please try again');
827
+ resetGenerateButton();
828
+ }
829
+ } else {
830
+ alert('Subtitle generation failed, please try again');
831
+ resetGenerateButton();
832
+ }
833
+ };
834
+ xhr.onerror = function () {
835
+ alert('Subtitle generation failed, please try again');
836
+ resetGenerateButton();
837
+ };
838
+ xhr.send(formData);
839
+ }
840
+
841
+ // 监控字幕生成进度
842
+ function monitorSubtitleGeneration(taskId) {
843
+ log('Monitoring subtitle generation for Task ID: ' + taskId);
844
+ let failedRequests = 0;
845
+ const maxFailedRequests = 10;
846
+
847
+ const intervalId = setInterval(function () {
848
+ const xhr = new XMLHttpRequest();
849
+ xhr.open('GET', `${serverUrl}/task_subtitle_prgs/${taskId}`, true);
850
+ xhr.onload = function () {
851
+ if (xhr.status === 200) {
852
+ const response = JSON.parse(xhr.responseText);
853
+ const progress = response.content.progress;
854
+ failedRequests = 0;
855
+
856
+ if (progress !== undefined) {
857
+ updateSubtitleProgress(progress * 100, 'Generating subtitles');
858
+ }
859
+
860
+ if (progress >= 1.0) {
861
+ clearInterval(intervalId);
862
+ document.getElementById('videoPlayer').src = `${serverUrl}/video/${taskId}`;
863
+ document.getElementById('downloadVideoBtn').style.display = 'inline'; // Show the download button
864
+ resetGenerateButton();
865
+ log("After generation, click on the player above to preview");
866
+ }
867
+ } else {
868
+ handleProgressError();
869
+ }
870
+ };
871
+ xhr.onerror = function () {
872
+ handleProgressError();
873
+ };
874
+ xhr.send();
875
+ }, 2000);
876
+ }
877
+
878
+ // 更新字幕生成进度
879
+ function updateSubtitleProgress(progress, msg) {
880
+ log('Subtitle progress: ' + progress + '%, Message: ' + msg);
881
+ document.getElementById('subtitleProgress').firstElementChild.style.width = `${progress}%`;
882
+ document.getElementById('subtitleProgress').firstElementChild.innerText = `${progress.toFixed(2)}%`;
883
+ document.getElementById('logContent').innerText = `progress: ${progress.toFixed(2)}%, status: ${msg}`;
884
+ }
885
+
886
+ // 重置上传按钮状态
887
+ function resetUploadButton() {
888
+ log('Resetting upload button state');
889
+ document.getElementById('uploadBtn').disabled = false;
890
+ document.getElementById('uploadBtn').innerText = 'Recognize';
891
+ }
892
+
893
+ // 重置字幕生成按钮状态
894
+ function resetGenerateButton() {
895
+ document.getElementById('generateSubtitleBtn').disabled = false;
896
+ document.getElementById('generateSubtitleBtn').innerText = 'Subtitles';
897
+ }
898
+
899
+ // 重置进度条和界面
900
+ function resetProgress() {
901
+ log('Resetting progress bars and UI elements');
902
+ document.getElementById('uploadProgress').firstElementChild.style.width = '0%';
903
+ document.getElementById('uploadProgress').firstElementChild.innerText = '';
904
+ document.getElementById('recognitionProgress').firstElementChild.style.width = '0%';
905
+ document.getElementById('recognitionProgress').firstElementChild.innerText = '';
906
+ document.getElementById('subtitleProgress').firstElementChild.style.width = '0%';
907
+ document.getElementById('subtitleProgress').firstElementChild.innerText = '';
908
+ document.getElementById('asrList').innerHTML = "";
909
+ document.getElementById('logContent').innerText = "";
910
+ document.getElementById('downloadVideoBtn').style.display = 'none';
911
+ }
912
+
913
+ // 下载视频
914
+ function downloadVideo() {
915
+ if (!currentTaskId) {
916
+ alert('Please make sure the subtitled video has been generated');
917
+ return;
918
+ }
919
+
920
+ const xhr = new XMLHttpRequest();
921
+ xhr.open('GET', `${serverUrl}/url/${currentTaskId}`, true);
922
+ xhr.onload = function () {
923
+ if (xhr.status === 200) {
924
+ const response = JSON.parse(xhr.responseText);
925
+ if (response.code === 0) {
926
+ const videoUrl = response.content.url;
927
+ const a = document.createElement('a');
928
+ a.href = `${serverUrl}/${videoUrl}`;
929
+ a.download = `subtitle_video_${currentTaskId}.mp4`; // Specify the filename
930
+ document.body.appendChild(a);
931
+ a.click();
932
+ document.body.removeChild(a);
933
+ } else {
934
+ alert('Download link generation failed, please try again');
935
+ }
936
+ } else {
937
+ alert('Download failed, please try again');
938
+ }
939
+ };
940
+ xhr.onerror = function () {
941
+ alert('An error occurred with the download request, please try again');
942
+ };
943
+ xhr.send();
944
+ }
945
+
946
+ // 格式化时间,用于字幕格式化
947
+ function formatTime(seconds) {
948
+ const hours = Math.floor(seconds / 3600);
949
+ const minutes = Math.floor((seconds % 3600) / 60);
950
+ const secs = Math.floor(seconds % 60);
951
+ const millis = Math.floor((seconds - Math.floor(seconds)) * 1000);
952
+ return `${pad(hours)}:${pad(minutes)}:${pad(secs)},${padMillis(millis)}`;
953
+ }
954
+
955
+ function pad(value) {
956
+ return value.toString().padStart(2, '0');
957
+ }
958
+
959
+ function padMillis(value) {
960
+ return value.toString().padStart(3, '0');
961
+ }
962
+ // 错误处理
963
+ function handleUploadError() {
964
+ log('Upload error occurred');
965
+ alert('Upload failed, please try again');
966
+ resetUploadButton();
967
+ }
968
+
969
+ function log(msg) {
970
+ document.getElementById('logContent').innerText = msg;
971
+ console.log(msg);
972
+ }
973
+ </script>
974
+
975
+ </body>
976
+
977
  </html>