QuadraV commited on
Commit
a3299b5
·
verified ·
1 Parent(s): c8ecb43

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +284 -61
index.html CHANGED
@@ -4,8 +4,8 @@
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;
@@ -83,10 +83,10 @@
83
 
84
  video {
85
  width: 100%;
86
- height: 40%; /* 自适应视频高度 */
87
  background-color: #000;
88
  border-radius: 8px;
89
- margin-bottom: 15px;
90
  }
91
 
92
  label {
@@ -251,11 +251,26 @@
251
  justify-content: center;
252
  margin-bottom: 10px;
253
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
  .progress-bar {
256
  width: 100%;
257
  background-color: #444;
258
- margin-top: 10px;
259
  border-radius: 4px;
260
  }
261
 
@@ -326,14 +341,38 @@
326
  width: auto; /* 使输入框在小屏幕上更灵活 */
327
  }
328
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  </style>
330
  </head>
331
 
332
  <body>
333
  <div class="container">
334
  <div class="title">
335
- <img src="https://www.funsound.cn/static/logo_whisper.png" alt="Funsound Logo">
336
- Funsound语音识别 (Whisper版)
337
  </div>
338
  <div class="content">
339
  <div class="video-container">
@@ -347,11 +386,26 @@
347
  您的浏览器不支持 video 标签。
348
  </video>
349
 
350
- <!-- 相关任务栏 -->
351
  <div style="margin: 10px 0;">
352
- <label>附加任务(勾选):</label>
353
- <label><input type="checkbox" id="speakerRecognition"> 说话人识别</label>
354
- <label><input type="checkbox" id="englishTranslation"> 中英翻译</label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  </div>
356
 
357
  <div class="buttons-container">
@@ -365,6 +419,10 @@
365
  <div id="recognitionProgress" class="progress-bar">
366
  <div>0%</div>
367
  </div>
 
 
 
 
368
  <div id="logContent" style="margin-top: 10px; color: #fff;"></div>
369
  </div>
370
  <div class="asr-container">
@@ -373,6 +431,8 @@
373
  <div class="center-buttons">
374
  <button id="exportJsonBtn" class="export-button" onclick="exportAsrData('json')">导出 JSON</button>
375
  <button id="exportSrtBtn" class="export-button" onclick="exportAsrData('srt')">导出 SRT</button>
 
 
376
  </div>
377
  </div>
378
  </div>
@@ -382,23 +442,27 @@
382
  联系邮箱: <a href="mailto:[email protected]">[email protected]</a> |
383
  CSDN: <a href="https://blog.csdn.net/Ephemeroptera" target="_blank">Pika在线</a> |
384
  Modelscope: <a href="https://modelscope.cn/studios/QuadraV/FunSound">Funsound</a> |
385
- Funasr版本: <a href="https://www.funsound.cn">Funsound-Funasr</a>
386
  </footer>
387
 
388
  <script>
389
- const serverUrl = "https://www.funsound.cn/whisper";
390
- let currentTaskId = null;
391
- let asrData = [];
392
-
 
393
  document.getElementById('videoInput').addEventListener('change', function (event) {
394
  const file = event.target.files[0];
 
395
  document.getElementById('uploadBtn').disabled = file.size > 300 * 1024 * 1024;
396
-
397
  const videoPlayer = document.getElementById('videoPlayer');
398
  const videoURL = URL.createObjectURL(file);
 
399
  videoPlayer.src = videoURL;
400
  });
401
 
 
402
  function uploadFile() {
403
  const fileInput = document.getElementById('videoInput');
404
  const file = fileInput.files[0];
@@ -408,23 +472,52 @@
408
  return;
409
  }
410
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  // 获取选择的任务类型
412
  const speakerRecognition = document.getElementById('speakerRecognition').checked;
413
  const englishTranslation = document.getElementById('englishTranslation').checked;
 
 
 
 
 
414
 
415
  document.getElementById('uploadBtn').disabled = true;
416
  document.getElementById('uploadBtn').innerText = '转写中...';
