AI prompts
base on 批量为本地视频生成字幕文件,并可将字幕文件翻译成其它语言, 跨平台支持 window, mac 系统 # 批量为视频生成字幕文件,并翻译成其它语言
> [!IMPORTANT]
> 🧨 💥 🎉 本项目是一个命令行工具,但非常荣幸得到了很多朋友的支持,也给我了很大的鼓舞,因此,我基于它制作了一款客户端工具,让大家能够更加方便地使用,也方便一部分不熟悉代码配置的朋友也能使用该工具。
>
> 欢迎大家移步 [video-subtitle-master](https://github.com/buxuku/video-subtitle-master) 以获得更加便捷的使用体验
> ![image](https://github.com/buxuku/video-subtitle-master/raw/main/resources/preview.png)
做这个小工具的初衷:
自己有一大批外文视频,没有字幕,希望能够添加字幕文件,同时也能够将字幕文件翻译成中文, 同时希望能够通过批量处理的方式来减轻工作量。
类似需求,有一批厂商已经提供到了支持,比如 讯飞听见, 网易见外 等,但这些在线服务都涉及到视频的上传动作,效率相对比较低下。
希望能够找一个客户端工具,在本地来生成,试用了一些工具,依然不理想
- Buzz 非 Store 版本没有对 apple silicon 做优化,字幕生成速度比较慢,也不支持翻译
- MacWhisper 免费版本只支持单个生成,不支持批量,不支持翻译
- WhisperScript 可以批量生成,但字幕文件需要手动一个个地保存,不支持翻译
- memo.ac 做了 mac 下的性能优化,可以使用 GPU ,也支持翻译功能,非常棒的一款软件,但目前批量模式 bug 太多,无法正常使用
最后想了一下,本地语音转文字,通常的做法就是使用目前最强的 whisper 模型来生成。那我的需求就比较简单了:
- 通过 ffmpeg 从视频文件中提取出音频文件
- 通过 whisper 模型将音频生成原语言的字幕文件
- 调用翻译 API, 将原语言的字幕文件翻译成目标语言的字幕文件
基于以上简单的思路和流程,就可以简单写一个小工具来批量处理本地的视频了。
## 💥特性
- 源语言字幕文件和目标语言字幕文件放在视频同目录下,方便播放时任意挂载字幕文件
- 批量处理目录下面的所有视频文件
- 可以只生成字幕,不翻译,方便批量为视频生成字幕
- 支持火山引擎翻译
- 支持百度翻译
- 支持 deeplx 翻译 (批量翻译容易存在被限流的情况)
- 支持 ollama 翻译
- 自定义字幕文件名,方便兼容不同的播放器挂载字幕识别
- 自定义翻译后的字幕文件内容,纯翻译结果,原字幕+翻译结果
- 项目集成 `whisper.cpp`, 它对 apple silicon 进行了优化,有较快的生成速度
- 项目集成了 `fluent-ffmpeg`, 无须安装 `ffmpeg`
### ⬆️ 支持的模型
```
tiny.en
tiny
base.en
base
small.en
small
medium.en
medium
large-v1
large-v2
large-v3
```
## 翻译服务
本项目的翻译能力是基于 **百度/火山/deeplx** 的翻译API来实现的,这些 API 的使用需要申请对的 KEY 和 SECRET, 因此,如果你需要使用到翻译服务,需要先申请一个 API 。
具体的申请方法,可以参考 https://bobtranslate.com/service/ , 感谢 [Bob](https://bobtranslate.com/) 这款优秀的软件。
## 🔦使用
1️⃣ 克隆本项目在本地
```shell
git clone https://github.com/buxuku/VideoSubtitleGenerator.git
```
2️⃣ 在项目中执行 `yarn install` 或者 `npm install`
```shell
cd VideoSubtitleGenerator
yarn install
```
3️⃣ 如果需要翻译,复制 `.env.local` 为 `.env` 在项目根目录,访文件用于配置翻译相关的 KEY 和 SECRET, 例如
`BAIDU_` 开头的为百度翻译的配置
`VOLC_` 开头的为火山翻译的配置
`OLLAMA_` 开头的为 ollama 翻译的配置
```shell
BAIDU_KEY=2023120600190xxxx
BAIDU_SECRET=PIbyKjEr1y8u18RZxxxx
VOLC_KEY=AKLTMDUwZjY4MTZkNTFmN4M3ZjlkMzlmYzAzMTdlMDExxxx
VOLC_SECRET=T0dRMllUUmpPREUzWWpjNE5HVm2Zamt4TlRObU9EUm1ORFk0T1dGbExxxx==
OLLAMA_API_URL=http://localhost:11434
OLLAMA_MODEL_NAME=llama3
OLLAMA_PROMPT=Please translate the following content from ${sourceLanguage} to ${targetLanguage}, only return the translation result can be. \n ${content}
```
4️⃣ 其余的配置在 `config.js` 文件中进行配置,每条配置均的详细的注释
```js
// 视频文件所在目录 如 /Users/demo/video
export const videoDir = './examples';
/*
whisper.cpp 模型 支持以下
tiny.en
tiny
base.en
base
small.en
small
medium.en
medium
large-v1
large-v2
large-v3
*/
export const whisperModel = 'base.en';
// 翻译配置,视频原语言与翻译后的目标语言
export const translateConfig = {
sourceLanguage: 'en',
targetLanguage: 'zh',
};
// 支持的翻译服务商
export const supportedService = {
baidu: Symbol.for('baidu'),
volc: Symbol.for('volc'),
deeplx: Symbol.for('deeplx'),
ollama: Symbol.for('ollama'),
};
// 当前使用的翻译服务商,如果不配置,则不执行翻译流程
export const translateServiceProvider = supportedService.volc;
// 翻译结果字幕文件内容配置
export const contentTemplateRuleMap = {
onlyTranslate: Symbol.for('onlyTranslate'), // 只输出翻译内容
sourceAndTranslate: Symbol.for('sourceAndTranslate'), // 输出原始字幕和翻译字幕, 原始字幕在上面
translateAndSource: Symbol.for('translateAndSource'), // 输出翻译后的字幕和原始字幕, 翻译字幕在上面
};
// 字幕文件内容模板 支持 ${sourceContent}, ${targetContent} 变量
export const contentTemplate = {
[contentTemplateRuleMap.onlyTranslate]: '${targetContent}\n\n',
[contentTemplateRuleMap.sourceAndTranslate]: '${sourceContent}\n${targetContent}\n\n',
[contentTemplateRuleMap.translateAndSource]: '${targetContent}\n${sourceContent}\n\n',
};
// 翻译内容输出模板规则,默认只输出翻译内容, 支持 contentTemplateRuleMap 内的规则
export const contentTemplateRule = contentTemplateRuleMap.onlyTranslate;
// 原始字幕文件保存命名规则 支持 fileName, sourceLanguage, targetLanguage 变量
// 如果为空,将不保存原始字幕文件
// eg: '${fileName}.${sourceLanguage}' -> 对于视频名为 text.mp4 的英文视频原始字幕文件名为 text.en.srt
export const sourceSrtSaveName = '${fileName}.${sourceLanguage}';
// 翻译后的字幕文件保存命名规则 支持 fileName, sourceLanguage, targetLanguage 变量
export const targetSrtSaveName = '${fileName}.${targetLanguage}';
```
这里面的字幕内容和字幕文件名可以自定义配置,同时会将里面的 `${xxx}`变量转为对应的字符
核心配置主要为以下几项
#### 选择翻译服务商
```js
// 当前使用的翻译服务商,如果不配置,则不执行翻译流程
export const translateServiceProvider = supportedService.volc;
```
支持
- supportedService.volc 火山翻译
- supportedService.baidu 百度翻译
- supportedService.deeplx deeplx 翻译
- supportedService.ollama ollama 翻译
#### 翻译结果的配置
```js
// 翻译内容输出模板规则,默认只输出翻译内容, 支持 contentTemplateRuleMap 内的规则
export const contentTemplateRule = contentTemplateRuleMap.onlyTranslate;
```
支持以下几种设置模式
##### contentTemplateRuleMap.onlyTranslate
只保留翻译后的结果内容,如
```
1
00:00:00,000 --> 00:00:09,360
我要和你们谈谈我这本书里的一些东西,我希望能
2
00:00:09,360 --> 00:00:13,680
和你们已经听到的东西产生共鸣,我会试着建立一些联系。
```
##### contentTemplateRuleMap.sourceAndTranslate
保留原字幕和翻译之后的字幕,且原字幕在上面
```
1
00:00:00,000 --> 00:00:09,360
I'm going to talk to you about some stuff that's in this book of mine that I hope will
我要和你们谈谈我这本书里的一些东西,我希望能
2
00:00:09,360 --> 00:00:13,680
resonate with other things that you've already heard and I'll try to make some connections
和你们已经听到的东西产生共鸣,我会试着建立一些联系。
```
##### contentTemplateRuleMap.translateAndSource
保留原字幕和翻译之后的字幕,且翻译字幕在上面
```
1
00:00:00,000 --> 00:00:09,360
我要和你们谈谈我这本书里的一些东西,我希望能
I'm going to talk to you about some stuff that's in this book of mine that I hope will
2
00:00:09,360 --> 00:00:13,680
和你们已经听到的东西产生共鸣,我会试着建立一些联系。
resonate with other things that you've already heard and I'll try to make some connections
```
5️⃣ 配置好该文件之后,执行 `yarn start` 或者 `npm start`, 首次执行会下载 `whisper.cpp` 和配置的对应的模型文件,会比较慢一些。下次执行将会跳过该流程
如果在使用过程中遇到啥问题,可以提 Issue 或者通过 Discussions 进行讨论
", Assign "at most 3 tags" to the expected json: {"id":"10091","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"