@evio/ffai 1.0.4 → 1.0.5

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.
Files changed (3) hide show
  1. package/README.md +375 -27
  2. package/dist/task.js +26 -6
  3. package/package.json +7 -4
package/README.md CHANGED
@@ -101,26 +101,46 @@ ffai build task.json
101
101
  00:05-00:10 这是第一条字幕
102
102
  00:12-00:18 这是第二条字幕
103
103
  00:20-00:25 支持中文和英文
104
+ 01:30-01:45 时间可以跨分钟
104
105
  ```
105
106
 
106
- 格式说明:
107
+ **格式说明:**
107
108
  - 时间格式:`MM:SS-MM:SS 字幕内容`
108
109
  - 开始时间和结束时间用 `-` 分隔
109
110
  - 时间和文本之间用空格分隔
111
+ - 支持跨分钟的时间范围
112
+
113
+ **字幕样式:**
114
+ - 字体大小:36px
115
+ - 字体颜色:白色
116
+ - 文字效果:黑色描边 + 阴影,确保在任何背景下清晰可见
117
+ - 位置:视频底部居中(距底部 300px)
118
+ - 字体:macOS 使用宋体,其他系统需在 `src/task.ts` 中修改 `fontfile` 变量
110
119
 
111
120
  ## 🔄 工作流程
112
121
 
113
122
  工具会按以下步骤自动处理每个任务:
114
123
 
115
124
  1. **检查 FFmpeg** - 验证 FFmpeg 是否可用
116
- 2. **解析配置** - 读取任务配置文件
117
- 3. **视频转码**(可选)- 将视频统一转码为 MP4 格式(H.264 + AAC)
118
- 4. **音频混音** - 将多个音频文件混合为一个音轨
119
- 5. **视频选择** - 根据模式选择视频片段
120
- 6. **视频拼接** - 将选中的视频片段拼接成完整视频
121
- 7. **添加音频** - 为视频添加混音后的背景音频
122
- 8. **渲染字幕** - 将字幕渲染到视频上
123
- 9. **输出成品** - 生成最终视频文件
125
+ 2. **解析配置** - 读取任务配置文件,支持数组形式的多任务
126
+ 3. **解析素材文件** - 根据 glob 模式或数组匹配音频和视频文件
127
+ 4. **视频转码**(可选)- 将视频统一转码为 MP4 格式(H.264 + AAC,44.1kHz,立体声)
128
+ 5. **获取时长信息** - 分析所有音频和视频的播放时长
129
+ 6. **音频混音** - 将多个音频文件混合为一个音轨(以第一个音频时长为准)
130
+ 7. **视频选择** - 根据 `videoPackMode` 选择视频片段:
131
+ - `random` 模式:随机选择视频直到总时长 ≥ 音频时长
132
+ - `all` 模式:使用所有视频文件
133
+ 8. **视频拼接** - 将选中的视频片段拼接成完整视频
134
+ 9. **添加音频** - 为视频添加混音后的背景音频(使用 `-shortest` 参数自动对齐)
135
+ 10. **渲染字幕** - 将字幕渲染到视频上(白色文字 + 黑色描边阴影)
136
+ 11. **清理临时文件** - 删除中间过程文件,保留最终成品
137
+ 12. **输出成品** - 生成最终视频文件到 `task-{index}/video.mp4`
138
+
139
+ **处理时间估算:**
140
+ - 视频转码:取决于视频数量和时长,通常为实际播放时长的 0.5-2 倍
141
+ - 音频混音:通常在几秒内完成
142
+ - 视频拼接:较快,通常在 10 秒内
143
+ - 字幕渲染:取决于视频时长,通常为实际播放时长的 1-3 倍
124
144
 
125
145
  ## 📂 输出结构
126
146
 
@@ -153,11 +173,15 @@ task-0/
153
173
  ]
154
174
  ```
155
175
 
