Method · model · limits · retention
How we do it
This page exists so that the claims on the landing page have somewhere to be checked. It covers the method, the model, the hardware, the enforced limits, what is deleted afterwards, and the claims we do not make.
The pipeline, in the order it runs
An episode link is not an audio file, so the first job is not transcription, it is resolution. An Apple Podcasts episode page carries its own metadata as JSON-LD in the document head; the show's RSS feed comes from the iTunes lookup API; the audio URL is the <enclosure> of the feed item whose title, duration and publish date match the episode the page described. A Spotify link goes through oEmbed and the show's RSS feed. A YouTube link is read with yt-dlp for its title, channel and length, and its audio track is fetched the same way when the job runs; a live stream is refused, because there is no finished recording to transcribe. A link that is already a feed or an audio file is used as given.
Then fetch: the enclosure is streamed to disk with a byte cap enforced inside the stream callback, so an episode that lies about its length stops the request rather than filling a volume. Then transcribe: ffmpeg decodes whatever the publisher served into 16 kHz mono PCM, and Parakeet, cut at the pauses Silero finds, writes the words with a time on each; a pyannote segmentation model with TitaNet speaker embeddings draws who spoke when, and every word takes the speaker whose region covers it.
Two decisions in that sequence change the output:
- 16 kHz mono before the model, always. Feeds arrive as 44.1 kHz stereo, 48 kHz, AAC, Opus, and one as a 96 kHz lossless master. The model wants 16 kHz mono. Normalising first means one guarantee holds regardless of what the publisher's encoder did.
- VAD first, then chunk. Silero finds the speech; silence gets cut before the model sees it, which is where both the speed and the resistance to hallucination come from. Given a few seconds of room tone, a recogniser will invent words.
The model, and which one
NVIDIA's Parakeet TDT 0.6B v3, a 600-million-parameter transducer, through the sherpa-onnx runtime on a GPU we own, with a pyannote segmentation model and TitaNet speaker embeddings for who spoke when. Not a hosted API, which would put another company's speech service and its retention policy in the path. Parakeet replaced Whisper large-v3 for two measured reasons: on the public English benchmarks it makes fewer word errors, and it decodes an hour of audio in well under a minute on the card, which is what lets the audio be deleted minutes after it arrived rather than hours. It covers 25 European languages.
The language is the language the model was configured for. If the audio arrives in another one, the job returns LANG_UNSUPPORTED. A fluent transcript in the wrong language is worse than a refusal.
Verbatim means verbatim
The text is what the model heard, at the offsets the model reported. The model adds punctuation and capitalisation as part of decoding, and that is where the editing stops. Specifically, we do not run a spelling corrector over proper nouns, do not insert a period where the model left a comma, and do not merge two segments because the sentence reads better that way.
A corrected transcript is a claim about what was said. A verbatim one is a record of what the recogniser heard, with the timing attached, so you can jump to the audio and check. When the model writes "HatGPT" instead of "GPT", that is visible in the transcript, which is what tells you to fix it. Every transcript ships with a marker saying it is machine output.
Limits, as numbers
- Episode length
- 4 hours of audio. Longer recordings are split into parts, transcribed one at a time, and the transcripts stitch back in order.
- Download size
- 400 MB, enforced mid-stream. The cap is checked on every chunk of the response body, so the transfer stops at the cap instead of after it.
- Workspace
- A byte cap on the scratch directory, checked before each download, so a queue of jobs cannot fill the volume on the way to being deleted.
- Redirects
- A bounded number, and a visited set. A feed that redirects to itself is
BAD_LINK, not a hang. - Feed cache
- 15 minutes per feed, so a batch of runs does not hit a publisher's server once per episode.
- Cost
- One credit per 30 minutes of audio, rounded up. No credits are taken for any failure.
What we delete, and when
The fetched audio is removed by the same step that produces the transcript, before the job reports itself done, not by a cleanup job that runs later. The raw download is written to a scratch directory under a name unique to the job, so two runs for the same episode cannot read each other's file, and the delete is a plain File.rm in the transcribe function.
What is kept after the run: the transcript, the episode metadata, the job record, and the fact that the job ran. No audio, no model training, no third-party speech API.
What we do not claim
- No accuracy percentage. Word error rate is a property of a model, a language, an audio quality and a domain, measured against a reference transcript. A percentage quoted for arbitrary podcasts comes from the model card, not from your episode.
- No latency promise. Transcription time depends on episode length and queue depth. The job reports progress as a percentage and shows the first lines while the model is still writing.
- No perfect speaker labels. Speaker turns come from a separate diarization model, and its boundaries can land a few hundred milliseconds off, so a turn change sometimes splits mid-sentence. Voices are grouped by how far apart their embeddings are, not counted; the
speakersnumber a caller gives is a floor, and ads, jingles and clips are voices of their own. Two people talking over each other in a remote interview recorded on laptop microphones often come out as one turn, and the transcript shows the merge. - No live transcription. This is a batch service on finished recordings.
Reproduce it yourself
The output on the landing page is what three commands print for a 45-second clip: cut it, decode it, draw the speakers. Any clip you have rights to works the same way:
ffmpeg -ss 00:11:02 -t 45 -i episode.mp3 -ac 1 -ar 16000 clip16.wav
sherpa-onnx-offline --model-type=nemo_transducer --encoder=parakeet/encoder.onnx \
--decoder=parakeet/decoder.onnx --joiner=parakeet/joiner.onnx \
--tokens=parakeet/tokens.txt clip16.wav
sherpa-onnx-offline-speaker-diarization --clustering.num-clusters=2 \
--segmentation.pyannote-model=pyannote/model.onnx \
--embedding.model=titanet_small.onnx clip16.wav
The decoder prints a time for every token, so the timings can be checked; the diarizer prints who spoke when; the two are joined by overlap, and that join is the only step of ours between the models and the page.
Changes to this page
- 2026-09-03
- Accounts. The API and the form both need one now: a key (
Authorization: Bearer) or the site's signed-in session. Each account starts with 6 credits, one per started half hour, charged only when a transcript files; a job an account cannot pay for is refused before it runs. A job is visible only to the account that ran it. - 2026-09-03
- YouTube video links accepted: the audio track is fetched with yt-dlp on our machine and goes through the same pipeline. Transcript downloads moved to opaque links under
/t/that say nothing about the job. - 2026-09-03
- Engine changed from whisper.cpp on a CPU to NVIDIA Parakeet TDT 0.6B v3 on our own GPU, with speaker turns from a pyannote + TitaNet pipeline. The API at
/api/v1/transcriptionswent live the same day. Later that day the speaker grouping was recalibrated after a two-host episode filed as 82 voices, and thespeakershint became a floor rather than a forced count. - 2026-09-02
- Reset as sheet 2 of the catalogue. The method, the limits and the claims are unchanged; only the print is.
- 2026-09-01
- First published with the landing page. Limits section reflects the enforced values in code, not the intended ones.