QuadraV commited on
Commit
ee4b467
·
verified ·
1 Parent(s): 201a7be

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +610 -18
index.html CHANGED
@@ -1,19 +1,611 @@
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="https://www.funsound.cn/static/logo_whisper.png" type="image/x-icon">
8
+ <title>Funsound音视频转写</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
+ height: 80%;
27
+ border: 1px solid #444;
28
+ border-radius: 8px;
29
+ padding: 20px;
30
+ box-sizing: border-box;
31
+ background-color: #282828;
32
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
33
+ overflow: hidden;
34
+ }
35
+
36
+ .title {
37
+ text-align: center;
38
+ font-size: 2.5rem;
39
+ margin-bottom: 20px;
40
+ color: #ffffff; /* 标题白色 */
41
+ border-bottom: 2px solid #444;
42
+ padding-bottom: 15px;
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ }
47
+
48
+ .title img {
49
+ width: 40px;
50
+ height: 40px;
51
+ margin-right: 15px;
52
+ }
53
+
54
+ .content {
55
+ display: flex;
56
+ height: calc(100% - 50px);
57
+ flex-grow: 1;
58
+ overflow: hidden;
59
+ }
60
+
61
+ .video-container {
62
+ flex: 0 0 40%;
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
+ .asr-container {
73
+ flex: 0 0 60%;
74
+ margin: 10px;
75
+ border: 1px solid #444;
76
+ border-radius: 8px;
77
+ padding: 15px;
78
+ box-sizing: border-box;
79
+ background-color: #333;
80
+ overflow-y: auto;
81
+ position: relative;
82
+ display: flex;
83
+ flex-direction: column;
84
+ }
85
+
86
+ video {
87
+ width: 100%;
88
+ height: 300px;
89
+ background-color: #000;
90
+ border-radius: 8px;
91
+ margin-bottom: 15px;
92
+ }
93
+
94
+ label {
95
+ margin-bottom: 5px;
96
+ font-size: 1rem;
97
+ color: #aaa;
98
+ display: block;
99
+ }
100
+
101
+ .asr-list {
102
+ flex-grow: 1;
103
+ overflow-y: auto;
104
+ background-color: #1c1c1c;
105
+ padding: 10px;
106
+ border-radius: 8px;
107
+ border: 1px solid #444;
108
+ color: #eaeaea;
109
+ margin-bottom: 10px;
110
+ }
111
+
112
+ .asr-item {
113
+ display: flex;
114
+ align-items: center;
115
+ padding: 10px;
116
+ border-bottom: 1px solid #444;
117
+ box-sizing: border-box;
118
+ color: #eaeaea;
119
+ justify-content: space-between;
120
+ }
121
+
122
+ .asr-item label,
123
+ .asr-item input,
124
+ .asr-item select,
125
+ .asr-item button {
126
+ margin: 0 5px;
127
+ }
128
+
129
+ .asr-item .timestamp {
130
+ display: flex;
131
+ align-items: center;
132
+ flex: 0 0 200px;
133
+ text-align: center;
134
+ }
135
+
136
+ .asr-item .timestamp input {
137
+ width: 60px;
138
+ text-align: center;
139
+ background-color: #444;
140
+ color: #eaeaea;
141
+ border: 1px solid #555;
142
+ border-radius: 4px;
143
+ }
144
+
145
+ .asr-item input[type="text"],
146
+ .asr-item select {
147
+ padding: 5px;
148
+ border: 1px solid #555;
149
+ background-color: #444;
150
+ color: #eaeaea;
151
+ width: 100%;
152
+ border-radius: 4px;
153
+ flex: 1;
154
+ }
155
+
156
+ .asr-item input.role-field {
157
+ width: 100px;
158
+ padding: 5px;
159
+ border: 1px solid #555;
160
+ background-color: #444;
161
+ color: #eaeaea;
162
+ border-radius: 4px;
163
+ flex: 0 0 100px; /* 保持宽度固定为 100px */
164
+ text-align: center;
165
+ }
166
+
167
+ .asr-item input[type="checkbox"] {
168
+ margin-right: 5px;
169
+ transform: scale(0.8);
170
+ }
171
+
172
+ .play-button {
173
+ margin: 0 5px;
174
+ padding: 3px 8px;
175
+ background-color: #ff8c00; /* 使用橙色 */
176
+ color: #000;
177
+ border: none;
178
+ cursor: pointer;
179
+ font-size: 0.8rem;
180
+ border-radius: 4px;
181
+ transition: background-color 0.3s;
182
+ white-space: nowrap;
183
+ }
184
+
185
+ .play-button:hover {
186
+ background-color: #e67e00;
187
+ }
188
+
189
+ .upload-button {
190
+ padding: 10px;
191
+ background-color: #007bff; /* 使用蓝色 */
192
+ color: #fff;
193
+ border: none;
194
+ cursor: pointer;
195
+ font-size: 1rem;
196
+ margin-right: 10px;
197
+ border-radius: 4px;
198
+ transition: background-color 0.3s;
199
+ }
200
+
201
+ .upload-button:hover {
202
+ background-color: #0056b3;
203
+ }
204
+
205
+ .file-input-wrapper {
206
+ position: relative;
207
+ overflow: hidden;
208
+ display: inline-block;
209
+ }
210
+
211
+ .file-input {
212
+ font-size: 1rem;
213
+ font-weight: bold;
214
+ color: white;
215
+ background-color: #17a2b8; /* 使用青色 */
216
+ border: none;
217
+ padding: 10px 20px;
218
+ border-radius: 4px;
219
+ cursor: pointer;
220
+ transition: background-color 0.3s;
221
+ }
222
+
223
+ .file-input:hover {
224
+ background-color: #138496;
225
+ }
226
+
227
+ .file-input-wrapper input[type="file"] {
228
+ font-size: 100px;
229
+ position: absolute;
230
+ left: 0;
231
+ top: 0;
232
+ opacity: 0;
233
+ cursor: pointer;
234
+ }
235
+
236
+ .export-button {
237
+ padding: 10px;
238
+ background-color: #28a745; /* 使用绿色 */
239
+ color: #fff;
240
+ border: none;
241
+ cursor: pointer;
242
+ font-size: 1rem;
243
+ border-radius: 4px;
244
+ transition: background-color 0.3s;
245
+ }
246
+
247
+ .export-button:hover {
248
+ background-color: #218838;
249
+ }
250
+
251
+ .buttons-container {
252
+ display: flex;
253
+ justify-content: center;
254
+ margin-bottom: 10px;
255
+ }
256
+
257
+ .progress-bar {
258
+ width: 100%;
259
+ background-color: #444;
260
+ margin-top: 10px;
261
+ border-radius: 4px;
262
+ }
263
+
264
+ .progress-bar div {
265
+ width: 0%;
266
+ background-color: #00ff84; /* 使用绿色 */
267
+ color: #000;
268
+ text-align: center;
269
+ padding: 2px 0;
270
+ border-radius: 4px;
271
+ transition: width 0.3s ease;
272
+ }
273
+
274
+ .center-buttons {
275
+ display: flex;
276
+ justify-content: center;
277
+ gap: 10px;
278
+ margin-top: 20px;
279
+ position: sticky;
280
+ bottom: 0;
281
+ background-color: #333;
282
+ padding: 10px 0;
283
+ border-top: 1px solid #444;
284
+ }
285
+
286
+ footer {
287
+ text-align: center;
288
+ padding: 10px;
289
+ background-color: #1c1c1c;
290
+ color: #888;
291
+ font-size: 0.9rem;
292
+ margin-top: 20px;
293
+ border-top: 1px solid #444;
294
+ width: 100%;
295
+ }
296
+
297
+ footer a {
298
+ color: #00ff84;
299
+ text-decoration: none;
300
+ transition: color 0.3s;
301
+ }
302
+
303
+ footer a:hover {
304
+ color: #00d473;
305
+ }
306
+ </style>
307
+ </head>
308
+
309
+ <body>
310
+ <div class="container">
311
+ <div class="title">
312
+ <img src="https://www.funsound.cn/static/logo_whisper.png" alt="Funsound Logo">
313
+ Funsound语音识别 (Whisper版)
314
+ </div>
315
+ <div class="content">
316
+ <div class="video-container">
317
+ <div class="file-input-wrapper">
318
+ <button class="file-input">选择文件</button>
319
+ <input type="file" id="videoInput" accept=".wav, .mp3, .m4a, .mp4, .aac">
320
+ </div>
321
+
322
+
323
+ <video id="videoPlayer" controls>
324
+ 您的浏览器不支持 video 标签。
325
+ </video>
326
+
327
+ <!-- 新增任务选择单选框 -->
328
+ <div style="margin: 10px 0;">
329
+ <label for="taskType">任务类型:</label>
330
+ <label><input type="radio" name="task" value="transcribe" checked> 识别 (transcribe)</label>
331
+ <label><input type="radio" name="task" value="translate"> 翻译 (translate)</label>
332
+ </div>
333
+
334
+ <div class="buttons-container">
335
+ <button id="uploadBtn" class="upload-button" onclick="uploadFile()">上传并识别</button>
336
+ </div>
337
+ <label>上传进度:</label>
338
+ <div id="uploadProgress" class="progress-bar">
339
+ <div>0%</div>
340
+ </div>
341
+ <label>识别进度:</label>
342
+ <div id="recognitionProgress" class="progress-bar">
343
+ <div>0%</div>
344
+ </div>
345
+ <div id="logContent" style="margin-top: 10px; color: #fff;"></div>
346
+ </div>
347
+ <div class="asr-container">
348
+ <label>识别结果:</label>
349
+ <div id="asrList" class="asr-list"></div>
350
+ <div class="center-buttons">
351
+ <button id="exportJsonBtn" class="export-button" onclick="exportAsrData('json')">导出 JSON</button>
352
+ <button id="exportSrtBtn" class="export-button" onclick="exportAsrData('srt')">导出 SRT</button>
353
+ </div>
354
+ </div>
355
+ </div>
356
+ </div>
357
+
358
+ <footer>
359
+ 联系邮箱: <a href="mailto:[email protected]">[email protected]</a> |
360
+ CSDN: <a href="https://blog.csdn.net/Ephemeroptera" target="_blank">Pika在线</a> |
361
+ Modelscope: <a href="https://modelscope.cn/studios/QuadraV/FunSound">Funsound</a> |
362
+ Funasr版本: <a href="https://www.funsound.cn">Funsound-Funasr</a>
363
+ </footer>
364
+
365
+ <script>
366
+ const serverUrl = "https://www.funsound.cn/whisper";
367
+ let currentTaskId = null;
368
+ let asrData = [];
369
+
370
+ document.getElementById('videoInput').addEventListener('change', function (event) {
371
+ const file = event.target.files[0];
372
+ document.getElementById('uploadBtn').disabled = file.size > 300 * 1024 * 1024;
373
+
374
+ const videoPlayer = document.getElementById('videoPlayer');
375
+ const videoURL = URL.createObjectURL(file);
376
+ videoPlayer.src = videoURL;
377
+ });
378
+
379
+ function uploadFile() {
380
+ const fileInput = document.getElementById('videoInput');
381
+ const file = fileInput.files[0];
382
+
383
+ if (!file) {
384
+ alert('请先选择一个文件');
385
+ return;
386
+ }
387
+
388
+ // 获取选择的任务类型
389
+ const taskType = document.querySelector('input[name="task"]:checked').value;
390
+
391
+ document.getElementById('uploadBtn').disabled = true;
392
+ document.getElementById('uploadBtn').innerText = '转写中...';
393
+
394
+ resetProgress();
395
+
396
+ const xhr = new XMLHttpRequest();
397
+ xhr.open('POST', `${serverUrl}/submit`, true);
398
+ xhr.upload.onprogress = updateUploadProgress;
399
+ xhr.onload = function () {
400
+ if (xhr.status === 200) {
401
+ const response = JSON.parse(xhr.responseText);
402
+ currentTaskId = response.content;
403
+ monitorTaskProgress(currentTaskId);
404
+ } else {
405
+ alert('上传失败,请重试');
406
+ resetUploadButton();
407
+ }
408
+ };
409
+ xhr.onerror = handleUploadError;
410
+
411
+ const formData = new FormData();
412
+ formData.append('file', file);
413
+ formData.append('task', taskType); // 将任务类型添加到请求中
414
+ xhr.send(formData);
415
+ }
416
+
417
+ function resetProgress() {
418
+ document.getElementById('uploadProgress').firstElementChild.style.width = '0%';
419
+ document.getElementById('uploadProgress').firstElementChild.innerText = '';
420
+ document.getElementById('recognitionProgress').firstElementChild.style.width = '0%';
421
+ document.getElementById('recognitionProgress').firstElementChild.innerText = '';
422
+ document.getElementById('asrList').innerHTML = "";
423
+ document.getElementById('logContent').innerText = "";
424
+ }
425
+
426
+ function updateUploadProgress(event) {
427
+ if (event.lengthComputable) {
428
+ const percentComplete = (event.loaded / event.total) * 100;
429
+ document.getElementById('uploadProgress').firstElementChild.style.width = `${percentComplete}%`;
430
+ document.getElementById('uploadProgress').firstElementChild.innerText = `${percentComplete.toFixed(2)}%`;
431
+ }
432
+ }
433
+
434
+ function handleUploadError() {
435
+ alert('上传失败,请重试');
436
+ resetUploadButton();
437
+ }
438
+
439
+ function resetUploadButton() {
440
+ document.getElementById('uploadBtn').disabled = false;
441
+ document.getElementById('uploadBtn').innerText = '上传并识别';
442
+ }
443
+
444
+ function monitorTaskProgress(taskId) {
445
+ let retries = 0;
446
+ const maxRetries = 3600;
447
+
448
+ const intervalId = setInterval(function () {
449
+ const xhr = new XMLHttpRequest();
450
+ xhr.open('GET', `${serverUrl}/task_prgs/${taskId}`, true);
451
+ xhr.onload = function () {
452
+ if (xhr.status === 200) {
453
+ const response = JSON.parse(xhr.responseText);
454
+ const status = response.content.status;
455
+ const progress = response.content.prgs;
456
+
457
+ if (progress) {
458
+ updateRecognitionProgress(progress.cur / progress.total * 100, progress.msg);
459
+ }
460
+
461
+ if (status === "SUCCESS") {
462
+ clearInterval(intervalId);
463
+ asrData = response.content.result;
464
+ displayResults(asrData);
465
+ resetUploadButton();
466
+ } else if (status === "FAIL") {
467
+ clearInterval(intervalId);
468
+ alert('识别任务失败');
469
+ resetUploadButton();
470
+ }
471
+ } else {
472
+ alert('获取进度失败,请重试');
473
+ clearInterval(intervalId);
474
+ resetUploadButton();
475
+ }
476
+ };
477
+ xhr.onerror = function () {
478
+ alert('获取进度失败,请重试');
479
+ clearInterval(intervalId);
480
+ resetUploadButton();
481
+ };
482
+ xhr.send();
483
+
484
+ retries += 1;
485
+ if (retries >= maxRetries) {
486
+ clearInterval(intervalId);
487
+ alert('超出最大重试次数,任务未完成');
488
+ resetUploadButton();
489
+ }
490
+ }, 1000);
491
+ }
492
+
493
+ function updateRecognitionProgress(progress, msg) {
494
+ document.getElementById('recognitionProgress').firstElementChild.style.width = `${progress}%`;
495
+ document.getElementById('recognitionProgress').firstElementChild.innerText = `${progress.toFixed(2)}%`;
496
+ document.getElementById('logContent').innerText = `进度: ${progress.toFixed(2)}%, 状态: ${msg}`;
497
+ }
498
+
499
+ function displayResults(results) {
500
+ const asrList = document.getElementById('asrList');
501
+ asrList.innerHTML = "";
502
+ results.forEach((entry) => {
503
+ const div = document.createElement('div');
504
+ div.className = 'asr-item';
505
+
506
+ div.innerHTML = `
507
+ <div class="timestamp">
508
+ <button class="play-button">播放</button>
509
+ <input type="number" value="${entry.start.toFixed(1)}" step="0.1" min="0" class="start-time">
510
+ -
511
+ <input type="number" value="${entry.end.toFixed(1)}" step="0.1" min="0" class="end-time">
512
+ </div>
513
+
514
+ <input type="text" value="说话人[${entry.role}]" placeholder="角色" class="role-field">
515
+
516
+ <input type="text" value="${entry.text}" placeholder="文本" class="text-field">
517
+
518
+ <label>
519
+ <input type="checkbox" ${entry.drop ? 'checked' : ''}> 丢弃
520
+ </label>
521
+ `;
522
+
523
+ const startInput = div.querySelector('.start-time');
524
+ const endInput = div.querySelector('.end-time');
525
+ const textInput = div.querySelector('input.text-field');
526
+ const dropCheckbox = div.querySelector('input[type="checkbox"]');
527
+ const playButton = div.querySelector('.play-button');
528
+
529
+ startInput.addEventListener('input', () => {
530
+ entry.start = parseFloat(startInput.value);
531
+ });
532
+
533
+ endInput.addEventListener('input', () => {
534
+ entry.end = parseFloat(endInput.value);
535
+ });
536
+
537
+ textInput.addEventListener('input', () => {
538
+ entry.text = textInput.value;
539
+ });
540
+
541
+ dropCheckbox.addEventListener('change', () => {
542
+ entry.drop = dropCheckbox.checked;
543
+ });
544
+
545
+ playButton.addEventListener('click', () => {
546
+ const video = document.getElementById('videoPlayer');
547
+ video.currentTime = entry.start;
548
+ video.play();
549
+ const interval = setInterval(() => {
550
+ if (video.currentTime >= entry.end) {
551
+ video.pause();
552
+ clearInterval(interval);
553
+ }
554
+ }, 100);
555
+ });
556
+
557
+ asrList.appendChild(div);
558
+ });
559
+ }
560
+
561
+ function exportAsrData(format) {
562
+ if (asrData.length === 0) {
563
+ alert('没有数据可以导出');
564
+ return;
565
+ }
566
+
567
+ // 过滤掉被标记为丢弃的条目
568
+ const filteredData = asrData.filter(entry => !entry.drop);
569
+
570
+ let content = '';
571
+ if (format === 'json') {
572
+ content = JSON.stringify(filteredData, null, 2);
573
+ } else if (format === 'srt') {
574
+ filteredData.forEach((entry, index) => {
575
+ content += `${index + 1}\n`;
576
+ const start = formatTime(entry.start);
577
+ const end = formatTime(entry.end);
578
+ content += `${start} --> ${end}\n`;
579
+ content += `${entry.text}\n\n`;
580
+ });
581
+ }
582
+
583
+ const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
584
+ const url = URL.createObjectURL(blob);
585
+ const a = document.createElement('a');
586
+ a.href = url;
587
+ a.download = `result.${format}`;
588
+ document.body.appendChild(a);
589
+ a.click();
590
+ document.body.removeChild(a);
591
+ }
592
+
593
+ function formatTime(seconds) {
594
+ const hours = Math.floor(seconds / 3600);
595
+ const minutes = Math.floor((seconds % 3600) / 60);
596
+ const secs = Math.floor(seconds % 60);
597
+ const millis = Math.floor((seconds - Math.floor(seconds)) * 1000);
598
+ return `${pad(hours)}:${pad(minutes)}:${pad(secs)},${padMillis(millis)}`;
599
+ }
600
+
601
+ function pad(value) {
602
+ return value.toString().padStart(2, '0');
603
+ }
604
+
605
+ function padMillis(value) {
606
+ return value.toString().padStart(3, '0');
607
+ }
608
+ </script>
609
+ </body>
610
+
611
  </html>