417
 
418
  resetProgress();
419
 
 
 
 
 
420
  const xhr = new XMLHttpRequest();
421
  xhr.open('POST', `${serverUrl}/submit`, true);
422
  xhr.upload.onprogress = updateUploadProgress;
423
  xhr.onload = function () {
 
424
  if (xhr.status === 200) {
425
  const response = JSON.parse(xhr.responseText);
426
- currentTaskId = response.content;
427
- monitorTaskProgress(currentTaskId);
 
 
 
 
 
 
 
428
  } else {
429
  alert('上传失败,请重试');
430
  resetUploadButton();
@@ -434,50 +527,67 @@
434
 
435
  const formData = new FormData();
436
  formData.append('file', file);
437
- formData.append('speakerRecognition', speakerRecognition); // 将说话人识别任务添加到请求中
438
- formData.append('englishTranslation', englishTranslation); // 将英文翻译任务添加到请求中
 
 
439
  xhr.send(formData);
440
  }
441
 
 
442
  function resetProgress() {
 
443
  document.getElementById('uploadProgress').firstElementChild.style.width = '0%';
444
  document.getElementById('uploadProgress').firstElementChild.innerText = '';
445
  document.getElementById('recognitionProgress').firstElementChild.style.width = '0%';
446
  document.getElementById('recognitionProgress').firstElementChild.innerText = '';
 
 
447
  document.getElementById('asrList').innerHTML = "";
448
  document.getElementById('logContent').innerText = "";
449
  }
450
-
 
451
  function updateUploadProgress(event) {
452
  if (event.lengthComputable) {
453
  const percentComplete = (event.loaded / event.total) * 100;
 
454
  document.getElementById('uploadProgress').firstElementChild.style.width = `${percentComplete}%`;
455
  document.getElementById('uploadProgress').firstElementChild.innerText = `${percentComplete.toFixed(2)}%`;
456
  }
457
  }
458
-
 
459
  function handleUploadError() {
 
460
  alert('上传失败,请重试');
461
  resetUploadButton();
462
  }
463
-
 
464
  function resetUploadButton() {
 
465
  document.getElementById('uploadBtn').disabled = false;
466
  document.getElementById('uploadBtn').innerText = '上传并识别';
467
  }
468
-
 
469
  function monitorTaskProgress(taskId) {
470
- let retries = 0;
471
- const maxRetries = 3600;
 
472
 
473
  const intervalId = setInterval(function () {
474
  const xhr = new XMLHttpRequest();
475
  xhr.open('GET', `${serverUrl}/task_prgs/${taskId}`, true);
476
  xhr.onload = function () {
 
477
  if (xhr.status === 200) {
478
  const response = JSON.parse(xhr.responseText);
 
479
  const status = response.content.status;
480
  const progress = response.content.prgs;
 
481
 
482
  if (progress) {
483
  updateRecognitionProgress(progress.cur / progress.total * 100, progress.msg);
@@ -486,6 +596,7 @@
486
  if (status === "SUCCESS") {
487
  clearInterval(intervalId);
488
  asrData = response.content.result;
 
489
  displayResults(asrData);
490
  resetUploadButton();
491
  } else if (status === "FAIL") {
@@ -494,40 +605,45 @@
494
  resetUploadButton();
495
  }
496
  } else {
497
- alert('获取进度失败,请重试');
498
- clearInterval(intervalId);
499
- resetUploadButton();
 
 
 
 
500
  }
501
  };
502
  xhr.onerror = function () {
503
- alert('获取进度失败,请重试');
504
- clearInterval(intervalId);
505
- resetUploadButton();
 
 
 
 
506
  };
507
  xhr.send();
508
-
509
- retries += 1;
510
- if (retries >= maxRetries) {
511
- clearInterval(intervalId);
512
- alert('超出最大重试次数,任务未完成');
513
- resetUploadButton();
514
- }
515
- }, 1000);
516
  }
517
 
 
518
  function updateRecognitionProgress(progress, msg) {
 
519
  document.getElementById('recognitionProgress').firstElementChild.style.width = `${progress}%`;
520
  document.getElementById('recognitionProgress').firstElementChild.innerText = `${progress.toFixed(2)}%`;
521
  document.getElementById('logContent').innerText = `进度: ${progress.toFixed(2)}%, 状态: ${msg}`;
522
  }
523
-
 
524
  function displayResults(results) {
 
525
  const asrList = document.getElementById('asrList');
526
  asrList.innerHTML = "";
527
  results.forEach((entry) => {
528
  const div = document.createElement('div');
529
  div.className = 'asr-item';
530
-
531
  div.innerHTML = `
532
  <div class="timestamp">
533
  <button class="play-button">播放</button>
@@ -535,18 +651,14 @@
535
  -
536
  <input type="number" value="${entry.end.toFixed(1)}" step="0.1" min="0" class="end-time">
537
  </div>
538
-
539
  <input type="text" value="${entry.role}" placeholder="角色" class="role-field">
540
-
541
  <input type="text" value="${entry.text}" placeholder="文本" class="text-field">
542
-
543
  <input type="text" value="${entry.trans}" placeholder="翻译" class="trans-field">
544
-
545
  <label>
546
  <input type="checkbox" ${entry.drop ? 'checked' : ''}> 丢弃
547
  </label>
548
  `;
549
-
550
  const startInput = div.querySelector('.start-time');
551
  const endInput = div.querySelector('.end-time');
552
  const roleInput = div.querySelector('input.role-field');
@@ -554,15 +666,15 @@
554
  const transInput = div.querySelector('input.trans-field');
555
  const dropCheckbox = div.querySelector('input[type="checkbox"]');
556
  const playButton = div.querySelector('.play-button');
557
-
558
  startInput.addEventListener('input', () => {
559
  entry.start = parseFloat(startInput.value);
560
  });
561
-
562
  endInput.addEventListener('input', () => {
563
  entry.end = parseFloat(endInput.value);
564
  });
565
-
566
  roleInput.addEventListener('input', () => {
567
  entry.role = roleInput.value;
568
  });
@@ -570,15 +682,15 @@
570
  textInput.addEventListener('input', () => {
571
  entry.text = textInput.value;
572
  });
573
-
574
  transInput.addEventListener('input', () => {
575
  entry.trans = transInput.value;
576
  });
577
-
578
  dropCheckbox.addEventListener('change', () => {
579
  entry.drop = dropCheckbox.checked;
580
  });
581
-
582
  playButton.addEventListener('click', () => {
583
  const video = document.getElementById('videoPlayer');
584
  video.currentTime = entry.start;
@@ -590,20 +702,21 @@
590
  }
591
  }, 100);
592
  });
593
-
594
  asrList.appendChild(div);
595
  });