156
- 这个配置会:
157
- - 混合两个背景音乐
176
+ **这个配置会:**
177
+ - 混合两个背景音乐(以 `music1.mp3` 的时长为准)
158
178
  - 从 `clips/` 目录随机选择视频片段,直到总时长匹配音频
159
- - 转码所有视频为统一格式
160
- - 添加字幕
179
+ - 转码所有视频为统一格式(H.264/AAC)
180
+ - 添加字幕并输出到 `task-0/video.mp4`
181
+
182
+ **适用场景:** 制作短视频、Vlog、产品宣传片等需要随机素材组合的场景
183
+
184
+ ---
161
185
 
162
186
  ### 示例 2:使用所有视频 + Glob 模式
163
187
 
@@ -172,10 +196,15 @@ task-0/
172
196
  ]
173
197
  ```
174
198
 
175
- 这个配置会:
199
+ **这个配置会:**
176
200
  - 混合 `audio/` 目录下的所有 MP3 文件
177
- - 按顺序拼接三个视频文件
178
- - 不进行视频转码(使用原始格式)
201
+ - 按顺序拼接三个视频文件(片头 + 内容 + 片尾)
202
+ - 不进行视频转码(使用原始格式,需确保格式一致)
203
+ - 添加字幕并输出到 `task-0/video.mp4`
204
+
205
+ **适用场景:** 固定结构的视频制作,如教程视频、演讲录制等
206
+
207
+ ---
179
208
 
180
209
  ### 示例 3:批量处理多个任务
181
210
 
@@ -192,10 +221,49 @@ task-0/
192
221
  "videos": "task2/videos/*.mp4",
193
222
  "narration": "task2/subtitle.txt",
194
223
  "videoPackMode": "all"
224
+ },
225
+ {
226
+ "audios": ["task3/bgm1.mp3", "task3/bgm2.mp3"],
227
+ "videos": "task3/clips/*.mp4",
228
+ "narration": "task3/subtitle.txt",
229
+ "videoTranscodeable": true,
230
+ "videoPackMode": "random"
231
+ }
232
+ ]
233
+ ```
234
+
235
+ **这个配置会:**
236
+ - 依次处理三个独立任务
237
+ - 每个任务输出到对应的 `task-0/`、`task-1/`、`task-2/` 目录
238
+ - 支持不同的配置组合
239
+
240
+ **适用场景:** 批量生产系列视频、多语言版本视频等
241
+
242
+ ---
243
+
244
+ ### 示例 4:实际项目配置(参考 workspace/task.json)
245
+
246
+ ```json
247
+ [
248
+ {
249
+ "audios": ["workspace/007/007阿勒泰.mp3", "workspace/新疆背景音乐-已编辑.MP3"],
250
+ "videos": "workspace/007/*.mp4",
251
+ "narration": "workspace/007/007.txt",
252
+ "videoTranscodeable": true,
253
+ "videoPackMode": "random"
195
254
  }
196
255
  ]
197
256
  ```
198
257
 
258
+ **这个配置会:**
259
+ - 混合旁白音频和背景音乐(以旁白时长为准)
260
+ - 从 `workspace/007/` 目录随机选择视频素材
261
+ - 转码所有视频确保格式统一
262
+ - 添加字幕文件 `007.txt` 中的内容
263
+ - 输出到 `task-0/video.mp4`
264
+
265
+ **适用场景:** 旅游 Vlog、纪录片、风景视频等需要旁白 + 背景音乐的场景
266
+
199
267
  ## 🛠️ 开发
200
268
 
201
269
  ```bash
@@ -226,27 +294,261 @@ import {
226
294
  } from '@evio/ffai';
227
295
  ```
228
296
 
