@aim-packages/subtitle 0.1.7 → 0.2.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 CHANGED
@@ -1,1616 +1,1616 @@
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
- const segments = [["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"]];
44
- const srtContent = tools.output.outputSrt({ segments1: segments });
45
-
46
- ## 📚 API 文档
47
-
48
- ### 类型定义
49
-
50
- #### `AimSegments` 接口
51
-
52
- 这是字幕处理库的核心数据结构,用于表示单个字幕片段。
53
-
54
- ```typescript
55
- interface AimSegments {
56
- // 通用字段
57
- st: string; // 开始时间戳 (Start timestamp)
58
- et: string; // 结束时间戳 (End timestamp)
59
- text: string; // 文本内容 (Text content)
60
- index?: number; // 字幕索引 (Subtitle index)
61
- children?: Array<AimSegments>; // 子片段 (Child segments)
62
- f?: number; // 批量文件转写时所属的文件索引 (File index)
63
-
64
- // 视频剪辑字段
65
- delete?: boolean; // 是否被删除 (Whether it is deleted)
66
- cut?: boolean; // 是否被裁剪 (Whether it is cut)
67
-
68
- // TTS字段
69
- md5?: string; // TTS使用的MD5 (MD5 used by TTS)
70
-
71
- // 翻译字段
72
- tr?: 0 | 1 | 2; // 翻译状态: 0-未翻译原文, 1-翻译中, 2-翻译完成
73
-
74
- // 说话人字段
75
- spk?: string; // 说话人 (Speaker)
76
-
77
- // 烧录字段
78
- duration?: number; // 时长 (Duration)
79
- range?: number; // 本句开始到下一个片段的可用时长
80
-
81
- // 重复检测字段
82
- hit?: number; // 重复次数,表示转写重复的片段
83
- rs?: Array<AimSegments>; // 重复片段 (Repeated segments)
84
-
85
- // 分句字段
86
- em?: 0 | 1; // 是否结束句 (End mark)
87
- punc?: 0 | 1; // 是否存在标点分割符号
88
- lng?: string; // 语言 (Language)
89
-
90
- // 优化字段
91
- zf?: 0 | 1; // 零帧字幕 (Zero frame subtitle)
92
- emt?: 0 | 1; // 空白字幕 (Empty text subtitle)
93
- ep?: 0 | 1; // 结尾标点 (End punctuation)
94
- space?: 0 | 1; // 存在多余空格
95
- }
96
- ```
97
-
98
- #### `Segment` 类型
99
-
100
- 用于表示简单的时间段。
101
-
102
- ```typescript
103
- type Segment = {
104
- start: number; // 开始时间(秒)
105
- end: number; // 结束时间(秒)
106
- };
107
- ```
108
-
109
- #### `OptimizationOptions` 接口
110
-
111
- 字幕优化选项配置。
112
-
113
- ```typescript
114
- interface OptimizationOptions {
115
- emt: boolean; // 是否移除空白字幕
116
- ep: boolean; // 是否移除结尾标点
117
- zf: boolean; // 是否移除零帧字幕
118
- punc: boolean; // 是否移除标点分割符号
119
- em: boolean; // 是否移除结束标记
120
- space: boolean; // 是否移除多余空格
121
- repeat: boolean; // 是否移除重复内容
122
- }
123
- ```
124
-
125
- #### `LanguageCode` 类型
126
-
127
- 支持的语言代码类型。
128
-
129
- ```typescript
130
- type LanguageCode =
131
- | "auto" | "none" | "zh" | "zh_cn" | "zh_tw" | "yue"
132
- | "en" | "ja" | "ko" | "fr" | "es" | "ru" | "de" | "it"
133
- | "tr" | "pt" | "vi" | "id" | "th" | "ms" | "ar" | "hi"
134
- | "ro" | "ug" | "uz" | "kk" | "az" | "ky" | "fa" | "tg";
135
- ```
136
-
137
- #### 工具类型
138
-
139
- ```typescript
140
- // 使指定键变为可选
141
- type PartialByKey<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
142
-
143
- // 使指定键变为必需
144
- type RequiredByKey<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
145
- ```
146
-
147
- ### Utils 工具函数
148
-
149
- #### 时间处理
150
-
151
- ##### `padNumber(num: number, length?: number): string`
152
- 为数字前方自动补0到特定的位数,默认两位。
153
-
154
- ```typescript
155
- import { utils } from '@aim-packages/subtitle';
156
-
157
- utils.padNumber(5); // "05"
158
- utils.padNumber(5, 3); // "005"
159
- utils.padNumber(123, 2); // "123"
160
- ```
161
-
162
- ##### `formatTime(seconds: number): string`
163
- 将秒数转换为 `HH:MM:SS.mmm` 格式。
164
-
165
- ```typescript
166
- import { utils } from '@aim-packages/subtitle';
167
-
168
- utils.formatTime(3661.5); // "01:01:01.500"
169
- utils.formatTime(125.75); // "00:02:05.750"
170
- utils.formatTime(0.123); // "00:00:00.123"
171
- ```
172
-
173
- ##### `convertToSeconds(time?: string): number`
174
- 将 `HH:MM:SS,mmm` 或 `HH:MM:SS.mmm` 格式的时间转换为秒数。
175
-
176
- ```typescript
177
- import { utils } from '@aim-packages/subtitle';
178
-
179
- utils.convertToSeconds("01:01:01,500"); // 3661.5
180
- utils.convertToSeconds("00:02:05.750"); // 125.75
181
- utils.convertToSeconds("00:00:00,123"); // 0.123
182
- utils.convertToSeconds(); // 0
183
- ```
184
-
185
- #### 语言处理
186
-
187
- ##### `containsCJKCharacters(str: string): boolean`
188
- 检测字符串是否包含中文、日文、韩文字符。
189
-
190
- ```typescript
191
- import { utils } from '@aim-packages/subtitle';
192
-
193
- utils.containsCJKCharacters("Hello 世界"); // true
194
- utils.containsCJKCharacters("こんにちは"); // true
195
- utils.containsCJKCharacters("안녕하세요"); // true
196
- utils.containsCJKCharacters("Hello World"); // false
197
- ```
198
-
199
- ##### `languageCodeToName(languageCode: LanguageCode): string`
200
- 将语言代码转换为可读的语言名称。
201
-
202
- ```typescript
203
- import { utils } from '@aim-packages/subtitle';
204
-
205
- utils.languageCodeToName("zh"); // "Chinese"
206
- utils.languageCodeToName("en"); // "English"
207
- utils.languageCodeToName("ja"); // "Japanese"
208
- utils.languageCodeToName("ko"); // "Korean"
209
- utils.languageCodeToName("auto"); // "Unknown"
210
- ```
211
-
212
- #### 文本分块处理
213
-
214
- ##### `chunkArrayStrings(strings: string[], characterLimit: number): string[]`
215
- 将字符串数组按字符限制分块,避免单次处理内容过多。
216
-
217
- ```typescript
218
- import { utils } from '@aim-packages/subtitle';
219
-
220
- const strings = ["第一句话", "第二句话", "第三句话", "第四句话"];
221
- const chunks = utils.chunkArrayStrings(strings, 20);
222
- // 结果: ["第一句话\n第二句话", "第三句话\n第四句话"]
223
- ```
224
-
225
- **参数说明:**
226
- - `strings`: 输入的字符串数组
227
- - `characterLimit`: 每个分块的最大字符数限制
228
-
229
- **作用:**
230
- 1. 将字符串数组按照字符限制进行分块,避免单次处理内容过多
231
- 2. 使用换行符连接同一分块内的字符串
232
- 3. 在分块时会预留一定的字符空间(10个字符)用于换行符和格式调整
233
-
234
- ##### `chunkSegmentStringsWithIndex(segments: AimSegments[], characterLimit: number)`
235
- 将字幕片段按字符限制分块,并生成多种格式的结果。
236
-
237
- ```typescript
238
- import { utils } from '@aim-packages/subtitle';
239
-
240
- const segments = [
241
- { st: "00:00:01", et: "00:00:03", text: "第一句话" },
242
- { st: "00:00:03", et: "00:00:05", text: "第二句话" },
243
- { st: "00:00:05", et: "00:00:07", text: "第三句话" }
244
- ];
245
-
246
- const result = utils.chunkSegmentStringsWithIndex(segments, 50);
247
- // 返回包含以下属性的对象:
248
- // - segmentsResult: 分块后的字幕片段数组
249
- // - stringResult: 纯文本分块结果
250
- // - indexStringResult: 带索引的文本分块结果
251
- // - indexResult: 每个分块的起始索引数组
252
- ```
253
-
254
- **参数说明:**
255
- - `segments`: 输入的字幕片段数组
256
- - `characterLimit`: 每个分块的最大字符数限制
257
-
258
- **返回值:**
259
- ```typescript
260
- {
261
- segmentsResult: AimSegments[][], // 分块后的字幕片段数组
262
- stringResult: string[], // 纯文本分块结果
263
- indexStringResult: string[], // 带索引的文本分块结果,格式为 [索引]文本
264
- indexResult: number[] // 每个分块的起始索引数组
265
- }
266
- ```
267
-
268
- **作用:**
269
- 1. 将字幕片段数组按照字符限制分块,避免单次处理内容过多
270
- 2. 为每个片段添加索引信息,便于后续处理和追踪
271
- 3. 生成多种格式的分块结果,满足不同场景的需求
272
-
273
- #### 字幕片段合并
274
-
275
- ##### `consolidateSegments(items: Segment[], option: { maxDistance: number, padding: number }): Segment[]`
276
- 合并字幕片段,优化字幕的时间轴。
277
-
278
- ```typescript
279
- import { utils } from '@aim-packages/subtitle';
280
-
281
- const segments = [
282
- { start: 0, end: 2 },
283
- { start: 2.5, end: 4 },
284
- { start: 8, end: 10 }
285
- ];
286
-
287
- const merged = utils.consolidateSegments(segments, {
288
- maxDistance: 1, // 时间间隔小于1秒的片段会被合并
289
- padding: 0.5 // 为每个片段扩展0.5秒的开始和结束时间
290
- });
291
- // 结果: [{ start: 0, end: 4.5 }, { start: 7.5, end: 10.5 }]
292
- ```
293
-
294
- **参数说明:**
295
- - `items`: 输入的字幕片段数组,每个片段包含 `start` 和 `end` 时间
296
- - `option.maxDistance`: 最大时间间隔,小于此值的相邻片段会被合并
297
- - `option.padding`: 可选的时间填充,为每个片段扩展开始和结束时间
298
-
299
- **作用:**
300
- 1. 将时间间隔小于 `maxDistance` 的相邻字幕片段合并为一个片段
301
- 2. 可选择性地为每个片段添加 `padding` 时间,扩展片段的开始和结束时间
302
- 3. 如果添加了 `padding`,会自动处理重叠的片段,将它们合并
303
-
304
- #### 颜色格式转换
305
-
306
- ##### `convertHexColorToAssFormat(hexColor?: string): string`
307
- 将十六进制颜色转换为 ASS 格式,用于字幕样式设置。
308
-
309
- ```typescript
310
- import { utils } from '@aim-packages/subtitle';
311
-
312
- utils.convertHexColorToAssFormat("#FF0000"); // "&H0000FF00"
313
- utils.convertHexColorToAssFormat("#00FF00"); // "&H0000FF00"
314
- utils.convertHexColorToAssFormat("#0000FF"); // "&H00FF0000"
315
- utils.convertHexColorToAssFormat("#FF0000FF"); // "&HFF00FF00"
316
- utils.convertHexColorToAssFormat(); // ""
317
- ```
318
-
319
- **参数说明:**
320
- - `hexColor?: string` - 十六进制颜色值,支持 7 位(#RRGGBB)和 9 位(#RRGGBBAA)格式
321
-
322
- **作用:**
323
- 1. 将十六进制颜色转换为 ASS 格式,用于字幕样式设置
324
- 2. 支持 7 位和 9 位十六进制颜色格式
325
- 3. 自动处理颜色通道的顺序转换(RGB 转 BGR)
326
-
327
- ##### `convertHexColorToFFmpegFormat(hexColor: string): string`
328
- 将十六进制颜色转换为 FFmpeg 格式,用于字幕样式设置。
329
-
330
- ```typescript
331
- import { utils } from '@aim-packages/subtitle';
332
-
333
- utils.convertHexColorToFFmpegFormat("#FF0000"); // "&H0000FF00&"
334
- utils.convertHexColorToFFmpegFormat("#00FF00"); // "&H0000FF00&"
335
- utils.convertHexColorToFFmpegFormat("#0000FF"); // "&H00FF0000&"
336
- utils.convertHexColorToFFmpegFormat("#FF0000FF"); // "&HFF00FF00&"
337
- ```
338
-
339
- **参数说明:**
340
- - `hexColor: string` - 十六进制颜色值,支持 7 位(#RRGGBB)和 9 位(#RRGGBBAA)格式
341
-
342
- **作用:**
343
- 1. 将十六进制颜色转换为 FFmpeg 格式,用于字幕样式设置
344
- 2. 支持 7 位和 9 位十六进制颜色格式
345
- 3. 自动处理颜色通道的顺序转换(RGB 转 BGR)
346
-
347
- #### 时间格式转换
348
-
349
- ##### `convertTimeToAssFormat(str: string): string`
350
- 将时间字符串转换为 ASS 字幕格式。
351
-
352
- ```typescript
353
- import { utils } from '@aim-packages/subtitle';
354
-
355
- utils.convertTimeToAssFormat("01:30:45.123"); // "1:30:45.12"
356
- utils.convertTimeToAssFormat("00:05:30.050"); // "0:05:30.05"
357
- utils.convertTimeToAssFormat("00:00:00.999"); // "0:00:00.99"
358
- ```
359
-
360
- **参数说明:**
361
- - `str: string` - 输入的时间字符串,格式为 HH:MM:SS.MMM
362
-
363
- **作用:**
364
- 1. 将标准时间格式字符串转换为 ASS 字幕文件所需的时间格式
365
- 2. 支持 HH:MM:SS.MMM 格式的输入
366
- 3. 确保时间轴的准确性和兼容性
367
- 4. 自动处理毫秒的精度转换(3位转2位)
368
-
369
- ##### `cleanTimeDisplay(timeString: string): string`
370
- 清理时间显示格式,移除毫秒部分和多余的小时前缀。
371
-
372
- ```typescript
373
- import { utils } from '@aim-packages/subtitle';
374
-
375
- utils.cleanTimeDisplay("00:01:30.500"); // "01:30"
376
- utils.cleanTimeDisplay("01:45:20,123"); // "01:45:20"
377
- utils.cleanTimeDisplay("00:00:05.00"); // "00:05"
378
- utils.cleanTimeDisplay("01:30:45,50"); // "01:30:45"
379
- ```
380
-
381
- **参数说明:**
382
- - `timeString: string` - 输入的时间字符串
383
-
384
- **作用:**
385
- 1. 移除时间字符串末尾的毫秒部分(支持 .xxx、,xxx 等分隔符)
386
- 2. 移除时间字符串开头多余的 "00:" 小时前缀
387
- 3. 保持时间格式的简洁性和可读性
388
- 4. 自动处理不同格式的时间字符串,统一输出格式
389
-
390
- ### Parser 字幕格式解析器
391
-
392
- Parser 模块提供了多种字幕格式的解析和转换功能,支持 SRT、VTT、ASS 等常见字幕格式,以及流式解析器用于实时处理。
393
-
394
- #### 基础格式转换
395
-
396
- ##### `srtToAimSegments(text: string): Promise<AimSegments[]>`
397
-
398
- 将 SRT 格式的字幕文本转换为 `AimSegments` 数组。
399
-
400
- **参数:**
401
- - `text: string` - SRT 格式的字幕文本内容
402
-
403
- **返回值:**
404
- - `Promise<AimSegments[]>` - 转换后的字幕片段数组
405
-
406
- **示例:**
407
- ```typescript
408
- import { parser } from '@aim-packages/subtitle';
409
-
410
- const srtContent = `1
411
- 00:00:01,000 --> 00:00:03,000
412
- Hello world.
413
-
414
- 2
415
- 00:00:03,000 --> 00:00:05,000
416
- How are you?`;
417
-
418
- const segments = await parser.srtToAimSegments(srtContent);
419
- // 返回: [
420
- // { st: "00:00:01.000", et: "00:00:03.000", text: "Hello world." },
421
- // { st: "00:00:03.000", et: "00:00:05.000", text: "How are you?" }
422
- // ]
423
- ```
424
-
425
- ##### `vttToAimSegments(text: string): Promise<AimSegments[]>`
426
-
427
- 将 VTT 格式的字幕文本转换为 `AimSegments` 数组。
428
-
429
- **参数:**
430
- - `text: string` - VTT 格式的字幕文本内容
431
-
432
- **返回值:**
433
- - `Promise<AimSegments[]>` - 转换后的字幕片段数组
434
-
435
- **示例:**
436
- ```typescript
437
- import { parser } from '@aim-packages/subtitle';
438
-
439
- const vttContent = `WEBVTT
440
-
441
- 00:00:01.000 --> 00:00:03.000
442
- Hello world.
443
-
444
- 00:00:03.000 --> 00:00:05.000
445
- How are you?`;
446
-
447
- const segments = await parser.vttToAimSegments(vttContent);
448
- // 返回: [
449
- // { st: "00:00:01.000", et: "00:00:03.000", text: "Hello world." },
450
- // { st: "00:00:03.000", et: "00:00:05.000", text: "How are you?" }
451
- // ]
452
- ```
453
-
454
- ##### `assToAimSegments(text: string): Promise<AimSegments[]>`
455
-
456
- 将 ASS 格式的字幕文本转换为 `AimSegments` 数组(通过先转换为 VTT 格式)。
457
-
458
- **参数:**
459
- - `text: string` - ASS 格式的字幕文本内容
460
-
461
- **返回值:**
462
- - `Promise<AimSegments[]>` - 转换后的字幕片段数组
463
-
464
- **示例:**
465
- ```typescript
466
- import { parser } from '@aim-packages/subtitle';
467
-
468
- const assContent = `[Script Info]
469
- Title: Example
470
- ScriptType: v4.00+
471
-
472
- [V4+ Styles]
473
- Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
474
- Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1
475
-
476
- [Events]
477
- Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
478
- Dialogue: 0,0:00:01.00,0:00:03.00,Default,,0,0,0,,Hello world.
479
- Dialogue: 0,0:00:03.00,0:00:05.00,Default,,0,0,0,,How are you?`;
480
-
481
- const segments = await parser.assToAimSegments(assContent);
482
- ```
483
-
484
- #### 第三方服务结果转换
485
-
486
- ##### `tingwuToAimSegments(json: TingwuResult): Promise<AimSegments[]>`
487
-
488
- 将腾讯云听悟服务的识别结果转换为 `AimSegments` 数组。
489
-
490
- **参数:**
491
- - `json: TingwuResult` - 腾讯云听悟服务的识别结果
492
-
493
- **返回值:**
494
- - `Promise<AimSegments[]>` - 转换后的字幕片段数组
495
-
496
- **示例:**
497
- ```typescript
498
- import { parser } from '@aim-packages/subtitle';
499
-
500
- const tingwuResult = {
501
- // 腾讯云听悟服务的识别结果
502
- };
503
-
504
- const segments = await parser.tingwuToAimSegments(tingwuResult);
505
- ```
506
-
507
- ##### `openaiToAimSegments(json: OpenAIResult): Promise<AimSegments[]>`
508
-
509
- 将 OpenAI Whisper 服务的识别结果转换为 `AimSegments` 数组。
510
-
511
- **参数:**
512
- - `json: OpenAIResult` - OpenAI Whisper 服务的识别结果
513
-
514
- **返回值:**
515
- - `Promise<AimSegments[]>` - 转换后的字幕片段数组
516
-
517
- **示例:**
518
- ```typescript
519
- import { parser } from '@aim-packages/subtitle';
520
-
521
- const openaiResult = {
522
- // OpenAI Whisper 服务的识别结果
523
- };
524
-
525
- const segments = await parser.openaiToAimSegments(openaiResult);
526
- ```
527
-
528
- #### 流式解析器
529
-
530
- ##### `createWhisperStreamParser(options?: ParserOptions)`
531
-
532
- 创建用于处理 Whisper 流式输出的解析器。
533
-
534
- **参数:**
535
- - `options?: ParserOptions` - 解析器配置选项
536
-
537
- **返回值:**
538
- - `Parser<string>` - 流式解析器实例
539
-
540
- **示例:**
541
- ```typescript
542
- import { parser } from '@aim-packages/subtitle';
543
-
544
- const whisperParser = parser.createWhisperStreamParser({
545
- onStart: (event) => {
546
- console.log('开始解析 Whisper 流');
547
- },
548
- onParse: (event) => {
549
- console.log('解析到字幕片段:', event.data);
550
- },
551
- onEnd: (event) => {
552
- console.log('解析完成');
553
- }
554
- });
555
-
556
- // 使用解析器
557
- whisperParser.feed(chunk);
558
- whisperParser.end();
559
- ```
560
-
561
- ##### `createTranslateStreamParser(options?: ParserOptions)`
562
-
563
- 创建用于处理翻译流式输出的解析器。
564
-
565
- **参数:**
566
- - `options?: ParserOptions` - 解析器配置选项
567
-
568
- **返回值:**
569
- - `Parser<string>` - 流式解析器实例
570
-
571
- **示例:**
572
- ```typescript
573
- import { parser } from '@aim-packages/subtitle';
574
-
575
- const translateParser = parser.createTranslateStreamParser({
576
- onParse: (event) => {
577
- console.log('解析到翻译片段:', event.data);
578
- }
579
- });
580
-
581
- // 使用解析器
582
- translateParser.feed(chunk);
583
- translateParser.end();
584
- ```
585
-
586
- ##### `createSegmentStreamParser(options?: ParserOptions)`
587
-
588
- 创建用于处理通用字幕片段流式输出的解析器。
589
-
590
- **参数:**
591
- - `options?: ParserOptions` - 解析器配置选项
592
-
593
- **返回值:**
594
- - `Parser<string>` - 流式解析器实例
595
-
596
- **示例:**
597
- ```typescript
598
- import { parser } from '@aim-packages/subtitle';
599
-
600
- const segmentParser = parser.createSegmentStreamParser({
601
- onParse: (event) => {
602
- console.log('解析到字幕片段:', event.data);
603
- }
604
- });
605
-
606
- // 使用解析器
607
- segmentParser.feed(chunk);
608
- segmentParser.end();
609
- ```
610
-
611
- #### Parser 模块相关接口
612
-
613
- ##### `Parser<T>` 接口
614
- ```typescript
615
- interface Parser<T = string> {
616
- /** 向解析器输入数据块 */
617
- feed(chunk: T): void
618
- /** 重置解析器状态 */
619
- reset(): void
620
- /** 结束解析 */
621
- end(): void
622
- }
623
- ```
624
-
625
- ##### `ParserOptions<S, P, E>` 类型
626
- ```typescript
627
- type ParserOptions<S, P, E> = {
628
- /** 解析开始时的回调函数 */
629
- onStart?: ParseCallback<S>
630
- /** 解析过程中调用的回调函数 */
631
- onParse?: ParseCallback<P>
632
- /** 解析进度更新时的回调函数 */
633
- onProgress?: ParseCallback<P>
634
- /** 解析结束时的回调函数 */
635
- onEnd?: ParseCallback<E>
636
- }
637
- ```
638
-
639
- ##### `ParsedEvent<T>` 接口
640
- ```typescript
641
- interface ParsedEvent<T> {
642
- type: "event"
643
- event: "start" | "message" | "end"
644
- data?: T
645
- }
646
- ```
647
-
648
- ##### `ParseCallback<T>` 类型
649
- ```typescript
650
- type ParseCallback<T> = (event: ParsedEvent<T>) => void
651
- ```
652
-
653
- ### Filter 流式文本过滤器
654
-
655
- Filter 模块提供了基于 DFA(确定有限自动机)算法的流式文本过滤功能,支持实时敏感词检测和替换。
656
-
657
- #### `StreamFilter` 类
658
-
659
- 流式文本过滤器,支持逐字符输入和实时过滤。
660
-
661
- **构造函数:**
662
- ```typescript
663
- constructor(onFilter?: (text: string) => any)
664
- ```
665
-
666
- **参数:**
667
- - `onFilter?: (text: string) => any` - 可选的回调函数,在每次过滤时调用
668
-
669
- **方法:**
670
-
671
- ##### `add(keyword: string, replaceText?: string)`
672
-
673
- 添加关键词和对应的替换文本。
674
-
675
- **参数:**
676
- - `keyword: string` - 需要过滤的关键词
677
- - `replaceText?: string` - 替换文本,默认为空字符串
678
-
679
- **示例:**
680
- ```typescript
681
- import { filter } from '@aim-packages/subtitle';
682
-
683
- const streamFilter = new filter.StreamFilter();
684
- streamFilter.add("敏感词", "***");
685
- streamFilter.add("另一个词", "替换文本");
686
- ```
687
-
688
- ##### `parse(data: [string, string][])`
689
-
690
- 批量添加关键词和替换文本。
691
-
692
- **参数:**
693
- - `data: [string, string][]` - 关键词和替换文本的数组
694
-
695
- **示例:**
696
- ```typescript
697
- import { filter } from '@aim-packages/subtitle';
698
-
699
- const streamFilter = new filter.StreamFilter();
700
- streamFilter.parse([
701
- ["敏感词1", "***"],
702
- ["敏感词2", "替换文本"],
703
- ["敏感词3", ""] // 空字符串表示删除
704
- ]);
705
- ```
706
-
707
- ##### `reParse(data: string[][])`
708
-
709
- 重新解析关键词列表,会清空之前的所有关键词。
710
-
711
- **参数:**
712
- - `data: string[][]` - 关键词和替换文本的二维数组
713
-
714
- **示例:**
715
- ```typescript
716
- import { filter } from '@aim-packages/subtitle';
717
-
718
- const streamFilter = new filter.StreamFilter();
719
- streamFilter.reParse([
720
- ["敏感词1", "***"],
721
- ["敏感词2", "替换文本"]
722
- ]);
723
- ```
724
-
725
- ##### `feed(c: string)`
726
-
727
- 逐字符输入文本进行过滤。
728
-
729
- **参数:**
730
- - `c: string` - 单个字符
731
-
732
- **示例:**
733
- ```typescript
734
- import { filter } from '@aim-packages/subtitle';
735
-
736
- const streamFilter = new filter.StreamFilter((text) => {
737
- console.log('过滤后的字符:', text);
738
- });
739
-
740
- streamFilter.add("敏感词", "***");
741
-
742
- // 逐字符输入
743
- streamFilter.feed("这");
744
- streamFilter.feed("是");
745
- streamFilter.feed("敏");
746
- streamFilter.feed("感");
747
- streamFilter.feed("词");
748
- streamFilter.feed("。");
749
-
750
- const result = streamFilter.end();
751
- console.log('最终结果:', result); // "这是***。"
752
- ```
753
-
754
- ##### `feedAll(text: string): string`
755
-
756
- 一次性输入完整文本进行过滤。
757
-
758
- **参数:**
759
- - `text: string` - 完整的文本内容
760
-
761
- **返回值:**
762
- - `string` - 过滤后的文本
763
-
764
- **示例:**
765
- ```typescript
766
- import { filter } from '@aim-packages/subtitle';
767
-
768
- const streamFilter = new filter.StreamFilter();
769
- streamFilter.add("敏感词", "***");
770
-
771
- const result = streamFilter.feedAll("这是一个敏感词测试。");
772
- console.log(result); // "这是一个***测试。"
773
- ```
774
-
775
- ##### `end(): string`
776
-
777
- 结束过滤并返回结果。
778
-
779
- **返回值:**
780
- - `string` - 过滤后的完整文本
781
-
782
- **示例:**
783
- ```typescript
784
- import { filter } from '@aim-packages/subtitle';
785
-
786
- const streamFilter = new filter.StreamFilter();
787
- streamFilter.add("敏感词", "***");
788
-
789
- // 逐字符输入
790
- streamFilter.feed("这");
791
- streamFilter.feed("是");
792
- streamFilter.feed("敏");
793
- streamFilter.feed("感");
794
- streamFilter.feed("词");
795
-
796
- const result = streamFilter.end();
797
- console.log(result); // "这是***"
798
- ```
799
-
800
- #### 完整使用示例
801
-
802
- ```typescript
803
- import { filter } from '@aim-packages/subtitle';
804
-
805
- // 创建流式过滤器
806
- const streamFilter = new filter.StreamFilter((char) => {
807
- console.log('实时输出字符:', char);
808
- });
809
-
810
- // 添加过滤规则
811
- streamFilter.parse([
812
- ["敏感词1", "***"],
813
- ["敏感词2", "替换文本"],
814
- ["要删除的词", ""]
815
- ]);
816
-
817
- // 方式1: 逐字符输入
818
- streamFilter.feed("这");
819
- streamFilter.feed("是");
820
- streamFilter.feed("敏");
821
- streamFilter.feed("感");
822
- streamFilter.feed("词");
823
- streamFilter.feed("1");
824
- streamFilter.feed("。");
825
-
826
- const result1 = streamFilter.end();
827
- console.log('逐字符结果:', result1);
828
-
829
- // 方式2: 一次性输入
830
- const result2 = streamFilter.feedAll("这是敏感词2测试。");
831
- console.log('一次性结果:', result2);
832
- ```
833
-
834
- ### Tools 字幕处理工具
835
-
836
- Tools 模块提供了字幕处理相关的各种工具方法,包括语言检测、文本分割和字幕优化等功能。
837
-
838
- #### 语言检测
839
-
840
- ##### `detectLanguage(text: string): LanguageDetectionResultsEntry`
841
-
842
- 检测文本的主要语言。
843
-
844
- **参数:**
845
- - `text: string` - 需要检测语言的文本内容
846
-
847
- **返回值:**
848
- - `LanguageDetectionResultsEntry` - 包含语言代码、语言名称和概率的对象
849
-
850
- **示例:**
851
- ```typescript
852
- import { tools } from '@aim-packages/subtitle';
853
-
854
- const result = tools.detection.detectLanguage("Hello world");
855
- // 返回: { language: 'en', languageName: 'English', probability: 1 }
856
- ```
857
-
858
- ##### `detectAllLanguage(text: string): LanguageDetectionResultsEntry[]`
859
-
860
- 检测文本中的所有可能语言,返回按概率排序的语言列表。
861
-
862
- **参数:**
863
- - `text: string` - 需要检测语言的文本内容
864
-
865
- **返回值:**
866
- - `LanguageDetectionResultsEntry[]` - 检测到的所有语言及其概率,按概率从高到低排序
867
-
868
- **示例:**
869
- ```typescript
870
- import { tools } from '@aim-packages/subtitle';
871
-
872
- const results = tools.detection.detectAllLanguage("Hello world 你好世界");
873
- // 返回: [
874
- // { language: 'en', languageName: 'English', probability: 0.8 },
875
- // { language: 'zh', languageName: '中文', probability: 0.2 }
876
- // ]
877
- ```
878
-
879
- #### 文本分割
880
-
881
- ##### `splitToSentences(text: string, languageCode?: LanguageCode): string[]`
882
-
883
- 将文本按句子进行分割。
884
-
885
- **参数:**
886
- - `text: string` - 需要分割的文本内容
887
- - `languageCode?: LanguageCode` - 可选的语言代码,用于更准确的分割
888
-
889
- **返回值:**
890
- - `string[]` - 分割后的句子数组
891
-
892
- **示例:**
893
- ```typescript
894
- import { tools } from '@aim-packages/subtitle';
895
-
896
- // 使用默认分割器
897
- const sentences1 = tools.segmentation.splitToSentences("Hello world. How are you?");
898
- // 返回: ["Hello world.", "How are you?"]
899
-
900
- // 使用指定语言的分割器
901
- const sentences2 = tools.segmentation.splitToSentences("你好世界。今天天气怎么样?", 'zh');
902
- // 返回: ["你好世界。", "今天天气怎么样?"]
903
- ```
904
-
905
- #### 字幕优化
906
-
907
- ##### `subtitleOptimization(segments: AimSegments[], options?: OptimizationOptions)`
908
-
909
- 对字幕片段进行全面的质量检查和优化。
910
-
911
- **功能包括:**
912
- - 重复内容检测和移除
913
- - 空白字幕处理
914
- - 0帧字幕处理(开始时间大于等于结束时间)
915
- - 标点符号检查
916
- - 句子结束标记检查
917
- - 多余空格处理
918
- - 结尾标点处理
919
-
920
- **参数:**
921
- - `segments: AimSegments[]` - 需要优化的字幕片段数组
922
- - `options?: OptimizationOptions` - 优化选项配置
923
-
924
- **返回值:**
925
- ```typescript
926
- {
927
- result: {
928
- emt: number[], // 空白字幕索引
929
- ep: number[], // 结尾标点索引
930
- zf: number[], // 0帧字幕索引
931
- punc: number[], // 标点符号索引
932
- em: number[], // 句子结束标记索引
933
- space: number[], // 多余空格索引
934
- repeat: number[], // 重复内容索引
935
- },
936
- repeat: AimSegments[], // 重复的片段列表
937
- segments: AimSegments[] // 优化后的字幕片段数组
938
- }
939
- ```
940
-
941
- **示例:**
942
- ```typescript
943
- import { tools } from '@aim-packages/subtitle';
944
-
945
- const segments = [
946
- { st: "00:00:01", et: "00:00:03", text: "Hello world." },
947
- { st: "00:00:03", et: "00:00:05", text: "Hello world." }, // 重复内容
948
- { st: "00:00:05", et: "00:00:06", text: " " }, // 空白字幕
949
- { st: "00:00:06", et: "00:00:06", text: "Zero frame" }, // 0帧字幕
950
- ];
951
-
952
- const result = tools.optimization.subtitleOptimization(segments, {
953
- repeat: true, // 移除重复内容
954
- emt: true, // 移除空白字幕
955
- zf: true, // 移除0帧字幕
956
- space: true, // 处理多余空格
957
- ep: true // 移除结尾标点
958
- });
959
- ```
960
-
961
- ##### `RepeatCheck` 类
962
-
963
- 重复内容检测器,用于检测字幕中连续重复的内容片段。
964
-
965
- **构造函数:**
966
- ```typescript
967
- constructor(options?: RepeatCheckOption)
968
- ```
969
-
970
- **方法:**
971
-
972
- - `push(segment: AimSegments)` - 添加字幕片段进行重复检测
973
- - `end()` - 结束检测,处理最后的重复片段
974
- - `reset()` - 重置检测器状态
975
-
976
- **属性:**
977
- - `threshold: number` - 重复检测阈值,默认为2次
978
- - `hit: number` - 当前重复次数
979
- - `prevSegment?: AimSegments` - 前一个字幕片段
980
- - `hitSegment?: AimSegments` - 当前重复的字幕片段
981
- - `hitSegmentList: AimSegments[]` - 重复片段列表
982
- - `options: RepeatCheckOption` - 配置选项
983
-
984
- **示例:**
985
- ```typescript
986
- import { tools } from '@aim-packages/subtitle';
987
-
988
- const repeatCheck = new tools.optimization.RepeatCheck({
989
- onHit: (segments) => {
990
- console.log('检测到重复内容:', segments);
991
- }
992
- });
993
-
994
- repeatCheck.push({ st: "00:00:01", et: "00:00:03", text: "Hello" });
995
- repeatCheck.push({ st: "00:00:03", et: "00:00:05", text: "Hello" }); // 重复
996
- repeatCheck.push({ st: "00:00:05", et: "00:00:07", text: "Hello" }); // 重复,触发回调
997
- repeatCheck.end();
998
- ```
999
-
1000
- #### 字幕输出
1001
-
1002
- 字幕输出模块提供了多种格式的字幕文件生成功能,支持SRT、VTT、LRC、ASS等主流字幕格式。
1003
-
1004
- ##### `outputSrt(params: OutputTextParams): string`
1005
-
1006
- 生成SRT格式的字幕文件。
1007
-
1008
- **参数:**
1009
- - `params: OutputTextParams` - 输出参数配置
1010
-
1011
- **返回值:**
1012
- - `string` - SRT格式的字幕内容
1013
-
1014
- **示例:**
1015
- ```typescript
1016
- import { tools } from '@aim-packages/subtitle';
1017
-
1018
- const segments1 = [
1019
- ["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
1020
- ["00:00:03,000", "00:00:05,000", "How are you?", "speaker2"]
1021
- ];
1022
-
1023
- const segments2 = [
1024
- ["00:00:01,000", "00:00:03,000", "你好世界", "speaker1"],
1025
- ["00:00:03,000", "00:00:05,000", "你好吗?", "speaker2"]
1026
- ];
1027
-
1028
- const speakerData = {
1029
- settings: {
1030
- speaker1: { spk: "speaker1", name: "张三", color: "#FF0000" },
1031
- speaker2: { spk: "speaker2", name: "李四", color: "#00FF00" }
1032
- },
1033
- speakers: { speaker1: 1, speaker2: 1 },
1034
- data: []
1035
- };
1036
-
1037
- const srtContent = tools.output.outputSrt({
1038
- segments1,
1039
- segments2,
1040
- speakerData
1041
- });
1042
-
1043
- console.log(srtContent);
1044
- // 输出:
1045
- // 1
1046
- // 00:00:01,000 --> 00:00:03,000
1047
- // 张三: Hello world
1048
- // 你好世界
1049
- //
1050
- // 2
1051
- // 00:00:03,000 --> 00:00:05,000
1052
- // 李四: How are you?
1053
- // 你好吗?
1054
- ```
1055
-
1056
- ##### `outputVtt(params: OutputTextParams): string`
1057
-
1058
- 生成VTT格式的字幕文件。
1059
-
1060
- **参数:**
1061
- - `params: OutputTextParams` - 输出参数配置
1062
-
1063
- **返回值:**
1064
- - `string` - VTT格式的字幕内容
1065
-
1066
- **示例:**
1067
- ```typescript
1068
- import { tools } from '@aim-packages/subtitle';
1069
-
1070
- const segments1 = [
1071
- ["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
1072
- ["00:00:03,000", "00:00:05,000", "How are you?", "speaker2"]
1073
- ];
1074
-
1075
- const vttContent = tools.output.outputVtt({
1076
- segments1,
1077
- speakerData
1078
- });
1079
-
1080
- console.log(vttContent);
1081
- // 输出:
1082
- // WEBVTT
1083
- //
1084
- // 00:00:01.000 --> 00:00:03.000
1085
- // 张三: Hello world
1086
- //
1087
- // 00:00:03.000 --> 00:00:05.000
1088
- // 李四: How are you?
1089
- ```
1090
-
1091
- ##### `outputLrc(params: OutputTextParams): string`
1092
-
1093
- 生成LRC格式的字幕文件。
1094
-
1095
- **参数:**
1096
- - `params: OutputTextParams` - 输出参数配置
1097
-
1098
- **返回值:**
1099
- - `string` - LRC格式的字幕内容
1100
-
1101
- **示例:**
1102
- ```typescript
1103
- import { tools } from '@aim-packages/subtitle';
1104
-
1105
- const segments1 = [
1106
- ["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
1107
- ["00:00:03,000", "00:00:05,000", "How are you?", "speaker2"]
1108
- ];
1109
-
1110
- const lrcContent = tools.output.outputLrc({
1111
- segments1,
1112
- segments2,
1113
- speakerData
1114
- });
1115
-
1116
- console.log(lrcContent);
1117
- // 输出:
1118
- // [01.000]张三: Hello world
1119
- // [01.000]你好世界
1120
- // [03.000]李四: How are you?
1121
- // [03.000]你好吗?
1122
- ```
1123
-
1124
- ##### `outputAss(params: OutputTextParams): string`
1125
-
1126
- 生成ASS格式的字幕文件,支持复杂的样式配置。
1127
-
1128
- **参数:**
1129
- - `params: OutputTextParams` - 输出参数配置
1130
-
1131
- **返回值:**
1132
- - `string` - ASS格式的字幕内容
1133
-
1134
- **示例:**
1135
- ```typescript
1136
- import { tools } from '@aim-packages/subtitle';
1137
-
1138
- const segments1 = [
1139
- ["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
1140
- ["00:00:03,000", "00:00:05,000", "How are you?", "speaker2"]
1141
- ];
1142
-
1143
- const segments2 = [
1144
- ["00:00:01,000", "00:00:03,000", "你好世界", "speaker1"],
1145
- ["00:00:03,000", "00:00:05,000", "你好吗?", "speaker2"]
1146
- ];
1147
-
1148
- const subtitleSettings = {
1149
- disabled: false,
1150
- stroke: true,
1151
- shadow: true,
1152
- background: true,
1153
- backgroundColor: "#000000",
1154
- main: {
1155
- color: "#FFFFFF",
1156
- borderColor: "#000000",
1157
- size: 18,
1158
- fontFamily: "Microsoft YaHei"
1159
- },
1160
- sub: {
1161
- color: "#FFFF00",
1162
- borderColor: "#000000",
1163
- size: 14,
1164
- fontFamily: "Microsoft YaHei"
1165
- },
1166
- position: {
1167
- bottom: 20,
1168
- left: 10
1169
- },
1170
- mode: "multiLang"
1171
- };
1172
-
1173
- const assContent = tools.output.outputAss({
1174
- segments1,
1175
- segments2,
1176
- subtitleSettings,
1177
- speakerData,
1178
- isMac: false,
1179
- reverse: false
1180
- });
1181
-
1182
- console.log(assContent);
1183
- // 输出完整的ASS格式字幕文件,包含样式定义和字幕内容
1184
- ```
1185
-
1186
- ##### `outputTxt(params: OutputTextParams): string`
1187
-
1188
- 生成TXT格式的纯文本文件,支持多种输出模式。
1189
-
1190
- **参数:**
1191
- - `params: OutputTextParams` - 输出参数配置
1192
- - `useIndex?: boolean` - 是否显示索引编号
1193
- - `useTimestamp?: boolean` - 是否显示时间戳
1194
- - `useParagraph?: boolean` - 是否使用段落模式(按发言人分组或按chunkSize分组)
1195
-
1196
- **返回值:**
1197
- - `string` - TXT格式的文本内容
1198
-
1199
- **功能特性:**
1200
- - **段落模式**: 支持按说话人分组或按固定大小分块
1201
- - **行模式**: 每个字幕片段单独一行
1202
- - **多语言支持**: 自动检测中日韩语言,调整文本连接方式
1203
- - **说话人支持**: 支持显示说话人名称和时间戳
1204
-
1205
- **示例:**
1206
- ```typescript
1207
- import { tools } from '@aim-packages/subtitle';
1208
-
1209
- const segments1 = [
1210
- ["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
1211
- ["00:00:03,000", "00:00:05,000", "How are you?", "speaker1"],
1212
- ["00:00:05,000", "00:00:07,000", "I'm fine, thank you.", "speaker2"]
1213
- ];
1214
-
1215
- const segments2 = [
1216
- ["00:00:01,000", "00:00:03,000", "你好世界", "speaker1"],
1217
- ["00:00:03,000", "00:00:05,000", "你好吗?", "speaker1"],
1218
- ["00:00:05,000", "00:00:07,000", "我很好,谢谢。", "speaker2"]
1219
- ];
1220
-
1221
- const speakerData = {
1222
- settings: {
1223
- speaker1: { spk: "speaker1", name: "张三", color: "#FF0000" },
1224
- speaker2: { spk: "speaker2", name: "李四", color: "#00FF00" }
1225
- },
1226
- speakers: { speaker1: 1, speaker2: 1 },
1227
- data: []
1228
- };
1229
-
1230
- // 行模式 - 每个字幕片段单独一行
1231
- const txtContent1 = tools.output.outputTxt({
1232
- segments1,
1233
- segments2,
1234
- speakerData,
1235
- useIndex: true,
1236
- useTimestamp: true,
1237
- useParagraph: false
1238
- });
1239
-
1240
- console.log(txtContent1);
1241
- // 输出:
1242
- // 0
1243
- // 00:00:01,000 --> 00:00:03,000
1244
- // 张三: Hello world
1245
- // 你好世界
1246
- //
1247
- // 1
1248
- // 00:00:03,000 --> 00:00:05,000
1249
- // 张三: How are you?
1250
- // 你好吗?
1251
- //
1252
- // 2
1253
- // 00:00:05,000 --> 00:00:07,000
1254
- // 李四: I'm fine, thank you.
1255
- // 我很好,谢谢。
1256
-
1257
- // 段落模式 - 按说话人分组
1258
- const txtContent2 = tools.output.outputTxt({
1259
- segments1,
1260
- segments2,
1261
- speakerData,
1262
- useIndex: true,
1263
- useTimestamp: true,
1264
- useParagraph: true
1265
- });
1266
-
1267
- console.log(txtContent2);
1268
- // 输出:
1269
- // 1
1270
- // 张三 - 00:00:01,000 --> 00:00:05,000
1271
- // Hello world How are you?
1272
- // 你好世界 你好吗?
1273
- //
1274
- // 2
1275
- // 李四 - 00:00:05,000 --> 00:00:07,000
1276
- // I'm fine, thank you.
1277
- // 我很好,谢谢。
1278
-
1279
- ##### `outputMarkdown(params: OutputTextParams): string`
1280
-
1281
- 生成 Markdown 格式的字幕文件,支持多种输出模式和说话人分组。
1282
-
1283
- **参数:**
1284
- - `params: OutputTextParams` - 输出参数配置
1285
- - `header?: string` - 文档标题
1286
- - `isMd?: boolean` - 是否为 Markdown 格式(影响换行符)
1287
- - `chunkSize?: number` - 分块大小,默认为 10
1288
-
1289
- **返回值:**
1290
- - `string` - Markdown 格式的字幕内容
1291
-
1292
- **功能特性:**
1293
- - **说话人分组**: 支持按说话人分组显示内容
1294
- - **多语言支持**: 自动检测中日韩语言,调整文本连接方式
1295
- - **分块处理**: 支持按固定大小分块处理长文本
1296
- - **时间显示**: 自动清理时间格式,移除毫秒和多余前缀
1297
- - **灵活格式**: 支持 Markdown 和纯文本两种输出格式
1298
-
1299
- **示例:**
1300
- ```typescript
1301
- import { tools } from '@aim-packages/subtitle';
1302
-
1303
- const segments1 = [
1304
- ["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
1305
- ["00:00:03,000", "00:00:05,000", "How are you?", "speaker1"],
1306
- ["00:00:05,000", "00:00:07,000", "I'm fine, thank you.", "speaker2"]
1307
- ];
1308
-
1309
- const segments2 = [
1310
- ["00:00:01,000", "00:00:03,000", "你好世界", "speaker1"],
1311
- ["00:00:03,000", "00:00:05,000", "你好吗?", "speaker1"],
1312
- ["00:00:05,000", "00:00:07,000", "我很好,谢谢。", "speaker2"]
1313
- ];
1314
-
1315
- const speakerData = {
1316
- settings: {
1317
- speaker1: { spk: "speaker1", name: "张三", color: "#FF0000" },
1318
- speaker2: { spk: "speaker2", name: "李四", color: "#00FF00" }
1319
- },
1320
- speakers: { speaker1: 1, speaker2: 1 },
1321
- data: []
1322
- };
1323
-
1324
- // 带说话人的 Markdown 输出
1325
- const mdContent = tools.output.outputMarkdown({
1326
- segments1,
1327
- segments2,
1328
- header: "# 会议记录",
1329
- speakerData,
1330
- chunkSize: 5
1331
- });
1332
-
1333
- console.log(mdContent);
1334
- // 输出:
1335
- // # 会议记录
1336
- //
1337
- // 张三 01:30 - 01:45
1338
- // Hello world How are you?
1339
- // 你好世界 你好吗?
1340
- //
1341
- // 李四 01:45 - 01:47
1342
- // I'm fine, thank you.
1343
- // 我很好,谢谢。
1344
-
1345
- // 纯文本输出(无说话人)
1346
- const textContent = tools.output.outputMarkdown({
1347
- segments1,
1348
- segments2,
1349
- header: "会议记录",
1350
- isMd: false,
1351
- chunkSize: 3
1352
- });
1353
-
1354
- console.log(textContent);
1355
- // 输出:
1356
- // 会议记录
1357
- // 1. Hello world
1358
- // 你好世界
1359
- // 2. How are you?
1360
- // 你好吗?
1361
- // 3. I'm fine, thank you.
1362
- // 我很好,谢谢。
1363
- ```
1364
-
1365
- #### 完整的字幕处理流程示例
1366
-
1367
- ```typescript
1368
- import { tools } from '@aim-packages/subtitle';
1369
-
1370
- // 1. 检测语言
1371
- const language = tools.detection.detectLanguage("Hello world. How are you?");
1372
-
1373
- // 2. 按句子分割
1374
- const sentences = tools.segmentation.splitToSentences("Hello world. How are you?", language.language);
1375
-
1376
- // 3. 转换为字幕片段
1377
- const segments = sentences.map((text, index) => ({
1378
- st: `00:00:${index * 2}`,
1379
- et: `00:00:${(index + 1) * 2}`,
1380
- text
1381
- }));
1382
-
1383
- // 4. 优化字幕
1384
- const optimized = tools.optimization.subtitleOptimization(segments, {
1385
- repeat: true,
1386
- emt: true,
1387
- zf: true,
1388
- space: true,
1389
- ep: true
1390
- });
1391
-
1392
- console.log('优化结果:', optimized);
1393
- ```
1394
-
1395
- #### 完整的字幕处理流程示例
1396
-
1397
- ```typescript
1398
- import { utils, parser, filter, tools } from '@aim-packages/subtitle';
1399
-
1400
- // 1. 解析字幕文件
1401
- const srtContent = `1
1402
- 00:00:01,000 --> 00:00:03,000
1403
- Hello world.
1404
-
1405
- 2
1406
- 00:00:03,000 --> 00:00:05,000
1407
- How are you?`;
1408
-
1409
- const segments = await parser.srtToAimSegments(srtContent);
1410
-
1411
- // 2. 检测语言
1412
- const language = tools.detection.detectLanguage(segments[0].text);
1413
-
1414
- // 3. 按句子分割并重新生成字幕片段
1415
- const sentences = tools.segmentation.splitToSentences(segments[0].text, language.language);
1416
- const newSegments = sentences.map((text, index) => ({
1417
- st: utils.formatTime(index * 2),
1418
- et: utils.formatTime((index + 1) * 2),
1419
- text
1420
- }));
1421
-
1422
- // 4. 优化字幕质量
1423
- const optimized = tools.optimization.subtitleOptimization(newSegments, {
1424
- repeat: true,
1425
- emt: true,
1426
- zf: true,
1427
- space: true,
1428
- ep: true
1429
- });
1430
-
1431
- // 5. 文本过滤
1432
- const streamFilter = new filter.StreamFilter();
1433
- streamFilter.parse([
1434
- ["敏感词", "***"],
1435
- ["不当词汇", "替换文本"]
1436
- ]);
1437
-
1438
- const filteredSegments = optimized.segments.map(segment => ({
1439
- ...segment,
1440
- text: streamFilter.feedAll(segment.text)
1441
- }));
1442
-
1443
- console.log('最终处理结果:', filteredSegments);
1444
- ```
1445
-
1446
- #### Tools 模块相关接口
1447
-
1448
- ##### `LanguageDetectionResultsEntry`
1449
- ```typescript
1450
- interface LanguageDetectionResultsEntry {
1451
- /** 语言代码 (如 'zh', 'en', 'ja' 等) */
1452
- language: LanguageCode
1453
- /** 语言名称 (如 '中文', 'English', '日本語' 等) */
1454
- languageName: string
1455
- /** 检测准确度概率 (0-1 之间的数值) */
1456
- probability: number
1457
- }
1458
- ```
1459
-
1460
- ##### `RepeatCheckOption`
1461
- ```typescript
1462
- interface RepeatCheckOption {
1463
- /** 当检测到重复内容时的回调函数 */
1464
- onHit?: (segment: AimSegments[]) => void
1465
- }
1466
- ```
1467
-
1468
- ##### `OutputTextParams`
1469
- ```typescript
1470
- interface OutputTextParams {
1471
- /** 主要字幕片段数组 */
1472
- segments1: Array<ISegment>;
1473
- /** 次要字幕片段数组(如翻译字幕) */
1474
- segments2?: Array<ISegment>;
1475
- /** 字幕样式设置 */
1476
- subtitleSettings?: SubtitleSettings;
1477
- /** 说话人数据 */
1478
- speakerData?: SpeakerData | null;
1479
- /** 本地化配置 */
1480
- locale?: Record<string, any>;
1481
-
1482
- // TXT格式相关
1483
- /** 是否使用索引 */
1484
- useIndex?: boolean;
1485
- /** 是否使用时间戳 */
1486
- useTimestamp?: boolean;
1487
- /** 是否使用段落格式 */
1488
- useParagraph?: boolean;
1489
-
1490
- // Markdown格式相关
1491
- /** 文档标题 */
1492
- header?: string;
1493
- /** 是否为Markdown格式 */
1494
- isMd?: boolean;
1495
- /** 分块大小 */
1496
- chunkSize?: number;
1497
-
1498
- // ASS格式相关
1499
- /** 是否为Mac系统 */
1500
- isMac?: boolean;
1501
- /** 是否反转字幕顺序 */
1502
- reverse?: boolean;
1503
- }
1504
- ```
1505
-
1506
- ##### `ISegment`
1507
- ```typescript
1508
- type ISegment = [string, string, string, string | undefined]
1509
- // [开始时间, 结束时间, 文本内容, 说话人标识]
1510
- ```
1511
-
1512
- ##### `SpeakerData`
1513
- ```typescript
1514
- interface SpeakerData {
1515
- /** 说话人设置配置 */
1516
- settings: Record<string, { spk: string; name?: string; color: string }>;
1517
- /** 说话人统计 */
1518
- speakers: Record<string, number>;
1519
- /** 说话人时间数据 */
1520
- data: { start: number; end: number; speaker: string }[];
1521
- /** 其他选项 */
1522
- options?: { speakerCount?: number; }
1523
- }
1524
- ```
1525
-
1526
- ##### `SubtitleSettings`
1527
- ```typescript
1528
- interface SubtitleSettings {
1529
- /** 是否禁用字幕 */
1530
- disabled: boolean;
1531
- /** 是否启用描边 */
1532
- stroke: boolean;
1533
- /** 是否启用阴影 */
1534
- shadow: boolean;
1535
- /** 是否启用背景 */
1536
- background: boolean;
1537
- /** 背景颜色 */
1538
- backgroundColor: string;
1539
- /** 主要字幕样式 */
1540
- main: SubtitleTextStyle;
1541
- /** 次要字幕样式 */
1542
- sub: SubtitleTextStyle;
1543
- /** 字幕位置 */
1544
- position: SubtitlePosition;
1545
- /** 字幕语言模式 */
1546
- mode: SubtitleLanguage;
1547
- /** 字幕顺序 */
1548
- order?: number[];
1549
- /** 水印样式 */
1550
- watermark?: WatermarkStyle;
1551
- }
1552
- ```
1553
-
1554
- ## 🏗️ 项目结构
1555
-
1556
- ```
1557
- src/
1558
- ├── utils/ # 工具函数 - 时间格式化、语言处理、文本分块等
1559
- ├── parser/ # 字幕格式解析器 - SRT、VTT、ASS 格式转换和流式解析
1560
- ├── tools/ # 字幕处理工具 - 语言检测、文本分割、字幕优化等
1561
- ├── filter/ # 字幕过滤器 - 基于 DFA 算法的流式文本过滤
1562
- ├── types/ # TypeScript 类型定义
1563
- └── main.ts # 主入口文件
1564
- ```
1565
-
1566
- ## 📋 模块功能概览
1567
-
1568
- ### Utils 模块
1569
- - **时间处理**: 时间格式化、秒数转换、数字补零、ASS 时间格式转换等
1570
- - **语言处理**: 中日韩字符检测、语言代码转换等
1571
- - **文本分块**: 按字符限制分块、字幕片段分块等
1572
- - **字幕合并**: 合并相邻字幕片段、时间轴优化等
1573
- - **颜色转换**: 十六进制颜色转 ASS 格式、FFmpeg 格式等
1574
-
1575
- ### Parser 模块
1576
- - **格式转换**: SRT、VTT、ASS 格式之间的相互转换
1577
- - **第三方服务**: 腾讯云听悟、OpenAI Whisper 结果转换
1578
- - **流式解析**: 支持实时流式字幕数据的解析和处理
1579
-
1580
- ### Tools 模块
1581
- - **语言检测**: 多语言文本的语言识别和概率分析
1582
- - **文本分割**: 按句子、段落等规则分割文本
1583
- - **字幕优化**: 重复检测、空白处理、质量优化等
1584
- - **字幕输出**: 支持SRT、VTT、LRC、ASS等多种格式的字幕文件生成
1585
-
1586
- ### Filter 模块
1587
- - **流式过滤**: 基于 DFA 算法的实时文本过滤
1588
- - **敏感词处理**: 支持关键词替换、删除等操作
1589
- - **逐字符处理**: 支持逐字符输入和实时过滤
1590
-
1591
- ## 🔧 开发
1592
-
1593
- ```bash
1594
- # 安装依赖
1595
- pnpm install
1596
-
1597
- # 开发模式
1598
- pnpm dev
1599
-
1600
- # 构建
1601
- pnpm build
1602
-
1603
- # 预览构建结果
1604
- pnpm preview
1605
-
1606
- # 发布
1607
- pnpm release
1608
- ```
1609
-
1610
- ## 📄 许可证
1611
-
1612
- MIT License
1613
-
1614
- ## 🤝 贡献
1615
-
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
+ const segments = [["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"]];
44
+ const srtContent = tools.output.outputSrt({ segments1: segments });
45
+
46
+ ## 📚 API 文档
47
+
48
+ ### 类型定义
49
+
50
+ #### `AimSegments` 接口
51
+
52
+ 这是字幕处理库的核心数据结构,用于表示单个字幕片段。
53
+
54
+ ```typescript
55
+ interface AimSegments {
56
+ // 通用字段
57
+ st: string; // 开始时间戳 (Start timestamp)
58
+ et: string; // 结束时间戳 (End timestamp)
59
+ text: string; // 文本内容 (Text content)
60
+ index?: number; // 字幕索引 (Subtitle index)
61
+ children?: Array<AimSegments>; // 子片段 (Child segments)
62
+ f?: number; // 批量文件转写时所属的文件索引 (File index)
63
+
64
+ // 视频剪辑字段
65
+ delete?: boolean; // 是否被删除 (Whether it is deleted)
66
+ cut?: boolean; // 是否被裁剪 (Whether it is cut)
67
+
68
+ // TTS字段
69
+ md5?: string; // TTS使用的MD5 (MD5 used by TTS)
70
+
71
+ // 翻译字段
72
+ tr?: 0 | 1 | 2; // 翻译状态: 0-未翻译原文, 1-翻译中, 2-翻译完成
73
+
74
+ // 说话人字段
75
+ spk?: string; // 说话人 (Speaker)
76
+
77
+ // 烧录字段
78
+ duration?: number; // 时长 (Duration)
79
+ range?: number; // 本句开始到下一个片段的可用时长
80
+
81
+ // 重复检测字段
82
+ hit?: number; // 重复次数,表示转写重复的片段
83
+ rs?: Array<AimSegments>; // 重复片段 (Repeated segments)
84
+
85
+ // 分句字段
86
+ em?: 0 | 1; // 是否结束句 (End mark)
87
+ punc?: 0 | 1; // 是否存在标点分割符号
88
+ lng?: string; // 语言 (Language)
89
+
90
+ // 优化字段
91
+ zf?: 0 | 1; // 零帧字幕 (Zero frame subtitle)
92
+ emt?: 0 | 1; // 空白字幕 (Empty text subtitle)
93
+ ep?: 0 | 1; // 结尾标点 (End punctuation)
94
+ space?: 0 | 1; // 存在多余空格
95
+ }
96
+ ```
97
+
98
+ #### `Segment` 类型
99
+
100
+ 用于表示简单的时间段。
101
+
102
+ ```typescript
103
+ type Segment = {
104
+ start: number; // 开始时间(秒)
105
+ end: number; // 结束时间(秒)
106
+ };
107
+ ```
108
+
109
+ #### `OptimizationOptions` 接口
110
+
111
+ 字幕优化选项配置。
112
+
113
+ ```typescript
114
+ interface OptimizationOptions {
115
+ emt: boolean; // 是否移除空白字幕
116
+ ep: boolean; // 是否移除结尾标点
117
+ zf: boolean; // 是否移除零帧字幕
118
+ punc: boolean; // 是否移除标点分割符号
119
+ em: boolean; // 是否移除结束标记
120
+ space: boolean; // 是否移除多余空格
121
+ repeat: boolean; // 是否移除重复内容
122
+ }
123
+ ```
124
+
125
+ #### `LanguageCode` 类型
126
+
127
+ 支持的语言代码类型。
128
+
129
+ ```typescript
130
+ type LanguageCode =
131
+ | "auto" | "none" | "zh" | "zh_cn" | "zh_tw" | "yue"
132
+ | "en" | "ja" | "ko" | "fr" | "es" | "ru" | "de" | "it"
133
+ | "tr" | "pt" | "vi" | "id" | "th" | "ms" | "ar" | "hi"
134
+ | "ro" | "ug" | "uz" | "kk" | "az" | "ky" | "fa" | "tg";
135
+ ```
136
+
137
+ #### 工具类型
138
+
139
+ ```typescript
140
+ // 使指定键变为可选
141
+ type PartialByKey<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
142
+
143
+ // 使指定键变为必需
144
+ type RequiredByKey<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
145
+ ```
146
+
147
+ ### Utils 工具函数
148
+
149
+ #### 时间处理
150
+
151
+ ##### `padNumber(num: number, length?: number): string`
152
+ 为数字前方自动补0到特定的位数,默认两位。
153
+
154
+ ```typescript
155
+ import { utils } from '@aim-packages/subtitle';
156
+
157
+ utils.padNumber(5); // "05"
158
+ utils.padNumber(5, 3); // "005"
159
+ utils.padNumber(123, 2); // "123"
160
+ ```
161
+
162
+ ##### `formatTime(seconds: number): string`
163
+ 将秒数转换为 `HH:MM:SS.mmm` 格式。
164
+
165
+ ```typescript
166
+ import { utils } from '@aim-packages/subtitle';
167
+
168
+ utils.formatTime(3661.5); // "01:01:01.500"
169
+ utils.formatTime(125.75); // "00:02:05.750"
170
+ utils.formatTime(0.123); // "00:00:00.123"
171
+ ```
172
+
173
+ ##### `convertToSeconds(time?: string): number`
174
+ 将 `HH:MM:SS,mmm` 或 `HH:MM:SS.mmm` 格式的时间转换为秒数。
175
+
176
+ ```typescript
177
+ import { utils } from '@aim-packages/subtitle';
178
+
179
+ utils.convertToSeconds("01:01:01,500"); // 3661.5
180
+ utils.convertToSeconds("00:02:05.750"); // 125.75
181
+ utils.convertToSeconds("00:00:00,123"); // 0.123
182
+ utils.convertToSeconds(); // 0
183
+ ```
184
+
185
+ #### 语言处理
186
+
187
+ ##### `containsCJKCharacters(str: string): boolean`
188
+ 检测字符串是否包含中文、日文、韩文字符。
189
+
190
+ ```typescript
191
+ import { utils } from '@aim-packages/subtitle';
192
+
193
+ utils.containsCJKCharacters("Hello 世界"); // true
194
+ utils.containsCJKCharacters("こんにちは"); // true
195
+ utils.containsCJKCharacters("안녕하세요"); // true
196
+ utils.containsCJKCharacters("Hello World"); // false
197
+ ```
198
+
199
+ ##### `languageCodeToName(languageCode: LanguageCode): string`
200
+ 将语言代码转换为可读的语言名称。
201
+
202
+ ```typescript
203
+ import { utils } from '@aim-packages/subtitle';
204
+
205
+ utils.languageCodeToName("zh"); // "Chinese"
206
+ utils.languageCodeToName("en"); // "English"
207
+ utils.languageCodeToName("ja"); // "Japanese"
208
+ utils.languageCodeToName("ko"); // "Korean"
209
+ utils.languageCodeToName("auto"); // "Unknown"
210
+ ```
211
+
212
+ #### 文本分块处理
213
+
214
+ ##### `chunkArrayStrings(strings: string[], characterLimit: number): string[]`
215
+ 将字符串数组按字符限制分块,避免单次处理内容过多。
216
+
217
+ ```typescript
218
+ import { utils } from '@aim-packages/subtitle';
219
+
220
+ const strings = ["第一句话", "第二句话", "第三句话", "第四句话"];
221
+ const chunks = utils.chunkArrayStrings(strings, 20);
222
+ // 结果: ["第一句话\n第二句话", "第三句话\n第四句话"]
223
+ ```
224
+
225
+ **参数说明:**
226
+ - `strings`: 输入的字符串数组
227
+ - `characterLimit`: 每个分块的最大字符数限制
228
+
229
+ **作用:**
230
+ 1. 将字符串数组按照字符限制进行分块,避免单次处理内容过多
231
+ 2. 使用换行符连接同一分块内的字符串
232
+ 3. 在分块时会预留一定的字符空间(10个字符)用于换行符和格式调整
233
+
234
+ ##### `chunkSegmentStringsWithIndex(segments: AimSegments[], characterLimit: number)`
235
+ 将字幕片段按字符限制分块,并生成多种格式的结果。
236
+
237
+ ```typescript
238
+ import { utils } from '@aim-packages/subtitle';
239
+
240
+ const segments = [
241
+ { st: "00:00:01", et: "00:00:03", text: "第一句话" },
242
+ { st: "00:00:03", et: "00:00:05", text: "第二句话" },
243
+ { st: "00:00:05", et: "00:00:07", text: "第三句话" }
244
+ ];
245
+
246
+ const result = utils.chunkSegmentStringsWithIndex(segments, 50);
247
+ // 返回包含以下属性的对象:
248
+ // - segmentsResult: 分块后的字幕片段数组
249
+ // - stringResult: 纯文本分块结果
250
+ // - indexStringResult: 带索引的文本分块结果
251
+ // - indexResult: 每个分块的起始索引数组
252
+ ```
253
+
254
+ **参数说明:**
255
+ - `segments`: 输入的字幕片段数组
256
+ - `characterLimit`: 每个分块的最大字符数限制
257
+
258
+ **返回值:**
259
+ ```typescript
260
+ {
261
+ segmentsResult: AimSegments[][], // 分块后的字幕片段数组
262
+ stringResult: string[], // 纯文本分块结果
263
+ indexStringResult: string[], // 带索引的文本分块结果,格式为 [索引]文本
264
+ indexResult: number[] // 每个分块的起始索引数组
265
+ }
266
+ ```
267
+
268
+ **作用:**
269
+ 1. 将字幕片段数组按照字符限制分块,避免单次处理内容过多
270
+ 2. 为每个片段添加索引信息,便于后续处理和追踪
271
+ 3. 生成多种格式的分块结果,满足不同场景的需求
272
+
273
+ #### 字幕片段合并
274
+
275
+ ##### `consolidateSegments(items: Segment[], option: { maxDistance: number, padding: number }): Segment[]`
276
+ 合并字幕片段,优化字幕的时间轴。
277
+
278
+ ```typescript
279
+ import { utils } from '@aim-packages/subtitle';
280
+
281
+ const segments = [
282
+ { start: 0, end: 2 },
283
+ { start: 2.5, end: 4 },
284
+ { start: 8, end: 10 }
285
+ ];
286
+
287
+ const merged = utils.consolidateSegments(segments, {
288
+ maxDistance: 1, // 时间间隔小于1秒的片段会被合并
289
+ padding: 0.5 // 为每个片段扩展0.5秒的开始和结束时间
290
+ });
291
+ // 结果: [{ start: 0, end: 4.5 }, { start: 7.5, end: 10.5 }]
292
+ ```
293
+
294
+ **参数说明:**
295
+ - `items`: 输入的字幕片段数组,每个片段包含 `start` 和 `end` 时间
296
+ - `option.maxDistance`: 最大时间间隔,小于此值的相邻片段会被合并
297
+ - `option.padding`: 可选的时间填充,为每个片段扩展开始和结束时间
298
+
299
+ **作用:**
300
+ 1. 将时间间隔小于 `maxDistance` 的相邻字幕片段合并为一个片段
301
+ 2. 可选择性地为每个片段添加 `padding` 时间,扩展片段的开始和结束时间
302
+ 3. 如果添加了 `padding`,会自动处理重叠的片段,将它们合并
303
+
304
+ #### 颜色格式转换
305
+
306
+ ##### `convertHexColorToAssFormat(hexColor?: string): string`
307
+ 将十六进制颜色转换为 ASS 格式,用于字幕样式设置。
308
+
309
+ ```typescript
310
+ import { utils } from '@aim-packages/subtitle';
311
+
312
+ utils.convertHexColorToAssFormat("#FF0000"); // "&H0000FF00"
313
+ utils.convertHexColorToAssFormat("#00FF00"); // "&H0000FF00"
314
+ utils.convertHexColorToAssFormat("#0000FF"); // "&H00FF0000"
315
+ utils.convertHexColorToAssFormat("#FF0000FF"); // "&HFF00FF00"
316
+ utils.convertHexColorToAssFormat(); // ""
317
+ ```
318
+
319
+ **参数说明:**
320
+ - `hexColor?: string` - 十六进制颜色值,支持 7 位(#RRGGBB)和 9 位(#RRGGBBAA)格式
321
+
322
+ **作用:**
323
+ 1. 将十六进制颜色转换为 ASS 格式,用于字幕样式设置
324
+ 2. 支持 7 位和 9 位十六进制颜色格式
325
+ 3. 自动处理颜色通道的顺序转换(RGB 转 BGR)
326
+
327
+ ##### `convertHexColorToFFmpegFormat(hexColor: string): string`
328
+ 将十六进制颜色转换为 FFmpeg 格式,用于字幕样式设置。
329
+
330
+ ```typescript
331
+ import { utils } from '@aim-packages/subtitle';
332
+
333
+ utils.convertHexColorToFFmpegFormat("#FF0000"); // "&H0000FF00&"
334
+ utils.convertHexColorToFFmpegFormat("#00FF00"); // "&H0000FF00&"
335
+ utils.convertHexColorToFFmpegFormat("#0000FF"); // "&H00FF0000&"
336
+ utils.convertHexColorToFFmpegFormat("#FF0000FF"); // "&HFF00FF00&"
337
+ ```
338
+
339
+ **参数说明:**
340
+ - `hexColor: string` - 十六进制颜色值,支持 7 位(#RRGGBB)和 9 位(#RRGGBBAA)格式
341
+
342
+ **作用:**
343
+ 1. 将十六进制颜色转换为 FFmpeg 格式,用于字幕样式设置
344
+ 2. 支持 7 位和 9 位十六进制颜色格式
345
+ 3. 自动处理颜色通道的顺序转换(RGB 转 BGR)
346
+
347
+ #### 时间格式转换
348
+
349
+ ##### `convertTimeToAssFormat(str: string): string`
350
+ 将时间字符串转换为 ASS 字幕格式。
351
+
352
+ ```typescript
353
+ import { utils } from '@aim-packages/subtitle';
354
+
355
+ utils.convertTimeToAssFormat("01:30:45.123"); // "1:30:45.12"
356
+ utils.convertTimeToAssFormat("00:05:30.050"); // "0:05:30.05"
357
+ utils.convertTimeToAssFormat("00:00:00.999"); // "0:00:00.99"
358
+ ```
359
+
360
+ **参数说明:**
361
+ - `str: string` - 输入的时间字符串,格式为 HH:MM:SS.MMM
362
+
363
+ **作用:**
364
+ 1. 将标准时间格式字符串转换为 ASS 字幕文件所需的时间格式
365
+ 2. 支持 HH:MM:SS.MMM 格式的输入
366
+ 3. 确保时间轴的准确性和兼容性
367
+ 4. 自动处理毫秒的精度转换(3位转2位)
368
+
369
+ ##### `cleanTimeDisplay(timeString: string): string`
370
+ 清理时间显示格式,移除毫秒部分和多余的小时前缀。
371
+
372
+ ```typescript
373
+ import { utils } from '@aim-packages/subtitle';
374
+
375
+ utils.cleanTimeDisplay("00:01:30.500"); // "01:30"
376
+ utils.cleanTimeDisplay("01:45:20,123"); // "01:45:20"
377
+ utils.cleanTimeDisplay("00:00:05.00"); // "00:05"
378
+ utils.cleanTimeDisplay("01:30:45,50"); // "01:30:45"
379
+ ```
380
+
381
+ **参数说明:**
382
+ - `timeString: string` - 输入的时间字符串
383
+
384
+ **作用:**
385
+ 1. 移除时间字符串末尾的毫秒部分(支持 .xxx、,xxx 等分隔符)
386
+ 2. 移除时间字符串开头多余的 "00:" 小时前缀
387
+ 3. 保持时间格式的简洁性和可读性
388
+ 4. 自动处理不同格式的时间字符串,统一输出格式
389
+
390
+ ### Parser 字幕格式解析器
391
+
392
+ Parser 模块提供了多种字幕格式的解析和转换功能,支持 SRT、VTT、ASS 等常见字幕格式,以及流式解析器用于实时处理。
393
+
394
+ #### 基础格式转换
395
+
396
+ ##### `srtToAimSegments(text: string): Promise<AimSegments[]>`
397
+
398
+ 将 SRT 格式的字幕文本转换为 `AimSegments` 数组。
399
+
400
+ **参数:**
401
+ - `text: string` - SRT 格式的字幕文本内容
402
+
403
+ **返回值:**
404
+ - `Promise<AimSegments[]>` - 转换后的字幕片段数组
405
+
406
+ **示例:**
407
+ ```typescript
408
+ import { parser } from '@aim-packages/subtitle';
409
+
410
+ const srtContent = `1
411
+ 00:00:01,000 --> 00:00:03,000
412
+ Hello world.
413
+
414
+ 2
415
+ 00:00:03,000 --> 00:00:05,000
416
+ How are you?`;
417
+
418
+ const segments = await parser.srtToAimSegments(srtContent);
419
+ // 返回: [
420
+ // { st: "00:00:01.000", et: "00:00:03.000", text: "Hello world." },
421
+ // { st: "00:00:03.000", et: "00:00:05.000", text: "How are you?" }
422
+ // ]
423
+ ```
424
+
425
+ ##### `vttToAimSegments(text: string): Promise<AimSegments[]>`
426
+
427
+ 将 VTT 格式的字幕文本转换为 `AimSegments` 数组。
428
+
429
+ **参数:**
430
+ - `text: string` - VTT 格式的字幕文本内容
431
+
432
+ **返回值:**
433
+ - `Promise<AimSegments[]>` - 转换后的字幕片段数组
434
+
435
+ **示例:**
436
+ ```typescript
437
+ import { parser } from '@aim-packages/subtitle';
438
+
439
+ const vttContent = `WEBVTT
440
+
441
+ 00:00:01.000 --> 00:00:03.000
442
+ Hello world.
443
+
444
+ 00:00:03.000 --> 00:00:05.000
445
+ How are you?`;
446
+
447
+ const segments = await parser.vttToAimSegments(vttContent);
448
+ // 返回: [
449
+ // { st: "00:00:01.000", et: "00:00:03.000", text: "Hello world." },
450
+ // { st: "00:00:03.000", et: "00:00:05.000", text: "How are you?" }
451
+ // ]
452
+ ```
453
+
454
+ ##### `assToAimSegments(text: string): Promise<AimSegments[]>`
455
+
456
+ 将 ASS 格式的字幕文本转换为 `AimSegments` 数组(通过先转换为 VTT 格式)。
457
+
458
+ **参数:**
459
+ - `text: string` - ASS 格式的字幕文本内容
460
+
461
+ **返回值:**
462
+ - `Promise<AimSegments[]>` - 转换后的字幕片段数组
463
+
464
+ **示例:**
465
+ ```typescript
466
+ import { parser } from '@aim-packages/subtitle';
467
+
468
+ const assContent = `[Script Info]
469
+ Title: Example
470
+ ScriptType: v4.00+
471
+
472
+ [V4+ Styles]
473
+ Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
474
+ Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1
475
+
476
+ [Events]
477
+ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
478
+ Dialogue: 0,0:00:01.00,0:00:03.00,Default,,0,0,0,,Hello world.
479
+ Dialogue: 0,0:00:03.00,0:00:05.00,Default,,0,0,0,,How are you?`;
480
+
481
+ const segments = await parser.assToAimSegments(assContent);
482
+ ```
483
+
484
+ #### 第三方服务结果转换
485
+
486
+ ##### `tingwuToAimSegments(json: TingwuResult): Promise<AimSegments[]>`
487
+
488
+ 将腾讯云听悟服务的识别结果转换为 `AimSegments` 数组。
489
+
490
+ **参数:**
491
+ - `json: TingwuResult` - 腾讯云听悟服务的识别结果
492
+
493
+ **返回值:**
494
+ - `Promise<AimSegments[]>` - 转换后的字幕片段数组
495
+
496
+ **示例:**
497
+ ```typescript
498
+ import { parser } from '@aim-packages/subtitle';
499
+
500
+ const tingwuResult = {
501
+ // 腾讯云听悟服务的识别结果
502
+ };
503
+
504
+ const segments = await parser.tingwuToAimSegments(tingwuResult);
505
+ ```
506
+
507
+ ##### `openaiToAimSegments(json: OpenAIResult): Promise<AimSegments[]>`
508
+
509
+ 将 OpenAI Whisper 服务的识别结果转换为 `AimSegments` 数组。
510
+
511
+ **参数:**
512
+ - `json: OpenAIResult` - OpenAI Whisper 服务的识别结果
513
+
514
+ **返回值:**
515
+ - `Promise<AimSegments[]>` - 转换后的字幕片段数组
516
+
517
+ **示例:**
518
+ ```typescript
519
+ import { parser } from '@aim-packages/subtitle';
520
+
521
+ const openaiResult = {
522
+ // OpenAI Whisper 服务的识别结果
523
+ };
524
+
525
+ const segments = await parser.openaiToAimSegments(openaiResult);
526
+ ```
527
+
528
+ #### 流式解析器
529
+
530
+ ##### `createWhisperStreamParser(options?: ParserOptions)`
531
+
532
+ 创建用于处理 Whisper 流式输出的解析器。
533
+
534
+ **参数:**
535
+ - `options?: ParserOptions` - 解析器配置选项
536
+
537
+ **返回值:**
538
+ - `Parser<string>` - 流式解析器实例
539
+
540
+ **示例:**
541
+ ```typescript
542
+ import { parser } from '@aim-packages/subtitle';
543
+
544
+ const whisperParser = parser.createWhisperStreamParser({
545
+ onStart: (event) => {
546
+ console.log('开始解析 Whisper 流');
547
+ },
548
+ onParse: (event) => {
549
+ console.log('解析到字幕片段:', event.data);
550
+ },
551
+ onEnd: (event) => {
552
+ console.log('解析完成');
553
+ }
554
+ });
555
+
556
+ // 使用解析器
557
+ whisperParser.feed(chunk);
558
+ whisperParser.end();
559
+ ```
560
+
561
+ ##### `createTranslateStreamParser(options?: ParserOptions)`
562
+
563
+ 创建用于处理翻译流式输出的解析器。
564
+
565
+ **参数:**
566
+ - `options?: ParserOptions` - 解析器配置选项
567
+
568
+ **返回值:**
569
+ - `Parser<string>` - 流式解析器实例
570
+
571
+ **示例:**
572
+ ```typescript
573
+ import { parser } from '@aim-packages/subtitle';
574
+
575
+ const translateParser = parser.createTranslateStreamParser({
576
+ onParse: (event) => {
577
+ console.log('解析到翻译片段:', event.data);
578
+ }
579
+ });
580
+
581
+ // 使用解析器
582
+ translateParser.feed(chunk);
583
+ translateParser.end();
584
+ ```
585
+
586
+ ##### `createSegmentStreamParser(options?: ParserOptions)`
587
+
588
+ 创建用于处理通用字幕片段流式输出的解析器。
589
+
590
+ **参数:**
591
+ - `options?: ParserOptions` - 解析器配置选项
592
+
593
+ **返回值:**
594
+ - `Parser<string>` - 流式解析器实例
595
+
596
+ **示例:**
597
+ ```typescript
598
+ import { parser } from '@aim-packages/subtitle';
599
+
600
+ const segmentParser = parser.createSegmentStreamParser({
601
+ onParse: (event) => {
602
+ console.log('解析到字幕片段:', event.data);
603
+ }
604
+ });
605
+
606
+ // 使用解析器
607
+ segmentParser.feed(chunk);
608
+ segmentParser.end();
609
+ ```
610
+
611
+ #### Parser 模块相关接口
612
+
613
+ ##### `Parser<T>` 接口
614
+ ```typescript
615
+ interface Parser<T = string> {
616
+ /** 向解析器输入数据块 */
617
+ feed(chunk: T): void
618
+ /** 重置解析器状态 */
619
+ reset(): void
620
+ /** 结束解析 */
621
+ end(): void
622
+ }
623
+ ```
624
+
625
+ ##### `ParserOptions<S, P, E>` 类型
626
+ ```typescript
627
+ type ParserOptions<S, P, E> = {
628
+ /** 解析开始时的回调函数 */
629
+ onStart?: ParseCallback<S>
630
+ /** 解析过程中调用的回调函数 */
631
+ onParse?: ParseCallback<P>
632
+ /** 解析进度更新时的回调函数 */
633
+ onProgress?: ParseCallback<P>
634
+ /** 解析结束时的回调函数 */
635
+ onEnd?: ParseCallback<E>
636
+ }
637
+ ```
638
+
639
+ ##### `ParsedEvent<T>` 接口
640
+ ```typescript
641
+ interface ParsedEvent<T> {
642
+ type: "event"
643
+ event: "start" | "message" | "end"
644
+ data?: T
645
+ }
646
+ ```
647
+
648
+ ##### `ParseCallback<T>` 类型
649
+ ```typescript
650
+ type ParseCallback<T> = (event: ParsedEvent<T>) => void
651
+ ```
652
+
653
+ ### Filter 流式文本过滤器
654
+
655
+ Filter 模块提供了基于 DFA(确定有限自动机)算法的流式文本过滤功能,支持实时敏感词检测和替换。
656
+
657
+ #### `StreamFilter` 类
658
+
659
+ 流式文本过滤器,支持逐字符输入和实时过滤。
660
+
661
+ **构造函数:**
662
+ ```typescript
663
+ constructor(onFilter?: (text: string) => any)
664
+ ```
665
+
666
+ **参数:**
667
+ - `onFilter?: (text: string) => any` - 可选的回调函数,在每次过滤时调用
668
+
669
+ **方法:**
670
+
671
+ ##### `add(keyword: string, replaceText?: string)`
672
+
673
+ 添加关键词和对应的替换文本。
674
+
675
+ **参数:**
676
+ - `keyword: string` - 需要过滤的关键词
677
+ - `replaceText?: string` - 替换文本,默认为空字符串
678
+
679
+ **示例:**
680
+ ```typescript
681
+ import { filter } from '@aim-packages/subtitle';
682
+
683
+ const streamFilter = new filter.StreamFilter();
684
+ streamFilter.add("敏感词", "***");
685
+ streamFilter.add("另一个词", "替换文本");
686
+ ```
687
+
688
+ ##### `parse(data: [string, string][])`
689
+
690
+ 批量添加关键词和替换文本。
691
+
692
+ **参数:**
693
+ - `data: [string, string][]` - 关键词和替换文本的数组
694
+
695
+ **示例:**
696
+ ```typescript
697
+ import { filter } from '@aim-packages/subtitle';
698
+
699
+ const streamFilter = new filter.StreamFilter();
700
+ streamFilter.parse([
701
+ ["敏感词1", "***"],
702
+ ["敏感词2", "替换文本"],
703
+ ["敏感词3", ""] // 空字符串表示删除
704
+ ]);
705
+ ```
706
+
707
+ ##### `reParse(data: string[][])`
708
+
709
+ 重新解析关键词列表,会清空之前的所有关键词。
710
+
711
+ **参数:**
712
+ - `data: string[][]` - 关键词和替换文本的二维数组
713
+
714
+ **示例:**
715
+ ```typescript
716
+ import { filter } from '@aim-packages/subtitle';
717
+
718
+ const streamFilter = new filter.StreamFilter();
719
+ streamFilter.reParse([
720
+ ["敏感词1", "***"],
721
+ ["敏感词2", "替换文本"]
722
+ ]);
723
+ ```
724
+
725
+ ##### `feed(c: string)`
726
+
727
+ 逐字符输入文本进行过滤。
728
+
729
+ **参数:**
730
+ - `c: string` - 单个字符
731
+
732
+ **示例:**
733
+ ```typescript
734
+ import { filter } from '@aim-packages/subtitle';
735
+
736
+ const streamFilter = new filter.StreamFilter((text) => {
737
+ console.log('过滤后的字符:', text);
738
+ });
739
+
740
+ streamFilter.add("敏感词", "***");
741
+
742
+ // 逐字符输入
743
+ streamFilter.feed("这");
744
+ streamFilter.feed("是");
745
+ streamFilter.feed("敏");
746
+ streamFilter.feed("感");
747
+ streamFilter.feed("词");
748
+ streamFilter.feed("。");
749
+
750
+ const result = streamFilter.end();
751
+ console.log('最终结果:', result); // "这是***。"
752
+ ```
753
+
754
+ ##### `feedAll(text: string): string`
755
+
756
+ 一次性输入完整文本进行过滤。
757
+
758
+ **参数:**
759
+ - `text: string` - 完整的文本内容
760
+
761
+ **返回值:**
762
+ - `string` - 过滤后的文本
763
+
764
+ **示例:**
765
+ ```typescript
766
+ import { filter } from '@aim-packages/subtitle';
767
+
768
+ const streamFilter = new filter.StreamFilter();
769
+ streamFilter.add("敏感词", "***");
770
+
771
+ const result = streamFilter.feedAll("这是一个敏感词测试。");
772
+ console.log(result); // "这是一个***测试。"
773
+ ```
774
+
775
+ ##### `end(): string`
776
+
777
+ 结束过滤并返回结果。
778
+
779
+ **返回值:**
780
+ - `string` - 过滤后的完整文本
781
+
782
+ **示例:**
783
+ ```typescript
784
+ import { filter } from '@aim-packages/subtitle';
785
+
786
+ const streamFilter = new filter.StreamFilter();
787
+ streamFilter.add("敏感词", "***");
788
+
789
+ // 逐字符输入
790
+ streamFilter.feed("这");
791
+ streamFilter.feed("是");
792
+ streamFilter.feed("敏");
793
+ streamFilter.feed("感");
794
+ streamFilter.feed("词");
795
+
796
+ const result = streamFilter.end();
797
+ console.log(result); // "这是***"
798
+ ```
799
+
800
+ #### 完整使用示例
801
+
802
+ ```typescript
803
+ import { filter } from '@aim-packages/subtitle';
804
+
805
+ // 创建流式过滤器
806
+ const streamFilter = new filter.StreamFilter((char) => {
807
+ console.log('实时输出字符:', char);
808
+ });
809
+
810
+ // 添加过滤规则
811
+ streamFilter.parse([
812
+ ["敏感词1", "***"],
813
+ ["敏感词2", "替换文本"],
814
+ ["要删除的词", ""]
815
+ ]);
816
+
817
+ // 方式1: 逐字符输入
818
+ streamFilter.feed("这");
819
+ streamFilter.feed("是");
820
+ streamFilter.feed("敏");
821
+ streamFilter.feed("感");
822
+ streamFilter.feed("词");
823
+ streamFilter.feed("1");
824
+ streamFilter.feed("。");
825
+
826
+ const result1 = streamFilter.end();
827
+ console.log('逐字符结果:', result1);
828
+
829
+ // 方式2: 一次性输入
830
+ const result2 = streamFilter.feedAll("这是敏感词2测试。");
831
+ console.log('一次性结果:', result2);
832
+ ```
833
+
834
+ ### Tools 字幕处理工具
835
+
836
+ Tools 模块提供了字幕处理相关的各种工具方法,包括语言检测、文本分割和字幕优化等功能。
837
+
838
+ #### 语言检测
839
+
840
+ ##### `detectLanguage(text: string): LanguageDetectionResultsEntry`
841
+
842
+ 检测文本的主要语言。
843
+
844
+ **参数:**
845
+ - `text: string` - 需要检测语言的文本内容
846
+
847
+ **返回值:**
848
+ - `LanguageDetectionResultsEntry` - 包含语言代码、语言名称和概率的对象
849
+
850
+ **示例:**
851
+ ```typescript
852
+ import { tools } from '@aim-packages/subtitle';
853
+
854
+ const result = tools.detection.detectLanguage("Hello world");
855
+ // 返回: { language: 'en', languageName: 'English', probability: 1 }
856
+ ```
857
+
858
+ ##### `detectAllLanguage(text: string): LanguageDetectionResultsEntry[]`
859
+
860
+ 检测文本中的所有可能语言,返回按概率排序的语言列表。
861
+
862
+ **参数:**
863
+ - `text: string` - 需要检测语言的文本内容
864
+
865
+ **返回值:**
866
+ - `LanguageDetectionResultsEntry[]` - 检测到的所有语言及其概率,按概率从高到低排序
867
+
868
+ **示例:**
869
+ ```typescript
870
+ import { tools } from '@aim-packages/subtitle';
871
+
872
+ const results = tools.detection.detectAllLanguage("Hello world 你好世界");
873
+ // 返回: [
874
+ // { language: 'en', languageName: 'English', probability: 0.8 },
875
+ // { language: 'zh', languageName: '中文', probability: 0.2 }
876
+ // ]
877
+ ```
878
+
879
+ #### 文本分割
880
+
881
+ ##### `splitToSentences(text: string, languageCode?: LanguageCode): string[]`
882
+
883
+ 将文本按句子进行分割。
884
+
885
+ **参数:**
886
+ - `text: string` - 需要分割的文本内容
887
+ - `languageCode?: LanguageCode` - 可选的语言代码,用于更准确的分割
888
+
889
+ **返回值:**
890
+ - `string[]` - 分割后的句子数组
891
+
892
+ **示例:**
893
+ ```typescript
894
+ import { tools } from '@aim-packages/subtitle';
895
+
896
+ // 使用默认分割器
897
+ const sentences1 = tools.segmentation.splitToSentences("Hello world. How are you?");
898
+ // 返回: ["Hello world.", "How are you?"]
899
+
900
+ // 使用指定语言的分割器
901
+ const sentences2 = tools.segmentation.splitToSentences("你好世界。今天天气怎么样?", 'zh');
902
+ // 返回: ["你好世界。", "今天天气怎么样?"]
903
+ ```
904
+
905
+ #### 字幕优化
906
+
907
+ ##### `subtitleOptimization(segments: AimSegments[], options?: OptimizationOptions)`
908
+
909
+ 对字幕片段进行全面的质量检查和优化。
910
+
911
+ **功能包括:**
912
+ - 重复内容检测和移除
913
+ - 空白字幕处理
914
+ - 0帧字幕处理(开始时间大于等于结束时间)
915
+ - 标点符号检查
916
+ - 句子结束标记检查
917
+ - 多余空格处理
918
+ - 结尾标点处理
919
+
920
+ **参数:**
921
+ - `segments: AimSegments[]` - 需要优化的字幕片段数组
922
+ - `options?: OptimizationOptions` - 优化选项配置
923
+
924
+ **返回值:**
925
+ ```typescript
926
+ {
927
+ result: {
928
+ emt: number[], // 空白字幕索引
929
+ ep: number[], // 结尾标点索引
930
+ zf: number[], // 0帧字幕索引
931
+ punc: number[], // 标点符号索引
932
+ em: number[], // 句子结束标记索引
933
+ space: number[], // 多余空格索引
934
+ repeat: number[], // 重复内容索引
935
+ },
936
+ repeat: AimSegments[], // 重复的片段列表
937
+ segments: AimSegments[] // 优化后的字幕片段数组
938
+ }
939
+ ```
940
+
941
+ **示例:**
942
+ ```typescript
943
+ import { tools } from '@aim-packages/subtitle';
944
+
945
+ const segments = [
946
+ { st: "00:00:01", et: "00:00:03", text: "Hello world." },
947
+ { st: "00:00:03", et: "00:00:05", text: "Hello world." }, // 重复内容
948
+ { st: "00:00:05", et: "00:00:06", text: " " }, // 空白字幕
949
+ { st: "00:00:06", et: "00:00:06", text: "Zero frame" }, // 0帧字幕
950
+ ];
951
+
952
+ const result = tools.optimization.subtitleOptimization(segments, {
953
+ repeat: true, // 移除重复内容
954
+ emt: true, // 移除空白字幕
955
+ zf: true, // 移除0帧字幕
956
+ space: true, // 处理多余空格
957
+ ep: true // 移除结尾标点
958
+ });
959
+ ```
960
+
961
+ ##### `RepeatCheck` 类
962
+
963
+ 重复内容检测器,用于检测字幕中连续重复的内容片段。
964
+
965
+ **构造函数:**
966
+ ```typescript
967
+ constructor(options?: RepeatCheckOption)
968
+ ```
969
+
970
+ **方法:**
971
+
972
+ - `push(segment: AimSegments)` - 添加字幕片段进行重复检测
973
+ - `end()` - 结束检测,处理最后的重复片段
974
+ - `reset()` - 重置检测器状态
975
+
976
+ **属性:**
977
+ - `threshold: number` - 重复检测阈值,默认为2次
978
+ - `hit: number` - 当前重复次数
979
+ - `prevSegment?: AimSegments` - 前一个字幕片段
980
+ - `hitSegment?: AimSegments` - 当前重复的字幕片段
981
+ - `hitSegmentList: AimSegments[]` - 重复片段列表
982
+ - `options: RepeatCheckOption` - 配置选项
983
+
984
+ **示例:**
985
+ ```typescript
986
+ import { tools } from '@aim-packages/subtitle';
987
+
988
+ const repeatCheck = new tools.optimization.RepeatCheck({
989
+ onHit: (segments) => {
990
+ console.log('检测到重复内容:', segments);
991
+ }
992
+ });
993
+
994
+ repeatCheck.push({ st: "00:00:01", et: "00:00:03", text: "Hello" });
995
+ repeatCheck.push({ st: "00:00:03", et: "00:00:05", text: "Hello" }); // 重复
996
+ repeatCheck.push({ st: "00:00:05", et: "00:00:07", text: "Hello" }); // 重复,触发回调
997
+ repeatCheck.end();
998
+ ```
999
+
1000
+ #### 字幕输出
1001
+
1002
+ 字幕输出模块提供了多种格式的字幕文件生成功能,支持SRT、VTT、LRC、ASS等主流字幕格式。
1003
+
1004
+ ##### `outputSrt(params: OutputTextParams): string`
1005
+
1006
+ 生成SRT格式的字幕文件。
1007
+
1008
+ **参数:**
1009
+ - `params: OutputTextParams` - 输出参数配置
1010
+
1011
+ **返回值:**
1012
+ - `string` - SRT格式的字幕内容
1013
+
1014
+ **示例:**
1015
+ ```typescript
1016
+ import { tools } from '@aim-packages/subtitle';
1017
+
1018
+ const segments1 = [
1019
+ ["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
1020
+ ["00:00:03,000", "00:00:05,000", "How are you?", "speaker2"]
1021
+ ];
1022
+
1023
+ const segments2 = [
1024
+ ["00:00:01,000", "00:00:03,000", "你好世界", "speaker1"],
1025
+ ["00:00:03,000", "00:00:05,000", "你好吗?", "speaker2"]
1026
+ ];
1027
+
1028
+ const speakerData = {
1029
+ settings: {
1030
+ speaker1: { spk: "speaker1", name: "张三", color: "#FF0000" },
1031
+ speaker2: { spk: "speaker2", name: "李四", color: "#00FF00" }
1032
+ },
1033
+ speakers: { speaker1: 1, speaker2: 1 },
1034
+ data: []
1035
+ };
1036
+
1037
+ const srtContent = tools.output.outputSrt({
1038
+ segments1,
1039
+ segments2,
1040
+ speakerData
1041
+ });
1042
+
1043
+ console.log(srtContent);
1044
+ // 输出:
1045
+ // 1
1046
+ // 00:00:01,000 --> 00:00:03,000
1047
+ // 张三: Hello world
1048
+ // 你好世界
1049
+ //
1050
+ // 2
1051
+ // 00:00:03,000 --> 00:00:05,000
1052
+ // 李四: How are you?
1053
+ // 你好吗?
1054
+ ```
1055
+
1056
+ ##### `outputVtt(params: OutputTextParams): string`
1057
+
1058
+ 生成VTT格式的字幕文件。
1059
+
1060
+ **参数:**
1061
+ - `params: OutputTextParams` - 输出参数配置
1062
+
1063
+ **返回值:**
1064
+ - `string` - VTT格式的字幕内容
1065
+
1066
+ **示例:**
1067
+ ```typescript
1068
+ import { tools } from '@aim-packages/subtitle';
1069
+
1070
+ const segments1 = [
1071
+ ["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
1072
+ ["00:00:03,000", "00:00:05,000", "How are you?", "speaker2"]
1073
+ ];
1074
+
1075
+ const vttContent = tools.output.outputVtt({
1076
+ segments1,
1077
+ speakerData
1078
+ });
1079
+
1080
+ console.log(vttContent);
1081
+ // 输出:
1082
+ // WEBVTT
1083
+ //
1084
+ // 00:00:01.000 --> 00:00:03.000
1085
+ // 张三: Hello world
1086
+ //
1087
+ // 00:00:03.000 --> 00:00:05.000
1088
+ // 李四: How are you?
1089
+ ```
1090
+
1091
+ ##### `outputLrc(params: OutputTextParams): string`
1092
+
1093
+ 生成LRC格式的字幕文件。
1094
+
1095
+ **参数:**
1096
+ - `params: OutputTextParams` - 输出参数配置
1097
+
1098
+ **返回值:**
1099
+ - `string` - LRC格式的字幕内容
1100
+
1101
+ **示例:**
1102
+ ```typescript
1103
+ import { tools } from '@aim-packages/subtitle';
1104
+
1105
+ const segments1 = [
1106
+ ["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
1107
+ ["00:00:03,000", "00:00:05,000", "How are you?", "speaker2"]
1108
+ ];
1109
+
1110
+ const lrcContent = tools.output.outputLrc({
1111
+ segments1,
1112
+ segments2,
1113
+ speakerData
1114
+ });
1115
+
1116
+ console.log(lrcContent);
1117
+ // 输出:
1118
+ // [01.000]张三: Hello world
1119
+ // [01.000]你好世界
1120
+ // [03.000]李四: How are you?
1121
+ // [03.000]你好吗?
1122
+ ```
1123
+
1124
+ ##### `outputAss(params: OutputTextParams): string`
1125
+
1126
+ 生成ASS格式的字幕文件,支持复杂的样式配置。
1127
+
1128
+ **参数:**
1129
+ - `params: OutputTextParams` - 输出参数配置
1130
+
1131
+ **返回值:**
1132
+ - `string` - ASS格式的字幕内容
1133
+
1134
+ **示例:**
1135
+ ```typescript
1136
+ import { tools } from '@aim-packages/subtitle';
1137
+
1138
+ const segments1 = [
1139
+ ["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
1140
+ ["00:00:03,000", "00:00:05,000", "How are you?", "speaker2"]
1141
+ ];
1142
+
1143
+ const segments2 = [
1144
+ ["00:00:01,000", "00:00:03,000", "你好世界", "speaker1"],
1145
+ ["00:00:03,000", "00:00:05,000", "你好吗?", "speaker2"]
1146
+ ];
1147
+
1148
+ const subtitleSettings = {
1149
+ disabled: false,
1150
+ stroke: true,
1151
+ shadow: true,
1152
+ background: true,
1153
+ backgroundColor: "#000000",
1154
+ main: {
1155
+ color: "#FFFFFF",
1156
+ borderColor: "#000000",
1157
+ size: 18,
1158
+ fontFamily: "Microsoft YaHei"
1159
+ },
1160
+ sub: {
1161
+ color: "#FFFF00",
1162
+ borderColor: "#000000",
1163
+ size: 14,
1164
+ fontFamily: "Microsoft YaHei"
1165
+ },
1166
+ position: {
1167
+ bottom: 20,
1168
+ left: 10
1169
+ },
1170
+ mode: "multiLang"
1171
+ };
1172
+
1173
+ const assContent = tools.output.outputAss({
1174
+ segments1,
1175
+ segments2,
1176
+ subtitleSettings,
1177
+ speakerData,
1178
+ isMac: false,
1179
+ reverse: false
1180
+ });
1181
+
1182
+ console.log(assContent);
1183
+ // 输出完整的ASS格式字幕文件,包含样式定义和字幕内容
1184
+ ```
1185
+
1186
+ ##### `outputTxt(params: OutputTextParams): string`
1187
+
1188
+ 生成TXT格式的纯文本文件,支持多种输出模式。
1189
+
1190
+ **参数:**
1191
+ - `params: OutputTextParams` - 输出参数配置
1192
+ - `useIndex?: boolean` - 是否显示索引编号
1193
+ - `useTimestamp?: boolean` - 是否显示时间戳
1194
+ - `useParagraph?: boolean` - 是否使用段落模式(按发言人分组或按chunkSize分组)
1195
+
1196
+ **返回值:**
1197
+ - `string` - TXT格式的文本内容
1198
+
1199
+ **功能特性:**
1200
+ - **段落模式**: 支持按说话人分组或按固定大小分块
1201
+ - **行模式**: 每个字幕片段单独一行
1202
+ - **多语言支持**: 自动检测中日韩语言,调整文本连接方式
1203
+ - **说话人支持**: 支持显示说话人名称和时间戳
1204
+
1205
+ **示例:**
1206
+ ```typescript
1207
+ import { tools } from '@aim-packages/subtitle';
1208
+
1209
+ const segments1 = [
1210
+ ["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
1211
+ ["00:00:03,000", "00:00:05,000", "How are you?", "speaker1"],
1212
+ ["00:00:05,000", "00:00:07,000", "I'm fine, thank you.", "speaker2"]
1213
+ ];
1214
+
1215
+ const segments2 = [
1216
+ ["00:00:01,000", "00:00:03,000", "你好世界", "speaker1"],
1217
+ ["00:00:03,000", "00:00:05,000", "你好吗?", "speaker1"],
1218
+ ["00:00:05,000", "00:00:07,000", "我很好,谢谢。", "speaker2"]
1219
+ ];
1220
+
1221
+ const speakerData = {
1222
+ settings: {
1223
+ speaker1: { spk: "speaker1", name: "张三", color: "#FF0000" },
1224
+ speaker2: { spk: "speaker2", name: "李四", color: "#00FF00" }
1225
+ },
1226
+ speakers: { speaker1: 1, speaker2: 1 },
1227
+ data: []
1228
+ };
1229
+
1230
+ // 行模式 - 每个字幕片段单独一行
1231
+ const txtContent1 = tools.output.outputTxt({
1232
+ segments1,
1233
+ segments2,
1234
+ speakerData,
1235
+ useIndex: true,
1236
+ useTimestamp: true,
1237
+ useParagraph: false
1238
+ });
1239
+
1240
+ console.log(txtContent1);
1241
+ // 输出:
1242
+ // 0
1243
+ // 00:00:01,000 --> 00:00:03,000
1244
+ // 张三: Hello world
1245
+ // 你好世界
1246
+ //
1247
+ // 1
1248
+ // 00:00:03,000 --> 00:00:05,000
1249
+ // 张三: How are you?
1250
+ // 你好吗?
1251
+ //
1252
+ // 2
1253
+ // 00:00:05,000 --> 00:00:07,000
1254
+ // 李四: I'm fine, thank you.
1255
+ // 我很好,谢谢。
1256
+
1257
+ // 段落模式 - 按说话人分组
1258
+ const txtContent2 = tools.output.outputTxt({
1259
+ segments1,
1260
+ segments2,
1261
+ speakerData,
1262
+ useIndex: true,
1263
+ useTimestamp: true,
1264
+ useParagraph: true
1265
+ });
1266
+
1267
+ console.log(txtContent2);
1268
+ // 输出:
1269
+ // 1
1270
+ // 张三 - 00:00:01,000 --> 00:00:05,000
1271
+ // Hello world How are you?
1272
+ // 你好世界 你好吗?
1273
+ //
1274
+ // 2
1275
+ // 李四 - 00:00:05,000 --> 00:00:07,000
1276
+ // I'm fine, thank you.
1277
+ // 我很好,谢谢。
1278
+
1279
+ ##### `outputMarkdown(params: OutputTextParams): string`
1280
+
1281
+ 生成 Markdown 格式的字幕文件,支持多种输出模式和说话人分组。
1282
+
1283
+ **参数:**
1284
+ - `params: OutputTextParams` - 输出参数配置
1285
+ - `header?: string` - 文档标题
1286
+ - `isMd?: boolean` - 是否为 Markdown 格式(影响换行符)
1287
+ - `chunkSize?: number` - 分块大小,默认为 10
1288
+
1289
+ **返回值:**
1290
+ - `string` - Markdown 格式的字幕内容
1291
+
1292
+ **功能特性:**
1293
+ - **说话人分组**: 支持按说话人分组显示内容
1294
+ - **多语言支持**: 自动检测中日韩语言,调整文本连接方式
1295
+ - **分块处理**: 支持按固定大小分块处理长文本
1296
+ - **时间显示**: 自动清理时间格式,移除毫秒和多余前缀
1297
+ - **灵活格式**: 支持 Markdown 和纯文本两种输出格式
1298
+
1299
+ **示例:**
1300
+ ```typescript
1301
+ import { tools } from '@aim-packages/subtitle';
1302
+
1303
+ const segments1 = [
1304
+ ["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
1305
+ ["00:00:03,000", "00:00:05,000", "How are you?", "speaker1"],
1306
+ ["00:00:05,000", "00:00:07,000", "I'm fine, thank you.", "speaker2"]
1307
+ ];
1308
+
1309
+ const segments2 = [
1310
+ ["00:00:01,000", "00:00:03,000", "你好世界", "speaker1"],
1311
+ ["00:00:03,000", "00:00:05,000", "你好吗?", "speaker1"],
1312
+ ["00:00:05,000", "00:00:07,000", "我很好,谢谢。", "speaker2"]
1313
+ ];
1314
+
1315
+ const speakerData = {
1316
+ settings: {
1317
+ speaker1: { spk: "speaker1", name: "张三", color: "#FF0000" },
1318
+ speaker2: { spk: "speaker2", name: "李四", color: "#00FF00" }
1319
+ },
1320
+ speakers: { speaker1: 1, speaker2: 1 },
1321
+ data: []
1322
+ };
1323
+
1324
+ // 带说话人的 Markdown 输出
1325
+ const mdContent = tools.output.outputMarkdown({
1326
+ segments1,
1327
+ segments2,
1328
+ header: "# 会议记录",
1329
+ speakerData,
1330
+ chunkSize: 5
1331
+ });
1332
+
1333
+ console.log(mdContent);
1334
+ // 输出:
1335
+ // # 会议记录
1336
+ //
1337
+ // 张三 01:30 - 01:45
1338
+ // Hello world How are you?
1339
+ // 你好世界 你好吗?
1340
+ //
1341
+ // 李四 01:45 - 01:47
1342
+ // I'm fine, thank you.
1343
+ // 我很好,谢谢。
1344
+
1345
+ // 纯文本输出(无说话人)
1346
+ const textContent = tools.output.outputMarkdown({
1347
+ segments1,
1348
+ segments2,
1349
+ header: "会议记录",
1350
+ isMd: false,
1351
+ chunkSize: 3
1352
+ });
1353
+
1354
+ console.log(textContent);
1355
+ // 输出:
1356
+ // 会议记录
1357
+ // 1. Hello world
1358
+ // 你好世界
1359
+ // 2. How are you?
1360
+ // 你好吗?
1361
+ // 3. I'm fine, thank you.
1362
+ // 我很好,谢谢。
1363
+ ```
1364
+
1365
+ #### 完整的字幕处理流程示例
1366
+
1367
+ ```typescript
1368
+ import { tools } from '@aim-packages/subtitle';
1369
+
1370
+ // 1. 检测语言
1371
+ const language = tools.detection.detectLanguage("Hello world. How are you?");
1372
+
1373
+ // 2. 按句子分割
1374
+ const sentences = tools.segmentation.splitToSentences("Hello world. How are you?", language.language);
1375
+
1376
+ // 3. 转换为字幕片段
1377
+ const segments = sentences.map((text, index) => ({
1378
+ st: `00:00:${index * 2}`,
1379
+ et: `00:00:${(index + 1) * 2}`,
1380
+ text
1381
+ }));
1382
+
1383
+ // 4. 优化字幕
1384
+ const optimized = tools.optimization.subtitleOptimization(segments, {
1385
+ repeat: true,
1386
+ emt: true,
1387
+ zf: true,
1388
+ space: true,
1389
+ ep: true
1390
+ });
1391
+
1392
+ console.log('优化结果:', optimized);
1393
+ ```
1394
+
1395
+ #### 完整的字幕处理流程示例
1396
+
1397
+ ```typescript
1398
+ import { utils, parser, filter, tools } from '@aim-packages/subtitle';
1399
+
1400
+ // 1. 解析字幕文件
1401
+ const srtContent = `1
1402
+ 00:00:01,000 --> 00:00:03,000
1403
+ Hello world.
1404
+
1405
+ 2
1406
+ 00:00:03,000 --> 00:00:05,000
1407
+ How are you?`;
1408
+
1409
+ const segments = await parser.srtToAimSegments(srtContent);
1410
+
1411
+ // 2. 检测语言
1412
+ const language = tools.detection.detectLanguage(segments[0].text);
1413
+
1414
+ // 3. 按句子分割并重新生成字幕片段
1415
+ const sentences = tools.segmentation.splitToSentences(segments[0].text, language.language);
1416
+ const newSegments = sentences.map((text, index) => ({
1417
+ st: utils.formatTime(index * 2),
1418
+ et: utils.formatTime((index + 1) * 2),
1419
+ text
1420
+ }));
1421
+
1422
+ // 4. 优化字幕质量
1423
+ const optimized = tools.optimization.subtitleOptimization(newSegments, {
1424
+ repeat: true,
1425
+ emt: true,
1426
+ zf: true,
1427
+ space: true,
1428
+ ep: true
1429
+ });
1430
+
1431
+ // 5. 文本过滤
1432
+ const streamFilter = new filter.StreamFilter();
1433
+ streamFilter.parse([
1434
+ ["敏感词", "***"],
1435
+ ["不当词汇", "替换文本"]
1436
+ ]);
1437
+
1438
+ const filteredSegments = optimized.segments.map(segment => ({
1439
+ ...segment,
1440
+ text: streamFilter.feedAll(segment.text)
1441
+ }));
1442
+
1443
+ console.log('最终处理结果:', filteredSegments);
1444
+ ```
1445
+
1446
+ #### Tools 模块相关接口
1447
+
1448
+ ##### `LanguageDetectionResultsEntry`
1449
+ ```typescript
1450
+ interface LanguageDetectionResultsEntry {
1451
+ /** 语言代码 (如 'zh', 'en', 'ja' 等) */
1452
+ language: LanguageCode
1453
+ /** 语言名称 (如 '中文', 'English', '日本語' 等) */
1454
+ languageName: string
1455
+ /** 检测准确度概率 (0-1 之间的数值) */
1456
+ probability: number
1457
+ }
1458
+ ```
1459
+
1460
+ ##### `RepeatCheckOption`
1461
+ ```typescript
1462
+ interface RepeatCheckOption {
1463
+ /** 当检测到重复内容时的回调函数 */
1464
+ onHit?: (segment: AimSegments[]) => void
1465
+ }
1466
+ ```
1467
+
1468
+ ##### `OutputTextParams`
1469
+ ```typescript
1470
+ interface OutputTextParams {
1471
+ /** 主要字幕片段数组 */
1472
+ segments1: Array<ISegment>;
1473
+ /** 次要字幕片段数组(如翻译字幕) */
1474
+ segments2?: Array<ISegment>;
1475
+ /** 字幕样式设置 */
1476
+ subtitleSettings?: SubtitleSettings;
1477
+ /** 说话人数据 */
1478
+ speakerData?: SpeakerData | null;
1479
+ /** 本地化配置 */
1480
+ locale?: Record<string, any>;
1481
+
1482
+ // TXT格式相关
1483
+ /** 是否使用索引 */
1484
+ useIndex?: boolean;
1485
+ /** 是否使用时间戳 */
1486
+ useTimestamp?: boolean;
1487
+ /** 是否使用段落格式 */
1488
+ useParagraph?: boolean;
1489
+
1490
+ // Markdown格式相关
1491
+ /** 文档标题 */
1492
+ header?: string;
1493
+ /** 是否为Markdown格式 */
1494
+ isMd?: boolean;
1495
+ /** 分块大小 */
1496
+ chunkSize?: number;
1497
+
1498
+ // ASS格式相关
1499
+ /** 是否为Mac系统 */
1500
+ isMac?: boolean;
1501
+ /** 是否反转字幕顺序 */
1502
+ reverse?: boolean;
1503
+ }
1504
+ ```
1505
+
1506
+ ##### `ISegment`
1507
+ ```typescript
1508
+ type ISegment = [string, string, string, string | undefined]
1509
+ // [开始时间, 结束时间, 文本内容, 说话人标识]
1510
+ ```
1511
+
1512
+ ##### `SpeakerData`
1513
+ ```typescript
1514
+ interface SpeakerData {
1515
+ /** 说话人设置配置 */
1516
+ settings: Record<string, { spk: string; name?: string; color: string }>;
1517
+ /** 说话人统计 */
1518
+ speakers: Record<string, number>;
1519
+ /** 说话人时间数据 */
1520
+ data: { start: number; end: number; speaker: string }[];
1521
+ /** 其他选项 */
1522
+ options?: { speakerCount?: number; }
1523
+ }
1524
+ ```
1525
+
1526
+ ##### `SubtitleSettings`
1527
+ ```typescript
1528
+ interface SubtitleSettings {
1529
+ /** 是否禁用字幕 */
1530
+ disabled: boolean;
1531
+ /** 是否启用描边 */
1532
+ stroke: boolean;
1533
+ /** 是否启用阴影 */
1534
+ shadow: boolean;
1535
+ /** 是否启用背景 */
1536
+ background: boolean;
1537
+ /** 背景颜色 */
1538
+ backgroundColor: string;
1539
+ /** 主要字幕样式 */
1540
+ main: SubtitleTextStyle;
1541
+ /** 次要字幕样式 */
1542
+ sub: SubtitleTextStyle;
1543
+ /** 字幕位置 */
1544
+ position: SubtitlePosition;
1545
+ /** 字幕语言模式 */
1546
+ mode: SubtitleLanguage;
1547
+ /** 字幕顺序 */
1548
+ order?: number[];
1549
+ /** 水印样式 */
1550
+ watermark?: WatermarkStyle;
1551
+ }
1552
+ ```
1553
+
1554
+ ## 🏗️ 项目结构
1555
+
1556
+ ```
1557
+ src/
1558
+ ├── utils/ # 工具函数 - 时间格式化、语言处理、文本分块等
1559
+ ├── parser/ # 字幕格式解析器 - SRT、VTT、ASS 格式转换和流式解析
1560
+ ├── tools/ # 字幕处理工具 - 语言检测、文本分割、字幕优化等
1561
+ ├── filter/ # 字幕过滤器 - 基于 DFA 算法的流式文本过滤
1562
+ ├── types/ # TypeScript 类型定义
1563
+ └── main.ts # 主入口文件
1564
+ ```
1565
+
1566
+ ## 📋 模块功能概览
1567
+
1568
+ ### Utils 模块
1569
+ - **时间处理**: 时间格式化、秒数转换、数字补零、ASS 时间格式转换等
1570
+ - **语言处理**: 中日韩字符检测、语言代码转换等
1571
+ - **文本分块**: 按字符限制分块、字幕片段分块等
1572
+ - **字幕合并**: 合并相邻字幕片段、时间轴优化等
1573
+ - **颜色转换**: 十六进制颜色转 ASS 格式、FFmpeg 格式等
1574
+
1575
+ ### Parser 模块
1576
+ - **格式转换**: SRT、VTT、ASS 格式之间的相互转换
1577
+ - **第三方服务**: 腾讯云听悟、OpenAI Whisper 结果转换
1578
+ - **流式解析**: 支持实时流式字幕数据的解析和处理
1579
+
1580
+ ### Tools 模块
1581
+ - **语言检测**: 多语言文本的语言识别和概率分析
1582
+ - **文本分割**: 按句子、段落等规则分割文本
1583
+ - **字幕优化**: 重复检测、空白处理、质量优化等
1584
+ - **字幕输出**: 支持SRT、VTT、LRC、ASS等多种格式的字幕文件生成
1585
+
1586
+ ### Filter 模块
1587
+ - **流式过滤**: 基于 DFA 算法的实时文本过滤
1588
+ - **敏感词处理**: 支持关键词替换、删除等操作
1589
+ - **逐字符处理**: 支持逐字符输入和实时过滤
1590
+
1591
+ ## 🔧 开发
1592
+
1593
+ ```bash
1594
+ # 安装依赖
1595
+ pnpm install
1596
+
1597
+ # 开发模式
1598
+ pnpm dev
1599
+
1600
+ # 构建
1601
+ pnpm build
1602
+
1603
+ # 预览构建结果
1604
+ pnpm preview
1605
+
1606
+ # 发布
1607
+ pnpm release
1608
+ ```
1609
+
1610
+ ## 📄 许可证
1611
+
1612
+ MIT License
1613
+
1614
+ ## 🤝 贡献
1615
+
1616
1616
  欢迎提交 Issue 和 Pull Request!