596
  }
597
-
 
598
  function exportAsrData(format) {
599
  if (asrData.length === 0) {
600
  alert('没有数据可以导出');
601
  return;
602
  }
603
-
604
  // 过滤掉被标记为丢弃的条目
605
  const filteredData = asrData.filter(entry => !entry.drop);
606
-
607
  let content = '';
608
  if (format === 'json') {
609
  content = JSON.stringify(filteredData, null, 2);
@@ -616,7 +729,7 @@
616
  content += `${entry.text}\n${entry.trans}\n\n`;
617
  });
618
  }
619
-
620
  const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
621
  const url = URL.createObjectURL(blob);
622
  const a = document.createElement('a');
@@ -626,7 +739,116 @@
626
  a.click();
627
  document.body.removeChild(a);
628
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
629
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630
  function formatTime(seconds) {
631
  const hours = Math.floor(seconds / 3600);
632
  const minutes = Math.floor((seconds % 3600) / 60);
@@ -634,15 +856,16 @@
634
  const millis = Math.floor((seconds - Math.floor(seconds)) * 1000);
635
  return `${pad(hours)}:${pad(minutes)}:${pad(secs)},${padMillis(millis)}`;
636
  }
637
-
638
  function pad(value) {
639
  return value.toString().padStart(2, '0');
640
  }
