- 选择文件夹/单文件 → SenseVoice 转录 → LLM 修正 → 多路线翻译 → 双语 SRT - 路线:直译中文 / 英转中 / 仅转录 - 并发流水线:转录(可调) + LLM 并发(可调),降噪拆锁并发 - 断点续跑:.subtitle-work/ 中间产物,三阶段独立续跑 + 翻译段级续跑 - 去重:history 跟随视频文件夹,防重复任务保护 - 实时进度:SSE 推送 + 耗时显示 + 子任务状态 + 重新生成按钮 - 时间戳调优:VAD silence_schedule + noisereduce 降噪 + 完整性校验
146 lines
9.0 KiB
HTML
146 lines
9.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>字幕生成系统 subtitle-studio</title>
|
|
<style>
|
|
:root { --bg:#0f1115; --card:#171a21; --border:#2a2f3a; --text:#e6e8eb; --dim:#8b93a3;
|
|
--accent:#4f8cff; --ok:#3fb950; --warn:#d29922; --err:#f85149; }
|
|
* { box-sizing:border-box; margin:0; padding:0; }
|
|
body { background:var(--bg); color:var(--text); font-family:"Segoe UI","Microsoft YaHei",sans-serif; font-size:14px; }
|
|
.container { max-width:1200px; margin:0 auto; padding:20px; }
|
|
h1 { font-size:20px; margin-bottom:16px; display:flex; align-items:center; gap:10px; }
|
|
h1 .dot { width:10px; height:10px; border-radius:50%; background:var(--ok); animation:pulse 2s infinite; }
|
|
@keyframes pulse { 50% { opacity:.4; } }
|
|
.card { background:var(--card); border:1px solid var(--border); border-radius:8px; padding:16px; margin-bottom:16px; }
|
|
.card h2 { font-size:15px; margin-bottom:12px; color:var(--dim); font-weight:600; }
|
|
/* 路径选择 */
|
|
.browser { display:flex; flex-direction:column; gap:8px; }
|
|
.browser-bar { display:flex; gap:8px; align-items:center; }
|
|
.browser-bar input { flex:1; background:#0d0f14; border:1px solid var(--border); color:var(--text); border-radius:6px; padding:8px 10px; font-size:13px; }
|
|
.browser-bar button { background:var(--accent); color:#fff; border:none; border-radius:6px; padding:8px 16px; cursor:pointer; font-size:13px; }
|
|
.browser-bar button:hover { opacity:.85; }
|
|
.browser-list { max-height:220px; overflow-y:auto; border:1px solid var(--border); border-radius:6px; background:#0d0f14; }
|
|
.browser-item { display:flex; align-items:center; gap:8px; padding:6px 10px; cursor:pointer; font-size:13px; }
|
|
.browser-item:hover { background:#1c212b; }
|
|
.browser-item.selected { background:#223049; }
|
|
.browser-item .icon { width:16px; text-align:center; color:var(--dim); }
|
|
.browser-item .is-file { color:var(--dim); margin-left:auto; font-size:11px; }
|
|
.sel-preview { margin-top:8px; font-size:13px; color:var(--dim); }
|
|
.sel-preview b { color:var(--text); }
|
|
/* 路线选择 */
|
|
.routes { display:flex; flex-direction:column; gap:8px; }
|
|
.route-opt { display:flex; align-items:flex-start; gap:10px; padding:10px 12px; border:1px solid var(--border); border-radius:6px; cursor:pointer; }
|
|
.route-opt:hover { border-color:var(--accent); }
|
|
.route-opt input { margin-top:3px; }
|
|
.route-opt .r-title { font-weight:600; }
|
|
.route-opt .r-desc { color:var(--dim); font-size:12px; margin-top:2px; }
|
|
.submit-row { display:flex; gap:10px; margin-top:14px; align-items:center; }
|
|
.submit-row button { background:var(--accent); color:#fff; border:none; border-radius:6px; padding:10px 24px; font-size:14px; cursor:pointer; }
|
|
.submit-row button:disabled { opacity:.4; cursor:not-allowed; }
|
|
.submit-row select { background:#0d0f14; border:1px solid var(--border); color:var(--text); border-radius:6px; padding:8px; }
|
|
.submit-row .hint { color:var(--dim); font-size:12px; }
|
|
/* 任务列表 */
|
|
.task { border:1px solid var(--border); border-radius:8px; padding:12px 14px; margin-bottom:10px; background:#14171f; }
|
|
.task-head { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
|
|
.task-name { font-weight:600; font-size:13px; max-width:60%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
.badge { padding:2px 8px; border-radius:10px; font-size:11px; font-weight:600; }
|
|
.badge.pending { background:#2a2f3a; color:var(--dim); }
|
|
.badge.transcribing,.badge.fixing,.badge.translating,.badge.srt { background:#223049; color:var(--accent); }
|
|
.badge.done { background:#13271a; color:var(--ok); }
|
|
.badge.error { background:#2a1618; color:var(--err); }
|
|
.badge.cancelled { background:#2a2f3a; color:var(--dim); }
|
|
.task-progress { margin-top:8px; height:6px; background:#0d0f14; border-radius:3px; overflow:hidden; }
|
|
.task-progress .bar { height:100%; background:var(--accent); transition:width .3s; }
|
|
.task-progress .bar.done { background:var(--ok); }
|
|
.task-progress .bar.error { background:var(--err); }
|
|
.task-msg { margin-top:6px; font-size:12px; color:var(--dim); }
|
|
.task-files { margin-top:8px; display:flex; flex-wrap:wrap; gap:6px; }
|
|
.task-files a { background:#1c212b; border:1px solid var(--border); color:var(--accent); text-decoration:none; font-size:12px; padding:3px 10px; border-radius:4px; }
|
|
.task-files a:hover { border-color:var(--accent); }
|
|
.task-actions { margin-top:8px; display:flex; gap:8px; }
|
|
.task-actions button { background:#1c212b; border:1px solid var(--border); color:var(--text); border-radius:4px; padding:3px 12px; font-size:12px; cursor:pointer; }
|
|
.task-actions button:hover { border-color:var(--accent); }
|
|
.task-time { font-size:11px; color:var(--dim); margin-left:auto; }
|
|
.empty { color:var(--dim); text-align:center; padding:30px 0; font-size:13px; }
|
|
.error-text { color:var(--err); font-size:12px; margin-top:4px; word-break:break-all; }
|
|
/* 可折叠区域 */
|
|
details.card { background:var(--card); border:1px solid var(--border); border-radius:8px; margin-bottom:16px; }
|
|
details.card > summary { cursor:pointer; padding:14px 16px; font-size:15px; font-weight:600; color:var(--dim); list-style:none; display:flex; align-items:center; gap:8px; user-select:none; }
|
|
details.card > summary::-webkit-details-marker { display:none; }
|
|
details.card > summary:hover { color:var(--text); }
|
|
details.card > summary .arrow { transition:transform .2s; font-size:12px; color:var(--accent); }
|
|
details.card[open] > summary .arrow { transform:rotate(90deg); }
|
|
details.card > .card-body { padding:0 16px 16px; }
|
|
details.card[open] > summary { border-bottom:1px solid var(--border); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1><span class="dot"></span> 字幕生成系统</h1>
|
|
|
|
<details class="card" open id="section-1">
|
|
<summary><span class="arrow">▶</span>① 选择视频</summary>
|
|
<div class="card-body">
|
|
<div class="browser">
|
|
<div class="browser-bar">
|
|
<select id="drive-select" style="background:#0d0f14;border:1px solid var(--border);color:var(--text);border-radius:6px;padding:8px;"></select>
|
|
<input id="path-input" placeholder="输入路径或浏览选择...">
|
|
<button onclick="goUp()">⬆ 上级</button>
|
|
<button onclick="refreshBrowse()">刷新</button>
|
|
</div>
|
|
<div id="browser-list" class="browser-list"></div>
|
|
<div id="sel-preview" class="sel-preview">未选择</div>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="card" open id="section-2">
|
|
<summary><span class="arrow">▶</span>② 选择翻译路线</summary>
|
|
<div class="card-body">
|
|
<div class="routes">
|
|
<label class="route-opt"><input type="radio" name="route" value="direct" checked>
|
|
<div><div class="r-title">路线 A:直译中文</div>
|
|
<div class="r-desc">转录 → LLM 修正 → 日文直译中文。生成 .ja.zh.srt(日上中下)+ .zh.srt</div></div></label>
|
|
<label class="route-opt"><input type="radio" name="route" value="via_en">
|
|
<div><div class="r-title">路线 B:英转中</div>
|
|
<div class="r-desc">转录 → LLM 修正 → 日文→英文→中文。生成 .r2.zh.en.srt(中英双语)+ .r2.zh.srt</div></div></label>
|
|
<label class="route-opt"><input type="radio" name="route" value="transcribe_only">
|
|
<div><div class="r-title">仅转录</div>
|
|
<div class="r-desc">只做语音识别,不翻译。输出 _transcript.json</div></div></label>
|
|
</div>
|
|
<div class="submit-row">
|
|
<button id="submit-btn" onclick="createTask()">开始生成字幕</button>
|
|
<select id="lang-select">
|
|
<option value="ja" selected>日语</option>
|
|
<option value="ko">韩语</option>
|
|
<option value="zh">中文</option>
|
|
<option value="en">英语</option>
|
|
<option value="yue">粤语</option>
|
|
</select>
|
|
<span class="hint">文件夹会批量处理其中所有视频(转录串行,LLM 并发)</span>
|
|
</div>
|
|
<div class="submit-row" style="margin-top:8px;">
|
|
<span class="hint" style="color:var(--dim);">转录并发:</span>
|
|
<input id="tr-conc-input" type="number" min="1" max="4" value="2" style="width:55px;background:#0d0f14;border:1px solid var(--border);color:var(--text);border-radius:6px;padding:6px;">
|
|
<span class="hint" style="color:var(--dim);">LLM 并发(修正/翻译):</span>
|
|
<input id="llm-conc-input" type="number" min="1" max="20" value="6" style="width:55px;background:#0d0f14;border:1px solid var(--border);color:var(--text);border-radius:6px;padding:6px;">
|
|
<button onclick="saveConcurrency()" style="background:#1c212b;border:1px solid var(--border);color:var(--text);border-radius:6px;padding:6px 14px;cursor:pointer;font-size:13px;">保存</button>
|
|
<span class="hint" id="conc-status" style="color:var(--dim);"></span>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="card" open id="section-3">
|
|
<summary><span class="arrow">▶</span>③ 任务列表</summary>
|
|
<div class="card-body">
|
|
<div id="task-list"></div>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
|
|
<script src="/static/app.js"></script>
|
|
</body>
|
|
</html>
|