Build Sonix

KINDAback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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.

estimated effort weekendthe files for this build are in the project pack

RuntimePython 3.12 with FastAPI and a single background workerRecogniserwhisper.cpp invoked as a process, model path in configurationAudioFFmpeg to one canonical form, sixteen kilohertz mono signed sixteen bitStorageSQLite for segments and jobs, media beside it on diskEditingCorrections stored as rows against segments, never overwriting the original output

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. 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

  2. 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

  3. 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

  4. 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

  5. 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.

VariableNeededExampleWhere the value comes from
DATABASE_PATHrequired./data/transcripts.dbJobs, chunks, segments and edits.
MEDIA_DIRrequired./data/mediaOriginals and converted audio, deleted together by retention.
WHISPER_BINrequired/usr/local/bin/whisper-cliThe recogniser binary invoked by the worker.
WHISPER_MODELrequired./models/ggml-base.en.binModel file. Its name is recorded on every job.
CHUNK_TARGET_SECONDSrequired240Target chunk length. The actual cut lands on nearby silence.
RETENTION_DAYSrequired180Audio and transcripts are removed together after this.
TIMING_TOLERANCE_MSrequired250What the long file timing test enforces at the end of a recording.

The build, in order

  1. One canonical audio file, produced before anything else runs

    Every recording becomes the exact format the recogniser accepts, and the original is kept.

    1. The tool accepts one shape of audio; produce exactly that shape.

      Files src/audio/convert.pytests/test_convert.py

    2. An hour of processing is an expensive way to discover a broken file.

      Files src/audio/probe.py

    3. The same recording gets imported twice more often than you would think.

      Files src/ingest.py

    done when · tick each as it passes
    watch 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.
  2. Long recordings, honest timestamps

    A ninety minute file transcribes with timings that line up at the end as well as the start.

    1. A fixed cut lands in the middle of the most important sentence.

      Files src/transcribe/chunking.py

    2. Every export inherits this arithmetic, so get it wrong in one place only.

      Files src/transcribe/timing.pytests/test_timing.py

    3. Fluent invented sentences are the failure mode nobody notices.

      Files src/transcribe/silence.py

    done when · tick each as it passes
    watch 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.
  3. Speakers, described as what they actually are

    Speaker changes are marked where the tool can mark them, and never invented.

    1. And say in the interface that it is experimental, because it is.

      Files src/transcribe/diarise.py

    2. And store that a person did it.

      Files src/app/segments.py

    3. A guessed speaker is worse than no speaker.

      Files src/config.py

    done when · tick each as it passes
    watch 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.
  4. Correcting a transcript without destroying its timing

    Editing text keeps every segment anchored to the audio.

    1. The document view is a rendering of segments, never the source.

      Files src/app/editor.py

    2. You will want to compare what the machine said with what it meant.

      Files src/db/edits.py

    3. Not from character counts, which have nothing to do with speech.

      Files src/transcribe/split.pytests/test_split.py

    done when · tick each as it passes
    watch 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.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more meeting notes.