Auto backup before evolver run - 2026年 03月 10日 星期二 19:00:50 CST
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
# KB Whisper 环境变量配置示例
|
||||
# 复制此文件为 .env 并填写实际值
|
||||
|
||||
# Whisper.cpp 安装路径
|
||||
WHISPER_CPP_PATH=~/whisper.cpp
|
||||
@@ -0,0 +1,211 @@
|
||||
# KB Whisper - 本地语音识别
|
||||
|
||||
基于 Whisper.cpp 的本地语音识别工具,支持将音视频文件转换为文本、字幕和 JSON 格式。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🎯 **本地执行** - 完全离线运行,保护隐私
|
||||
- 🚀 **多模型支持** - 从 tiny 到 large,根据需求选择速度/精度
|
||||
- 📝 **多格式输出** - JSON、SRT、TXT 三种格式
|
||||
- ⚡ **异步执行** - 支持后台运行,不阻塞主代理
|
||||
- 🎬 **广泛兼容** - 支持常见音视频格式(MP3、MP4、WAV 等)
|
||||
|
||||
## 安装前提
|
||||
|
||||
### 1. 安装 whisper.cpp
|
||||
|
||||
```bash
|
||||
# 克隆仓库
|
||||
git clone https://github.com/ggerganov/whisper.cpp.git ~/whisper.cpp
|
||||
|
||||
# 编译
|
||||
cd ~/whisper.cpp
|
||||
make
|
||||
|
||||
# 下载模型(至少需要一个模型)
|
||||
./models/download-ggml-model.sh tiny
|
||||
./models/download-ggml-model.sh base # 可选
|
||||
```
|
||||
|
||||
### 2. 配置环境变量
|
||||
|
||||
在 `~/.clawdbot/clawdbot.json` 中添加:
|
||||
|
||||
```json5
|
||||
{
|
||||
skills: {
|
||||
entries: {
|
||||
"kb-whisper": {
|
||||
env: {
|
||||
WHISPER_CPP_PATH: "~/whisper.cpp"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
或通过命令行配置:
|
||||
|
||||
```bash
|
||||
clawdbot skills config kb-whisper WHISPER_CPP_PATH "~/whisper.cpp"
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
# 使用默认 tiny 模型处理
|
||||
python whisper.py --input video.mp4
|
||||
|
||||
# 使用 base 模型(推荐,平衡速度和精度)
|
||||
python whisper.py --input video.mp4 --model base
|
||||
|
||||
# 后台异步执行
|
||||
python whisper.py --input video.mp4 --background
|
||||
```
|
||||
|
||||
### 指定输出目录
|
||||
|
||||
```bash
|
||||
python whisper.py --input video.mp4 --output ./transcripts
|
||||
```
|
||||
|
||||
### 处理音频文件
|
||||
|
||||
```bash
|
||||
python whisper.py --input podcast.mp3 --model base
|
||||
```
|
||||
|
||||
## 输出文件
|
||||
|
||||
对于输入 `video.mp4`,将生成以下文件:
|
||||
|
||||
```
|
||||
output/
|
||||
├── video.json # 详细结果(含时间戳)
|
||||
├── video.srt # 字幕格式
|
||||
└── video.txt # 纯文本
|
||||
```
|
||||
|
||||
### JSON 格式
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"input_file": "video.mp4",
|
||||
"model": "base",
|
||||
"output_files": {
|
||||
"json": "video.json",
|
||||
"srt": "video.srt",
|
||||
"txt": "video.txt"
|
||||
},
|
||||
"duration_seconds": 120.5,
|
||||
"segments": [
|
||||
{
|
||||
"id": 0,
|
||||
"start": 0.0,
|
||||
"end": 3.2,
|
||||
"text": "这是第一段字幕"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"start": 3.2,
|
||||
"end": 6.8,
|
||||
"text": "这是第二段字幕"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 模型对比
|
||||
|
||||
| 模型 | 速度 | 精度 | 适用场景 |
|
||||
|------|------|------|----------|
|
||||
| tiny | ⚡⚡⚡ | ⭐ | 快速预览、实时转录 |
|
||||
| base | ⚡⚡ | ⭐⭐⭐ | 日常使用(推荐) |
|
||||
| small | ⚡ | ⭐⭐⭐⭐ | 高质量需求 |
|
||||
| medium | ⭐ | ⭐⭐⭐⭐⭐ | 专业用途 |
|
||||
| large | 🐢 | ⭐⭐⭐⭐⭐ | 最高精度要求 |
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 示例 1: 转录播客
|
||||
|
||||
```bash
|
||||
python whisper.py --input podcast.mp3 --model base --background
|
||||
```
|
||||
|
||||
### 示例 2: 生成视频字幕
|
||||
|
||||
```bash
|
||||
python whisper.py --input tutorial.mp4 --model small
|
||||
```
|
||||
|
||||
### 示例 3: 批量处理(配合脚本)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
for video in videos/*.mp4; do
|
||||
python whisper.py --input "$video" --model base
|
||||
done
|
||||
```
|
||||
|
||||
## 在 Workflow 中使用
|
||||
|
||||
在知识库处理流程中,如果视频没有字幕:
|
||||
|
||||
```bash
|
||||
# 步骤4: 下载视频后,如果没有字幕
|
||||
# 步骤5: 使用 Whisper 生成字幕
|
||||
python whisper.py --input ${video_path} --model base --background
|
||||
|
||||
# 等待 Whisper 完成,然后继续后续步骤
|
||||
```
|
||||
|
||||
## 技术细节
|
||||
|
||||
- **引擎**: Whisper.cpp(C++ 实现,性能优异)
|
||||
- **音频采样率**: 自动转换为 16kHz
|
||||
- **语言**: 自动检测(支持 99 种语言)
|
||||
- **GPU 支持**: whisper.cpp 支持 CUDA 加速(需编译时启用)
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题: 找不到 whisper.cpp
|
||||
|
||||
```bash
|
||||
# 检查环境变量
|
||||
echo $WHISPER_CPP_PATH
|
||||
|
||||
# 手动测试
|
||||
~/whisper.cpp/main --help
|
||||
```
|
||||
|
||||
### 问题: 模型未下载
|
||||
|
||||
```bash
|
||||
cd ~/whisper.cpp
|
||||
./models/download-ggml-model.sh tiny
|
||||
```
|
||||
|
||||
### 问题: 音频格式不支持
|
||||
|
||||
```bash
|
||||
# 转换音频格式
|
||||
ffmpeg -i input.avi -acodec libmp3lame output.mp3
|
||||
python whisper.py --input output.mp3
|
||||
```
|
||||
|
||||
## 性能参考
|
||||
|
||||
在 CPU 环境下(Intel i7):
|
||||
|
||||
- **tiny 模型**: ~1x 实时速度(1分钟音频 ~1分钟处理)
|
||||
- **base 模型**: ~0.5x 实时速度(1分钟音频 ~2分钟处理)
|
||||
- **small 模型**: ~0.25x 实时速度(1分钟音频 ~4分钟处理)
|
||||
|
||||
## 许可证
|
||||
|
||||
本技能使用 MIT 许可证,Whisper.cpp 使用 MIT 许可证。
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
name: kb-whisper
|
||||
description: 本地语音识别,支持音视频转文字(基于 Whisper.cpp)
|
||||
---
|
||||
|
||||
# KB Whisper Skill
|
||||
|
||||
**kb-whisper** - 本地语音识别,使用 Whisper.cpp 将音视频文件转换为文本、字幕和 JSON 格式输出。
|
||||
|
||||
## 什么时候使用
|
||||
|
||||
当用户需要:
|
||||
- 将视频/音频文件转换为文字
|
||||
- 生成视频字幕(SRT 格式)
|
||||
- 获取带时间戳的详细识别结果
|
||||
|
||||
## 环境变量
|
||||
|
||||
在使用之前,必须检查环境变量是否已经设置。
|
||||
|
||||
| 变量名 | 用途 | 示例 |
|
||||
|--------|------|------|
|
||||
| `WHISPER_CPP_PATH` | whisper.cpp 安装路径 | `~/whisper.cpp` |
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
# 基本用法
|
||||
python whisper.py --input video.mp4
|
||||
|
||||
# 指定模型(默认 tiny)
|
||||
python whisper.py --input video.mp4 --model base
|
||||
|
||||
# 指定输出目录
|
||||
python whisper.py --input video.mp4 --output ./output
|
||||
|
||||
# 异步执行(推荐)
|
||||
python whisper.py --input video.mp4 --background
|
||||
```
|
||||
|
||||
## 命令参数
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `--input` | 输入音视频文件路径 | 必需 |
|
||||
| `--model` | Whisper 模型类型 | `tiny` |
|
||||
| `--output` | 输出目录 | 文件所在目录 |
|
||||
| `--background` | 后台异步执行 | False |
|
||||
|
||||
## 模型选项
|
||||
|
||||
- `tiny` - 最小模型,速度快,精度较低(推荐快速识别)
|
||||
- `base` - 基础模型,平衡速度和精度(推荐)
|
||||
- `small` - 小型模型,精度更高,速度较慢
|
||||
- `medium` - 中型模型,高精度
|
||||
- `large` - 大型模型,最高精度,速度慢
|
||||
|
||||
## 输出文件
|
||||
|
||||
对于输入 `video.mp4`,将生成:
|
||||
|
||||
- `video.json` - 详细结果(包含时间戳、分段信息)
|
||||
- `video.srt` - 字幕格式文件
|
||||
- `video.txt` - 纯文本内容
|
||||
|
||||
## 输出格式示例
|
||||
|
||||
### JSON 格式
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"input_file": "video.mp4",
|
||||
"model": "tiny",
|
||||
"output_files": {
|
||||
"json": "video.json",
|
||||
"srt": "video.srt",
|
||||
"txt": "video.txt"
|
||||
},
|
||||
"duration_seconds": 120.5,
|
||||
"segments": [
|
||||
{
|
||||
"id": 0,
|
||||
"start": 0.0,
|
||||
"end": 3.2,
|
||||
"text": "这是第一段字幕"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Workflow 中的使用
|
||||
|
||||
```bash
|
||||
# 步骤4之后:如果已有音频但无字幕,可以使用 whisper
|
||||
python whisper.py --input ${video_path} --model base --background
|
||||
```
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
# 1. 克隆 whisper.cpp
|
||||
git clone https://github.com/ggerganov/whisper.cpp.git ~/whisper.cpp
|
||||
|
||||
# 2. 编译
|
||||
cd ~/whisper.cpp
|
||||
make
|
||||
|
||||
# 3. 下载模型(tiny)
|
||||
./models/download-ggml-model.sh tiny
|
||||
|
||||
# 4. 下载更多模型(可选)
|
||||
# ./models/download-ggml-model.sh base
|
||||
# ./models/download-ggml-model.sh small
|
||||
|
||||
# 5. 复制技能到 skills 目录
|
||||
cp -r kb-whisper/ ~/skills/
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- **异步执行**: 使用 `--background` 参数时,命令会在后台运行,不阻塞主代理
|
||||
- **模型选择**: tiny 模型适合快速识别,base 模型适合一般用途,large 模型适合高精度需求
|
||||
- **文件格式**: 支持 MP3、MP4、WAV、FLAC、OGG、M4A 等常见格式
|
||||
Binary file not shown.
@@ -0,0 +1,273 @@
|
||||
# KB Whisper 使用示例
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 基础使用
|
||||
|
||||
```bash
|
||||
# 使用默认 tiny 模型转录视频
|
||||
python whisper.py --input video.mp4
|
||||
|
||||
# 使用 base 模型(推荐)
|
||||
python whisper.py --input video.mp4 --model base
|
||||
```
|
||||
|
||||
### 2. 异步执行
|
||||
|
||||
```bash
|
||||
# 后台处理,不阻塞主代理
|
||||
python whisper.py --input video.mp4 --model base --background
|
||||
```
|
||||
|
||||
### 3. 指定输出目录
|
||||
|
||||
```bash
|
||||
python whisper.py --input video.mp4 --output ./transcripts
|
||||
```
|
||||
|
||||
### 4. 指定语言
|
||||
|
||||
```bash
|
||||
# 指定中文
|
||||
python whisper.py --input video.mp4 --language zh
|
||||
|
||||
# 指定英文
|
||||
python whisper.py --input video.mp4 --language en
|
||||
|
||||
# 指定日语
|
||||
python whisper.py --input video.mp4 --language ja
|
||||
```
|
||||
|
||||
## 实际应用场景
|
||||
|
||||
### 场景 1: 转录播客
|
||||
|
||||
```bash
|
||||
python whisper.py --input podcast.mp3 --model base --background
|
||||
```
|
||||
|
||||
**输出**:
|
||||
- `podcast.json` - 带时间戳的详细结果
|
||||
- `podcast.srt` - 字幕格式
|
||||
- `podcast.txt` - 纯文本
|
||||
|
||||
### 场景 2: 生成视频字幕
|
||||
|
||||
```bash
|
||||
python whisper.py --input tutorial.mp4 --model small
|
||||
```
|
||||
|
||||
**用途**:
|
||||
- 为教程视频生成字幕
|
||||
- 导出 SRT 文件到视频编辑软件
|
||||
- 生成多语言字幕版本
|
||||
|
||||
### 场景 3: 会议录音转文字
|
||||
|
||||
```bash
|
||||
python whisper.py --input meeting.mp3 --model base --output ./meetings
|
||||
```
|
||||
|
||||
**输出示例**:
|
||||
```
|
||||
meetings/
|
||||
├── meeting.json # 完整 JSON(含时间戳)
|
||||
├── meeting.srt # 字幕格式
|
||||
└── meeting.txt # 纯文本
|
||||
```
|
||||
|
||||
### 场景 4: 批量处理
|
||||
|
||||
创建脚本 `batch_transcribe.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
OUTPUT_DIR="./transcripts"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
for video in videos/*.mp4; do
|
||||
echo "处理: $video"
|
||||
python whisper.py --input "$video" --output "$OUTPUT_DIR" --model base
|
||||
done
|
||||
|
||||
echo "批量处理完成!"
|
||||
```
|
||||
|
||||
```bash
|
||||
chmod +x batch_transcribe.sh
|
||||
./batch_transcribe.sh
|
||||
```
|
||||
|
||||
## 在 Workflow 中使用
|
||||
|
||||
### 知识库处理流程
|
||||
|
||||
在 `kb-workflow` 技能中,如果视频没有字幕:
|
||||
|
||||
```bash
|
||||
# 步骤 4: 下载视频后
|
||||
# 检查是否有字幕文件
|
||||
if [ ! -f "${subtitle_file}" ]; then
|
||||
echo "无字幕文件,使用 Whisper 生成..."
|
||||
|
||||
# 使用 Whisper 生成字幕(后台执行)
|
||||
python /path/to/whisper.py \
|
||||
--input "${video_path}" \
|
||||
--model base \
|
||||
--output "${content_dir}" \
|
||||
--background
|
||||
|
||||
# 等待 Whisper 完成
|
||||
sleep 5 # 等待后台任务启动
|
||||
while ! [ -f "${content_dir}/$(basename ${video_path} .mp4).srt" ]; do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Whisper 处理完成"
|
||||
fi
|
||||
```
|
||||
|
||||
## 输出文件使用
|
||||
|
||||
### JSON 格式
|
||||
|
||||
```python
|
||||
import json
|
||||
|
||||
# 读取 JSON 结果
|
||||
with open('video.json', 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# 访问分段
|
||||
for segment in data['segments']:
|
||||
print(f"[{segment['start']:.2f}-{segment['end']:.2f}] {segment['text']}")
|
||||
```
|
||||
|
||||
### SRT 格式
|
||||
|
||||
```bash
|
||||
# 直接在视频播放器中使用
|
||||
mpv video.mp4 --sub-file=video.srt
|
||||
|
||||
# 或转换字幕格式
|
||||
ffmpeg -i video.srt video.ass
|
||||
```
|
||||
|
||||
### TXT 格式
|
||||
|
||||
```bash
|
||||
# 搜索关键词
|
||||
grep "重要信息" video.txt
|
||||
|
||||
# 统计字数
|
||||
wc -m video.txt
|
||||
```
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
### 1. 根据内容选择模型
|
||||
|
||||
```bash
|
||||
# 快速预览(短视频、测试)
|
||||
python whisper.py --input video.mp4 --model tiny
|
||||
|
||||
# 日常使用(播客、会议)
|
||||
python whisper.py --input video.mp4 --model base
|
||||
|
||||
# 高质量需求(正式内容、多语言)
|
||||
python whisper.py --input video.mp4 --model small
|
||||
```
|
||||
|
||||
### 2. 使用后台执行
|
||||
|
||||
```bash
|
||||
# 处理长文件时使用后台模式
|
||||
python whisper.py --input long_video.mp4 --model base --background
|
||||
```
|
||||
|
||||
### 3. 指定语言提升准确度
|
||||
|
||||
```bash
|
||||
# 如果知道内容语言,指定后准确度更高
|
||||
python whisper.py --input video.mp4 --language zh
|
||||
```
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题 1: whisper.cpp 未找到
|
||||
|
||||
```bash
|
||||
# 检查环境变量
|
||||
echo $WHISPER_CPP_PATH
|
||||
|
||||
# 或配置
|
||||
clawdbot skills config kb-whisper WHISPER_CPP_PATH "~/whisper.cpp"
|
||||
```
|
||||
|
||||
### 问题 2: 模型未下载
|
||||
|
||||
```bash
|
||||
cd ~/whisper.cpp
|
||||
./models/download-ggml-model.sh base
|
||||
```
|
||||
|
||||
### 问题 3: 音频格式不支持
|
||||
|
||||
```bash
|
||||
# 转换格式
|
||||
ffmpeg -i input.avi -acodec libmp3lame output.mp3
|
||||
python whisper.py --input output.mp3
|
||||
```
|
||||
|
||||
### 问题 4: 后台任务未完成
|
||||
|
||||
```bash
|
||||
# 查看日志
|
||||
cat video_whisper.log
|
||||
|
||||
# 检查进程
|
||||
ps aux | grep whisper
|
||||
```
|
||||
|
||||
## 集成到其他工具
|
||||
|
||||
### 集成到 Python 脚本
|
||||
|
||||
```python
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
def transcribe_audio(audio_path, model='base'):
|
||||
"""调用 whisper.py 转录音频"""
|
||||
cmd = [
|
||||
'python3', '/path/to/whisper.py',
|
||||
'--input', audio_path,
|
||||
'--model', model
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
return json.loads(result.stdout)
|
||||
|
||||
# 使用
|
||||
result = transcribe_audio('podcast.mp3', 'base')
|
||||
print(f"识别到 {result['total_segments']} 个片段")
|
||||
```
|
||||
|
||||
### 集成到 Shell 脚本
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
transcribe() {
|
||||
local file=$1
|
||||
local model=${2:-base}
|
||||
|
||||
echo "转录: $file"
|
||||
python3 whisper.py --input "$file" --model "$model" --background
|
||||
}
|
||||
|
||||
# 批量处理
|
||||
transcribe "video1.mp4"
|
||||
transcribe "video2.mp4" "tiny"
|
||||
```
|
||||
@@ -0,0 +1,403 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
KB Whisper - 本地语音识别工具
|
||||
基于 Whisper.cpp 的音视频转文字
|
||||
|
||||
功能:
|
||||
- 自动将视频/音频转换为 16kHz WAV 格式
|
||||
- 支持指定语言(默认中文)
|
||||
- 输出标准 JSON 格式供 KB Workflow 使用
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
"""解析命令行参数"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='KB Whisper - 本地语音识别工具',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
示例:
|
||||
python whisper.py --input video.mp4
|
||||
python whisper.py --input video.mp4 --model base
|
||||
python whisper.py --input video.mp4 --output ./output --background
|
||||
"""
|
||||
)
|
||||
parser.add_argument(
|
||||
'--input',
|
||||
required=True,
|
||||
help='输入音视频文件路径'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--model',
|
||||
default='base',
|
||||
choices=['tiny', 'base', 'small', 'medium', 'large'],
|
||||
help='Whisper 模型类型 (默认: base)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--output',
|
||||
help='输出目录 (默认: 输入文件所在目录)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--background',
|
||||
action='store_true',
|
||||
help='后台异步执行'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--language',
|
||||
default='zh',
|
||||
help='指定语言 (默认: zh),例如: en, zh, ja'
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_whisper_cpp_path():
|
||||
"""获取 whisper.cpp 路径"""
|
||||
# 优先从环境变量获取
|
||||
path = os.environ.get('WHISPER_CPP_PATH')
|
||||
if path:
|
||||
path = os.path.expanduser(path)
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
|
||||
# 尝试常见位置
|
||||
common_paths = [
|
||||
os.path.expanduser('~/whisper.cpp'),
|
||||
'/usr/local/bin/whisper',
|
||||
'/opt/whisper.cpp',
|
||||
]
|
||||
|
||||
for cp in common_paths:
|
||||
if os.path.exists(cp):
|
||||
return cp
|
||||
|
||||
raise FileNotFoundError(
|
||||
"未找到 whisper.cpp。请设置环境变量 WHISPER_CPP_PATH "
|
||||
"或安装到 ~/whisper.cpp\n"
|
||||
"安装方法: git clone https://github.com/ggerganov/whisper.cpp.git ~/whisper.cpp && cd ~/whisper.cpp && make"
|
||||
)
|
||||
|
||||
|
||||
def check_model(whisper_path, model):
|
||||
"""检查模型是否存在"""
|
||||
model_path = os.path.join(whisper_path, 'models', f'ggml-{model}.bin')
|
||||
if not os.path.exists(model_path):
|
||||
raise FileNotFoundError(
|
||||
f"模型文件不存在: {model_path}\n"
|
||||
f"请运行以下命令下载模型:\n"
|
||||
f"cd {whisper_path} && ./models/download-ggml-model.sh {model}"
|
||||
)
|
||||
return model_path
|
||||
|
||||
|
||||
def convert_to_wav(input_path, output_dir):
|
||||
"""将音视频转换为 16kHz WAV 格式
|
||||
|
||||
Whisper 需要 WAV 格式的输入文件。
|
||||
"""
|
||||
input_path = os.path.abspath(input_path)
|
||||
|
||||
# 如果已经是 WAV 且采样率正确,直接返回
|
||||
if input_path.lower().endswith('.wav'):
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['ffprobe', '-v', 'error', '-show_entries',
|
||||
'stream=sample_rate', '-of', 'default=noprint_wrappers=1:nokey=1',
|
||||
input_path],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode == 0 and int(result.stdout.strip()) == 16000:
|
||||
print(f"输入文件已是 16kHz WAV 格式,直接使用")
|
||||
return input_path
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 转换
|
||||
output_path = os.path.join(output_dir, f"{Path(input_path).stem}_audio.wav")
|
||||
|
||||
print(f"转换音频格式: {input_path} -> {output_path}")
|
||||
|
||||
try:
|
||||
subprocess.run([
|
||||
'ffmpeg', '-y', '-i', input_path,
|
||||
'-acodec', 'pcm_s16le',
|
||||
'-ar', '16000',
|
||||
'-ac', '1',
|
||||
'-loglevel', 'error',
|
||||
output_path
|
||||
], check=True, capture_output=True)
|
||||
print("音频转换完成")
|
||||
return output_path
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"音频转换失败: {e}")
|
||||
raise RuntimeError(f"无法转换音频格式: {e}")
|
||||
except FileNotFoundError:
|
||||
raise RuntimeError("未找到 ffmpeg,请安装: sudo apt-get install ffmpeg")
|
||||
|
||||
|
||||
def run_whisper(whisper_path, audio_path, model, output_dir, language='zh'):
|
||||
"""运行 Whisper 语音识别
|
||||
|
||||
Args:
|
||||
whisper_path: whisper.cpp 目录
|
||||
audio_path: WAV 音频文件路径
|
||||
model: 模型名称
|
||||
output_dir: 输出目录
|
||||
language: 语言代码
|
||||
"""
|
||||
# 输出文件名(使用原始输入文件名,不是 audio.wav)
|
||||
# 从 output_dir 和 audio_path 推断原始文件名
|
||||
original_stem = Path(audio_path).stem.replace('_audio', '')
|
||||
output_basename = original_stem
|
||||
|
||||
# 构建命令
|
||||
cmd = [
|
||||
os.path.join(whisper_path, 'whisper-cli'),
|
||||
'-m', os.path.join(whisper_path, 'models', f'ggml-{model}.bin'),
|
||||
'-f', audio_path,
|
||||
'-of', os.path.join(output_dir, output_basename),
|
||||
'-l', language,
|
||||
'--output-txt',
|
||||
'--output-srt',
|
||||
'--output-json',
|
||||
]
|
||||
|
||||
print(f"执行命令: {' '.join(cmd)}")
|
||||
|
||||
# 运行
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=whisper_path,
|
||||
timeout=3600 # 1小时超时
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
# 检查是否有警告但仍成功
|
||||
if "error" in result.stderr.lower() and not os.path.exists(
|
||||
os.path.join(output_dir, f"{output_basename}.txt")
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"Whisper 执行失败:\n"
|
||||
f"标准输出: {result.stdout}\n"
|
||||
f"错误输出: {result.stderr}"
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def parse_srt_for_duration(srt_file):
|
||||
"""从 SRT 文件解析时长"""
|
||||
if not os.path.exists(srt_file):
|
||||
return 0
|
||||
|
||||
with open(srt_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# 找到最后一个时间戳
|
||||
pattern = r'(\d{2}:\d{2}:\d{2}),\d{3} --> \d{2}:\d{2}:\d{2},\d{3}'
|
||||
matches = re.findall(pattern, content)
|
||||
|
||||
if not matches:
|
||||
return 0
|
||||
|
||||
# 解析最后一个时间戳
|
||||
last_time = matches[-1]
|
||||
h, m, s = last_time.split(':')
|
||||
return int(h) * 3600 + int(m) * 60 + int(s)
|
||||
|
||||
|
||||
def create_workflow_json(input_file, output_dir, model, language, duration):
|
||||
"""创建标准格式的 JSON 输出,供 KB Workflow 使用"""
|
||||
input_path = Path(input_file)
|
||||
basename = input_path.stem
|
||||
|
||||
txt_file = os.path.join(output_dir, f"{basename}.txt")
|
||||
srt_file = os.path.join(output_dir, f"{basename}.srt")
|
||||
json_file = os.path.join(output_dir, f"{basename}.json")
|
||||
|
||||
# 读取文本内容
|
||||
text_content = ""
|
||||
if os.path.exists(txt_file):
|
||||
with open(txt_file, 'r', encoding='utf-8') as f:
|
||||
text_content = f.read().strip()
|
||||
|
||||
# 解析 SRT 获取分段
|
||||
segments = []
|
||||
if os.path.exists(srt_file):
|
||||
with open(srt_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# SRT 格式: 序号 -> 时间 -> 文本
|
||||
pattern = r'(\d+)\n(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})\n(.+?)(?=\n\n|\Z)'
|
||||
matches = re.findall(pattern, content, re.DOTALL)
|
||||
|
||||
def parse_time(t):
|
||||
h, m, s_ms = t.split(':')
|
||||
s, ms = s_ms.split(',')
|
||||
return float(h) * 3600 + float(m) * 60 + float(s) + float(ms) / 1000
|
||||
|
||||
for idx, (num, start, end, text) in enumerate(matches):
|
||||
segments.append({
|
||||
"id": idx,
|
||||
"start": round(parse_time(start), 2),
|
||||
"end": round(parse_time(end), 2),
|
||||
"text": text.strip()
|
||||
})
|
||||
|
||||
# 构建 JSON
|
||||
workflow_json = {
|
||||
"success": True,
|
||||
"tool": "whisper",
|
||||
"model": model,
|
||||
"language": language,
|
||||
"input_file": str(input_path),
|
||||
"output_files": {
|
||||
"txt": f"{basename}.txt",
|
||||
"srt": f"{basename}.srt",
|
||||
"json": f"{basename}.json"
|
||||
},
|
||||
"duration_seconds": duration,
|
||||
"segments": segments,
|
||||
"text": text_content
|
||||
}
|
||||
|
||||
# 写入 JSON
|
||||
with open(json_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(workflow_json, f, ensure_ascii=False, indent=2)
|
||||
|
||||
return workflow_json
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
args = parse_arguments()
|
||||
|
||||
# 检查输入文件
|
||||
input_file = os.path.abspath(args.input)
|
||||
if not os.path.exists(input_file):
|
||||
print(f"错误: 输入文件不存在: {input_file}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 获取输出目录
|
||||
if args.output:
|
||||
output_dir = os.path.abspath(args.output)
|
||||
else:
|
||||
output_dir = os.path.dirname(input_file)
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# 获取 whisper.cpp 路径
|
||||
try:
|
||||
whisper_path = get_whisper_cpp_path()
|
||||
print(f"✓ Whisper.cpp 路径: {whisper_path}")
|
||||
except FileNotFoundError as e:
|
||||
print(f"✗ 错误: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 检查模型
|
||||
try:
|
||||
check_model(whisper_path, args.model)
|
||||
print(f"✓ 使用模型: {args.model}")
|
||||
except FileNotFoundError as e:
|
||||
print(f"✗ 错误: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 后台执行
|
||||
if args.background:
|
||||
print("启动后台处理...")
|
||||
log_file = os.path.join(output_dir, f"{Path(input_file).stem}_whisper.log")
|
||||
|
||||
with open(log_file, 'w') as log:
|
||||
subprocess.Popen(
|
||||
[sys.executable, __file__,
|
||||
'--input', input_file,
|
||||
'--model', args.model,
|
||||
'--output', output_dir,
|
||||
'--language', args.language],
|
||||
stdout=log,
|
||||
stderr=log,
|
||||
start_new_session=True
|
||||
)
|
||||
|
||||
print(f"✓ 后台任务已启动")
|
||||
print(f" 日志: {log_file}")
|
||||
print(f" 输出: {output_dir}")
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"background": True,
|
||||
"status": "running",
|
||||
"log_file": log_file,
|
||||
"output_dir": output_dir
|
||||
}
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return
|
||||
|
||||
# 前台执行
|
||||
print(f"\n📁 处理文件: {input_file}")
|
||||
print(f"📁 输出目录: {output_dir}")
|
||||
|
||||
try:
|
||||
# 1. 转换为 WAV
|
||||
print(f"\n🔄 步骤1: 转换音频格式...")
|
||||
audio_path = convert_to_wav(input_file, output_dir)
|
||||
|
||||
# 2. 运行 Whisper
|
||||
print(f"\n🎙️ 步骤2: 语音识别 (语言: {args.language})...")
|
||||
run_whisper(whisper_path, audio_path, args.model, output_dir, args.language)
|
||||
|
||||
# 3. 清理临时 WAV 文件
|
||||
if audio_path != input_file and audio_path.endswith('_audio.wav'):
|
||||
try:
|
||||
os.remove(audio_path)
|
||||
print("✓ 清理临时文件")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 4. 获取时长并创建 JSON
|
||||
print(f"\n📝 步骤3: 生成输出文件...")
|
||||
basename = Path(input_file).stem
|
||||
srt_file = os.path.join(output_dir, f"{basename}.srt")
|
||||
duration = parse_srt_for_duration(srt_file)
|
||||
|
||||
workflow_json = create_workflow_json(
|
||||
input_file, output_dir, args.model, args.language, duration
|
||||
)
|
||||
|
||||
# 输出结果
|
||||
print(f"\n✅ 处理完成!")
|
||||
print(f" 时长: {duration} 秒")
|
||||
print(f" 分段数: {len(workflow_json['segments'])}")
|
||||
print(f"\n📄 输出文件:")
|
||||
for fmt in ['txt', 'srt', 'json']:
|
||||
fpath = os.path.join(output_dir, f"{basename}.{fmt}")
|
||||
if os.path.exists(fpath):
|
||||
size = os.path.getsize(fpath)
|
||||
print(f" • {fmt.upper()}: {fpath} ({size:,} bytes)")
|
||||
|
||||
print(f"\n📋 Workflow JSON:")
|
||||
print(json.dumps({
|
||||
"success": workflow_json["success"],
|
||||
"tool": "whisper",
|
||||
"text_length": len(workflow_json["text"]),
|
||||
"segments_count": len(workflow_json["segments"])
|
||||
}, ensure_ascii=False, indent=2))
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n✗ 错误: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user