docs: align bilingual CLI guide with parser and output contracts

Signed-off-by: zhifu gao <[email protected]>
This commit is contained in:
zhifu gao
2026-09-06 22:37:12 +00:00
parent 682ea10e80
commit 0ccbc82469
5 changed files with 641 additions and 96 deletions
+186 -95
View File
@@ -1,142 +1,233 @@
# Command-Line Interface
FunASR provides an agent-friendly CLI for speech recognition from the terminal. Designed for AI agents (Claude Code, Codex, Cursor), shell scripts, and automation pipelines.
[中文](cli_zh.md) | [Python SDK](python_api.md) | [Model selection](model_selection.md)
## Installation
```bash
pip install funasr
```
Use `funasr` to transcribe local audio files, write structured results, or produce
subtitles. This guide follows the parser and formatter in
[`funasr/cli.py`](../funasr/cli.py); it is not the HTTP server or the Hydra CLI.
## Basic Usage
Transcribe one file with the default `sensevoice` model, or select a CLI alias:
```bash
# Transcribe audio (simplest)
funasr audio.wav
# Specify model
funasr audio.wav --model paraformer
# JSON output (structured, parseable)
funasr audio.wav --output-format json
# SRT subtitles
funasr audio.wav --output-format srt --output-dir ./subs
funasr audio.wav --device cpu
```
`srt` and `tsv` outputs request sentence-level timestamps. In FunASR 1.3.18
and newer, the default `sensevoice` CLI path also loads punctuation for subtitle
generation, so subtitle files are split into sentence cues instead of one
full-text block when the model returns `sentence_info`. SRT output groups short
or continuation cues into bounded, readable subtitles by default. Use
`--subtitle-segment-mode sentence` to preserve the model's raw sentence boundaries.
Choose the next task: [structured JSON](#json), [multiple files](#advanced-examples),
[subtitles](#srt), or [speakers and hotwords](#speakers-and-hotwords).
Inputs must be existing local files. Download remote audio yourself first; the
CLI does not accept an audio URL as its positional input.
## Options
## Installation
| Option | Short | Default | Description |
|--------|-------|---------|-------------|
| `--model` | `-m` | sensevoice | Model: sensevoice, paraformer, paraformer-en, fun-asr-nano |
| `--hub` | `-H` | ms | Model hub: ms (ModelScope) or hf (Hugging Face) |
| `--language` | `-l` | auto | Language: zh, en, ja, ko, yue, auto |
| `--device` | | auto | Device: cuda:0, cpu |
| `--output-format` | `-f` | text | Output: text, json, srt, tsv |
| `--subtitle-segment-mode` | | readable | SRT grouping: readable or raw sentence boundaries |
| `--output-dir` | `-o` | stdout | Write output files to directory |
| `--timestamps` | | off | Include word-level timestamps |
| `--spk` | | off | Enable speaker diarization |
| `--hotwords` | | none | Comma-separated hotwords |
| `--verbose` | `-v` | off | Show loading/timing info on stderr |
Install FunASR in your chosen Python environment, following the
[installation guide](installation/installation.md) for prerequisites and optional
model dependencies. These are usage commands, not a clean-install validation:
```bash
python -m pip install funasr
funasr --help
funasr --version
```
The selected models and VAD/punctuation/speaker components may download on first
use. They need working hub access or a populated cache, enough memory, and the
dependencies required by that checkpoint. `--hub ms` is the default; use `--hub hf`
to select Hugging Face. Device selection is `cuda:0` when PyTorch reports CUDA
available, otherwise `cpu`; use `--device` to override it. This availability check
does not guarantee enough GPU memory or a compatible model environment.
## Output Formats
### text (default)
Plain transcription text, one result per file. Best for piping:
Plain transcription text, one result per input file. Rich `<|...|>` model tags are
removed, so this is not an emotion/event-tag output interface.
```bash
funasr audio.wav | wc -w
funasr audio.wav -f text -o ./transcripts
```
Without `-o`, the CLI prints results to stdout. `--verbose` adds CLI loading and
timing messages to stderr; dependency/model logging may still appear on stdout.
Use output files for automation that needs isolated payloads.
### json
Structured output for programmatic use:
```bash
funasr audio.wav --timestamps -f json -o ./results
jq '.text' ./results/audio.json
```
This is an **illustrative formatter fixture**, not a measured transcription,
performance result, or promise that every model returns these optional fields:
```json
{
"text": "欢迎大家来体验达摩院推出的语音识别模型",
"text": "Example.",
"segments": [
{"start": 0, "end": 5540, "text": "欢迎大家来体验达摩院推出的语音识别模型"}
{"start": 0, "end": 1200, "text": "Example.", "timestamp": [[0, 1200]]}
],
"timestamps": [[0, 1200]],
"file": "audio.wav",
"model": "sensevoice",
"language": "auto",
"duration_s": 0.29
"audio_duration_s": 1.2,
"processing_s": 0.01
}
```
### srt
SubRip subtitle format:
```
1
00:00:00,000 --> 00:00:01,200
第一句。
| Field | Meaning |
|-------|---------|
| `text` | Cleaned transcription text. |
| `segments` | Included only when nonempty `sentence_info` produces segments. `start`/`end` are copied from the SDK (milliseconds for the sentence contract); text is cleaned. The per-segment `timestamp` can be null. |
| `timestamps` | Optional top-level model timestamp data retained with `--timestamps`. The CLI does not normalize it to a universal word schema. Pair arrays in this example are milliseconds; model-specific dictionary timestamp representations may use seconds. |
| `file` | Input basename, not its full path. |
| `model` | Selected CLI alias, not an immutable checkpoint revision. |
| `language` | Supplied hint or `auto` when omitted; not a detected-language result. |
| `audio_duration_s` | Audio metadata duration in seconds, rounded to three decimals; null if `soundfile.info` cannot read it. |
| `processing_s` | Per-file elapsed seconds around generation, rounded to three decimals. Excludes initial model loading and output formatting/writing; not end-to-end latency. |
2
00:00:01,200 --> 00:00:02,600
第二句。
```
If a model does not return sentence-level timestamps, the CLI falls back to one
valid cue spanning the known timestamp or audio duration.
Readable mode only joins adjacent cues when the gap is at most 500 ms, the
combined cue is at most 8 seconds and 42 characters, and the speaker is unchanged.
It preserves recognized text and punctuation. JSON and TSV segments are unchanged.
### tsv
Tab-separated values (start/end in seconds):
```
start end text
0.000 1.200 第一句。
1.200 2.600 第二句。
```
`--timestamps` retains timestamps already returned by the model. It does **not**
request alignment or guarantee word-level timestamps. In JSON without this flag,
the top-level field is omitted, but nested segment timestamps can remain. Plain
text does not display timestamps. See [the SDK output contract](python_api.md)
for model-dependent results.
## Advanced Examples
### Multiple Files
```bash
# Speaker diarization + JSON
funasr meeting.wav --spk --timestamps -f json
# Batch transcribe all WAV files
funasr *.wav --output-format srt --output-dir ./output
# Chinese with hotwords
funasr audio.wav --model paraformer --language zh --hotwords "FunASR,达摩院"
# Pipe to jq for processing
funasr audio.wav -f json | jq '.text'
# Load models from Hugging Face instead of ModelScope
funasr audio.wav --hub hf --model fun-asr-nano
# Use with AI agents
result=$(funasr audio.wav -f json)
echo "$result" | jq -r '.text'
funasr first.wav second.wav -f json -o ./results
funasr ./*.wav -f srt -o ./subs
```
One model instance is reused, and files are processed sequentially with
`batch_size=1`. The shell expands the glob; this is not parallel batch inference.
Outputs use each input's basename without its extension plus `.txt`, `.json`,
`.srt`, or `.tsv`. The output directory is created when needed. Files with the same
stem can overwrite each other, including results from previous runs.
Without `-o`, multiple JSON results are separate pretty-printed objects, **not** a
JSON array or JSONL stream. A missing file stops execution with exit code 1;
earlier output files remain. There is no resume or transactional batch option.
### srt
```bash
funasr audio.wav -f srt -o ./subs
funasr audio.wav -f srt --subtitle-segment-mode sentence -o ./raw-subs
```
SRT uses `HH:MM:SS,mmm` cue times. SRT and TSV request
`sentence_timestamp`, `output_timestamp`, and `return_time_stamps` from the SDK.
The default SenseVoice subtitle path also adds `ct-punc`; output still depends on
the model returning usable `sentence_info` and timing data.
The default `readable` mode groups eligible adjacent cues with a gap of at most
500 ms, a combined duration of at most 8 seconds, and at most 42 characters,
without crossing a known speaker change. Long cues are split only when their
available timestamps can support text alignment. These are grouping targets,
not a guarantee that every cue fits: unalignable or indivisible text can remain
over the limits. The CLI does not invent evenly spaced word timestamps.
`sentence` mode keeps raw model sentence boundaries. JSON and TSV are not grouped.
Without sentence segments, SRT falls back to one cue using available timestamp
bounds, then audio duration. If neither is available, the fallback can have zero
duration; inspect timing before publishing subtitles.
### tsv
```bash
funasr audio.wav -f tsv -o ./tables
```
TSV has `start`, `end`, and `text` columns, with start/end converted from sentence
milliseconds to seconds at three decimal places. Without segments, it emits one
text row with `0.000` start and end, not inferred alignment.
### Speakers and Hotwords
```bash
funasr meeting.wav --model paraformer --spk --timestamps -f json -o ./meetings
funasr audio.wav --model paraformer --language zh --hotwords "FunASR,达摩院"
funasr audio.wav --hub hf --model fun-asr-nano
```
`--spk` adds the `cam++` speaker model. JSON segments include `speaker` only when
the SDK supplies `spk` in `sentence_info`. It does not identify people by name or
guarantee diarization for every model. SRT grouping respects available speaker
boundaries, but the CLI's text/SRT/TSV formatters do not print speaker labels.
Hotwords are comma-separated, trimmed, and empty items discarded. The
`paraformer` alias forwards a space-joined `hotword` string; other aliases forward
a `hotwords` list. Recognition support depends on the model, not merely the
parser accepting the option. `--language` is also a model-specific hint: the
parser accepts any string and does not validate model language coverage.
## Limits
- This is a local-file SDK wrapper, not streaming, a network service, or native
vLLM serving. See the [deployment matrix](deployment_matrix.md) for those paths.
- The four aliases below are the entire `--model` choice set. Arbitrary hub IDs,
local model directories, and backend-selection flags are not supported here.
- `moss-transcribe-diarize` is **not** a CLI choice. Use the separate `AutoModel`
adapter or `funasr-server` path in the [MOSS guide](moss_transcribe_diarize.md),
with that guide's dependencies and limitations; do not pass it to `--model`.
- Model loading happens before the per-file existence check. Invalid input can
therefore still incur loading/download time. Inference or dependency errors
are not converted into a stable JSON error envelope.
- Timing, memory, language coverage, alignment, and speaker quality depend on
checkpoint, hardware, environment, and audio. This guide makes no speed or
production-capacity claim.
## Options
`audio` is one or more local file paths. `None` below is the actual parser default,
not a string to supply on the command line.
| Option | Short | Parser default | Meaning / choices |
|--------|-------|----------------|-------------------|
| `--model` | `-m` | `sensevoice` | `sensevoice`, `paraformer`, `paraformer-en`, `fun-asr-nano`. |
| `--hub` | `-H` | `ms` | `ms` (ModelScope) or `hf` (Hugging Face). |
| `--language` | `-l` | `None` | Omitted: no language argument is forwarded. Explicit hints such as `zh`, `en`, `ja`, `ko`, `yue`, `auto` depend on the model. |
| `--device` | | `None` | Automatically `cuda:0` if CUDA is available, otherwise `cpu`; explicit device string overrides this. |
| `--output-format` | `-f` | `text` | `text`, `json`, `srt`, `tsv`. |
| `--subtitle-segment-mode` | | `readable` | `readable` or `sentence`; affects SRT only. |
| `--output-dir` | `-o` | `None` | Omitted: stdout. Otherwise write per-file results to this directory. |
| `--timestamps` | | `False` | Retain available top-level timestamps; does not request alignment. |
| `--spk` | | `False` | Add speaker model; JSON speaker fields depend on SDK results. |
| `--hotwords` | | `None` | Comma-separated hints, forwarded according to the model alias. |
| `--verbose` | `-v` | `False` | CLI loading/timing messages to stderr. |
| `--version` | | Not applicable | Print installed FunASR package version and exit, not a model revision. |
| `--help` | `-h` | Not applicable | Print parser help and exit. |
## Models
| Model | Languages | Speed | Best for |
|-------|-----------|-------|----------|
| sensevoice | zh/en/ja/ko/yue | ~70ms/10s | CPU-friendly ASR, emotion/audio events |
| paraformer | zh + mixed | ~60ms/10s | Chinese production (with punctuation) |
| paraformer-en | en | ~60ms/10s | English |
| fun-asr-nano | zh/en/ja + Chinese dialects/accents | varies | Encoder+LLM, complex audio |
| CLI alias | ASR model mapping | Scope |
|-----------|-------------------|-------|
| `sensevoice` | `iic/SenseVoiceSmall` | Chinese, English, Japanese, Korean, Cantonese; rich tags are stripped from CLI text. |
| `paraformer` | `paraformer-zh` | Chinese recognition with VAD and punctuation. |
| `paraformer-en` | `paraformer-en` | English recognition; the CLI also adds punctuation. |
| `fun-asr-nano` | `FunAudioLLM/Fun-ASR-Nano-2512` | Chinese, English, Japanese and Chinese dialects/accents; additional model dependencies apply. |
Language coverage is checkpoint-specific. For example, the separate
Fun-ASR-MLT-Nano checkpoint covers 31 languages, while the default CLI
`fun-asr-nano` choice targets Chinese, English, Japanese, and Chinese dialects
or accents.
All four configurations include `fsmn-vad`; speaker and punctuation additions
follow the selected options described above. These mappings do not pin a hub
revision. The separate Fun-ASR-MLT-Nano checkpoint is not selected by the
`fun-asr-nano` alias. For other checkpoints use the [Python SDK](python_api.md)
and [model selection guide](model_selection.md).
## Legacy CLI
The original Hydra-based CLI is available as `funasr-hydra`:
The original Hydra-based entry point remains `funasr-hydra`:
```bash
funasr-hydra ++model=paraformer-zh ++input=audio.wav
```
Its `++key=value` configuration is separate from the argparse flags documented
here; do not mix the two syntaxes.
+235
View File
@@ -0,0 +1,235 @@
<span id="command-line-interface"></span>
# 命令行接口
[English](cli.md) | [Python SDK](python_api_zh.md) | [模型选择](model_selection_zh.md)
使用 `funasr` 转写本地音频、保存结构化结果或生成字幕。本指南依据
[`funasr/cli.py`](../funasr/cli.py) 的参数解析器与格式化逻辑;这里的命令不是
HTTP 服务,也不是 Hydra CLI。
<span id="basic-usage"></span>
## 基本用法
用默认的 `sensevoice` 模型转写一个文件,或选择 CLI 支持的模型别名:
```bash
funasr audio.wav
funasr audio.wav --model paraformer
funasr audio.wav --device cpu
```
按任务继续阅读:[结构化 JSON](#json)、[多个文件](#advanced-examples)、
[字幕](#srt)或[说话人与热词](#speakers-and-hotwords)。
输入必须是已存在的本地文件。远程音频需先自行下载;CLI 的位置参数不接受音频 URL。
<span id="installation"></span>
## 安装与前提
在选定的 Python 环境中安装 FunASR,并按[安装指南](installation/installation_zh.md)
准备前提条件与模型所需的可选依赖。下列是使用命令,不代表全新环境安装验证:
```bash
python -m pip install funasr
funasr --help
funasr --version
```
首次使用可能下载所选模型以及 VAD、标点、说话人组件。需要可用的模型源访问权限
或完整缓存、足够内存,以及对应检查点要求的依赖。默认 `--hub ms` 使用 ModelScope,
`--hub hf` 选择 Hugging Face。PyTorch 报告 CUDA 可用时自动选择 `cuda:0`,否则选择
`cpu`;可用 `--device` 覆盖。这个可用性检查不保证显存充足或模型环境兼容。
<span id="output-formats"></span>
## 输出格式
<span id="text-default"></span>
### 纯文本(默认)
每个输入文件产生一份纯转写文本。模型的 `<|...|>` 富文本标签会被移除,因此它不是
情感或声音事件标签输出接口。
```bash
funasr audio.wav -f text -o ./transcripts
```
未指定 `-o` 时,CLI 将结果打印到标准输出。`--verbose` 将 CLI 的加载与计时信息写到
标准错误;模型或依赖仍可能向标准输出打印日志。需要独立结果载荷的自动化应使用输出文件。
<span id="json"></span>
### 结构化结果
```bash
funasr audio.wav --timestamps -f json -o ./results
jq '.text' ./results/audio.json
```
以下是**格式化器的示意测试数据**,不是实测识别结果、性能数据,也不保证每个模型都
返回这些可选字段:
```json
{
"text": "Example.",
"segments": [
{"start": 0, "end": 1200, "text": "Example.", "timestamp": [[0, 1200]]}
],
"timestamps": [[0, 1200]],
"file": "audio.wav",
"model": "sensevoice",
"language": "auto",
"audio_duration_s": 1.2,
"processing_s": 0.01
}
```
| 字段 | 含义 |
|------|------|
| `text` | 清理标签后的识别文本。 |
| `segments` | 仅当非空 `sentence_info` 生成分段时包含。`start`/`end` 直接复制 SDK 值(句子契约使用毫秒),文本会清理标签。分段内 `timestamp` 可以为 null。 |
| `timestamps` | `--timestamps` 保留的可选顶层模型时间戳。CLI 不将其统一为通用词级结构。示例中的数值对数组使用毫秒;部分模型的字典时间戳表示可能使用秒。 |
| `file` | 输入文件名,不含完整路径。 |
| `model` | 所选 CLI 别名,不是不可变的检查点修订号。 |
| `language` | 用户传入的提示,省略时为 `auto`;不是检测出的语言。 |
| `audio_duration_s` | 音频元数据时长,单位为秒,保留三位小数;`soundfile.info` 无法读取时为 null。 |
| `processing_s` | 每个文件生成调用周围的耗时,单位为秒,保留三位小数。不含初次模型加载和输出格式化、写盘时间,不是端到端延迟。 |
`--timestamps` 只保留模型已经返回的时间戳,**不会请求对齐,也不保证词级时间戳**。
JSON 未指定该参数时省略顶层时间戳,但分段内部的时间戳仍可能存在。纯文本格式不显示
时间戳。模型相关的结果差异见 [SDK 输出契约](python_api_zh.md)。
<span id="advanced-examples"></span>
## 进阶用法
### 多个文件
```bash
funasr first.wav second.wav -f json -o ./results
funasr ./*.wav -f srt -o ./subs
```
模型只实例化一次,多个文件依次处理,每次生成使用 `batch_size=1`。通配符由 shell
展开,这不是并行批推理。输出文件名使用输入文件去掉扩展名后的名称,再加 `.txt`、
`.json`、`.srt` 或 `.tsv`;输出目录不存在时会创建。同名文件可能互相覆盖,也可能覆盖
上一次运行的结果,即使输入来自不同目录。
不指定 `-o` 时,多份 JSON 会作为独立的多行对象逐个打印,**不是 JSON 数组或 JSONL**。
遇到不存在的文件会以退出码 1 停止,之前写出的文件保留。CLI 没有断点续跑或事务式批处理选项。
<span id="srt"></span>
### 字幕
```bash
funasr audio.wav -f srt -o ./subs
funasr audio.wav -f srt --subtitle-segment-mode sentence -o ./raw-subs
```
SRT 使用 `HH:MM:SS,mmm` 时间格式。SRT 和 TSV 都向 SDK 请求
`sentence_timestamp`、`output_timestamp` 和 `return_time_stamps`。默认的 SenseVoice
字幕路径还会增加 `ct-punc`;结果仍取决于模型是否返回可用的 `sentence_info` 和时间信息。
默认 `readable` 模式会合并符合条件的相邻字幕:间隔不超过 500 毫秒,合并后时长不超过
8 秒,文字不超过 42 个字符,且不跨越已知的说话人变化。长字幕只有在已有时间戳能够
支持文本对齐时才会拆分。这些是分组目标,不能保证每条字幕都满足:无法对齐或不可再拆分
的文本可能超限。CLI 不会编造均匀分布的词级时间戳。`sentence` 模式保留模型原始句子边界。
JSON 和 TSV 不执行这种分组。
没有句子分段时,SRT 回退为单条字幕,先使用可用的时间戳范围,再尝试音频时长。
两者都不可用时,回退字幕可能是零时长;发布字幕前应检查时间信息。
<span id="tsv"></span>
### 表格
```bash
funasr audio.wav -f tsv -o ./tables
```
TSV 包含 `start`、`end`、`text` 三列,将句子起止时间从毫秒换算成秒,保留三位小数。
没有分段时仅输出一行文本,起止时间均为 `0.000`,不代表推导出的对齐时间。
<span id="speakers-and-hotwords"></span>
### 说话人与热词
```bash
funasr meeting.wav --model paraformer --spk --timestamps -f json -o ./meetings
funasr audio.wav --model paraformer --language zh --hotwords "FunASR,达摩院"
funasr audio.wav --hub hf --model fun-asr-nano
```
`--spk` 增加 `cam++` 说话人模型。仅当 SDK 的 `sentence_info` 中含 `spk` 时,JSON
分段才包含 `speaker`。这不是实名身份识别,也不保证所有模型组合都能进行说话人分离。
SRT 分组会遵守已有的说话人边界,但 CLI 的纯文本、SRT、TSV 格式化器不输出说话人标签。
热词使用逗号分隔,去除前后空白并丢弃空项。`paraformer` 别名向 SDK 传入以空格连接的
`hotword` 字符串;其他别名传入 `hotwords` 列表。是否生效取决于模型,而不只是解析器
接受了该参数。`--language` 也属于模型相关提示:解析器接受任意字符串,不校验模型语言覆盖。
## 使用边界
- 这是本地文件 SDK 包装命令,不是流式推理、网络服务或原生 vLLM 服务。其他路径见
[部署矩阵](deployment_matrix_zh.md)。
- 下表的四个别名就是 `--model` 的全部选项。不能在这里传任意模型源 ID、本地模型目录
或后端选择参数。
- `moss-transcribe-diarize` **不是 CLI 模型选项**。请按 [MOSS 指南](moss_transcribe_diarize_zh.md)
使用独立的 `AutoModel` 适配器或 `funasr-server` 路径,并遵守其依赖与限制;不要将它传给 `--model`。
- 模型加载先于逐文件存在性检查,错误输入也可能触发加载或下载。推理或依赖异常不会被
转成稳定的 JSON 错误对象。
- 速度、内存、语言覆盖、对齐和说话人质量依赖检查点、硬件、环境和音频。本指南不作速度
或生产容量承诺。
<span id="options"></span>
## 参数参考
`audio` 为一个或多个本地文件路径。下表 `None` 是解析器的真实默认值,不是应在命令行输入的字符串。
| 参数 | 短参数 | 解析器默认值 | 含义 / 可选值 |
|------|--------|--------------|---------------|
| `--model` | `-m` | `sensevoice` | `sensevoice`、`paraformer`、`paraformer-en`、`fun-asr-nano`。 |
| `--hub` | `-H` | `ms` | `ms`(ModelScope)或 `hf`(Hugging Face)。 |
| `--language` | `-l` | `None` | 省略时不向模型传语言参数。`zh`、`en`、`ja`、`ko`、`yue`、`auto` 等显式提示是否支持取决于模型。 |
| `--device` | | `None` | CUDA 可用时自动选择 `cuda:0`,否则 `cpu`;显式设备字符串覆盖自动选择。 |
| `--output-format` | `-f` | `text` | `text`、`json`、`srt`、`tsv`。 |
| `--subtitle-segment-mode` | | `readable` | `readable` 或 `sentence`,仅影响 SRT。 |
| `--output-dir` | `-o` | `None` | 省略时输出到 stdout,否则按输入文件分别写到指定目录。 |
| `--timestamps` | | `False` | 保留已有顶层时间戳,不请求对齐。 |
| `--spk` | | `False` | 增加说话人模型,JSON 说话人字段取决于 SDK 返回结果。 |
| `--hotwords` | | `None` | 逗号分隔的提示词,按模型别名转发。 |
| `--verbose` | `-v` | `False` | 将 CLI 加载和计时信息写到 stderr。 |
| `--version` | | 不适用 | 打印已安装的 FunASR 包版本并退出,不是模型修订号。 |
| `--help` | `-h` | 不适用 | 打印解析器帮助并退出。 |
<span id="models"></span>
## 模型别名
| CLI 别名 | ASR 模型映射 | 范围 |
|----------|--------------|------|
| `sensevoice` | `iic/SenseVoiceSmall` | 中文、英语、日语、韩语、粤语;CLI 文本移除富文本标签。 |
| `paraformer` | `paraformer-zh` | 中文识别,带 VAD 和标点。 |
| `paraformer-en` | `paraformer-en` | 英语识别,CLI 还会增加标点模型。 |
| `fun-asr-nano` | `FunAudioLLM/Fun-ASR-Nano-2512` | 中文、英语、日语及中文方言/口音;需要额外模型依赖。 |
四种配置均包含 `fsmn-vad`;说话人和标点组件按上文选项逻辑添加。这些映射没有固定模型源
修订号。`fun-asr-nano` 别名不选择独立的 Fun-ASR-MLT-Nano 检查点。
其他检查点请使用 [Python SDK](python_api_zh.md)并参考[模型选择指南](model_selection_zh.md)。
<span id="legacy-cli"></span>
## 旧版 CLI
原有的 Hydra 入口仍为 `funasr-hydra`:
```bash
funasr-hydra ++model=paraformer-zh ++input=audio.wav
```
其 `++key=value` 配置与本指南的 argparse 参数分属两套语法,不要混用。
+1
View File
@@ -23,6 +23,7 @@ entry to these guides. Source Markdown remains in this repository.
model_selection
model_selection_zh
cli
cli_zh
migration_from_whisper
migration_from_whisper_zh
+218
View File
@@ -0,0 +1,218 @@
"""Check CLI documentation against the real parser and formatter, without models."""
import argparse
import importlib.util
import json
import os
from pathlib import Path
import re
import shlex
import subprocess
import sys
import types
from bs4 import BeautifulSoup
from markdown import markdown
import pytest
ROOT = Path(__file__).resolve().parents[1]
DOCS = [ROOT / 'docs/cli.md', ROOT / 'docs/cli_zh.md']
spec = importlib.util.spec_from_file_location('cli_docs_subject', ROOT / 'funasr/cli.py')
cli = importlib.util.module_from_spec(spec)
spec.loader.exec_module(cli)
docs_spec = importlib.util.spec_from_file_location(
'cli_docs_renderer', ROOT / 'web-pages/product-site/documentation.py')
renderer = importlib.util.module_from_spec(docs_spec)
docs_spec.loader.exec_module(renderer)
def source(path):
assert path.is_file(), f'Missing bilingual CLI source: {path.name}'
return path.read_text(encoding='utf-8')
def blocks(text, language):
return re.findall(r'^```' + language + r'\n(.*?)^```', text, re.M | re.S)
@pytest.fixture
def parser(monkeypatch):
captured = []
class Captured(Exception):
pass
def capture(self, *args, **kwargs):
captured.append(self)
raise Captured
with monkeypatch.context() as patch:
patch.setattr(cli, '_get_version', lambda: 'fixture')
patch.setattr(argparse.ArgumentParser, 'parse_args', capture)
with pytest.raises(Captured):
cli.main()
assert len(captured) == 1
return captured[0]
@pytest.mark.parametrize('path', DOCS, ids=lambda p: p.name)
def test_json_example_is_real_formatter_output_not_old_duration_field(path, monkeypatch):
examples = blocks(source(path), 'json')
assert len(examples) == 1
monkeypatch.setitem(sys.modules, 'soundfile', types.SimpleNamespace(
info=lambda _: types.SimpleNamespace(duration=1.2)))
segments = [{'start': 0, 'end': 1200, 'text': 'Example.', 'timestamp': [[0, 1200]]}]
actual = cli._format_output('Example.', segments, [[0, 1200]], 'json',
'audio.wav', 'sensevoice', None, 0.01)
assert json.loads(examples[0]) == json.loads(actual)
assert '"duration_s"' not in source(path)
def test_formatter_missing_audio_metadata_is_null(monkeypatch):
def missing(_):
raise OSError('fixture metadata unavailable')
monkeypatch.setitem(sys.modules, 'soundfile', types.SimpleNamespace(info=missing))
payload = json.loads(cli._format_output('Example.', [], None, 'json',
'missing.wav', 'sensevoice', None, 0.01))
assert payload['audio_duration_s'] is None
assert payload['processing_s'] == 0.01
assert 'segments' not in payload and 'timestamps' not in payload
@pytest.mark.parametrize('path', DOCS, ids=lambda p: p.name)
def test_reference_matches_actual_options_defaults_and_model_choices(path, parser):
text = source(path)
soup = BeautifulSoup(markdown(text, extensions=['extra']), 'html.parser')
rows = {row.select_one('code').get_text(): row.get_text(' ', strip=True)
for table in soup.select('table') for row in table.select('tr')
if row.select_one('code') and row.select_one('code').get_text().startswith('--')}
defaults = parser.parse_args(['audio.wav'])
assert defaults.language is None and defaults.device is None
assert defaults.model == 'sensevoice' and defaults.hub == 'ms'
for action in parser._actions:
long = next((option for option in action.option_strings if option.startswith('--')), None)
if long:
assert long in rows, long
for alias in action.option_strings:
assert alias in rows[long]
if action.dest in ('language', 'device', 'output_dir', 'hotwords'):
assert 'None' in rows[long]
if action.dest in ('model', 'hub', 'output_format', 'subtitle_segment_mode'):
assert str(action.default) in rows[long]
if isinstance(action.default, bool):
assert str(action.default) in rows[long]
for choice in action.choices or []:
assert choice in rows[long]
model_action = next(a for a in parser._actions if a.dest == 'model')
assert set(model_action.choices) == set(cli.MODEL_CONFIGS)
for model in model_action.choices:
assert model in text
with pytest.raises(SystemExit) as stopped:
parser.parse_args(['audio.wav', '--model', 'moss-transcribe-diarize'])
assert stopped.value.code == 2
@pytest.mark.parametrize('path', DOCS, ids=lambda p: p.name)
def test_documented_shell_commands_parse_without_running_inference(path, parser):
for block in blocks(source(path), 'bash'):
checked = subprocess.run(['bash', '-n'], input=block, text=True,
capture_output=True, timeout=10)
assert checked.returncode == 0, checked.stderr
for line in block.splitlines():
args = shlex.split(line, comments=True)
if not args or args[0] != 'funasr':
continue
stop = next((i for i, arg in enumerate(args) if arg in ('|', '>')), len(args))
options = args[1:stop]
if options == ['--help'] or options == ['--version']:
continue
parser.parse_args(options)
def test_bilingual_commands_are_identical():
assert blocks(source(DOCS[0]), 'bash') == blocks(source(DOCS[1]), 'bash')
def test_bilingual_cli_navigation_uses_own_sources():
catalogue = renderer.load_catalogue()
entry = next(page for page in catalogue['pages'] if page['slug'] == 'command-line')
assert entry['source_en'] == 'docs/cli.md'
assert entry['source_zh'] == 'docs/cli_zh.md'
index = (ROOT / 'docs/index.rst').read_text()
assert re.search(r'^\s+cli_zh\s*$', index, re.M)
def test_sphinx_cli_pages_keep_unique_legacy_anchors(tmp_path):
pytest.importorskip('sphinx')
output = tmp_path / 'html'
result = subprocess.run(
[sys.executable, '-m', 'sphinx', '-b', 'html', '-q', str(ROOT / 'docs'), str(output)],
text=True, capture_output=True, timeout=90,
env={**os.environ, 'PYTHONDONTWRITEBYTECODE': '1'})
assert result.returncode == 0, result.stdout + result.stderr
for name in ('cli', 'cli_zh'):
soup = BeautifulSoup((output / f'{name}.html').read_text(), 'html.parser')
ids = [node['id'] for node in soup.select('[id]')]
assert len(ids) == len(set(ids)), name
for anchor in ('command-line-interface', 'installation', 'basic-usage', 'options',
'output-formats', 'text-default', 'json', 'srt', 'tsv',
'advanced-examples', 'models', 'legacy-cli'):
assert anchor in ids, (name, anchor)
@pytest.mark.parametrize('path', DOCS, ids=lambda p: p.name)
def test_existing_public_anchors_and_local_links_remain_valid(path):
from urllib.parse import unquote, urlsplit
soup = BeautifulSoup(markdown(source(path), extensions=['extra', 'toc'],
extension_configs={'toc': {'slugify': renderer.source_slug}}),
'html.parser')
ids = [node['id'] for node in soup.select('[id]')]
expected = {'command-line-interface', 'installation', 'basic-usage', 'options',
'output-formats', 'text-default', 'json', 'srt', 'tsv',
'advanced-examples', 'models', 'legacy-cli'}
assert expected <= set(ids)
assert len(ids) == len(set(ids))
for link in soup.select('a[href]'):
parts = urlsplit(link['href'])
if parts.scheme or parts.netloc:
continue
target = (path.parent / unquote(parts.path)).resolve() if parts.path else path
assert target.is_relative_to(ROOT) and target.is_file(), link['href']
if parts.fragment and target == path:
assert unquote(parts.fragment) in ids
@pytest.mark.parametrize('path', DOCS, ids=lambda p: p.name)
def test_scoped_claims_and_required_boundaries(path):
text = source(path)
for stale in ('~70ms/10s', '~60ms/10s', 'Include word-level timestamps'):
assert stale not in text
for marker in ('audio_duration_s', 'processing_s', 'moss-transcribe-diarize',
'moss_transcribe_diarize', 'AutoModel', 'funasr-server',
'None', '500', '42', 'sentence_info'):
assert marker in text
def test_timestamps_flag_retains_existing_json_data_without_requesting_alignment(tmp_path, monkeypatch, capsys):
audio = tmp_path / 'audio.wav'
audio.write_bytes(b'fixture; no decoder invoked')
seen = []
class Model:
def __init__(self, **kwargs):
pass
def generate(self, **kwargs):
seen.append(kwargs)
return [{'text': 'Example.', 'timestamp': [[0, 1200]]}]
monkeypatch.setitem(sys.modules, 'torch', types.SimpleNamespace(
cuda=types.SimpleNamespace(is_available=lambda: False)))
monkeypatch.setitem(sys.modules, 'funasr', types.SimpleNamespace(AutoModel=Model))
monkeypatch.setitem(sys.modules, 'soundfile', types.SimpleNamespace(
info=lambda _: types.SimpleNamespace(duration=1.2)))
monkeypatch.setattr(sys, 'argv', ['funasr', str(audio), '--timestamps', '-f', 'json'])
cli.main()
payload = json.loads(capsys.readouterr().out)
assert payload['timestamps'] == [[0, 1200]]
assert seen == [{'input': str(audio), 'batch_size': 1}]
@@ -13,7 +13,7 @@
{"slug": "quickstart", "group": "start", "zh": "第一次转写", "en": "Your first transcription", "source_zh": "docs/tutorial/README_zh.md", "source_en": "docs/tutorial/README.md"},
{"slug": "python-api", "group": "start", "zh": "Python SDK: AutoModel", "en": "Python SDK: AutoModel", "source_zh": "docs/python_api_zh.md", "source_en": "docs/python_api.md"},
{"slug": "model-selection", "group": "start", "zh": "选择第一个模型", "en": "Choose your first model", "source_zh": "docs/model_selection_zh.md", "source_en": "docs/model_selection.md"},
{"slug": "command-line", "group": "start", "zh": "命令行转写", "en": "Command-line transcription", "source_zh": "docs/cli.md", "source_en": "docs/cli.md"},
{"slug": "command-line", "group": "start", "zh": "命令行转写", "en": "Command-line transcription", "source_zh": "docs/cli_zh.md", "source_en": "docs/cli.md"},
{"slug": "migration", "group": "start", "zh": "从 Whisper 与云 API 迁移", "en": "Migrate from Whisper & cloud APIs", "source_zh": "docs/migration_from_whisper_zh.md", "source_en": "docs/migration_from_whisper.md"},
{"slug": "moss-transcribe-diarize", "group": "models", "zh": "MOSS 转写与说话人分离", "en": "MOSS transcription & diarization", "source_zh": "docs/moss_transcribe_diarize_zh.md", "source_en": "docs/moss_transcribe_diarize.md"},
{"slug": "model-zoo", "group": "models", "zh": "Model Zoo", "en": "Model Zoo", "source_zh": "model_zoo/readme_zh.md", "source_en": "model_zoo/readme.md"},