### 环境
- FunClip 版本:v2.1.0(GitHub release tag)
- funasr:1.4.0(requirements 要求 >=1.3.29)
- ASR 模型:`iic/speech_seaco_paraformer_large_asr_nat-zh-cn-16k-common-vocab8404-pytorch`(中文)
- 平台:Windows 11 / Python 3.11(与平台无关)
### 问题概述
对中文视频剪辑时,**只要裁剪文本(dest_text)中包含英文字母,匹配就永远失败**;
更隐蔽的是,匹配失败时 `video_clip()` 不报任何错误,而是**静默返回原始视频路径**,
调用方会误以为剪辑成功。
### 根因分析
`funclip/videoclipper.py` 的 `video_clip()` 中(约 309 行):
```python
ts = proc(recog_res_raw, timestamp, _dest_text.lower())
```
`proc()`(`funclip/utils/trans_utils.py:28`)用 `raw_text.find(dest_text)` 做精确匹配。
这里存在**单边归一化**:
- 搜索词 `_dest_text` 被 `.lower()` 强制转小写;
- 被搜索的 `recog_res_raw` 保持 ASR 原始输出,其中英文缩写/型号名按惯例为**大写**
(如 `FK`、`AE`、`ASML`)。
于是 `"fk 三 零 零 零"` 在 `"FK 三 零 零 零"` 中 `find()` 返回 -1,该句永远匹配不上。
纯中文内容不受影响(无大小写概念),所以该 bug 只在"中文内容夹英文词"时发作。
### 相关的不一致
1. **音频/视频路径行为不对称**:`clip()`(音频,约 193 行)调用 `proc()` 时**没有**
`.lower()`,`video_clip()` 有——同一份 `recog_res_raw` 在两条路径上匹配语义不同。
2. **零命中静默失败**:`video_clip()` 在所有目标文本都未匹配时(约 384-386 行):
```python
else:
clip_video_file = video_filename # 返回原始视频路径,无任何错误
```
调用方拿到的返回值与成功剪辑无法区分,属于静默失败。
### 最小复现
```python
from funasr import AutoModel
from videoclipper import VideoClipper
model = AutoModel(
model="iic/speech_seaco_paraformer_large_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
vad_model="damo/speech_fsmn_vad_zh-cn-16k-common-pytorch",
punc_model="damo/punc_ct-transformer_zh-cn-common-vocab272727-pytorch",
spk_model="damo/speech_campplus_sv_zh-cn_16k-common",
)
clipper = VideoClipper(model)
clipper.lang = "zh"
text, srt, state = clipper.video_recog("含英文缩写词的视频.mp4")
# 假设识别文本包含句子 "但航天科工的FK三零零零已经列装部队……"
# state["recog_res_raw"] 中对应内容为 "FK"(大写)
result, message, _ = clipper.video_clip(
"但航天科工的FK三零零零已经列装部队", 0, 100, state
)
# 期望:命中并输出剪辑片段
# 实际:message = "No period found in the audio...",result = 原始视频路径(无报错)
```
### 实际影响
按句多段剪辑(`#` 连接)时,所有含英文的句子被静默丢弃,输出视频丢句;
单段剪辑时直接返回原始完整视频,表现为"剪辑成功但内容是整个原视频"。
### 修复建议
1. 匹配前对**两侧**统一大小写归一化(任选一):
- `recog()` 中将 `state['recog_res_raw']` 统一 `.lower()` 后存入(等长变换,
不影响空格计数与 timestamp 下标映射);
- 或 `proc()` 内部对 `raw_text` 与 `dest_text` 做一致归一化。
2. `clip()` / `video_clip()` 两条路径对齐 `.lower()` 行为。
3. 零命中时输出 WARNING 日志或抛出异常,不要静默返回原始视频路径。
### 临时规避(调用方)
在 `video_recog()` 返回后手动 `state["recog_res_raw"] = state["recog_res_raw"].lower()`,
并检查返回值是否等于原始视频路径以识别零命中。