news 2026/9/16 18:11:15

OpenWhispr 自定义 ASR Shim 实战:让 Self-Hosted 转录接入任意语音识别后端

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenWhispr 自定义 ASR Shim 实战:让 Self-Hosted 转录接入任意语音识别后端

OpenWhispr 自定义 ASR Shim 实战:让 Self-Hosted 转录接入任意语音识别后端

【免费下载链接】openwhisprVoice-to-text dictation app with local (Nvidia Parakeet/Whisper) and cloud models (BYOK). Privacy-first and available cross-platform.项目地址: https://gitcode.com/GitHub_Trending/op/openwhispr

本篇技术指南围绕 OpenWhispr 官方示例 custom-asr-shim 展开,讲解如何在不改动 OpenWhispr 源码的前提下,通过一个运行在本机的轻量代理(shim),把 OpenWhispr Self-Hosted 转录模式对接给任何不遵循 OpenAI/audio/transcriptions协议的 ASR 后端(如 StepFun StepAudio、阿里、百度、腾讯及各类企业级语音平台)。读完本文你将掌握 shim 的工作原理、OpenWhispr 侧的完整 HTTP 契约、StepAudio 实战接入方法,以及如何基于模板为任意厂商扩展自己的适配器。

为什么要一个 shim:协议不匹配问题

OpenWhispr 的 Self-Hosted 转录模式假设后端遵循 OpenAI 的线上协议格式:它向{Server URL}/audio/transcriptions发起multipart/form-dataPOST请求,并读取返回的 JSON{"text": "..."}。这一点可以从仓库源码得到印证:transcriptionRoute.ts 中buildBatchEndpoint的 fallback 路径就是buildApiUrl(base, "/audio/transcriptions"),而 selfHostedTranscription.js 则定义了transcriptionMode === "self-hosted"且配置了远端 URL 时的启用条件。

然而,大量 ASR API 并不说这种"语言":StepFun StepAudio 2.5、阿里、百度、腾讯以及各种企业级语音平台,各有各的请求结构、音频编码和响应封装。如果直接把 OpenWhispr 指向其中一个,由于线上传输的字节格式对不上,调用会以 400/501 之类的错误失败。

解决方案就是 shim:一个运行在你机器上的小型本地代理,充当"翻译官"。OpenWhispr 继续讲 OpenAI 协议,shim 负责把它翻译成厂商协议再转发,并把厂商响应翻译回 OpenAI 格式。你不需要改动 OpenWhispr 的任何代码。

工作流程与整体架构

官方文档给出了清晰的请求/响应流向:

OpenWhispr --multipart audio--> shim (localhost:8765) --vendor format--> ASR API OpenWhispr <--{"text": "..."}-- shim <--vendor reply-- ASR API

shim 监听/audio/transcriptions(以及/v1/audio/transcriptions)两个路径,用 ffmpeg 把录音转码成厂商需要的格式,调用厂商接口,最后返回 OpenWhispr 期望的 OpenAI 风格 JSON。

从 shim_template.py 的实现看,整个处理管线分为四步(见ShimHandler.do_POST):

  1. 校验与解析:检查Content-Length,超过MAX_BODY_BYTES(25 MB)直接返回 HTTP 413;用parse_multipart_form解析multipart/form-data得到文本字段与文件字段;
  2. 临时落盘:按上传文件名后缀(默认.webm)创建临时文件写入音频字节;
  3. 转码convert_audio调用 ffmpeg 统一转码;通用模板转成16 kHz 单声道 WAV,StepAudio 版则转成16 kHz 单声道 s16le 裸 PCM(见 stepaudio_shim.py);
  4. 调用厂商并返回transcribe()是唯一需要你改写的函数,其结果包装为{"text": ..., "object": "transcription"}返回 HTTP 200。

无论成功失败,finally块都会清理所有临时文件,不留磁盘残留。

OpenWhispr 发送什么、期待什么:完整契约

