# Transcribe Desk > THE UNIT OF WORK IS A 30-SECOND WINDOW, NOT A SECOND. The > encoder takes exactly 30 s of 16 kHz mono audio as a log-mel spectrogram and > PADS anything shorter with zeros, so 4 s of audio costs 30 s of encode — 7.5x its own length — and one > second past 30 s buys 30 s more. Silence costs exactly what speech costs, and the > only thing that lowers the bill is removing audio, which moves every timestamp > the model then hands you. Five lanes over one job sheet, plus a free > browser-side engine doing exact integer and rational arithmetic on the > encoder's own geometry. URL: https://transcribe-desk.skillsafe.ai/ API: https://transcribe-desk.skillsafe.ai/api.html Token panel: https://transcribe-desk.skillsafe.ai/tokens.html (noindex) Platform: SkillSafe (https://skillsafe.ai/) Derived from two skills: `openai-whisper-api` in https://github.com/steipete/clawdis, which runs Whisper locally with no API key, and `transcribe` in https://github.com/openai/skills, which sends the file to the hosted API. The two paths differ in exactly the constraint this app is about: locally there is no upload ceiling and the cost is wall clock; hosted there is a 25 MiB ceiling and a price per minute. Not affiliated with or endorsed by the authors of either repository. ## The one thing to know **THE UNIT OF WORK IS A 30-SECOND WINDOW, NOT A SECOND.** The encoder takes exactly 30 s of 16 kHz mono audio as a log-mel spectrogram — 480,000 samples, 3,000 mel frames at a 10 ms hop — and it PADS anything shorter with zeros. So: ``` 4 s of audio 1 window 30 s encoded 7.5x its length 31 s of audio 2 windows 1:00 encoded 1.94x its length ``` One extra second of audio past 30 s buys 30 s more encode. **And silence costs exactly what speech costs**, because the mel spectrogram of a silent window is the same shape as any other — there is no discount to find, only audio to remove. **WHICH IS THE TRAP.** Removing the silence is the one thing that lowers the bill: an hour that is 30% silence goes from 120 windows to 84, a real saving of 36. But the model is now working on the TRIMMED clock, so every timestamp it returns is early by all the silence removed before it — up to 18:00 at the end of the file. Keep the removal map, or the subtitles are wrong by minutes rather than milliseconds. **AND THE ANSWER HAS A RESOLUTION YOU DID NOT CHOOSE.** The encoder returns 1,500 positions for those 30 s, so a position is 20 ms and **every timestamp this family of models can express is a multiple of 0.02 s**. Nothing finer is available at any setting. Which makes the frame rate you are cutting against a real decision: ``` 25 fps frame 40 ms grids meet every 0.04 s every frame is expressible 24 fps frame 41.6667 ms grids meet every 0.5 s 1 frame in 12 is 29.97 frame 33.3667 ms grids meet every 20.02 s 1 frame in 600 is ``` **At 25 fps the tick divides the frame and every frame boundary is a time the model can name. At 24 fps one boundary in 12 is, and at 30000/1001 one in 600** — once every 20.02 s. Between those instants every cue has to be rounded by somebody, and the rounding has to be decided once for the whole file rather than cue by cue. **FINALLY THE CEILING IS A DURATION.** The hosted path takes 25 MiB, and the model consumes 32,000 B/s, so the ceiling is exactly 13:39 of audio. Sending 48 kHz stereo 24-bit instead lowers it to 1:31 for an identical transcript, because everything above 16 kHz mono is discarded before the encoder sees it. ## What a duration costs | Audio | Windows | Encoded | Of that, zeros | You pay | | --- | --- | --- | --- | --- | | 4 s | 1 | 30 s | 26 s | **7.5x** | | 20 s | 1 | 30 s | 10 s | 1.5x | | 30 s | 1 | 30 s | none | 1x | | 31 s | 2 | 1:00 | 29 s | **1.94x** | | 45 s | 2 | 1:00 | 15 s | 1.33x | | 1:00 | 2 | 1:00 | none | 1x | | 1:01 | 3 | 1:30 | 29 s | 1.48x | | 5:00 | 10 | 5:00 | none | 1x | | 30:00 | 60 | 30:00 | none | 1x | | 58:00 | 116 | 58:00 | none | 1x | | 1:12:34 | 146 | 1:13:00 | 26 s | 1.01x | `windows = ceil(duration / 30)`, and there is no fractional window and no discount for a short file. The two rows that matter are the pair either side of a boundary: 30 s is 1 window and 31 s is 2 — one extra second of audio buys 30 s of extra encode. And a 4 s clip costs **7.5x** its own length, which is the worst unit price this model has. ## The same ceiling in five formats | Format | Byte rate | Times the model's rate | Fits in 25 MiB | | --- | --- | --- | --- | | PCM 16 kHz mono 16-bit | 32,000 B/s | 1x | 13:39 | | PCM 44.1 kHz stereo 16-bit | 176,400 B/s | 5.51x | 2:28 | | PCM 48 kHz stereo 24-bit | 288,000 B/s | 9x | 1:31 | | 64 kbps CBR | 8,000 B/s | 0.25x | 54:36 | | 128 kbps CBR | 16,000 B/s | 0.5x | 27:18 | | 320 kbps CBR | 40,000 B/s | 1.25x | 10:55 | | lossless (FLAC, ALAC) | follows the content | unknown | state the file size | **The model is fed 16 kHz mono whatever you send** — 32,000 B/s — so every row above 1x is spending bytes on samples that are thrown away before the encoder sees them. It buys nothing and it lowers the ceiling: 13:39 at the model's own rate against 1:31 as 48 kHz stereo 24-bit, for an identical transcript. The rows BELOW 1x are the one case where compressing helps: 64 kbps fits 54:36. ## Where the two grids meet | Timeline | One frame | Grids coincide every | That is | Frame boundaries the model can name | Worst gap | | --- | --- | --- | --- | --- | --- | | 24 fps | 41.6667 ms | 0.5 s | 12 frames | 1 in 12 — 8.3% | 0.48 frames | | 24000/1001 (23.976) fps | 41.7083 ms | 20.02 s | 480 frames | 1 in 480 — 0.2% | 0.5 frames | | 25 fps | 40 ms | 0.04 s | 1 frames | **all of them** | 0.5 frames | | 30000/1001 (29.97) fps | 33.3667 ms | 20.02 s | 600 frames | 1 in 600 — 0.2% | 0.5 frames | | 30 fps | 33.3333 ms | 0.1 s | 3 frames | 1 in 3 — 33.3% | 0.4 frames | | 50 fps | 20 ms | 0.02 s | 1 frames | **all of them** | 0 frames | | 60000/1001 (59.94) fps | 16.6833 ms | 20.02 s | 1200 frames | 1 in 1200 — 0.1% | 0.5 frames | | 60 fps | 16.6667 ms | 0.1 s | 6 frames | 1 in 6 — 16.7% | 0.4 frames | The tick is 20 ms and a frame is `den/num` seconds; both are exact rationals, so where they coincide is the rational LCM and not a tolerance. **25 and 50 fps are the rates that work** — the tick divides the frame, so every frame boundary is a time the model can name. At 24 fps one boundary in 12 is expressible; at 30000/1001 it is one in 600, which is once every 20.02 s. ## What removing silence buys, and costs | Speech | Audio kept | Windows | Saved | Last cue arrives early by | | --- | --- | --- | --- | --- | | 100% | 1:00:00 | 120 | none | nothing | | 90% | 54:00 | 108 | **12** | 6:00 | | 80% | 48:00 | 96 | **24** | 12:00 | | 70% | 42:00 | 84 | **36** | 18:00 | | 60% | 36:00 | 72 | **48** | 24:00 | | 50% | 30:00 | 60 | **60** | 30:00 | | 35% | 21:00 | 42 | **78** | 39:00 | | 20% | 12:00 | 24 | **96** | 48:00 | On 1:00:00 of recording, which is 120 windows untrimmed. The saving is real and it is in whole windows. **The price is in the last column**: the model is now working on the trimmed clock, so every timestamp it returns is early by all the silence removed before it — and unless the removal map is kept and applied, a subtitle file built from those times is wrong by minutes rather than milliseconds. ## What protecting the boundaries costs | Overlap each side | Hop | Windows for 1:00:00 | Extra | Surcharge | | --- | --- | --- | --- | --- | | 0 s | 30 s | 120 | none | none | | 1 s | 28 s | 129 | +9 | 7.1% | | 2 s | 26 s | 139 | +19 | 15.4% | | 5 s | 20 s | 180 | +60 | 50% | | 7.5 s | 15 s | 239 | +119 | 100% | | 10 s | 10 s | 358 | +238 | 200% | | 12 s | 6 s | 596 | +476 | 400% | **The surcharge is `W/hop - 1` and does not depend on the file.** At the usual 5 s each side the hop is 20 s and the surcharge is 50% — for a five-minute file and for a five-hour one, identically. Which is why it is a policy rather than a per-job decision: the price is known in advance. ## Constants and thresholds | Constant | Value | What it decides | | --- | --- | --- | | window | 30 s | the unit of work, and the only reason any of this arithmetic exists | | sample rate | 16000 Hz mono | what the model is fed, so a window is 480,000 samples | | mel hop | 10 ms | so a window is 3,000 mel frames | | encoder positions | 1,500 | divided into the window, this is the 20 ms timestamp grid | | sample depth | 16-bit | so the model's own byte rate is 32,000 B/s | | hosted upload ceiling | 25 MiB | which converts to 13:39 at that byte rate | | padding worth naming | 25% of what is encoded | above this the padded tail is reported | | silence worth removing | 20% | above this, trimming is reported as an option | | boundaries worth a policy | 20 | above this, cut words are a rate rather than an accident | | assumed speech share | 100% | all speech, which claims no trimming saving | | assumed speaking rate | 150 wpm | used only for the words-at-risk figure | | assumed overlap | 5 s each side | the usual sixth of a window, and 50% more compute | | assumed timeline | 25 fps | the rate where the grids agree, so it is the OPTIMISTIC assumption | | assumed path | hosted | because the ceiling only exists there | **The first five are the model's geometry, not this page's policy** — they are the published shape of the front end and the encoder, and they are in this table so a reader can check them rather than take them on trust. Everything below them is a threshold this page chose, and the harness proves each one is load-bearing by moving it and requiring the report to move with it. ## Sheet grammar One `KEY: value` per line, in any order. `DURATION` is the only line the engine cannot do without. ``` JOB: quarterly all-hands a name for the record DURATION: 1:12:34 hh:mm:ss, mm:ss, `750`, `750 s` or `12m 30s` SPEECH: 68% a percentage, or a duration of speech FILE: 66 MB what you actually have. MB is read as MiB FORMAT: mp3 128 `pcm `, `mp3 `, `flac ...` PATH: hosted hosted or local RTF: 12x local throughput, from a run you have done WPM: 165 speaking rate, for the words-at-risk figure OVERLAP: 5 seconds carried each side of every boundary FPS: 29.97 25, 30000/1001, 23.976, 50 ... TRIM: yes whether the silence is being removed SILENCE: 00:11:40-00:12:55 repeatable, and comma-separated within a line CUE: 00:00:04.12 -> 00:00:07.40 text repeatable ``` **A BARE NUMBER IN `DURATION` IS SECONDS**, because that is what a media tool prints. `1:12:34` is hours, `12:34` is minutes, and `12m 30s` works too. **A BARE NUMBER IN `SPEECH` IS A PERCENTAGE.** `68` and `68%` and `0.68` all mean the same thing; a value over 100 can only be a duration and is read as one. **`FILE` IS READ IN BINARY UNITS.** `66 MB` is 66 MiB, the same as `66 MiB`, because that is how a file browser reports it. If your provider means 1,000,000 bytes, a file near the line is over it. **`FORMAT` IS PRICED, NEVER GUESSED.** PCM is `rate x channels x bits / 8` exactly and constant-bitrate audio is `kbps / 8` exactly. A lossless codec's rate follows the content, so `flac` has no rate here at all — state `FILE` and the engine uses the file you have. **`FPS` TAKES THE EXACT FRACTION.** `29.97` is read as 30000/1001 and `23.976` as 24000/1001, because the whole grid answer turns on that distinction and a decimal would quietly give the wrong period. **`OVERLAP: 0` IS A DECISION AND NOT A DEFAULT.** Leaving `OVERLAP` out is read as undecided and reported as such; stating `0` says fixed stride, no surcharge, and you accept the cut words. ## Lanes | Lane | What it answers | Sections | | --- | --- | --- | | `plan` | Size the job before you spend it | Summary, The Sheet, The Numbers, Reasoning, Next Step | | `check` (primary) | Whether this job costs what you think, and lands where you think | Summary, Verdict, Findings, Corrected Sheet, Next Step | | `split` | The ceiling question: what has to be split, and where | Summary, The Ceiling, Where To Cut, What Splitting Costs, Next Step | | `times` | The grid question: what the timestamps can land on | Summary, The Two Grids, Cue By Cue, What A Conform Absorbs, Next Step | | `deliver` | Decide what changes: the format, the audio, or nothing | Summary, A Format Change Fixes, Only Removing Audio Fixes, Nothing Fixes, Next Step | | If the question is | Ask | Because | | --- | --- | --- | | nothing is decided yet | `plan` | it derives the window count, the ceiling and the path from what you have, instead of estimating in minutes and discovering the windows on the bill. | | this is the job, is it going to cost that | `check` | the full read: windows, padding, seams, the ceiling, and what trimming would do to every timestamp. | | it will not upload | `split` | the ceiling is a duration once you stop wasting bytes. This works out the parts and moves each boundary onto a silence. | | the subtitles do not land on frames | `times` | because the grid is 20 ms and your frame is not a multiple of it. This reads both grids and prices the rounding. | | we know it is wrong, what do we change | `deliver` | sorts the findings into the format, the audio, and nothing. | ## Findings All 49 are computed in the browser and cost nothing: 3 errors, 18 warnings, 28 notes. | Code | Severity | Scope | What it says | | --- | --- | --- | --- | | `NOTHING-READABLE` | error | sheet | not one line of the sheet parsed | | `NO-DURATION` | error | job | no duration, so there is no window count | | `NO-BYTES-FOR-HOSTED` | error | upload | hosted path, and nothing says how big the file is | | `WINDOWS-NOT-SECONDS` | note | cost | what the job is, in windows rather than minutes | | `THE-MODEL-HEARS-16K-MONO` | note | upload | the input is resampled to 16 kHz mono before anything happens | | `TICK-IS-20-MS` | note | grid | every timestamp is a multiple of 0.02 s | | `SHORT-CLIP-PREMIUM` | warn | cost | shorter than one window, so it costs a whole one | | `PADDING-IS-A-QUARTER` | warn | cost | the padded tail is a large share of what is processed | | `PAY-FOR-THE-TAIL` | note | cost | the ordinary, unavoidable padding on the last window | | `EXACTLY-WHOLE-WINDOWS` | note | cost | the duration is an exact multiple of the window | | `SILENCE-COSTS-FULL-PRICE` | note | cost | silence is encoded at the same price as speech | | `SILENCE-WORTH-REMOVING` | warn | cost | enough silence that removing it would change the window count | | `NO-SAVING-FROM-TRIM` | note | cost | trimming here saves nothing, because the ceiling rounds the same way | | `TRIM-MOVES-EVERY-TIMESTAMP` | warn | grid | trimming puts the model on a different clock | | `WORDS-AT-RISK` | note | cost | how many window boundaries fall mid-speech | | `MANY-SEAMS` | note | cost | enough boundaries that cut words are a rate rather than an accident | | `SEAM-POLICY-NOT-STATED` | warn | cost | the sheet has not said what happens at the boundaries | | `NO-OVERLAP-BY-CHOICE` | note | cost | fixed stride, chosen deliberately | | `OVERLAP-COSTS-A-FIXED-SURCHARGE` | note | cost | what protecting the boundaries costs | | `RATE-FROM-YOUR-OWN-FILE` | note | upload | a rateless codec, priced from the file you actually have | | `FORMAT-SENDS-MORE-BYTES-THAN-THE-MODEL-USES` | warn | upload | bytes spent on samples the model discards | | `FORMAT-DOES-NOT-MATTER-LOCALLY` | note | upload | the same extra bytes, costing nothing, because there is no upload | | `FORMAT-IS-WHAT-THE-MODEL-USES` | note | upload | the format is exactly the model's own rate | | `FORMAT-IS-BELOW-THE-MODELS-OWN-RATE` | note | upload | compressed below what the model consumes | | `FILE-SIZE-DISAGREES-WITH-FORMAT` | warn | sheet | the stated size and the stated format describe different files | | `FILE-SIZE-WAS-DERIVED` | note | upload | no size stated, so it was computed from the format | | `OVER-THE-UPLOAD-CAP` | warn | upload | the file cannot be sent whole | | `FITS-THE-CAP` | note | upload | under the ceiling, with the headroom stated | | `THE-CEILING-IS-A-DURATION` | note | upload | past 13:39 no format at the model's own rate can send it whole | | `SPLIT-INTO-PARTS` | note | split | how many parts the ceiling forces | | `EACH-PART-PAYS-ITS-OWN-PADDED-TAIL` | warn | split | splitting costs windows as well as effort | | `SPLIT-BOUNDARY-NOT-ON-SILENCE` | warn | split | a boundary with no silence to move to | | `BOUNDARIES-MOVED-TO-SILENCE` | note | split | every boundary landed inside a listed silence | | `SPLIT-NOT-NEEDED` | note | split | one piece, one padded tail | | `GRID-LANDS-ON-EVERY-FRAME` | note | grid | the model's grid divides the frame exactly | | `GRID-AND-TIMELINE-RARELY-COINCIDE` | warn | grid | the two grids almost never land on the same instant | | `CUE-OFF-THE-MODEL-GRID` | warn | cues | a pasted time this model could not have produced | | `CUES-ARE-ON-THE-MODEL-GRID` | note | cues | the pasted cues look like untouched model output | | `CUE-OFF-THE-FRAME-GRID` | warn | cues | a cue that does not land on a frame | | `CUE-ENDS-BEFORE-IT-STARTS` | warn | cues | a cue holding no time at all | | `CUE-PAST-THE-END` | warn | cues | a cue after the end of the stated duration | | `SILENCE-PAST-THE-END` | warn | sheet | a listed silence that runs past the end of the file | | `SILENCE-LIST-DISAGREES-WITH-SPEECH` | warn | sheet | the listed silences and the speech share do not add up | | `WALL-CLOCK-FROM-YOUR-OWN-FIGURE` | note | local | the local wall clock, from the throughput the sheet states | | `NO-THROUGHPUT-STATED` | note | local | local path with no measured throughput | | `LOCAL-HAS-NO-UPLOAD-CEILING` | note | local | a file over the hosted ceiling, which locally does not matter | | `RTF-IS-UNUSED-ON-THE-HOSTED-PATH` | note | local | a throughput figure that describes a machine nothing will run on | | `LINES-COULD-NOT-BE-READ` | warn | sheet | lines the parser could not use | | `ASSUMED-VALUES` | note | sheet | what was not stated, and what was assumed instead | **Severity means one thing here.** An `error` is something the engine cannot compute at all — there are 3 of those. A `warn` is a consequence somebody has to decide about. A `note` is the arithmetic itself. Nothing that fires on every usable sheet is a warning. ## What this page cannot do This page computes from the numbers on the sheet. It has not opened an audio file, measured a machine, or transcribed anything. - **It does not run the model.** Every figure here is arithmetic over the duration, the format and the frame rate you stated. It cannot tell you whether the transcript will be any good. - **The window, the hop and the encoder length are the published shape of the Whisper family.** A different model — or a vendor's altered deployment of this one — can have a different front end, and then the whole table moves. Check against your provider's documentation before betting a delivery on it. - **A price per window is not in here.** Providers bill per minute of audio, per second, or per token, and they round differently. What this page computes is the WORK — windows and encoded seconds — which is what the rounding is applied to. - **The 25 MiB ceiling is read as 25 x 1,048,576 bytes.** A provider reading MB as 1,000,000 would give a ceiling 4.6% lower, so a file within a few per cent of the line should be treated as over it. - **Nothing here measures your machine.** The local wall clock is your own stated throughput multiplied out. A figure from a run you have already done is worth more than any benchmark this page could quote. - **The speech share is a number you supply.** A real voice-activity detector disagrees with itself between settings, and the boundary between a pause and silence is a judgement. The trimming arithmetic is exact; its input is not. - **The words-at-risk figure is an expectation, not a list.** It is boundaries times the speech share. Which words actually get cut depends on where the sentences fall, and that needs the audio. - **Splitting on silence is modelled as moving to the middle of a listed silence.** A real split needs a few hundred milliseconds of handle on each side and an encoder that will cut on a frame boundary. - Nothing here reaches the network, reads a file, or uploads anything. ## API `POST https://api.skillsafe.ai/v1/app-api/run` with an app session token. The body is the input object itself — there is no `input` wrapper and no `X-App-Slug` header. Every lane takes `task` plus its own fields: | Lane | Fields | | --- | --- | | `plan` | `brief` (required), `known` | | `check` | `sheet` (required), `worry` | | `split` | `sheet` (required) | | `times` | `sheet` (required) | | `deliver` | `sheet` (required), `fixed` | `GET /v1/app-api/estimate` prices a lane for nothing and is the way to check an input shape without spending credits. Full worked examples in nine languages are at https://transcribe-desk.skillsafe.ai/api.html. ## Provenance The arithmetic, the thresholds, the sheet grammar and every finding are this app's own. The window, the hop, the sample rate and the encoder length are the published shape of the Whisper family, and they are in the constants table above so a reader can check them rather than take them on trust. _Generated from `stt.js` by `build-skill.js`, like SKILL.md, so the two documents and the app cannot disagree._