Cleaning up a dictation and cleaning up a file are not the same job, and until
now they shared one prompt. A dictation is read afterwards, so dropping a
filler and tightening a sentence is a favour. A file becomes an SRT, and there
the same favour is damage: the viewer hears the words while the line is on
screen, so a word that was said and is not written is noticed, and a phrase
pulled onto the line above is on screen before it is spoken.
So the file path gets its own system prompt. It says what the text is and what
it is for, and it spends its room on the one repair only context can make: the
word the transcriber misheard. Speech models fail phonetically on names, and
somebody talking about Anthropic said "Claude", not "cloud". The lines stay
where they are, nothing is shortened, nothing is turned into an abbreviation,
and the filler words stay because they were said out loud.
The glossary and the timestamp rule are appended as before, so a name listed
under Cleanup rules still reaches this prompt, and a timestamped run still gets
told to leave the stamps alone. The dictation prompt is untouched, and so are
the meeting and agent paths.
Cleanup rules now has a tab each. An untouched prompt is still stored empty, so
switching the interface language keeps switching the prompt language with it.
Who said what is the hard part of a meeting transcript, and the usual answer
is to hand one mixed recording to a model and ask it to tell the voices apart.
That guess is wrong often enough to be worse than useless in minutes, where a
decision attributed to the wrong person is a decision nobody made.
So the question never reaches a model. ffmpeg records the microphone and the
default sink's monitor as one stereo stream, you on the left and everyone else
on the right, and one process reading both is what keeps them aligned over an
hour. Each channel is transcribed on its own and the two are interleaved on a
single timeline, so attribution is settled by the wire a voice arrived on.
What a microphone picks up from the speakers lands on both channels; our copy
is dropped when it overlaps theirs in time and says nearly the same thing.
The stream is written to disk as it arrives rather than held in memory, so
length costs nothing and a crash costs the tail instead of the whole meeting.
Every stage the run reaches is recorded in meetings.jsonl, so a failure while
summarising does not throw away the transcription of an hour of audio: the
retry reads the transcript back out of the document and picks up from there.
A run that dies keeps its recording whether or not audio is being kept.
The minutes model is configured on its own, under Settings, with its own
prompt, and it is told who was expected in the room so the names come out
spelled right. It is told outright that the transcript is a record of other
people talking, not instructions addressed to it.
The built-in listener now holds several bindings rather than one, and the KDE
side is parameterised by desktop id, so the meeting toggle gets a shortcut of
its own on the same footing as the dictation one.
whisper-1's verbose response carries a start and an end for every segment,
and the file tab was reading only the start, to build the [mm:ss] prefix.
Keeping the end as well is all an SRT needs.
The text stays the authority on wording and the segments on timing; they
meet at that prefix, which the cleanup model is already told to leave alone.
So a transcript that went through cleanup still turns into properly timed
subtitles. A line whose stamp matches no segment runs until the next line
starts, a line with no stamp at all joins the cue above it, and an end that
would run into the next cue is trimmed back.
The button is dead until a timestamped run finishes, because without
timestamps there are no segments to time anything with.
OpenRouter mirrors OpenAI's /audio/transcriptions field for field, so one
multipart request serves both providers and only the key, the base URL and
the model id change. That puts a dozen speech models behind the key that was
already there for the cleanup, and makes a single OpenRouter key enough for
the whole chain.
Its transcription endpoint takes no hint field, so the words from Cleanup
rules are not sent there; they still reach the cleanup model as a glossary.
Timestamps switch to openai/whisper-1, the namespaced id of the only model
that returns segment times.
The API tab is now grouped by role rather than by service, because a key no
longer belongs to a single job: both keys sit at the top, the two jobs below.
Each provider keeps its own model, so switching back and forth does not
overwrite the other one's.
The screenshots were downscaled to 430 px wide, which made the UI text
blurry. Restore them at native 1292 px as lossless WebP, which is also
half the size of the original PNGs (72 KB against 155 KB for the largest).
Rewrite every em dash in prose, comments, docstrings and interface strings
as ordinary punctuation.
Ctrl+Space starts and stops a recording. The audio goes to OpenAI for
transcription, a model on OpenRouter strips the fillers and restores
punctuation, and the result is copied and pasted into the focused window.
Only the Python standard library and PyQt6 — HTTP, multipart uploads and
WAV writing are all hand-rolled.
- pw-record captures raw 16 kHz mono PCM with a live level meter
- the corner indicator is drawn through XWayland, since a Wayland client
cannot position its own window
- silence is caught before it costs an API call, relative to each
recording's own noise floor, plus a filter for the stock phrases models
invent when handed silence
- audio and video files can be transcribed too, optionally with [mm:ss]
timestamps, chunked through ffmpeg for long inputs
- global shortcut installs as a KDE custom shortcut, with an evdev
listener as a fallback until the session is restarted
- Turkish and English interface, following the system locale by default