Build Sonix
KINDAback to the verdict
A local transcription pipeline whose hard problems are getting audio into the exact form the recogniser accepts, keeping timestamps honest across a long recording where the model hallucinates through silence, and exporting subtitle formats that players will actually load.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the application and the worker.
Get it python.org, or your package manager. open ↗
Verify
python3 --version prints 3.12 or higher - installfree
Why Produces the one audio form the recogniser accepts, and probes files before they are queued.
Get it Your package manager, or a static build from the project site. open ↗
Verify
ffmpeg -i sample.m4a -ar 16000 -ac 1 -c:a pcm_s16le out.wav produces a playable file - installfree
Why The recogniser. Model size decides both quality and how long a ninety minute file takes on your machine.
Get it Build the project and run its model download script for the size you want. open ↗
Verify
The command line tool transcribes the bundled sample - have readyfree
Why Timestamp drift and hallucination through silence only appear on long real audio, never on a thirty second clip.
Get it One recording of an hour or more that you have the right to use, plus a generated file with two minutes of silence in the middle.
Verify
Both are in the test fixtures and used by the timing tests - decidefree
Why Both are asserted by tests, and a retention period chosen after a year of recordings have accumulated is not a decision.
Get it Write down how long audio and transcripts are kept, and the tolerance in milliseconds the timing test enforces.
Verify
Both appear in configuration and in tests
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- media(id, original_path, sha256, mime, duration_ms, imported_at, consent_note) - audio(media_id, canonical_path, sample_rate, channels, encoding) · the only file the recogniser sees - jobs(id, media_id, state, model_name, params_json, attempt, started_at, finished_at, error) - chunks(id, media_id, ordinal, start_ms, end_ms, silence_split) · where the file was cut and why - segments(id, media_id, ordinal, start_ms, end_ms, text, speaker_turn, chunk_id) - edits(id, segment_id, previous_text, new_text, at) · corrections are additive, the machine output stays - exports(id, media_id, format, path, at) · every timestamp in every export derives from segments, never from a chunk offset
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_PATH | required | ./data/transcripts.db | Jobs, chunks, segments and edits. |
MEDIA_DIR | required | ./data/media | Originals and converted audio, deleted together by retention. |
WHISPER_BIN | required | /usr/local/bin/whisper-cli | The recogniser binary invoked by the worker. |
WHISPER_MODEL | required | ./models/ggml-base.en.bin | Model file. Its name is recorded on every job. |
CHUNK_TARGET_SECONDS | required | 240 | Target chunk length. The actual cut lands on nearby silence. |
RETENTION_DAYS | required | 180 | Audio and transcripts are removed together after this. |
TIMING_TOLERANCE_MS | required | 250 | What the long file timing test enforces at the end of a recording. |
The build, in order
One canonical audio file, produced before anything else runs
Every recording becomes the exact format the recogniser accepts, and the original is kept.
The tool accepts one shape of audio; produce exactly that shape.
Files
src/audio/convert.pytests/test_convert.pyAn hour of processing is an expensive way to discover a broken file.
Files
src/audio/probe.pyThe same recording gets imported twice more often than you would think.
Files
src/ingest.py
done when · tick each as it passeswatch out- Handing the recogniser the original file because it played fine in the browser gets you either an immediate failure or, worse, a run that produces plausible nonsense from a misread sample rate, and on a two hour recording you find out after two hours of processing. Convert first, assert the sample rate and channel count of the converted file, and treat probe failure as a rejected import.
Long recordings, honest timestamps
A ninety minute file transcribes with timings that line up at the end as well as the start.
A fixed cut lands in the middle of the most important sentence.
Files
src/transcribe/chunking.pyEvery export inherits this arithmetic, so get it wrong in one place only.
Files
src/transcribe/timing.pytests/test_timing.pyFluent invented sentences are the failure mode nobody notices.
Files
src/transcribe/silence.py
done when · tick each as it passeswatch out- Cutting a recording every thirty seconds on a fixed boundary slices words in half and, worse, feeding the model a passage of silence or music makes it emit fluent invented sentences, which are indistinguishable from real ones in the output because they carry timestamps too. Cut on silence, skip silent stretches explicitly, and mark anything the detector called non speech.
Speakers, described as what they actually are
Speaker changes are marked where the tool can mark them, and never invented.
And say in the interface that it is experimental, because it is.
Files
src/transcribe/diarise.pyAnd store that a person did it.
Files
src/app/segments.pyA guessed speaker is worse than no speaker.
Files
src/config.py
done when · tick each as it passeswatch out- Presenting turn markers as 'Speaker 1' and 'Speaker 2' implies the tool tracked two voices across the recording, when it only noticed that the voice changed, so two people swapping back and forth get relabelled every few minutes and a reader trusts an attribution that was never computed. Show turns, let a person attach the names, and record who attached them.
Correcting a transcript without destroying its timing
Editing text keeps every segment anchored to the audio.
The document view is a rendering of segments, never the source.
Files
src/app/editor.pyYou will want to compare what the machine said with what it meant.
Files
src/db/edits.pyNot from character counts, which have nothing to do with speech.
Files
src/transcribe/split.pytests/test_split.py
done when · tick each as it passeswatch out- Letting the transcript be edited as one long document loses the mapping between text and time the moment someone reflows a paragraph, and the subtitles exported afterwards drift further out of sync the longer the recording runs. Keep the segment as the unit of editing, and derive the document view from segments rather than the other way round.
Exports that load, and recordings that do not lingerproduct builder
Subtitle files open in a player, and a deletion removes the audio as well as the text.
They differ in the header and in one punctuation mark, which is enough.
Files
src/export/vtt.pysrc/export/srt.pyThe cheapest possible test for a format you do not control.
Files
tests/test_export_roundtrip.pyA deleted transcript with the recording still on disk has deleted nothing.
Files
src/retention.py
done when · tick each as it passeswatch out- Writing SubRip timestamps into a file named with a WebVTT extension, or omitting the WEBVTT line, produces a file that every player accepts silently and displays no captions from, so the failure is discovered by an audience rather than by you. Write each format from its own function and validate an exported file by parsing it back before declaring the export complete.
That is the whole plan for Sonix. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Meeting bots and call capture.
- Live streaming transcription.
- Translation and subtitle burn-in.
- Cross recording analytics.
- live multi-speaker accuracy
- calendar and CRM integrations
- cross-call team analytics
- meeting-bot auto-join
- A measured accuracy report against transcripts you typed, with the sample size stated
- Word level timing for a karaoke style review view, which the recogniser can already emit
- A watched folder that imports and queues automatically, once retention has run for a month
Need the files? The project pack on the verdict page hands your agent the whole brief · more meeting notes.