官方文档详细列出了 OpenWhispr 侧发出请求的字段,这是编写 shim 时必须严格遵守的契约。

请求POST {Server URL}/audio/transcriptionsmultipart/form-data

字段是否必需说明
file录音文件。默认是 WebM/Opus,文件名audio.webm;也可能是 ogg/mp4/mp3/wav。发给厂商前需要转码。
modelSelf-Hosted 面板中填写的 Model 字符串(若设置了)。要容忍空值或缺失。
language仅当选择非 auto 语言时才出现(如en这样的 ISO 代码)。
prompt自定义词典提示字符串。

几点重要的实现细节:

  • 不要从 OpenWhispr 侧要求鉴权Authorization: Bearer <key>不属于 Self-Hosted 面板的功能。厂商 API key 应放在 shim 内部的环境变量中(见下文STEP_API_KEY)。这与 shim_template.py 中的API_KEY = os.environ.get("SHIM_API_KEY", "")设计一致——模板本身也只提供可选的SHIM_API_KEY,而不是要求 OpenWhispr 传 key。
  • 只走批量 HTTP:Self-Hosted 转录是批量 HTTP 模式,OpenWhispr 不会在此路径发送stream=true,因此 shim 应返回单个 JSON 对象,而不是 SSE 流——即使上游厂商是流式的。模板中的_send_json实现正是固定返回单对象 JSON。
  • 响应约定:HTTP 200 + JSON{"text": "..."}。OpenWhispr 读取.text字段;文本为空或缺失时,用户端会显示 "No audio detected";非 200 状态则作为 API 错误呈现。

从源码侧印证:transcriptionRoute.tslanguage取自preferredLanguage"auto"时不携带),而模型字段在 Self-Hosted 模式下由resolveSelfHostedTranscriptionModel解析,未配置时即为 null——这正是字段表里"可选"的原因。

HTTP 与 HTTPS 的豁免规则

OpenWhispr 对自定义端点强制要求 HTTPS,但对私有与回环主机网开一面。跑在http://localhost:8765的 shim 可以走纯 HTTP;而部署在公网主机上的 shim 必须使用 HTTPS(放在 TLS 反向代理后面,或在 shim 内终止 TLS)。

官方文档明确列出允许纯http://的主机清单:

  • localhost以及任何*.local主机名
  • 127.0.0.0/8(回环)以及 IPv6::1
  • 10.0.0.0/8172.16.0.0/12192.168.0.0/16(私网段)
  • 169.254.0.0/16(链路本地)以及 IPv6fe80::/10
  • 100.64.0.0/10(CGNAT)
  • IPv6fc00::/7(唯一本地地址)

这一规则在源码中有精确对应:urlUtils.ts 的isPrivateHost实现了完全相同的判定逻辑——包括对 IPv4 字面量做严格 dotted-quad 解析以防止127.example.com之类的字符串前缀绕过(见parseIPv4Literal,它拒绝前导零和超过 255 的八位组),以及isSecureHttpEndpointhttps:http:+ 私有主机的放行规则。值得注意的额外细节:源码还把 Tailscale MagicDNS(.ts.net后缀)视为私有主机,因为其解析到仅限 tailnet 内可达的 CGNAT 地址。

前置条件与快速启动

前置条件

  • Python 3.8 或更新版本(仅用标准库,无需 pip 安装任何依赖)
  • PATH 中有 ffmpeg

通用模板启动(填入transcribe()函数后):

python3 shim_template.py

StepAudio 2.5 启动

export STEP_API_KEY=sk-... python3 stepaudio_shim.py

Windows PowerShell 下

$env:STEP_API_KEY = "sk-..." python stepaudio_shim.py

注意:StepAudio shim 默认以中文转写(language: zh),并忽略 OpenWhispr 转发过来的语言;如需其他语言,直接修改 stepaudio_shim.py 里的language值。

然后在 OpenWhispr 中配置

  1. 打开Settings → Transcription
  2. 选择Self-Hosted
  3. Server URL设为http://localhost:8765
  4. 可选地设置Model(会作为model字段透传;StepAudio shim 会忽略它)

