<!--
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 推理*
