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
8 changes: 8 additions & 0 deletions CHANGELOG.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
- added multiline batch submission to the web UI and API
- added multiline batch processing to the desktop window

### MiMo Transcription Engine

- added Xiaomi MiMo (`mimo-v2.5-asr`) as a cloud engine, using OpenAI-compatible chat completions with `input_audio`
- long audio is split into 60s chunks and transcribed concurrently; added `--workers` and the `mimo.workers` config key (default 4)
- chunks retry with backoff so a single rate-limit response cannot discard a long job
- successful chunks are cached under `.b2t/cache/mimo/` keyed by audio content, so a re-run after an interruption only transcribes what is missing
- fixed a crash when indexing local-file transcriptions whose `download` metadata is `None`

## 2026-04-10 (v0.3.0)

### Version Cleanup
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
- Web/API 支持多行输入与批量任务提交
- 桌面窗口支持在输入框中粘贴多行来源并逐条处理

### MiMo 转写引擎

- 新增小米 MiMo(`mimo-v2.5-asr`)云端引擎,走 OpenAI 兼容的 chat completions + `input_audio` 协议
- 长音频按 60 秒切片并发转写,新增 `--workers` 与配置项 `mimo.workers`(默认 4)
- 切片失败自动退避重试,避免单次限流让整个长任务作废
- 转写成功的切片按音频内容哈希缓存到 `.b2t/cache/mimo/`,中断后重跑只补缺失部分
- 修复本地文件转写时 `download` 元数据为 `None` 导致的入库报错

## 2026-04-10 (v0.3.0)

### 版本整理
Expand Down
20 changes: 19 additions & 1 deletion README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ There's also a simple web UI and a desktop window for anyone who'd rather not us
| **Whisper** | Local model | OpenAI's open-source speech recognition model. Runs offline, general-purpose |
| **SenseVoice** | Local model | ONNX-based local model with strong Chinese recognition |
| **Volcengine** | Cloud API | ByteDance's commercial ASR service, good for batch or service-oriented workloads |
| **MiMo** | Cloud API | Xiaomi MiMo ASR (`mimo-v2.5-asr`) over an OpenAI-compatible API; long audio is chunked and transcribed in parallel |

## Quick Start

Expand All @@ -50,7 +51,7 @@ This only installs core dependencies. Transcription engines and extra features a
uv sync --extra whisper --extra web
```

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

### Set Up

Expand Down Expand Up @@ -92,6 +93,23 @@ Or put one BV, URL, or local file path per line:
uv run bili2text batch --file sources.txt
```

### MiMo cloud transcription

MiMo uses an OpenAI-compatible API, so install the extra and configure a key first:

```bash
uv sync --extra mimo
uv run bili2text bootstrap # pick mimo, paste the API key
```

Long audio is split into 60-second chunks and transcribed concurrently. `--workers` sets the concurrency (default 4):

```bash
uv run bili2text tx "BV1kfDTBXEfu" --provider mimo --workers 6
```

Pushing concurrency too high gets you rate-limited (HTTP 429). Individual chunks retry with backoff, and successful chunks are cached under `.b2t/cache/mimo/` keyed by audio content — so re-running an interrupted job **only pays for what is missing**.

## Commands

| Command | Alias | What it does |
Expand Down
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
| **Whisper** | 本地模型 | OpenAI 开源的语音识别模型,离线运行,通用性强 |
| **SenseVoice** | 本地模型 | 阿里云开源本地语音识别模型,中文识别效果好 |
| **火山引擎** | 云端 API | 字节跳动旗下的商用语音识别服务,识别很准很推荐 |
| **MiMo** | 云端 API | 小米 MiMo 语音识别(`mimo-v2.5-asr`),OpenAI 兼容协议,长音频自动切片并发转写 |

## 快速开始

Expand All @@ -57,7 +58,7 @@ uv sync
uv sync --extra whisper --extra web
```

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

### 初始化配置

Expand Down Expand Up @@ -99,6 +100,23 @@ uv run bili2text batch "BV1kfDTBXEfu" "https://www.bilibili.com/video/BV1xx411c7
uv run bili2text batch --file sources.txt
```

### MiMo 云端转写

MiMo 走的是 OpenAI 兼容协议,需要先装 extra 并配好 API Key:

