CLAUDE.md is a working note for the local editing setup, not project
documentation. This repository is publicly readable, so it does not belong in
the tree. The file stays on disk and is now ignored, together with the other
assistant context files.
The null sink and its two loopbacks were loaded at startup and stayed
loaded forever. While loaded they link the microphone and the speaker
device into a single PipeWire driver group: the microphone drives the
graph and the speaker device runs as a clock follower, so every playback
stream pays for continuous cross-device resampling.
Nothing needs the chain while idle — recording is triggered manually.
Build it in toggle_recording() and drop it as soon as the audio is
captured, before transcription runs for minutes.
Module ids are now derived from pactl instead of tracked in a state file,
so a crashed process cannot leave stale ids behind. The state file only
holds the device selection. Startup tears down any leftover chain.
Loopbacks get latency_msec=200; transcription is offline, and a small
buffer would put these nodes on the graph's realtime deadline.
The meeting pipeline created a fresh Diarizer per recording, each loading
the multi-GB pyannote speaker-diarization model anew (api/pipeline.py).
Whisper and Ollama run remotely in this deployment, so pyannote was the
only heavy in-process consumer. Reloading it per recording leaked CPU
memory (torch reference cycles + glibc arena fragmentation) that was
never returned to the OS, climbing to a 37 GB peak over a multi-day run
until the kernel OOM-killed the service.
Cache the loaded pipeline on the class and reuse it across Diarizer
instances, mirroring TranscriptionEngine._model. RSS now stays flat.
- llm: punctuate() adds punctuation/capitalisation without changing words
- llm: _strip_code_fences() handles markdown-wrapped JSON from gemma3
- llm: filter string 'null' from identify_speakers result
- pipeline: punctuate raw_text in parallel with refine for solo recordings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- llm: generate_title_and_tldr() returns concise title and 2-3 sentence summary
- output: index in root, transkript+zusammenfassung in {base}/ subdir with backlinks
- pipeline: call generate_title_and_tldr for both solo and meeting recordings
- router: mirror subdir structure when copying to Obsidian vault
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Config: obsidian.vault path. On Obsidian button click, file is copied to
vault dir then opened via obsidian:// URI. Vault path configurable in settings.
- /audio/devices now returns sounddevice device names (not pactl source names)
so the stored device name works directly with sd.InputStream
- /audio/combined maps sounddevice names back to pactl source names via
description matching for the loopback commands
- Combined sink description set to 'transkriptor-combined' (no spaces) so
sounddevice name matches the value stored in config
- Add _pactl_source_for_sd_name() helper for the mapping
Adds a "Diarisierung" section with an enabled/disabled toggle,
HuggingFace token input, and a help link to pyannote/speaker-diarization-3.1.
loadConfig() and the save handler now persist diarization settings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows a card with excerpt navigation and name inputs when the backend
emits speakers_unknown. Submitting posts the mapping to /speakers or
leaves speakers anonymous; handles awaiting_speakers status label.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces cfg.update(body) with _deep_merge so partial updates (e.g.
setting whisper.base_url) no longer wipe sibling keys. Also persists
the merged config back to disk via tomli_w. Adds test_put_config_deep_merges.