81 Commits
Author SHA1 Message Date
thomas.kopp 52e55e112f fix: build PipeWire capture chain on demand instead of permanently
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.
2026-08-02 01:25:15 +02:00
thomas.kopp 6b0ee60d94 fix: cache pyannote diarization pipeline to stop memory leak / OOM
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.
2026-07-22 09:03:42 +02:00
thomas.koppandClaude Sonnet 4.6 8ec9044c75 fix: whisper repetition loops, meeting transcript punctuation
- transcription: add temperature_inc=0 to whispercpp to disable fallback (prevents loops)
- pipeline: punctuate meeting transcript in one pass (parallel with summarize)
- output: write_meeting_docs accepts pre-built transcript_text
- llm: punctuate prompt preserves speaker labels

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 12:34:11 +02:00
thomas.koppandClaude Sonnet 4.6 658f9be47f fix: punctuate raw transcript, strip JSON code fences, filter null speaker names
- 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>
2026-04-02 12:23:25 +02:00
thomas.koppandClaude Sonnet 4.6 d3582eaeb7 feat: tab navigation in modal (Index/Transkript/Zusammenfassung)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 12:10:50 +02:00
thomas.koppandClaude Sonnet 4.6 336628341b feat: AI-generated title+tldr, subfolder structure, backlinks in transkript/zusammenfassung
- 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>
2026-04-02 12:07:54 +02:00
thomas.koppandClaude Sonnet 4.6 1cfb9c127b fix: use vault+file URI format for Obsidian, more reliable than path=
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 11:57:47 +02:00
thomas.koppandClaude Sonnet 4.6 fe8b8bb125 fix: auto-include transkript/zusammenfassung siblings when copying index to Obsidian vault
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 11:47:29 +02:00
thomas.koppandClaude Sonnet 4.6 ca10cbb20b fix: call obsidian binary directly instead of xdg-open for URI handling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 11:42:23 +02:00
thomas.koppandClaude Sonnet 4.6 180fe43df7 fix: handle pyannote 4.x DiarizeOutput wrapper in diarize()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 11:34:02 +02:00
thomas.koppandClaude Sonnet 4.6 8ee11a31a1 fix: use token= instead of use_auth_token= for pyannote Pipeline.from_pretrained
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 11:15:14 +02:00
thomas.koppandClaude Sonnet 4.6 06f7361004 feat: write 3 files per solo recording (index + transkript + zusammenfassung)
- pipeline: call write_solo_docs() instead of save_transcript(); broadcast paths dict
- router: /open accepts paths list for Obsidian mode, copies all 3 files to vault
- app.js: store _modalPaths from saved event; Obsidian button sends all paths
- tests: test_write_solo_docs_creates_three_files added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 11:10:28 +02:00
thomas.kopp a37e09fb4e feat: copy transcript to Obsidian vault on open
Config: obsidian.vault path. On Obsidian button click, file is copied to
vault dir then opened via obsidian:// URI. Vault path configurable in settings.
2026-04-02 11:00:55 +02:00
thomas.kopp 6f718f0753 feat: add Obsidian open button; fix folder button using dolphin --select 2026-04-02 10:55:19 +02:00
thomas.kopp 348ce332c7 feat: add folder button to transcript modal 2026-04-02 10:47:08 +02:00
thomas.kopp 7e0851fc95 fix: pass whisper backend to solo pipeline transcribe_file call 2026-04-02 09:18:20 +02:00
thomas.kopp 11dee75ab3 fix: record at 48000 Hz — PipeWire virtual sinks reject 16 kHz resampling
Whisper and faster-whisper both handle arbitrary sample rates internally.
2026-04-02 09:14:34 +02:00
thomas.kopp b4e7e08918 fix: update audio devices test to mock sounddevice instead of pactl 2026-04-02 07:52:34 +02:00
thomas.kopp 04b655e664 fix: use sounddevice names for audio device list and combined source
- /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
2026-04-02 07:51:42 +02:00
thomas.kopp 251f9c238d fix: restore PipeWire combined source automatically on startup
Save mic/monitor device names to pipewire-modules.json alongside module IDs.
On startup, recreate transkriptor-combined if not already loaded.
2026-04-02 01:46:19 +02:00
thomas.kopp 1a61b53027 fix: serve /settings without auth header — JS handles token check 2026-04-02 01:38:17 +02:00
thomas.kopp c7cad4bb2a feat: add whisper.cpp ROCm backend support for AMD GPU acceleration
- transcription.py: new _transcribe_remote_whispercpp() using /inference endpoint
- transcription.py: backend param routes to openai or whispercpp remote path
- config.py: whisper.backend default 'openai', alt 'whispercpp'
- pipeline.py: passes backend from config to transcribe_file
- settings: backend dropdown (OpenAI-compat / whisper.cpp)
- SETUP.md: whisper.cpp ROCm build and systemd setup instructions

