@aim-packages/subtitle 0.0.19 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,1074 @@
1
+ # Aim Subtitle
2
+
3
+ 字幕处理工具库,支持多种字幕格式的解析、转换、优化和处理。
4
+
5
+ ## 📦 安装
6
+
7
+ ```bash
8
+ pnpm add @aim-packages/subtitle
9
+ ```
10
+
11
+ ## 🚀 快速开始
12
+
13
+ ```typescript
14
+ import { utils, parser, filter, tools } from '@aim-packages/subtitle';
15
+
16
+ // 时间格式化
17
+ const formattedTime = utils.formatTime(3661.5); // "01:01:01.500"
18
+
19
+ // 字幕格式转换
20
+ const segments = await parser.srtToAimSegments(srtContent);
21
+
22
+ // 语言检测
23
+ const language = tools.detection.detectLanguage("Hello world");
24
+
25
+ // 文本分割
26
+ const sentences = tools.segmentation.splitToSentences("Hello world. How are you?");
27
+
28
+ // 字幕优化
29
+ const optimizedSegments = tools.optimization.subtitleOptimization(segments, {
30
+ repeat: true,
31
+ emt: true,
32
+ zf: true,
33
+ space: true,
34
+ ep: true
35
+ });
36
+
37
+ // 文本过滤
38
+ const streamFilter = new filter.StreamFilter();
39
+ streamFilter.add("敏感词", "***");
40
+ const filteredText = streamFilter.feedAll("这是一个敏感词测试。");
41
+ ```
42
+
43
+ ## 📚 API 文档
44
+
45
+ ### 类型定义
46
+
47
+ #### `AimSegments` 接口
48
+
49
+ 这是字幕处理库的核心数据结构,用于表示单个字幕片段。
50
+
51
+ ```typescript
52
+ interface AimSegments {
53
+ // 通用字段
54
+ st: string; // 开始时间戳 (Start timestamp)
55
+ et: string; // 结束时间戳 (End timestamp)
56
+ text: string; // 文本内容 (Text content)
57
+ index?: number; // 字幕索引 (Subtitle index)
58
+ children?: Array<AimSegments>; // 子片段 (Child segments)
59
+ f?: number; // 批量文件转写时所属的文件索引 (File index)
60
+
61
+ // 视频剪辑字段
62
+ delete?: boolean; // 是否被删除 (Whether it is deleted)
63
+ cut?: boolean; // 是否被裁剪 (Whether it is cut)
64
+
65
+ // TTS字段
66
+ md5?: string; // TTS使用的MD5 (MD5 used by TTS)
67
+
68
+ // 翻译字段
69
+ tr?: 0 | 1 | 2; // 翻译状态: 0-未翻译原文, 1-翻译中, 2-翻译完成
70
+
71
+ // 说话人字段
72
+ spk?: string; // 说话人 (Speaker)
73
+
74
+ // 烧录字段
75
+ duration?: number; // 时长 (Duration)
76
+ range?: number; // 本句开始到下一个片段的可用时长
77
+
78
+ // 重复检测字段
79
+ hit?: number; // 重复次数,表示转写重复的片段
80
+ rs?: Array<AimSegments>; // 重复片段 (Repeated segments)
81
+
82
+ // 分句字段
83
+ em?: 0 | 1; // 是否结束句 (End mark)
84
+ punc?: 0 | 1; // 是否存在标点分割符号
85
+ lng?: string; // 语言 (Language)
86
+
87
+ // 优化字段
88
+ zf?: 0 | 1; // 零帧字幕 (Zero frame subtitle)
89
+ emt?: 0 | 1; // 空白字幕 (Empty text subtitle)
90
+ ep?: 0 | 1; // 结尾标点 (End punctuation)
91
+ space?: 0 | 1; // 存在多余空格
92
+ }
93
+ ```
94
+
95
+ #### `Segment` 类型
96
+
97
+ 用于表示简单的时间段。
98
+
99
+ ```typescript
100
+ type Segment = {
101
+ start: number; // 开始时间(秒)
102
+ end: number; // 结束时间(秒)
103
+ };
104
+ ```
105
+
106
+ #### `OptimizationOptions` 接口
107
+
108
+ 字幕优化选项配置。
109
+
110
+ ```typescript
111
+ interface OptimizationOptions {
112
+ emt: boolean; // 是否移除空白字幕
113
+ ep: boolean; // 是否移除结尾标点
114
+ zf: boolean; // 是否移除零帧字幕
115
+ punc: boolean; // 是否移除标点分割符号
116
+ em: boolean; // 是否移除结束标记
117
+ space: boolean; // 是否移除多余空格
118
+ repeat: boolean; // 是否移除重复内容
119
+ }
120
+ ```
121
+
122
+ #### `LanguageCode` 类型
123
+
124
+ 支持的语言代码类型。
125
+
126
+ ```typescript
127
+ type LanguageCode =
128
+ | "auto" | "none" | "zh" | "zh_cn" | "zh_tw" | "yue"
129
+ | "en" | "ja" | "ko" | "fr" | "es" | "ru" | "de" | "it"
130
+ | "tr" | "pt" | "vi" | "id" | "th" | "ms" | "ar" | "hi"
131
+ | "ro" | "ug" | "uz" | "kk" | "az" | "ky" | "fa" | "tg";
132
+ ```
133
+
134
+ #### 工具类型
135
+
136
+ ```typescript
137
+ // 使指定键变为可选
138
+ type PartialByKey<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
139
+
140
+ // 使指定键变为必需
141
+ type RequiredByKey<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
142
+ ```
143
+
144
+ ### Utils 工具函数
145
+
146
+ #### 时间处理
147
+
148
+ ##### `padNumber(num: number, length?: number): string`
149
+ 为数字前方自动补0到特定的位数,默认两位。
150
+
151
+ ```typescript
152
+ import { utils } from '@aim-packages/subtitle';
153
+
154
+ utils.padNumber(5); // "05"
155
+ utils.padNumber(5, 3); // "005"
156
+ utils.padNumber(123, 2); // "123"
157
+ ```
158
+
159
+ ##### `formatTime(seconds: number): string`
160
+ 将秒数转换为 `HH:MM:SS.mmm` 格式。
161
+
162
+ ```typescript
163
+ import { utils } from '@aim-packages/subtitle';
164
+
165
+ utils.formatTime(3661.5); // "01:01:01.500"
166
+ utils.formatTime(125.75); // "00:02:05.750"
167
+ utils.formatTime(0.123); // "00:00:00.123"
168
+ ```
169
+
170
+ ##### `convertToSeconds(time?: string): number`
171
+ 将 `HH:MM:SS,mmm` 或 `HH:MM:SS.mmm` 格式的时间转换为秒数。
172
+
173
+ ```typescript
174
+ import { utils } from '@aim-packages/subtitle';
175
+
176
+ utils.convertToSeconds("01:01:01,500"); // 3661.5
177
+ utils.convertToSeconds("00:02:05.750"); // 125.75
178
+ utils.convertToSeconds("00:00:00,123"); // 0.123
179
+ utils.convertToSeconds(); // 0
180
+ ```
181
+
182
+ #### 语言处理
183
+
184
+ ##### `containsCJKCharacters(str: string): boolean`
185
+ 检测字符串是否包含中文、日文、韩文字符。
186
+
187
+ ```typescript
188
+ import { utils } from '@aim-packages/subtitle';
189
+
190
+ utils.containsCJKCharacters("Hello 世界"); // true
191
+ utils.containsCJKCharacters("こんにちは"); // true
192
+ utils.containsCJKCharacters("안녕하세요"); // true
193
+ utils.containsCJKCharacters("Hello World"); // false
194
+ ```
195
+
196
+ ##### `languageCodeToName(languageCode: LanguageCode): string`
197
+ 将语言代码转换为可读的语言名称。
198
+
199
+ ```typescript
200
+ import { utils } from '@aim-packages/subtitle';
201
+
202
+ utils.languageCodeToName("zh"); // "Chinese"
203
+ utils.languageCodeToName("en"); // "English"
204
+ utils.languageCodeToName("ja"); // "Japanese"
205
+ utils.languageCodeToName("ko"); // "Korean"
206
+ utils.languageCodeToName("auto"); // "Unknown"
207
+ ```
208
+
209
+ #### 文本分块处理
210
+
211
+ ##### `chunkArrayStrings(strings: string[], characterLimit: number): string[]`
212
+ 将字符串数组按字符限制分块,避免单次处理内容过多。
213
+
214
+ ```typescript
215
+ import { utils } from '@aim-packages/subtitle';
216
+
217
+ const strings = ["第一句话", "第二句话", "第三句话", "第四句话"];
218
+ const chunks = utils.chunkArrayStrings(strings, 20);
219
+ // 结果: ["第一句话\n第二句话", "第三句话\n第四句话"]
220
+ ```
221
+
222
+ **参数说明:**
223
+ - `strings`: 输入的字符串数组
224
+ - `characterLimit`: 每个分块的最大字符数限制
225
+
226
+ **作用:**
227
+ 1. 将字符串数组按照字符限制进行分块,避免单次处理内容过多
228
+ 2. 使用换行符连接同一分块内的字符串
229
+ 3. 在分块时会预留一定的字符空间(10个字符)用于换行符和格式调整
230
+
231
+ ##### `chunkSegmentStringsWithIndex(segments: AimSegments[], characterLimit: number)`
232
+ 将字幕片段按字符限制分块,并生成多种格式的结果。
233
+
234
+ ```typescript
235
+ import { utils } from '@aim-packages/subtitle';
236
+
237
+ const segments = [
238
+ { st: "00:00:01", et: "00:00:03", text: "第一句话" },
239
+ { st: "00:00:03", et: "00:00:05", text: "第二句话" },
240
+ { st: "00:00:05", et: "00:00:07", text: "第三句话" }
241
+ ];
242
+
243
+ const result = utils.chunkSegmentStringsWithIndex(segments, 50);
244
+ // 返回包含以下属性的对象:
245
+ // - segmentsResult: 分块后的字幕片段数组
246
+ // - stringResult: 纯文本分块结果
247
+ // - indexStringResult: 带索引的文本分块结果
248
+ // - indexResult: 每个分块的起始索引数组
249
+ ```
250
+
251
+ **参数说明:**
252
+ - `segments`: 输入的字幕片段数组
253
+ - `characterLimit`: 每个分块的最大字符数限制
254
+
255
+ **返回值:**
256
+ ```typescript
257
+ {
258
+ segmentsResult: AimSegments[][], // 分块后的字幕片段数组
259
+ stringResult: string[], // 纯文本分块结果
260
+ indexStringResult: string[], // 带索引的文本分块结果,格式为 [索引]文本
261
+ indexResult: number[] // 每个分块的起始索引数组
262
+ }
263
+ ```
264
+
265
+ **作用:**
266
+ 1. 将字幕片段数组按照字符限制分块,避免单次处理内容过多
267
+ 2. 为每个片段添加索引信息,便于后续处理和追踪
268
+ 3. 生成多种格式的分块结果,满足不同场景的需求
269
+
270
+ #### 字幕片段合并
271
+
272
+ ##### `consolidateSegments(items: Segment[], option: { maxDistance: number, padding: number }): Segment[]`
273
+ 合并字幕片段,优化字幕的时间轴。
274
+
275
+ ```typescript
276
+ import { utils } from '@aim-packages/subtitle';
277
+
278
+ const segments = [
279
+ { start: 0, end: 2 },
280
+ { start: 2.5, end: 4 },
281
+ { start: 8, end: 10 }
282
+ ];
283
+
284
+ const merged = utils.consolidateSegments(segments, {
285
+ maxDistance: 1, // 时间间隔小于1秒的片段会被合并
286
+ padding: 0.5 // 为每个片段扩展0.5秒的开始和结束时间
287
+ });
288
+ // 结果: [{ start: 0, end: 4.5 }, { start: 7.5, end: 10.5 }]
289
+ ```
290
+
291
+ **参数说明:**
292
+ - `items`: 输入的字幕片段数组,每个片段包含 `start` 和 `end` 时间
293
+ - `option.maxDistance`: 最大时间间隔,小于此值的相邻片段会被合并
294
+ - `option.padding`: 可选的时间填充,为每个片段扩展开始和结束时间
295
+
296
+ **作用:**
297
+ 1. 将时间间隔小于 `maxDistance` 的相邻字幕片段合并为一个片段
298
+ 2. 可选择性地为每个片段添加 `padding` 时间,扩展片段的开始和结束时间
299
+ 3. 如果添加了 `padding`,会自动处理重叠的片段,将它们合并
300
+
301
+ ### Parser 字幕格式解析器
302
+
303
+ Parser 模块提供了多种字幕格式的解析和转换功能,支持 SRT、VTT、ASS 等常见字幕格式,以及流式解析器用于实时处理。
304
+
305
+ #### 基础格式转换
306
+
307
+ ##### `srtToAimSegments(text: string): Promise<AimSegments[]>`
308
+
309
+ 将 SRT 格式的字幕文本转换为 `AimSegments` 数组。
310
+
311
+ **参数:**
312
+ - `text: string` - SRT 格式的字幕文本内容
313
+
314
+ **返回值:**
315
+ - `Promise<AimSegments[]>` - 转换后的字幕片段数组
316
+
317
+ **示例:**
318
+ ```typescript
319
+ import { parser } from '@aim-packages/subtitle';
320
+
321
+ const srtContent = `1
322
+ 00:00:01,000 --> 00:00:03,000
323
+ Hello world.
324
+
325
+ 2
326
+ 00:00:03,000 --> 00:00:05,000
327
+ How are you?`;
328
+
329
+ const segments = await parser.srtToAimSegments(srtContent);
330
+ // 返回: [
331
+ // { st: "00:00:01.000", et: "00:00:03.000", text: "Hello world." },
332
+ // { st: "00:00:03.000", et: "00:00:05.000", text: "How are you?" }
333
+ // ]
334
+ ```
335
+
336
+ ##### `vttToAimSegments(text: string): Promise<AimSegments[]>`
337
+
338
+ 将 VTT 格式的字幕文本转换为 `AimSegments` 数组。
339
+
340
+ **参数:**
341
+ - `text: string` - VTT 格式的字幕文本内容
342
+
343
+ **返回值:**
344
+ - `Promise<AimSegments[]>` - 转换后的字幕片段数组
345
+
346
+ **示例:**
347
+ ```typescript
348
+ import { parser } from '@aim-packages/subtitle';
349
+
350
+ const vttContent = `WEBVTT
351
+
352
+ 00:00:01.000 --> 00:00:03.000
353
+ Hello world.
354
+
355
+ 00:00:03.000 --> 00:00:05.000
356
+ How are you?`;
357
+
358
+ const segments = await parser.vttToAimSegments(vttContent);
359
+ // 返回: [
360
+ // { st: "00:00:01.000", et: "00:00:03.000", text: "Hello world." },
361
+ // { st: "00:00:03.000", et: "00:00:05.000", text: "How are you?" }
362
+ // ]
363
+ ```
364
+
365
+ ##### `assToAimSegments(text: string): Promise<AimSegments[]>`
366
+
367
+ 将 ASS 格式的字幕文本转换为 `AimSegments` 数组(通过先转换为 VTT 格式)。
368
+
369
+ **参数:**
370
+ - `text: string` - ASS 格式的字幕文本内容
371
+
372
+ **返回值:**
373
+ - `Promise<AimSegments[]>` - 转换后的字幕片段数组
374
+
375
+ **示例:**
376
+ ```typescript
377
+ import { parser } from '@aim-packages/subtitle';
378
+
379
+ const assContent = `[Script Info]
380
+ Title: Example
381
+ ScriptType: v4.00+
382
+
383
+ [V4+ Styles]
384
+ Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
385
+ Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1
386
+
387
+ [Events]
388
+ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
389
+ Dialogue: 0,0:00:01.00,0:00:03.00,Default,,0,0,0,,Hello world.
390
+ Dialogue: 0,0:00:03.00,0:00:05.00,Default,,0,0,0,,How are you?`;
391
+
392
+ const segments = await parser.assToAimSegments(assContent);
393
+ ```
394
+
395
+ #### 第三方服务结果转换
396
+
397
+ ##### `tingwuToAimSegments(json: TingwuResult): Promise<AimSegments[]>`
398
+
399
+ 将腾讯云听悟服务的识别结果转换为 `AimSegments` 数组。
400
+
401
+ **参数:**
402
+ - `json: TingwuResult` - 腾讯云听悟服务的识别结果
403
+
404
+ **返回值:**
405
+ - `Promise<AimSegments[]>` - 转换后的字幕片段数组
406
+
407
+ **示例:**
408
+ ```typescript
409
+ import { parser } from '@aim-packages/subtitle';
410
+
411
+ const tingwuResult = {
412
+ // 腾讯云听悟服务的识别结果
413
+ };
414
+
415
+ const segments = await parser.tingwuToAimSegments(tingwuResult);
416
+ ```
417
+
418
+ ##### `openaiToAimSegments(json: OpenAIResult): Promise<AimSegments[]>`
419
+
420
+ 将 OpenAI Whisper 服务的识别结果转换为 `AimSegments` 数组。
421
+
422
+ **参数:**
423
+ - `json: OpenAIResult` - OpenAI Whisper 服务的识别结果
424
+
425
+ **返回值:**
426
+ - `Promise<AimSegments[]>` - 转换后的字幕片段数组
427
+
428
+ **示例:**
429
+ ```typescript
430
+ import { parser } from '@aim-packages/subtitle';
431
+
432
+ const openaiResult = {
433
+ // OpenAI Whisper 服务的识别结果
434
+ };
435
+
436
+ const segments = await parser.openaiToAimSegments(openaiResult);
437
+ ```
438
+
439
+ #### 流式解析器
440
+
441
+ ##### `createWhisperStreamParser(options?: ParserOptions)`
442
+
443
+ 创建用于处理 Whisper 流式输出的解析器。
444
+
445
+ **参数:**
446
+ - `options?: ParserOptions` - 解析器配置选项
447
+
448
+ **返回值:**
449
+ - `Parser<string>` - 流式解析器实例
450
+
451
+ **示例:**
452
+ ```typescript
453
+ import { parser } from '@aim-packages/subtitle';
454
+
455
+ const whisperParser = parser.createWhisperStreamParser({
456
+ onStart: (event) => {
457
+ console.log('开始解析 Whisper 流');
458
+ },
459
+ onParse: (event) => {
460
+ console.log('解析到字幕片段:', event.data);
461
+ },
462
+ onEnd: (event) => {
463
+ console.log('解析完成');
464
+ }
465
+ });
466
+
467
+ // 使用解析器
468
+ whisperParser.feed(chunk);
469
+ whisperParser.end();
470
+ ```
471
+
472
+ ##### `createTranslateStreamParser(options?: ParserOptions)`
473
+
474
+ 创建用于处理翻译流式输出的解析器。
475
+
476
+ **参数:**
477
+ - `options?: ParserOptions` - 解析器配置选项
478
+
479
+ **返回值:**
480
+ - `Parser<string>` - 流式解析器实例
481
+
482
+ **示例:**
483
+ ```typescript
484
+ import { parser } from '@aim-packages/subtitle';
485
+
486
+ const translateParser = parser.createTranslateStreamParser({
487
+ onParse: (event) => {
488
+ console.log('解析到翻译片段:', event.data);
489
+ }
490
+ });
491
+
492
+ // 使用解析器
493
+ translateParser.feed(chunk);
494
+ translateParser.end();
495
+ ```
496
+
497
+ ##### `createSegmentStreamParser(options?: ParserOptions)`
498
+
499
+ 创建用于处理通用字幕片段流式输出的解析器。
500
+
501
+ **参数:**
502
+ - `options?: ParserOptions` - 解析器配置选项
503
+
504
+ **返回值:**
505
+ - `Parser<string>` - 流式解析器实例
506
+
507
+ **示例:**
508
+ ```typescript
509
+ import { parser } from '@aim-packages/subtitle';
510
+
511
+ const segmentParser = parser.createSegmentStreamParser({
512
+ onParse: (event) => {
513
+ console.log('解析到字幕片段:', event.data);
514
+ }
515
+ });
516
+
517
+ // 使用解析器
518
+ segmentParser.feed(chunk);
519
+ segmentParser.end();
520
+ ```
521
+
522
+ #### Parser 模块相关接口
523
+
524
+ ##### `Parser<T>` 接口
525
+ ```typescript
526
+ interface Parser<T = string> {
527
+ /** 向解析器输入数据块 */
528
+ feed(chunk: T): void
529
+ /** 重置解析器状态 */
530
+ reset(): void
531
+ /** 结束解析 */
532
+ end(): void
533
+ }
534
+ ```
535
+
536
+ ##### `ParserOptions<S, P, E>` 类型
537
+ ```typescript
538
+ type ParserOptions<S, P, E> = {
539
+ /** 解析开始时的回调函数 */
540
+ onStart?: ParseCallback<S>
541
+ /** 解析过程中调用的回调函数 */
542
+ onParse?: ParseCallback<P>
543
+ /** 解析进度更新时的回调函数 */
544
+ onProgress?: ParseCallback<P>
545
+ /** 解析结束时的回调函数 */
546
+ onEnd?: ParseCallback<E>
547
+ }
548
+ ```
549
+
550
+ ##### `ParsedEvent<T>` 接口
551
+ ```typescript
552
+ interface ParsedEvent<T> {
553
+ type: "event"
554
+ event: "start" | "message" | "end"
555
+ data?: T
556
+ }
557
+ ```
558
+
559
+ ##### `ParseCallback<T>` 类型
560
+ ```typescript
561
+ type ParseCallback<T> = (event: ParsedEvent<T>) => void
562
+ ```
563
+
564
+ ### Filter 流式文本过滤器
565
+
566
+ Filter 模块提供了基于 DFA(确定有限自动机)算法的流式文本过滤功能,支持实时敏感词检测和替换。
567
+
568
+ #### `StreamFilter` 类
569
+
570
+ 流式文本过滤器,支持逐字符输入和实时过滤。
571
+
572
+ **构造函数:**
573
+ ```typescript
574
+ constructor(onFilter?: (text: string) => any)
575
+ ```
576
+
577
+ **参数:**
578
+ - `onFilter?: (text: string) => any` - 可选的回调函数,在每次过滤时调用
579
+
580
+ **方法:**
581
+
582
+ ##### `add(keyword: string, replaceText?: string)`
583
+
584
+ 添加关键词和对应的替换文本。
585
+
586
+ **参数:**
587
+ - `keyword: string` - 需要过滤的关键词
588
+ - `replaceText?: string` - 替换文本,默认为空字符串
589
+
590
+ **示例:**
591
+ ```typescript
592
+ import { filter } from '@aim-packages/subtitle';
593
+
594
+ const streamFilter = new filter.StreamFilter();
595
+ streamFilter.add("敏感词", "***");
596
+ streamFilter.add("另一个词", "替换文本");
597
+ ```
598
+
599
+ ##### `parse(data: [string, string][])`
600
+
601
+ 批量添加关键词和替换文本。
602
+
603
+ **参数:**
604
+ - `data: [string, string][]` - 关键词和替换文本的数组
605
+
606
+ **示例:**
607
+ ```typescript
608
+ import { filter } from '@aim-packages/subtitle';
609
+
610
+ const streamFilter = new filter.StreamFilter();
611
+ streamFilter.parse([
612
+ ["敏感词1", "***"],
613
+ ["敏感词2", "替换文本"],
614
+ ["敏感词3", ""] // 空字符串表示删除
615
+ ]);
616
+ ```
617
+
618
+ ##### `reParse(data: string[][])`
619
+
620
+ 重新解析关键词列表,会清空之前的所有关键词。
621
+
622
+ **参数:**
623
+ - `data: string[][]` - 关键词和替换文本的二维数组
624
+
625
+ **示例:**
626
+ ```typescript
627
+ import { filter } from '@aim-packages/subtitle';
628
+
629
+ const streamFilter = new filter.StreamFilter();
630
+ streamFilter.reParse([
631
+ ["敏感词1", "***"],
632
+ ["敏感词2", "替换文本"]
633
+ ]);
634
+ ```
635
+
636
+ ##### `feed(c: string)`
637
+
638
+ 逐字符输入文本进行过滤。
639
+
640
+ **参数:**
641
+ - `c: string` - 单个字符
642
+
643
+ **示例:**
644
+ ```typescript
645
+ import { filter } from '@aim-packages/subtitle';
646
+
647
+ const streamFilter = new filter.StreamFilter((text) => {
648
+ console.log('过滤后的字符:', text);
649
+ });
650
+
651
+ streamFilter.add("敏感词", "***");
652
+
653
+ // 逐字符输入
654
+ streamFilter.feed("这");
655
+ streamFilter.feed("是");
656
+ streamFilter.feed("敏");
657
+ streamFilter.feed("感");
658
+ streamFilter.feed("词");
659
+ streamFilter.feed("。");
660
+
661
+ const result = streamFilter.end();
662
+ console.log('最终结果:', result); // "这是***。"
663
+ ```
664
+
665
+ ##### `feedAll(text: string): string`
666
+
667
+ 一次性输入完整文本进行过滤。
668
+
669
+ **参数:**
670
+ - `text: string` - 完整的文本内容
671
+
672
+ **返回值:**
673
+ - `string` - 过滤后的文本
674
+
675
+ **示例:**
676
+ ```typescript
677
+ import { filter } from '@aim-packages/subtitle';
678
+
679
+ const streamFilter = new filter.StreamFilter();
680
+ streamFilter.add("敏感词", "***");
681
+
682
+ const result = streamFilter.feedAll("这是一个敏感词测试。");
683
+ console.log(result); // "这是一个***测试。"
684
+ ```
685
+
686
+ ##### `end(): string`
687
+
688
+ 结束过滤并返回结果。
689
+
690
+ **返回值:**
691
+ - `string` - 过滤后的完整文本
692
+
693
+ **示例:**
694
+ ```typescript
695
+ import { filter } from '@aim-packages/subtitle';
696
+
697
+ const streamFilter = new filter.StreamFilter();
698
+ streamFilter.add("敏感词", "***");
699
+
700
+ // 逐字符输入
701
+ streamFilter.feed("这");
702
+ streamFilter.feed("是");
703
+ streamFilter.feed("敏");
704
+ streamFilter.feed("感");
705
+ streamFilter.feed("词");
706
+
707
+ const result = streamFilter.end();
708
+ console.log(result); // "这是***"
709
+ ```
710
+
711
+ #### 完整使用示例
712
+
713
+ ```typescript
714
+ import { filter } from '@aim-packages/subtitle';
715
+
716
+ // 创建流式过滤器
717
+ const streamFilter = new filter.StreamFilter((char) => {
718
+ console.log('实时输出字符:', char);
719
+ });
720
+
721
+ // 添加过滤规则
722
+ streamFilter.parse([
723
+ ["敏感词1", "***"],
724
+ ["敏感词2", "替换文本"],
725
+ ["要删除的词", ""]
726
+ ]);
727
+
728
+ // 方式1: 逐字符输入
729
+ streamFilter.feed("这");
730
+ streamFilter.feed("是");
731
+ streamFilter.feed("敏");
732
+ streamFilter.feed("感");
733
+ streamFilter.feed("词");
734
+ streamFilter.feed("1");
735
+ streamFilter.feed("。");
736
+
737
+ const result1 = streamFilter.end();
738
+ console.log('逐字符结果:', result1);
739
+
740
+ // 方式2: 一次性输入
741
+ const result2 = streamFilter.feedAll("这是敏感词2测试。");
742
+ console.log('一次性结果:', result2);
743
+ ```
744
+
745
+ ### Tools 字幕处理工具
746
+
747
+ Tools 模块提供了字幕处理相关的各种工具方法,包括语言检测、文本分割和字幕优化等功能。
748
+
749
+ #### 语言检测
750
+
751
+ ##### `detectLanguage(text: string): LanguageDetectionResultsEntry`
752
+
753
+ 检测文本的主要语言。
754
+
755
+ **参数:**
756
+ - `text: string` - 需要检测语言的文本内容
757
+
758
+ **返回值:**
759
+ - `LanguageDetectionResultsEntry` - 包含语言代码、语言名称和概率的对象
760
+
761
+ **示例:**
762
+ ```typescript
763
+ import { tools } from '@aim-packages/subtitle';
764
+
765
+ const result = tools.detection.detectLanguage("Hello world");
766
+ // 返回: { language: 'en', languageName: 'English', probability: 1 }
767
+ ```
768
+
769
+ ##### `detectAllLanguage(text: string): LanguageDetectionResultsEntry[]`
770
+
771
+ 检测文本中的所有可能语言,返回按概率排序的语言列表。
772
+
773
+ **参数:**
774
+ - `text: string` - 需要检测语言的文本内容
775
+
776
+ **返回值:**
777
+ - `LanguageDetectionResultsEntry[]` - 检测到的所有语言及其概率,按概率从高到低排序
778
+
779
+ **示例:**
780
+ ```typescript
781
+ import { tools } from '@aim-packages/subtitle';
782
+
783
+ const results = tools.detection.detectAllLanguage("Hello world 你好世界");
784
+ // 返回: [
785
+ // { language: 'en', languageName: 'English', probability: 0.8 },
786
+ // { language: 'zh', languageName: '中文', probability: 0.2 }
787
+ // ]
788
+ ```
789
+
790
+ #### 文本分割
791
+
792
+ ##### `splitToSentences(text: string, languageCode?: LanguageCode): string[]`
793
+
794
+ 将文本按句子进行分割。
795
+
796
+ **参数:**
797
+ - `text: string` - 需要分割的文本内容
798
+ - `languageCode?: LanguageCode` - 可选的语言代码,用于更准确的分割
799
+
800
+ **返回值:**
801
+ - `string[]` - 分割后的句子数组
802
+
803
+ **示例:**
804
+ ```typescript
805
+ import { tools } from '@aim-packages/subtitle';
806
+
807
+ // 使用默认分割器
808
+ const sentences1 = tools.segmentation.splitToSentences("Hello world. How are you?");
809
+ // 返回: ["Hello world.", "How are you?"]
810
+
811
+ // 使用指定语言的分割器
812
+ const sentences2 = tools.segmentation.splitToSentences("你好世界。今天天气怎么样?", 'zh');
813
+ // 返回: ["你好世界。", "今天天气怎么样?"]
814
+ ```
815
+
816
+ #### 字幕优化
817
+
818
+ ##### `subtitleOptimization(segments: AimSegments[], options?: OptimizationOptions)`
819
+
820
+ 对字幕片段进行全面的质量检查和优化。
821
+
822
+ **功能包括:**
823
+ - 重复内容检测和移除
824
+ - 空白字幕处理
825
+ - 0帧字幕处理(开始时间大于等于结束时间)
826
+ - 标点符号检查
827
+ - 句子结束标记检查
828
+ - 多余空格处理
829
+ - 结尾标点处理
830
+
831
+ **参数:**
832
+ - `segments: AimSegments[]` - 需要优化的字幕片段数组
833
+ - `options?: OptimizationOptions` - 优化选项配置
834
+
835
+ **返回值:**
836
+ ```typescript
837
+ {
838
+ result: {
839
+ emt: number[], // 空白字幕索引
840
+ ep: number[], // 结尾标点索引
841
+ zf: number[], // 0帧字幕索引
842
+ punc: number[], // 标点符号索引
843
+ em: number[], // 句子结束标记索引
844
+ space: number[], // 多余空格索引
845
+ repeat: number[], // 重复内容索引
846
+ },
847
+ repeat: AimSegments[], // 重复的片段列表
848
+ segments: AimSegments[] // 优化后的字幕片段数组
849
+ }
850
+ ```
851
+
852
+ **示例:**
853
+ ```typescript
854
+ import { tools } from '@aim-packages/subtitle';
855
+
856
+ const segments = [
857
+ { st: "00:00:01", et: "00:00:03", text: "Hello world." },
858
+ { st: "00:00:03", et: "00:00:05", text: "Hello world." }, // 重复内容
859
+ { st: "00:00:05", et: "00:00:06", text: " " }, // 空白字幕
860
+ { st: "00:00:06", et: "00:00:06", text: "Zero frame" }, // 0帧字幕
861
+ ];
862
+
863
+ const result = tools.optimization.subtitleOptimization(segments, {
864
+ repeat: true, // 移除重复内容
865
+ emt: true, // 移除空白字幕
866
+ zf: true, // 移除0帧字幕
867
+ space: true, // 处理多余空格
868
+ ep: true // 移除结尾标点
869
+ });
870
+ ```
871
+
872
+ ##### `RepeatCheck` 类
873
+
874
+ 重复内容检测器,用于检测字幕中连续重复的内容片段。
875
+
876
+ **构造函数:**
877
+ ```typescript
878
+ constructor(options?: RepeatCheckOption)
879
+ ```
880
+
881
+ **方法:**
882
+
883
+ - `push(segment: AimSegments)` - 添加字幕片段进行重复检测
884
+ - `end()` - 结束检测,处理最后的重复片段
885
+ - `reset()` - 重置检测器状态
886
+
887
+ **属性:**
888
+ - `threshold: number` - 重复检测阈值,默认为2次
889
+ - `hit: number` - 当前重复次数
890
+ - `prevSegment?: AimSegments` - 前一个字幕片段
891
+ - `hitSegment?: AimSegments` - 当前重复的字幕片段
892
+ - `hitSegmentList: AimSegments[]` - 重复片段列表
893
+ - `options: RepeatCheckOption` - 配置选项
894
+
895
+ **示例:**
896
+ ```typescript
897
+ import { tools } from '@aim-packages/subtitle';
898
+
899
+ const repeatCheck = new tools.optimization.RepeatCheck({
900
+ onHit: (segments) => {
901
+ console.log('检测到重复内容:', segments);
902
+ }
903
+ });
904
+
905
+ repeatCheck.push({ st: "00:00:01", et: "00:00:03", text: "Hello" });
906
+ repeatCheck.push({ st: "00:00:03", et: "00:00:05", text: "Hello" }); // 重复
907
+ repeatCheck.push({ st: "00:00:05", et: "00:00:07", text: "Hello" }); // 重复,触发回调
908
+ repeatCheck.end();
909
+ ```
910
+
911
+ #### 完整的字幕处理流程示例
912
+
913
+ ```typescript
914
+ import { tools } from '@aim-packages/subtitle';
915
+
916
+ // 1. 检测语言
917
+ const language = tools.detection.detectLanguage("Hello world. How are you?");
918
+
919
+ // 2. 按句子分割
920
+ const sentences = tools.segmentation.splitToSentences("Hello world. How are you?", language.language);
921
+
922
+ // 3. 转换为字幕片段
923
+ const segments = sentences.map((text, index) => ({
924
+ st: `00:00:${index * 2}`,
925
+ et: `00:00:${(index + 1) * 2}`,
926
+ text
927
+ }));
928
+
929
+ // 4. 优化字幕
930
+ const optimized = tools.optimization.subtitleOptimization(segments, {
931
+ repeat: true,
932
+ emt: true,
933
+ zf: true,
934
+ space: true,
935
+ ep: true
936
+ });
937
+
938
+ console.log('优化结果:', optimized);
939
+ ```
940
+
941
+ #### 完整的字幕处理流程示例
942
+
943
+ ```typescript
944
+ import { utils, parser, filter, tools } from '@aim-packages/subtitle';
945
+
946
+ // 1. 解析字幕文件
947
+ const srtContent = `1
948
+ 00:00:01,000 --> 00:00:03,000
949
+ Hello world.
950
+
951
+ 2
952
+ 00:00:03,000 --> 00:00:05,000
953
+ How are you?`;
954
+
955
+ const segments = await parser.srtToAimSegments(srtContent);
956
+
957
+ // 2. 检测语言
958
+ const language = tools.detection.detectLanguage(segments[0].text);
959
+
960
+ // 3. 按句子分割并重新生成字幕片段
961
+ const sentences = tools.segmentation.splitToSentences(segments[0].text, language.language);
962
+ const newSegments = sentences.map((text, index) => ({
963
+ st: utils.formatTime(index * 2),
964
+ et: utils.formatTime((index + 1) * 2),
965
+ text
966
+ }));
967
+
968
+ // 4. 优化字幕质量
969
+ const optimized = tools.optimization.subtitleOptimization(newSegments, {
970
+ repeat: true,
971
+ emt: true,
972
+ zf: true,
973
+ space: true,
974
+ ep: true
975
+ });
976
+
977
+ // 5. 文本过滤
978
+ const streamFilter = new filter.StreamFilter();
979
+ streamFilter.parse([
980
+ ["敏感词", "***"],
981
+ ["不当词汇", "替换文本"]
982
+ ]);
983
+
984
+ const filteredSegments = optimized.segments.map(segment => ({
985
+ ...segment,
986
+ text: streamFilter.feedAll(segment.text)
987
+ }));
988
+
989
+ console.log('最终处理结果:', filteredSegments);
990
+ ```
991
+
992
+ #### Tools 模块相关接口
993
+
994
+ ##### `LanguageDetectionResultsEntry`
995
+ ```typescript
996
+ interface LanguageDetectionResultsEntry {
997
+ /** 语言代码 (如 'zh', 'en', 'ja' 等) */
998
+ language: LanguageCode
999
+ /** 语言名称 (如 '中文', 'English', '日本語' 等) */
1000
+ languageName: string
1001
+ /** 检测准确度概率 (0-1 之间的数值) */
1002
+ probability: number
1003
+ }
1004
+ ```
1005
+
1006
+ ##### `RepeatCheckOption`
1007
+ ```typescript
1008
+ interface RepeatCheckOption {
1009
+ /** 当检测到重复内容时的回调函数 */
1010
+ onHit?: (segment: AimSegments[]) => void
1011
+ }
1012
+ ```
1013
+
1014
+ ## 🏗️ 项目结构
1015
+
1016
+ ```
1017
+ src/
1018
+ ├── utils/ # 工具函数 - 时间格式化、语言处理、文本分块等
1019
+ ├── parser/ # 字幕格式解析器 - SRT、VTT、ASS 格式转换和流式解析
1020
+ ├── tools/ # 字幕处理工具 - 语言检测、文本分割、字幕优化等
1021
+ ├── filter/ # 字幕过滤器 - 基于 DFA 算法的流式文本过滤
1022
+ ├── types/ # TypeScript 类型定义
1023
+ └── main.ts # 主入口文件
1024
+ ```
1025
+
1026
+ ## 📋 模块功能概览
1027
+
1028
+ ### Utils 模块
1029
+ - **时间处理**: 时间格式化、秒数转换、数字补零等
1030
+ - **语言处理**: 中日韩字符检测、语言代码转换等
1031
+ - **文本分块**: 按字符限制分块、字幕片段分块等
1032
+ - **字幕合并**: 合并相邻字幕片段、时间轴优化等
1033
+
1034
+ ### Parser 模块
1035
+ - **格式转换**: SRT、VTT、ASS 格式之间的相互转换
1036
+ - **第三方服务**: 腾讯云听悟、OpenAI Whisper 结果转换
1037
+ - **流式解析**: 支持实时流式字幕数据的解析和处理
1038
+
1039
+ ### Tools 模块
1040
+ - **语言检测**: 多语言文本的语言识别和概率分析
1041
+ - **文本分割**: 按句子、段落等规则分割文本
1042
+ - **字幕优化**: 重复检测、空白处理、质量优化等
1043
+
1044
+ ### Filter 模块
1045
+ - **流式过滤**: 基于 DFA 算法的实时文本过滤
1046
+ - **敏感词处理**: 支持关键词替换、删除等操作
1047
+ - **逐字符处理**: 支持逐字符输入和实时过滤
1048
+
1049
+ ## 🔧 开发
1050
+
1051
+ ```bash
1052
+ # 安装依赖
1053
+ pnpm install
1054
+
1055
+ # 开发模式
1056
+ pnpm dev
1057
+
1058
+ # 构建
1059
+ pnpm build
1060
+
1061
+ # 预览构建结果
1062
+ pnpm preview
1063
+
1064
+ # 发布
1065
+ pnpm release
1066
+ ```
1067
+
1068
+ ## 📄 许可证
1069
+
1070
+ MIT License
1071
+
1072
+ ## 🤝 贡献
1073
+
1074
+ 欢迎提交 Issue 和 Pull Request!