297
+ ### 函数详细说明
298
+
299
+ #### `getMediaDuration(filePath: string): Promise<number>`
300
+ 获取媒体文件(音频或视频)的播放时长。
301
+
302
+ **参数:**
303
+ - `filePath`: 媒体文件的完整路径
304
+
305
+ **返回:** Promise,resolve 时返回时长(秒)
306
+
307
+ **示例:**
308
+ ```typescript
309
+ const duration = await getMediaDuration('video.mp4');
310
+ console.log(`视频时长: ${duration} 秒`);
311
+ ```
312
+
313
+ ---
314
+
315
+ #### `transcodeVideo(input: string, output: string): Promise<void>`
316
+ 将单个视频转码为 MP4 格式(H.264 + AAC)。
317
+
318
+ **参数:**
319
+ - `input`: 输入视频文件路径
320
+ - `output`: 输出视频文件路径
321
+
322
+ **编码参数:**
323
+ - 视频编码:H.264 (libx264)
324
+ - 音频编码:AAC
325
+ - 音频采样率:44.1kHz
326
+ - 音频声道:立体声
327
+
328
+ **示例:**
329
+ ```typescript
330
+ await transcodeVideo('input.avi', 'output.mp4');
331
+ ```
332
+
333
+ ---
334
+
335
+ #### `concatVideos(files: string[], outputFilePath: string, tmp?: string): Promise<void>`
336
+ 拼接多个视频文件为一个完整视频。
337
+
338
+ **参数:**
339
+ - `files`: 视频文件路径数组
340
+ - `outputFilePath`: 输出文件路径
341
+ - `tmp`: 可选,临时文件目录(用于存放 videofiles.txt)
342
+
343
+ **注意:** 建议所有视频使用相同的编码格式、分辨率和帧率
344
+
345
+ **示例:**
346
+ ```typescript
347
+ await concatVideos(
348
+ ['clip1.mp4', 'clip2.mp4', 'clip3.mp4'],
349
+ 'final.mp4',
350
+ './tmp'
351
+ );
352
+ ```
353
+
354
+ ---
355
+
356
+ #### `mergeAudioFiles(audioFiles: string[], outputFilePath: string): Promise<void>`
357
+ 混音多个音频文件,以第一个音频的时长为准。
358
+
359
+ **参数:**
360
+ - `audioFiles`: 音频文件路径数组
361
+ - `outputFilePath`: 输出文件路径
362
+
363
+ **混音规则:**
364
+ - 使用 FFmpeg 的 `amix` 滤镜
365
+ - 自动调整音量避免爆音
366
+ - 输出时长由第一个音频决定
367
+
368
+ **示例:**
369
+ ```typescript
370
+ await mergeAudioFiles(
371
+ ['narration.mp3', 'bgm.mp3'],
372
+ 'mixed.mp3'
373
+ );
374
+ ```
375
+
376
+ ---
377
+
378
+ #### `addBackgroundAudio(inputVideoPath: string, outputVideoPath: string, audioPath: string): Promise<void>`
379
+ 为视频添加背景音频。
380
+
381
+ **参数:**
382
+ - `inputVideoPath`: 输入视频路径
383
+ - `outputVideoPath`: 输出视频路径
384
+ - `audioPath`: 音频文件路径
385
+
386
+ **处理规则:**
387
+ - 视频流直接复制,不重新编码
388
+ - 音频使用 AAC 编码
389
+ - 使用 `-shortest` 参数,输出长度取视频和音频中较短的一个
390
+
391
+ **示例:**
392
+ ```typescript
393
+ await addBackgroundAudio(
394
+ 'video.mp4',
395
+ 'video_with_audio.mp4',
396
+ 'bgm.mp3'
397
+ );
398
+ ```
399
+
400
+ ---
401
+
402
+ #### `addSubTitles(inputfile: string, outputfile: string, texts: string, fontfile?: string): Promise<void>`
403
+ 为视频添加字幕。
404
+
405
+ **参数:**
406
+ - `inputfile`: 输入视频路径
407
+ - `outputfile`: 输出视频路径
408
+ - `texts`: 字幕文件路径(格式:`MM:SS-MM:SS 文本内容`)
409
+ - `fontfile`: 可选,字体文件路径
410
+
411
+ **字幕样式:**
412
+ - 字体大小:36px
413
+ - 颜色:白色
414
+ - 效果:黑色描边 + 阴影
415
+ - 位置:底部居中(距底部 300px)
416
+
417
+ **示例:**
418
+ ```typescript
419
+ await addSubTitles(
420
+ 'video.mp4',
421
+ 'video_with_subtitles.mp4',
422
+ 'subtitle.txt',
423
+ '/path/to/font.ttf'
424
+ );
425
+ ```
426
+
427
+ ---
428
+
429
+ #### `checkFfmpegAvailable(): Promise<boolean>`
430
+ 检查系统中 FFmpeg 是否可用。
431
+
432
+ **返回:** Promise,resolve 时返回 `true`(可用)或 `false`(不可用)
433
+
434
+ **示例:**
435
+ ```typescript
436
+ const available = await checkFfmpegAvailable();
437
+ if (!available) {
438
+ console.error('FFmpeg 未安装或不可用');
439
+ }
440
+ ```
441
+
229
442
  ## ⚠️ 注意事项