StepAudio shim 从环境变量读取STEP_API_KEY,而不是从 OpenWhispr 读取。这也是 stepaudio_shim.py 启动时校验的:若环境变量未设置,会直接打印错误并退出。

StepAudio 2.5 实现剖析

stepaudio_shim.py 是模板的一个完整工作示例,其关键差异点值得研究:

转码输出裸 PCM:StepAudio 需要的是裸 PCM 而非 WAV 容器,因此convert_audio使用-f s16le输出 16 kHz 单声道 16 位小端 PCM(-ar 16000 -ac 1)。

请求封装transcribe()把 PCM 读取后 base64 编码,构造一个 JSON payload 发送到https://api.stepfun.com/v1/audio/asr/sse

payload = { "audio": { "data": audio_b64, "input": { "transcription": { "model": "stepaudio-2.5-asr", "language": "zh", "enable_itn": True, }, "format": { "type": "pcm", "codec": "pcm_s16le", "rate": 16000, "bits": 16, "channel": 1, }, }, } }

请求头携带Authorization: Bearer <STEP_API_KEY>Content-Type: application/jsonAccept: text/event-stream,超时设为REQUEST_TIMEOUT = 120秒。

SSE 解析:StepAudio 通过 SSE 流式返回结果,parse_sse_transcript负责解析:累积transcript.text.delta事件的delta片段,优先采用transcript.text.done事件的完整text,当事件名缺失时回退到 data 载荷内的type字段,并跳过[DONE]标记与无法解析的 JSON 块。

错误处理HTTPError会把状态码与响应体详情一并抛出,URLError抛出底层原因,统一由ShimHandler包装为 500 错误返回给 OpenWhispr。

适配到其他厂商:只改一个函数

官方文档给出的扩展方法是:复制 shim_template.py,然后只替换transcribe()这一个函数为你的厂商调用。模板文件中transcribe的签名如下:

def transcribe(audio_path: str, model: str, language: str | None, prompt: str | None) -> str: """The ONLY function you edit. Call your vendor's ASR backend with the WAV at `audio_path` and return the transcript as a plain string.""" raise NotImplementedError(...)

其余一切——HTTP 服务器、multipart 解析器、ffmpeg 转码、体积上限守卫、临时文件清理、响应格式——都已经与 OpenWhispr 的期望匹配,你只需要碰那个跟后端对话的函数。model/language/prompt三个参数直接来自 OpenWhispr 的 UI,可以选择使用也可以忽略。

模板的设计也考虑了健壮性,值得复用:

  • 文件大小守卫:超过MAX_BODY_BYTES(25 MB)返回 413;
  • 缺失file字段返回 400,路径不对返回 404;
  • ffmpeg 不在 PATH 时返回 500 并给出明确错误信息;
  • except Exception兜底"surface, never swallow",确保任何异常都能反馈给 OpenWhispr 而不是静默丢失。

测试验证

test_shim.py 是标准库 unittest 编写的测试套件,无需网络、无需 ffmpeg——HTTP 测试通过 monkeypatch 掉convert_audiotranscribe,从而在隔离环境中验证请求处理、multipart 解析和响应形状。运行方式:

python3 test_shim.py

或:

cd examples/custom-asr-shim && python3 -m unittest

测试覆盖了以下关键场景(这也是理解 shim 契约边界的绝佳教材):

  • multipart 解析器:二进制内容内嵌 CR/LF 的往返保真(防过度剥离)、UTF-8 文本字段(含caffè, naïve, 日本語)、缺失 boundary 抛错、boundary 后带 RFC 合法尾随空格、无file字段的情况;解析器在两个模块中是逐字复制的,测试对两份拷贝都跑;
  • StepAudio SSE 解析器:优先采用done完整文本而非拼接delta、无done时拼接 delta、type字段回退、跳过[DONE]和畸形 JSON、空流返回空串;
  • HTTP 端点:合法请求在两个路径(/audio/transcriptions/v1/audio/transcriptions)都返回 200 与{"text": "hello world", "object": "transcription"},且transcribe()收到的字节与上传字节完全一致、model/language透传正确;错误路径返回 404、缺file返回 400、超限返回 413。

