@evio/ffai 1.0.1 → 1.0.3
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 +202 -51
- package/dist/index.js +5 -0
- package/dist/lib.d.ts +2 -0
- package/dist/lib.js +24 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,111 +1,262 @@
|
|
|
1
1
|
# FFmpeg 视频处理工具
|
|
2
2
|
|
|
3
|
-
一个基于 FFmpeg
|
|
3
|
+
一个基于 FFmpeg 的命令行工具,用于批量处理视频和音频文件,支持视频转码、音频合并、字幕添加等功能。适用于自动化视频制作、批量视频处理等场景。
|
|
4
4
|
|
|
5
|
-
## 功能特性
|
|
5
|
+
## ✨ 功能特性
|
|
6
6
|
|
|
7
|
-
- 🎬
|
|
8
|
-
- 🎵
|
|
9
|
-
- 📝
|
|
10
|
-
- 🎲
|
|
11
|
-
- 📦
|
|
12
|
-
- ⚙️
|
|
7
|
+
- 🎬 **批量视频转码和拼接** - 支持多种视频格式转换为 MP4,自动拼接多个视频片段
|
|
8
|
+
- 🎵 **多音频文件混音** - 将多个音频文件混合为一个音轨,支持 glob 模式批量处理
|
|
9
|
+
- 📝 **自动添加字幕** - 支持自定义时间格式的字幕文件,自动渲染到视频上
|
|
10
|
+
- 🎲 **智能视频选择** - 随机模式根据音频时长智能选择视频片段,避免重复
|
|
11
|
+
- 📦 **Glob 模式匹配** - 灵活的文件匹配模式,支持通配符批量处理
|
|
12
|
+
- ⚙️ **JSON 配置管理** - 基于配置文件的任务管理,支持批量任务处理
|
|
13
|
+
- 🔄 **自动化工作流** - 一键完成从素材到成品的全流程处理
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## 📋 前置要求
|
|
15
16
|
|
|
17
|
+
- **Node.js** >= 14.0.0
|
|
18
|
+
- **FFmpeg** - 必须在系统中安装并配置到环境变量
|
|
19
|
+
|
|
20
|
+
### 安装 FFmpeg
|
|
21
|
+
|
|
22
|
+
**macOS:**
|
|
23
|
+
```bash
|
|
24
|
+
brew install ffmpeg
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Ubuntu/Debian:**
|
|
16
28
|
```bash
|
|
29
|
+
sudo apt update
|
|
30
|
+
sudo apt install ffmpeg
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Windows:**
|
|
34
|
+
从 [FFmpeg 官网](https://ffmpeg.org/download.html) 下载并配置环境变量
|
|
35
|
+
|
|
36
|
+
## 🚀 快速开始
|
|
37
|
+
|
|
38
|
+
### 安装
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 克隆项目
|
|
42
|
+
git clone <repository-url>
|
|
43
|
+
cd <project-directory>
|
|
44
|
+
|
|
45
|
+
# 安装依赖
|
|
17
46
|
npm install
|
|
47
|
+
|
|
48
|
+
# 构建项目
|
|
18
49
|
npm run build
|
|
19
|
-
```
|
|
20
50
|
|
|
21
|
-
|
|
51
|
+
# 全局安装(可选)
|
|
52
|
+
npm link
|
|
53
|
+
```
|
|
22
54
|
|
|
23
|
-
###
|
|
55
|
+
### 基本使用
|
|
24
56
|
|
|
25
57
|
```bash
|
|
26
|
-
|
|
58
|
+
# 执行任务配置文件
|
|
59
|
+
ffai build task.json
|
|
27
60
|
```
|
|
28
61
|
|
|
62
|
+
## 📖 详细使用说明
|
|
63
|
+
|
|
29
64
|
### 任务配置文件
|
|
30
65
|
|
|
31
|
-
创建一个 JSON
|
|
66
|
+
创建一个 JSON 配置文件来定义处理任务,支持多个任务批量执行:
|
|
32
67
|
|
|
33
68
|
```json
|
|
34
69
|
[
|
|
35
70
|
{
|
|
36
71
|
"audios": ["audio1.mp3", "audio2.mp3"],
|
|
37
72
|
"videos": "videos/*.mp4",
|
|
38
|
-
"narration": "subtitle.
|
|
73
|
+
"narration": "subtitle.txt",
|
|
39
74
|
"videoTranscodeable": true,
|
|
40
75
|
"videoPackMode": "random"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"audios": "bgm/*.mp3",
|
|
79
|
+
"videos": ["clip1.mp4", "clip2.mp4"],
|
|
80
|
+
"narration": "subtitle2.txt",
|
|
81
|
+
"videoPackMode": "all"
|
|
41
82
|
}
|
|
42
83
|
]
|
|
43
84
|
```
|
|
44
85
|
|
|
45
|
-
###
|
|
86
|
+
### 配置参数详解
|
|
87
|
+
|
|
88
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
89
|
+
|------|------|------|------|
|
|
90
|
+
| `audios` | `string \| string[]` | ✅ | 音频文件路径,支持数组或 glob 模式(如 `bgm/*.mp3`)<br>⚠️ **重要**:如果是数组,最终音频时长由第一个音频文件决定,其他音频会被混音叠加 |
|
|
91
|
+
| `videos` | `string \| string[]` | ✅ | 视频文件路径,支持数组或 glob 模式(如 `videos/*.mp4`) |
|
|
92
|
+
| `narration` | `string` | ✅ | 字幕文件路径,使用自定义时间格式 |
|
|
93
|
+
| `videoTranscodeable` | `boolean` | ❌ | 是否对视频进行转码,默认 `false`。启用后会将所有视频统一转码为 H.264/AAC 格式 |
|
|
94
|
+
| `videoPackMode` | `'random' \| 'all'` | ❌ | 视频打包模式,默认 `'all'`<br>- `random`: 根据音频时长随机选择视频片段<br>- `all`: 使用所有视频文件 |
|
|
95
|
+
|
|
96
|
+
### 字幕文件格式
|
|
46
97
|
|
|
47
|
-
|
|
48
|
-
- `videos`: 视频文件路径,支持数组或 glob 模式
|
|
49
|
-
- `narration`: 字幕文件路径(SRT 格式)
|
|
50
|
-
- `videoTranscodeable`: 是否对视频进行转码(可选,默认 false)
|
|
51
|
-
- `videoPackMode`: 视频打包模式
|
|
52
|
-
- `random`: 随机选择视频片段直到满足音频时长
|
|
53
|
-
- `all`: 使用所有视频文件
|
|
98
|
+
字幕文件使用简化的时间格式,每行一条字幕:
|
|
54
99
|
|
|
55
|
-
|
|
100
|
+
```
|
|
101
|
+
00:05-00:10 这是第一条字幕
|
|
102
|
+
00:12-00:18 这是第二条字幕
|
|
103
|
+
00:20-00:25 支持中文和英文
|
|
104
|
+
```
|
|
56
105
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
5. 添加背景音频
|
|
62
|
-
6. 添加字幕
|
|
63
|
-
7. 输出最终视频到 `task-{index}/video.mp4`
|
|
106
|
+
格式说明:
|
|
107
|
+
- 时间格式:`MM:SS-MM:SS 字幕内容`
|
|
108
|
+
- 开始时间和结束时间用 `-` 分隔
|
|
109
|
+
- 时间和文本之间用空格分隔
|
|
64
110
|
|
|
65
|
-
##
|
|
111
|
+
## 🔄 工作流程
|
|
66
112
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
113
|
+
工具会按以下步骤自动处理每个任务:
|
|
114
|
+
|
|
115
|
+
1. **检查 FFmpeg** - 验证 FFmpeg 是否可用
|
|
116
|
+
2. **解析配置** - 读取任务配置文件
|
|
117
|
+
3. **视频转码**(可选)- 将视频统一转码为 MP4 格式(H.264 + AAC)
|
|
118
|
+
4. **音频混音** - 将多个音频文件混合为一个音轨
|
|
119
|
+
5. **视频选择** - 根据模式选择视频片段
|
|
120
|
+
6. **视频拼接** - 将选中的视频片段拼接成完整视频
|
|
121
|
+
7. **添加音频** - 为视频添加混音后的背景音频
|
|
122
|
+
8. **渲染字幕** - 将字幕渲染到视频上
|
|
123
|
+
9. **输出成品** - 生成最终视频文件
|
|
124
|
+
|
|
125
|
+
## 📂 输出结构
|
|
126
|
+
|
|
127
|
+
每个任务会在当前目录下创建 `task-{index}` 文件夹:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
task-0/
|
|
131
|
+
├── videos/ # 转码后的视频文件(如果启用 videoTranscodeable)
|
|
132
|
+
│ ├── 1.mp4
|
|
133
|
+
│ ├── 2.mp4
|
|
134
|
+
│ └── ...
|
|
135
|
+
├── tmp/ # 临时文件目录
|
|
136
|
+
├── audio.mp3 # 混音后的音频文件
|
|
137
|
+
└── video.mp4 # 最终输出的视频文件 ⭐
|
|
70
138
|
```
|
|
71
139
|
|
|
72
|
-
|
|
140
|
+
## 💡 使用示例
|
|
141
|
+
|
|
142
|
+
### 示例 1:随机视频片段 + 多音频混音
|
|
73
143
|
|
|
74
144
|
```json
|
|
75
145
|
[
|
|
76
146
|
{
|
|
77
|
-
"audios": ["bgm
|
|
78
|
-
"videos":
|
|
79
|
-
"narration": "subtitles/
|
|
147
|
+
"audios": ["bgm/music1.mp3", "bgm/music2.mp3"],
|
|
148
|
+
"videos": "clips/*.mp4",
|
|
149
|
+
"narration": "subtitles/narration.txt",
|
|
80
150
|
"videoTranscodeable": true,
|
|
81
151
|
"videoPackMode": "random"
|
|
82
152
|
}
|
|
83
153
|
]
|
|
84
154
|
```
|
|
85
155
|
|
|
86
|
-
|
|
156
|
+
这个配置会:
|
|
157
|
+
- 混合两个背景音乐
|
|
158
|
+
- 从 `clips/` 目录随机选择视频片段,直到总时长匹配音频
|
|
159
|
+
- 转码所有视频为统一格式
|
|
160
|
+
- 添加字幕
|
|
87
161
|
|
|
88
|
-
|
|
162
|
+
### 示例 2:使用所有视频 + Glob 模式
|
|
89
163
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
164
|
+
```json
|
|
165
|
+
[
|
|
166
|
+
{
|
|
167
|
+
"audios": "audio/*.mp3",
|
|
168
|
+
"videos": ["intro.mp4", "content.mp4", "outro.mp4"],
|
|
169
|
+
"narration": "subtitle.txt",
|
|
170
|
+
"videoPackMode": "all"
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
```
|
|
93
174
|
|
|
94
|
-
|
|
175
|
+
这个配置会:
|
|
176
|
+
- 混合 `audio/` 目录下的所有 MP3 文件
|
|
177
|
+
- 按顺序拼接三个视频文件
|
|
178
|
+
- 不进行视频转码(使用原始格式)
|
|
95
179
|
|
|
96
|
-
|
|
97
|
-
- FFmpeg(需要系统安装)
|
|
180
|
+
### 示例 3:批量处理多个任务
|
|
98
181
|
|
|
99
|
-
|
|
182
|
+
```json
|
|
183
|
+
[
|
|
184
|
+
{
|
|
185
|
+
"audios": "task1/audio.mp3",
|
|
186
|
+
"videos": "task1/videos/*.mp4",
|
|
187
|
+
"narration": "task1/subtitle.txt",
|
|
188
|
+
"videoPackMode": "random"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"audios": "task2/audio.mp3",
|
|
192
|
+
"videos": "task2/videos/*.mp4",
|
|
193
|
+
"narration": "task2/subtitle.txt",
|
|
194
|
+
"videoPackMode": "all"
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## 🛠️ 开发
|
|
100
200
|
|
|
101
201
|
```bash
|
|
102
|
-
#
|
|
202
|
+
# 开发模式(使用 ts-node 直接运行)
|
|
103
203
|
npm run dev
|
|
104
204
|
|
|
105
|
-
#
|
|
205
|
+
# 构建项目
|
|
106
206
|
npm run build
|
|
207
|
+
|
|
208
|
+
# 构建并修复 ESM 导入路径
|
|
209
|
+
npm run fix
|
|
107
210
|
```
|
|
108
211
|
|
|
109
|
-
##
|
|
212
|
+
## 📝 API 说明
|
|
213
|
+
|
|
214
|
+
项目提供了一系列可复用的函数,可以在其他项目中引入使用:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import {
|
|
218
|
+
getMediaDuration, // 获取媒体文件时长
|
|
219
|
+
transcodeVideo, // 转码单个视频
|
|
220
|
+
transcodeVideos, // 批量转码视频
|
|
221
|
+
concatVideos, // 拼接多个视频
|
|
222
|
+
mergeAudioFiles, // 混音多个音频文件
|
|
223
|
+
addBackgroundAudio, // 为视频添加背景音频
|
|
224
|
+
addSubTitles, // 为视频添加字幕
|
|
225
|
+
checkFfmpegAvailable // 检查 FFmpeg 是否可用
|
|
226
|
+
} from '@evio/ffai';
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## ⚠️ 注意事项
|
|
230
|
+
|
|
231
|
+
1. **FFmpeg 必须安装** - 工具依赖 FFmpeg,请确保已正确安装
|
|
232
|
+
2. **字幕字体** - macOS 默认使用宋体(`/System/Library/Fonts/Supplemental/Songti.ttc`),其他系统可能需要修改字体路径
|
|
233
|
+
3. **视频格式** - 建议启用 `videoTranscodeable` 以确保所有视频格式统一,避免拼接问题
|
|
234
|
+
4. **文件路径** - 所有路径都相对于执行命令的当前目录
|
|
235
|
+
5. **临时文件** - 处理完成后会自动清理临时文件,保留最终成品
|
|
236
|
+
|
|
237
|
+
## 🐛 常见问题
|
|
238
|
+
|
|
239
|
+
**Q: 提示 "FFmpeg is not available"**
|
|
240
|
+
A: 请确保已安装 FFmpeg 并配置到系统环境变量中
|
|
241
|
+
|
|
242
|
+
**Q: 视频拼接后出现黑屏或卡顿**
|
|
243
|
+
A: 建议启用 `videoTranscodeable: true` 将所有视频转码为统一格式
|
|
244
|
+
|
|
245
|
+
**Q: 字幕显示不正常**
|
|
246
|
+
A: 检查字幕文件格式是否正确,时间格式必须为 `MM:SS-MM:SS`
|
|
247
|
+
|
|
248
|
+
**Q: 音频混音后声音太小或太大**
|
|
249
|
+
A: FFmpeg 的 `amix` 滤镜会自动调整音量,如需手动控制可修改 `lib.ts` 中的混音参数
|
|
250
|
+
|
|
251
|
+
## 📄 License
|
|
110
252
|
|
|
111
253
|
ISC
|
|
254
|
+
|
|
255
|
+
## 🤝 贡献
|
|
256
|
+
|
|
257
|
+
欢迎提交 Issue 和 Pull Request!
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
**版本**: 1.0.2
|
|
262
|
+
**作者**: @evio/ffai
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const commander_1 = require("commander");
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
6
|
const task_1 = require("./task");
|
|
7
|
+
const lib_1 = require("./lib");
|
|
7
8
|
const program = new commander_1.Command();
|
|
8
9
|
const pkg = require('../package.json');
|
|
9
10
|
program
|
|
@@ -13,6 +14,10 @@ program
|
|
|
13
14
|
program
|
|
14
15
|
.command('build <file>')
|
|
15
16
|
.action(async (file) => {
|
|
17
|
+
const enable = await (0, lib_1.checkFfmpegAvailable)();
|
|
18
|
+
if (!enable) {
|
|
19
|
+
throw new Error('FFmpeg is not available or not installed. Please install FFmpeg to continue.');
|
|
20
|
+
}
|
|
16
21
|
const taskFile = (0, node_path_1.resolve)(process.cwd(), file);
|
|
17
22
|
if (!taskFile.endsWith('.json')) {
|
|
18
23
|
throw new Error(`Task file must end with '.json' extension. Received: ${file}`);
|
package/dist/lib.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare function concatVideos(files: string[], outputFilePath: string, tm
|
|
|
30
30
|
export declare function addBackgroundAudio(inputVideoPath: string, outputVideoPath: string, audioPath: string): Promise<void>;
|
|
31
31
|
/**
|
|
32
32
|
* 合并多个音频文件(.mp3) - 重叠播放(混音)
|
|
33
|
+
* 以第一个音频的时长为准,超过的部分会被切掉
|
|
33
34
|
* @param audioFiles 音频文件路径数组
|
|
34
35
|
* @param outputFilePath 输出文件路径
|
|
35
36
|
*/
|
|
@@ -43,3 +44,4 @@ export declare function mergeAudioFiles(audioFiles: string[], outputFilePath: st
|
|
|
43
44
|
* @returns Promise
|
|
44
45
|
*/
|
|
45
46
|
export declare function addSubTitles(inputfile: string, outputfile: string, texts: string, fontfile?: string): Promise<void>;
|
|
47
|
+
export declare function checkFfmpegAvailable(): Promise<boolean>;
|
package/dist/lib.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.concatVideos = concatVideos;
|
|
|
10
10
|
exports.addBackgroundAudio = addBackgroundAudio;
|
|
11
11
|
exports.mergeAudioFiles = mergeAudioFiles;
|
|
12
12
|
exports.addSubTitles = addSubTitles;
|
|
13
|
+
exports.checkFfmpegAvailable = checkFfmpegAvailable;
|
|
13
14
|
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
|
|
14
15
|
const dayjs_1 = __importDefault(require("dayjs"));
|
|
15
16
|
const node_fs_1 = require("node:fs");
|
|
@@ -145,14 +146,17 @@ function addBackgroundAudio(inputVideoPath, outputVideoPath, audioPath) {
|
|
|
145
146
|
}
|
|
146
147
|
/**
|
|
147
148
|
* 合并多个音频文件(.mp3) - 重叠播放(混音)
|
|
149
|
+
* 以第一个音频的时长为准,超过的部分会被切掉
|
|
148
150
|
* @param audioFiles 音频文件路径数组
|
|
149
151
|
* @param outputFilePath 输出文件路径
|
|
150
152
|
*/
|
|
151
|
-
function mergeAudioFiles(audioFiles, outputFilePath) {
|
|
153
|
+
async function mergeAudioFiles(audioFiles, outputFilePath) {
|
|
154
|
+
if (!audioFiles?.length) {
|
|
155
|
+
throw new Error("音频文件列表为空");
|
|
156
|
+
}
|
|
157
|
+
// 获取第一个音频文件的时长
|
|
158
|
+
const firstAudioDuration = await getMediaDuration(audioFiles[0]);
|
|
152
159
|
return new Promise((_resolve, reject) => {
|
|
153
|
-
if (!audioFiles?.length) {
|
|
154
|
-
return reject(new Error("音频文件列表为空"));
|
|
155
|
-
}
|
|
156
160
|
// 创建FFmpeg命令实例
|
|
157
161
|
const cmd = (0, fluent_ffmpeg_1.default)();
|
|
158
162
|
// 添加所有音频文件作为输入
|
|
@@ -164,14 +168,15 @@ function mergeAudioFiles(audioFiles, outputFilePath) {
|
|
|
164
168
|
// 设置音频混音滤镜
|
|
165
169
|
// 使用 complexFilter 来处理多个输入流的混合
|
|
166
170
|
// amix 滤镜会将多个音频流混合在一起,默认会自动调整音量
|
|
167
|
-
//
|
|
171
|
+
// duration=first 参数保持与第一个输入相同的时长
|
|
168
172
|
cmd.complexFilter(`amix=inputs=${inputCount}:duration=first`);
|
|
169
173
|
// 设置输出选项
|
|
170
174
|
cmd.outputOptions([
|
|
171
175
|
"-c:a libmp3lame", // 使用MP3编码器
|
|
172
176
|
"-b:a 192k", // 设置音频比特率
|
|
173
177
|
"-ar 44100", // 设置音频采样率
|
|
174
|
-
"-ac 2" // 设置声道数
|
|
178
|
+
"-ac 2", // 设置声道数
|
|
179
|
+
`-t ${firstAudioDuration}` // 明确限制输出时长为第一个音频的时长
|
|
175
180
|
])
|
|
176
181
|
// .on("start", (cmd) => console.log("混音音频命令:", cmd, '\n'))
|
|
177
182
|
// .on("progress", (progress) => {
|
|
@@ -233,7 +238,7 @@ function addSubTitles(inputfile, outputfile, texts, fontfile) {
|
|
|
233
238
|
// max_width: "w*0.8", // 最大宽度为视频宽度的80%,超过则自动换行
|
|
234
239
|
// line_spacing: 8, // 行间距为8像素
|
|
235
240
|
x: "(w-text_w)/2", // 水平居中
|
|
236
|
-
y: "(h-text_h-
|
|
241
|
+
y: "(h-text_h-300)", // 底部以上100像素的位置,确保在视频范围内
|
|
237
242
|
enable: `between(t,${e.start},${e.end})`,
|
|
238
243
|
},
|
|
239
244
|
};
|
|
@@ -261,3 +266,15 @@ function addSubTitles(inputfile, outputfile, texts, fontfile) {
|
|
|
261
266
|
.save(outputfile);
|
|
262
267
|
});
|
|
263
268
|
}
|
|
269
|
+
function checkFfmpegAvailable() {
|
|
270
|
+
return new Promise((resolve) => {
|
|
271
|
+
fluent_ffmpeg_1.default.getAvailableFormats((err, formats) => {
|
|
272
|
+
if (err) {
|
|
273
|
+
resolve(false);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
resolve(true);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
}
|