whisper-cpp-server running on beastix :8080 (ROCm0, gfx1030, RX 6800 XT)
2026-04-02 01:33:32 +02:00
thomas.kopp 56d41b8620 docs: add HuggingFace diarization setup instructions to SETUP.md 2026-04-02 01:18:55 +02:00
thomas.koppandClaude Sonnet 4.6 5f384af6cf feat: add diarization section to settings page
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>
2026-04-02 01:18:26 +02:00
thomas.koppandClaude Sonnet 4.6 0eb85b98f1 feat: add frontend speaker naming card for diarization
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>
2026-04-02 01:17:23 +02:00
thomas.kopp e04816fce6 feat: meeting pipeline — parallel diarization, speaker ID, 3-doc output 2026-04-02 01:13:24 +02:00
thomas.kopp 37e432f7fa feat: POST /speakers — resolves pipeline pause with speaker name mapping 2026-04-02 01:07:41 +02:00
thomas.kopp dbb35ce71d feat: AppState gains speaker pause fields and AWAITING_SPEAKERS status 2026-04-02 01:06:30 +02:00
thomas.kopp 033c1fc486 feat: write_meeting_docs() — creates index, transkript, zusammenfassung 2026-04-02 01:05:07 +02:00
thomas.kopp 9b5b89e159 feat: OllamaClient.identify_speakers() and summarize() for diarization pipeline 2026-04-02 01:03:40 +02:00
thomas.kopp b8cc8a3b33 feat: align_segments() — map Whisper timestamps to pyannote speakers 2026-04-02 01:00:58 +02:00
thomas.kopp 1a9d0eacc2 feat: Diarizer class wrapping pyannote/speaker-diarization-3.1 2026-04-02 00:59:50 +02:00
thomas.kopp 47909637a8 feat: transcribe_file returns timestamped segments when with_segments=True 2026-04-02 00:55:53 +02:00
thomas.kopp 7dfc0e0c5f feat: add diarization config defaults (enabled=false, hf_token) 2026-04-02 00:53:53 +02:00
thomas.kopp 7cd6c2a848 docs: diarization implementation plan (13 tasks) 2026-04-02 00:50:57 +02:00
thomas.kopp 8d1af32ef3 docs: diarization + speaker identification design 2026-04-02 00:46:18 +02:00
thomas.kopp 80ce1aa77c docs: add setup guide for Beastix server and client installation 2026-04-02 00:01:05 +02:00
thomas.kopp 52ba53bec4 fix: validate Ollama URL protocol before fetching api/tags 2026-04-01 20:51:23 +02:00
thomas.koppandClaude Sonnet 4.6 0bdc0a5e42 feat: settings page — PipeWire audio device + remote Whisper/Ollama config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 20:48:56 +02:00
thomas.koppandClaude Sonnet 4.6 81fbbfb56e feat: status includes is_admin, gear icon in header for admins
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 20:45:33 +02:00
thomas.kopp 2376bf5d71 fix: PUT /config deep-merges nested config instead of shallow update
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.
2026-04-01 20:40:40 +02:00
thomas.kopp ff68827280 fix: module_ids as integers in response, add 403 test for POST /audio/combined 2026-04-01 20:38:43 +02:00
thomas.kopp 478a1ac9d0 feat: GET /audio/devices, POST /audio/combined — PipeWire source management 2026-04-01 20:36:27 +02:00
thomas.koppandClaude Sonnet 4.6 ef4aa2a840 feat: AudioRecorder accepts device param — reads audio.device from config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 20:32:44 +02:00
thomas.kopp 5e7faa8844 fix: use get_running_loop() instead of deprecated get_event_loop() 2026-04-01 20:30:06 +02:00
thomas.kopp 8300851e77 feat: remote Whisper via whisper.base_url — OpenAI-compatible upload 2026-04-01 20:28:31 +02:00
thomas.kopp 912b333124 feat: add audio.device and whisper.base_url to config defaults 2026-04-01 20:25:48 +02:00
thomas.kopp 3f9abc6a89 docs: settings page + remote whisper design 2026-04-01 20:11:38 +02:00
thomas.kopp d8c6fc790b fix: define _guest_user() for tray/hotkey-triggered recording 2026-04-01 16:00:33 +02:00
thomas.kopp ccdc75c74c feat: show date and time in transcript list items 2026-04-01 14:40:01 +02:00
thomas.kopp b74147967b feat: tüit logo in header, clean transcript item layout with grouped action buttons 2026-04-01 14:37:03 +02:00
thomas.kopp 2ab6e7d73b fix: move reprocess button to transcript list item, remove from modal 2026-04-01 14:30:28 +02:00
thomas.kopp 33ae9dc1d8 feat: reprocess existing transcript via Ollama — modal button + POST /transcripts/{filename}/reprocess 2026-04-01 14:27:15 +02:00
thomas.kopp 3673e28e73 fix: improve system prompt — explicit paragraph breaks, lists, section headings 2026-04-01 14:23:26 +02:00
thomas.kopp 1ab023c2ac feat: transcript modal with markdown rendering, delete button, remove preview section 2026-04-01 14:18:04 +02:00
thomas.kopp ae3ae61593 feat: DELETE /transcripts/{filename} — delete transcript with path-confinement check 2026-04-01 14:13:02 +02:00
thomas.kopp aa3eef8fb1 feat: GET /transcripts/{filename} — serve transcript content 2026-04-01 14:12:30 +02:00
thomas.kopp 0bb0975a09 docs: add transcript modal + delete implementation plan 2026-04-01 14:09:23 +02:00
thomas.kopp 629341930e fix: show recording state on first click, fix error state via WS 2026-04-01 13:57:41 +02:00
thomas.kopp d60d6bb1be fix: test_config_loads_defaults — remove reload() that undid CONFIG_PATH patch 2026-04-01 13:49:21 +02:00
thomas.kopp b63f5d59a9 fix: read WebSocket token from query_params directly, not function arg 2026-04-01 13:22:20 +02:00
thomas.kopp 6e317a9c67 fix: error state resettable via mic click, debug logging, pipeline traceback 2026-04-01 12:41:45 +02:00
thomas.kopp 6574481647 fix: web-based first-run setup — removes terminal input(), works under systemd 2026-04-01 12:26:17 +02:00
thomas.kopp d9db41b0b4 fix: use ip route instead of hostname -I for Arch compatibility 2026-04-01 12:23:41 +02:00
thomas.kopp f133b9590c fix: use venv instead of pip --user for Arch PEP 668 compatibility 2026-04-01 12:21:40 +02:00
thomas.kopp 325cb2e6e7 feat: merge implement/transkriptor — full tüit Transkriptor implementation 2026-04-01 11:47:32 +02:00
thomas.kopp 1466a1529f feat: multi-user auth — per-user spaces, pbkdf2 passwords, session tokens, login page 2026-04-01 08:39:16 +02:00
thomas.kopp 94dd871031 feat: install.sh — ROCm check, LAN-mode prompt, systemd user service, hotkey instructions 2026-04-01 02:30:31 +02:00
thomas.kopp 4ec9c56812 feat: main entry point — FastAPI + pystray tray + SIGUSR1 via uvicorn loop capture 2026-04-01 02:29:52 +02:00
thomas.kopp b62c3d6604 feat: browser UI — tüit CI dark theme, XSS-safe DOM, auth-aware 2026-04-01 02:29:07 +02:00
thomas.kopp 319db8c788 feat: API router + pipeline — toggle, status, transcripts, WebSocket, auth stub 2026-04-01 02:26:47 +02:00
thomas.kopp ad84ad5aa3 feat: app state module with status enum and subscriber pattern 2026-04-01 02:25:39 +02:00
thomas.kopp 927e75aa5e feat: audio module — sounddevice recorder with WAV export 2026-04-01 02:22:53 +02:00
thomas.kopp 3976ecb52e feat: transcription module — faster-whisper with ROCm auto-detect 2026-04-01 02:22:03 +02:00
thomas.kopp 333898c5ab feat: LLM module — Ollama client with transcript refinement 2026-04-01 02:20:56 +02:00
thomas.kopp 6775f98259 feat: output module — Markdown file writer with slugified filenames 2026-04-01 02:14:57 +02:00
thomas.kopp 8a39d8b97e feat: config module with TOML defaults 2026-04-01 02:14:14 +02:00
thomas.kopp 20e20f44cd chore: project scaffold 2026-04-01 02:12:41 +02:00
thomas.kopp e4e2322eed chore: add .gitignore with .worktrees/ 2026-04-01 02:11:05 +02:00
thomas.kopp 6b0f2ba39a docs: implementation plan for tüit Transkriptor 2026-04-01 02:04:40 +02:00
thomas.kopp 4c04e17d06 docs: initial design for tüit Transkriptor desktop transcription tool 2026-04-01 01:58:15 +02:00