mirror of
https://github.com/modelscope/FunASR.git
synced 2026-10-12 02:19:18 +00:00
docs: align bilingual CLI guide with parser and output contracts
Signed-off-by: zhifu gao <[email protected]>
This commit is contained in:
+186
-95
@@ -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
@@ -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 参数分属两套语法,不要混用。
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"},
|
||||
|
||||
Reference in New Issue
Block a user