230
443
 
231
- 1. **FFmpeg 必须安装** - 工具依赖 FFmpeg,请确保已正确安装
232
- 2. **字幕字体** - macOS 默认使用宋体(`/System/Library/Fonts/Supplemental/Songti.ttc`),其他系统可能需要修改字体路径
233
- 3. **视频格式** - 建议启用 `videoTranscodeable` 以确保所有视频格式统一,避免拼接问题
234
- 4. **文件路径** - 所有路径都相对于执行命令的当前目录
235
- 5. **临时文件** - 处理完成后会自动清理临时文件,保留最终成品
444
+ 1. **FFmpeg 必须安装** - 工具依赖 FFmpeg,请确保已正确安装并配置到环境变量
445
+ 2. **字幕字体路径** - macOS 默认使用宋体(`/System/Library/Fonts/Supplemental/Songti.ttc`),其他系统需要在 `src/task.ts` 中修改 `fontfile` 变量
446
+ 3. **视频格式统一** - 强烈建议启用 `videoTranscodeable` 以确保所有视频格式统一,避免拼接时出现黑屏、卡顿或音画不同步
447
+ 4. **文件路径规则** - 所有路径都相对于执行命令的当前目录(`process.cwd()`)
448
+ 5. **临时文件清理** - 处理完成后会自动清理中间过程文件(`video_concat.mp4`、`video_concat_audio.mp4`、`videofiles.txt`),仅保留最终成品
449
+ 6. **音频时长规则** - 当 `audios` 为数组时,最终音频时长由第一个音频文件决定,其他音频会被混音叠加
450
+ 7. **视频时长匹配** - 添加背景音频时使用 `-shortest` 参数,输出视频长度会与音频或视频中较短的一个对齐
451
+ 8. **字幕文件编码** - 字幕文件必须使用 UTF-8 编码,避免中文乱码
452
+ 9. **Glob 模式注意** - 使用 glob 模式时,文件匹配顺序可能不是字母顺序,建议使用数组明确指定顺序
453
+ 10. **磁盘空间** - 视频处理需要较大的临时存储空间,确保磁盘有足够空间(建议至少为源文件总大小的 2-3 倍)
454
+
455
+ ## 🎯 最佳实践
456
+
457
+ 1. **项目结构建议**
458
+ ```
459
+ project/
460
+ ├── task.json # 任务配置文件
461
+ ├── audios/ # 音频素材目录
462
+ │ ├── narration.mp3
463
+ │ └── bgm.mp3
464
+ ├── videos/ # 视频素材目录
465
+ │ ├── clip1.mp4
466
+ │ └── clip2.mp4
467
+ ├── subtitles/ # 字幕文件目录
468
+ │ └── subtitle.txt
469
+ └── task-0/ # 输出目录(自动生成)
470
+ └── video.mp4
471
+ ```
472
+
473
+ 2. **视频素材准备**
474
+ - 尽量使用相同分辨率和帧率的视频素材
475
+ - 如果素材格式不统一,务必启用 `videoTranscodeable: true`
476
+ - 建议使用 1920x1080 (1080p) 或 1280x720 (720p) 分辨率
477
+
478
+ 3. **音频素材准备**
479
+ - 旁白音频放在数组第一位,背景音乐放在后面
480
+ - 确保音频采样率一致(建议 44.1kHz)
481
+ - 背景音乐音量建议比旁白低 20-30%(可以预先处理)
482
+
483
+ 4. **字幕文件编写**
484
+ - 使用文本编辑器保存为 UTF-8 编码
485
+ - 每条字幕时长建议 3-8 秒,便于阅读
486
+ - 字幕文本不要过长,建议每行不超过 20 个汉字
487
+ - 预留字幕间隔时间,避免连续出现
488
+
489
+ 5. **性能优化**
490
+ - 如果不需要转码,关闭 `videoTranscodeable` 可以大幅提升速度
491
+ - 批量任务建议分批处理,避免一次处理过多任务
492
+ - 使用 SSD 硬盘可以显著提升处理速度
493
+
494
+ 6. **质量控制**
495
+ - 处理完成后检查输出视频的音画同步
496
+ - 验证字幕显示时间和内容是否正确
497
+ - 检查音频混音效果,确保旁白清晰可听
236
498
 