```bash
uv sync --extra mimo
uv run bili2text bootstrap # 选 mimo,填 API Key
```

长音频会自动按 60 秒切片并发转写,`--workers` 控制并发数(默认 4):

```bash
uv run bili2text tx "BV1kfDTBXEfu" --provider mimo --workers 6
```

并发调太高会被服务端限流(HTTP 429)。单个切片失败会自动退避重试;转写成功的切片按内容哈希缓存在 `.b2t/cache/mimo/`,所以任务中断后**原样重跑只会补缺失的部分**,不会重复计费。

## 命令一览

| 命令 | 缩写 | 说明 |
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ sensevoice = [
volcengine = [
"requests>=2.32.3",
]
mimo = [
"openai>=1.40.0",
]
web = [
"fastapi>=0.115.12",
"jinja2>=3.1.6",
Expand Down
26 changes: 26 additions & 0 deletions src/b2t/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ def run_bootstrap(*, settings: Settings, interactive: bool = True) -> AppConfig:
"value": "volcengine",
"enabled": "volcengine" in config.enabled_providers,
},
{
"name": f"mimo — {tr(lang, 'provider_mimo_short')}",
"value": "mimo",
"enabled": "mimo" in config.enabled_providers,
},
]
selected_providers: list[str] = inquirer.checkbox(
message=tr(lang, "bootstrap_providers_prompt"),
Expand Down Expand Up @@ -212,6 +217,8 @@ def run_bootstrap(*, settings: Settings, interactive: bool = True) -> AppConfig:
_configure_sensevoice(config, lang)
elif provider == "volcengine":
_configure_volcengine(config, lang)
elif provider == "mimo":
_configure_mimo(config, lang)

# ── 5. Pick default provider ─────────────────────────────
console.print()
Expand Down Expand Up @@ -324,6 +331,25 @@ def _configure_volcengine(config: AppConfig, lang: str) -> None:
).execute()


def _configure_mimo(config: AppConfig, lang: str) -> None:
config.mimo.api_key = inquirer.secret(
message=tr(lang, "bootstrap_mimo_api_key_prompt"),
default=config.mimo.api_key,
).execute().strip()
config.mimo.base_url = inquirer.text(
message=tr(lang, "bootstrap_mimo_base_url_prompt"),
default=config.mimo.base_url,
).execute().strip()
config.mimo.model_name = inquirer.text(
message=tr(lang, "bootstrap_mimo_model_prompt"),
default=config.mimo.model_name,
).execute().strip()
config.mimo.language = inquirer.text(
message=tr(lang, "bootstrap_mimo_language_prompt"),
default=config.mimo.language,
).execute().strip()


def _show_next_steps(
*,
console: Console,
Expand Down
4 changes: 4 additions & 0 deletions src/b2t/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,14 @@ def transcribe(
prompt: str = typer.Option("", "--prompt", help=tr(language, "opt_prompt_help")),
output: Path | None = typer.Option(None, "--output", help=tr(language, "opt_output_help")),
workspace: Path | None = typer.Option(None, "--workspace", help=tr(language, "opt_workspace_help")),
workers: int | None = typer.Option(None, "--workers", help=tr(language, "opt_workers_help")),
) -> None:
"""Download or open media, then transcribe it with the selected provider."""
try:
settings, config = _load_runtime(workspace=workspace, provider=provider, model=model)
if workers is not None:
# only MiMo splits audio into chunks and runs them in parallel today
config.mimo.workers = max(1, workers)
renderer = TqdmTaskRenderer(config.language)
service = _build_task_service(
settings=settings,
Expand Down
3 changes: 3 additions & 0 deletions src/b2t/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Settings:
transcripts_edited_dir: Path
metadata_dir: Path
tasks_dir: Path
cache_dir: Path
config_path: Path
app_db_path: Path

Expand All @@ -33,6 +34,7 @@ def from_workspace(cls, workspace: Path | None = None) -> "Settings":
transcripts_edited_dir=root / "transcripts" / "edited",
metadata_dir=root / "metadata",
tasks_dir=root / "tasks",
cache_dir=root / "cache",
config_path=root / "config.json",
app_db_path=root / "app.db",
)
Expand All @@ -47,5 +49,6 @@ def ensure_directories(self) -> None:
self.transcripts_edited_dir,
self.metadata_dir,
self.tasks_dir,
self.cache_dir,
):
directory.mkdir(parents=True, exist_ok=True)
14 changes: 14 additions & 0 deletions src/b2t/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ def build_pipeline(
model_name=selected_model or config.volcengine.model_name,
use_itn=config.volcengine.use_itn,
)
elif selected_provider == "mimo":
from b2t.transcribers.mimo import MimoTranscriber

