349 lines
7.7 KiB
Markdown
349 lines
7.7 KiB
Markdown
<!--
|
||
input: 视频/音频文件
|
||
output: 带时间戳的转录 JSON
|
||
pos: 辅助,转录参数和输出格式说明
|
||
|
||
架构守护者:一旦我被修改,请同步更新:
|
||
1. ../README.md 文件清单
|
||
-->
|
||
|
||
# 转录最佳实践
|
||
|
||
---
|
||
|
||
## 零、环境准备
|
||
|
||
### 0.1 安装依赖
|
||
|
||
```bash
|
||
pip install funasr
|
||
pip install modelscope # 模型下载
|
||
```
|
||
|
||
### 0.2 模型下载
|
||
|
||
首次运行会自动下载模型到 `~/.cache/modelscope/`(约 2GB):
|
||
|
||
| 模型 | 大小 | 用途 |
|
||
|------|------|------|
|
||
| paraformer-zh | 953MB | 语音识别(带时间戳) |
|
||
| punc_ct | 1.1GB | 标点预测 |
|
||
| fsmn-vad | 4MB | 语音活动检测 |
|
||
|
||
**手动预下载**(可选,避免首次运行等待):
|
||
|
||
```python
|
||
from funasr import AutoModel
|
||
|
||
# 运行一次即可触发下载
|
||
model = AutoModel(
|
||
model="paraformer-zh",
|
||
vad_model="fsmn-vad",
|
||
punc_model="ct-punc",
|
||
)
|
||
print("模型下载完成")
|
||
```
|
||
|
||
### 0.3 验证安装
|
||
|
||
```python
|
||
from funasr import AutoModel
|
||
model = AutoModel(model="paraformer-zh", disable_update=True)
|
||
result = model.generate(input="test.wav")
|
||
print(result) # 应该输出转录结果
|
||
```
|
||
|
||
---
|
||
|
||
## 一、技术选型
|
||
|
||
### FunASR Paraformer
|
||
|
||
阿里开源,中文识别最优,支持字符级时间戳。
|
||
|
||
### ⚠️ 关键发现(2026-01-15)
|
||
|
||
| 方案 | 问题 |
|
||
|------|------|
|
||
| FunASR 全视频 | 长视频时间戳漂移(~10s/3分钟)→ 剪辑不准 |
|
||
| **FunASR 30s分段** | ✅ 无漂移 + 精确时间戳 |
|
||
|
||
**结论**:口播剪辑用 **FunASR 30s 分段转录**
|
||
|
||
---
|
||
|
||
## 二、音频预处理
|
||
|
||
### 2.1 从视频提取音频
|
||
|
||
```bash
|
||
ffmpeg -i video.mp4 \
|
||
-vn \ # 不要视频
|
||
-acodec pcm_s16le \ # 16-bit PCM
|
||
-ar 16000 \ # 16kHz 采样率
|
||
-ac 1 \ # 单声道
|
||
output.wav
|
||
```
|
||
|
||
### 2.2 参数说明
|
||
|
||
| 参数 | 值 | 原因 |
|
||
|------|-----|------|
|
||
| 采样率 | 16000 Hz | FunASR 模型训练采样率 |
|
||
| 声道 | 单声道 | 语音识别不需要立体声 |
|
||
| 格式 | WAV | 无损,兼容性好 |
|
||
| 位深 | 16-bit | 足够,文件更小 |
|
||
|
||
---
|
||
|
||
## 三、FunASR 使用
|
||
|
||
### 3.1 ⭐ 推荐:30s 分段转录(口播剪辑用这个)
|
||
|
||
```python
|
||
from funasr import AutoModel
|
||
import subprocess
|
||
import os
|
||
|
||
video = "video.mp4"
|
||
segment_len = 30 # 30秒一段
|
||
duration = 217.97 # 视频时长(用 ffprobe 获取)
|
||
|
||
model = AutoModel(model="paraformer-zh", disable_update=True)
|
||
all_chars = []
|
||
|
||
num_segments = int(duration // segment_len) + 1
|
||
for i in range(num_segments):
|
||
start = i * segment_len
|
||
dur = min(segment_len, duration - start)
|
||
wav = f'/tmp/seg_{i}.wav'
|
||
|
||
# 提取音频段
|
||
subprocess.run(['ffmpeg', '-y', '-i', video, '-ss', str(start), '-t', str(dur),
|
||
'-vn', '-acodec', 'pcm_s16le', '-ar', '16000', '-ac', '1', wav],
|
||
capture_output=True)
|
||
|
||
# FunASR 转录(字符级时间戳)
|
||
result = model.generate(input=wav, return_raw_text=True,
|
||
timestamp_granularity="character")
|
||
|
||
for item in result:
|
||
if 'timestamp' in item and 'text' in item:
|
||
text = item['text'].replace(' ', '')
|
||
for char, ts in zip(text, item['timestamp']):
|
||
all_chars.append({
|
||
'char': char,
|
||
'start': round(start + ts[0] / 1000, 2), # 加偏移!
|
||
'end': round(start + ts[1] / 1000, 2)
|
||
})
|
||
os.remove(wav)
|
||
```
|
||
|
||
**关键点**:
|
||
- 30s 分段避免时间戳漂移
|
||
- `timestamp_granularity="character"` 获取字符级时间戳
|
||
- 每段结果要 **加上段起始偏移**
|
||
|
||
### 3.2 基础用法(短视频可用)
|
||
|
||
```python
|
||
from funasr import AutoModel
|
||
|
||
model = AutoModel(
|
||
model="paraformer-zh", # 中文模型
|
||
vad_model="fsmn-vad", # 语音活动检测
|
||
punc_model="ct-punc", # 标点预测
|
||
)
|
||
|
||
result = model.generate(
|
||
input="audio.wav",
|
||
batch_size_s=300, # 批处理时长(秒)
|
||
)
|
||
```
|
||
|
||
### 3.2 输出格式
|
||
|
||
```json
|
||
[{
|
||
"key": "audio",
|
||
"text": "大家好,我是陈峰。",
|
||
"timestamp": [
|
||
[880, 1120], // 第1个字的时间范围 (ms)
|
||
[1120, 1360], // 第2个字
|
||
...
|
||
]
|
||
}]
|
||
```
|
||
|
||
### 3.3 模型说明
|
||
|
||
| 模型 | 用途 |
|
||
|------|------|
|
||
| `paraformer-zh` | 中文语音识别主模型 |
|
||
| `fsmn-vad` | 检测哪里有人说话 |
|
||
| `ct-punc` | 自动添加标点符号 |
|
||
|
||
---
|
||
|
||
## 四、输出格式设计
|
||
|
||
### 4.1 详细 JSON 格式
|
||
|
||
```json
|
||
{
|
||
"audio_file": "/path/to/audio.wav",
|
||
"full_text": "完整转录文本...",
|
||
"duration_ms": 935455,
|
||
"segments": [
|
||
{
|
||
"char": "大",
|
||
"start_ms": 880,
|
||
"end_ms": 1120
|
||
},
|
||
...
|
||
],
|
||
"raw_result": { /* FunASR 原始输出 */ }
|
||
}
|
||
```
|
||
|
||
### 4.2 可读 TXT 格式
|
||
|
||
```
|
||
======================================
|
||
视频转录结果 - video.mp4
|
||
======================================
|
||
|
||
总时长: 15:35 (15分35秒)
|
||
字符数: 2006
|
||
|
||
======================================
|
||
完整文本
|
||
======================================
|
||
|
||
大家好,我是陈峰。一直有同学问我...
|
||
|
||
======================================
|
||
带时间戳的句子记录
|
||
======================================
|
||
|
||
[00:01 - 00:02]
|
||
大家好,我是陈峰。
|
||
|
||
[00:05 - 00:17]
|
||
一直有同学问我能不能做一期企业级PPT模板的教程?
|
||
```
|
||
|
||
---
|
||
|
||
## 五、常见问题
|
||
|
||
### Q0: 调用方式错误(2026-01-13)
|
||
|
||
**错误**:尝试用命令行 `funasr --input video.mp4` 调用
|
||
**正确**:只能用 Python API
|
||
|
||
```python
|
||
# ❌ 错误 - 没有 funasr CLI
|
||
subprocess.run(['funasr', '--input', 'video.mp4'])
|
||
|
||
# ✅ 正确 - 用 Python API
|
||
from funasr import AutoModel
|
||
model = AutoModel(model="paraformer-zh", ...)
|
||
result = model.generate(input="video.mp4")
|
||
```
|
||
|
||
### Q0.5: 模型选错没有时间戳(2026-01-13)
|
||
|
||
**错误**:用 `SenseVoiceSmall` 模型,只输出文本没有时间戳
|
||
**正确**:必须用 `paraformer-zh` 才有字符级时间戳
|
||
|
||
```python
|
||
# ❌ 错误 - 没有时间戳
|
||
model = AutoModel(model="iic/SenseVoiceSmall", ...)
|
||
|
||
# ✅ 正确 - 有时间戳
|
||
model = AutoModel(
|
||
model="paraformer-zh", # 这个才有时间戳!
|
||
vad_model="fsmn-vad",
|
||
punc_model="ct-punc",
|
||
)
|
||
```
|
||
|
||
### Q1: 模型下载慢
|
||
|
||
首次运行会下载 ~1GB 模型到 `~/.cache/modelscope/`
|
||
|
||
**解决**:
|
||
- 使用国内镜像
|
||
- 或手动下载后放到缓存目录
|
||
|
||
### Q2: 时间戳和文字对不上
|
||
|
||
**原因**:标点符号没有时间戳,需要特殊处理
|
||
|
||
**解决**:
|
||
```python
|
||
# 去掉标点后再对齐
|
||
import re
|
||
text_no_punc = re.sub(r'[,。!?、;:]', '', text)
|
||
```
|
||
|
||
### Q2.5: 时间戳数量少于字符数(2026-01-13)
|
||
|
||
**现象**:纯字符数828,时间戳数763,末尾67个字符没有时间戳
|
||
|
||
**原因**:FunASR 对视频末尾部分可能丢失时间戳
|
||
|
||
**解决**:
|
||
```python
|
||
# 访问时间戳要兜底
|
||
if idx < len(timestamps):
|
||
ts = timestamps[idx]
|
||
else:
|
||
ts = timestamps[-1] # 用最后一个时间戳兜底
|
||
```
|
||
|
||
### Q2.6: 正则表达式漏掉英文标点(2026-01-13)
|
||
|
||
**现象**:搜索文本时位置偏移,因为 clean_text 里还有英文标点
|
||
|
||
**原因**:正则只移除中文标点,没处理英文 `,` `.` 等
|
||
|
||
**解决**:
|
||
```python
|
||
# ❌ 错误 - 只有中文标点
|
||
clean = re.sub(r'[,。!?、;:]', '', text)
|
||
|
||
# ✅ 正确 - 包含英文标点
|
||
clean = re.sub(r'[,。?!、:;""''()《》【】\s\.,!?;:\'"()]', '', text)
|
||
```
|
||
|
||
### Q3: 长视频处理慢
|
||
|
||
**解决**:
|
||
- 增大 `batch_size_s` 参数
|
||
- 使用 GPU 加速(需要 PyTorch CUDA)
|
||
|
||
### Q4: 识别准确率低
|
||
|
||
**可能原因**:
|
||
- 背景噪音太大
|
||
- 说话人口音重
|
||
- 音频采样率不对
|
||
|
||
**解决**:
|
||
- 预处理降噪
|
||
- 确保 16kHz 采样率
|
||
|
||
---
|
||
|
||
## 六、性能参考
|
||
|
||
| 指标 | 值 |
|
||
|------|-----|
|
||
| RTF (Real-Time Factor) | ~0.16 |
|
||
| 含义 | 1秒音频只需0.16秒处理 |
|
||
| 15分钟视频 | 约2.5分钟处理完 |
|
||
|
||
*测试环境:M1 Mac,CPU 推理*
|