Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ uv sync --extra whisper --extra web

Available extras: `whisper`, `sensevoice`, `volcengine`, `web`, `server`.

#### Prepare the SenseVoice ONNX model

When using SenseVoice, download [`iic/SenseVoiceSmall-onnx`](https://modelscope.cn/models/iic/SenseVoiceSmall-onnx) to a local directory. That ONNX repository does not include the required `chn_jpn_yue_eng_ko_spectok.bpe.model`; download the file with that name from [`iic/SenseVoiceSmall`](https://modelscope.cn/models/iic/SenseVoiceSmall) and place it in the same directory. The final directory must contain at least:

```text
config.yaml
am.mvn
chn_jpn_yue_eng_ko_spectok.bpe.model
model_quant.onnx # official quantized model; model.onnx is also supported
```

Point the SenseVoice model-directory prompt at this directory. bili2text automatically selects quantized or non-quantized loading from `model_quant.onnx` or `model.onnx`, and reports incomplete directories before loading the runtime.

### Set Up

A setup wizard runs automatically the first time, or you can launch it manually:
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ uv sync --extra whisper --extra web

可选的 extras:`whisper`、`sensevoice`、`volcengine`、`web`、`server`。可以暂时不用安装,详看下方的初始化文档。

#### 准备 SenseVoice ONNX 模型

选择 SenseVoice 时,先下载 [`iic/SenseVoiceSmall-onnx`](https://modelscope.cn/models/iic/SenseVoiceSmall-onnx) 到本地目录。该 ONNX 仓库缺少运行时必需的 `chn_jpn_yue_eng_ko_spectok.bpe.model`,还需要从 [`iic/SenseVoiceSmall`](https://modelscope.cn/models/iic/SenseVoiceSmall) 下载这个同名文件,并放进同一目录。最终目录至少应包含:

```text
config.yaml
am.mvn
chn_jpn_yue_eng_ko_spectok.bpe.model
model_quant.onnx # 官方量化模型;也支持非量化 model.onnx
```

配置向导中的 SenseVoice 模型目录应指向这个目录。bili2text 会根据 `model_quant.onnx` 或 `model.onnx` 自动选择量化模式,并在文件不完整时列出缺失项。

### 初始化配置

第一次运行时会自动弹出配置向导,也可以手动运行:
Expand Down
44 changes: 40 additions & 4 deletions src/b2t/transcribers/sensevoice_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@
from b2t.i18n import dependency_sync_guidance
from b2t.transcribers.base import Transcriber

SENSEVOICE_TOKENIZER = "chn_jpn_yue_eng_ko_spectok.bpe.model"
SENSEVOICE_SUPPORT_FILES = ("config.yaml", "am.mvn", SENSEVOICE_TOKENIZER)


class SenseVoiceSmallTranscriber(Transcriber):
name = "sensevoice"

def __init__(self, *, model_dir: Path, language: str = "auto", use_itn: bool = True) -> None:
def __init__(
self, *, model_dir: Path, language: str = "auto", use_itn: bool = True
) -> None:
self.model_dir = model_dir
self.language = language
self.use_itn = use_itn
Expand All @@ -28,7 +33,9 @@ def transcribe(
progress.running("transcribing", message="transcribing", indeterminate=True)

try:
from funasr_onnx.utils.postprocess_utils import rich_transcription_postprocess
from funasr_onnx.utils.postprocess_utils import (
rich_transcription_postprocess,
)
except ImportError as exc:
raise RuntimeError(
"SenseVoice support is not installed. "
Expand Down Expand Up @@ -58,7 +65,11 @@ def _ensure_model(self) -> Any:
return self._model

if not self.model_dir.exists():
raise RuntimeError(f"SenseVoice model directory does not exist: {self.model_dir}")
raise RuntimeError(
f"SenseVoice model directory does not exist: {self.model_dir}"
)

quantize = _validate_model_dir(self.model_dir)

try:
from funasr_onnx import SenseVoiceSmall
Expand All @@ -68,11 +79,36 @@ def _ensure_model(self) -> Any:
f"{dependency_sync_guidance('en-US')}"
) from exc

self._model = SenseVoiceSmall(str(self.model_dir))
self._model = SenseVoiceSmall(str(self.model_dir), quantize=quantize)
return self._model


def _extract_text(item: object) -> str:
if isinstance(item, dict):
return str(item.get("text", ""))
return str(item)


def _validate_model_dir(model_dir: Path) -> bool:
missing = [
filename
for filename in SENSEVOICE_SUPPORT_FILES
if not (model_dir / filename).is_file()
]
if missing:
missing_text = ", ".join(missing)
raise RuntimeError(
f"SenseVoice model directory is incomplete ({model_dir}). Missing: {missing_text}. "
"The ONNX repository does not include the SentencePiece tokenizer; copy "
f"{SENSEVOICE_TOKENIZER} from iic/SenseVoiceSmall into this directory."
)

if (model_dir / "model_quant.onnx").is_file():
return True
if (model_dir / "model.onnx").is_file():
return False

raise RuntimeError(
f"SenseVoice model directory is incomplete ({model_dir}). "
"Missing model_quant.onnx or model.onnx."
)
59 changes: 59 additions & 0 deletions tests/test_sensevoice_local.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from __future__ import annotations

import sys
from pathlib import Path
from types import SimpleNamespace

import pytest

from b2t.transcribers.sensevoice_local import SenseVoiceSmallTranscriber

REQUIRED_SUPPORT_FILES = (
"config.yaml",
"am.mvn",
"chn_jpn_yue_eng_ko_spectok.bpe.model",
)


def _write_support_files(model_dir: Path) -> None:
for filename in REQUIRED_SUPPORT_FILES:
(model_dir / filename).write_text("test", encoding="utf-8")


@pytest.mark.parametrize(
("model_filename", "expected_quantize"),
(("model_quant.onnx", True), ("model.onnx", False)),
)
def test_sensevoice_selects_quantization_from_available_model(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
model_filename: str,
expected_quantize: bool,
) -> None:
_write_support_files(tmp_path)
(tmp_path / model_filename).write_bytes(b"onnx")
calls: list[tuple[str, bool]] = []

def fake_model(model_dir: str, *, quantize: bool):
calls.append((model_dir, quantize))
return object()

monkeypatch.setitem(
sys.modules, "funasr_onnx", SimpleNamespace(SenseVoiceSmall=fake_model)
)

transcriber = SenseVoiceSmallTranscriber(model_dir=tmp_path)
transcriber._ensure_model()

assert calls == [(str(tmp_path), expected_quantize)]


def test_sensevoice_reports_missing_tokenizer_before_model_load(tmp_path: Path) -> None:
(tmp_path / "model_quant.onnx").write_bytes(b"onnx")
(tmp_path / "config.yaml").write_text("test", encoding="utf-8")
(tmp_path / "am.mvn").write_text("test", encoding="utf-8")

transcriber = SenseVoiceSmallTranscriber(model_dir=tmp_path)

with pytest.raises(RuntimeError, match="chn_jpn_yue_eng_ko_spectok.bpe.model"):
transcriber._ensure_model()