237
499
  ## 🐛 常见问题
238
500
 
239
501
  **Q: 提示 "FFmpeg is not available"**
240
- A: 请确保已安装 FFmpeg 并配置到系统环境变量中
502
+ A: 请确保已安装 FFmpeg 并配置到系统环境变量中。可以运行 `ffmpeg -version` 验证安装。
241
503
 
242
504
  **Q: 视频拼接后出现黑屏或卡顿**
243
- A: 建议启用 `videoTranscodeable: true` 将所有视频转码为统一格式
505
+ A: 建议启用 `videoTranscodeable: true` 将所有视频转码为统一格式。不同编码格式的视频直接拼接可能导致兼容性问题。
244
506
 
245
- **Q: 字幕显示不正常**
246
- A: 检查字幕文件格式是否正确,时间格式必须为 `MM:SS-MM:SS`
507
+ **Q: 字幕显示不正常或不显示**
508
+ A:
509
+ - 检查字幕文件格式是否正确,时间格式必须为 `MM:SS-MM:SS 文本内容`
510
+ - 确保字幕文件使用 UTF-8 编码
511
+ - 检查时间范围是否在视频时长内
512
+ - 确保每行格式正确,时间和文本之间有空格
247
513
 
248
514
  **Q: 音频混音后声音太小或太大**
