npm.io
0.8.0 • Published yesterday

@aim-packages/subtitle

Licence
Version
0.8.0
Deps
4
Size
1.9 MB
Vulns
0
Weekly
0

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;     // 是否移除结尾标点
  epKeep?: EndPunctuationKeepOptions; // 删除结尾标点时需要保留的符号
  zf: boolean;     // 是否移除零帧字幕
  punc: boolean;   // 是否移除标点分割符号
  em: boolean;     // 是否移除结束标记
  space: boolean;  // 是否移除多余空格
  repeat: boolean; // 是否移除重复内容
}

interface EndPunctuationKeepOptions {
  question?: boolean;    // 保留中英文问号 ?、?
  exclamation?: boolean; // 保留中英文感叹号 !、!
  symbols?: string;      // 从现有结尾标点集合中额外保留的符号,例如 ",…"
}
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,mmmHH: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");   // false
languageCodeToName(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: 每个分块的最大字符数限制

作用:

  1. 将字符串数组按照字符限制进行分块,避免单次处理内容过多
  2. 使用换行符连接同一分块内的字符串
  3. 在分块时会预留一定的字符空间(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[]             // 每个分块的起始索引数组
}

作用:

  1. 将字幕片段数组按照字符限制分块,避免单次处理内容过多
  2. 为每个片段添加索引信息,便于后续处理和追踪
  3. 生成多种格式的分块结果,满足不同场景的需求
字幕片段合并
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: 输入的字幕片段数组,每个片段包含 startend 时间
  • option.maxDistance: 最大时间间隔,小于此值的相邻片段会被合并
  • option.padding: 可选的时间填充,为每个片段扩展开始和结束时间

作用:

  1. 将时间间隔小于 maxDistance 的相邻字幕片段合并为一个片段
  2. 可选择性地为每个片段添加 padding 时间,扩展片段的开始和结束时间
  3. 如果添加了 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)格式

作用:

  1. 将十六进制颜色转换为 ASS 格式,用于字幕样式设置
  2. 支持 7 位和 9 位十六进制颜色格式
  3. 自动处理颜色通道的顺序转换(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)格式

作用:

  1. 将十六进制颜色转换为 FFmpeg 格式,用于字幕样式设置
  2. 支持 7 位和 9 位十六进制颜色格式
  3. 自动处理颜色通道的顺序转换(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:01.00"

参数说明:

  • str: string - 输入的时间字符串,格式为 HHSS.MMM

作用:

  1. 将标准时间格式字符串转换为 ASS 字幕文件所需的时间格式
  2. 支持 HHSS.MMM 格式的输入
  3. 确保时间轴的准确性和兼容性
  4. 自动处理毫秒的精度转换(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 - 输入的时间字符串

作用:

  1. 移除时间字符串末尾的毫秒部分(支持 .xxx、,xxx 等分隔符)
  2. 移除时间字符串开头多余的 "00:" 小时前缀
  3. 保持时间格式的简洁性和可读性
  4. 自动处理不同格式的时间字符串,统一输出格式
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>) => void
Filter 流式文本过滤器

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 - 优化选项配置

ep: true 默认会删除支持的行尾标点。可以通过 epKeep 保留中英文问号、感叹号,或传入其他需要保留的符号:

const result = tools.optimization.subtitleOptimization(segments, {
  ep: true,
  epKeep: {
    question: true,
    exclamation: true,
    symbols: ",…"
  }
});

question: true 会同时保留 ?exclamation: true 会同时保留 !symbols 中的字符会追加到保留集合中;它主要用于现有结尾标点集合中的 .,?!。未传 epKeep 时保持原有行为。

返回值:

{
  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!