问题:使用rq队列通过subprocess调用ffmpeg进行视频格式转换,但队列消费,subprocess创建进程后,ffmpeg不运行,未生成转换后文件。但当我不使用队列,直接手动运行脚本使用subprocess调用ffmpeg时却能运行,是什么原因?
解决:需要禁用从标准输入(stdin)读取数据
方法:
cmd = [
"ffmpeg",
"-nostdin", # 不要从标准输入读取
"-loglevel", "error", # 只输出错误信息
"-i", input_file,
"-c:v", "libx264", # 使用H.264编码
"-preset", "ultrafast", # 使用最快的预设,加快转换速度
"-c:a", "aac", # 使用AAC音频编码
"-strict", "-2", # 允许使用实验性编码器
"-y", # 覆盖输出文件
output_file
]
print(f'转换命令: {" ".join(cmd)}')
try:
print(f'开始执行转换命令,输出文件: {output_file}')
# 使用Popen以非阻塞方式执行转换命令
process = subprocess.Popen(
cmd,
stdin=subprocess.DEVNULL, # 重定向标准输入,防止ffmpeg等待交互
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# 等待进程完成,带超时
try:
stdout, stderr = process.communicate(timeout=3600) # 1小时超时
except subprocess.TimeoutExpired:
process.kill()
process.communicate() # 清理输出
raise subprocess.TimeoutExpired(cmd, 3600)
if process.returncode != 0:
print(f'转换命令执行失败,返回码: {process.returncode}, 错误信息: {stderr}')
raise Exception(f"视频转换失败: {stderr}")
print(f'转换命令执行成功,输出文件大小: {os.path.getsize(output_file) if os.path.exists(output_file) else "文件不存在"}')
return output_file
except subprocess.TimeoutExpired:
# 清理临时文件
if os.path.exists(output_file):
os.unlink(output_file)
print(f'视频转换超时,命令: {" ".join(cmd)}')
raise Exception("视频转换超时")
except Exception as e:
# 清理临时文件
if os.path.exists(output_file):
os.unlink(output_file)
print(f'转换过程发生异常: {str(e)}')
raise Exception(f"视频转换失败: {str(e)}")
作用详解:
默认情况下,ffmpeg 会监听标准输入(例如键盘输入),以便用户可以:
- 按
q或ESC键提前终止转码; - 按其他键(如
+、-、s等)进行运行时控制(如调整日志级别、暂停等)。
但在某些使用场景中,这种行为可能带来问题,例如:
- 在 脚本或后台任务 中运行
ffmpeg时,标准输入可能被重定向或不可用; - 当
ffmpeg作为子进程被其他程序调用时,父进程的标准输入可能会意外干扰ffmpeg; - 在容器化环境(如 Docker)或 CI/CD 流水线中,stdin 可能未连接,导致
ffmpeg报错或挂起。
使用 -nostdin 后,ffmpeg 将完全忽略 stdin,不会尝试从中读取任何字符,从而避免上述问题。