249
- A: FFmpeg 的 `amix` 滤镜会自动调整音量,如需手动控制可修改 `lib.ts` 中的混音参数
515
+ A: FFmpeg 的 `amix` 滤镜会自动调整音量。如需手动控制,可以在 `src/lib.ts` 的 `mergeAudioFiles` 函数中修改 `amix` 参数,例如添加 `weights` 选项来调整各音频的权重。
516
+
517
+ **Q: 字幕字体显示异常或乱码**
518
+ A:
519
+ - macOS 默认使用宋体,路径为 `/System/Library/Fonts/Supplemental/Songti.ttc`
520
+ - Windows 可以使用 `C:/Windows/Fonts/simhei.ttf`(黑体)
521
+ - Linux 需要安装中文字体,如 `sudo apt install fonts-wqy-zenhei`
522
+ - 修改 `src/task.ts` 中的 `fontfile` 变量指向正确的字体文件
523
+
524
+ **Q: 任务配置文件必须是 .json 后缀吗?**
525
+ A: 是的,工具会检查文件扩展名,必须使用 `.json` 后缀。
526
+
527
+ **Q: 可以处理哪些视频格式?**
528
+ A: FFmpeg 支持几乎所有常见视频格式(MP4、AVI、MOV、MKV、FLV 等)。建议启用 `videoTranscodeable` 转码为统一的 MP4 格式以确保兼容性。
529
+
530
+ **Q: random 模式如何选择视频?**
531
+ A: 工具会随机选择视频片段,累加时长直到 ≥ 音频时长。每个视频片段只会被选择一次,避免重复。如果所有视频总时长仍小于音频时长,则使用所有视频。
532
+
533
+ **Q: 处理大文件时速度很慢怎么办?**
534
+ A:
535
+ - 视频转码是最耗时的步骤,如果源视频已经是 MP4 格式,可以关闭 `videoTranscodeable`
536
+ - 字幕渲染需要重新编码视频,可以在 `src/lib.ts` 中调整 `-preset` 参数(如改为 `ultrafast`)来加快速度,但会增大文件体积
537
+ - 考虑使用更强大的硬件或启用硬件加速
538
+
539
+ **Q: 如何自定义字幕样式?**
540
+ A: 在 `src/lib.ts` 的 `addSubTitles` 函数中修改 `videoFilters` 的 `options`,可以调整:
541
+ - `fontsize`: 字体大小
542
+ - `fontcolor`: 文字颜色
543
+ - `shadowcolor`/`shadowx`/`shadowy`: 阴影效果
544
+ - `borderw`/`bordercolor`: 边框效果
545
+ - `x`/`y`: 字幕位置
546
+
547
+ **Q: 输出视频的质量如何控制?**
548
+ A: 在 `src/lib.ts` 中可以调整编码参数:
549
+ - 转码时的 `-c:v libx264` 可以改为 `-c:v libx265`(更高压缩率)
550
+ - 字幕渲染时的 `-crf 18` 控制质量(0-51,数值越小质量越高,18 为高质量)
551
+ - 音频比特率 `-b:a 192k` 可以调整为更高或更低的值
250
552
 
251
553
  ## 📄 License
252
554
 
@@ -256,7 +558,53 @@ ISC
256
558
 
257
559
  欢迎提交 Issue 和 Pull Request!
258
560
 
