添加技能模块
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# FunASR 模型缓存目录
|
||||
FUNASR_CACHE_DIR=~/.cache/modelscope/hub
|
||||
@@ -0,0 +1,212 @@
|
||||
# KB FunASR - 本地语音识别
|
||||
|
||||
基于阿里 FunASR 的本地语音识别工具,支持将音视频文件转换为文本和 JSON 格式。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🎯 **本地执行** - 完全离线运行,保护隐私
|
||||
- 🚀 **多模型支持** - Paraformer、SenseVoice 等模型可选
|
||||
- 📝 **多格式输出** - JSON、TXT 两种格式
|
||||
- ⚡ **异步执行** - 支持后台运行,不阻塞主代理
|
||||
- 🎬 **广泛兼容** - 支持常见音视频格式(MP3、MP4、WAV 等)
|
||||
- 🇨🇳 **中文优化** - Paraformer 模型针对中文语音优化
|
||||
|
||||
## 安装前提
|
||||
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
pip install funasr modelscope
|
||||
```
|
||||
|
||||
### 2. 配置环境变量(可选)
|
||||
|
||||
在 `~/.clawdbot/clawdbot.json` 中添加:
|
||||
|
||||
```json5
|
||||
{
|
||||
skills: {
|
||||
entries: {
|
||||
"kb-funasr": {
|
||||
env: {
|
||||
FUNASR_CACHE_DIR: "~/.cache/modelscope/hub"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
或通过命令行配置:
|
||||
|
||||
```bash
|
||||
clawdbot skills config kb-funasr FUNASR_CACHE_DIR "~/.cache/modelscope/hub"
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
# 使用默认 Paraformer 中文模型处理
|
||||
python funasr.py --input video.mp4
|
||||
|
||||
# 后台异步执行
|
||||
python funasr.py --input video.mp4 --background
|
||||
```
|
||||
|
||||
### 指定模型
|
||||
|
||||
```bash
|
||||
# Paraformer 中文模型(推荐)
|
||||
python funasr.py --input video.mp4 --model paraformer-zh
|
||||
|
||||
# Paraformer 多语言模型
|
||||
python funasr.py --input video.mp4 --model paraformer
|
||||
|
||||
# SenseVoice 多语言模型
|
||||
python funasr.py --input video.mp4 --model sensevoice
|
||||
```
|
||||
|
||||
### 指定输出目录
|
||||
|
||||
```bash
|
||||
python funasr.py --input video.mp4 --output ./transcripts
|
||||
```
|
||||
|
||||
## 输出文件
|
||||
|
||||
对于输入 `video.mp4`,将生成以下文件:
|
||||
|
||||
```
|
||||
output/
|
||||
├── video.json # 详细结果(含时间戳)
|
||||
└── video.txt # 纯文本
|
||||
```
|
||||
|
||||
### JSON 格式
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"input_file": "video.mp4",
|
||||
"model": "paraformer-zh",
|
||||
"output_files": {
|
||||
"json": "video.json",
|
||||
"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": "这是第二段识别结果"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 模型对比
|
||||
|
||||
| 模型 | 速度 | 精度 | 适用场景 |
|
||||
|------|------|------|----------|
|
||||
| paraformer-zh | ⚡⚡⚡ | ⭐⭐⭐⭐ | 中文语音(推荐) |
|
||||
| paraformer | ⚡⚡ | ⭐⭐⭐⭐ | 多语言识别 |
|
||||
| sensevoice | ⚡ | ⭐⭐⭐⭐⭐ | 高精度多语言 |
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 示例 1: 转录播客
|
||||
|
||||
```bash
|
||||
python funasr.py --input podcast.mp3 --model paraformer-zh --background
|
||||
```
|
||||
|
||||
### 示例 2: 生成视频识别结果
|
||||
|
||||
```bash
|
||||
python funasr.py --input tutorial.mp4 --model paraformer
|
||||
```
|
||||
|
||||
### 示例 3: 批量处理(配合脚本)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
for video in videos/*.mp4; do
|
||||
python funasr.py --input "$video" --model paraformer-zh
|
||||
done
|
||||
```
|
||||
|
||||
## 在 Workflow 中使用
|
||||
|
||||
在知识库处理流程中,如果视频没有识别结果:
|
||||
|
||||
```bash
|
||||
# 步骤4: 下载视频后
|
||||
# 步骤5: 使用 FunASR 生成识别结果
|
||||
python funasr.py --input ${video_path} --model paraformer-zh --background
|
||||
|
||||
# 等待 FunASR 完成,然后继续后续步骤
|
||||
```
|
||||
|
||||
## 技术细节
|
||||
|
||||
- **引擎**: 阿里 FunASR
|
||||
- **模型框架**: ModelScope
|
||||
- **音频采样率**: 自动转换为 16kHz
|
||||
- **语言**: 根据模型支持不同语言
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题: 模型下载失败
|
||||
|
||||
```bash
|
||||
# 首次运行会自动下载,确保网络稳定
|
||||
python funasr.py --input test.mp4
|
||||
|
||||
# 手动检查缓存目录
|
||||
ls ~/.cache/modelscope/hub/
|
||||
```
|
||||
|
||||
### 问题: 音频格式不支持
|
||||
|
||||
```bash
|
||||
# 转换音频格式
|
||||
ffmpeg -i input.avi -acodec libmp3lame output.mp3
|
||||
python funasr.py --input output.mp3
|
||||
```
|
||||
|
||||
### 问题: 依赖安装失败
|
||||
|
||||
```bash
|
||||
# 使用清华镜像源
|
||||
pip install funasr modelscope -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
```
|
||||
|
||||
## 性能参考
|
||||
|
||||
在 CPU 环境下(Intel i7):
|
||||
|
||||
- **paraformer-zh 模型**: ~2x 实时速度(1分钟音频 ~30秒处理)
|
||||
- **paraformer 模型**: ~1.5x 实时速度(1分钟音频 ~40秒处理)
|
||||
- **sensevoice 模型**: ~1x 实时速度(1分钟音频 ~1分钟处理)
|
||||
|
||||
## 与 Whisper 对比
|
||||
|
||||
| 特性 | FunASR | Whisper |
|
||||
|------|--------|---------|
|
||||
| 中文识别 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
|
||||
| 速度 | ⚡⚡⚡ | ⚡⚡ |
|
||||
| 模型大小 | 较小 | 较大 |
|
||||
| 多语言支持 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
|
||||
## 许可证
|
||||
|
||||
本技能使用 MIT 许可证,FunASR 使用 Apache 2.0 许可证。
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
name: kb-funasr
|
||||
description: 本地语音识别,支持音视频转文字(基于阿里 FunASR)
|
||||
---
|
||||
|
||||
# KB FunASR Skill
|
||||
|
||||
**kb-funasr** - 本地语音识别,使用阿里 FunASR 将音视频文件转换为文本和 JSON 格式输出。
|
||||
|
||||
## 什么时候使用
|
||||
|
||||
当用户需要:
|
||||
- 将视频/音频文件转换为文字
|
||||
- 获取带时间戳的详细识别结果
|
||||
- 需要中文优化的语音识别(Paraformer 模型)
|
||||
|
||||
## 环境变量
|
||||
|
||||
在使用之前,必须检查环境变量是否已经设置。
|
||||
|
||||
| 变量名 | 用途 | 示例 |
|
||||
|--------|------|------|
|
||||
| `FUNASR_CACHE_DIR` | FunASR 模型缓存目录 | `~/.cache/modelscope/hub` |
|
||||
|
||||
## 使用方法
|
||||
|
||||
```bash
|
||||
# 基本用法(使用 Paraformer 模型)
|
||||
python funasr.py --input video.mp4
|
||||
|
||||
# 指定模型
|
||||
python funasr.py --input video.mp4 --model paraformer-zh
|
||||
|
||||
# 指定输出目录
|
||||
python funasr.py --input video.mp4 --output ./output
|
||||
|
||||
# 异步执行(推荐)
|
||||
python funasr.py --input video.mp4 --background
|
||||
```
|
||||
|
||||
## 命令参数
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `--input` | 输入音视频文件路径 | 必需 |
|
||||
| `--model` | FunASR 模型类型 | `paraformer-zh` |
|
||||
| `--output` | 输出目录 | 文件所在目录 |
|
||||
| `--background` | 后台异步执行 | False |
|
||||
|
||||
## 模型选项
|
||||
|
||||
- `paraformer-zh` - Paraformer 中文模型(轻量,中文优化,推荐)
|
||||
- `paraformer` - Paraformer 多语言模型
|
||||
- `sensevoice` - SenseVoice 多语言模型
|
||||
|
||||
## 输出文件
|
||||
|
||||
对于输入 `video.mp4`,将生成:
|
||||
|
||||
- `video.json` - 详细结果(包含时间戳、分段信息)
|
||||
- `video.txt` - 纯文本内容
|
||||
|
||||
## 输出格式示例
|
||||
|
||||
### JSON 格式
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"input_file": "video.mp4",
|
||||
"model": "paraformer-zh",
|
||||
"output_files": {
|
||||
"json": "video.json",
|
||||
"txt": "video.txt"
|
||||
},
|
||||
"duration_seconds": 120.5,
|
||||
"segments": [
|
||||
{
|
||||
"id": 0,
|
||||
"start": 0.0,
|
||||
"end": 3.2,
|
||||
"text": "这是第一段识别结果"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Workflow 中的使用
|
||||
|
||||
```bash
|
||||
# 步骤4之后:如果已有音频但无字幕,可以使用 FunASR
|
||||
python funasr.py --input ${video_path} --model paraformer-zh --background
|
||||
```
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
# 1. 安装依赖
|
||||
pip install funasr modelscope
|
||||
|
||||
# 2. 首次运行时会自动下载模型到缓存目录
|
||||
python funasr.py --input test.mp4
|
||||
|
||||
# 3. 复制技能到 skills 目录
|
||||
cp -r kb-funasr/ ~/skills/
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- **异步执行**: 使用 `--background` 参数时,命令会在后台运行,不阻塞主代理
|
||||
- **模型选择**: paraformer-zh 模型适合中文语音识别,速度较快,精度较高
|
||||
- **文件格式**: 支持 MP3、MP4、WAV、FLAC、OGG、M4A 等常见格式
|
||||
- **首次运行**: 首次运行时会自动下载模型文件,需要稳定的网络连接
|
||||
Binary file not shown.
@@ -0,0 +1,313 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
KB FunASR - 本地语音识别工具
|
||||
基于阿里 FunASR 实现音视频转文字
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
|
||||
class FunASRTranscriber:
|
||||
"""FunASR 语音识别器"""
|
||||
|
||||
# 模型映射
|
||||
MODEL_MAP = {
|
||||
"paraformer-zh": "damo/speech_paraformer-large-vad_punc_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
|
||||
"paraformer": "damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
|
||||
"sensevoice": "FunASR/SenseVoiceSmall"
|
||||
}
|
||||
|
||||
def __init__(self, model: str = "paraformer-zh", cache_dir: Optional[str] = None):
|
||||
"""初始化 FunASR 识别器
|
||||
|
||||
Args:
|
||||
model: 模型名称
|
||||
cache_dir: 模型缓存目录
|
||||
"""
|
||||
self.model_name = model
|
||||
self.model_id = self.MODEL_MAP.get(model, self.MODEL_MAP["paraformer-zh"])
|
||||
self.cache_dir = cache_dir or os.path.expanduser("~/.cache/modelscope/hub")
|
||||
self._model = None
|
||||
|
||||
def _load_model(self):
|
||||
"""延迟加载模型"""
|
||||
if self._model is None:
|
||||
try:
|
||||
from funasr import AutoModel
|
||||
print(f"正在加载模型: {self.model_name} ({self.model_id})")
|
||||
print(f"缓存目录: {self.cache_dir}")
|
||||
self._model = AutoModel(
|
||||
model=self.model_id,
|
||||
disable_pbar=False,
|
||||
disable_log=False,
|
||||
)
|
||||
print("模型加载完成")
|
||||
except ImportError:
|
||||
print("错误: 未安装 funasr 库")
|
||||
print("请运行: pip install funasr modelscope")
|
||||
sys.exit(1)
|
||||
|
||||
def _convert_audio(self, input_path: str) -> str:
|
||||
"""转换音频格式为 16kHz WAV
|
||||
|
||||
Args:
|
||||
input_path: 输入文件路径
|
||||
|
||||
Returns:
|
||||
转换后的文件路径(如果不需要转换则返回原路径)
|
||||
"""
|
||||
# 检查是否已经是 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:
|
||||
sample_rate = int(result.stdout.strip())
|
||||
if sample_rate == 16000:
|
||||
return input_path
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 需要转换
|
||||
output_path = input_path.rsplit('.', 1)[0] + '_converted.wav'
|
||||
print(f"正在转换音频格式: {input_path} -> {output_path}")
|
||||
|
||||
try:
|
||||
subprocess.run([
|
||||
'ffmpeg', '-y', '-i', input_path,
|
||||
'-acodec', 'pcm_s16le',
|
||||
'-ar', '16000',
|
||||
'-ac', '1',
|
||||
output_path
|
||||
], check=True, capture_output=True)
|
||||
print("音频转换完成")
|
||||
return output_path
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"音频转换失败: {e}")
|
||||
# 如果转换失败,尝试使用原文件
|
||||
print("尝试使用原文件继续处理")
|
||||
return input_path
|
||||
except FileNotFoundError:
|
||||
print("警告: 未找到 ffmpeg,跳过音频转换")
|
||||
return input_path
|
||||
|
||||
def transcribe(self, input_path: str) -> Dict:
|
||||
"""转录音频文件
|
||||
|
||||
Args:
|
||||
input_path: 输入文件路径
|
||||
|
||||
Returns:
|
||||
识别结果字典
|
||||
"""
|
||||
input_path = os.path.abspath(input_path)
|
||||
|
||||
if not os.path.exists(input_path):
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"文件不存在: {input_path}"
|
||||
}
|
||||
|
||||
print(f"\n开始处理: {input_path}")
|
||||
print(f"使用模型: {self.model_name}")
|
||||
|
||||
# 转换音频格式
|
||||
audio_path = self._convert_audio(input_path)
|
||||
|
||||
# 加载模型
|
||||
self._load_model()
|
||||
|
||||
# 执行识别
|
||||
print("正在进行语音识别...")
|
||||
try:
|
||||
result = self._model.generate(
|
||||
input=audio_path,
|
||||
batch_size_s=300,
|
||||
)
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"识别失败: {str(e)}"
|
||||
}
|
||||
|
||||
# 解析结果
|
||||
if not result or not isinstance(result, list):
|
||||
return {
|
||||
"success": False,
|
||||
"error": "识别结果格式错误"
|
||||
}
|
||||
|
||||
# FunASR 返回格式处理
|
||||
segments = []
|
||||
full_text = ""
|
||||
|
||||
for seg in result:
|
||||
if isinstance(seg, dict):
|
||||
text = seg.get("text", "").strip()
|
||||
if "timestamp" in seg and seg["timestamp"]:
|
||||
# 包含时间戳
|
||||
timestamp = seg["timestamp"]
|
||||
if len(timestamp) >= 2:
|
||||
start_time = timestamp[0][0] if timestamp[0] else 0
|
||||
end_time = timestamp[-1][1] if timestamp[-1] else start_time
|
||||
else:
|
||||
start_time = 0
|
||||
end_time = 0
|
||||
else:
|
||||
start_time = 0
|
||||
end_time = 0
|
||||
|
||||
if text:
|
||||
segments.append({
|
||||
"id": len(segments),
|
||||
"start": round(start_time, 2),
|
||||
"end": round(end_time, 2),
|
||||
"text": text
|
||||
})
|
||||
full_text += text + "\n"
|
||||
|
||||
# 清理临时文件
|
||||
if audio_path != input_path and "_converted.wav" in audio_path:
|
||||
try:
|
||||
os.remove(audio_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 计算时长
|
||||
duration = segments[-1]["end"] if segments else 0
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"input_file": os.path.basename(input_path),
|
||||
"model": self.model_name,
|
||||
"output_files": {},
|
||||
"duration_seconds": round(duration, 2),
|
||||
"segments": segments,
|
||||
"text": full_text.strip()
|
||||
}
|
||||
|
||||
def save_outputs(self, result: Dict, output_dir: str):
|
||||
"""保存输出文件
|
||||
|
||||
Args:
|
||||
result: 识别结果
|
||||
output_dir: 输出目录
|
||||
"""
|
||||
if not result.get("success"):
|
||||
return
|
||||
|
||||
output_dir = Path(output_dir)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
basename = Path(result["input_file"]).stem
|
||||
|
||||
# 保存 JSON
|
||||
json_path = output_dir / f"{basename}.json"
|
||||
with open(json_path, "w", encoding="utf-8") as f:
|
||||
json.dump(result, f, ensure_ascii=False, indent=2)
|
||||
print(f"已保存: {json_path}")
|
||||
result["output_files"]["json"] = str(json_path)
|
||||
|
||||
# 保存 TXT
|
||||
txt_path = output_dir / f"{basename}.txt"
|
||||
with open(txt_path, "w", encoding="utf-8") as f:
|
||||
f.write(result.get("text", ""))
|
||||
print(f"已保存: {txt_path}")
|
||||
result["output_files"]["txt"] = str(txt_path)
|
||||
|
||||
|
||||
def run_as_background(input_path: str, model: str, output_dir: str):
|
||||
"""后台运行识别
|
||||
|
||||
Args:
|
||||
input_path: 输入文件路径
|
||||
model: 模型名称
|
||||
output_dir: 输出目录
|
||||
"""
|
||||
# 创建日志文件
|
||||
log_file = os.path.join(output_dir, f"funasr_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
||||
|
||||
# 重新执行命令(后台模式)
|
||||
cmd = [
|
||||
sys.executable, __file__,
|
||||
"--input", input_path,
|
||||
"--model", model,
|
||||
"--output", output_dir
|
||||
]
|
||||
|
||||
with open(log_file, "w") as f:
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=f,
|
||||
stderr=subprocess.STDOUT,
|
||||
start_new_session=True
|
||||
)
|
||||
|
||||
print(f"\n✓ 后台任务已启动 (PID: {process.pid})")
|
||||
print(f"日志文件: {log_file}")
|
||||
print(f"输出目录: {output_dir}")
|
||||
print("\n使用以下命令查看进度:")
|
||||
print(f" tail -f {log_file}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="KB FunASR - 本地语音识别工具",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
示例:
|
||||
python funasr.py --input video.mp4
|
||||
python funasr.py --input podcast.mp3 --model paraformer-zh
|
||||
python funasr.py --input video.mp4 --output ./transcripts --background
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument("--input", "-i", required=True, help="输入音视频文件路径")
|
||||
parser.add_argument("--model", "-m", default="paraformer-zh",
|
||||
choices=["paraformer-zh", "paraformer", "sensevoice"],
|
||||
help="模型名称 (默认: paraformer-zh)")
|
||||
parser.add_argument("--output", "-o", default=None, help="输出目录 (默认: 文件所在目录)")
|
||||
parser.add_argument("--background", "-b", action="store_true", help="后台异步执行")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 确定输出目录
|
||||
if args.output:
|
||||
output_dir = args.output
|
||||
else:
|
||||
output_dir = os.path.dirname(args.input) or "."
|
||||
|
||||
# 后台模式
|
||||
if args.background:
|
||||
run_as_background(args.input, args.model, output_dir)
|
||||
return
|
||||
|
||||
# 前台模式
|
||||
transcriber = FunASRTranscriber(model=args.model)
|
||||
result = transcriber.transcribe(args.input)
|
||||
|
||||
if result.get("success"):
|
||||
transcriber.save_outputs(result, output_dir)
|
||||
print(f"\n✓ 处理完成!")
|
||||
print(f" 时长: {result['duration_seconds']} 秒")
|
||||
print(f" 分段数: {len(result['segments'])}")
|
||||
else:
|
||||
print(f"\n✗ 处理失败: {result.get('error')}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user