Whisper语音识别定时任务:cron调度实现
1. 引言
1.1 业务场景描述
在实际生产环境中,语音识别服务常用于批量处理历史录音文件,例如客服通话记录、会议录音归档、教育音频转写等。这些任务通常不需要实时响应,但要求在固定时间或周期性自动执行,以降低人工干预成本并提升处理效率。
本文介绍如何将基于 OpenAI Whisper Large v3 的多语言语音识别 Web 服务(由 by113 小贝二次开发)与 Linux 系统的cron定时任务机制结合,实现自动化语音转录流程。通过该方案,用户可设定每日凌晨自动处理指定目录中的新增音频文件,并输出结构化文本结果。
1.2 痛点分析
当前 Web 服务虽支持手动上传和实时识别,但在以下场景存在不足:
- 批量音频需逐一手动上传,操作繁琐
- 缺乏自动触发机制,无法实现无人值守运行
- 多语言文件需人工选择模式,易出错
- 无日志记录与错误重试机制
这些问题限制了其在企业级数据流水线中的应用。
1.3 方案预告
本文将提供一套完整的 Whisper 语音识别定时任务落地方案,涵盖:
- 构建命令行调用接口
- 设计音频监听与去重逻辑
- 编写 cron 调度规则
- 实现日志追踪与异常处理
- 验证 GPU 资源利用率
最终实现“上传即识别”的自动化工作流。
2. 技术方案选型
2.1 为什么选择 cron?
| 对比项 | cron | Airflow | Kubernetes CronJob |
|---|---|---|---|
| 部署复杂度 | ⭐⭐⭐⭐⭐(极简) | ⭐⭐ | ⭐ |
| 学习成本 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐ |
| 适用规模 | 单机/边缘设备 | 中大型集群 | 云原生环境 |
| 资源占用 | 极低 | 高 | 中 |
| 实时性 | 分钟级 | 秒级 | 秒级 |
对于单台部署 Whisper 服务的边缘服务器(如 RTX 4090 D 主机),cron 是最轻量、稳定且高效的调度方案,无需引入额外依赖。
2.2 命令行接口设计目标
为适配 cron 调度,需对原始 Gradio Web 服务进行扩展,新增 CLI 模式,支持:
- 指定输入音频路径
- 自动语言检测(默认开启)
- 输出 JSON 或 TXT 格式
- 支持翻译为英文选项
- 记录处理耗时与状态码
3. 实现步骤详解
3.1 扩展主程序支持 CLI 模式
修改/root/Whisper-large-v3/app.py,增加命令行解析功能。
# app.py 片段:添加 argparse 支持 import argparse import os import json from datetime import datetime def transcribe_cli(audio_path, output_dir="./transcripts", translate=False): if not os.path.exists(audio_path): print(f"[ERROR] Audio file not found: {audio_path}") return 1 # 加载模型(复用原有逻辑) model = whisper.load_model("large-v3", device="cuda") # 自动检测语言 result = model.transcribe(audio_path, task="translate" if translate else "transcribe") # 生成输出文件名 base_name = os.path.splitext(os.path.basename(audio_path))[0] ext = "txt" if not translate else "en.txt" output_path = os.path.join(output_dir, f"{base_name}.{ext}") # 写入结果 with open(output_path, 'w', encoding='utf-8') as f: f.write(result["text"].strip()) # 日志记录 log_entry = { "timestamp": datetime.now().isoformat(), "input": audio_path, "output": output_path, "language": result.get("language", "auto"), "duration": result.get("segments", [{}])[-1].get("end", 0), "status": "success" } with open("transcribe.log", 'a', encoding='utf-8') as lf: lf.write(json.dumps(log_entry, ensure_ascii=False) + "\n") print(f"[SUCCESS] Transcribed: {audio_path} -> {output_path}") return 0 if __name__ == "__main__": parser = argparse.ArgumentParser(description="Whisper Large-v3 CLI Mode") parser.add_argument("--audio", type=str, help="Path to audio file") parser.add_argument("--output", type=str, default="./transcripts", help="Output directory") parser.add_argument("--translate", action="store_true", help="Translate to English") parser.add_argument("--web", action="store_true", help="Launch Gradio web UI (default)") args = parser.parse_args() if args.audio: # CLI 模式 os.makedirs(args.output, exist_ok=True) exit(transcribe_cli(args.audio, args.output, args.translate)) else: # 默认启动 Web UI import gradio as gr # ...原有 Gradio 接口代码...3.2 创建独立的调度脚本
新建/root/Whisper-large-v3/scheduler.sh,用于扫描待处理音频并调用 CLI。
#!/bin/bash # scheduler.sh - Whisper 定时识别调度脚本 ROOT_DIR="/root/Whisper-large-v3" AUDIO_INPUT="/data/audio/incoming" TRANSCRIPT_OUTPUT="/data/audio/transcripts" LOG_FILE="$ROOT_DIR/scheduler.log" PYTHON_CMD="python3 $ROOT_DIR/app.py" # 日志函数 log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } cd $ROOT_DIR || exit 1 # 检查是否已有进程运行(防重复执行) if pgrep -f "app.py.*--audio" > /dev/null; then log "SKIP: Another transcription process is running." exit 0 fi # 查找未处理的音频文件(排除已处理过的) find "$AUDIO_INPUT" -type f \( -name "*.wav" -o -name "*.mp3" -o -name "*.m4a" -o -name "*.flac" -o -name "*.ogg" \) | while read audio_file; do base_name=$(basename "$audio_file") # 判断是否已转录(简单去重机制) if [ -f "$TRANSCRIPT_OUTPUT/${base_name%.*}.txt" ] || [ -f "$TRANSCRIPT_OUTPUT/${base_name%.*}.en.txt" ]; then log "SKIP: Already processed $base_name" continue fi log "PROCESSING: $base_name" # 执行转录(可根据需求启用翻译) if $PYTHON_CMD --audio "$audio_file" --output "$TRANSCRIPT_OUTPUT" --translate; then log "COMPLETED: $base_name" # 可选:移动原文件至归档目录 # mv "$audio_file" "/data/audio/archive/" else log "FAILED: $base_name" fi done log "Scheduling run completed."赋予执行权限:
chmod +x /root/Whisper-large-v3/scheduler.sh3.3 配置 cron 定时任务
编辑 crontab:
crontab -e添加如下条目(每小时执行一次):
0 * * * * /root/Whisper-large-v3/scheduler.sh >> /root/Whisper-large-v3/cron.log 2>&1其他常用调度示例:
# 每天凌晨2点运行 0 2 * * * /root/Whisper-large-v3/scheduler.sh # 每30分钟运行一次 */30 * * * * /root/Whisper-large-v3/scheduler.sh # 工作日早8点运行 0 8 * * 1-5 /root/Whisper-large-v3/scheduler.sh3.4 目录结构优化建议
调整项目结构以支持批处理:
/data/audio/ ├── incoming/ # 新增音频存放处 ├── transcripts/ # 转录文本输出 ├── archive/ # 已处理音频归档 └── failed/ # 识别失败文件保留确保路径存在:
mkdir -p /data/audio/{incoming,transcripts,archive,failed}4. 实践问题与优化
4.1 常见问题及解决方案
| 问题 | 原因 | 解决方法 |
|---|---|---|
ModuleNotFoundError: No module named 'whisper' | Python 环境未激活 | 在 cron 中使用绝对路径调用 pipenv 或 conda |
| 音频文件被重复处理 | 缺少去重机制 | 使用输出文件存在判断或数据库记录 |
| GPU 显存溢出 | 并发任务过多 | 添加进程锁防止并发 |
| 中文路径乱码 | 编码问题 | 设置环境变量LANG=en_US.UTF-8 |
修复示例:在 cron 中指定完整环境
0 * * * * export LANG=en_US.UTF-8 && /usr/bin/python3 /root/Whisper-large-v3/scheduler.sh4.2 性能优化建议
- 避免高频调度:由于 Whisper large-v3 单次推理约需 30~60 秒(视音频长度),建议调度间隔 ≥5 分钟。
- 启用进程互斥锁:
# 修改 cron 命令,使用 flock 防止并发 0 * * * * flock -n /tmp/whisper.lock /root/Whisper-large-v3/scheduler.sh- 按大小过滤长音频:可在 shell 脚本中加入判断,跳过超过 10MB 的文件(约 10 分钟音频)
file_size=$(stat -c%s "$audio_file") if [ $file_size -gt 10485760 ]; then log "SKIP: File too large (>10MB): $base_name" continue fi- 异步提交 + 队列机制(进阶):可结合 Redis 或 SQLite 实现任务队列,进一步提升稳定性。
5. 总结
5.1 实践经验总结
通过本次实践,我们成功将一个交互式 Web 语音识别服务升级为支持自动化调度的生产级系统。关键收获包括:
- CLI 扩展是 Web 服务自动化的第一步,应作为标准开发规范纳入项目。
- cron 虽简单,但配合 shell 脚本能实现强大功能,适合资源受限环境。
- 日志与状态追踪必不可少,便于后期审计与故障排查。
- 去重与容错机制决定系统健壮性,不可忽视。
5.2 最佳实践建议
- 始终使用绝对路径:在 cron 和脚本中避免相对路径导致的执行失败。
- 定期清理日志文件:防止
cron.log和transcribe.log过大影响磁盘空间。 - 监控 GPU 利用率:可通过
nvidia-smi结合脚本记录资源使用情况,评估扩容需求。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。