fix: reconcile speakers automatically by default
The plain transcribe command still used chunk overlap speaker matching, so users had to discover a model flag or supply a known speaker count to get the improved voice comparison behavior. Enable OpenRouter voice comparison with automatic speaker counting for normal text and SRT invocations. Keep --speakers and --speaker-model as optional overrides, and let --no-diarization disable both provider labels and voice comparison. Make the basic command the documentation entry point and clarify the defaults in CLI help and repository guidance. Cover bare invocations across one and multiple uploads, default SRT, optional count constraints, opt-out ordering, and atomic output when the default comparison fails. Test Plan: - python3 -B -m unittest -v: all 85 tests passed. - bash -n transcribe: passed. - git diff --cached --check: passed. - Tests use real audio encoding with fake provider responses. Live voice accuracy remains unverified; no live API request was made for this change.
This commit is contained in:
1 parent
138c796399
commit
d7827d39f5
5 files changed
+107
-48
No files matched your search
@@ -1,23 +1,18 @@
|
||||
# Transcribe
|
||||
|
||||
```sh
|
||||
./transcribe too_long.webm > transcript.txt
|
||||
```
|
||||
|
||||
Speaker detection and matching across the recording happen automatically.
|
||||
You do not need to know the number of speakers or choose a model.
|
||||
Use `--srt` for subtitles or `--no-diarization` for text without speaker labels.
|
||||
|
||||
Requires Bash 5.1 or newer, FFmpeg, curl, jq, Python 3, and standard coreutils. Keep
|
||||
`chunks.py`, `subtitles.py`, `transcript.py`, and `diarization.py` beside the script.
|
||||
The API key is read using the 1Password CLI (`op`) from
|
||||
`op://API Keys/openrouter-mai-transcribe-2/credential`.
|
||||
|
||||
```sh
|
||||
./transcribe recording.m4a
|
||||
./transcribe video.mp4 > transcript.txt
|
||||
./transcribe --srt video.mp4 > subtitles.srt
|
||||
./transcribe --no-diarization recording.m4a > transcript.txt
|
||||
./transcribe --srt --no-diarization video.mp4 > subtitles.srt
|
||||
./transcribe --chunk-minutes 10 too_long.webm > transcript.txt
|
||||
./transcribe --jobs 4 too_long.webm > transcript.txt
|
||||
./transcribe --jobs 1 --srt too_long.webm > subtitles.srt
|
||||
./transcribe --speakers 2 too_long.webm > transcript.txt
|
||||
./transcribe --speaker-model google/gemini-3.8-flash recording.m4a > transcript.txt
|
||||
```
|
||||
|
||||
Accepts m4a, mp4, aac, flac, opus, mp3, ogg, wav, and other formats your
|
||||
FFmpeg supports. Uses the first audio track, converts it to mono 16 kHz MP3
|
||||
at 64 kbps (requires FFmpeg's libmp3lame encoder),
|
||||
@@ -25,11 +20,14 @@ and sends it to OpenRouter's `microsoft/mai-transcribe-2` model. Temporary
|
||||
files are removed on exit. The transcript goes to stdout; progress and errors
|
||||
go to stderr. Output is emitted only after all requests and conversion succeed.
|
||||
|
||||
Speaker diarization is **enabled by default** through
|
||||
Speaker diarization and voice comparison are **enabled by default**.
|
||||
MAI first labels speech through
|
||||
`provider.options.azure.diarization.enabled`, following
|
||||
[OpenRouter's speaker diarization API](https://openrouter.ai/docs/guides/overview/multimodal/stt#verbose-transcripts-timestamps-and-speakers).
|
||||
Use `--no-diarization` to explicitly disable it for every request, with either
|
||||
plain text or `--srt` output. Text output prints returned IDs above each paragraph:
|
||||
Gemini then compares voice excerpts through OpenRouter to match speakers across
|
||||
the recording, automatically estimating their number. `--no-diarization`
|
||||
disables both steps, with either plain text or `--srt` output. Text output prints
|
||||
speaker IDs above each paragraph:
|
||||
|
||||
```text
|
||||
[Speaker 0]
|
||||
@@ -83,22 +81,17 @@ are an error. Plain text for a chunked recording is assembled from the words.
|
||||
SRT timestamps are shifted back to the original recording's timeline before
|
||||
cue generation, with continuous cue numbering. Text uses `[Speaker 0]`,
|
||||
`[Speaker 1]`, etc., without exposing upload boundaries to the reader.
|
||||
For a chunked recording, provider IDs are mapped to transcript-wide IDs:
|
||||
at least two unambiguous words shared at matching times in the overlap must
|
||||
agree on the speaker correspondence. Conflicting or absent evidence gets a
|
||||
new ID and a warning on stderr. This is not voice recognition across the whole
|
||||
recording: the same person can receive another ID when absent from an overlap
|
||||
or when diarization disagrees. Numeric provider IDs alone are never used to
|
||||
identify a speaker across requests.
|
||||
Provider IDs are request-local. The automatic voice comparison maps them to
|
||||
transcript-wide IDs using audio evidence, including for people absent from a
|
||||
chunk overlap. Numeric provider IDs alone never establish identity across
|
||||
requests.
|
||||
|
||||
For interviews and other recordings with fragmented speaker IDs, use
|
||||
`--speakers N` (1 to 64) to add a voice comparison pass through OpenRouter.
|
||||
This selects `google/gemini-3.8-flash` and tells it the known number of people.
|
||||
Use `--speaker-model MODEL` to select a different audio chat model supporting
|
||||
multiple audio inputs and JSON schema output; without `--speakers`, the model
|
||||
estimates the number of voices. Both options require
|
||||
diarization and cannot be combined with `--no-diarization`. The normal command
|
||||
without either option retains the overlap-based behavior described above.
|
||||
Voice comparison uses `google/gemini-3.8-flash` by default and estimates the
|
||||
number of speakers. If you already know the count, `--speakers N` (1 to 64)
|
||||
supplies it as an optional constraint. `--speaker-model MODEL` overrides the
|
||||
default with another audio chat model supporting multiple audio inputs and JSON
|
||||
schema output. Neither flag is needed for the normal command. Both options
|
||||
require diarization and cannot be combined with `--no-diarization`.
|
||||
|
||||
The comparison keeps MAI's words and timestamps, initially treating IDs from
|
||||
different requests as separate. It takes up to three nonoverlapping excerpts
|
||||
@@ -150,7 +143,7 @@ cancels the remaining workers. Interruptions also stop workers and their child
|
||||
processes before temporary files are removed. Requests already accepted by the
|
||||
provider may still be processed and billed. There is no automatic retry or
|
||||
resume, and rerunning sends earlier chunks again. No transcript is emitted
|
||||
unless every worker, any requested voice comparison, and the final conversion
|
||||
unless every worker, the enabled voice comparison, and the final conversion
|
||||
succeed.
|
||||
|
||||
`--srt` requests word timestamps using OpenRouter's `verbose_json` response.
|
||||
|
||||
Reference in new issue
Block a user