Xin Zhang
commited on
Commit
·
ea81a46
1
Parent(s):
c055a7b
[feature]: update web portal.
Browse files- config.py +1 -1
- frontend/assets/index-a48dc1a0.css +1 -0
- frontend/assets/index-d7a7d66c.js +0 -0
- frontend/favicon.ico +0 -0
- frontend/index.html +13 -167
- frontend/index2.html +169 -0
- transcribe/strategy.py +1 -0
config.py
CHANGED
@@ -23,7 +23,7 @@ WHISPER_PROMPT_ZH = "以下是简体中文普通话的句子。"
|
|
23 |
MAX_LENTH_ZH = 4
|
24 |
|
25 |
WHISPER_PROMPT_EN = "The following is an English sentence."
|
26 |
-
MAX_LENGTH_EN=
|
27 |
|
28 |
WHISPER_MODEL = 'medium-q5_0'
|
29 |
|
|
|
23 |
MAX_LENTH_ZH = 4
|
24 |
|
25 |
WHISPER_PROMPT_EN = "The following is an English sentence."
|
26 |
+
MAX_LENGTH_EN= 3
|
27 |
|
28 |
WHISPER_MODEL = 'medium-q5_0'
|
29 |
|
frontend/assets/index-a48dc1a0.css
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
html,body{width:100%;height:100%}input::-ms-clear,input::-ms-reveal{display:none}*,*:before,*:after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{margin:0}[tabindex="-1"]:focus{outline:none}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5em;font-weight:500}p{margin-top:0;margin-bottom:1em}abbr[title],abbr[data-original-title]{-webkit-text-decoration:underline dotted;text-decoration:underline;text-decoration:underline dotted;border-bottom:0;cursor:help}address{margin-bottom:1em;font-style:normal;line-height:inherit}input[type=text],input[type=password],input[type=number],textarea{-webkit-appearance:none}ol,ul,dl{margin-top:0;margin-bottom:1em}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:500}dd{margin-bottom:.5em;margin-left:0}blockquote{margin:0 0 1em}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}pre,code,kbd,samp{font-size:1em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}pre{margin-top:0;margin-bottom:1em;overflow:auto}figure{margin:0 0 1em}img{vertical-align:middle;border-style:none}a,area,button,[role=button],input:not([type=range]),label,select,summary,textarea{touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75em;padding-bottom:.3em;text-align:left;caption-side:bottom}input,button,select,optgroup,textarea{margin:0;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{padding:0;border-style:none}input[type=radio],input[type=checkbox]{box-sizing:border-box;padding:0}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;margin:0;padding:0;border:0}legend{display:block;width:100%;max-width:100%;margin-bottom:.5em;padding:0;color:inherit;font-size:1.5em;line-height:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}mark{padding:.2em;background-color:#feffe6}:root{font-family:Inter,system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-text-size-adjust:100%}a{font-weight:500;color:#646cff;text-decoration:inherit}a:hover{color:#535bf2}body{margin:0;display:flex;place-items:center;min-width:320px;height:auto;min-height:auto;color:#333;background:#fff}h1{font-size:3.2em;line-height:1.1}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;background-color:#1a1a1a;cursor:pointer;transition:border-color .25s}.card{border-bottom:solid 2px lightgray;align-items:center;justify-content:center;margin-top:40px;display:flex;max-width:1024px;width:100%}.seg-title{margin:24px 0;font-size:20px;font-weight:500}.seg-co{width:1022px;text-align:left;border-left:solid 6px midnightblue;padding-left:8px;margin-left:2px;margin-top:36px;line-height:24px}#app{margin:0 auto;padding:0;text-align:center;width:100%}.ant-btn{padding:4px 12px}@media (prefers-color-scheme: light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}}.ant-card{background:#f5f6fa}.ant-card .ant-card-actions{background-color:#e8e8f8cc!important}.ant-popover{max-width:800px!important}.ant-form-item{background:transparent;margin-bottom:40px!important}.ant-form-item .ant-form-item-explain-error{color:#ff4d4f;text-align:left!important}.ant-form-item-label label{font-size:18px!important;color:#1a1a1a!important;font-weight:500!important}.ant-tooltip{max-width:1022px!important}.ant-page-header-heading{width:1022px!important}.highlight{background:ghostwhite}.right[data-v-f1347e90]{position:fixed;bottom:0;width:100%;height:40px;line-height:40px;text-align:center;font-size:.8em;background:white}.content[data-v-6ff6f541]{background-color:#fff;max-width:1280px;min-height:720px;margin:0 auto;display:flex;flex-direction:column;align-items:center;justify-content:space-between}.not-found-wrapper[data-v-aef52a59]{height:calc(100vh - 104px)}.view-wrapper[data-v-3036387c]{width:100%;height:100%;background-color:#fff}.view-wrapper .content-wrapper[data-v-3036387c]{text-align:left;width:1280px;max-width:100vw;min-width:320px;margin-bottom:64px;min-height:calc(100vh - 438px)}.view-wrapper .content-wrapper .chat-box[data-v-3036387c]{width:100%;height:54vh;border-radius:4px;padding:12px;color:#2e2f33;font-size:18px}.view-wrapper .content-wrapper .chat-box-placeholder[data-v-3036387c]{width:100%;height:58vh;border-radius:4px;padding:12px;font-size:18px;color:#a4a6ac}.view-wrapper .content-wrapper .actions-box[data-v-3036387c]{display:flex;align-items:center;justify-content:space-between;margin:0 24px;height:48px}.view-wrapper .content-wrapper .trans-list[data-v-3036387c]{overflow-y:auto;width:100%;height:58vh;scrollbar-width:none;-ms-overflow-style:none}.view-wrapper .content-wrapper .trans-list[data-v-3036387c]::-webkit-scrollbar{display:none}.view-wrapper .content-wrapper .trans-list .node[data-v-3036387c]{margin-bottom:36px;width:100%!important;transition:all .3s ease}.view-wrapper .content-wrapper .trans-list .node .trans-time[data-v-3036387c]{font-size:14px;color:#c4c6cc}.view-wrapper .content-wrapper .trans-list .node .trans-src-lang[data-v-3036387c]{font-size:17px;color:#909299}.view-wrapper .content-wrapper .trans-list .node .trans-dst-lang[data-v-3036387c]{font-size:18px;color:#2e2f33;font-weight:600}.view-wrapper .content-wrapper .trans-list .current_node[data-v-3036387c]{background-color:#f0f1f7;padding:4px 8px}@keyframes highlight-3036387c{0%{background-color:transparent}50%{background-color:#fff1ce80}to{background-color:transparent}}@keyframes slideIn-3036387c{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.content-wrapper[data-v-c39ab0d6]{text-align:left;max-width:800px;min-width:320px;margin-bottom:64px;min-height:calc(100vh - 438px)}.content-wrapper .content-box[data-v-c39ab0d6]{padding:24px;height:240px;background-color:#e8e8e8;border-radius:16px;width:50%;margin:48px auto;min-width:300px}.content-wrapper .video-box[data-v-c39ab0d6]{max-width:800px;min-width:320px;width:90vw;height:auto}
|
frontend/assets/index-d7a7d66c.js
ADDED
The diff for this file is too large to render.
See raw diff
|
|
frontend/favicon.ico
ADDED
|
frontend/index.html
CHANGED
@@ -1,169 +1,15 @@
|
|
1 |
-
<!
|
2 |
-
<html lang="
|
3 |
-
<head>
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
.translation-box {
|
14 |
-
background: #f2f2f8;
|
15 |
-
border-radius: 12px;
|
16 |
-
padding: 1.5rem;
|
17 |
-
max-width: 800px;
|
18 |
-
margin: 0 auto;
|
19 |
-
min-height: 200px;
|
20 |
-
}
|
21 |
-
.entry {
|
22 |
-
margin-bottom: 1.5rem;
|
23 |
-
}
|
24 |
-
.timestamp {
|
25 |
-
font-size: 0.75rem;
|
26 |
-
color: #999;
|
27 |
-
}
|
28 |
-
.original {
|
29 |
-
font-size: 1rem;
|
30 |
-
color: #333;
|
31 |
-
}
|
32 |
-
.translation {
|
33 |
-
font-size: 1rem;
|
34 |
-
font-weight: bold;
|
35 |
-
color: #000;
|
36 |
-
}
|
37 |
-
.footer {
|
38 |
-
display: flex;
|
39 |
-
justify-content: space-between;
|
40 |
-
align-items: center;
|
41 |
-
margin-top: 2rem;
|
42 |
-
}
|
43 |
-
.lang-select {
|
44 |
-
background: white;
|
45 |
-
border-radius: 9999px;
|
46 |
-
padding: 0.4rem 1rem;
|
47 |
-
border: none;
|
48 |
-
font-size: 1rem;
|
49 |
-
box-shadow: 0 0 0 1px #ddd;
|
50 |
-
}
|
51 |
-
.record-button {
|
52 |
-
background-color: #1e40af;
|
53 |
-
color: white;
|
54 |
-
border: none;
|
55 |
-
padding: 0.6rem 1.2rem;
|
56 |
-
border-radius: 9999px;
|
57 |
-
font-size: 1rem;
|
58 |
-
cursor: pointer;
|
59 |
-
}
|
60 |
-
</style>
|
61 |
-
</head>
|
62 |
-
<body>
|
63 |
-
<div class="translation-box" id="translationBox">
|
64 |
-
<!-- 实时内容将插入这里 -->
|
65 |
-
</div>
|
66 |
-
|
67 |
-
<div class="footer">
|
68 |
-
<select class="lang-select">
|
69 |
-
<option>中文 » 英语</option>
|
70 |
-
</select>
|
71 |
-
<button class="record-button" onclick="startRecording()">🎤 录音</button>
|
72 |
-
</div>
|
73 |
-
|
74 |
-
<script>
|
75 |
-
let ws;
|
76 |
-
let mediaRecorder;
|
77 |
-
|
78 |
-
function formatTimestamp(ms) {
|
79 |
-
const sec = ms / 1000;
|
80 |
-
const min = Math.floor(sec / 60);
|
81 |
-
const s = (sec % 60).toFixed(1);
|
82 |
-
return `${String(min).padStart(2, '0')}:${s.padStart(4, '0')}`;
|
83 |
-
}
|
84 |
-
|
85 |
-
let lastSegId = null; // 用于存储上一个 seg_id
|
86 |
-
|
87 |
-
function addTranslation(result) {
|
88 |
-
const box = document.getElementById('translationBox');
|
89 |
-
|
90 |
-
// 创建一个新的 div 来显示翻译
|
91 |
-
const entry = document.createElement('div');
|
92 |
-
entry.className = 'entry';
|
93 |
-
|
94 |
-
console.log(result);
|
95 |
-
|
96 |
-
const start = formatTimestamp(result.bg);
|
97 |
-
const end = formatTimestamp(result.ed);
|
98 |
-
|
99 |
-
// 判断是否是新的一行
|
100 |
-
if (result.seg_id === lastSegId) {
|
101 |
-
// 如果 seg_id 相同,更新该行内容
|
102 |
-
const existingEntry = box.querySelector(`.entry[data-seg-id="${result.seg_id}"]`);
|
103 |
-
if (existingEntry) {
|
104 |
-
const translationDiv = existingEntry.querySelector('.translation');
|
105 |
-
translationDiv.innerHTML = result.tranContent;
|
106 |
-
}
|
107 |
-
} else {
|
108 |
-
// 如果 seg_id 不同,表示是新的行,添加新行
|
109 |
-
entry.setAttribute('data-seg-id', result.seg_id); // 设置 seg_id
|
110 |
-
entry.innerHTML = `
|
111 |
-
<div class="original">${result.context}</div>
|
112 |
-
<div class="translation">${result.tranContent}</div>
|
113 |
-
`;
|
114 |
-
box.appendChild(entry);
|
115 |
-
}
|
116 |
-
|
117 |
-
// 更新 lastSegId 以便下一次判断
|
118 |
-
lastSegId = result.seg_id;
|
119 |
-
}
|
120 |
-
|
121 |
-
|
122 |
|
123 |
-
|
124 |
-
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
125 |
-
const audioContext = new AudioContext({ sampleRate: 16000 });
|
126 |
-
const source = audioContext.createMediaStreamSource(stream);
|
127 |
-
const processor = audioContext.createScriptProcessor(4096, 1, 1);
|
128 |
-
|
129 |
-
const wsUrl = "ws://localhost:9090?from=zh&to=en";
|
130 |
-
ws = new WebSocket(wsUrl);
|
131 |
-
|
132 |
-
ws.binaryType = "arraybuffer";
|
133 |
-
|
134 |
-
ws.onopen = () => {
|
135 |
-
console.log("WebSocket opened");
|
136 |
-
source.connect(processor);
|
137 |
-
processor.connect(audioContext.destination);
|
138 |
-
|
139 |
-
processor.onaudioprocess = (e) => {
|
140 |
-
const input = e.inputBuffer.getChannelData(0);
|
141 |
-
const buffer = new Int16Array(input.length);
|
142 |
-
for (let i = 0; i < input.length; i++) {
|
143 |
-
buffer[i] = Math.max(-1, Math.min(1, input[i])) * 0x7FFF;
|
144 |
-
}
|
145 |
-
ws.send(buffer);
|
146 |
-
};
|
147 |
-
};
|
148 |
-
|
149 |
-
ws.onmessage = (event) => {
|
150 |
-
try {
|
151 |
-
const msg = JSON.parse(event.data);
|
152 |
-
if (msg.result) {
|
153 |
-
addTranslation(msg.result);
|
154 |
-
}
|
155 |
-
} catch (e) {
|
156 |
-
console.error("Parse error:", e);
|
157 |
-
}
|
158 |
-
};
|
159 |
-
|
160 |
-
ws.onerror = (e) => console.error("WebSocket error:", e);
|
161 |
-
ws.onclose = () => {
|
162 |
-
console.log("WebSocket closed");
|
163 |
-
processor.disconnect();
|
164 |
-
source.disconnect();
|
165 |
-
};
|
166 |
-
}
|
167 |
-
</script>
|
168 |
-
</body>
|
169 |
</html>
|
|
|
1 |
+
<!doctype html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<link rel="icon" type="image/svg+xml" href="./favicon.ico" />
|
6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7 |
+
<title>Translator</title>
|
8 |
+
<script type="module" crossorigin src="./assets/index-d7a7d66c.js"></script>
|
9 |
+
<link rel="stylesheet" href="./assets/index-a48dc1a0.css">
|
10 |
+
</head>
|
11 |
+
<body>
|
12 |
+
<div id="app"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
|
14 |
+
</body>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
</html>
|
frontend/index2.html
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="zh">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<title>试试翻译</title>
|
6 |
+
<style>
|
7 |
+
body {
|
8 |
+
background-color: #f9f9fc;
|
9 |
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
|
10 |
+
margin: 0;
|
11 |
+
padding: 2rem;
|
12 |
+
}
|
13 |
+
.translation-box {
|
14 |
+
background: #f2f2f8;
|
15 |
+
border-radius: 12px;
|
16 |
+
padding: 1.5rem;
|
17 |
+
max-width: 800px;
|
18 |
+
margin: 0 auto;
|
19 |
+
min-height: 200px;
|
20 |
+
}
|
21 |
+
.entry {
|
22 |
+
margin-bottom: 1.5rem;
|
23 |
+
}
|
24 |
+
.timestamp {
|
25 |
+
font-size: 0.75rem;
|
26 |
+
color: #999;
|
27 |
+
}
|
28 |
+
.original {
|
29 |
+
font-size: 1rem;
|
30 |
+
color: #333;
|
31 |
+
}
|
32 |
+
.translation {
|
33 |
+
font-size: 1rem;
|
34 |
+
font-weight: bold;
|
35 |
+
color: #000;
|
36 |
+
}
|
37 |
+
.footer {
|
38 |
+
display: flex;
|
39 |
+
justify-content: space-between;
|
40 |
+
align-items: center;
|
41 |
+
margin-top: 2rem;
|
42 |
+
}
|
43 |
+
.lang-select {
|
44 |
+
background: white;
|
45 |
+
border-radius: 9999px;
|
46 |
+
padding: 0.4rem 1rem;
|
47 |
+
border: none;
|
48 |
+
font-size: 1rem;
|
49 |
+
box-shadow: 0 0 0 1px #ddd;
|
50 |
+
}
|
51 |
+
.record-button {
|
52 |
+
background-color: #1e40af;
|
53 |
+
color: white;
|
54 |
+
border: none;
|
55 |
+
padding: 0.6rem 1.2rem;
|
56 |
+
border-radius: 9999px;
|
57 |
+
font-size: 1rem;
|
58 |
+
cursor: pointer;
|
59 |
+
}
|
60 |
+
</style>
|
61 |
+
</head>
|
62 |
+
<body>
|
63 |
+
<div class="translation-box" id="translationBox">
|
64 |
+
<!-- 实时内容将插入这里 -->
|
65 |
+
</div>
|
66 |
+
|
67 |
+
<div class="footer">
|
68 |
+
<select class="lang-select">
|
69 |
+
<option>中文 » 英语</option>
|
70 |
+
</select>
|
71 |
+
<button class="record-button" onclick="startRecording()">🎤 录音</button>
|
72 |
+
</div>
|
73 |
+
|
74 |
+
<script>
|
75 |
+
let ws;
|
76 |
+
let mediaRecorder;
|
77 |
+
|
78 |
+
function formatTimestamp(ms) {
|
79 |
+
const sec = ms / 1000;
|
80 |
+
const min = Math.floor(sec / 60);
|
81 |
+
const s = (sec % 60).toFixed(1);
|
82 |
+
return `${String(min).padStart(2, '0')}:${s.padStart(4, '0')}`;
|
83 |
+
}
|
84 |
+
|
85 |
+
let lastSegId = null; // 用于存储上一个 seg_id
|
86 |
+
|
87 |
+
function addTranslation(result) {
|
88 |
+
const box = document.getElementById('translationBox');
|
89 |
+
|
90 |
+
// 创建一个新的 div 来显示翻译
|
91 |
+
const entry = document.createElement('div');
|
92 |
+
entry.className = 'entry';
|
93 |
+
|
94 |
+
console.log(result);
|
95 |
+
|
96 |
+
const start = formatTimestamp(result.bg);
|
97 |
+
const end = formatTimestamp(result.ed);
|
98 |
+
|
99 |
+
// 判断是否是新的一行
|
100 |
+
if (result.seg_id === lastSegId) {
|
101 |
+
// 如果 seg_id 相同,更新该行内容
|
102 |
+
const existingEntry = box.querySelector(`.entry[data-seg-id="${result.seg_id}"]`);
|
103 |
+
if (existingEntry) {
|
104 |
+
const translationDiv = existingEntry.querySelector('.translation');
|
105 |
+
translationDiv.innerHTML = result.tranContent;
|
106 |
+
}
|
107 |
+
} else {
|
108 |
+
// 如果 seg_id 不同,表示是新的行,添加新行
|
109 |
+
entry.setAttribute('data-seg-id', result.seg_id); // 设置 seg_id
|
110 |
+
entry.innerHTML = `
|
111 |
+
<div class="original">${result.context}</div>
|
112 |
+
<div class="translation">${result.tranContent}</div>
|
113 |
+
`;
|
114 |
+
box.appendChild(entry);
|
115 |
+
}
|
116 |
+
|
117 |
+
// 更新 lastSegId 以便下一次判断
|
118 |
+
lastSegId = result.seg_id;
|
119 |
+
}
|
120 |
+
|
121 |
+
|
122 |
+
|
123 |
+
async function startRecording() {
|
124 |
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
125 |
+
const audioContext = new AudioContext({ sampleRate: 16000 });
|
126 |
+
const source = audioContext.createMediaStreamSource(stream);
|
127 |
+
const processor = audioContext.createScriptProcessor(4096, 1, 1);
|
128 |
+
|
129 |
+
const wsUrl = "ws://localhost:9090?from=zh&to=en";
|
130 |
+
ws = new WebSocket(wsUrl);
|
131 |
+
|
132 |
+
ws.binaryType = "arraybuffer";
|
133 |
+
|
134 |
+
ws.onopen = () => {
|
135 |
+
console.log("WebSocket opened");
|
136 |
+
source.connect(processor);
|
137 |
+
processor.connect(audioContext.destination);
|
138 |
+
|
139 |
+
processor.onaudioprocess = (e) => {
|
140 |
+
const input = e.inputBuffer.getChannelData(0);
|
141 |
+
const buffer = new Int16Array(input.length);
|
142 |
+
for (let i = 0; i < input.length; i++) {
|
143 |
+
buffer[i] = Math.max(-1, Math.min(1, input[i])) * 0x7FFF;
|
144 |
+
}
|
145 |
+
ws.send(buffer);
|
146 |
+
};
|
147 |
+
};
|
148 |
+
|
149 |
+
ws.onmessage = (event) => {
|
150 |
+
try {
|
151 |
+
const msg = JSON.parse(event.data);
|
152 |
+
if (msg.result) {
|
153 |
+
addTranslation(msg.result);
|
154 |
+
}
|
155 |
+
} catch (e) {
|
156 |
+
console.error("Parse error:", e);
|
157 |
+
}
|
158 |
+
};
|
159 |
+
|
160 |
+
ws.onerror = (e) => console.error("WebSocket error:", e);
|
161 |
+
ws.onclose = () => {
|
162 |
+
console.log("WebSocket closed");
|
163 |
+
processor.disconnect();
|
164 |
+
source.disconnect();
|
165 |
+
};
|
166 |
+
}
|
167 |
+
</script>
|
168 |
+
</body>
|
169 |
+
</html>
|
transcribe/strategy.py
CHANGED
@@ -118,6 +118,7 @@ def segments_split(segments, audio_buffer: np.ndarray, sample_rate=16000):
|
|
118 |
is_end = False
|
119 |
|
120 |
for idx, seg in enumerate(segments):
|
|
|
121 |
left_watch_sequences.append(seg)
|
122 |
if seg.text and seg.text[-1] in markers:
|
123 |
seg_index = int(seg.t1 / 100 * sample_rate)
|
|
|
118 |
is_end = False
|
119 |
|
120 |
for idx, seg in enumerate(segments):
|
121 |
+
# print('>>>>>>>>>>>>>>>> seg : ', seg)
|
122 |
left_watch_sequences.append(seg)
|
123 |
if seg.text and seg.text[-1] in markers:
|
124 |
seg_index = int(seg.t1 / 100 * sample_rate)
|