关于 asr-transcribe-to-text
asr-transcribe-to-text 是将音频或视频转为带说话人标签文本的技能。它会先检查是否已有规范转录文本,再提供云端转写或本地 ASR 路径,并支持媒体预处理和说话人分离。
主要功能
- 01接受本地文件和媒体 URL
- 02处理前检查是否已有转录文本
- 03支持 Feishu Minutes 和本地 ASR 路径
- 04预处理、合并并验证音频输入
- 05生成带说话人标签的文本和 CSV
- 06提供无时间戳的纯文本模式
适用场景
- 01转写尚无规范文本的录音
- 02转写前合并录音设备分段
- 03离线或隐私敏感场景使用本地 ASR
- 04为会议录音生成说话人时间线
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ daymade/ claude-code-skills --skill asr-transcribe-to-text安装后在 Claude Code 中输入/asr-transcribe-to-text确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.gitignorereferences/batch_vad_coverage.mddecoupled_speaker_alignment.mdexecution_location_and_comparison.mdlocal_mlx_guide.mdoverlap_merge_strategy.mdreview_workbench.mdspeaker_diarization.mdvoiceprint_speaker_id.mdwhisper_word_timestamps.mdwhispercpp_gpu_runtime.mdscripts/align_speakers.pybuild_whispercpp_gpu.pydiarize_speakers.pydiarize_speakers.py.lockfuse_whispercpp_diarization.pygenerate_audit_html.pymeasure_asr.pyoverlap_merge_transcribe.pyprepare_asr_input.pyresolve_media_input.pyscripts/review-workbench/prepare_project.pyserver.pyscripts/review-workbench/static/app.jsindex.htmlstyles.cssscripts/review-workbench/tests/test_client_integrity.cjstest_frontend.cjstest_playback.cjstest_prepare_project.py详细介绍
ASR 转写为文本
将音频/视频转写为带说话人标签的文本。本地执行有两条明确路径。长录音或无人值守录音使用带检查点的 whisper.cpp + Silero VAD 分块处理,然后再与 pyannote 说话人信息进行后期融合。短/中等长度录音可使用 Qwen3-ASR + mlx-whisper 对齐路径。两条路径都不会按说话人分段切分 ASR 输入;说话人归属在连续上下文 ASR 之后完成。
ASR 前的路径选择:转录文本才是结果,不是运行过程
开始转录前,请检查所属项目的转录目录、外部来源索引和声明的既有工作载体,按来源 ID、日期、标题和实体词查找现有的规范转录文本。经核实、由人工审阅且为最新版本的转录文本会结束此任务,除非用户明确要求进行新的独立比较。原始音频仍然存在,并不是重新生成已有文本的理由。
如果不存在规范转录文本:
- 对于允许云端处理的一般会议/DJI 录音,通常优先使用飞书妙记(先预处理为较小的 M4A 文件)。
- 如果用户要求离线/隐私处理、飞书不可用或失败,或任务明确需要独立的质量比较,则使用本地 ASR。
2b. 如需第二条独立的说话人时间线,请通过
stepfun-asr技能的asr_file.py使用 StepFun 的异步文件端点。这里的供应商说话人分离路径中,只有它不与本地 pyannote 共用代码或模型,因此两者结果一致才是真正的交叉验证,而不是一个工具赞同自己。2026-09-19 在一部 10 分钟影片上的测量结果:两者对关键轮次的切分完全一致,而飞书妙记错误归属了该轮次。限制在于它需要获取一个公开 URL——不接受 base64——因此需要先暂存文件(参见aliyun-multi-account.md);仅当归属结果至关重要时才使用,不要用于常规转录。 - 对于非会议媒体或明确要求使用本地/远程 ASR 的任务,请依据下方的音频位置规则选择执行位置。
不要只是为了让双路径流程看起来完整,就运行本地 ASR。
在选择本地还是远程执行前,请阅读 执行位置与比较。 优先使用现有文本和经验证的检查点,然后比较来源位置、处理权限、实际 GPU/模型身份以及端到端质量证据。 缺少测量数据时,优先选择靠近音频且符合条件的路径,并说明这样做避免了文件传输;不要声称它更快。
配置保存在 ${CLAUDE_PLUGIN_DATA}/config.json。
默认添加说话人标签。 每次运行都会生成
[start-end] SPEAKER_xx: text
- CSV。纯文本输出为可选项(
--no-diarization),适用于独白、 播客,或只需要摘要的情况——参见步骤 3。它还会移除所有时间戳: 纯文本路径只返回会话文本,不包含其他内容。需要逐行时间戳的独白 (字幕、可跳转到具体时刻的档案)应运行完整流水线,并忽略SPEAKER_00标签。说话人分离的一次性设置: pyannote 是受限访问的 HuggingFace 模型—— 需要设置一次令牌(见下方的
## Speaker Diarization & Identification)。首次运行 若未设置令牌,会失败并显示设置步骤;完成设置后,全部功能将永久可用并自动检测。
步骤 0:检测平台并加载配置
cat "${CLAUDE_PLUGIN_DATA}/config.json" 2>/dev/null
如果配置存在,读取值并继续步骤 1。
如果配置不存在,先自动检测平台:
python3 -c "
import sys, platform
is_mac_arm = sys.platform == 'darwin' and platform.machine() in ('arm64', 'aarch64')
print(f'Platform: {sys.platform} {platform.machine()}')
print(f'Apple Silicon: {is_mac_arm}')
if is_mac_arm:
print('RECOMMEND: local-mlx')
else:
print('RECOMMEND: remote-api')
"
然后根据平台使用带适配默认值的 AskUserQuestion:
对于 macOS Apple Silicon(推荐:本地):
ASR setup — your Mac supports local MLX; choose from data location and the execution SOP.
Q1: Transcription mode?
A) Local MLX — runs on your Mac's GPU, no API key needed
B) Remote API — send audio to a server (vLLM, Tailscale workstation, etc.)
Q2: Does your network have an HTTP proxy that might intercept traffic?
A) Yes — bypass proxy for ASR traffic (Recommended if using Shadowrocket/Clash)
B) No — direct connection
对于 其他平台(推荐:远程):
ASR setup — local MLX requires macOS Apple Silicon. Using remote API mode.
Q1: ASR Endpoint URL?
A) https://asr.example.com/v1/audio/transcriptions (Self-hosted remote ASR)
B) http://localhost:8002/v1/audio/transcriptions (Local ASR server)
C) Custom URL
Q2: Proxy bypass needed?
A) Yes (Recommended for Shadowrocket/Clash/corporate proxy)
B) No
保存配置:
mkdir -p "${CLAUDE_PLUGIN_DATA}"
python3 -c "
import json
config = {
'mode': 'MODE', # 'local-mlx' or 'remote-api'
'model': 'MODEL_ID', # local: 'mlx-community/Qwen3-ASR-1.7B-8bit', remote: 'Qwen/Qwen3-ASR-1.7B'
'max_tokens': 8192, # local only; PER 20-minute chunk, not per recording
'endpoint': 'URL', # remote only
'noproxy': True,
'max_timeout': 900 # remote only
# 'diarization_declined': True # set only after the user explicitly declines
# the pyannote setup in Step 3 — every run then warns + goes plain-text
# until an HF token appears (auto-detected)
}
with open('${CLAUDE_PLUGIN_DATA}/config.json', 'w') as f:
json.dump(config, f, indent=2)
print('Config saved.')
"
步骤 1:解析输入
接受本地文件、直接媒体 URL 或网页/播客单集页面。
- 网页或播客页面 URL:先检查页面上是否有现成的转录文本。只有当用户的账户可以直接访问官方/平台转录文本时,才使用该文本。如果转录接口需要登录令牌,而当前没有可用令牌,请明确说明,并改用音频 URL 进行 ASR。
- 本地文件、直接媒体 URL 或页面 URL 的备用方案:运行随附的解析器。它会从常见页面元数据(
og:audio、媒体标签、JSON-LD、RSS 样式的 enclosure 链接)中提取媒体,通过原子临时文件替换方式下载 URL,在远程Content-Length存在时进行验证,计算 SHA-256,并使用ffprobe验证结果。
uv run ${CLAUDE_SKILL_DIR}/scripts/resolve_media_input.py \
INPUT_FILE_OR_URL [INPUT_FILE_OR_URL2 ...] \
--output-dir OUTPUT_DIR \
--manifest OUTPUT_DIR/media_manifest.json
对于可疑或高价值的下载,可添加 --decode-check,让 ffmpeg 在转录前解码整个文件:
uv run ${CLAUDE_SKILL_DIR}/scripts/resolve_media_input.py \
"https://www.xiaoyuzhoufm.com/episode/EPISODE_ID" \
--output-dir OUTPUT_DIR \
--manifest OUTPUT_DIR/media_manifest.json \
--decode-check
预期输出:
Downloaded ... bytes in ...s -> OUTPUT_DIR/episode-title.m4a
OUTPUT_DIR/episode-title.m4a
在后续步骤中,将打印出的本地路径用作 INPUT_AUDIO。如果运行环境显示的是字面值 ${CLAUDE_SKILL_DIR},而不是替换后的路径,请按照本文档底部“故障排除”条目的说明解析技能目录。
对于第三方公开播客或受版权保护的媒体,请将转录文本保存为本地文件,供用户个人分析。不要在聊天中粘贴完整的长篇转录文本;请改为提供文件路径、预览、摘要或简短摘录。
步骤 2:提取音频(输入为视频时)
对于视频文件(mp4、mov、mkv、avi、webm),提取为 16kHz 单声道 WAV:
ffmpeg -i INPUT_VIDEO -vn -acodec pcm_s16le -ar 16000 -ac 1 OUTPUT.wav -y
音频文件(wav、mp3、m4a、flac、ogg)可直接使用。获取时长:
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 INPUT_FILE
清理:转录成功后,删除提取出的 WAV 文件以节省磁盘空间。
预处理:合并片段并缩减按量计费的上传文件(可选)
在转写之前运行此步骤,适用于以下任一情况:
- 录音是由多个片段组成的转储文件——领夹麦克风和现场录音机通常会将录音拆分为固定时长的文件(例如
TX02_MIC024_....wav、TX02_MIC025_....wav;TX01/TX02= DJI MIC MINI 2S 内部录音——设备清单以及录音机→飞书妙记路径:meeting-ingest 技能的meeting-ingest/references/architecture.md§①-L0)。 将它们合并一次,以保留录制顺序;随后由显式的长音频运行器(步骤 3,路径 L)负责稳定的源时间块。分别转写设备片段会丢失这些分块中的跨片段上下文。 - 音频将提交给按量计费的 ASR(飞书妙记、任何按分钟计费的配额)——保留音高的加速可直接缩短计费时长,现代 ASR 也不受影响: 用户已在飞书妙记上验证,1.3 倍速没有可察觉的识别差异(2026-07-16);公开的 Whisper 基准测试显示,直到 2.0 倍速,词错误率(WER)才会明显下降(≤1.5 倍速 = 安全区,1.5 倍速时 WER 增加约 3%;>2 倍速则无法使用)。
使用附带的脚本——它会合并、规范化为 16 kHz 单声道、可选加速,并自行验证输出,而不是盲信 ffmpeg 的退出代码:
uv run ${CLAUDE_SKILL_DIR}/scripts/prepare_asr_input.py SEG1.wav SEG2.wav -o merged.wav # merge only
uv run ${CLAUDE_SKILL_DIR}/scripts/prepare_asr_input.py SEG*.wav -o upload.m4a --speed 1.3 # merge + quota-saving speedup
预期输出:
Merge order:
1. SEG1.wav [pcm_s24le 48000Hz ch=1 1800.14s]
2. SEG2.wav [pcm_s24le 48000Hz ch=1 1800.15s]
[OK] duration: 4946.19s vs expected 4946.18s (delta +0.00s)
[OK] boundary 1 @ 1384.7s: max_volume -15.5 dB
[info] overall: mean_volume -38.3 dB, max_volume 0.0 dB
Wrote upload.m4a
-
如果每个文件名中都包含
YYYYMMDD_HHMMSS时间戳(录音机转储文件包含),片段将按时间戳排序;否则会保留给定顺序并附带说明——转写前请检查打印出的合并顺序。 -
自我验证:输出时长必须等于 Σ片段 ÷ 速度(误差 ±1.5 秒,否则硬性失败);每个拼接处都会进行 10 秒音量抽查(边界处出现静音 = 顺序错误或缺少片段);还会打印整体响度,以便与源文件比较。
-
加速必须采用
atempo风格的保留音高拉伸——绝不要使用采样率技巧,否则会改变音高,并破坏 ASR 准确率和说话人日志中的声纹。 -
根据目标选择输出格式——编解码器由文件扩展名决定:
目标 格式 原因 本地 MLX 流水线(路径 A) .wav或.m4a两种格式都可直接输入流水线(m4a 已于 2026-07-18 验证:3 分钟片段可干净转写)。M4A 体积约小 5 倍——2 小时 49 分钟的合并文件从 324 MB WAV 降至 63 MB M4A,时长完全相同 按量计费的上传(飞书妙记,按分钟计费的配额) .m4a+--speed 1.3AAC 48k 对 ASR 来说在语音质量上几乎无损;同等语音质量下,体积比 mp3 小约 30%;加速可使计费时长缩短约 23% 可自托管的 vLLM 端点(路径 B) .ogg在 MP3 被拒绝的情况下仍可接受,体积约比 WAV 小 8 倍——这正是让长录音低于服务端 25 MB 请求上限的关键。参见路径 B 的限制部分 无损归档 .flac体积约为 WAV 的 50%,位级无损 仅当目标不接受上述格式时 .mp3兼容性回退方案 -
保留原始文件,直到转录文本通过步骤 4 的验证。
选项:上传到飞书妙记进行转写
预处理后,如果用户需要飞书妙记,或普通会议 / DJI 录音没有权威转录文本且允许云端处理,请使用此路径。这是常规的会议音频处理路径,并非必须先在本地运行失败后才使用的回退方案。
触发短语:传到妙记 / 上传到飞书妙记 / 让妙记转写 / 根据此音频创建妙记 / 上传到飞书妙记。
上传前确认用户想要的结果:
- 仅上传 — 用户明确表示只需要一个妙记链接,或只需要上传媒体。创建
minute_url即为终止状态。 - 仅需转写文本 — 用户需要转写文本/摘要,但不需要归档到项目中。上传后,按照当前版本匹配的
lark-minutes说明执行,直到获得就绪的转写文本。 - 交付到项目 — 用户需要校正、路由、知识库文件、项目索引或 Git 交接。
meeting-ingest负责整体任务;此技能仅参与预处理。在上传前切换到该编排器,并持续运行,直到交付回执通过验证。 - 未指定后续处理 — 请求要求预处理/上传(例如,“上传到飞书妙记,先转成适合 ASR 的格式”),但未说明妙记创建后应如何处理。按要求完成上传,不要擅自推断转写或项目范围,然后返回
outcome_pending及持久化令牌/URL。之后若用户要求转写或存入知识库,应继续处理同一个妙记;不得从头开始,也不得依赖最初的猜测。
不要仅仅因为用户的第一句话提到“上传到妙记”,就推断为仅上传。同一请求或项目上下文中明确指定的后续处理结果优先。
约束:
- 不使用代理:所有
lark-cli调用都必须使用LARK_CLI_NO_PROXY=1。 - 仅使用单个配置文件:只使用当前启用的飞书配置文件。不要遍历租户配置文件,也不要调用租户路由。
- 默认不进行本地重复 ASR:此分支由飞书负责转写。只有明确要求离线处理/对比时,才运行本地 ASR。
- 加载当前飞书指南:使用
lark-cli-router,然后阅读版本匹配的lark-minutes上传/详情说明。不要用此技能中的过时停止条件覆盖当前 CLI 契约。
分步操作:
-
尽可能使用上一节中已预处理的音频。飞书接受 MP4/MOV 封装中的
.m4a、.mp3、.wav、.aac文件;预处理器中的“Metered upload”行已符合要求。文件大小须低于 6 GB,时长须低于 6 小时——这是飞书上传的硬性限制。 -
以用户身份上传到云盘:
LARK_CLI_NO_PROXY=1 lark-cli drive +upload \ --file '<preprocessed-media-path>' \ --name '<basename>' \ --as user \ --format json从结果中记录
file_token。如果命令因路径校验或 multipart 错误而失败,不要盲目重试——更换格式或大小策略后再试一次,然后报告确切错误。 -
从该云盘文件创建妙记:
LARK_CLI_NO_PROXY=1 lark-cli minutes +upload \ --file-token '<file_token>' \ --as user \ --format json从结果中记录
minute_token和minute_url。 -
根据先前确定的终止处理结果选择分支:
- 仅上传:返回
minute_url并停止。 - 仅需转写文本:使用当前
lark-minutes详情命令及其就绪等待行为,并指定--transcript。创建的 URL 只是中间结果;只有获得可读的转写文本产物才算成功。 - 交付到项目:将以下交接信息返回给
meeting-ingest,并在同一次运行中继续:prepared_media、file_token、minute_token、minute_url、next_required_phase=minute_ready。编排器会等待云端转写文本,调用sync-feishu-minutes执行令牌范围内的导入/路由/委派,运行完整的transcript-fixer,更新所有项目所属索引,完成 Git 交接,并记录交付情况。 - 未指定后续处理:返回
minute_token、minute_url、outcome_pending和next_required_phase=outcome_decision。在用户说明后续处理方式之前,不要等待转写文本,也不要归档转写文本。
- 仅上传:返回
如果就绪等待超时或后续阶段受阻,请报告最后完成阶段的确切信息,以及持久化的 minute_token/minute_url。继续处理同一个令牌;切勿只为获取新 URL 而重新上传。
预期输出:
- 仅上传成功:一个用户可打开的
minute_url。 - 下游未指定的情况下成功:所请求的上传已完成,同时运行状态明确保持为可恢复的
outcome_pending;不要称其为项目交付。 - 仅转录成功:
minute_url加上可读的转录文本文件。 - 项目交付成功:仅提供
meeting-ingest推送的交付回执;预处理、创建 URL 和下载转录文本都属于中间阶段。 - 失败:提供
drive +upload或minutes +upload返回的准确 API 错误,以及一项建议的后续操作。
技能选择错误时的恢复:如果此请求是在你使用
sync-feishu-minutes 时收到的,请根据结果选择:仅上传则转到此处;项目交付则转到
meeting-ingest,它会调用此预处理分支,然后继续执行基于令牌范围的飞书摄取。绝不要悄悄将项目交付降级为 Minute URL。
步骤 3:转录(默认标注说话人)
路径 L:较长的本地录音——whisper.cpp + Silero VAD(默认用于超过 30 分钟的录音)
超过 30 分钟的录音、无人值守的批量任务,或任何可能包含长时间静音/会后环境音的来源,都使用此路径。这是面向 Apple Silicon 的长音频处理方案:按源时间划分的区块会创建检查点,每个区块都会运行 whisper.cpp 的 Silero VAD,在区块交界处对 2 秒重叠部分去重,随后再融合 pyannote 说话人标签。
运行时资源和 GPU 构建清单都是明确的操作员输入。不要在批处理中静默发现或下载模型。使用此路径前,
请阅读 仅 GPU 的运行时准备。
Agent 会在现有设置授权范围内准备隔离且固定版本的构建;
未经修改的二进制文件带有 --use-gpu 标志,并不能证明 GPU VAD 已启用。运行器会在加载模型前拒绝未知的构建身份,原生防护机制也会在推理前拒绝 CPU 或未知的计算图分配。
# 1. Checkpointed ASR on the original source timeline
uv run ${CLAUDE_SKILL_DIR}/scripts/transcribe_long_whispercpp.py \
INPUT_16K_MONO_PCM16.wav OUTPUT_DIR \
--ffmpeg-path /absolute/path/to/ffmpeg \
--whisper-bin /absolute/path/to/whisper-cli \
--gpu-runtime-manifest /absolute/path/to/user-cache/gpu-runtime.json \
--whisper-model /absolute/path/to/ggml-large-v2-or-v3.bin \
--vad-model /absolute/path/to/ggml-silero-v6.2.0.bin
# 2. Independent speaker timeline (controlled FFmpeg decode; no TorchCodec path)
uv run --frozen ${CLAUDE_SKILL_DIR}/scripts/diarize_speakers.py \
INPUT_16K_MONO_PCM16.wav OUTPUT_DIR/STEM.diarization.json \
--device mps --ffmpeg-path /absolute/path/to/ffmpeg
# 3. Keep only speech-grounded ASR segments and assign speakers by time overlap
uv run ${CLAUDE_SKILL_DIR}/scripts/fuse_whispercpp_diarization.py \
OUTPUT_DIR/STEM.whispercpp.json \
OUTPUT_DIR/STEM.diarization.json \
INPUT_16K_MONO_PCM16.wav OUTPUT_DIR
如果实体录音设备在业务会议结束后仍继续录音,
请向两个相关步骤都传入有证据支持的源时间戳(--end-at SECONDS)。保留原始音频和不设时长限制的 ASR 证据,但不要将会后的车内噪声或静音强行纳入会议转录文本。
完成意味着最终的 TXT/CSV/融合回执均已生成,并且已抽查开头、中间、结尾和每个外部区块交界处。仅有 N/N blocks complete 并不代表质量合格。重新运行时必须报告每个已完成区块均已缓存。
官方架构依据:whisper.cpp VAD 会在推理前提取语音;OpenAI Whisper 会重置先前文本的上下文,以避免重复循环;NeMo 的长音频指南使用带重叠的缓冲区块。项目特定的实测证据和参数说明见
references/speaker_diarization.md。
路径 A:本地 MLX(macOS Apple Silicon)——短/中音频的替代方案
运行解耦的说话人处理流水线——它会在内部处理依赖版本固定、分块生成上限、可恢复检查点、模型加载和进程树清理。
输入扩展名不能作为解码器契约。Qwen 工作进程首先使用固定版本的 MLX 解码器;如果该解码器报告了可识别的输入/容器解码失败(例如 miniaudio 无法处理 Ogg/Opus),则会使用 ffmpeg 为固定版本的默认模型创建临时的 16 kHz 单声道 PCM WAV,然后重试(自定义本地模型使用其声明的采样率)。GPU、内存及其他无关的运行时错误会原样传递,绝不会触发规范化处理。检查点、输出名称和来源信息仍与原始源字节绑定;临时 WAV 绝不会作为完成产物。
uv run ${CLAUDE_SKILL_DIR}/scripts/speaker_transcribe.py \
INPUT_AUDIO [INPUT_AUDIO2 ...] OUTPUT_DIR
无人值守批处理:每次调用处理一个文件。任何环节发生确定性故障时(例如某段录音触发每个分块的令牌上限,导致 ASR_DETERMINISTIC_BLOCKED: ChunkTokenLimitError),整个多输入运行都会按设计退出,排在该文件之后的文件将不会启动——一次 31 个文件的批处理就曾因此在第 11 个文件处中止。逐文件调用可将影响限制在单个录音内;中间环节会缓存,因此重新运行其他文件只需执行对齐。
(每个文件的)预期输出:
Device: mps
+ uv run .../transcribe_local_mlx.py ... (leg 1: session text)
Chunk 1/6 starting at 0.0s (max_tokens=8192)
Chunk 1/6 committed: 6310 chars, 3812 tokens
+ uv run .../word_timestamps_whisper.py ... (leg 2: timing lattice)
... diarization ... (leg 3: pyannote segments)
STEM: 42 turns, speakers=['SPEAKER_00', 'SPEAKER_01'], anchored_ratio=0.93
Wrote STEM.txt, STEM.csv, STEM.alignment.json, STEM.receipt.json
每个输入对应的输出:<stem>.txt([MM:SS - MM:SS] SPEAKER_xx + 文本)、
<stem>.csv(file,start,end,duration,speaker,text——供审核 UI 和
声纹 ID 使用)、<stem>.diarization.json、<stem>.alignment.json(来源信息
anchored_ratio可信度信号;低于 0.5 时会打印醒目警告——请先对照音频核验 标签,再予以信任),以及<stem>.receipt.json(原子完成记录,绑定源文件字节、 全部四个最终产物的哈希值、生成脚本、固定版本的模型/依赖项和语义参数)。中间环节会缓存在OUTPUT_DIR/_align/中,因此重新运行开销很低(--force会重新执行最终环节)。每个中间缓存 旁车文件都会绑定源音频字节、生成脚本字节、语义参数和产物字节;仅文件存在并不构成缓存命中。下游完成状态检查要求提供最终回执,而非仅凭非空产物或对齐 JSON。Qwen 分块检查点位于其暂存目录下;运行中断后,会核验 源音频 SHA-256、生成器/拆分器/依赖项约定、不可变模型版本、生成参数和已完成分块的哈希值,然后跳过已完成的分块,而不是从头重新处理录音。一个与语言无关的 12 字符 n-gram 防护机制会在最终交付前,拒绝文本高度重复的分块或整个会话; 质量策略 ID 是检查点身份的一部分,因此较早生成且未经检查的部分无法绕过该防护。
使用 Qwen3 路线前,先对其环节进行一次冒烟测试:
uv run ${CLAUDE_SKILL_DIR}/scripts/transcribe_local_mlx.py --smoke-test
预期会看到随附脚本的依赖项验证结果和 Smoke test OK;固定版本定义在
scripts/transcribe_local_mlx.py 中。有关性能、每个分块的令牌语义、
资源限制和恢复机制,请阅读 references/local_mlx_guide.md。
工作原理(及原因): 使用整个会话的 Qwen3-ASR 文本、mlx-whisper 词级
时间戳和 pyannote 说话人片段,事后再进行对齐。这样可以避免质量受损的旧级联方案——它会分别转录每个经过说话人分离的轮次。不要将其有界的生成分块视为适用于长时间录音的保证:一段真实的多小时录音在 20、10 和
5 分钟窗口下都触及了令牌上限。架构、对齐算法和故障模式:
references/decoupled_speaker_alignment.md。
首次运行:pyannote 需要一次性配置 HuggingFace 令牌。 如果脚本因显示设置提示而退出 (退出代码为 3),请停止并使用 AskUserQuestion:
Speaker diarization needs a one-time setup (gated model, free):
1. Accept terms at https://hf.co/pyannote/speaker-diarization-3.1
2. Run `huggingface-cli login` (or set HF_TOKEN)
Options:
A) Set it up now — I'll wait, then rerun with full speaker labels (Recommended)
B) Continue without speakers this time — plain text only
- A → 用户确认登录后,重新运行相同命令。每次运行都会自动检测令牌;从此即可永久使用完整功能。
- B → 持久化此选择(在 config.json 中设置
diarization_declined: true),然后重新运行相同命令。脚本会检测此标志,打印一行警告并附上两个设置步骤,然后在本次运行中自动回退到纯文本——无需传递--no-diarization(现在会自动回退,由脚本强制执行,而非仅记录在文档中)。只要令牌仍缺失,之后每次运行都会同样发出警告并继续。令牌日后出现时,说话人分离会自动恢复(令牌存在后,此标志将被忽略)——请说明这一点,让用户知道只需完成设置即可。
纯文本快速路径(独白、播客、“只要总结一下”):
uv run ${CLAUDE_SKILL_DIR}/scripts/speaker_transcribe.py \
INPUT_AUDIO OUTPUT_DIR --no-diarization
远程/预制 ASR 文本(例如来自 Path B 或其他 ASR 服务):跳过
Qwen3 环节,改为对该文本进行对齐。--text-file 会将一份转录文本与一个输入 wav 配对——传入多个输入会被拒绝(一份转录文本不能与多个文件对齐):
uv run ${CLAUDE_SKILL_DIR}/scripts/speaker_transcribe.py \
INPUT_AUDIO OUTPUT_DIR --text-file TRANSCRIPT.txt
**非 Apple Silicon 设备:**whisper 计时环节仅支持 MLX。没有该环节,就没有可用于对齐说话人的时间网格——请使用 --no-diarization 运行,并告知用户说话人模式目前需要 Apple Silicon(带内置说话人分离功能的云 ASR,例如 Feishu Minutes,是无需本地 GPU 的替代方案)。
批量处理多个短文件之前(宣传片段、蒙太奇剪辑——任何可能包含纯音乐音频的内容),请阅读下文的 ## Batch Transcription (many short files):一个纯音乐片段就可能让整个批次停滞 10 分钟以上。
Path B:远程 API
远程端点只返回纯文本——之后会在本地将该文本(环节 1)与本地计时和说话人分离环节对齐,从而添加说话人。因此,Path B = 远程获取文本,再使用 --text-file 运行 Path A 的流程。
请使用随附的远程运行器和现有配置。它会检查响应语义、绑定源字节与结果字节,并在配置的服务公开相关信息时记录观测到的生产者身份;它绝不会切换共享模型。关于可自托管 GPU 的证据和性能比较,请参阅执行位置和比较。
python3 ${CLAUDE_SKILL_DIR}/scripts/transcribe_remote.py \
INPUT_AUDIO OUTPUT.txt --config "${CLAUDE_PLUGIN_DATA}/config.json"
预期会生成 OUTPUT.txt 和 OUTPUT.txt.asr.json;回执代表纯文本环节,并不表示识别质量或说话人信息已完整。
然后在本地添加说话人(需要 Apple Silicon 和 pyannote 令牌):
uv run ${CLAUDE_SKILL_DIR}/scripts/speaker_transcribe.py \
INPUT_AUDIO OUTPUT_DIR --text-file OUTPUT.txt
可自托管 vLLM:容易以令人困惑的方式触发的限制
**版本在这里很重要——其中两项限制在不同版本之间有过变更。**以下行为是在 vLLM 0.15.2rc1.dev68(开发版本;没有 0.15.2 正式版——PyPI 版本从 0.15.1 跳到 0.16.0)上提供 Qwen/Qwen3-ASR-1.7B 时端到端测得的,之后又对照 v0.26.0 源码重新核查。请先检查你使用的版本——运行 pip show vllm——并阅读第 1 项和第 3 项的版本说明。
**1. 发送 OGG,而不是 WAV——绝不要发送 MP3。**在 0.15.x 上,MP3 会被直接拒绝,但下意识采用的修复方法(转换为 WAV)会让你撞上第 2 项中的大小上限:
| 格式 | 60 秒 @ 16 kHz 单声道,16 位 | 是否接受(0.15.x) |
|---|---|---|
WAV pcm_s16le | 1,920 KB | 是 |
| FLAC | 1,092 KB | 是 |
| OGG Vorbis | 245 KB | 是 |
| MP3 | — | 否 |
在相同采样率下,OGG 比 WAV 小约 8 倍:
ffmpeg -nostdin -v error -i INPUT -ar 16000 -ac 1 -c:a libvorbis OUTPUT.ogg
自行比较格式时,请固定位深——解码有损源文件时,ffmpeg 可以自由提高位深;24 位 FLAC 的体积会比 16 位 PCM 更大。如果两者并非来自同一段录音,就会让人误以为“FLAC 不压缩”。请添加 -sample_fmt s16。
值得注意的是,MP3 被拒绝时,HTTP 状态码为 200,但响应正文中包含错误——只检查 %{http_code} 的判断会报告成功:
HTTP=200
{"error": {"message": "Error opening <_io.BytesIO object>: Format not recognised.", ...}}
*版本说明:*在 0.15.x 上,上传内容会通过 BytesIO 交由 librosa/soundfile 读取;即使主机上的 libsndfile 能处理磁盘上的 MP3 文件,BytesIO 中的 MP3 仍会被拒绝。v0.26.0 在 soundfile 抛出 LibsndfileError 后新增了 pyav 回退方案(multimodal/media/audio.py),因此当前版本可能可以解码 MP3/M4A;不过,基于上文所述的大小原因,OGG 仍是更好的选择。
2. 请求大小上限为 25 MB。
{"error":{"message":"Maximum file size exceeded (parameter=audio_filesize_mb, value=28.6)",...}}
VLLM_MAX_AUDIO_CLIP_FILESIZE_MB 的默认值为 25(vllm/envs.py;从 0.15.1 到 v0.26.0 均未变)。按 OGG 每分钟约 245 KB 计算,大约 100 分钟就会达到上限——足以处理一场会议,但全天录音或合并的多段录音文件就会超出上限。当任务足够长、需要调整时,请提高该值:
VLLM_MAX_AUDIO_CLIP_FILESIZE_MB=800 vllm serve <model> --port <port> ...
3. v0.26.0 新增了第二项独立限制:音频时长不得超过 10 分钟。提高大小上限不会解除这项限制——两者是彼此独立的门槛,而且超过这项时长限制会导致请求被拒绝,而非截断:
Audio exceeds maximum allowed duration of 600s (metadata reports 5998.0s).
Set VLLM_MAX_AUDIO_DECODE_DURATION_S to increase this limit.
VLLM_MAX_AUDIO_DECODE_DURATION_S 默认为 600,位于 envs.py 中大小上限设置的下一行——它在 0.15.x 中不存在,因此,在旧版服务上可正常处理的讲座长度文件,在全新安装的服务上却会遭到拒绝。在 v0.26.0+ 上,同时设置这两个值:
VLLM_MAX_AUDIO_CLIP_FILESIZE_MB=800 VLLM_MAX_AUDIO_DECODE_DURATION_S=36000 \
vllm serve <model> --port <port> ...
4. 无法访问 huggingface.co 的主机,即使模型已缓存在本地,加载仍会失败。 vLLM 会在启动时对 config.json 发起 HEAD 请求,重试 5 次后退出——错误提示称“在缓存文件中找不到它们”,但文件明明就在那儿:
HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 vllm serve <model> ...
相同症状也可能由另一种原因导致,值得先排查:容器化服务有自己的 HF_HOME,看不到主机用户的 ~/.cache/huggingface,因此,你用 ls 能看到的模型,对容器来说确实不存在。
5. vLLM 已经能切分长音频——而且比客户端切分更好。 SpeechToTextConfig 包含 overlap_chunk_second=1 和 min_energy_split_window_size=1600,也就是说,它会在约 100 ms 的窗口内最安静的位置切分,而不是按固定偏移量切分,因此切口会落在词与词之间。解除上述上限后,100 分钟的文件可以一次请求完成。这也是下面的步骤 5 仅针对不支持此功能的服务的原因。
没有权限重启服务? #2/#3 中的上限是在服务启动时设置的,因此,如果你无法修改服务端,剩下的办法就是在客户端切分——也就是步骤 5。对于这类端点,这才是合适的工具,而不是备用方案。
⚠️ 但 overlap_merge_transcribe.py 无法直接驱动 0.15.x vLLM 端点:它会用 -acodec copy 将分块写入 chunk_NN.mp3,因此,输入本身是 MP3 时,它会输出 MP3(参见 #1,服务会拒绝);输入为其他格式时,它则会直接失败——它从不检查 ffmpeg 的退出状态,所以坏分块会在之后以 JSON 解析错误的形式暴露出来,而不是提示“ffmpeg 失败”。这些分块也只会在同一个 TemporaryDirectory 中短暂存在,因此根本没有机会转换它们。对于这类端点,请手动切分为 OGG,再逐块提交:
ffmpeg -nostdin -v error -i INPUT -f segment -segment_time 900 \
-ar 16000 -ac 1 -c:a libvorbis chunk_%02d.ogg
请注意,这样会失去重叠合并拼接功能,因此句子可能会在接缝处断开——而这正是 #5 中服务端基于能量的切分器要避免的问题。
如果远程健康检查失败,请按顺序排查:
- 网络:
ping -c 1 HOST或tailscale status | grep HOST - 服务:
tailscale ssh USER@HOST "curl -s localhost:PORT/v1/models" - 代理:切换
--noproxy '*'后重试
4. “现在真的有进程在监听吗?”——单靠 ss 会误导你。 它只显示当前用户的进程,因此,以其他用户身份运行或在容器内运行的服务对它不可见,却仍能正常提供流量。查询时也一并检查 Docker:
tailscale ssh USER@HOST "ss -ltn | grep -E ':(8000|8001|8002)'; \
docker ps --format '{{.Names}}\t{{.Ports}}\t{{.Status}}'"
5. “GPU 可用于此任务吗?”——启动另一个模型之前,请先检查显存和声明的服务归属。仅凭 compute-apps 列表为空,不能证明 GPU 处于空闲状态,尤其是在 WSL 下:
tailscale ssh USER@HOST "nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv"
# under WSL nvidia-smi is often off PATH: /usr/lib/wsl/lib/nvidia-smi
请勿为了释放 GPU 或测试冷启动路径而重启或卸载其他会话的服务。检查当前归属并使用现有的兼容路由;如果执行身份不可用,请明确说明,不要悄悄回退到 CPU。
第 4 步:验证输出
转录后,检查完整性:
- 确认输出非空
- 字符数和密度(中文约 400 字/分钟,英文约 200 词/分钟)仅作为检查线索,不是最低输出要求或拒绝阈值;切勿为了达到这些指标而填充文本
- 对于本地 MLX,确认检查点清单显示
status: complete;当任何分块达到其令牌上限时,脚本会拒绝写入最终的.txt - 对于旧版/远程输出,检查结尾——句子中途截断的尾部可能意味着内容被截断
- 向用户展示开头和结尾各约 200 个字符作为预览
- 说话人路径:检查对齐报告——
anchored_ratio应 ≥ 0.5(低于此值时脚本会发出警告);说话人数应与录音情况相符(两人访谈却显示 5 位说话人,或独白被拆分为 2 位及以上说话人,都意味着说话人分离过度拆分——关于何时不应信任标签,请参阅references/speaker_diarization.md)
将空结果、疑似循环结果或时间上不合乎情理的 ASR 结果保留为不可靠草稿,
并视为语音内容未知;保留原始输出,不要将其写入规范转录稿。将其纳入前,
请对照原始音频或现有的已验证来源转录稿,核实受影响的时间段。仅凭重复本身,
包括重复的词语或歌词,并不能证明 ASR 失败或不存在语音。只有在独立核实
所述时间段的原始音频后,才能标注 no speech;绝不能依据 ASR 结果或密度启发式
进行标注。
当本次运行是现有高风险转录稿的独立证据路径时,完整性意味着整个基准录音都已
到达完整检查点/最终回执。选定片段可以核实选定的发言,但不能作为整份转录稿
或“更高质量最终版”的依据。将完整输出交给 transcript-fixer;让其人工审核环节
解决尚未确定的专有名称分歧。
对于被截断或不可靠的输出,执行任务授权已涵盖的恢复操作:检查来源或复用其 已验证的转录稿,然后仅在新输入、预处理、参数或证据能够解决已观察到的问题时 重试。如果来源核实确认存在密集的真实语音,且每个分块的令牌上限导致了截断, 则在测量后将每个分块的预算提高到最高 16384。若没有此类变更,不要用相同的 来源/模型组合重复运行。保留部分输出;仅在确实需要新的许可、费用或用户自主选择时 才询问用户。
第 5 步:回退方案 — 重叠合并(仅限远程 API)
在采用此方案前,先检查你的服务端是否会在内部进行分块。 vLLM 会进行分块(路径 B 限制 #5),而且其基于能量的切分效果优于此脚本的固定偏移切分——因此,对于你控制的 vLLM 端点,应通过提高上限来解决过长文件的问题,而不是在客户端进行切分。
仅当端点无法接收整个文件时,才在客户端进行分块:它直接拒绝较长的音频 (固定上下文窗口、严格的单次请求时长限制),每次在相同输入长度下都会 OOM,或者 它会在内部进行分块,但你无权提高其上限。
超时是另一种故障,通常有更省事的解决办法——请求已被接受,而且仍在运行。
先在配置中提高 max_timeout(一个 100 分钟的文件,以约 60 倍实时速度处理仍需
几分钟,而默认值可能不够);只有在超时时限已足够宽裕的情况下仍然超时,才考虑分块,
这意味着服务端确实慢到无法一次完成处理。
适用上述任一情况时,请回退到分块转录:
python3 ${CLAUDE_SKILL_DIR}/scripts/overlap_merge_transcribe.py \
--config "${CLAUDE_PLUGIN_DATA}/config.json" \
INPUT_AUDIO OUTPUT.txt
将音频切分为 18 分钟的分块,每块重叠 2 分钟,并使用去除标点后的模糊匹配进行合并。算法详情请参阅 references/overlap_merge_strategy.md。
对于本地 MLX 模式,无需使用重叠合并——随附脚本使用固定版本的 Qwen 低能量切分器,
以原子方式提交每个分块,并默认将 max_tokens=8192 设为每个分块的上限。
步骤 6:建议修正转录文本
ASR 输出总会包含识别错误,例如同音词、乱码般的技术术语和不完整的句子。成功转录后,主动建议对输出运行 transcript-fixer 技能:
Transcription complete: [N] chars saved to [output_path].
ASR output typically contains recognition errors (homophones, garbled terms, broken sentences).
Would you like me to run /daymade-audio:transcript-fixer to clean up the text?
Options:
A) Yes — run daymade-audio:transcript-fixer on the output now (Recommended)
B) No — the raw transcription is good enough for my needs
C) Later — I'll run it myself when ready
如果用户选择 A,请使用输出文件路径调用 transcript-fixer 技能。这两项技能构成自然的工作流:转录 → 修正 → 审阅。
如果用户在同一轮对话中已要求修正、合并多轨内容或生成更高质量的转录文本,则该请求已代表用户选择 A。直接继续使用 transcript-fixer,无需再次询问用户是否批准同一项工作。
重新配置
rm "${CLAUDE_PLUGIN_DATA}/config.json"
然后重新运行步骤 0。
批量转录(多个短文件)
在使用外部 VAD 选择批次、解读空窗口或声称已覆盖积压内容之前,请先阅读 VAD 和批次覆盖范围。 该文档区分了源样本、候选窗口、尝试次数和已验证内容;请复用所选生产器现有的检查点,并遵循下方对应的分支。
将多个文件传递给一次 transcribe_local_mlx.py 调用十分高效(模型只需加载一次)——但前提是每个文件都包含实际语音。 如果批次中可能包含仅有音乐/背景音乐的片段(例如短宣传视频、以字幕代替旁白的蒙太奇片段),请勿在一个进程中批量处理:
- 对于只有音乐/节奏的音频,模型可能陷入重复循环幻觉(例如无休止地输出“One, two, three, one, two, three...”)。每个分块的 8192 个 token 上限可限制资源增长,而 12 字符的 n-gram 质量门槛会拒绝重复循环,即使循环在达到上限前停止;但一个有问题的文件仍可能耗尽整个分块的超时时间,拖累整个批次。
- 批处理任务应采用每个文件一个进程,并为每个文件设置超时(例如在每次调用外包裹
timeout 240/perl -e 'alarm 240; exec @ARGV',并保留失败记录,以便按条件恢复)。这样,卡住的文件最多耗时 4 分钟,不会拖垮整个批次。 - 先检查卡住文件的源内容和失败原因。只有证据支持较小的输出预算时,才使用
--max-tokens 3000;将任何被截断的结果保留为草稿,并遵循步骤 4 中按条件重试的规则。 - 应揭示不可靠的转录结果,而不是臆造内容判断:可将较低的唯一词比例、空文本或可疑重复作为检查线索。应用步骤 4 中的草稿/采纳边界;这些信号无法证明片段没有旁白,也不能作为丢弃正常重复语音或歌词的理由。只有在实际源内容支持这项独立任务时,才使用屏幕字幕 OCR。
词级时间戳(字幕、音视频对齐)
mlx-whisper 的词级时间戳是短/中等长度 Qwen 说话人流水线中的时间戳环节(第 2 环节——scripts/word_timestamps_whisper.py 会自动运行它)。本节介绍独立使用词级时间戳的场景:生成字幕、将旁白与镜头边界对齐、为单个片段生成字幕。
Qwen3-ASR 是一种 LLM 解码器 ASR:无论本地还是远程路径,它都只输出纯文本,不含对齐信息。当任务需要确定每个词的说出时间时,请使用 mlx-whisper 并设置 word_timestamps=True。对于这类任务,Whisper 的交叉注意力词级对齐是本地事实标准解决方案。
要点(完整操作方法见 references/whisper_word_timestamps.md):
- 模型:
mlx-community/whisper-large-v3-turbo(约 1.6GB)。纯转录任务中,它的中文 WER 高于 Qwen3-ASR;但对于对齐任务,完全无法使用 Qwen3-ASR;可通过initial_prompt提供领域术语。 - 分段粒度陷阱:对于短视频(15–40 秒),whisper 经常会将整个片段作为一个分段返回——务必以词列表为准,并按时间中点将词分配到对应时间窗口。
- 与 ffmpeg 场景检测(
select='gt(scene,0.3)')配合处理视觉内容;对于非 ASCII 路径,请避免使用 PySceneDetect。
说话人分离与识别(谁说了什么)
说话人标签是步骤 3 的默认输出。两种本地方案都会将 ASR 与 pyannote 解耦,并按时间融合;两者都不会单独转录说话人轮次切分片段。本节介绍相关内容。
- 短/中时长流水线 —
scripts/speaker_transcribe.py在一条命令中运行全部三个环节 + 对齐,并写入带说话人标签的转录文本 + CSV。 架构、对齐算法、可信度信号(anchored_ratio)和故障模式:references/decoupled_speaker_alignment.md。生产环境中的注意事项(过度分段、麦克风领域效应、何时不应信任标签):references/speaker_diarization.md。 - 仅进行分离 —
scripts/diarize_speakers.py仅输出speaker × time片段(不进行转录)。 - 旧版级联方案 —
scripts/speaker_transcribe_cascade.py是旧的先切分再转录方案(先分离 → 按轮次切分音频 → 对每个片段进行 ASR)。它会在每次切分时打断 ASR 上下文,降低文本质量;仅在极度嘈杂或严重重叠的音频中保留使用,此时逐片段隔离占主导的近场说话人,效果优于会话级 ASR。其他情况均使用默认的解耦方案。 - 声纹识别 — 分离标签是匿名的(
SPEAKER_00…),且仅适用于单个文件。若要将其映射到真实姓名、跨文件统一同一说话人,或合并分离时过度细分的片段,请通过scripts/voiceprint_id.py使用 CAM++ 声纹。使用方法以及关键的声学领域注意事项 — 用一种麦克风录制的声纹,在另一种麦克风下对同一个人的匹配效果会差得多:references/voiceprint_speaker_id.md。
一次性 pyannote 设置(受限模型):在 hf.co/pyannote/speaker-diarization-3.1 接受条款,然后运行一次 huggingface-cli login(或设置 HF_TOKEN)。之后每次运行都会自动检测。
转录审校与复核(HTML)
如需在同一处进行音频聆听、说话人标注和转录修正,请使用捆绑的 Listening Desk / 声间。它将专注的复核队列与完整转录导航、同步源音频、键盘决策、持久化本地保存、撤销以及 JSON/CSV/Markdown 导出功能结合起来。未经决策,浏览器不会将模型候选项升级为人工确认的身份。
python3 ${CLAUDE_SKILL_DIR}/scripts/review-workbench/prepare_project.py \
--csv /absolute/output/meeting.csv --audio /absolute/audio/meeting.m4a \
--output /absolute/review/project.json --title "Meeting review"
python3 ${CLAUDE_SKILL_DIR}/scripts/review-workbench/server.py \
--project /absolute/review/project.json --state-dir /absolute/review/state
打开服务器打印的本地 URL。请将项目数据和决策保存在 Skill 包之外。关于多台录音设备、选定的语音样本、源时钟、状态恢复,以及与词典/队列操作的边界,请阅读Listening Desk 指南。
离线审查导出
保留现有的独立页面,以便离线或批量浏览。其浏览器本地别名和问题标记与 Listening Desk 保存的单条决策相互独立;不要将别名视为经过独立验证的身份。
说话人分离后,每个文件会生成一个 CSV(file,start,end,duration,speaker,text)。随附的审查 HTML 生成器会将这些 CSV 转换为一个以阅读为先的审查页面,其中包含音频播放、逐轮标记/备注、说话人别名和导出功能。
从说话人转录输出目录生成:
uv run ${CLAUDE_SKILL_DIR}/scripts/generate_audit_html.py \
OUTPUT_DIR \
--output OUTPUT_DIR/audit/index.html \
--audio-dir /path/to/original/audio
默认情况下,假设 PROJECT_DIR 下的目录结构是扁平的:PROJECT_DIR/*.csv 转录文件、PROJECT_DIR/*.diarization.json 文件,以及与输出文件放在一起的原始音频文件。speaker_transcribe.py 本身会将 CSV、TXT 和说话人分离文件直接写入 OUTPUT_DIR。如果你的项目使用不同的结构,可以覆盖其中任意路径:
uv run ${CLAUDE_SKILL_DIR}/scripts/generate_audit_html.py \
/path/to/project \
--output /path/to/project/audit/index.html \
--csv-dir /path/to/project/csv \
--txt-dir /path/to/project/txt \
--diarization-dir /path/to/project/diarization \
--audio-dir /path/to/project/audio \
--original-dir /path/to/project/original \
--manifest /path/to/project/manifest.json \
--title "Project Audit" \
--subtitle "Speaker-labeled transcript review" \
--storage-key "project-audit" \
--known-speaker "Speaker A" \
--known-speaker "Speaker B"
主要 CLI 选项:
| 选项 | 含义 |
|---|---|
project_dir | 项目基础目录(必需) |
--output | 写入 index.html 的位置 |
--csv-dir | 包含 *.csv 转录文件的目录 |
--txt-dir | 包含 *.txt 纯文本转录文件的目录(可选) |
--diarization-dir | 包含 *.diarization.json 文件的目录 |
--audio-dir | 包含用于播放的音频文件的目录 |
--original-dir | 包含原始源媒体文件的目录(可选) |
--manifest | 将文件 ID 映射到元数据的 JSON 清单(可选) |
--title / --subtitle | 页面标题和副标题 |
--storage-key | 用于持久化状态的 localStorage 命名空间 |
--known-speaker | 可重复使用;"Name" 会自动分配颜色,"Name=#hex" 会显式设置颜色 |
--material-final / --material-rough | 用于筛选的可重复材料分类标签 |
输出是一个不依赖外部资源的独立 HTML 文件。在浏览器中打开即可审查、标记和注释各轮内容;导出按钮会生成一份报告,列出所有带标记的行及其原因和备注。
故障排除
本地 MLX 加载模型失败
如果模型加载失败,并出现类似以下错误:
AttributeError: 'str' object has no attribute '__module__'
请检查 Agent 是否正在使用当前随附的脚本。其 PEP 723 声明和运行时依赖检查负责管理版本固定。运行其 --smoke-test 并查看验证结果;验证成功之前,不要开始耗时较长的转录。
可自托管的远程端点拒绝音频
以下每种情况都可能让人误判真正原因,因此值得根据症状加以辨认。完整详情和解决方法请参阅路径 B 的“可自托管 vLLM:容易以令人困惑的方式触发的限制”部分。
| 症状 | 实际原因 |
|---|---|
Maximum file size exceeded (parameter=audio_filesize_mb, ...) | 25 MB 上限,按字节而非分钟计算——转换为 WAV 通常会导致超限;请发送 OGG(体积约小 8 倍) |
HTTP 200,但响应正文是 {"error": ... "Format not recognised."} | 向 0.15.x 服务发送了 MP3——而仅检查状态码的检查会将其判定为成功 |
Audio exceeds maximum allowed duration of 600s | 在 v0.26.0 中新增的第二个独立上限;提高大小上限无法解除此限制 → VLLM_MAX_AUDIO_DECODE_DURATION_S |
| 服务无法启动:提示“couldn't find them in the cached files”,但模型确实已缓存 | 启动时尝试访问 huggingface.co → HF_HUB_OFFLINE=1;如果使用容器,容器中的 HF_HOME 可能无法访问主机的缓存 |
| 长文件处理失败,你正准备在客户端分块 | vLLM 已会在低能量点分割音频——请提高上限,而不是分块;除非你无法重启服务(第 5 步会解释何时分块才是正确做法) |
Whisper 时间戳步骤因 httpx.ProxyError: 503 Service Unavailable 失败
word_timestamps_whisper.py 在从本地缓存加载模型前,会先向 huggingface.co 查询模型。在代理隧道不稳定的情况下,该请求会失败,导致这一步骤退出;即使所有模型文件都已在磁盘上,也会使整个文件处理失败(某个批次中的 44 个文件里有 6 个如此)。运行时设置 HF_HUB_OFFLINE=1:离线模式会直接从缓存加载,且不会建立连接。与上方 vLLM 服务行中的设置相同,原因也相同。
${CLAUDE_SKILL_DIR} 不会被替换
此技能中的脚本路径使用 ${CLAUDE_SKILL_DIR},即技能自身的目录;Claude Code 会在技能加载时替换它。如果命令中出现字面值 ${CLAUDE_SKILL_DIR}(某些运行时不会进行替换),请按以下顺序确定技能目录:
- 技能加载信封:
Base directory for this skill: <path>→<path>即技能目录。 - 没有信封 → 查找候选目录,并选择本次会话的可用技能列表所指向的目录(已安装的副本可能落后于源代码检出目录):
find ~/.claude ~/.claude-profiles ~/.codex ~/workspace -maxdepth 7 -type d -name asr-transcribe-to-text 2>/dev/null | head -5
在本文档中,将 ${CLAUDE_SKILL_DIR} 全部替换为解析出的绝对路径。
捆绑资源
脚本:
transcribe_remote.py— 现有的远程纯文本 ASR,包含来源/结果/生成方回执,并可选记录观测到的运行时 GPU 身份measure_asr.py— 测量指定的现有 ASR 命令,检查原始语音质量,并按需比较观测结果resolve_media_input.py— 将本地路径、直接媒体 URL 以及播客/网页解析为经过验证的本地媒体文件prepare_asr_input.py— 合并多段录音并为 ASR 进行规范化处理(16 kHz 单声道);可选保留音高加速,以适应按量计费的上传;自动验证时长计算和拼接边界transcribe_local_mlx.py— 本地 MLX 转录(macOS ARM64,PEP 723 依赖项),有界低能量分块、原子检查点/续传、绑定所有者存活状态transcribe_long_whispercpp.py— 长音频 ASR 默认方案:明确的源时间块 + 重叠区段归属 + whisper.cpp/Silero VAD + 原子检查点/续传build_whispercpp_gpu.py— 准备隔离且固定版本的 Metal 运行时,并检查原生 GPU 计算图;将其二进制文件/清单保存在用户缓存中fuse_whispercpp_diarization.py— 将规范化的 whisper.cpp 时间片段与 pyannote 语音/说话人信息后期融合;移除缺乏依据的静音幻觉,并生成 TXT/CSV/回执speaker_transcribe.py— 短/中音频解耦流水线(会话级 Qwen3-ASR + whisper 定时 + pyannote);--no-diarization纯文本快速路径;使用--text-file输入远程/预制 ASR 文本align_speakers.py— 解耦对齐核心(标准库):将完整转录稿映射到 whisper 词级格网 + pyannote 片段;在停顿和说话人变更处切分轮次,但绝不切开拉丁词;可独立用于调试word_timestamps_whisper.py— mlx-whisper 词级时间戳 → JSON 定时格网(Apple Silicon)speaker_transcribe_cascade.py— 旧版先切分再转录方案(仅适用于极度嘈杂/重叠的音频)diarize_speakers.py— 仅进行说话人分离(pyannote 3.1 @ MPS)→ 每段 JSONvoiceprint_id.py— CAM++ 声纹注册/匹配:将匿名 SPEAKER_xx 映射到真实姓名overlap_merge_transcribe.py— 使用重叠合并进行分块转录(远程 API 回退方案)generate_audit_html.py— 根据 speaker-transcribe CSV 输出构建独立的 HTML 审核/复核页面
参考文档:
execution_location_and_comparison.md— 有条件地选择本地/远程方案,复用现有转录稿/缓存,安全识别运行时身份并比较相同来源whispercpp_gpu_runtime.md— 路径 L GPU-VAD 准备、原生计算证据及检查点恢复边界decoupled_speaker_alignment.md— 默认架构:解耦的原因、对齐算法、可信信号、故障模式speaker_diarization.md— 生产环境中的陷阱:过度分段、麦克风域效应、何时不应相信标签;旧版级联方案说明voiceprint_speaker_id.md— CAM++ 说话人识别:注册/匹配、阈值 + 间隔门槛、声学域注意事项、引导流程local_mlx_guide.md— 性能基准、分块令牌/资源约定、检查点恢复、模型兼容性whisper_word_timestamps.md— mlx-whisper 词级定时:短/中音频 Qwen 流水线中的定时环节;独立字幕/音视频对齐方案overlap_merge_strategy.md— 朴素分块为何会失败、模糊合并算法