641
-
642
  function padMillis(value) {
643
  return value.toString().padStart(3, '0');
644
  }
645
  </script>
 
646
  </body>
647
 
648
  </html>
 
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语音识别</title>
9
  <style>
10
  body {
11
  display: flex;
 
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 {
 
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
 
 
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
  </style>
369
  </head>
370
 
371
  <body>
372
  <div class="container">
373
  <div class="title">
374
+ <img src="../static/logo_whisper.png" alt="Funsound Logo">
375
+ Funsound 语音识别
376
  </div>
377
  <div class="content">
378
  <div class="video-container">
 
386
  您的浏览器不支持 video 标签。
387
  </video>
388
 
389
+ <!-- Added serverUrl input field -->
390
  <div style="margin: 10px 0;">
391
+ <label for="serverUrlInput">服务器地址:</label>
392
+ <input type="text" id="serverUrlInput" value="https://www.funsound.cn/" class="server-url-input">
393
+ </div>
394
+
395
+ <div class="options-container">
396
+ <!-- Recognition Model -->
397
+ <div style="margin: 10px;">
398
+ <label>识别模型(单选):</label>
399
+ <label><input type="radio" name="pipeline" value="whisper" checked> Whisper</label>
400
+ <label><input type="radio" name="pipeline" value="funasr"> FunASR</label>
401
+ </div>
402
+
403
+ <!-- Additional Tasks -->
404
+ <div style="margin: 10px;">
405
+ <label>附加任务(多选):</label>
406
+ <label><input type="checkbox" id="speakerRecognition"> 说话人识别</label>
407
+ <label><input type="checkbox" id="englishTranslation"> 中英翻译</label>
408
+ </div>
409
  </div>
410
 
411
  <div class="buttons-container">
 
419
  <div id="recognitionProgress" class="progress-bar">
420
  <div>0%</div>
421
  </div>
422
+ <label>合成进度:</label>
423
+ <div id="subtitleProgress" class="progress-bar">
424
+ <div>0%</div>
425
+ </div>
426
  <div id="logContent" style="margin-top: 10px; color: #fff;"></div>
427
  </div>
428
  <div class="asr-container">
 
431
  <div class="center-buttons">
432
  <button id="exportJsonBtn" class="export-button" onclick="exportAsrData('json')">导出 JSON</button>
433
  <button id="exportSrtBtn" class="export-button" onclick="exportAsrData('srt')">导出 SRT</button>
434
+ <!-- 修改合成按钮颜色 -->
435
+ <button id="generateSubtitleBtn" class="subtitle-button" onclick="generateSubtitle()">合成字幕</button>
436
  </div>
437
  </div>
438
  </div>
 
442
  联系邮箱: <a href="mailto:[email protected]">[email protected]</a> |
443
  CSDN: <a href="https://blog.csdn.net/Ephemeroptera" target="_blank">Pika在线</a> |
444
  Modelscope: <a href="https://modelscope.cn/studios/QuadraV/FunSound">Funsound</a> |
445
+ 私有化部署
446
  </footer>
447
 
448
  <script>
449
+ let currentTaskId = null; // 当前的任务ID
450
+ let asrData = []; // 存储识别结果数据
451
+ let serverUrl = null; // 服务器地址
452
+
453
+ // 监听文件输入更改,预览视频
454
  document.getElementById('videoInput').addEventListener('change', function (event) {
455
  const file = event.target.files[0];
456
+ console.log('Selected file:', file);
457
  document.getElementById('uploadBtn').disabled = file.size > 300 * 1024 * 1024;
458
+
459
  const videoPlayer = document.getElementById('videoPlayer');
460
  const videoURL = URL.createObjectURL(file);
461
+ console.log('Video URL:', videoURL);
462
  videoPlayer.src = videoURL;
463
  });
464
 
465
+ // 上传文件并提交任务
466
  function uploadFile() {
467
  const fileInput = document.getElementById('videoInput');
468
  const file = fileInput.files[0];
 
472
  return;
473
  }
474
 
475
+ // 获取文件的 MIME 类型
476
+ const fileType = file.type;
477
+ console.log('File type:', fileType);
478
+
479
+ // 检查是否为视频文件
480
+ const validVideoTypes = ['video/mp4', 'video/x-m4v', 'video/*', 'audio/mp4'];
481
+ const isVideo = validVideoTypes.includes(fileType);
482
+ console.log('Is valid video:', isVideo);
483
+
484
+ // 保存文件类型到全局变量
485
+ window.currentFileType = fileType;
486
+
487
  // 获取选择的任务类型
488
  const speakerRecognition = document.getElementById('speakerRecognition').checked;
489
  const englishTranslation = document.getElementById('englishTranslation').checked;
490
+ console.log('Speaker Recognition:', speakerRecognition, 'English Translation:', englishTranslation);
491
+
492
+ // 获取用户选择的识别模型
493
+ const selectedPipeline = document.querySelector('input[name="pipeline"]:checked').value;
494
+ console.log('Selected pipeline:', selectedPipeline);
495
 
496
  document.getElementById('uploadBtn').disabled = true;
497
  document.getElementById('uploadBtn').innerText = '转写中...';
498
 
499
  resetProgress();
500
 
501
+ // 从输入框获取服务器地址
502
+ serverUrl = document.getElementById('serverUrlInput').value.trim();
503
+ console.log('Server URL:', serverUrl);
504
+
505
  const xhr = new XMLHttpRequest();
506
  xhr.open('POST', `${serverUrl}/submit`, true);
507
  xhr.upload.onprogress = updateUploadProgress;
508
  xhr.onload = function () {
509
+ console.log('Upload response status:', xhr.status);
510
  if (xhr.status === 200) {
511
  const response = JSON.parse(xhr.responseText);
512
+ console.log('Upload response:', response);
513
+ if (response.code === 0) {
514
+ currentTaskId = response.content; // 获取任务ID
515
+ console.log('Task ID:', currentTaskId);
516
+ monitorTaskProgress(currentTaskId);
517
+ } else {
518
+ alert('上传失败,请重试');
519
+ resetUploadButton();
520
+ }
521
  } else {
522
  alert('上传失败,请重试');
523
  resetUploadButton();
 
527
 
528
  const formData = new FormData();
529
  formData.append('file', file);
530
+ formData.append('speakerRecognition', speakerRecognition); // 传递说话人识别参数
531
+ formData.append('englishTranslation', englishTranslation); // 传递英文翻译参数
532
+ formData.append('pipeline', selectedPipeline); // 传递选择的识别模型
533
+ console.log('Form Data:', formData);
534
  xhr.send(formData);
535
  }
536
 
537
+ // 重置进度条和界面
538
  function resetProgress() {
539
+ console.log('Resetting progress bars and UI elements');
540
  document.getElementById('uploadProgress').firstElementChild.style.width = '0%';
541
  document.getElementById('uploadProgress').firstElementChild.innerText = '';
542
  document.getElementById('recognitionProgress').firstElementChild.style.width = '0%';
543
  document.getElementById('recognitionProgress').firstElementChild.innerText = '';
544
+ document.getElementById('subtitleProgress').firstElementChild.style.width = '0%';
545
+ document.getElementById('subtitleProgress').firstElementChild.innerText = '';
546
  document.getElementById('asrList').innerHTML = "";
547
  document.getElementById('logContent').innerText = "";
548
  }
549
+
550
+ // 更新上传进度
551
  function updateUploadProgress(event) {
552
  if (event.lengthComputable) {
553
  const percentComplete = (event.loaded / event.total) * 100;
554
+ console.log('Upload progress:', percentComplete);
555
  document.getElementById('uploadProgress').firstElementChild.style.width = `${percentComplete}%`;
556
  document.getElementById('uploadProgress').firstElementChild.innerText = `${percentComplete.toFixed(2)}%`;
557
  }
558
  }
559
+
560
+ // 上传失败的处理
561
  function handleUploadError() {
562
+ console.log('Upload error occurred');
563
  alert('上传失败,请重试');
564
  resetUploadButton();
565
  }
566
+
567
+ // 重置上传按钮状态
568
  function resetUploadButton() {
569
+ console.log('Resetting upload button state');
570
  document.getElementById('uploadBtn').disabled = false;
571
  document.getElementById('uploadBtn').innerText = '上传并识别';
572
  }
573
+
574
+ // 监控任务进度
575
  function monitorTaskProgress(taskId) {
576
+ console.log('Monitoring task progress for Task ID:', taskId);
577
+ let failedRequests = 0; // 计数连续失败的请求
578
+ const maxFailedRequests = 10; // 连续失败的最大请求数
579
 
580
  const intervalId = setInterval(function () {
581
  const xhr = new XMLHttpRequest();
582
  xhr.open('GET', `${serverUrl}/task_prgs/${taskId}`, true);
583
  xhr.onload = function () {
584
+ console.log('Progress response status:', xhr.status);
585
  if (xhr.status === 200) {
586
  const response = JSON.parse(xhr.responseText);
587
+ console.log('Progress response:', response);
588
  const status = response.content.status;
589
  const progress = response.content.prgs;
590
+ failedRequests = 0;
591
 
592
  if (progress) {
593
  updateRecognitionProgress(progress.cur / progress.total * 100, progress.msg);
 
596
  if (status === "SUCCESS") {
597
  clearInterval(intervalId);
598
  asrData = response.content.result;
599
+ console.log('Recognition successful, ASR data:', asrData);
600
  displayResults(asrData);
601
  resetUploadButton();
602
  } else if (status === "FAIL") {
 
605
  resetUploadButton();
606
  }
607
  } else {
608
+ failedRequests += 1; // 增加失败计数
609
+ document.getElementById('logContent').innerText = `请求失败,当前重连次数: ${failedRequests}`;
610
+ if (failedRequests >= maxFailedRequests) {
611
+ clearInterval(intervalId);
612
+ alert('连续请求失败,任务未完成');
613
+ resetUploadButton();
614
+ }
615
  }
616
  };
617
  xhr.onerror = function () {
618
+ failedRequests += 1; // 增加失败计数
619
+ document.getElementById('logContent').innerText = `请求失败,当前重连次数: ${failedRequests}`;
620
+ if (failedRequests >= maxFailedRequests) {
621
+ clearInterval(intervalId);
622
+ alert('连续请求失败,任务未完成');
623
+ resetUploadButton();
624
+ }
625
  };
626
  xhr.send();
627
+ }, 2000);
 
 
 
 
 
 
 
628
  }
629
 
630
+ // 更新识别进度
631
  function updateRecognitionProgress(progress, msg) {
632
+ console.log('Recognition progress:', progress, 'Message:', msg);
633
  document.getElementById('recognitionProgress').firstElementChild.style.width = `${progress}%`;
634
  document.getElementById('recognitionProgress').firstElementChild.innerText = `${progress.toFixed(2)}%`;
635
  document.getElementById('logContent').innerText = `进度: ${progress.toFixed(2)}%, 状态: ${msg}`;
636
  }
637
+
638
+ // 显示识别结果
639
  function displayResults(results) {
640
+ console.log('Displaying ASR results:', results);
641
  const asrList = document.getElementById('asrList');
642
  asrList.innerHTML = "";
643
  results.forEach((entry) => {
644
  const div = document.createElement('div');
645
  div.className = 'asr-item';
646
+
647
  div.innerHTML = `
648
  <div class="timestamp">
649
  <button class="play-button">播放</button>
 
651
  -
652
  <input type="number" value="${entry.end.toFixed(1)}" step="0.1" min="0" class="end-time">
653
  </div>
 
654
  <input type="text" value="${entry.role}" placeholder="角色" class="role-field">
 
655
  <input type="text" value="${entry.text}" placeholder="文本" class="text-field">
 
656
  <input type="text" value="${entry.trans}" placeholder="翻译" class="trans-field">
 
657
  <label>
658
  <input type="checkbox" ${entry.drop ? 'checked' : ''}> 丢弃
659
  </label>
660
  `;
661
+
662
  const startInput = div.querySelector('.start-time');
663
  const endInput = div.querySelector('.end-time');
664
  const roleInput = div.querySelector('input.role-field');
 
666
  const transInput = div.querySelector('input.trans-field');
667
  const dropCheckbox = div.querySelector('input[type="checkbox"]');
668
  const playButton = div.querySelector('.play-button');
669
+
670
  startInput.addEventListener('input', () => {
671
  entry.start = parseFloat(startInput.value);
672
  });
673
+
674
  endInput.addEventListener('input', () => {
675
  entry.end = parseFloat(endInput.value);
676
  });
677
+
678
  roleInput.addEventListener('input', () => {
679
  entry.role = roleInput.value;
680
  });
 
682
  textInput.addEventListener('input', () => {
683
  entry.text = textInput.value;
684
  });
685
+
686
  transInput.addEventListener('input', () => {
687
  entry.trans = transInput.value;
688
  });
689
+
690
  dropCheckbox.addEventListener('change', () => {
691
  entry.drop = dropCheckbox.checked;
692
  });
693
+
694
  playButton.addEventListener('click', () => {
695
  const video = document.getElementById('videoPlayer');
696
  video.currentTime = entry.start;
 
702
  }
703
  }, 100);
704
  });
705
+
706
  asrList.appendChild(div);
707
  });
708
  }
709
+
710
+ // 导出识别结果为 JSON 或 SRT 格式
711
  function exportAsrData(format) {
712
  if (asrData.length === 0) {
713
  alert('没有数据可以导出');
714
  return;
715
  }
716
+
717
  // 过滤掉被标记为丢弃的条目
718
  const filteredData = asrData.filter(entry => !entry.drop);
719
+
720
  let content = '';
721
  if (format === 'json') {
722
  content = JSON.stringify(filteredData, null, 2);
 
729
  content += `${entry.text}\n${entry.trans}\n\n`;
730
  });
731
  }
