
昨天在调试 n8n 工作流时,遇到了一个 FFmpeg 视频截取的错误,折腾了好一阵子才搞定。
今天就把这个问题分享出来,希望能帮到遇到类似情况的朋友。
错误现场
先看看报错信息:
json
{
"errorMessage": "Command failed: VIDEO_FILE=\"/mnt/d/Downloads/movie/a45video/XF1327/renwu/\"",
"n8nDetails": {
"nodeName": "Execute Command",
"nodeType": "n8n-nodes-base.executeCommand",
"time": "2025/11/11 11:36:25"
}
}
脚本的逻辑是这样的:
bash
VIDEO_FILE="/mnt/d/Downloads/movie/a45video/XF1327/renwu/"
CLIP_DURATION_FLOAT=""
# 获取视频长度
DURATION=$(ffprobe -v quiet -show_entries format=duration -of csv=p=0 "$VIDEO_FILE" 2>/dev/null)
# ...中间的计算逻辑...
# 执行截取
OUTPUT_FILE="/mnt/g/youtube-rensheng/251111-6jtqrs/temp/1762832185804.mp4"
ffmpeg -ss $START_TIME -t $ACTUAL_DURATION -i "$VIDEO_FILE" -c copy "$OUTPUT_FILE"
看起来没什么问题,但就是执行失败。
问题一:文件路径不对(最明显的坑)
仔细看第一行:
bash
VIDEO_FILE="/mnt/d/Downloads/movie/a45video/XF1327/renwu/"
发现问题了吗?这是一个目录路径,不是文件路径!
FFmpeg 需要的是具体的视频文件,比如:
bash
VIDEO_FILE="/mnt/d/Downloads/movie/a45video/XF1327/renwu/video.mp4"
如果你只给它一个目录,FFmpeg 会一脸懵逼,不知道该处理哪个文件。
怎么修复?
最简单的方法就是确保路径指向具体的文件:
bash
# 错误示例
VIDEO_FILE="/mnt/d/Downloads/movie/a45video/XF1327/renwu/"
# 正确示例
VIDEO_FILE="/mnt/d/Downloads/movie/a45video/XF1327/renwu/my_video.mp4"
或者,如果你想处理目录下的某个文件,可以用通配符:
bash
VIDEO_FILE="/mnt/d/Downloads/movie/a45video/XF1327/renwu/*.mp4"
问题二:参数顺序错了(隐蔽的坑)
即使文件路径对了,还有一个更隐蔽的问题。
原来的命令是这样的:
bash
ffmpeg -ss $START_TIME -t $ACTUAL_DURATION -i "$VIDEO_FILE" -c copy "$OUTPUT_FILE"
把 -ss 和 -t 参数放在 -i 之前,虽然能快速定位到指定位置,但有个副作用:时间戳可能不准确。
这会导致什么问题呢?
- 截取出来的视频可能时长不对
- 可能会出现空文件
- 视频开头可能不是你想要的位置
怎么修复?
把 -ss 参数移到 -i 之后:
bash
# 错误示例
ffmpeg -ss $START_TIME -t $ACTUAL_DURATION -i "$VIDEO_FILE" -c copy "$OUTPUT_FILE"
# 正确示例
ffmpeg -i "$VIDEO_FILE" -ss $START_TIME -t $ACTUAL_DURATION -c copy "$OUTPUT_FILE"
这样虽然会慢一点(因为要先读取整个视频),但时间戳会准确很多。
完整的解决方案
结合上面两个问题,这是修复后的完整脚本:
bash
#!/bin/bash
# 确保指向具体的视频文件
VIDEO_FILE="/mnt/d/Downloads/movie/a45video/XF1327/renwu/specific_video.mp4"
OUTPUT_FILE="/mnt/g/youtube-rensheng/251111-6jtqrs/temp/1762832185804.mp4"
# 检查文件是否存在
if [ ! -f "$VIDEO_FILE" ]; then
echo "错误:视频文件不存在 - $VIDEO_FILE"
exit 1
fi
# 获取视频时长(添加错误处理)
DURATION=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$VIDEO_FILE")
if [ -z "$DURATION" ] || [ "$DURATION" = "N/A" ]; then
echo "错误:无法获取视频时长"
exit 1
fi
# 确保数值有效
DURATION_FLOAT=$(printf "%.6f" "$DURATION")
# 这里是你原有的时间计算逻辑
# ...
# 执行截取(改进的命令)
ffmpeg -i "$VIDEO_FILE" -ss $START_TIME -t $ACTUAL_DURATION -c copy -avoid_negative_ts 1 "$OUTPUT_FILE"
# 检查执行结果
if [ $? -eq 0 ]; then
echo "视频截取成功: $OUTPUT_FILE"
else
echo "视频截取失败"
exit 1
fi
关键改进点
-
文件路径验证
- 检查文件是否存在:
[ ! -f "$VIDEO_FILE" ] - 确保路径指向具体文件,不是目录
- 检查文件是否存在:
-
参数顺序调整
- 把
-ss移到-i之后 - 提高时间戳的准确性
- 把
-
添加
-avoid_negative_ts 1- 防止时间戳问题导致的截取错误
- 这个参数在处理某些特殊编码的视频时很有用
-
错误处理
- 检查文件是否存在
- 检查视频时长是否能获取
- 检查 FFmpeg 命令是否执行成功
如果视频本身有问题怎么办?
有些视频可能有编码问题,用 -c copy 直接复制流可能会失败。
这时候可以考虑重新编码:
bash
ffmpeg -i "$VIDEO_FILE" -ss $START_TIME -t $ACTUAL_DURATION \
-c:v libx264 -c:a aac -avoid_negative_ts 1 "$OUTPUT_FILE"
虽然会慢一些,但更稳定。
预防措施:避免踩同样的坑
为了避免以后再遇到类似问题,建议:
1. 在执行前先检查视频
bash
# 用 ffprobe 检查视频信息
ffprobe -v error -show_entries format=duration,streams=codec_name "$VIDEO_FILE"
这会告诉你视频的时长、编码格式等信息,方便判断是否有问题。
2. 对问题视频先转码
如果视频本身有问题,先转码再处理:
bash
# 先转码
ffmpeg -i "$VIDEO_FILE" -c:v libx264 -c:a aac temp_video.mp4
# 再截取
ffmpeg -i temp_video.mp4 -ss $START_TIME -t $ACTUAL_DURATION -c copy output.mp4
3. 添加详细的日志
在脚本里多加一些日志输出:
bash
echo "视频文件: $VIDEO_FILE"
echo "视频时长: $DURATION 秒"
echo "开始时间: $START_TIME 秒"
echo "截取时长: $ACTUAL_DURATION 秒"
echo "输出文件: $OUTPUT_FILE"
这样出问题时能快速定位。
常见问题排查
问题 1:截取出来的视频是空的
可能原因:
- 开始时间超过了视频时长
- 视频本身有问题
解决方法:
bash
# 检查视频时长
ffprobe -v error -show_entries format=duration -of csv=p=0 "$VIDEO_FILE"
# 确保开始时间在合理范围内
问题 2:截取出来的视频时长不对
可能原因:
-ss参数位置不对- 时间戳计算错误
解决方法:
bash
# 把 -ss 放在 -i 之后
ffmpeg -i "$VIDEO_FILE" -ss $START_TIME -t $ACTUAL_DURATION ...
问题 3:视频开头有花屏或黑屏
可能原因:
- 关键帧位置不对
解决方法:
bash
# 添加 -accurate_seek 参数
ffmpeg -ss $START_TIME -i "$VIDEO_FILE" -t $ACTUAL_DURATION -c copy -avoid_negative_ts 1 "$OUTPUT_FILE"
最后的话
FFmpeg 视频截取看似简单,但有很多细节需要注意。
这次遇到的问题主要有两个:
- 文件路径错误:指向了目录而不是文件
- 参数顺序错误 :
-ss放在了-i之前
在 n8n 的 Execute Command 节点中,如果命令比较复杂,建议先在终端里测试通过,再放到 n8n 里执行。这样调试起来会方便很多。