@aim-packages/subtitle
v0.5.1
Published
字幕处理工具库,支持多种字幕格式的解析、转换、优化和处理。
Readme
Aim Subtitle
字幕处理工具库,支持多种字幕格式的解析、转换、优化和处理。
📦 安装
pnpm add @aim-packages/subtitle🚀 快速开始
import { utils, parser, filter, tools } from '@aim-packages/subtitle';
// 时间格式化
const formattedTime = utils.formatTime(3661.5); // "01:01:01.500"
// 字幕格式转换
const segments = await parser.srtToAimSegments(srtContent);
// 语言检测
const language = tools.detection.detectLanguage("Hello world");
// 文本分割
const sentences = tools.segmentation.splitToSentences("Hello world. How are you?");
// 字幕优化
const optimizedSegments = tools.optimization.subtitleOptimization(segments, {
repeat: true,
emt: true,
zf: true,
space: true,
ep: true
});
// 文本过滤
const streamFilter = new filter.StreamFilter();
streamFilter.add("敏感词", "***");
const filteredText = streamFilter.feedAll("这是一个敏感词测试。");
// 字幕输出
const segments = [["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"]];
const srtContent = tools.output.outputSrt({ segments1: segments });
## 📚 API 文档
### 类型定义
#### `AimSegments` 接口
这是字幕处理库的核心数据结构,用于表示单个字幕片段。
```typescript
interface AimSegments {
// 通用字段
st: string; // 开始时间戳 (Start timestamp)
et: string; // 结束时间戳 (End timestamp)
text: string; // 文本内容 (Text content)
index?: number; // 字幕索引 (Subtitle index)
children?: Array<AimSegments>; // 子片段 (Child segments)
f?: number; // 批量文件转写时所属的文件索引 (File index)
// 视频剪辑字段
delete?: boolean; // 是否被删除 (Whether it is deleted)
cut?: boolean; // 是否被裁剪 (Whether it is cut)
// TTS字段
md5?: string; // TTS使用的MD5 (MD5 used by TTS)
// 翻译字段
tr?: 0 | 1 | 2; // 翻译状态: 0-未翻译原文, 1-翻译中, 2-翻译完成
// 说话人字段
spk?: string; // 说话人 (Speaker)
// 烧录字段
duration?: number; // 时长 (Duration)
range?: number; // 本句开始到下一个片段的可用时长
// 重复检测字段
hit?: number; // 重复次数,表示转写重复的片段
rs?: Array<AimSegments>; // 重复片段 (Repeated segments)
// 分句字段
em?: 0 | 1; // 是否结束句 (End mark)
punc?: 0 | 1; // 是否存在标点分割符号
lng?: string; // 语言 (Language)
// 优化字段
zf?: 0 | 1; // 零帧字幕 (Zero frame subtitle)
emt?: 0 | 1; // 空白字幕 (Empty text subtitle)
ep?: 0 | 1; // 结尾标点 (End punctuation)
space?: 0 | 1; // 存在多余空格
}Segment 类型
用于表示简单的时间段。
type Segment = {
start: number; // 开始时间(秒)
end: number; // 结束时间(秒)
};OptimizationOptions 接口
字幕优化选项配置。
interface OptimizationOptions {
emt: boolean; // 是否移除空白字幕
ep: boolean; // 是否移除结尾标点
zf: boolean; // 是否移除零帧字幕
punc: boolean; // 是否移除标点分割符号
em: boolean; // 是否移除结束标记
space: boolean; // 是否移除多余空格
repeat: boolean; // 是否移除重复内容
}LanguageCode 类型
支持的语言代码类型。
type LanguageCode =
| "auto" | "none" | "zh" | "zh_cn" | "zh_tw" | "yue"
| "en" | "ja" | "ko" | "fr" | "es" | "ru" | "de" | "it"
| "tr" | "pt" | "vi" | "id" | "th" | "ms" | "ar" | "hi"
| "ro" | "ug" | "uz" | "kk" | "az" | "ky" | "fa" | "tg";工具类型
// 使指定键变为可选
type PartialByKey<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
// 使指定键变为必需
type RequiredByKey<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;Utils 工具函数
时间处理
padNumber(num: number, length?: number): string
为数字前方自动补0到特定的位数,默认两位。
import { utils } from '@aim-packages/subtitle';
utils.padNumber(5); // "05"
utils.padNumber(5, 3); // "005"
utils.padNumber(123, 2); // "123"formatTime(seconds: number): string
将秒数转换为 HH:MM:SS.mmm 格式。
import { utils } from '@aim-packages/subtitle';
utils.formatTime(3661.5); // "01:01:01.500"
utils.formatTime(125.75); // "00:02:05.750"
utils.formatTime(0.123); // "00:00:00.123"convertToSeconds(time?: string): number
将 HH:MM:SS,mmm 或 HH:MM:SS.mmm 格式的时间转换为秒数。
import { utils } from '@aim-packages/subtitle';
utils.convertToSeconds("01:01:01,500"); // 3661.5
utils.convertToSeconds("00:02:05.750"); // 125.75
utils.convertToSeconds("00:00:00,123"); // 0.123
utils.convertToSeconds(); // 0语言处理
containsCJKCharacters(str: string): boolean
检测字符串是否包含中文、日文、韩文字符。
import { utils } from '@aim-packages/subtitle';
utils.containsCJKCharacters("Hello 世界"); // true
utils.containsCJKCharacters("こんにちは"); // true
utils.containsCJKCharacters("안녕하세요"); // true
utils.containsCJKCharacters("Hello World"); // falselanguageCodeToName(languageCode: LanguageCode): string
将语言代码转换为可读的语言名称。
import { utils } from '@aim-packages/subtitle';
utils.languageCodeToName("zh"); // "Chinese"
utils.languageCodeToName("en"); // "English"
utils.languageCodeToName("ja"); // "Japanese"
utils.languageCodeToName("ko"); // "Korean"
utils.languageCodeToName("auto"); // "Unknown"文本分块处理
chunkArrayStrings(strings: string[], characterLimit: number): string[]
将字符串数组按字符限制分块,避免单次处理内容过多。
import { utils } from '@aim-packages/subtitle';
const strings = ["第一句话", "第二句话", "第三句话", "第四句话"];
const chunks = utils.chunkArrayStrings(strings, 20);
// 结果: ["第一句话\n第二句话", "第三句话\n第四句话"]参数说明:
strings: 输入的字符串数组characterLimit: 每个分块的最大字符数限制
作用:
- 将字符串数组按照字符限制进行分块,避免单次处理内容过多
- 使用换行符连接同一分块内的字符串
- 在分块时会预留一定的字符空间(10个字符)用于换行符和格式调整
chunkSegmentStringsWithIndex(segments: AimSegments[], characterLimit: number)
将字幕片段按字符限制分块,并生成多种格式的结果。
import { utils } from '@aim-packages/subtitle';
const segments = [
{ st: "00:00:01", et: "00:00:03", text: "第一句话" },
{ st: "00:00:03", et: "00:00:05", text: "第二句话" },
{ st: "00:00:05", et: "00:00:07", text: "第三句话" }
];
const result = utils.chunkSegmentStringsWithIndex(segments, 50);
// 返回包含以下属性的对象:
// - segmentsResult: 分块后的字幕片段数组
// - stringResult: 纯文本分块结果
// - indexStringResult: 带索引的文本分块结果
// - indexResult: 每个分块的起始索引数组参数说明:
segments: 输入的字幕片段数组characterLimit: 每个分块的最大字符数限制
返回值:
{
segmentsResult: AimSegments[][], // 分块后的字幕片段数组
stringResult: string[], // 纯文本分块结果
indexStringResult: string[], // 带索引的文本分块结果,格式为 [索引]文本
indexResult: number[] // 每个分块的起始索引数组
}作用:
- 将字幕片段数组按照字符限制分块,避免单次处理内容过多
- 为每个片段添加索引信息,便于后续处理和追踪
- 生成多种格式的分块结果,满足不同场景的需求
字幕片段合并
consolidateSegments(items: Segment[], option: { maxDistance: number, padding: number }): Segment[]
合并字幕片段,优化字幕的时间轴。
import { utils } from '@aim-packages/subtitle';
const segments = [
{ start: 0, end: 2 },
{ start: 2.5, end: 4 },
{ start: 8, end: 10 }
];
const merged = utils.consolidateSegments(segments, {
maxDistance: 1, // 时间间隔小于1秒的片段会被合并
padding: 0.5 // 为每个片段扩展0.5秒的开始和结束时间
});
// 结果: [{ start: 0, end: 4.5 }, { start: 7.5, end: 10.5 }]参数说明:
items: 输入的字幕片段数组,每个片段包含start和end时间option.maxDistance: 最大时间间隔,小于此值的相邻片段会被合并option.padding: 可选的时间填充,为每个片段扩展开始和结束时间
作用:
- 将时间间隔小于
maxDistance的相邻字幕片段合并为一个片段 - 可选择性地为每个片段添加
padding时间,扩展片段的开始和结束时间 - 如果添加了
padding,会自动处理重叠的片段,将它们合并
颜色格式转换
convertHexColorToAssFormat(hexColor?: string): string
将十六进制颜色转换为 ASS 格式,用于字幕样式设置。
import { utils } from '@aim-packages/subtitle';
utils.convertHexColorToAssFormat("#FF0000"); // "&H0000FF00"
utils.convertHexColorToAssFormat("#00FF00"); // "&H0000FF00"
utils.convertHexColorToAssFormat("#0000FF"); // "&H00FF0000"
utils.convertHexColorToAssFormat("#FF0000FF"); // "&HFF00FF00"
utils.convertHexColorToAssFormat(); // ""参数说明:
hexColor?: string- 十六进制颜色值,支持 7 位(#RRGGBB)和 9 位(#RRGGBBAA)格式
作用:
- 将十六进制颜色转换为 ASS 格式,用于字幕样式设置
- 支持 7 位和 9 位十六进制颜色格式
- 自动处理颜色通道的顺序转换(RGB 转 BGR)
convertHexColorToFFmpegFormat(hexColor: string): string
将十六进制颜色转换为 FFmpeg 格式,用于字幕样式设置。
import { utils } from '@aim-packages/subtitle';
utils.convertHexColorToFFmpegFormat("#FF0000"); // "&H0000FF00&"
utils.convertHexColorToFFmpegFormat("#00FF00"); // "&H0000FF00&"
utils.convertHexColorToFFmpegFormat("#0000FF"); // "&H00FF0000&"
utils.convertHexColorToFFmpegFormat("#FF0000FF"); // "&HFF00FF00&"参数说明:
hexColor: string- 十六进制颜色值,支持 7 位(#RRGGBB)和 9 位(#RRGGBBAA)格式
作用:
- 将十六进制颜色转换为 FFmpeg 格式,用于字幕样式设置
- 支持 7 位和 9 位十六进制颜色格式
- 自动处理颜色通道的顺序转换(RGB 转 BGR)
时间格式转换
convertTimeToAssFormat(str: string): string
将时间字符串转换为 ASS 字幕格式。
import { utils } from '@aim-packages/subtitle';
utils.convertTimeToAssFormat("01:30:45.123"); // "1:30:45.12"
utils.convertTimeToAssFormat("00:05:30.050"); // "0:05:30.05"
utils.convertTimeToAssFormat("00:00:00.999"); // "0:00:00.99"参数说明:
str: string- 输入的时间字符串,格式为 HH:MM:SS.MMM
作用:
- 将标准时间格式字符串转换为 ASS 字幕文件所需的时间格式
- 支持 HH:MM:SS.MMM 格式的输入
- 确保时间轴的准确性和兼容性
- 自动处理毫秒的精度转换(3位转2位)
cleanTimeDisplay(timeString: string): string
清理时间显示格式,移除毫秒部分和多余的小时前缀。
import { utils } from '@aim-packages/subtitle';
utils.cleanTimeDisplay("00:01:30.500"); // "01:30"
utils.cleanTimeDisplay("01:45:20,123"); // "01:45:20"
utils.cleanTimeDisplay("00:00:05.00"); // "00:05"
utils.cleanTimeDisplay("01:30:45,50"); // "01:30:45"参数说明:
timeString: string- 输入的时间字符串
作用:
- 移除时间字符串末尾的毫秒部分(支持 .xxx、,xxx 等分隔符)
- 移除时间字符串开头多余的 "00:" 小时前缀
- 保持时间格式的简洁性和可读性
- 自动处理不同格式的时间字符串,统一输出格式
Parser 字幕格式解析器
Parser 模块提供了多种字幕格式的解析和转换功能,支持 SRT、VTT、ASS 等常见字幕格式,以及流式解析器用于实时处理。
基础格式转换
srtToAimSegments(text: string): Promise<AimSegments[]>
将 SRT 格式的字幕文本转换为 AimSegments 数组。
参数:
text: string- SRT 格式的字幕文本内容
返回值:
Promise<AimSegments[]>- 转换后的字幕片段数组
示例:
import { parser } from '@aim-packages/subtitle';
const srtContent = `1
00:00:01,000 --> 00:00:03,000
Hello world.
2
00:00:03,000 --> 00:00:05,000
How are you?`;
const segments = await parser.srtToAimSegments(srtContent);
// 返回: [
// { st: "00:00:01.000", et: "00:00:03.000", text: "Hello world." },
// { st: "00:00:03.000", et: "00:00:05.000", text: "How are you?" }
// ]vttToAimSegments(text: string): Promise<AimSegments[]>
将 VTT 格式的字幕文本转换为 AimSegments 数组。
参数:
text: string- VTT 格式的字幕文本内容
返回值:
Promise<AimSegments[]>- 转换后的字幕片段数组
示例:
import { parser } from '@aim-packages/subtitle';
const vttContent = `WEBVTT
00:00:01.000 --> 00:00:03.000
Hello world.
00:00:03.000 --> 00:00:05.000
How are you?`;
const segments = await parser.vttToAimSegments(vttContent);
// 返回: [
// { st: "00:00:01.000", et: "00:00:03.000", text: "Hello world." },
// { st: "00:00:03.000", et: "00:00:05.000", text: "How are you?" }
// ]assToAimSegments(text: string): Promise<AimSegments[]>
将 ASS 格式的字幕文本转换为 AimSegments 数组(通过先转换为 VTT 格式)。
参数:
text: string- ASS 格式的字幕文本内容
返回值:
Promise<AimSegments[]>- 转换后的字幕片段数组
示例:
import { parser } from '@aim-packages/subtitle';
const assContent = `[Script Info]
Title: Example
ScriptType: v4.00+
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:01.00,0:00:03.00,Default,,0,0,0,,Hello world.
Dialogue: 0,0:00:03.00,0:00:05.00,Default,,0,0,0,,How are you?`;
const segments = await parser.assToAimSegments(assContent);第三方服务结果转换
tingwuToAimSegments(json: TingwuResult): Promise<AimSegments[]>
将腾讯云听悟服务的识别结果转换为 AimSegments 数组。
参数:
json: TingwuResult- 腾讯云听悟服务的识别结果
返回值:
Promise<AimSegments[]>- 转换后的字幕片段数组
示例:
import { parser } from '@aim-packages/subtitle';
const tingwuResult = {
// 腾讯云听悟服务的识别结果
};
const segments = await parser.tingwuToAimSegments(tingwuResult);openaiToAimSegments(json: OpenAIResult): Promise<AimSegments[]>
将 OpenAI Whisper 服务的识别结果转换为 AimSegments 数组。
参数:
json: OpenAIResult- OpenAI Whisper 服务的识别结果
返回值:
Promise<AimSegments[]>- 转换后的字幕片段数组
示例:
import { parser } from '@aim-packages/subtitle';
const openaiResult = {
// OpenAI Whisper 服务的识别结果
};
const segments = await parser.openaiToAimSegments(openaiResult);流式解析器
createWhisperStreamParser(options?: ParserOptions)
创建用于处理 Whisper 流式输出的解析器。
参数:
options?: ParserOptions- 解析器配置选项
返回值:
Parser<string>- 流式解析器实例
示例:
import { parser } from '@aim-packages/subtitle';
const whisperParser = parser.createWhisperStreamParser({
onStart: (event) => {
console.log('开始解析 Whisper 流');
},
onParse: (event) => {
console.log('解析到字幕片段:', event.data);
},
onEnd: (event) => {
console.log('解析完成');
}
});
// 使用解析器
whisperParser.feed(chunk);
whisperParser.end();createTranslateStreamParser(options?: ParserOptions)
创建用于处理翻译流式输出的解析器。
参数:
options?: ParserOptions- 解析器配置选项
返回值:
Parser<string>- 流式解析器实例
示例:
import { parser } from '@aim-packages/subtitle';
const translateParser = parser.createTranslateStreamParser({
onParse: (event) => {
console.log('解析到翻译片段:', event.data);
}
});
// 使用解析器
translateParser.feed(chunk);
translateParser.end();createSegmentStreamParser(options?: ParserOptions)
创建用于处理通用字幕片段流式输出的解析器。
参数:
options?: ParserOptions- 解析器配置选项
返回值:
Parser<string>- 流式解析器实例
示例:
import { parser } from '@aim-packages/subtitle';
const segmentParser = parser.createSegmentStreamParser({
onParse: (event) => {
console.log('解析到字幕片段:', event.data);
}
});
// 使用解析器
segmentParser.feed(chunk);
segmentParser.end();Parser 模块相关接口
Parser<T> 接口
interface Parser<T = string> {
/** 向解析器输入数据块 */
feed(chunk: T): void
/** 重置解析器状态 */
reset(): void
/** 结束解析 */
end(): void
}ParserOptions<S, P, E> 类型
type ParserOptions<S, P, E> = {
/** 解析开始时的回调函数 */
onStart?: ParseCallback<S>
/** 解析过程中调用的回调函数 */
onParse?: ParseCallback<P>
/** 解析进度更新时的回调函数 */
onProgress?: ParseCallback<P>
/** 解析结束时的回调函数 */
onEnd?: ParseCallback<E>
}ParsedEvent<T> 接口
interface ParsedEvent<T> {
type: "event"
event: "start" | "message" | "end"
data?: T
}ParseCallback<T> 类型
type ParseCallback<T> = (event: ParsedEvent<T>) => voidFilter 流式文本过滤器
Filter 模块提供了基于 DFA(确定有限自动机)算法的流式文本过滤功能,支持实时敏感词检测和替换。
StreamFilter 类
流式文本过滤器,支持逐字符输入和实时过滤。
构造函数:
constructor(onFilter?: (text: string) => any)参数:
onFilter?: (text: string) => any- 可选的回调函数,在每次过滤时调用
方法:
add(keyword: string, replaceText?: string)
添加关键词和对应的替换文本。
参数:
keyword: string- 需要过滤的关键词replaceText?: string- 替换文本,默认为空字符串
示例:
import { filter } from '@aim-packages/subtitle';
const streamFilter = new filter.StreamFilter();
streamFilter.add("敏感词", "***");
streamFilter.add("另一个词", "替换文本");parse(data: [string, string][])
批量添加关键词和替换文本。
参数:
data: [string, string][]- 关键词和替换文本的数组
示例:
import { filter } from '@aim-packages/subtitle';
const streamFilter = new filter.StreamFilter();
streamFilter.parse([
["敏感词1", "***"],
["敏感词2", "替换文本"],
["敏感词3", ""] // 空字符串表示删除
]);reParse(data: string[][])
重新解析关键词列表,会清空之前的所有关键词。
参数:
data: string[][]- 关键词和替换文本的二维数组
示例:
import { filter } from '@aim-packages/subtitle';
const streamFilter = new filter.StreamFilter();
streamFilter.reParse([
["敏感词1", "***"],
["敏感词2", "替换文本"]
]);feed(c: string)
逐字符输入文本进行过滤。
参数:
c: string- 单个字符
示例:
import { filter } from '@aim-packages/subtitle';
const streamFilter = new filter.StreamFilter((text) => {
console.log('过滤后的字符:', text);
});
streamFilter.add("敏感词", "***");
// 逐字符输入
streamFilter.feed("这");
streamFilter.feed("是");
streamFilter.feed("敏");
streamFilter.feed("感");
streamFilter.feed("词");
streamFilter.feed("。");
const result = streamFilter.end();
console.log('最终结果:', result); // "这是***。"feedAll(text: string): string
一次性输入完整文本进行过滤。
参数:
text: string- 完整的文本内容
返回值:
string- 过滤后的文本
示例:
import { filter } from '@aim-packages/subtitle';
const streamFilter = new filter.StreamFilter();
streamFilter.add("敏感词", "***");
const result = streamFilter.feedAll("这是一个敏感词测试。");
console.log(result); // "这是一个***测试。"end(): string
结束过滤并返回结果。
返回值:
string- 过滤后的完整文本
示例:
import { filter } from '@aim-packages/subtitle';
const streamFilter = new filter.StreamFilter();
streamFilter.add("敏感词", "***");
// 逐字符输入
streamFilter.feed("这");
streamFilter.feed("是");
streamFilter.feed("敏");
streamFilter.feed("感");
streamFilter.feed("词");
const result = streamFilter.end();
console.log(result); // "这是***"完整使用示例
import { filter } from '@aim-packages/subtitle';
// 创建流式过滤器
const streamFilter = new filter.StreamFilter((char) => {
console.log('实时输出字符:', char);
});
// 添加过滤规则
streamFilter.parse([
["敏感词1", "***"],
["敏感词2", "替换文本"],
["要删除的词", ""]
]);
// 方式1: 逐字符输入
streamFilter.feed("这");
streamFilter.feed("是");
streamFilter.feed("敏");
streamFilter.feed("感");
streamFilter.feed("词");
streamFilter.feed("1");
streamFilter.feed("。");
const result1 = streamFilter.end();
console.log('逐字符结果:', result1);
// 方式2: 一次性输入
const result2 = streamFilter.feedAll("这是敏感词2测试。");
console.log('一次性结果:', result2);Tools 字幕处理工具
Tools 模块提供了字幕处理相关的各种工具方法,包括语言检测、文本分割和字幕优化等功能。
语言检测
detectLanguage(text: string): LanguageDetectionResultsEntry
检测文本的主要语言。
参数:
text: string- 需要检测语言的文本内容
返回值:
LanguageDetectionResultsEntry- 包含语言代码、语言名称和概率的对象
示例:
import { tools } from '@aim-packages/subtitle';
const result = tools.detection.detectLanguage("Hello world");
// 返回: { language: 'en', languageName: 'English', probability: 1 }detectAllLanguage(text: string): LanguageDetectionResultsEntry[]
检测文本中的所有可能语言,返回按概率排序的语言列表。
参数:
text: string- 需要检测语言的文本内容
返回值:
LanguageDetectionResultsEntry[]- 检测到的所有语言及其概率,按概率从高到低排序
示例:
import { tools } from '@aim-packages/subtitle';
const results = tools.detection.detectAllLanguage("Hello world 你好世界");
// 返回: [
// { language: 'en', languageName: 'English', probability: 0.8 },
// { language: 'zh', languageName: '中文', probability: 0.2 }
// ]文本分割
splitToSentences(text: string, languageCode?: LanguageCode): string[]
将文本按句子进行分割。
参数:
text: string- 需要分割的文本内容languageCode?: LanguageCode- 可选的语言代码,用于更准确的分割
返回值:
string[]- 分割后的句子数组
示例:
import { tools } from '@aim-packages/subtitle';
// 使用默认分割器
const sentences1 = tools.segmentation.splitToSentences("Hello world. How are you?");
// 返回: ["Hello world.", "How are you?"]
// 使用指定语言的分割器
const sentences2 = tools.segmentation.splitToSentences("你好世界。今天天气怎么样?", 'zh');
// 返回: ["你好世界。", "今天天气怎么样?"]字幕优化
subtitleOptimization(segments: AimSegments[], options?: OptimizationOptions)
对字幕片段进行全面的质量检查和优化。
功能包括:
- 重复内容检测和移除
- 空白字幕处理
- 0帧字幕处理(开始时间大于等于结束时间)
- 标点符号检查
- 句子结束标记检查
- 多余空格处理
- 结尾标点处理
参数:
segments: AimSegments[]- 需要优化的字幕片段数组options?: OptimizationOptions- 优化选项配置
返回值:
{
result: {
emt: number[], // 空白字幕索引
ep: number[], // 结尾标点索引
zf: number[], // 0帧字幕索引
punc: number[], // 标点符号索引
em: number[], // 句子结束标记索引
space: number[], // 多余空格索引
repeat: number[], // 重复内容索引
},
repeat: AimSegments[], // 重复的片段列表
segments: AimSegments[] // 优化后的字幕片段数组
}示例:
import { tools } from '@aim-packages/subtitle';
const segments = [
{ st: "00:00:01", et: "00:00:03", text: "Hello world." },
{ st: "00:00:03", et: "00:00:05", text: "Hello world." }, // 重复内容
{ st: "00:00:05", et: "00:00:06", text: " " }, // 空白字幕
{ st: "00:00:06", et: "00:00:06", text: "Zero frame" }, // 0帧字幕
];
const result = tools.optimization.subtitleOptimization(segments, {
repeat: true, // 移除重复内容
emt: true, // 移除空白字幕
zf: true, // 移除0帧字幕
space: true, // 处理多余空格
ep: true // 移除结尾标点
});RepeatCheck 类
重复内容检测器,用于检测字幕中连续重复的内容片段。
构造函数:
constructor(options?: RepeatCheckOption)方法:
push(segment: AimSegments)- 添加字幕片段进行重复检测end()- 结束检测,处理最后的重复片段reset()- 重置检测器状态
属性:
threshold: number- 重复检测阈值,默认为2次hit: number- 当前重复次数prevSegment?: AimSegments- 前一个字幕片段hitSegment?: AimSegments- 当前重复的字幕片段hitSegmentList: AimSegments[]- 重复片段列表options: RepeatCheckOption- 配置选项
示例:
import { tools } from '@aim-packages/subtitle';
const repeatCheck = new tools.optimization.RepeatCheck({
onHit: (segments) => {
console.log('检测到重复内容:', segments);
}
});
repeatCheck.push({ st: "00:00:01", et: "00:00:03", text: "Hello" });
repeatCheck.push({ st: "00:00:03", et: "00:00:05", text: "Hello" }); // 重复
repeatCheck.push({ st: "00:00:05", et: "00:00:07", text: "Hello" }); // 重复,触发回调
repeatCheck.end();字幕输出
字幕输出模块提供了多种格式的字幕文件生成功能,支持SRT、VTT、LRC、ASS等主流字幕格式。
outputSrt(params: OutputTextParams): string
生成SRT格式的字幕文件。
参数:
params: OutputTextParams- 输出参数配置
返回值:
string- SRT格式的字幕内容
示例:
import { tools } from '@aim-packages/subtitle';
const segments1 = [
["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
["00:00:03,000", "00:00:05,000", "How are you?", "speaker2"]
];
const segments2 = [
["00:00:01,000", "00:00:03,000", "你好世界", "speaker1"],
["00:00:03,000", "00:00:05,000", "你好吗?", "speaker2"]
];
const speakerData = {
settings: {
speaker1: { spk: "speaker1", name: "张三", color: "#FF0000" },
speaker2: { spk: "speaker2", name: "李四", color: "#00FF00" }
},
speakers: { speaker1: 1, speaker2: 1 },
data: []
};
const srtContent = tools.output.outputSrt({
segments1,
segments2,
speakerData
});
console.log(srtContent);
// 输出:
// 1
// 00:00:01,000 --> 00:00:03,000
// 张三: Hello world
// 你好世界
//
// 2
// 00:00:03,000 --> 00:00:05,000
// 李四: How are you?
// 你好吗?outputVtt(params: OutputTextParams): string
生成VTT格式的字幕文件。
参数:
params: OutputTextParams- 输出参数配置
返回值:
string- VTT格式的字幕内容
示例:
import { tools } from '@aim-packages/subtitle';
const segments1 = [
["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
["00:00:03,000", "00:00:05,000", "How are you?", "speaker2"]
];
const vttContent = tools.output.outputVtt({
segments1,
speakerData
});
console.log(vttContent);
// 输出:
// WEBVTT
//
// 00:00:01.000 --> 00:00:03.000
// 张三: Hello world
//
// 00:00:03.000 --> 00:00:05.000
// 李四: How are you?outputLrc(params: OutputTextParams): string
生成LRC格式的字幕文件。
参数:
params: OutputTextParams- 输出参数配置
返回值:
string- LRC格式的字幕内容
示例:
import { tools } from '@aim-packages/subtitle';
const segments1 = [
["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
["00:00:03,000", "00:00:05,000", "How are you?", "speaker2"]
];
const lrcContent = tools.output.outputLrc({
segments1,
segments2,
speakerData
});
console.log(lrcContent);
// 输出:
// [01.000]张三: Hello world
// [01.000]你好世界
// [03.000]李四: How are you?
// [03.000]你好吗?outputAss(params: OutputTextParams): string
生成ASS格式的字幕文件,支持复杂的样式配置。
参数:
params: OutputTextParams- 输出参数配置
返回值:
string- ASS格式的字幕内容
示例:
import { tools } from '@aim-packages/subtitle';
const segments1 = [
["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
["00:00:03,000", "00:00:05,000", "How are you?", "speaker2"]
];
const segments2 = [
["00:00:01,000", "00:00:03,000", "你好世界", "speaker1"],
["00:00:03,000", "00:00:05,000", "你好吗?", "speaker2"]
];
const subtitleSettings = {
disabled: false,
stroke: true,
shadow: true,
background: true,
backgroundColor: "#000000",
main: {
color: "#FFFFFF",
borderColor: "#000000",
size: 18,
fontFamily: "Microsoft YaHei"
},
sub: {
color: "#FFFF00",
borderColor: "#000000",
size: 14,
fontFamily: "Microsoft YaHei"
},
position: {
bottom: 20,
left: 10
},
mode: "multiLang"
};
const assContent = tools.output.outputAss({
segments1,
segments2,
subtitleSettings,
speakerData,
isMac: false,
reverse: false
});
console.log(assContent);
// 输出完整的ASS格式字幕文件,包含样式定义和字幕内容outputTxt(params: OutputTextParams): string
生成TXT格式的纯文本文件,支持多种输出模式。
参数:
params: OutputTextParams- 输出参数配置useIndex?: boolean- 是否显示索引编号useTimestamp?: boolean- 是否显示时间戳useParagraph?: boolean- 是否使用段落模式(按发言人分组或按chunkSize分组)
返回值:
string- TXT格式的文本内容
功能特性:
- 段落模式: 支持按说话人分组或按固定大小分块
- 行模式: 每个字幕片段单独一行
- 多语言支持: 自动检测中日韩语言,调整文本连接方式
- 说话人支持: 支持显示说话人名称和时间戳
示例:
import { tools } from '@aim-packages/subtitle';
const segments1 = [
["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
["00:00:03,000", "00:00:05,000", "How are you?", "speaker1"],
["00:00:05,000", "00:00:07,000", "I'm fine, thank you.", "speaker2"]
];
const segments2 = [
["00:00:01,000", "00:00:03,000", "你好世界", "speaker1"],
["00:00:03,000", "00:00:05,000", "你好吗?", "speaker1"],
["00:00:05,000", "00:00:07,000", "我很好,谢谢。", "speaker2"]
];
const speakerData = {
settings: {
speaker1: { spk: "speaker1", name: "张三", color: "#FF0000" },
speaker2: { spk: "speaker2", name: "李四", color: "#00FF00" }
},
speakers: { speaker1: 1, speaker2: 1 },
data: []
};
// 行模式 - 每个字幕片段单独一行
const txtContent1 = tools.output.outputTxt({
segments1,
segments2,
speakerData,
useIndex: true,
useTimestamp: true,
useParagraph: false
});
console.log(txtContent1);
// 输出:
// 0
// 00:00:01,000 --> 00:00:03,000
// 张三: Hello world
// 你好世界
//
// 1
// 00:00:03,000 --> 00:00:05,000
// 张三: How are you?
// 你好吗?
//
// 2
// 00:00:05,000 --> 00:00:07,000
// 李四: I'm fine, thank you.
// 我很好,谢谢。
// 段落模式 - 按说话人分组
const txtContent2 = tools.output.outputTxt({
segments1,
segments2,
speakerData,
useIndex: true,
useTimestamp: true,
useParagraph: true
});
console.log(txtContent2);
// 输出:
// 1
// 张三 - 00:00:01,000 --> 00:00:05,000
// Hello world How are you?
// 你好世界 你好吗?
//
// 2
// 李四 - 00:00:05,000 --> 00:00:07,000
// I'm fine, thank you.
// 我很好,谢谢。
##### `outputMarkdown(params: OutputTextParams): string`
生成 Markdown 格式的字幕文件,支持多种输出模式和说话人分组。
**参数:**
- `params: OutputTextParams` - 输出参数配置
- `header?: string` - 文档标题
- `isMd?: boolean` - 是否为 Markdown 格式(影响换行符)
- `chunkSize?: number` - 分块大小,默认为 10
**返回值:**
- `string` - Markdown 格式的字幕内容
**功能特性:**
- **说话人分组**: 支持按说话人分组显示内容
- **多语言支持**: 自动检测中日韩语言,调整文本连接方式
- **分块处理**: 支持按固定大小分块处理长文本
- **时间显示**: 自动清理时间格式,移除毫秒和多余前缀
- **灵活格式**: 支持 Markdown 和纯文本两种输出格式
**示例:**
```typescript
import { tools } from '@aim-packages/subtitle';
const segments1 = [
["00:00:01,000", "00:00:03,000", "Hello world", "speaker1"],
["00:00:03,000", "00:00:05,000", "How are you?", "speaker1"],
["00:00:05,000", "00:00:07,000", "I'm fine, thank you.", "speaker2"]
];
const segments2 = [
["00:00:01,000", "00:00:03,000", "你好世界", "speaker1"],
["00:00:03,000", "00:00:05,000", "你好吗?", "speaker1"],
["00:00:05,000", "00:00:07,000", "我很好,谢谢。", "speaker2"]
];
const speakerData = {
settings: {
speaker1: { spk: "speaker1", name: "张三", color: "#FF0000" },
speaker2: { spk: "speaker2", name: "李四", color: "#00FF00" }
},
speakers: { speaker1: 1, speaker2: 1 },
data: []
};
// 带说话人的 Markdown 输出
const mdContent = tools.output.outputMarkdown({
segments1,
segments2,
header: "# 会议记录",
speakerData,
chunkSize: 5
});
console.log(mdContent);
// 输出:
// # 会议记录
//
// 张三 01:30 - 01:45
// Hello world How are you?
// 你好世界 你好吗?
//
// 李四 01:45 - 01:47
// I'm fine, thank you.
// 我很好,谢谢。
// 纯文本输出(无说话人)
const textContent = tools.output.outputMarkdown({
segments1,
segments2,
header: "会议记录",
isMd: false,
chunkSize: 3
});
console.log(textContent);
// 输出:
// 会议记录
// 1. Hello world
// 你好世界
// 2. How are you?
// 你好吗?
// 3. I'm fine, thank you.
// 我很好,谢谢。完整的字幕处理流程示例
import { tools } from '@aim-packages/subtitle';
// 1. 检测语言
const language = tools.detection.detectLanguage("Hello world. How are you?");
// 2. 按句子分割
const sentences = tools.segmentation.splitToSentences("Hello world. How are you?", language.language);
// 3. 转换为字幕片段
const segments = sentences.map((text, index) => ({
st: `00:00:${index * 2}`,
et: `00:00:${(index + 1) * 2}`,
text
}));
// 4. 优化字幕
const optimized = tools.optimization.subtitleOptimization(segments, {
repeat: true,
emt: true,
zf: true,
space: true,
ep: true
});
console.log('优化结果:', optimized);完整的字幕处理流程示例
import { utils, parser, filter, tools } from '@aim-packages/subtitle';
// 1. 解析字幕文件
const srtContent = `1
00:00:01,000 --> 00:00:03,000
Hello world.
2
00:00:03,000 --> 00:00:05,000
How are you?`;
const segments = await parser.srtToAimSegments(srtContent);
// 2. 检测语言
const language = tools.detection.detectLanguage(segments[0].text);
// 3. 按句子分割并重新生成字幕片段
const sentences = tools.segmentation.splitToSentences(segments[0].text, language.language);
const newSegments = sentences.map((text, index) => ({
st: utils.formatTime(index * 2),
et: utils.formatTime((index + 1) * 2),
text
}));
// 4. 优化字幕质量
const optimized = tools.optimization.subtitleOptimization(newSegments, {
repeat: true,
emt: true,
zf: true,
space: true,
ep: true
});
// 5. 文本过滤
const streamFilter = new filter.StreamFilter();
streamFilter.parse([
["敏感词", "***"],
["不当词汇", "替换文本"]
]);
const filteredSegments = optimized.segments.map(segment => ({
...segment,
text: streamFilter.feedAll(segment.text)
}));
console.log('最终处理结果:', filteredSegments);Tools 模块相关接口
LanguageDetectionResultsEntry
interface LanguageDetectionResultsEntry {
/** 语言代码 (如 'zh', 'en', 'ja' 等) */
language: LanguageCode
/** 语言名称 (如 '中文', 'English', '日本語' 等) */
languageName: string
/** 检测准确度概率 (0-1 之间的数值) */
probability: number
}RepeatCheckOption
interface RepeatCheckOption {
/** 当检测到重复内容时的回调函数 */
onHit?: (segment: AimSegments[]) => void
}OutputTextParams
interface OutputTextParams {
/** 主要字幕片段数组 */
segments1: Array<ISegment>;
/** 次要字幕片段数组(如翻译字幕) */
segments2?: Array<ISegment>;
/** 字幕样式设置 */
subtitleSettings?: SubtitleSettings;
/** 说话人数据 */
speakerData?: SpeakerData | null;
/** 本地化配置 */
locale?: Record<string, any>;
// TXT格式相关
/** 是否使用索引 */
useIndex?: boolean;
/** 是否使用时间戳 */
useTimestamp?: boolean;
/** 是否使用段落格式 */
useParagraph?: boolean;
// Markdown格式相关
/** 文档标题 */
header?: string;
/** 是否为Markdown格式 */
isMd?: boolean;
/** 分块大小 */
chunkSize?: number;
// ASS格式相关
/** 是否为Mac系统 */
isMac?: boolean;
/** 是否反转字幕顺序 */
reverse?: boolean;
}ISegment
type ISegment = [string, string, string, string | undefined]
// [开始时间, 结束时间, 文本内容, 说话人标识]SpeakerData
interface SpeakerData {
/** 说话人设置配置 */
settings: Record<string, { spk: string; name?: string; color: string }>;
/** 说话人统计 */
speakers: Record<string, number>;
/** 说话人时间数据 */
data: { start: number; end: number; speaker: string }[];
/** 其他选项 */
options?: { speakerCount?: number; }
}SubtitleSettings
interface SubtitleSettings {
/** 是否禁用字幕 */
disabled: boolean;
/** 是否启用描边 */
stroke: boolean;
/** 是否启用阴影 */
shadow: boolean;
/** 是否启用背景 */
background: boolean;
/** 背景颜色 */
backgroundColor: string;
/** 主要字幕样式 */
main: SubtitleTextStyle;
/** 次要字幕样式 */
sub: SubtitleTextStyle;
/** 字幕位置 */
position: SubtitlePosition;
/** 字幕语言模式 */
mode: SubtitleLanguage;
/** 字幕顺序 */
order?: number[];
/** 水印样式 */
watermark?: WatermarkStyle;
}🏗️ 项目结构
src/
├── utils/ # 工具函数 - 时间格式化、语言处理、文本分块等
├── parser/ # 字幕格式解析器 - SRT、VTT、ASS 格式转换和流式解析
├── tools/ # 字幕处理工具 - 语言检测、文本分割、字幕优化等
├── filter/ # 字幕过滤器 - 基于 DFA 算法的流式文本过滤
├── types/ # TypeScript 类型定义
└── main.ts # 主入口文件📋 模块功能概览
Utils 模块
- 时间处理: 时间格式化、秒数转换、数字补零、ASS 时间格式转换等
- 语言处理: 中日韩字符检测、语言代码转换等
- 文本分块: 按字符限制分块、字幕片段分块等
- 字幕合并: 合并相邻字幕片段、时间轴优化等
- 颜色转换: 十六进制颜色转 ASS 格式、FFmpeg 格式等
Parser 模块
- 格式转换: SRT、VTT、ASS 格式之间的相互转换
- 第三方服务: 腾讯云听悟、OpenAI Whisper 结果转换
- 流式解析: 支持实时流式字幕数据的解析和处理
Tools 模块
- 语言检测: 多语言文本的语言识别和概率分析
- 文本分割: 按句子、段落等规则分割文本
- 字幕优化: 重复检测、空白处理、质量优化等
- 字幕输出: 支持SRT、VTT、LRC、ASS等多种格式的字幕文件生成
Filter 模块
- 流式过滤: 基于 DFA 算法的实时文本过滤
- 敏感词处理: 支持关键词替换、删除等操作
- 逐字符处理: 支持逐字符输入和实时过滤
🔧 开发
# 安装依赖
pnpm install
# 开发模式
pnpm dev
# 构建
pnpm build
# 预览构建结果
pnpm preview
# 发布
pnpm release📄 许可证
MIT License
🤝 贡献
欢迎提交 Issue 和 Pull Request!