# selected_model may carry whisper's default ("small"); only honor it when it
# actually looks like a MiMo model id.
chosen_model = selected_model if selected_model.startswith("mimo") else config.mimo.model_name
transcriber = MimoTranscriber(
api_key=config.mimo.api_key,
base_url=config.mimo.base_url,
model_name=chosen_model or "mimo-v2.5-asr",
language=config.mimo.language,
workers=config.mimo.workers,
cache_dir=settings.cache_dir,
)
else:
raise RuntimeError(f"Unsupported provider: {selected_provider}")

Expand Down
20 changes: 18 additions & 2 deletions src/b2t/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@
"cmd_language_help": "切换界面语言(缩写: lang)。",
"arg_source_help": "BV 号、Bilibili 链接或本地文件路径。",
"arg_sources_help": "一条或多条输入;也可以用 --file 读取每行一个输入。",
"opt_provider_help": "转写引擎: whisper / sensevoice / volcengine。",
"opt_provider_help": "转写引擎: whisper / sensevoice / volcengine / mimo。",
"opt_model_help": "模型名称。",
"opt_prompt_help": "转写提示词(可选)。",
"opt_output_help": "输出文件或目录。",
"opt_workspace_help": "工作目录,默认 ./.b2t。",
"opt_workers_help": "MiMo 分片并发数,默认读配置(4)。调高更快,太高会被服务端限流。",
"opt_host_help": "监听地址。",
"opt_port_help": "监听端口。",
"opt_language_help": "语言代码,如 zh-CN、en-US。",
Expand Down Expand Up @@ -79,6 +80,10 @@
"bootstrap_volc_resource_prompt": "火山引擎 Resource ID",
"bootstrap_volc_model_prompt": "火山引擎模型名称",
"bootstrap_volc_itn_prompt": "启用逆文本正则化",
"bootstrap_mimo_api_key_prompt": "小米 MiMo API Key(Token Plan)",
"bootstrap_mimo_base_url_prompt": "小米 MiMo Base URL",
"bootstrap_mimo_model_prompt": "小米 MiMo 模型名称",
"bootstrap_mimo_language_prompt": "识别语言(如 zh、en,留空表示自动)",
"bootstrap_saved": "配置已保存到 {path}",
"bootstrap_auto_start": "还没有配置文件,来走一下初始化向导吧……",
"bootstrap_finish": "搞定!随时可以用 `bili2text bootstrap` 重新配置,或 `bili2text lang <代码>` 切换语言。",
Expand Down Expand Up @@ -113,6 +118,7 @@
"provider_whisper_short": "本地离线,不依赖云服务",
"provider_sensevoice_short": "本地 ONNX 模型,中文效果好",
"provider_volcengine_short": "火山引擎云端识别,需要凭据",
"provider_mimo_short": "小米 MiMo 云端 ASR,OpenAI 兼容协议",
"feature_web_short": "浏览器界面,在网页上操作",
"feature_server_short": "服务模式,适合 Docker / 局域网部署",
"feature_window_short": "简单的桌面窗口",
Expand All @@ -123,6 +129,8 @@
"provider_sensevoice_desc": "基于 ONNX 的本地模型,中文识别效果不错。需要模型文件和对应依赖。",
"provider_volcengine_name": "火山引擎云端识别",
"provider_volcengine_desc": "商用云端语音识别,适合服务化部署。需要配置火山引擎凭据。",
"provider_mimo_name": "小米 MiMo(云端)",
"provider_mimo_desc": "小米 MiMo 云端语音识别(mimo-v2.5-asr),走 OpenAI chat completions + input_audio 协议。长音频自动切片并发转写。需要配置 API Key。",

