Repository navigation
feat(web): OpenAI-compatible STT endpoint for self-hosted transcription (whisper.cpp, faster-whisper) - #3523
prosomoalexd wants to merge 4 commits into
Conversation
…ndpoint Adds STT_BASE_URL, STT_MODEL and STT_API_KEY. When STT_BASE_URL is set, recorded-video transcription and editable-transcript backfills post the extracted audio to <STT_BASE_URL>/audio/transcriptions (verbose_json with word timestamps) instead of AssemblyAI, so self-hosters can use whisper.cpp whisper-server, faster-whisper-server or speaches. The words go through the existing createEditTranscript pipeline, so one pass still produces both the caption VTT and the editable word transcript. isTranscriptionConfigured() now decides whether transcription is available in lib/transcribe, the transcription workflows, get-status, the share page and finalize-desktop-recording, so an STT endpoint alone is enough. Live transcription stays AssemblyAI-only. 5xx/408/429 responses, connection failures and a response body cut off after the headers are retried; other errors, including a body that isn't JSON, are fatal. Co-authored-by: rafael kovashikawa <kovashikawaa@gmail.com>
Co-authored-by: rafael kovashikawa <kovashikawaa@gmail.com>
… speaches Co-authored-by: rafael kovashikawa <kovashikawaa@gmail.com>
|
@prosomoalexd is attempting to deploy a commit to the Cap Software Inc Team on Vercel. A member of the Team first needs to authorize it. |
| const text = typeof body.text === "string" ? body.text.trim() : ""; | ||
| if (words.length === 0 && text.length > 0) { | ||
| throw new FatalError( | ||
| "STT response has text but no word timestamps; the endpoint must support response_format=verbose_json with timestamp_granularities[]=word", | ||
| ); | ||
| } |
There was a problem hiding this comment.
Wrong responses look like silence
parseOpenAICompatibleTranscription accepts objects without any transcript fields. If a misrouted endpoint returns {"status":"ok"}, the parser produces empty words. The workflow then marks the video NO_AUDIO and reports success, hiding the configuration error.
Require a recognized transcript shape before accepting an empty result, while keeping valid silent responses supported.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/lib/stt.ts
Line: 236-241
Comment:
**Wrong responses look like silence**
`parseOpenAICompatibleTranscription` accepts objects without any transcript fields. If a misrouted endpoint returns `{"status":"ok"}`, the parser produces empty words. The workflow then marks the video `NO_AUDIO` and reports success, hiding the configuration error.
Require a recognized transcript shape before accepting an empty result, while keeping valid silent responses supported.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Fixed in e580f3b: a JSON object with no text, words or segments is now a fatal configuration error instead of silence; {"text":"","segments":[]} still maps to NO_AUDIO.
…f treating them as silence
Picks up #2397 by @kovashikawa, which was closed in favour of #2771 and then approved pending an update to main. This is that work rebased onto main, plus the fixes below. The author's later commit 54dcc0b (whisper.cpp
--vadtimeline re-anchoring) came after the reviewed head 8763a86, so it's new for review here.Self-hosters can transcribe recorded videos with their own speech-to-text server instead of AssemblyAI:
STT_BASE_URLset, recorded-video transcription and editable-transcript backfills use<STT_BASE_URL>/audio/transcriptions(verbose_json, word timestamps). Otherwise AssemblyAI is used as today.isTranscriptionConfigured()replaces theASSEMBLY_API_KEYchecks, so an STT endpoint alone is enough. Live transcription of instant recordings stays AssemblyAI-only.createAssemblyAIClient()(keepsASSEMBLY_BASE_URLfrom feat(self-hosting): add ASSEMBLY_BASE_URL for EU transcription #2998);STT_*passed through both compose files (empty values count as unset); a response body cut off after the headers is now retried instead of reported as "did not return JSON"; with AssemblyAI only, the backfill's "no spoken audio" case is no longer retried (it already ended in the same backfill error).--vad, words right after a long pause can still land early (about 7.5 s early after 8 s of silence in our test).Coordination: #3520 adds
ASSEMBLY_API_KEYguards in four places. Whichever merges second switches them toisTranscriptionConfigured(); STT-only tests here for those four entry points fail on a merge of the two as they stand.Closes #1996. Refs #2161.
Tests
stt.test.tsand whisper.cpp fixture, plus STT-only cases for every entry point, retry classification (including a real loopback reset and timeout mid-body), and compose's empty values. The STT tests fail on main; the 7 regression controls pass on both.apps/websuite on Node 24: main 4771 passed / 1 failed, this branch 4819 passed / 1 failed; the samehomepage-seotest fails on both.bun run typecheckandbiome ci --linter-enabled=falseclean.whisper-server(base.en,--convert --vad): mp3 and concatenated segment audio transcribe correctly; silence returns no words.The PR appears safe to merge, with a non-blocking improvement needed to reject unexpected JSON responses.
Findings
Fix with agent prompt
Summary
Adds an OpenAI-compatible speech-to-text provider for recorded videos and editable-transcript backfills.
Reviews (1) · Last reviewed commit: "docs(self-hosting): document local trans..." · Reviewed by Greptile