测试的_HandlerHarness会把 handler 起在临时端口上、静默访问日志、并在结束时恢复被 patch 的全局,展示了如何干净地对http.serverhandler 做单元测试。

小结

OpenWhispr 的 Self-Hosted 转录模式只认 OpenAI 的/audio/transcriptions协议,而现实世界中的 ASR 厂商协议五花八门。这个社区示例用不足三百行的纯标准库 Python 搭建了一座可靠的"协议桥":OpenWhispr 侧零改动、密钥不出本机、临时文件自动清理,且扩展点收敛到一个函数。无论你要接 StepFun StepAudio、阿里、百度、腾讯,还是企业内部语音平台,遵循本文的契约与模板路径即可快速落地;遇到边界行为拿不准时,test_shim.py 就是契约行为的最精确说明书。

说明:本示例基于社区贡献者 ErogosZhou 的原始 StepAudio shim 移植而来,对应 OpenWhispr 的 issue #972,详见 examples/custom-asr-shim/README.md 的 Credit 一节。

【免费下载链接】openwhisprVoice-to-text dictation app with local (Nvidia Parakeet/Whisper) and cloud models (BYOK). Privacy-first and available cross-platform.项目地址: https://gitcode.com/GitHub_Trending/op/openwhispr

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 18:10:18

从CTF信息泄露看备份文件风险:源码、bak、vim缓存与.DS_Store

CTF圈里有句老话&#xff1a;信息收集做得好&#xff0c;漏洞利用不用愁。我刷 CTFHUB 的时候&#xff0c;信息泄露这个模块最初是被我跳过的——总觉得"备份文件下载不就是下载个文件嘛&#xff0c;能有什么技术含量"。直到后来在一次线上赛里&#xff0c;一道最简单…

作者头像 李华
网站建设 2026/9/16 18:08:22

医疗大数据实战:癌症数据分析与可视化系统构建

1. 项目背景与核心价值癌症数据分析与可视化系统是一个典型的医疗大数据应用场景。根据世界卫生组织统计&#xff0c;全球每年新增癌症病例超过1900万例&#xff0c;这些病例背后产生的临床数据、基因组数据、影像数据等呈现爆发式增长。传统的数据处理方式已经无法满足科研和临…

作者头像 李华
网站建设 2026/9/16 18:08:22

Flutter视频解析播放器开发实战:从地址解析到下载缓存的全流程拆解

做视频解析类工具&#xff0c;最麻烦的从来不是“能不能跑通”&#xff0c;而是“跑通之后怎么让它一直好用”。LunaTV 这个项目我断断续续维护了大半年&#xff0c;从最初只想做一个临时自用的视频观看工具&#xff0c;慢慢折腾成了带完整解析、播放、下载和缓存体系的移动端应…

作者头像 李华
网站建设 2026/9/16 18:07:45

基于区块链的数字身份证明系统:DID与可验证凭证实战

简介&#xff1a;基于区块链的数字身份证明系统实现方案&#xff0c;包含完整可运行的源码与详细设计报告&#xff0c;面向高校计算机相关专业学生、教师及科研工作者&#xff0c;适用于毕业设计、课程设计、项目初期立项演示&#xff0c;也可作为区块链DApp开发学习案例&#…

作者头像 李华
网站建设 2026/9/16 18:07:13

Java核心知识点与JVM内存管理深度解析

1. Java核心知识点全景解析作为一门诞生近30年依然活跃的编程语言&#xff0c;Java凭借其"一次编写&#xff0c;到处运行"的特性在企业级开发领域占据着不可替代的地位。根据2023年最新开发者调查报告显示&#xff0c;Java在全球编程语言排行榜中稳居前三&#xff0c…

作者头像 李华