561
+ ### 贡献指南
562
+
563
+ 1. Fork 本项目
564
+ 2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
565
+ 3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
566
+ 4. 推送到分支 (`git push origin feature/AmazingFeature`)
567
+ 5. 开启 Pull Request
568
+
569
+ ### 开发建议
570
+
571
+ - 遵循现有代码风格
572
+ - 添加适当的注释和文档
573
+ - 测试新功能确保不影响现有功能
574
+ - 更新 README 文档(如有必要)
575
+
576
+ ## 📚 相关资源
577
+
578
+ - [FFmpeg 官方文档](https://ffmpeg.org/documentation.html)
579
+ - [fluent-ffmpeg 文档](https://github.com/fluent-ffmpeg/node-fluent-ffmpeg)
580
+ - [FFmpeg 滤镜文档](https://ffmpeg.org/ffmpeg-filters.html)
581
+ - [视频编码指南](https://trac.ffmpeg.org/wiki/Encode/H.264)
582
+
583
+ ## 🔮 未来计划
584
+
585
+ - [ ] 支持更多字幕格式(SRT、ASS 等)
586
+ - [ ] 添加视频特效和转场效果
587
+ - [ ] 支持硬件加速编码(NVENC、QSV 等)
588
+ - [ ] 提供 GUI 界面
589
+ - [ ] 支持实时预览
590
+ - [ ] 添加进度条显示
591
+ - [ ] 支持视频裁剪和缩放
592
+ - [ ] 添加水印功能
593
+ - [ ] 支持多语言字幕
594
+
595
+ ## 📞 联系方式
596
+
597
+ 如有问题或建议,欢迎通过以下方式联系:
598
+
599
+ - 提交 [Issue](https://github.com/your-repo/issues)
600
+ - 发送邮件至:[your-email@example.com]
601
+
259
602
  ---
260
603
 
261
604
  **版本**: 1.0.2
262
- **作者**: @evio/ffai
605
+ **作者**: @evio/ffai
606
+ **最后更新**: 2024
607
+
608
+ ---
609
+
610
+ ⭐ 如果这个项目对你有帮助,欢迎给个 Star!
package/dist/task.js CHANGED
@@ -4,10 +4,15 @@ exports.RunTask = RunTask;
4
4
  const node_path_1 = require("node:path");
5
5
  const glob_1 = require("glob");
6
6
  const node_fs_1 = require("node:fs");
7
+ const signale_1 = require("signale");
7
8
  const lib_1 = require("./lib");
8
9
  const fontfile = '/System/Library/Fonts/Supplemental/Songti.ttc';
9
10
  async function RunTask(props, index) {
10
- console.log(`开始第${index + 1}个任务:`);
11
+ const cmd = new signale_1.Signale({
12
+ interactive: true,
13
+ scope: 'task-' + (index + 1),
14
+ });
15
+ cmd.await(`[%d/7] - 正在进行第${index + 1}个任务...`, 1);
11
16
  const cwd = process.cwd();
12
17
  const directory = (0, node_path_1.resolve)(cwd, 'task-' + index);
13
18
  const narrationFile = (0, node_path_1.resolve)(cwd, props.narration);
@@ -15,8 +20,8 @@ async function RunTask(props, index) {
15
20
  throw new Error('缺少旁白文件');
16
21
  if (!(0, node_fs_1.existsSync)(directory))
17
22
  (0, node_fs_1.mkdirSync)(directory);
18
- const videosData = await parseVideos(props.videoTranscodeable, props.videos, directory);
19
- const audioData = await parseAudios(props.audios, directory);
23
+ const videosData = await parseVideos(props.videoTranscodeable, props.videos, directory, cmd);
24
+ const audioData = await parseAudios(props.audios, directory, cmd);
20
25
  const output_concat = (0, node_path_1.resolve)(directory, 'video_concat.mp4');
21
26
  const output_concat_audio = (0, node_path_1.resolve)(directory, 'video_concat_audio.mp4');
22
27
  const output_concat_audio_subtitle = (0, node_path_1.resolve)(directory, 'video.mp4');
@@ -36,14 +41,26 @@ async function RunTask(props, index) {
36
41
  const videoTmpDir = (0, node_path_1.resolve)(directory, 'tmp');
37
42
  if (!(0, node_fs_1.existsSync)(videoTmpDir))
38
43
  (0, node_fs_1.mkdirSync)(videoTmpDir);
44
+ cmd.await(`[%d/7] - 正在合并视频...`, 4);
39
45
  await (0, lib_1.concatVideos)(packVideos, output_concat, videoTmpDir);
46
+ cmd.await(`[%d/7] - 正在添加背景音频...`, 5);
40
47
  await (0, lib_1.addBackgroundAudio)(output_concat, output_concat_audio, audioData.file);
48
+ cmd.await(`[%d/7] - 正在添加旁白...`, 6);
41
49
  await (0, lib_1.addSubTitles)(output_concat_audio, output_concat_audio_subtitle, narrationFile, fontfile);
42
50
  // 清理
43
51
  (0, node_fs_1.unlinkSync)((0, node_path_1.resolve)(videoTmpDir, 'videofiles.txt'));
52
+ (0, node_fs_1.rmdirSync)(videoTmpDir);
53
+ (0, node_fs_1.unlinkSync)(audioData.file);
44
54
  (0, node_fs_1.unlinkSync)(output_concat);
45
55
  (0, node_fs_1.unlinkSync)(output_concat_audio);
46
- console.log('+', output_concat_audio_subtitle);
56
+ const videoDirectory = (0, node_path_1.resolve)(directory, 'videos');
57
+ if (props.videoTranscodeable && (0, node_fs_1.existsSync)(videoDirectory)) {
58
+ for (let i = 0; i < videosData.length; i++) {
59
+ (0, node_fs_1.unlinkSync)(videosData[i].file);
60
+ }
61
+ (0, node_fs_1.rmdirSync)(videoDirectory);
62
+ }
63
+ cmd.success(`[%d/7] - ${output_concat_audio_subtitle}`, 7);
47
64
  }
48
65
  async function getVideoFiles(videos) {
49
66
  const cwd = process.cwd();
@@ -65,7 +82,7 @@ async function getAudioFiles(audios) {
65
82
  return files.map(file => (0, node_path_1.resolve)(cwd, file));
66
83
  }
67
84
  }
68
- async function parseVideos(transcodeable, videos, directory) {
85
+ async function parseVideos(transcodeable, videos, directory, cmd) {
69
86
  let files = await getVideoFiles(videos);
70
87
  if (transcodeable) {
71
88
  const to = (0, node_path_1.resolve)(directory, 'videos');
@@ -73,6 +90,7 @@ async function parseVideos(transcodeable, videos, directory) {
73
90
  (0, node_fs_1.mkdirSync)(to);
74
91
  let newFiles = [];
75
92
  for (let i = 0; i < files.length; i++) {
93
+ cmd.await(`[%d/7] - 正在转换视频格式: %d/%d`, 2, i + 1, files.length);
76
94
  const outfile = (0, node_path_1.resolve)(to, `${i + 1}.mp4`);
77
95
  await (0, lib_1.transcodeVideo)(files[i], outfile);
78
96
  newFiles.push(outfile);
@@ -81,6 +99,7 @@ async function parseVideos(transcodeable, videos, directory) {
81
99
  }
82
100
  const result = [];
83
101
  for (let i = 0; i < files.length; i++) {
102
+ cmd.await(`[%d/7] - 正在转换视频格式: %d/%d`, 2, i + 1, files.length);
84
103
  const sec = Math.ceil(await (0, lib_1.getMediaDuration)(files[i]));
85
104
  result.push({
86
105
  file: files[i],
@@ -89,9 +108,10 @@ async function parseVideos(transcodeable, videos, directory) {
89
108
  }
90
109
  return result;
91
110
  }
92
- async function parseAudios(audios, directory) {
111
+ async function parseAudios(audios, directory, cmd) {
93
112
  const files = await getAudioFiles(audios);
94
113
  const file = (0, node_path_1.resolve)(directory, 'audio.mp3');
114
+ cmd.await(`[%d/7] - 正在合并音频`, 3);
95
115
  await (0, lib_1.mergeAudioFiles)(files, file);
96
116
  const sec = Math.ceil(await (0, lib_1.getMediaDuration)(file));
97
117
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evio/ffai",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "一个基于 FFmpeg 的命令行工具,用于批量处理视频和音频文件,支持视频转码、音频合并、字幕添加等功能。",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {
@@ -22,15 +22,18 @@
22
22
  "commander": "^14.0.2",
23
23
  "dayjs": "1.11.19",
24
24
  "fluent-ffmpeg": "^2.1.3",
25
- "glob": "^13.0.0"
25
+ "glob": "^13.0.0",
26
+ "signale": "^1.4.0"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@types/fluent-ffmpeg": "^2.1.27",
30
+ "@types/fs-extra": "^11.0.4",
29
31
  "@types/node": "^22.0.0",
32
+ "@types/signale": "^1.4.7",
30
33
  "dayjs": "1.11.19",
34
+ "fix-esm-import-path": "^1.10.1",
31
35
  "glob": "^13.0.0",
32
36
  "ts-node": "^10.9.2",
33
- "typescript": "^5.4.0",
34
- "fix-esm-import-path": "^1.10.1"
37
+ "typescript": "^5.4.0"
35
38
  }
36
39
  }