@yeah126139163/video-merger-cli 1.0.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 +549 -0
- package/bin/vvm +3 -0
- package/dist/batch.d.ts +48 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/batch.js +162 -0
- package/dist/batch.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +144 -0
- package/dist/index.js.map +1 -0
- package/dist/merger.d.ts +26 -0
- package/dist/merger.d.ts.map +1 -0
- package/dist/merger.js +179 -0
- package/dist/merger.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
# Video Merger CLI (vvm)
|
|
2
|
+
|
|
3
|
+
一个基于 Node.js 和 FFmpeg 的视频合并 CLI 工具,支持为视频添加片头、片尾,以及批量处理多个视频文件。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- ✅ **视频合并**:将两个视频合并为一个(支持开头或结尾位置)
|
|
8
|
+
- ✅ **批量处理**:批量处理目录下的所有视频文件
|
|
9
|
+
- ✅ **灵活配置**:支持命令行参数和配置文件两种方式
|
|
10
|
+
- ✅ **素材管理**:独立管理片头、片尾等素材
|
|
11
|
+
- ✅ **规则匹配**:支持按文件名模式应用不同的处理规则
|
|
12
|
+
- ✅ **全局安装**:可作为全局命令行工具使用
|
|
13
|
+
|
|
14
|
+
## 前置要求
|
|
15
|
+
|
|
16
|
+
- Node.js >= 16.0.0
|
|
17
|
+
- FFmpeg(必须已安装并添加到系统环境变量)
|
|
18
|
+
|
|
19
|
+
### 安装 FFmpeg
|
|
20
|
+
|
|
21
|
+
**Windows**:
|
|
22
|
+
```bash
|
|
23
|
+
# 使用 Chocolatey
|
|
24
|
+
choco install ffmpeg
|
|
25
|
+
|
|
26
|
+
# 或从官网下载: https://ffmpeg.org/download.html
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**macOS**:
|
|
30
|
+
```bash
|
|
31
|
+
brew install ffmpeg
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Linux**:
|
|
35
|
+
```bash
|
|
36
|
+
sudo apt-get install ffmpeg
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 安装
|
|
40
|
+
|
|
41
|
+
### 全局安装(推荐)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install -g video-merger-cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 本地开发
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# 克隆项目
|
|
51
|
+
git clone <repository-url>
|
|
52
|
+
cd vvm
|
|
53
|
+
|
|
54
|
+
# 安装依赖
|
|
55
|
+
npm install
|
|
56
|
+
|
|
57
|
+
# 构建项目
|
|
58
|
+
npm run build
|
|
59
|
+
|
|
60
|
+
# 链接到全局
|
|
61
|
+
npm link
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 快速开始
|
|
65
|
+
|
|
66
|
+
### 1. 合并两个视频
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
vvm merge video1.mp4 video2.mp4 output.mp4
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
将 `video2.mp4` 合并到 `video1.mp4` 的结尾,输出为 `output.mp4`。
|
|
73
|
+
|
|
74
|
+
**参数说明**:
|
|
75
|
+
- `<video1>`: 第一个视频文件路径
|
|
76
|
+
- `<video2>`: 第二个视频文件路径
|
|
77
|
+
- `[output]`: 输出文件路径(默认:output.mp4)
|
|
78
|
+
- `-p, --position <position>`: 合并位置,start 或 end(默认:end)
|
|
79
|
+
|
|
80
|
+
**示例**:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# 合并到开头
|
|
84
|
+
vvm merge video1.mp4 video2.mp4 output.mp4 -p start
|
|
85
|
+
|
|
86
|
+
# 合并到结尾(默认)
|
|
87
|
+
vvm merge video1.mp4 video2.mp4 output.mp4 -p end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2. 批量处理视频
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
vvm batch ./videos -i intro.mp4 -o outro.mp4 -d output
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
批量处理 `./videos` 目录下的所有视频,为每个视频添加片头 `intro.mp4` 和片尾 `outro.mp4`,输出到 `output` 目录。
|
|
97
|
+
|
|
98
|
+
**参数说明**:
|
|
99
|
+
- `[inputDir]`: 输入目录路径
|
|
100
|
+
- `-i, --intro <path>`: 片头视频路径
|
|
101
|
+
- `-o, --outro <path>`: 片尾视频路径
|
|
102
|
+
- `-d, --dir <path>`: 输出目录路径(默认:输入目录/output)
|
|
103
|
+
- `-c, --config <path>`: 配置文件路径(使用配置文件时优先)
|
|
104
|
+
|
|
105
|
+
**示例**:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# 只添加片头
|
|
109
|
+
vvm batch ./videos -i intro.mp4 -d output
|
|
110
|
+
|
|
111
|
+
# 只添加片尾
|
|
112
|
+
vvm batch ./videos -o outro.mp4 -d output
|
|
113
|
+
|
|
114
|
+
# 同时添加片头和片尾
|
|
115
|
+
vvm batch ./videos -i intro.mp4 -o outro.mp4 -d output
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 3. 使用配置文件批量处理
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# 创建配置文件模板
|
|
122
|
+
vvm init -f my-config.json
|
|
123
|
+
|
|
124
|
+
# 使用配置文件批量处理
|
|
125
|
+
vvm batch -c my-config.json
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## 配置文件说明
|
|
129
|
+
|
|
130
|
+
配置文件使用 JSON 格式,提供更灵活的批量处理规则。
|
|
131
|
+
|
|
132
|
+
### 配置文件结构
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"materials": {
|
|
137
|
+
"intro": {
|
|
138
|
+
"path": "./assets/intro.mp4",
|
|
139
|
+
"enabled": true
|
|
140
|
+
},
|
|
141
|
+
"outro": {
|
|
142
|
+
"path": "./assets/outro.mp4",
|
|
143
|
+
"enabled": true
|
|
144
|
+
},
|
|
145
|
+
"watermark": {
|
|
146
|
+
"path": "./assets/watermark.png",
|
|
147
|
+
"enabled": false
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"batchRules": [
|
|
151
|
+
{
|
|
152
|
+
"pattern": "*.mp4",
|
|
153
|
+
"applyIntro": true,
|
|
154
|
+
"applyOutro": true,
|
|
155
|
+
"outputDir": "./output"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"pattern": "special_*.mp4",
|
|
159
|
+
"applyIntro": false,
|
|
160
|
+
"applyOutro": true,
|
|
161
|
+
"outputDir": "./output/special"
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
"output": {
|
|
165
|
+
"format": "mp4",
|
|
166
|
+
"codec": "libx264",
|
|
167
|
+
"quality": "high"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 配置项说明
|
|
173
|
+
|
|
174
|
+
#### materials(素材配置)
|
|
175
|
+
|
|
176
|
+
- **intro**: 片头视频
|
|
177
|
+
- `path`: 片头文件路径
|
|
178
|
+
- `enabled`: 是否启用(true/false)
|
|
179
|
+
|
|
180
|
+
- **outro**: 片尾视频
|
|
181
|
+
- `path`: 片尾文件路径
|
|
182
|
+
- `enabled`: 是否启用(true/false)
|
|
183
|
+
|
|
184
|
+
- **watermark**: 水印图片(预留功能,暂未实现)
|
|
185
|
+
- `path`: 水印文件路径
|
|
186
|
+
- `enabled`: 是否启用(true/false)
|
|
187
|
+
|
|
188
|
+
#### batchRules(批量规则)
|
|
189
|
+
|
|
190
|
+
数组形式,支持多个规则。每个规则包含:
|
|
191
|
+
|
|
192
|
+
- **pattern**: 文件匹配模式(支持 glob 语法)
|
|
193
|
+
- `*.mp4`: 匹配所有 mp4 文件
|
|
194
|
+
- `special_*.mp4`: 匹配以 special_ 开头的 mp4 文件
|
|
195
|
+
- `**/*.mp4`: 递归匹配所有子目录的 mp4 文件
|
|
196
|
+
|
|
197
|
+
- **applyIntro**: 是否应用片头(true/false)
|
|
198
|
+
|
|
199
|
+
- **applyOutro**: 是否应用片尾(true/false)
|
|
200
|
+
|
|
201
|
+
- **outputDir**: 输出目录路径
|
|
202
|
+
|
|
203
|
+
#### output(输出配置)
|
|
204
|
+
|
|
205
|
+
- **format**: 输出格式(如:mp4)
|
|
206
|
+
- **codec**: 编码器(如:libx264)
|
|
207
|
+
- **quality**: 质量设置(如:high, medium, low)
|
|
208
|
+
|
|
209
|
+
### 配置文件示例
|
|
210
|
+
|
|
211
|
+
**示例 1:简单批量处理**
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"materials": {
|
|
215
|
+
"intro": {
|
|
216
|
+
"path": "./assets/intro.mp4",
|
|
217
|
+
"enabled": true
|
|
218
|
+
},
|
|
219
|
+
"outro": {
|
|
220
|
+
"path": "./assets/outro.mp4",
|
|
221
|
+
"enabled": true
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
"batchRules": [
|
|
225
|
+
{
|
|
226
|
+
"pattern": "*.mp4",
|
|
227
|
+
"applyIntro": true,
|
|
228
|
+
"applyOutro": true,
|
|
229
|
+
"outputDir": "./output"
|
|
230
|
+
}
|
|
231
|
+
],
|
|
232
|
+
"output": {
|
|
233
|
+
"format": "mp4",
|
|
234
|
+
"codec": "libx264",
|
|
235
|
+
"quality": "high"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**示例 2:多规则处理**
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"materials": {
|
|
244
|
+
"intro": {
|
|
245
|
+
"path": "./assets/intro.mp4",
|
|
246
|
+
"enabled": true
|
|
247
|
+
},
|
|
248
|
+
"outro": {
|
|
249
|
+
"path": "./assets/outro.mp4",
|
|
250
|
+
"enabled": true
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
"batchRules": [
|
|
254
|
+
{
|
|
255
|
+
"pattern": "*.mp4",
|
|
256
|
+
"applyIntro": true,
|
|
257
|
+
"applyOutro": true,
|
|
258
|
+
"outputDir": "./output"
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"pattern": "preview_*.mp4",
|
|
262
|
+
"applyIntro": false,
|
|
263
|
+
"applyOutro": false,
|
|
264
|
+
"outputDir": "./output/preview"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"pattern": "final_*.mp4",
|
|
268
|
+
"applyIntro": true,
|
|
269
|
+
"applyOutro": true,
|
|
270
|
+
"outputDir": "./output/final"
|
|
271
|
+
}
|
|
272
|
+
],
|
|
273
|
+
"output": {
|
|
274
|
+
"format": "mp4",
|
|
275
|
+
"codec": "libx264",
|
|
276
|
+
"quality": "high"
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## 命令参考
|
|
282
|
+
|
|
283
|
+
### vvm merge
|
|
284
|
+
|
|
285
|
+
合并两个视频文件。
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
vvm merge <video1> <video2> [output] [options]
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**选项**:
|
|
292
|
+
- `-p, --position <position>`: 合并位置(start/end),默认:end
|
|
293
|
+
|
|
294
|
+
**示例**:
|
|
295
|
+
```bash
|
|
296
|
+
vvm merge video1.mp4 video2.mp4 merged.mp4
|
|
297
|
+
vvm merge video1.mp4 video2.mp4 merged.mp4 -p start
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### vvm batch
|
|
301
|
+
|
|
302
|
+
批量处理目录下的视频文件。
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
vvm batch [inputDir] [options]
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**选项**:
|
|
309
|
+
- `-i, --intro <path>`: 片头视频路径
|
|
310
|
+
- `-o, --outro <path>`: 片尾视频路径
|
|
311
|
+
- `-d, --dir <path>`: 输出目录路径
|
|
312
|
+
- `-c, --config <path>`: 配置文件路径
|
|
313
|
+
|
|
314
|
+
**示例**:
|
|
315
|
+
```bash
|
|
316
|
+
vvm batch ./videos -i intro.mp4 -d output
|
|
317
|
+
vvm batch ./videos -o outro.mp4 -d output
|
|
318
|
+
vvm batch ./videos -i intro.mp4 -o outro.mp4 -d output
|
|
319
|
+
vvm batch -c config.json
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### vvm init
|
|
323
|
+
|
|
324
|
+
创建配置文件模板。
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
vvm init [options]
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**选项**:
|
|
331
|
+
- `-f, --file <path>`: 配置文件路径,默认:vvm.config.json
|
|
332
|
+
|
|
333
|
+
**示例**:
|
|
334
|
+
```bash
|
|
335
|
+
vvm init
|
|
336
|
+
vvm init -f my-config.json
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### vvm --help
|
|
340
|
+
|
|
341
|
+
显示帮助信息。
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
vvm --help
|
|
345
|
+
vvm merge --help
|
|
346
|
+
vvm batch --help
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### vvm --version
|
|
350
|
+
|
|
351
|
+
显示版本信息。
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
vvm --version
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## 使用场景
|
|
358
|
+
|
|
359
|
+
### 场景 1:为视频添加统一片头
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
vvm batch ./my_videos -i ./assets/intro.mp4 -d ./output
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### 场景 2:为视频添加片尾
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
vvm batch ./my_videos -o ./assets/outro.mp4 -d ./output
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### 场景 3:为不同类型的视频应用不同规则
|
|
372
|
+
|
|
373
|
+
使用配置文件 `config.json`:
|
|
374
|
+
```json
|
|
375
|
+
{
|
|
376
|
+
"materials": {
|
|
377
|
+
"intro": {
|
|
378
|
+
"path": "./assets/intro.mp4",
|
|
379
|
+
"enabled": true
|
|
380
|
+
},
|
|
381
|
+
"outro": {
|
|
382
|
+
"path": "./assets/outro.mp4",
|
|
383
|
+
"enabled": true
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
"batchRules": [
|
|
387
|
+
{
|
|
388
|
+
"pattern": "tutorial_*.mp4",
|
|
389
|
+
"applyIntro": true,
|
|
390
|
+
"applyOutro": true,
|
|
391
|
+
"outputDir": "./output/tutorials"
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"pattern": "demo_*.mp4",
|
|
395
|
+
"applyIntro": true,
|
|
396
|
+
"applyOutro": false,
|
|
397
|
+
"outputDir": "./output/demos"
|
|
398
|
+
}
|
|
399
|
+
],
|
|
400
|
+
"output": {
|
|
401
|
+
"format": "mp4",
|
|
402
|
+
"codec": "libx264",
|
|
403
|
+
"quality": "high"
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
执行:
|
|
409
|
+
```bash
|
|
410
|
+
vvm batch -c config.json
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### 场景 4:合并两个视频
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
vvm merge part1.mp4 part2.mp4 full_video.mp4
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## 工作原理
|
|
420
|
+
|
|
421
|
+
1. **视频合并**:使用 FFmpeg 的 `concat` 功能将两个视频合并为一个
|
|
422
|
+
2. **批量处理**:使用 glob 模式匹配文件,逐个处理
|
|
423
|
+
3. **片头/片尾**:通过控制合并顺序实现(添加到开头或结尾)
|
|
424
|
+
4. **临时文件**:同时添加片头和片尾时,会创建临时文件进行中间处理
|
|
425
|
+
5. **统一编码参数**:在合并前,系统会自动将所有视频重新编码为统一的参数(分辨率、帧率、编码格式等),确保视频兼容性和播放质量
|
|
426
|
+
6. **时间戳修复**:使用 `-fflags +genpts` 和 `-avoid_negative_ts` 参数修复视频时间戳问题,支持拖动进度条
|
|
427
|
+
|
|
428
|
+
## 注意事项
|
|
429
|
+
|
|
430
|
+
1. **FFmpeg 要求**:必须安装 FFmpeg 并添加到系统环境变量
|
|
431
|
+
2. **文件路径**:建议使用绝对路径,或确保相对路径正确
|
|
432
|
+
3. **输出目录**:输出目录不存在时会自动创建
|
|
433
|
+
4. **文件格式**:建议使用相同的视频格式和编码器
|
|
434
|
+
5. **临时文件**:处理完成后会自动删除临时文件
|
|
435
|
+
6. **磁盘空间**:确保有足够的磁盘空间存储输出文件
|
|
436
|
+
7. **视频编码**:系统会自动统一视频编码参数,处理时间可能较长
|
|
437
|
+
8. **播放器兼容性**:生成的视频支持所有主流播放器(VLC、Windows Media Player、PopPlayer 等)
|
|
438
|
+
9. **进度条支持**:生成的视频支持拖动进度条和时间跳转
|
|
439
|
+
|
|
440
|
+
## 故障排除
|
|
441
|
+
|
|
442
|
+
### 问题 1:提示 "FFmpeg not found"
|
|
443
|
+
|
|
444
|
+
**解决方案**:
|
|
445
|
+
- 检查 FFmpeg 是否已安装:`ffmpeg -version`
|
|
446
|
+
- 确保 FFmpeg 已添加到系统环境变量
|
|
447
|
+
|
|
448
|
+
### 问题 2:合并后的视频质量下降
|
|
449
|
+
|
|
450
|
+
**解决方案**:
|
|
451
|
+
- 系统会自动统一编码参数,确保质量
|
|
452
|
+
- 如果仍然不满意,可以调整源视频的质量设置
|
|
453
|
+
|
|
454
|
+
### 问题 3:批量处理速度慢
|
|
455
|
+
|
|
456
|
+
**解决方案**:
|
|
457
|
+
- 由于需要重新编码视频,处理时间较长是正常的
|
|
458
|
+
- 减少同时处理的视频数量
|
|
459
|
+
- 确保系统有足够的 CPU 和内存资源
|
|
460
|
+
|
|
461
|
+
### 问题 4:某些视频无法合并
|
|
462
|
+
|
|
463
|
+
**解决方案**:
|
|
464
|
+
- 系统会自动统一编码参数,大多数视频都能正常合并
|
|
465
|
+
- 如果仍然失败,使用 FFprobe 检查视频信息:`ffprobe -i video.mp4`
|
|
466
|
+
- 确保视频文件没有损坏
|
|
467
|
+
|
|
468
|
+
### 问题 5:合并后的视频无法拖动进度条
|
|
469
|
+
|
|
470
|
+
**解决方案**:
|
|
471
|
+
- 系统已自动修复时间戳问题,支持拖动进度条
|
|
472
|
+
- 如果仍然有问题,请尝试使用 VLC 播放器
|
|
473
|
+
- 确保视频文件已完全生成(检查文件大小是否正常)
|
|
474
|
+
|
|
475
|
+
### 问题 6:合并后的视频画面不显示
|
|
476
|
+
|
|
477
|
+
**解决方案**:
|
|
478
|
+
- 系统已统一编码参数,确保画面正常显示
|
|
479
|
+
- 如果仍然看不到画面,请尝试使用 VLC 或 Windows Media Player
|
|
480
|
+
- 检查视频文件是否完整(文件大小应该大于几 MB)
|
|
481
|
+
|
|
482
|
+
## 开发
|
|
483
|
+
|
|
484
|
+
### 项目结构
|
|
485
|
+
|
|
486
|
+
```
|
|
487
|
+
vvm/
|
|
488
|
+
├── bin/
|
|
489
|
+
│ └── vvm # CLI 入口文件
|
|
490
|
+
├── src/
|
|
491
|
+
│ ├── index.ts # 主入口
|
|
492
|
+
│ ├── merger.ts # 视频合并核心逻辑
|
|
493
|
+
│ └── batch.ts # 批量处理逻辑
|
|
494
|
+
├── dist/ # 编译输出目录
|
|
495
|
+
├── package.json
|
|
496
|
+
├── tsconfig.json
|
|
497
|
+
└── README.md
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### 开发命令
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
# 安装依赖
|
|
504
|
+
npm install
|
|
505
|
+
|
|
506
|
+
# 开发模式
|
|
507
|
+
npm run dev
|
|
508
|
+
|
|
509
|
+
# 构建项目
|
|
510
|
+
npm run build
|
|
511
|
+
|
|
512
|
+
# 链接到全局
|
|
513
|
+
npm link
|
|
514
|
+
|
|
515
|
+
# 发布到 npm
|
|
516
|
+
npm publish
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## 许可证
|
|
520
|
+
|
|
521
|
+
MIT
|
|
522
|
+
|
|
523
|
+
## 贡献
|
|
524
|
+
|
|
525
|
+
欢迎提交 Issue 和 Pull Request!
|
|
526
|
+
|
|
527
|
+
## 更新日志
|
|
528
|
+
|
|
529
|
+
### 1.0.0 (2026-01-07)
|
|
530
|
+
|
|
531
|
+
- ✨ 初始版本发布
|
|
532
|
+
- ✅ 支持视频合并功能
|
|
533
|
+
- ✅ 支持批量处理
|
|
534
|
+
- ✅ 支持配置文件
|
|
535
|
+
- ✅ 支持片头/片尾独立配置
|
|
536
|
+
- ✅ 支持规则匹配
|
|
537
|
+
- 🔧 统一视频编码参数,确保合并兼容性
|
|
538
|
+
- 🔧 修复视频时间戳问题,支持拖动进度条
|
|
539
|
+
- 🔧 优化输出文件名,与原文件名保持一致
|
|
540
|
+
- 🔧 使用 concat demuxer 提高合并效率
|
|
541
|
+
|
|
542
|
+
## 联系方式
|
|
543
|
+
|
|
544
|
+
如有问题或建议,欢迎通过以下方式联系:
|
|
545
|
+
|
|
546
|
+
- **微信**:w846903522
|
|
547
|
+
- **邮箱**:yeah126139163@163.com
|
|
548
|
+
- **个人主页**:https://blog.zbztb.cn
|
|
549
|
+
- **GitHub Issues**:提交 Issue 反馈问题
|
package/bin/vvm
ADDED
package/dist/batch.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface BatchOptions {
|
|
2
|
+
inputDir: string;
|
|
3
|
+
intro?: string;
|
|
4
|
+
outro?: string;
|
|
5
|
+
outputDir?: string;
|
|
6
|
+
pattern?: string;
|
|
7
|
+
codec?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ConfigFile {
|
|
10
|
+
materials: {
|
|
11
|
+
intro?: {
|
|
12
|
+
path: string;
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
};
|
|
15
|
+
outro?: {
|
|
16
|
+
path: string;
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
};
|
|
19
|
+
watermark?: {
|
|
20
|
+
path: string;
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
batchRules?: Array<{
|
|
25
|
+
pattern: string;
|
|
26
|
+
applyIntro: boolean;
|
|
27
|
+
applyOutro: boolean;
|
|
28
|
+
outputDir: string;
|
|
29
|
+
}>;
|
|
30
|
+
output?: {
|
|
31
|
+
format: string;
|
|
32
|
+
codec: string;
|
|
33
|
+
quality: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export declare class BatchProcessor {
|
|
37
|
+
private merger;
|
|
38
|
+
constructor();
|
|
39
|
+
/**
|
|
40
|
+
* 批量处理目录下的视频
|
|
41
|
+
*/
|
|
42
|
+
processBatch(options: BatchOptions): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* 从配置文件批量处理
|
|
45
|
+
*/
|
|
46
|
+
processFromConfig(configPath: string): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=batch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE;QACT,KAAK,CAAC,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,OAAO,CAAA;SAAE,CAAC;QAC3C,KAAK,CAAC,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,OAAO,CAAA;SAAE,CAAC;QAC3C,SAAS,CAAC,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,OAAO,CAAA;SAAE,CAAC;KAChD,CAAC;IACF,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,OAAO,CAAC;QACpB,UAAU,EAAE,OAAO,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC,CAAC;IACH,MAAM,CAAC,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAc;;IAM5B;;OAEG;IACG,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAuFxD;;OAEG;IACG,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA2E3D"}
|
package/dist/batch.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BatchProcessor = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const glob_1 = require("glob");
|
|
10
|
+
const merger_1 = require("./merger");
|
|
11
|
+
const ora_1 = __importDefault(require("ora"));
|
|
12
|
+
class BatchProcessor {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.merger = new merger_1.VideoMerger();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 批量处理目录下的视频
|
|
18
|
+
*/
|
|
19
|
+
async processBatch(options) {
|
|
20
|
+
const { inputDir, intro, outro, outputDir = path_1.default.join(inputDir, 'output'), pattern = '**/*.mp4', codec = 'libx264', } = options;
|
|
21
|
+
// 验证输入目录
|
|
22
|
+
if (!fs_1.default.existsSync(inputDir)) {
|
|
23
|
+
throw new Error(`输入目录不存在: ${inputDir}`);
|
|
24
|
+
}
|
|
25
|
+
// 验证素材文件
|
|
26
|
+
if (intro && !fs_1.default.existsSync(intro)) {
|
|
27
|
+
throw new Error(`片头文件不存在: ${intro}`);
|
|
28
|
+
}
|
|
29
|
+
if (outro && !fs_1.default.existsSync(outro)) {
|
|
30
|
+
throw new Error(`片尾文件不存在: ${outro}`);
|
|
31
|
+
}
|
|
32
|
+
// 创建输出目录
|
|
33
|
+
if (!fs_1.default.existsSync(outputDir)) {
|
|
34
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
// 查找所有视频文件
|
|
37
|
+
const spinner = (0, ora_1.default)('正在查找视频文件...').start();
|
|
38
|
+
const videoFiles = await (0, glob_1.glob)(pattern, {
|
|
39
|
+
cwd: inputDir,
|
|
40
|
+
absolute: true,
|
|
41
|
+
});
|
|
42
|
+
if (videoFiles.length === 0) {
|
|
43
|
+
spinner.fail('没有找到视频文件');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
spinner.succeed(`找到 ${videoFiles.length} 个视频文件`);
|
|
47
|
+
// 批量处理
|
|
48
|
+
const processSpinner = (0, ora_1.default)('开始批量处理...').start();
|
|
49
|
+
let successCount = 0;
|
|
50
|
+
let failCount = 0;
|
|
51
|
+
for (const videoFile of videoFiles) {
|
|
52
|
+
const fileName = path_1.default.basename(videoFile, path_1.default.extname(videoFile));
|
|
53
|
+
const outputFile = path_1.default.join(outputDir, `${fileName}.mp4`);
|
|
54
|
+
try {
|
|
55
|
+
// 如果有片头和片尾,需要两次处理
|
|
56
|
+
if (intro && outro) {
|
|
57
|
+
// 使用时间戳生成唯一的临时文件名,避免中文文件名问题
|
|
58
|
+
const tempFile = path_1.default.join(outputDir, `temp_merge_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.mp4`);
|
|
59
|
+
// 先添加片头
|
|
60
|
+
await this.merger.addIntroOrOutro(videoFile, intro, 'start', tempFile, codec);
|
|
61
|
+
// 再添加片尾
|
|
62
|
+
await this.merger.addIntroOrOutro(tempFile, outro, 'end', outputFile, codec);
|
|
63
|
+
// 删除临时文件
|
|
64
|
+
if (fs_1.default.existsSync(tempFile)) {
|
|
65
|
+
fs_1.default.unlinkSync(tempFile);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (intro) {
|
|
69
|
+
// 只添加片头
|
|
70
|
+
await this.merger.addIntroOrOutro(videoFile, intro, 'start', outputFile, codec);
|
|
71
|
+
}
|
|
72
|
+
else if (outro) {
|
|
73
|
+
// 只添加片尾
|
|
74
|
+
await this.merger.addIntroOrOutro(videoFile, outro, 'end', outputFile, codec);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
throw new Error('至少需要指定片头或片尾');
|
|
78
|
+
}
|
|
79
|
+
successCount++;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error(`\n✗ 处理失败: ${fileName}`, error);
|
|
83
|
+
failCount++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
processSpinner.succeed(`批量处理完成! 成功: ${successCount}, 失败: ${failCount}`);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 从配置文件批量处理
|
|
90
|
+
*/
|
|
91
|
+
async processFromConfig(configPath) {
|
|
92
|
+
// 验证配置文件
|
|
93
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
94
|
+
throw new Error(`配置文件不存在: ${configPath}`);
|
|
95
|
+
}
|
|
96
|
+
const spinner = (0, ora_1.default)('正在加载配置文件...').start();
|
|
97
|
+
const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
|
|
98
|
+
spinner.succeed('配置文件加载成功');
|
|
99
|
+
const { materials, batchRules, output } = config;
|
|
100
|
+
// 默认配置
|
|
101
|
+
const defaultCodec = output?.codec || 'libx264';
|
|
102
|
+
// 如果有批量规则,按规则处理
|
|
103
|
+
if (batchRules && batchRules.length > 0) {
|
|
104
|
+
for (const rule of batchRules) {
|
|
105
|
+
const intro = materials.intro?.enabled ? materials.intro.path : undefined;
|
|
106
|
+
const outro = materials.outro?.enabled ? materials.outro.path : undefined;
|
|
107
|
+
if (rule.applyIntro && !intro) {
|
|
108
|
+
console.warn(`警告: 规则 "${rule.pattern}" 要求添加片头,但片头未启用或未配置`);
|
|
109
|
+
}
|
|
110
|
+
if (rule.applyOutro && !outro) {
|
|
111
|
+
console.warn(`警告: 规则 "${rule.pattern}" 要求添加片尾,但片尾未启用或未配置`);
|
|
112
|
+
}
|
|
113
|
+
// 获取匹配的文件
|
|
114
|
+
const files = await (0, glob_1.glob)(rule.pattern, {
|
|
115
|
+
absolute: true,
|
|
116
|
+
cwd: process.cwd(),
|
|
117
|
+
});
|
|
118
|
+
if (files.length === 0) {
|
|
119
|
+
console.log(`\n没有文件匹配规则: ${rule.pattern}`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
console.log(`\n处理规则: ${rule.pattern} (${files.length} 个文件)`);
|
|
123
|
+
// 创建输出目录
|
|
124
|
+
if (!fs_1.default.existsSync(rule.outputDir)) {
|
|
125
|
+
fs_1.default.mkdirSync(rule.outputDir, { recursive: true });
|
|
126
|
+
}
|
|
127
|
+
// 处理每个文件
|
|
128
|
+
for (const file of files) {
|
|
129
|
+
const fileName = path_1.default.basename(file, path_1.default.extname(file));
|
|
130
|
+
const outputFile = path_1.default.join(rule.outputDir, `${fileName}_merged.mp4`);
|
|
131
|
+
try {
|
|
132
|
+
if (rule.applyIntro && rule.applyOutro && intro && outro) {
|
|
133
|
+
const tempFile = path_1.default.join(rule.outputDir, `temp_${fileName}.mp4`);
|
|
134
|
+
await this.merger.addIntroOrOutro(file, intro, 'start', tempFile, defaultCodec);
|
|
135
|
+
await this.merger.addIntroOrOutro(tempFile, outro, 'end', outputFile, defaultCodec);
|
|
136
|
+
if (fs_1.default.existsSync(tempFile)) {
|
|
137
|
+
fs_1.default.unlinkSync(tempFile);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (rule.applyIntro && intro) {
|
|
141
|
+
await this.merger.addIntroOrOutro(file, intro, 'start', outputFile, defaultCodec);
|
|
142
|
+
}
|
|
143
|
+
else if (rule.applyOutro && outro) {
|
|
144
|
+
await this.merger.addIntroOrOutro(file, outro, 'end', outputFile, defaultCodec);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
console.log(`跳过文件: ${fileName} (没有应用任何素材)`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error(`✗ 处理失败: ${fileName}`, error);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
throw new Error('配置文件中没有定义批量规则');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
exports.BatchProcessor = BatchProcessor;
|
|
162
|
+
//# sourceMappingURL=batch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch.js","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const merger_1 = require("./merger");
|
|
10
|
+
const batch_1 = require("./batch");
|
|
11
|
+
const program = new commander_1.Command();
|
|
12
|
+
// 设置版本和描述
|
|
13
|
+
program
|
|
14
|
+
.name('vvm')
|
|
15
|
+
.description('视频合并 CLI 工具 - 支持添加片头、片尾和批量处理')
|
|
16
|
+
.version('1.0.0');
|
|
17
|
+
// 合并两个视频命令
|
|
18
|
+
program
|
|
19
|
+
.command('merge')
|
|
20
|
+
.description('合并两个视频')
|
|
21
|
+
.argument('<video1>', '第一个视频文件路径')
|
|
22
|
+
.argument('<video2>', '第二个视频文件路径')
|
|
23
|
+
.argument('[output]', '输出文件路径', 'output.mp4')
|
|
24
|
+
.option('-p, --position <position>', '合并位置: start 或 end', 'end')
|
|
25
|
+
.action(async (video1, video2, output, options) => {
|
|
26
|
+
try {
|
|
27
|
+
console.log(chalk_1.default.blue('开始合并视频...'));
|
|
28
|
+
console.log(` 视频1: ${video1}`);
|
|
29
|
+
console.log(` 视频2: ${video2}`);
|
|
30
|
+
console.log(` 位置: ${options.position}`);
|
|
31
|
+
console.log(` 输出: ${output}\n`);
|
|
32
|
+
const merger = new merger_1.VideoMerger();
|
|
33
|
+
await merger.merge({
|
|
34
|
+
video1,
|
|
35
|
+
video2,
|
|
36
|
+
position: options.position,
|
|
37
|
+
output,
|
|
38
|
+
});
|
|
39
|
+
console.log(chalk_1.default.green('✓ 合并完成!'));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error(chalk_1.default.red('✗ 错误:'), error);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
// 批量处理命令
|
|
47
|
+
program
|
|
48
|
+
.command('batch')
|
|
49
|
+
.description('批量处理目录下的视频')
|
|
50
|
+
.argument('[inputDir]', '输入目录路径')
|
|
51
|
+
.option('-i, --intro <path>', '片头视频路径')
|
|
52
|
+
.option('-o, --outro <path>', '片尾视频路径')
|
|
53
|
+
.option('-d, --dir <path>', '输出目录路径')
|
|
54
|
+
.option('-c, --config <path>', '配置文件路径')
|
|
55
|
+
.action(async (inputDir, options) => {
|
|
56
|
+
try {
|
|
57
|
+
// 如果指定了配置文件,使用配置文件
|
|
58
|
+
if (options.config) {
|
|
59
|
+
console.log(chalk_1.default.blue('使用配置文件批量处理...'));
|
|
60
|
+
console.log(` 配置文件: ${options.config}\n`);
|
|
61
|
+
const processor = new batch_1.BatchProcessor();
|
|
62
|
+
await processor.processFromConfig(options.config);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// 否则使用命令行参数
|
|
66
|
+
if (!inputDir) {
|
|
67
|
+
console.error(chalk_1.default.red('✗ 错误: 必须指定输入目录或配置文件'));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
if (!options.intro && !options.outro) {
|
|
71
|
+
console.error(chalk_1.default.red('✗ 错误: 至少需要指定片头或片尾'));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
console.log(chalk_1.default.blue('开始批量处理视频...'));
|
|
75
|
+
console.log(` 输入目录: ${inputDir}`);
|
|
76
|
+
if (options.intro)
|
|
77
|
+
console.log(` 片头: ${options.intro}`);
|
|
78
|
+
if (options.outro)
|
|
79
|
+
console.log(` 片尾: ${options.outro}`);
|
|
80
|
+
if (options.dir)
|
|
81
|
+
console.log(` 输出目录: ${options.dir}\n`);
|
|
82
|
+
const processor = new batch_1.BatchProcessor();
|
|
83
|
+
await processor.processBatch({
|
|
84
|
+
inputDir,
|
|
85
|
+
intro: options.intro,
|
|
86
|
+
outro: options.outro,
|
|
87
|
+
outputDir: options.dir,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
console.log(chalk_1.default.green('\n✓ 批量处理完成!'));
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error(chalk_1.default.red('✗ 错误:'), error);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
// 创建配置文件模板命令
|
|
98
|
+
program
|
|
99
|
+
.command('init')
|
|
100
|
+
.description('创建配置文件模板')
|
|
101
|
+
.option('-f, --file <path>', '配置文件路径', 'vvm.config.json')
|
|
102
|
+
.action(async (options) => {
|
|
103
|
+
const configTemplate = {
|
|
104
|
+
materials: {
|
|
105
|
+
intro: {
|
|
106
|
+
path: './assets/intro.mp4',
|
|
107
|
+
enabled: true,
|
|
108
|
+
},
|
|
109
|
+
outro: {
|
|
110
|
+
path: './assets/outro.mp4',
|
|
111
|
+
enabled: true,
|
|
112
|
+
},
|
|
113
|
+
watermark: {
|
|
114
|
+
path: './assets/watermark.png',
|
|
115
|
+
enabled: false,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
batchRules: [
|
|
119
|
+
{
|
|
120
|
+
pattern: '*.mp4',
|
|
121
|
+
applyIntro: true,
|
|
122
|
+
applyOutro: true,
|
|
123
|
+
outputDir: './output',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
pattern: 'special_*.mp4',
|
|
127
|
+
applyIntro: false,
|
|
128
|
+
applyOutro: true,
|
|
129
|
+
outputDir: './output/special',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
output: {
|
|
133
|
+
format: 'mp4',
|
|
134
|
+
codec: 'libx264',
|
|
135
|
+
quality: 'high',
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
const fs = require('fs');
|
|
139
|
+
fs.writeFileSync(options.file, JSON.stringify(configTemplate, null, 2));
|
|
140
|
+
console.log(chalk_1.default.green(`✓ 配置文件模板已创建: ${options.file}`));
|
|
141
|
+
});
|
|
142
|
+
// 解析命令行参数
|
|
143
|
+
program.parse(process.argv);
|
|
144
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/merger.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface MergeOptions {
|
|
2
|
+
video1: string;
|
|
3
|
+
video2: string;
|
|
4
|
+
position: 'start' | 'end';
|
|
5
|
+
output: string;
|
|
6
|
+
codec?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class VideoMerger {
|
|
9
|
+
/**
|
|
10
|
+
* 合并两个视频
|
|
11
|
+
*/
|
|
12
|
+
merge(options: MergeOptions): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* 添加片头或片尾到视频
|
|
15
|
+
*/
|
|
16
|
+
addIntroOrOutro(video: string, introOrOutro: string, position: 'start' | 'end', output: string, codec?: string): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* 验证文件是否存在
|
|
19
|
+
*/
|
|
20
|
+
private validateFile;
|
|
21
|
+
/**
|
|
22
|
+
* 获取视频信息
|
|
23
|
+
*/
|
|
24
|
+
getVideoInfo(filePath: string): Promise<any>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=merger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merger.d.ts","sourceRoot":"","sources":["../src/merger.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,GAAG,KAAK,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,WAAW;IACtB;;OAEG;IACG,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IA4IjD;;OAEG;IACG,eAAe,CACnB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,OAAO,GAAG,KAAK,EACzB,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAkB,GACxB,OAAO,CAAC,IAAI,CAAC;IAUhB;;OAEG;IACH,OAAO,CAAC,YAAY;IAMpB;;OAEG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;CAWnD"}
|
package/dist/merger.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.VideoMerger = void 0;
|
|
7
|
+
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
class VideoMerger {
|
|
11
|
+
/**
|
|
12
|
+
* 合并两个视频
|
|
13
|
+
*/
|
|
14
|
+
async merge(options) {
|
|
15
|
+
const { video1, video2, position, output, codec = 'libx264' } = options;
|
|
16
|
+
// 验证输入文件存在
|
|
17
|
+
this.validateFile(video1);
|
|
18
|
+
this.validateFile(video2);
|
|
19
|
+
// 确保输出目录存在
|
|
20
|
+
const outputDir = path_1.default.dirname(output);
|
|
21
|
+
if (!fs_1.default.existsSync(outputDir)) {
|
|
22
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
// 获取目标视频(video1)的分辨率和帧率
|
|
25
|
+
const video1Info = await this.getVideoInfo(video1);
|
|
26
|
+
const videoStream = video1Info.streams.find((s) => s.codec_type === 'video');
|
|
27
|
+
const audioStream = video1Info.streams.find((s) => s.codec_type === 'audio');
|
|
28
|
+
const targetWidth = videoStream?.width || 1920;
|
|
29
|
+
const targetHeight = videoStream?.height || 1080;
|
|
30
|
+
const targetFps = videoStream?.r_frame_rate || '30/1';
|
|
31
|
+
const targetSampleRate = audioStream?.sample_rate || 44100;
|
|
32
|
+
// 统一的编码参数
|
|
33
|
+
const commonEncodeOptions = [
|
|
34
|
+
'-pix_fmt', 'yuv420p',
|
|
35
|
+
'-movflags', '+faststart',
|
|
36
|
+
'-bf', '0',
|
|
37
|
+
'-flags', '+cgop',
|
|
38
|
+
'-sc_threshold', '0',
|
|
39
|
+
'-g', '30',
|
|
40
|
+
'-keyint_min', '30',
|
|
41
|
+
'-refs', '1',
|
|
42
|
+
'-level', '40',
|
|
43
|
+
'-r', '30',
|
|
44
|
+
'-ar', '44100',
|
|
45
|
+
'-ac', '2'
|
|
46
|
+
];
|
|
47
|
+
// 第一步:重新编码 video1(主视频)
|
|
48
|
+
const tempVideo1 = path_1.default.join(outputDir, `temp_v1_${Date.now()}.mp4`);
|
|
49
|
+
await new Promise((resolve, reject) => {
|
|
50
|
+
(0, fluent_ffmpeg_1.default)(video1)
|
|
51
|
+
.videoCodec(codec)
|
|
52
|
+
.audioCodec('aac')
|
|
53
|
+
.outputOptions(commonEncodeOptions)
|
|
54
|
+
.on('start', (commandLine) => {
|
|
55
|
+
console.log('FFmpeg command (re-encode video1):', commandLine);
|
|
56
|
+
})
|
|
57
|
+
.on('end', () => {
|
|
58
|
+
console.log(`✓ 主视频重新编码完成: ${tempVideo1}`);
|
|
59
|
+
resolve();
|
|
60
|
+
})
|
|
61
|
+
.on('error', (err) => {
|
|
62
|
+
console.error('✗ 主视频重新编码失败:', err.message);
|
|
63
|
+
reject(err);
|
|
64
|
+
})
|
|
65
|
+
.save(tempVideo1);
|
|
66
|
+
});
|
|
67
|
+
// 第二步:重新编码 video2(片头/片尾)并调整分辨率
|
|
68
|
+
const tempVideo2 = path_1.default.join(outputDir, `temp_v2_${Date.now()}.mp4`);
|
|
69
|
+
await new Promise((resolve, reject) => {
|
|
70
|
+
(0, fluent_ffmpeg_1.default)(video2)
|
|
71
|
+
.size(`${targetWidth}x${targetHeight}`)
|
|
72
|
+
.videoFilter(`scale=${targetWidth}:${targetHeight}:force_original_aspect_ratio=decrease,pad=${targetWidth}:${targetHeight}:(ow-iw)/2:(oh-ih)/2`)
|
|
73
|
+
.videoCodec(codec)
|
|
74
|
+
.audioCodec('aac')
|
|
75
|
+
.outputOptions(commonEncodeOptions)
|
|
76
|
+
.on('start', (commandLine) => {
|
|
77
|
+
console.log('FFmpeg command (re-encode video2):', commandLine);
|
|
78
|
+
})
|
|
79
|
+
.on('end', () => {
|
|
80
|
+
console.log(`✓ 片头/片尾重新编码完成: ${tempVideo2}`);
|
|
81
|
+
resolve();
|
|
82
|
+
})
|
|
83
|
+
.on('error', (err) => {
|
|
84
|
+
console.error('✗ 片头/片尾重新编码失败:', err.message);
|
|
85
|
+
reject(err);
|
|
86
|
+
})
|
|
87
|
+
.save(tempVideo2);
|
|
88
|
+
});
|
|
89
|
+
// 第三步:使用 concat demuxer 合并两个视频
|
|
90
|
+
const concatListPath = path_1.default.join(outputDir, `concat_${Date.now()}.txt`);
|
|
91
|
+
const videos = position === 'start' ? [tempVideo2, tempVideo1] : [tempVideo1, tempVideo2];
|
|
92
|
+
const concatListContent = videos.map(v => {
|
|
93
|
+
const absPath = path_1.default.resolve(v);
|
|
94
|
+
return `file '${absPath.replace(/\\/g, '/')}'`;
|
|
95
|
+
}).join('\n');
|
|
96
|
+
fs_1.default.writeFileSync(concatListPath, concatListContent);
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
(0, fluent_ffmpeg_1.default)()
|
|
99
|
+
.input(concatListPath)
|
|
100
|
+
.inputOptions('-f', 'concat', '-safe', '0')
|
|
101
|
+
.on('start', (commandLine) => {
|
|
102
|
+
console.log('FFmpeg command (concat demuxer):', commandLine);
|
|
103
|
+
})
|
|
104
|
+
.on('end', () => {
|
|
105
|
+
console.log(`✓ 视频合并成功: ${output}`);
|
|
106
|
+
// 清理临时文件
|
|
107
|
+
if (fs_1.default.existsSync(tempVideo1)) {
|
|
108
|
+
fs_1.default.unlinkSync(tempVideo1);
|
|
109
|
+
}
|
|
110
|
+
if (fs_1.default.existsSync(tempVideo2)) {
|
|
111
|
+
fs_1.default.unlinkSync(tempVideo2);
|
|
112
|
+
}
|
|
113
|
+
if (fs_1.default.existsSync(concatListPath)) {
|
|
114
|
+
fs_1.default.unlinkSync(concatListPath);
|
|
115
|
+
}
|
|
116
|
+
resolve();
|
|
117
|
+
})
|
|
118
|
+
.on('error', (err) => {
|
|
119
|
+
console.error('✗ 视频合并失败:', err.message);
|
|
120
|
+
// 清理临时文件
|
|
121
|
+
if (fs_1.default.existsSync(tempVideo1)) {
|
|
122
|
+
fs_1.default.unlinkSync(tempVideo1);
|
|
123
|
+
}
|
|
124
|
+
if (fs_1.default.existsSync(tempVideo2)) {
|
|
125
|
+
fs_1.default.unlinkSync(tempVideo2);
|
|
126
|
+
}
|
|
127
|
+
if (fs_1.default.existsSync(concatListPath)) {
|
|
128
|
+
fs_1.default.unlinkSync(concatListPath);
|
|
129
|
+
}
|
|
130
|
+
reject(err);
|
|
131
|
+
})
|
|
132
|
+
.output(output)
|
|
133
|
+
.videoCodec('copy')
|
|
134
|
+
.audioCodec('copy')
|
|
135
|
+
.outputOptions([
|
|
136
|
+
'-fflags', '+genpts',
|
|
137
|
+
'-avoid_negative_ts', 'make_zero'
|
|
138
|
+
])
|
|
139
|
+
.run();
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 添加片头或片尾到视频
|
|
144
|
+
*/
|
|
145
|
+
async addIntroOrOutro(video, introOrOutro, position, output, codec = 'libx264') {
|
|
146
|
+
return this.merge({
|
|
147
|
+
video1: video,
|
|
148
|
+
video2: introOrOutro,
|
|
149
|
+
position,
|
|
150
|
+
output,
|
|
151
|
+
codec,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 验证文件是否存在
|
|
156
|
+
*/
|
|
157
|
+
validateFile(filePath) {
|
|
158
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
159
|
+
throw new Error(`文件不存在: ${filePath}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 获取视频信息
|
|
164
|
+
*/
|
|
165
|
+
async getVideoInfo(filePath) {
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
fluent_ffmpeg_1.default.ffprobe(filePath, (err, metadata) => {
|
|
168
|
+
if (err) {
|
|
169
|
+
reject(err);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
resolve(metadata);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
exports.VideoMerger = VideoMerger;
|
|
179
|
+
//# sourceMappingURL=merger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merger.js","sourceRoot":"","sources":["../src/merger.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yeah126139163/video-merger-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A CLI tool for merging videos with intro/outro using ffmpeg",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vvm": "./bin/vvm"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "ts-node src/index.ts",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"prepare": "npm run build",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"video",
|
|
18
|
+
"merge",
|
|
19
|
+
"ffmpeg",
|
|
20
|
+
"cli",
|
|
21
|
+
"intro",
|
|
22
|
+
"outro",
|
|
23
|
+
"batch",
|
|
24
|
+
"video-processing"
|
|
25
|
+
],
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"commander": "^12.1.0",
|
|
30
|
+
"fluent-ffmpeg": "^2.1.2",
|
|
31
|
+
"chalk": "^5.3.0",
|
|
32
|
+
"glob": "^10.3.10",
|
|
33
|
+
"ora": "^8.0.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/fluent-ffmpeg": "^2.1.24",
|
|
37
|
+
"@types/node": "^20.10.6",
|
|
38
|
+
"typescript": "^5.3.3",
|
|
39
|
+
"ts-node": "^10.9.2"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=16.0.0"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"bin",
|
|
46
|
+
"dist",
|
|
47
|
+
"README.md"
|
|
48
|
+
]
|
|
49
|
+
}
|