stnh70 commited on
Commit
e2a4c95
·
verified ·
1 Parent(s): 498d2c5

Create subtitle.ts

Browse files
Files changed (1) hide show
  1. subtitle.ts +733 -0
subtitle.ts ADDED
@@ -0,0 +1,733 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { WebSocketServer } from "https://deno.land/x/websocket@v0.1.4/mod.ts";
2
+ import { LRU } from "https://deno.land/x/lru@1.0.2/mod.ts";
3
+ import { franc } from 'https://esm.sh/franc-min@6.1.0';
4
+
5
+ // 全局配置
6
+ // https://deeplx.missuo.ru/translate?key=mMZa2BuFpZiTULngWvlw3DVTP3u4e3W-ARinoADluhA=
7
+ // https://api.deeplx.org/MN2ioAtWVa42Z_RyQBYaN2pOXUbdgygDdX09YnnuH9s/translate
8
+ // https://rnxivywi.deploy.cx/translate
9
+ const config = {
10
+ DEEPLX_API_URL: "https://api.deeplx.org/lmhZMS6wSPgyvEdVl0TJREt4PeYOCBLqIqDauwtaaoI/translate",
11
+ BATCH_SIZE: 15,
12
+ SUBTITLE_SEPARATOR: "\n",
13
+ SUBTITLE_MARKER: "‖",
14
+ OPTIMAL_TEXT_LENGTH: 1000,
15
+ DELAY_BETWEEN_REQUESTS: 1000,
16
+ INITIAL_BATCH_SIZE: 20,
17
+ SEND_REPORTS: false,
18
+ ALERT_THRESHOLD: 1000, // 毫秒
19
+ NTFY_TOPIC: "aston",
20
+ NTFY_URL: "https://ntfy.sh/aston",
21
+ LANGUAGE_DETECTION_SAMPLE_SIZE: 10,
22
+ LANGUAGE_DETECTION_THRESHOLD: 7,
23
+ USE_ADAPTIVE_RATE_LIMITER: true, // 新增:控制是否使用AdaptiveRateLimiter
24
+ };
25
+
26
+ // 语言代码映射
27
+ const languageCodeMapping: { [key: string]: string } = {
28
+ 'cmn': 'ZH', // 简体中文
29
+ 'zho': 'ZH', // 中文(通用)
30
+ 'yue': 'ZH-TW', // 粤语,映射到繁体中文
31
+ 'eng': 'EN', // 英语
32
+ 'jpn': 'JA', // 日语
33
+ 'kor': 'KO', // 韩语
34
+ 'fra': 'FR', // 法语
35
+ 'deu': 'DE', // 德语
36
+ 'spa': 'ES', // 西班牙语
37
+ 'rus': 'RU', // 俄语
38
+ 'por': 'PT', // 葡萄牙语
39
+ 'ita': 'IT', // 意大利语
40
+ 'nld': 'NL', // 荷兰语
41
+ 'pol': 'PL', // 波兰语
42
+ 'bul': 'BG', // 保加利亚语
43
+ 'ces': 'CS', // 捷克语
44
+ 'dan': 'DA', // 丹麦语
45
+ 'ell': 'EL', // 希腊语
46
+ 'est': 'ET', // 爱沙尼亚语
47
+ 'fin': 'FI', // 芬兰语
48
+ 'hun': 'HU', // 匈牙利语
49
+ 'ind': 'ID', // 印度尼西亚语
50
+ 'lit': 'LT', // 立陶宛语
51
+ 'lav': 'LV', // 拉脱维亚语
52
+ 'nob': 'NB', // 挪威语(博克马尔语)
53
+ 'nno': 'NB', // 挪威语(尼诺斯克语)
54
+ 'ron': 'RO', // 罗马尼亚语
55
+ 'slk': 'SK', // 斯洛伐克语
56
+ 'slv': 'SL', // 斯洛文尼亚语
57
+ 'swe': 'SV', // 瑞典语
58
+ 'tur': 'TR', // 土耳其语
59
+ 'ukr': 'UK', // 乌克兰语
60
+ };
61
+
62
+ // 缓存配置
63
+ const translationCache = new LRU<string, string>(1000);
64
+
65
+ // 接口定义
66
+ interface SubtitleEntry {
67
+ id: string;
68
+ startTime: number;
69
+ endTime: number;
70
+ text: string;
71
+ translatedText?: string;
72
+ originalSubtitles?: SubtitleEntry[];
73
+ }
74
+
75
+ // AdaptiveRateLimiter 实现
76
+ // class AdaptiveRateLimiter {
77
+ // private queue: (() => Promise<void>)[] = [];
78
+ // private running = 0;
79
+ // private maxConcurrent = 8;
80
+ // private minInterval = 1000; // ms
81
+ // private lastRunTime = 0;
82
+
83
+ // async schedule<T>(fn: () => Promise<T>): Promise<T> {
84
+ // return new Promise((resolve, reject) => {
85
+ // this.queue.push(async () => {
86
+ // try {
87
+ // resolve(await fn());
88
+ // } catch (err) {
89
+ // if (err.status === 429) { // Too Many Requests
90
+ // this.adjustLimits();
91
+ // }
92
+ // reject(err);
93
+ // }
94
+ // });
95
+ // this.runNext();
96
+ // });
97
+ // }
98
+
99
+ // private async runNext() {
100
+ // if (this.running >= this.maxConcurrent || this.queue.length === 0) return;
101
+
102
+ // const now = Date.now();
103
+ // if (now - this.lastRunTime < this.minInterval) {
104
+ // setTimeout(() => this.runNext(), this.minInterval - (now - this.lastRunTime));
105
+ // return;
106
+ // }
107
+
108
+ // this.running++;
109
+ // this.lastRunTime = now;
110
+ // const next = this.queue.shift();
111
+ // if (next) {
112
+ // try {
113
+ // await next();
114
+ // } finally {
115
+ // this.running--;
116
+ // this.runNext();
117
+ // }
118
+ // }
119
+ // }
120
+
121
+ // private adjustLimits() {
122
+ // this.maxConcurrent = Math.max(1, this.maxConcurrent - 1);
123
+ // this.minInterval += 500;
124
+ // console.log(`[AdaptiveRateLimiter] Adjusted limits: maxConcurrent=${this.maxConcurrent}, minInterval=${this.minInterval}ms`);
125
+ // }
126
+ // }
127
+
128
+ class AdaptiveRateLimiter {
129
+ private queue: Array<{
130
+ fn: () => Promise<any>,
131
+ resolve: (value: any) => void,
132
+ reject: (reason?: any) => void
133
+ }> = [];
134
+ private running = 0;
135
+ private maxConcurrent = 8;
136
+ private minInterval = 1000; // ms
137
+ private lastRunTime = 0;
138
+
139
+ async schedule<T>(fn: () => Promise<T>): Promise<T> {
140
+ return new Promise((resolve, reject) => {
141
+ this.queue.push({ fn, resolve, reject });
142
+ this.runNext();
143
+ });
144
+ }
145
+
146
+ private async runNext() {
147
+ if (this.running >= this.maxConcurrent || this.queue.length === 0) return;
148
+
149
+ const now = Date.now();
150
+ if (now - this.lastRunTime < this.minInterval) {
151
+ setTimeout(() => this.runNext(), this.minInterval - (now - this.lastRunTime));
152
+ return;
153
+ }
154
+
155
+ this.running++;
156
+ this.lastRunTime = now;
157
+ const next = this.queue.shift();
158
+ if (next) {
159
+ try {
160
+ console.log(`[AdaptiveRateLimiter] 开始执行请求`);
161
+ const result = await next.fn();
162
+ console.log(`[AdaptiveRateLimiter] 完成请求`);
163
+ next.resolve(result);
164
+ } catch (err) {
165
+ if (err.status === 429) { // Too Many Requests
166
+ this.adjustLimits();
167
+ }
168
+ next.reject(err);
169
+ } finally {
170
+ this.running--;
171
+ this.runNext();
172
+ }
173
+ }
174
+ }
175
+
176
+ private adjustLimits() {
177
+ this.maxConcurrent = Math.max(1, this.maxConcurrent - 1);
178
+ this.minInterval += 500;
179
+ console.log(`[AdaptiveRateLimiter] Adjusted limits: maxConcurrent=${this.maxConcurrent}, minInterval=${this.minInterval}ms`);
180
+ }
181
+ }
182
+
183
+ const rateLimiter = new AdaptiveRateLimiter();
184
+
185
+ // 性能监控模块
186
+ class PerformanceMonitor {
187
+ private samples: Map<string, number[]> = new Map();
188
+ private readonly sampleSize = 10;
189
+ private dailyData: number[] = [];
190
+
191
+ private updateMetric(metric: string, value: number) {
192
+ if (!this.samples.has(metric)) {
193
+ this.samples.set(metric, []);
194
+ }
195
+ const samples = this.samples.get(metric)!;
196
+ samples.push(value);
197
+ if (samples.length > this.sampleSize) {
198
+ samples.shift();
199
+ }
200
+ }
201
+
202
+ private getAverageMetric(metric: string): number {
203
+ const samples = this.samples.get(metric) || [];
204
+ if (samples.length === 0) return 0;
205
+ return samples.reduce((a, b) => a + b, 0) / samples.length;
206
+ }
207
+
208
+ updateApiResponseTime(responseTime: number) {
209
+ this.updateMetric('apiResponseTime', responseTime);
210
+ this.dailyData.push(responseTime);
211
+ }
212
+
213
+ getAverageApiResponseTime(): number {
214
+ return this.getAverageMetric('apiResponseTime');
215
+ }
216
+
217
+ getDailyData(): number[] {
218
+ return [...this.dailyData];
219
+ }
220
+
221
+ clearDailyData() {
222
+ this.dailyData = [];
223
+ }
224
+
225
+ logPerformanceMetrics() {
226
+ console.log(`[Performance] Average API Response Time: ${this.getAverageApiResponseTime().toFixed(2)}ms`);
227
+ }
228
+ }
229
+
230
+ const performanceMonitor = new PerformanceMonitor();
231
+
232
+ // 性能分析器
233
+ class PerformanceAnalyzer {
234
+ static async analyzeDailyPerformance(performanceData: number[]) {
235
+ const avgResponseTime = performanceData.reduce((a, b) => a + b, 0) / performanceData.length;
236
+ const maxResponseTime = Math.max(...performanceData);
237
+
238
+ console.log(`每日性能报告:`);
239
+ console.log(`平均响应时间:${avgResponseTime.toFixed(2)}ms`);
240
+ console.log(`最大响应时间:${maxResponseTime}ms`);
241
+
242
+ if (config.SEND_REPORTS) {
243
+ await this.sendAlert(`每日性能报告:平均响应时间:${avgResponseTime.toFixed(2)}ms,最大响应时间:${maxResponseTime}ms`);
244
+ }
245
+
246
+ if (avgResponseTime > config.ALERT_THRESHOLD) {
247
+ await this.sendAlert(`警告:平均响应时间 (${avgResponseTime.toFixed(2)}ms) 超过阈值`);
248
+ }
249
+ }
250
+
251
+ private static async sendAlert(message: string) {
252
+ const title = "字幕翻译服务性能报告";
253
+
254
+ try {
255
+ const response = await fetch(config.NTFY_URL, {
256
+ method: 'POST',
257
+ headers: {
258
+ 'Content-Type': 'application/json',
259
+ },
260
+ body: JSON.stringify({
261
+ topic: config.NTFY_TOPIC,
262
+ title: title,
263
+ message: message,
264
+ priority: 3,
265
+ }),
266
+ });
267
+
268
+ if (response.ok) {
269
+ console.log("警报发送成功");
270
+ } else {
271
+ console.error("发送警报失败:", response.statusText);
272
+ }
273
+ } catch (error) {
274
+ console.error("发送警报时出错:", error);
275
+ }
276
+ }
277
+ }
278
+
279
+ async function translateWithDeepLX(
280
+ text: string,
281
+ sourceLanguage: string,
282
+ targetLanguage: string,
283
+ subtitleId: string,
284
+ signal: AbortSignal
285
+ ): Promise<string> {
286
+ const cacheKey = `${sourceLanguage}-${targetLanguage}-${subtitleId}-${text}`;
287
+ console.log(`[DeepLX] 尝试翻译文本 (subtitleId: ${subtitleId})`);
288
+ const cachedTranslation = translationCache.get(cacheKey);
289
+ if (cachedTranslation) {
290
+ console.log(`[DeepLX] 使用缓存的翻译结果 (subtitleId: ${subtitleId})`);
291
+ return cachedTranslation;
292
+ }
293
+
294
+ const translate = async () => {
295
+ if (signal.aborted) {
296
+ console.log(`[DeepLX] 翻译被中止 (subtitleId: ${subtitleId})`);
297
+ throw new Error("Translation aborted");
298
+ }
299
+ try {
300
+ console.log(`[DeepLX] 等待 ${config.DELAY_BETWEEN_REQUESTS}ms 后发送请求 (subtitleId: ${subtitleId})`);
301
+ await new Promise(resolve => setTimeout(resolve, config.DELAY_BETWEEN_REQUESTS));
302
+
303
+ console.log(`[DeepLX] 发送翻译请求到 API (subtitleId: ${subtitleId})`);
304
+ const startTime = Date.now();
305
+ const response = await fetch(config.DEEPLX_API_URL, {
306
+ method: "POST",
307
+ headers: { "Content-Type": "application/json" },
308
+ body: JSON.stringify({
309
+ text,
310
+ source_lang: sourceLanguage,
311
+ target_lang: targetLanguage,
312
+ }),
313
+ signal,
314
+ });
315
+ const endTime = Date.now();
316
+ performanceMonitor.updateApiResponseTime(endTime - startTime);
317
+
318
+ if (!response.ok) {
319
+ if (response.status === 429) {
320
+ console.warn(`[DeepLX] 遇到限流,等待后重试 (subtitleId: ${subtitleId})`);
321
+ await new Promise(resolve => setTimeout(resolve, 5000));
322
+ return translateWithDeepLX(text, sourceLanguage, targetLanguage, subtitleId, signal);
323
+ }
324
+ throw new Error(`DeepLX API error: ${response.statusText}`);
325
+ }
326
+
327
+ const result = await response.json();
328
+ const translation = result.data;
329
+ console.log(`[DeepLX] 翻译成功: ${translation.substring(0, 50)}... (subtitleId: ${subtitleId})`);
330
+
331
+ const markerRegex = new RegExp(`${config.SUBTITLE_MARKER}.*?${config.SUBTITLE_MARKER}`, 'g');
332
+ const markers = text.match(markerRegex) || [];
333
+ let translatedTextWithMarkers = translation;
334
+ markers.forEach((marker, index) => {
335
+ translatedTextWithMarkers = translatedTextWithMarkers.replace(
336
+ new RegExp(`^(.{${index * marker.length}})(.*)`, 's'),
337
+ `$1${marker}$2`
338
+ );
339
+ });
340
+
341
+ translationCache.set(cacheKey, translatedTextWithMarkers);
342
+ return translatedTextWithMarkers;
343
+ } catch (error) {
344
+ console.error(`[DeepLX] 翻译失败 (subtitleId: ${subtitleId}):`, error);
345
+ throw error;
346
+ }
347
+ };
348
+
349
+ if (config.USE_ADAPTIVE_RATE_LIMITER) {
350
+ return rateLimiter.schedule(translate);
351
+ } else {
352
+ return translate();
353
+ }
354
+ }
355
+
356
+ async function translateWithFallback(
357
+ text: string,
358
+ sourceLanguage: string,
359
+ targetLanguage: string,
360
+ subtitleId: string,
361
+ signal: AbortSignal
362
+ ): Promise<string> {
363
+ try {
364
+ console.log(`[Fallback] 尝试整体翻译 (subtitleId: ${subtitleId})`);
365
+ return await translateWithDeepLX(text, sourceLanguage, targetLanguage, subtitleId, signal);
366
+ } catch (error) {
367
+ console.error(`[Fallback] 整体翻译失败 (subtitleId: ${subtitleId}):`, error);
368
+ if (signal.aborted) {
369
+ console.log(`[Fallback] 翻译被中止 (subtitleId: ${subtitleId})`);
370
+ throw new Error("Translation aborted");
371
+ }
372
+ const parts = text.split(config.SUBTITLE_SEPARATOR);
373
+ console.log(`[Fallback] 尝试单独翻译 ${parts.length} 个部分 (subtitleId: ${subtitleId})`);
374
+ const translatedParts = [];
375
+ for (let i = 0; i < parts.length; i++) {
376
+ if (signal.aborted) {
377
+ console.log(`[Fallback] 翻译过程中被中止 (subtitleId: ${subtitleId})`);
378
+ throw new Error("Translation aborted");
379
+ }
380
+ try {
381
+ const partId = `${subtitleId}-part${i}`;
382
+ const translatedPart = await translateWithDeepLX(parts[i], sourceLanguage, targetLanguage, partId, signal);
383
+ translatedParts.push(translatedPart);
384
+ } catch (partError) {
385
+ console.error(`[Fallback] 部分翻译失败 (subtitleId: ${subtitleId}, part: ${i}):`, partError);
386
+ translatedParts.push(`[翻译失败] ${parts[i]}`);
387
+ }
388
+ }
389
+ console.log(`[Fallback] 单独翻译完成 (subtitleId: ${subtitleId})`);
390
+ return translatedParts.join(config.SUBTITLE_SEPARATOR);
391
+ }
392
+ }
393
+
394
+ function initializeSubtitles(subtitles: SubtitleEntry[]): SubtitleEntry[] {
395
+ return subtitles;
396
+ }
397
+
398
+ function mergeSubtitles(subtitles: SubtitleEntry[]): SubtitleEntry[] {
399
+ console.log(`[Merger] 开始合并 ${subtitles.length} 条字幕`);
400
+ const mergedSubtitles: SubtitleEntry[] = [];
401
+ let currentGroup: SubtitleEntry[] = [];
402
+ let currentLength = 0;
403
+
404
+ for (const subtitle of subtitles) {
405
+ if (currentLength + subtitle.text.length > config.OPTIMAL_TEXT_LENGTH && currentGroup.length > 0) {
406
+ mergedSubtitles.push(mergeGroup(currentGroup));
407
+ currentGroup = [];
408
+ currentLength = 0;
409
+ }
410
+ currentGroup.push(subtitle);
411
+ currentLength += subtitle.text.length;
412
+ }
413
+
414
+ if (currentGroup.length > 0) {
415
+ mergedSubtitles.push(mergeGroup(currentGroup));
416
+ }
417
+
418
+ console.log(`[Merger] 合并完成,得到 ${mergedSubtitles.length} 个合并组`);
419
+ return mergedSubtitles;
420
+ }
421
+
422
+ function mergeGroup(group: SubtitleEntry[]): SubtitleEntry {
423
+ const mergedText = group.map(sub => sub.text.replace(/\n/g, '<br>')).join(config.SUBTITLE_SEPARATOR);
424
+ return {
425
+ id: `merged_${group[0].id}_to_${group[group.length - 1].id}`,
426
+ startTime: group[0].startTime,
427
+ endTime: group[group.length - 1].endTime,
428
+ text: mergedText,
429
+ originalSubtitles: group
430
+ };
431
+ }
432
+
433
+ function splitMergedSubtitle(mergedSubtitle: SubtitleEntry): SubtitleEntry[] {
434
+ console.log(`[Splitter] 拆分合并的字幕: ${mergedSubtitle.id}`);
435
+ const translatedText = mergedSubtitle.translatedText || '';
436
+ const originalSubtitles = mergedSubtitle.originalSubtitles || [];
437
+ const translatedParts = translatedText.split(config.SUBTITLE_SEPARATOR);
438
+
439
+ return originalSubtitles.map((original, index) => {
440
+ const translatedPart = translatedParts[index] || '';
441
+ return {
442
+ ...original,
443
+ translatedText: translatedPart || `[翻译失败] ${original.text}`
444
+ };
445
+ });
446
+ }
447
+
448
+ async function handleWebSocket(ws: WebSocket) {
449
+ console.log("[WebSocket] 新的 WebSocket 连接已建立");
450
+ let subtitles: SubtitleEntry[] = [];
451
+ let heartbeatInterval: number;
452
+ let isTranslating = false;
453
+ let isConnected = true;
454
+ let abortController: AbortController | null = null;
455
+ const translatedSubtitleIds = new Set<string>();
456
+ let shouldStopTranslation = false;
457
+
458
+ const heartbeat = () => {
459
+ if (ws.readyState === WebSocket.OPEN) {
460
+ ws.send(JSON.stringify({ action: "heartbeat" }));
461
+ } else {
462
+ clearInterval(heartbeatInterval);
463
+ }
464
+ };
465
+
466
+ ws.on("open", () => {
467
+ console.log("[WebSocket] 连接已打开");
468
+ heartbeatInterval = setInterval(heartbeat, 30000);
469
+ console.log("[WebSocket] 心跳机制已启动");
470
+ });
471
+
472
+ async function stopTranslation() {
473
+ if (abortController) {
474
+ abortController.abort();
475
+ abortController = null;
476
+ }
477
+ isTranslating = false;
478
+ shouldStopTranslation = false;
479
+ console.log("[WebSocket] 翻译已停止");
480
+ if (isConnected) {
481
+ ws.send(JSON.stringify({ action: "translationStopped" }));
482
+ }
483
+ }
484
+
485
+ ws.on("message", async (message: string) => {
486
+ if (!isConnected) return;
487
+
488
+ try {
489
+ const data = JSON.parse(message);
490
+
491
+ switch (data.action) {
492
+ case "initialize":
493
+ subtitles = initializeSubtitles(data.subtitles);
494
+ translatedSubtitleIds.clear();
495
+ isTranslating = false;
496
+ if (!Array.isArray(subtitles) || subtitles.length === 0) {
497
+ throw new Error("无效的字幕数组");
498
+ }
499
+ console.log(`[WebSocket] 初始化字幕数组,长度: ${subtitles.length}`);
500
+ ws.send(JSON.stringify({ action: "initialized" }));
501
+ break;
502
+
503
+ case "translate":
504
+ console.log("[WebSocket] 收到翻译请求");
505
+ if (isTranslating) {
506
+ console.log("[WebSocket] 翻译已在进行中,忽略新的请求");
507
+ return;
508
+ }
509
+
510
+ const { timestamp, sourceLanguage, targetLanguage } = data;
511
+ const currentTime = typeof timestamp === 'number' ? timestamp : parseFloat(timestamp);
512
+
513
+ console.log(`[WebSocket] 开始翻译,时间戳: ${currentTime}, 源语言: ${sourceLanguage}, 目标语言: ${targetLanguage}`);
514
+
515
+ isTranslating = true;
516
+ shouldStopTranslation = false;
517
+ abortController = new AbortController();
518
+ const signal = abortController.signal;
519
+
520
+ const subtitlesToTranslate = subtitles.filter(sub => sub.startTime >= currentTime && !translatedSubtitleIds.has(sub.id));
521
+ console.log(`[Translator] 筛选出 ${subtitlesToTranslate.length} 条字幕需要翻译`);
522
+
523
+ // Language detection logic
524
+ const sampleSize = Math.min(config.LANGUAGE_DETECTION_SAMPLE_SIZE, subtitlesToTranslate.length);
525
+ const samples = subtitlesToTranslate.slice(0, sampleSize);
526
+ const detectedLanguages = samples.map(sample => {
527
+ const detectedCode = franc(sample.text) as string;
528
+ return languageCodeMapping[detectedCode] || detectedCode;
529
+ });
530
+
531
+ console.log(`[Translator] 检测到的语言: ${detectedLanguages.join(', ')}`);
532
+
533
+ const validDetections = detectedLanguages.filter(lang => lang !== 'und');
534
+ const targetLangCount = validDetections.filter(lang => lang === targetLanguage).length;
535
+ const detectionThreshold = Math.max(1, Math.floor(validDetections.length * 0.6));
536
+
537
+ const detectedLanguage = validDetections.length > 0
538
+ ? validDetections.reduce((a, b, i, arr) =>
539
+ arr.filter(v => v === a).length >= arr.filter(v => v === b).length ? a : b
540
+ )
541
+ : 'unknown';
542
+
543
+ if (targetLangCount >= detectionThreshold) {
544
+ console.log(`[Translator] 检测到字幕主要是目标语言 (${targetLanguage}),跳过翻译`);
545
+ ws.send(JSON.stringify({
546
+ action: "languageDetected",
547
+ language: detectedLanguage,
548
+ message: "Source language matches target language, translation skipped"
549
+ }));
550
+ ws.send(JSON.stringify({ action: "translationComplete" }));
551
+ isTranslating = false;
552
+ abortController = null;
553
+ } else {
554
+ console.log(`[Translator] 检测到字幕不是目标语言,继续翻译`);
555
+
556
+ ws.send(JSON.stringify({
557
+ action: "languageDetected",
558
+ language: "different",
559
+ message: "Source language differs from target language, proceeding with translation"
560
+ }));
561
+
562
+ try {
563
+ const initialBatch = subtitlesToTranslate.slice(0, config.INITIAL_BATCH_SIZE);
564
+ console.log(`[Translator] 开始初始快速翻译,包含 ${initialBatch.length} 条字幕`);
565
+
566
+ const translatedInitialBatch = await Promise.all(initialBatch.map(async (item) => {
567
+ if (shouldStopTranslation) throw new Error("Translation stopped");
568
+ const translatedText = await translateWithFallback(
569
+ item.text,
570
+ sourceLanguage,
571
+ targetLanguage,
572
+ item.id,
573
+ signal
574
+ );
575
+ translatedSubtitleIds.add(item.id);
576
+ return { ...item, translatedText };
577
+ }));
578
+
579
+ if (isConnected && !shouldStopTranslation) {
580
+ console.log(`[WebSocket] 发送初始快速翻译结果,包含 ${translatedInitialBatch.length} 条字幕`);
581
+ ws.send(JSON.stringify({
582
+ action: "translationResult",
583
+ subtitles: translatedInitialBatch
584
+ }));
585
+ }
586
+
587
+ console.log(`[Translator] 合并前的字幕数量: ${subtitlesToTranslate.length}`);
588
+ const remainingSubtitles = subtitlesToTranslate.slice(config.INITIAL_BATCH_SIZE);
589
+ const mergedSubtitles = mergeSubtitles(remainingSubtitles);
590
+ console.log(`[Translator] 合并后的剩余字幕数量: ${mergedSubtitles.length}`);
591
+
592
+ for (let i = 0; i < mergedSubtitles.length; i += config.BATCH_SIZE) {
593
+ if (!isConnected || shouldStopTranslation) {
594
+ console.log("[Translator] 连接已断开或收到停止命令,停止翻译");
595
+ break;
596
+ }
597
+
598
+ const batch = mergedSubtitles.slice(i, i + config.BATCH_SIZE);
599
+ console.log(`[Translator] 处理批次 ${i / config.BATCH_SIZE + 1}, 包含 ${batch.length} 条合并字幕`);
600
+
601
+ const translatedBatch = await Promise.all(batch.map(async (item) => {
602
+ if (shouldStopTranslation) throw new Error("Translation stopped");
603
+ try {
604
+ console.log(`[Translator] 翻译文本: ${item.text.substring(0, 50)}...`);
605
+ const translatedText = await translateWithFallback(
606
+ item.text,
607
+ sourceLanguage,
608
+ targetLanguage,
609
+ item.id,
610
+ signal
611
+ );
612
+ console.log(`[Translator] 翻译完成: ${translatedText.substring(0, 50)}...`);
613
+ return { ...item, translatedText };
614
+ } catch (error) {
615
+ console.error(`[Translator] 翻译失败: ${error.message}`);
616
+ if (error.name === 'AbortError' || error.message === "Translation stopped") {
617
+ throw error;
618
+ }
619
+ return { ...item, translatedText: `[翻译失败] ${item.text}` };
620
+ }
621
+ }));
622
+
623
+ if (isConnected && !shouldStopTranslation) {
624
+ const distributedResults = translatedBatch.flatMap(splitMergedSubtitle);
625
+ distributedResults.forEach(sub => translatedSubtitleIds.add(sub.id));
626
+ console.log(`[WebSocket] 发送翻译结果,包含 ${distributedResults.length} 条字幕`);
627
+ ws.send(JSON.stringify({
628
+ action: "translationResult",
629
+ subtitles: distributedResults
630
+ }));
631
+ }
632
+ }
633
+
634
+ if (isConnected && !shouldStopTranslation) {
635
+ console.log(`[WebSocket] 翻译完成,共翻译 ${subtitlesToTranslate.length} 条字幕`);
636
+ ws.send(JSON.stringify({ action: "translationComplete" }));
637
+ }
638
+ } catch (error) {
639
+ console.error("[Translator] 翻译过程中出错:", error);
640
+ if (error.message === "Translation stopped") {
641
+ console.log("[Translator] 翻译被手动停止");
642
+ } else if (error.name !== 'AbortError' && isConnected) {
643
+ ws.send(JSON.stringify({ action: "error", message: error.message }));
644
+ }
645
+ } finally {
646
+ isTranslating = false;
647
+ abortController = null;
648
+ shouldStopTranslation = false;
649
+ }
650
+ }
651
+ performanceMonitor.logPerformanceMetrics();
652
+ break;
653
+
654
+ case "stopTranslation":
655
+ console.log("[WebSocket] 收到停止翻译请求");
656
+ shouldStopTranslation = true;
657
+ await stopTranslation();
658
+ break;
659
+
660
+ case "closeConnection":
661
+ console.log("[WebSocket] 收到关闭连接请求");
662
+ ws.close();
663
+ break;
664
+
665
+ case "heartbeatResponse":
666
+ console.log("[WebSocket] 收到心跳响应");
667
+ break;
668
+
669
+ case "heartbeat":
670
+ // console.log("[WebSocket] 收到客户端心跳");
671
+ ws.send(JSON.stringify({ action: "heartbeatResponse" }));
672
+ break;
673
+
674
+ case "setAdaptiveRateLimiter":
675
+ config.USE_ADAPTIVE_RATE_LIMITER = data.useAdaptiveRateLimiter;
676
+ console.log(`[WebSocket] 设置 AdaptiveRateLimiter: ${config.USE_ADAPTIVE_RATE_LIMITER ? '启用' : '禁用'}`);
677
+ ws.send(JSON.stringify({ action: "adaptiveRateLimiterSet", useAdaptiveRateLimiter: config.USE_ADAPTIVE_RATE_LIMITER }));
678
+ break;
679
+
680
+ default:
681
+ console.warn(`[WebSocket] 收到未知操作: ${data.action}`);
682
+ }
683
+ } catch (error) {
684
+ console.error("[WebSocket] 处理消息时出错:", error);
685
+ if (isConnected) {
686
+ ws.send(JSON.stringify({ action: "error", message: error.message }));
687
+ }
688
+ }
689
+ });
690
+
691
+ ws.on("close", () => {
692
+ console.log("[WebSocket] 连接已关闭");
693
+ isConnected = false;
694
+ clearInterval(heartbeatInterval);
695
+ if (isTranslating) {
696
+ stopTranslation();
697
+ }
698
+ });
699
+
700
+ ws.on("error", async (error) => {
701
+ console.error("[WebSocket] 发生错误:", error);
702
+ if (isTranslating) {
703
+ await stopTranslation();
704
+ }
705
+ });
706
+ }
707
+
708
+ // 设置WebSocket服务器
709
+ const wss = new WebSocketServer(8000);
710
+
711
+ wss.on("connection", (ws: WebSocket) => {
712
+ handleWebSocket(ws);
713
+ });
714
+
715
+ // 添加每日分析的定时器
716
+ const dailyAnalysisInterval = setInterval(async () => {
717
+ const dailyData = performanceMonitor.getDailyData();
718
+ await PerformanceAnalyzer.analyzeDailyPerformance(dailyData);
719
+ performanceMonitor.clearDailyData();
720
+ }, 24 * 60 * 60 * 1000); // 每24小时运行一次
721
+
722
+ // 确保在程序退出时清理定时器
723
+ Deno.addSignalListener("SIGINT", () => {
724
+ clearInterval(dailyAnalysisInterval);
725
+ // 其他清理代码...
726
+ console.log("正在关闭服务器...");
727
+ wss.close(() => {
728
+ console.log("WebSocket 服务器已关闭");
729
+ Deno.exit(0);
730
+ });
731
+ });
732
+
733
+ console.log("WebSocket 字幕翻译服务器正在运行,地址为 ws://localhost:8000");