732
+
733
  const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
734
  const url = URL.createObjectURL(blob);
735
  const a = document.createElement('a');
 
739
  a.click();
740
  document.body.removeChild(a);
741
  }
742
+
743
+ // 生成字幕并监控字幕合成进度
744
+ function generateSubtitle() {
745
+ // 检查当前文件是否为视频文件
746
+ const validVideoTypes = ['video/mp4', 'video/x-m4v', 'video/*', 'audio/mp4'];
747
+ if (!validVideoTypes.includes(window.currentFileType)) {
748
+ alert('当前文件不是视频文件,无法合成字幕。');
749
+ return;
750
+ }
751
+
752
+ document.getElementById('subtitleProgress').firstElementChild.style.width = '0%';
753
+ document.getElementById('subtitleProgress').firstElementChild.innerText = '0%';
754
+
755
+ if (!currentTaskId || asrData.length === 0) {
756
+ alert('请确保选择了视频文件并进行了识别');
757
+ return;
758
+ }
759
+
760
+ const postData = {
761
+ task_id: currentTaskId,
762
+ asr_results: asrData
763
+ };
764
+
765
+ document.getElementById('generateSubtitleBtn').disabled = true;
766
+ document.getElementById('generateSubtitleBtn').innerText = '合成中...';
767
+
768
+ const xhr = new XMLHttpRequest();
769
+ xhr.open('POST', `${serverUrl}/make_subtitle`, true);
770
+ xhr.setRequestHeader('Content-Type', 'application/json');
771
+ xhr.onload = function () {
772
+ if (xhr.status === 202) {
773
+ const response = JSON.parse(xhr.responseText);
774
+ monitorSubtitleGeneration(response.task_id);
775
+ } else {
776
+ alert('合成字幕失败,请重试');
777
+ resetGenerateButton();
778
+ }
779
+ };
780
+ xhr.onerror = function () {
781
+ alert('合成字幕失败,请重试');
782
+ resetGenerateButton();
783
+ };
784
+ xhr.send(JSON.stringify(postData));
785
+ }
786
+
787
+ // 监控字幕合成进度
788
+ function monitorSubtitleGeneration(taskId) {
789
+ console.log('Monitoring subtitle generation for Task ID:', taskId);
790
+ let failedRequests = 0; // 计数连续失败的请求
791
+ const maxFailedRequests = 10; // 连续失败的最大请求数
792
+
793
+ const intervalId = setInterval(function () {
794
+ const xhr = new XMLHttpRequest();
795
+ xhr.open('GET', `${serverUrl}/get_subtitle_progress/${taskId}`, true);
796
+ xhr.onload = function () {
797
+
798
+ if (xhr.status === 200) {
799
+ const response = JSON.parse(xhr.responseText);
800
+ const progress = response.progress;
801
+ failedRequests = 0; // 请求成功时重置失败计数
802
+
803
 
804
+ if (progress !== undefined) {
805
+ updateSubtitleProgress(progress * 100, '正在生成字幕');
806
+ }
807
+
808
+ if (progress >= 1.0) {
809
+ clearInterval(intervalId);
810
+ document.getElementById('videoPlayer').src = `${serverUrl}/video/${response.video_url}`;
811
+ resetGenerateButton();
812
+ }
813
+ } else {
814
+ failedRequests += 1; // 增加失败计数
815
+ document.getElementById('logContent').innerText = `请求失败,当前重连次数: ${failedRequests}`;
816
+ if (failedRequests >= maxFailedRequests) {
817
+ clearInterval(intervalId);
818
+ alert('连续请求失败,字幕生成未完成');
819
+ resetGenerateButton();
820
+ }
821
+ }
822
+ };
823
+ xhr.onerror = function () {
824
+ failedRequests += 1; // 增加失败计数
825
+ document.getElementById('logContent').innerText = `请求失败,当前重连次数: ${failedRequests}`;
826
+ if (failedRequests >= maxFailedRequests) {
827
+ clearInterval(intervalId);
828
+ alert('连续请求失败,字幕生成未完成');
829
+ resetGenerateButton();
830
+ }
831
+ };
832
+ xhr.send();
833
+
834
+ }, 1000);
835
+ }
836
+
837
+ // 更新字幕合成进度
838
+ function updateSubtitleProgress(progress, msg) {
839
+ const progressBar = document.getElementById('subtitleProgress');
840
+ progressBar.firstElementChild.style.width = `${progress}%`;
841
+ progressBar.firstElementChild.innerText = `${progress.toFixed(2)}%`;
842
+ document.getElementById('logContent').innerText = `进度: ${progress.toFixed(2)}%, 状态: 正在合成含字幕视频,合成完毕将在上面播放器展示`;
843
+ }
844
+
845
+ // 重置生成字幕按钮状态
846
+ function resetGenerateButton() {
847
+ document.getElementById('generateSubtitleBtn').disabled = false;
848
+ document.getElementById('generateSubtitleBtn').innerText = '合成字幕';
849
+ }
850
+
851
+ // 格式化时间,用于字幕格式化
852
  function formatTime(seconds) {
853
  const hours = Math.floor(seconds / 3600);
854
  const minutes = Math.floor((seconds % 3600) / 60);
 
856
  const millis = Math.floor((seconds - Math.floor(seconds)) * 1000);
857
  return `${pad(hours)}:${pad(minutes)}:${pad(secs)},${padMillis(millis)}`;
858
  }
859
+
860
  function pad(value) {
861
  return value.toString().padStart(2, '0');
862
  }
863
+
864
  function padMillis(value) {
865
  return value.toString().padStart(3, '0');
866
  }
867
  </script>
868
+
869
  </body>
870
 
871
  </html>