# ── Language ─────────────────────────────────────────
"language_updated": "语言已切换为: {language}",
Expand Down Expand Up @@ -215,11 +223,12 @@
"cmd_language_help": "Switch the interface language (alias: lang).",
"arg_source_help": "BV id, Bilibili URL, or a local media file.",
"arg_sources_help": "One or more inputs; use --file to read one input per line.",
"opt_provider_help": "Transcription engine: whisper / sensevoice / volcengine.",
"opt_provider_help": "Transcription engine: whisper / sensevoice / volcengine / mimo.",
"opt_model_help": "Model name.",
"opt_prompt_help": "Optional transcription prompt.",
"opt_output_help": "Output file or directory.",
"opt_workspace_help": "Workspace root, defaults to ./.b2t.",
"opt_workers_help": "MiMo chunk concurrency; defaults to config (4). Higher is faster until the API rate-limits you.",
"opt_host_help": "Bind address.",
"opt_port_help": "Bind port.",
"opt_language_help": "Language code, e.g. zh-CN or en-US.",
Expand Down Expand Up @@ -269,6 +278,10 @@
"bootstrap_volc_resource_prompt": "Volcengine resource ID",
"bootstrap_volc_model_prompt": "Volcengine model name",
"bootstrap_volc_itn_prompt": "Turn on inverse text normalization",
"bootstrap_mimo_api_key_prompt": "Xiaomi MiMo API key (Token Plan)",
"bootstrap_mimo_base_url_prompt": "MiMo base URL",
"bootstrap_mimo_model_prompt": "MiMo model name",
"bootstrap_mimo_language_prompt": "Recognition language (e.g. zh, en — leave blank for auto)",
"bootstrap_saved": "Config saved to {path}",
"bootstrap_auto_start": "No config file found — let's get you set up...",
"bootstrap_finish": "You're all set! Run `bili2text bootstrap` anytime to reconfigure, or `bili2text lang <code>` to switch languages.",
Expand Down Expand Up @@ -303,6 +316,7 @@
"provider_whisper_short": "Runs locally, no cloud needed",
"provider_sensevoice_short": "Local ONNX model, great for Chinese",
"provider_volcengine_short": "Volcengine cloud ASR, needs credentials",
"provider_mimo_short": "Xiaomi MiMo cloud ASR, OpenAI-compatible",
"feature_web_short": "Web interface — use in your browser",
"feature_server_short": "Server mode for Docker / LAN",
"feature_window_short": "Simple desktop window",
Expand All @@ -313,6 +327,8 @@
"provider_sensevoice_desc": "ONNX-based local model with strong Chinese recognition. Needs the model files and dependencies.",
"provider_volcengine_name": "Volcengine (cloud)",
"provider_volcengine_desc": "Commercial cloud-based speech recognition. Good for service deployments. Requires Volcengine credentials.",
"provider_mimo_name": "Xiaomi MiMo (cloud)",
"provider_mimo_desc": "Xiaomi MiMo cloud ASR (mimo-v2.5-asr) over OpenAI chat completions with input_audio. Long audio is chunked and transcribed in parallel. Requires an API key.",

# ── Language ─────────────────────────────────────────
"language_updated": "Language switched to: {language}",
Expand Down
2 changes: 1 addition & 1 deletion src/b2t/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def register_transcript_result(self, result: TranscriptResult) -> int:
source_input=result.source.raw_input,
source_url=result.source.url,
source_bv=result.source.bv,
title=result.metadata.get("download", {}).get("title") or result.source.display_name,
title=(result.metadata.get("download") or {}).get("title") or result.source.display_name,
display_name=result.source.display_name,
language=result.metadata.get("language"),
engine=result.engine,
Expand Down
2 changes: 2 additions & 0 deletions src/b2t/transcribers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from b2t.transcribers.base import Transcriber
from b2t.transcribers.mimo import MimoTranscriber
from b2t.transcribers.sensevoice_local import SenseVoiceSmallTranscriber
from b2t.transcribers.volcengine import VolcengineFlashTranscriber
from b2t.transcribers.whisper_local import LocalWhisperTranscriber

__all__ = [
"Transcriber",
"LocalWhisperTranscriber",
"MimoTranscriber",
"SenseVoiceSmallTranscriber",
"VolcengineFlashTranscriber",
]
Loading