Skip to main content

tapeback

Local meeting recorder for Linux. Records system audio + microphone via PipeWire/PulseAudio, transcribes with Whisper (local by default), identifies speakers, saves Markdown to your Obsidian vault. Transcription stays on your machine unless you opt into a remote STT backend (OpenAI today).

Works with any video call platform: Google Meet, Zoom, Teams, Telegram, Discord, Slack huddles.

tapeback in Obsidian

Features

  • Live transcription (opt-in): read the transcript while the meeting is still going — Whisper transcribes in the background every 60 seconds (set TAPEBACK_LIVE=true)
  • Platform-agnostic: captures OS-level audio, works with any app
  • Local transcription: faster-whisper on CPU or CUDA GPU (default)
  • Optional remote STT: set TAPEBACK_STT_BACKEND=openai to use a remote transcription backend instead of local Whisper. OpenAI is the only remote backend today (requires tapeback[stt] + TAPEBACK_STT_API_KEY; meeting audio leaves the machine). Uploads retry on timeouts/transient errors with status heartbeats; finished chunks are cached so tapeback process on the same WAV only re-uploads what is missing.
  • Speaker diarization: pyannote identifies who said what
  • Stereo channel separation: your mic (left) vs. others (right) for accurate "You" attribution
  • Obsidian-native output: Markdown with YAML frontmatter, wikilinks to audio files
  • LLM summarization: Anthropic, OpenAI, Groq, Gemini, DeepSeek, OpenRouter, Qwen (with automatic provider fallback)
  • System tray: start/stop recording from the tray icon, no terminal needed
  • CLI-first: tapeback start, Ctrl+C to stop, done

tapeback is modular — the base package handles recording and transcription. Speaker diarization and LLM summaries are optional and installed separately.

Installation

Arch Linux (AUR)

yay -S tapeback                              # recording + transcription
yay -S tapeback tapeback-tray                # + system tray icon
yay -S tapeback tapeback-llm                 # + LLM summaries
yay -S tapeback tapeback-diarize             # + speaker diarization
yay -S tapeback tapeback-cuda                # + GPU transcription on CUDA 13 systems
yay -S tapeback tapeback-tray tapeback-llm tapeback-diarize tapeback-cuda  # everything

All system dependencies (ffmpeg, PipeWire) are installed automatically.

Ubuntu / Debian (.deb)

Pre-built .deb packages are attached to every GitHub Release. Pick a version (see the releases page for the current one), download, install:

wget https://github.com/yastcher/tapeback/releases/download/v0.9.10/tapeback_0.9.10_amd64.deb
sudo apt install ./tapeback_0.9.10_amd64.deb

# Optional extras:
sudo apt install ./tapeback-tray_0.9.10_all.deb       # tray icon
sudo apt install ./tapeback-llm_0.9.10_all.deb        # LLM summaries
sudo apt install ./tapeback-diarize_0.9.10_all.deb    # speaker diarization
sudo apt install ./tapeback-cuda_0.9.10_all.deb       # GPU transcription on CUDA 13

The base package bundles its own Python interpreter (from python-build-standalone) so it works on any modern Ubuntu or Debian regardless of the system Python version. Only system dependencies are ffmpeg and pulseaudio-utils (for parecord / pactl).

pip / uv

Install system dependencies first:

# Arch / Manjaro
sudo pacman -S python uv ffmpeg pipewire-pulse

# Ubuntu / Debian
sudo apt install python3 pipx ffmpeg pulseaudio-utils

# Fedora
sudo dnf install python3 pipx ffmpeg pipewire-pulseaudio

Then install tapeback:

uv tool install tapeback                          # recording + transcription
uv tool install "tapeback[tray]"                  # + system tray icon
uv tool install "tapeback[stt]"                   # + remote OpenAI STT
uv tool install "tapeback[llm]"                   # + LLM summaries (also installs openai)
uv tool install "tapeback[diarize]"               # + speaker diarization
uv tool install "tapeback[tray,stt,llm,diarize]"  # everything

tapeback[stt] installs the openai SDK for remote transcription. tapeback[llm] also installs that SDK (plus anthropic), so remote STT works if you already have the LLM extra.

pipx or Nix

# pipx
pipx install tapeback
pipx install "tapeback[tray,llm,diarize]"         # everything

# Nix
nix run github:yastcher/tapeback                  # basic
nix run github:yastcher/tapeback#tray             # + system tray icon
nix run github:yastcher/tapeback#llm              # + LLM summaries
nix run github:yastcher/tapeback#diarize          # + speaker diarization
nix run github:yastcher/tapeback#full             # everything

Quick start

tapeback start                     # start recording, Ctrl+C to stop

That's it. The transcript is saved to ~/tapeback/meetings/.

To save to your Obsidian vault instead:

mkdir -p ~/.config/tapeback
echo 'TAPEBACK_VAULT_PATH=~/Documents/obsidian/vault' > ~/.config/tapeback/.env

Tip: if you always meet in one language, pin it — auto-detection can misfire on a channel that starts silent (and even hallucinate). English terms inside another language still transcribe fine:

TAPEBACK_LANGUAGE=en tapeback start          # or add TAPEBACK_LANGUAGE=en to .env

System tray

Run without a terminal — right-click the tray icon to start/stop recording:

tapeback tray

Icon color shows the current state: gray = idle, red = recording, orange = processing.

To autostart on login, create ~/.config/autostart/tapeback-tray.desktop:

[Desktop Entry]
Name=tapeback
Exec=tapeback tray
Type=Application
X-GNOME-Autostart-enabled=true

tapeback's tray speaks the StatusNotifierItem D-Bus protocol directly — no pystray, no GTK, no XEmbed. KDE Plasma, Hyprland (with waybar), and Sway (with waybar/eww) display it out of the box on both X11 and Wayland.

GNOME

GNOME Shell does not display SNI items natively. Install the AppIndicator Support extension (one-time setup, also needed by Slack, Dropbox, etc.):

# Ubuntu / Debian
sudo apt install gnome-shell-extension-appindicator
# Fedora
sudo dnf install gnome-shell-extension-appindicator

Open the Extensions app, enable Ubuntu AppIndicators (or AppIndicator and KStatusNotifierItem Support), log out + back in. tapeback tray prints this hint to stderr on startup if it detects an affected session. See issue #3 for background.

Speaker diarization

Speaker diarization identifies who said what in the recording. Without it, tapeback uses stereo channels: your mic is labeled "You", everything else is labeled "Other".

To enable diarization, you need a HuggingFace token with access to pyannote models:

  1. Create account at huggingface.co
  2. Accept license at pyannote/speaker-diarization-3.1
  3. Accept license at pyannote/segmentation-3.0
  4. Create token at huggingface.co/settings/tokens
  5. Add to ~/.config/tapeback/.env:
TAPEBACK_HF_TOKEN=hf_your_token_here

First run downloads the pyannote model (~1 GB). An NVIDIA GPU is strongly recommended — diarization on CPU is very slow.

LLM summarization

After transcription, tapeback can add a brief summary, action items, and key decisions using an LLM.

Set an API key for at least one provider in ~/.config/tapeback/.env:

TAPEBACK_LLM_PROVIDER=gemini
GEMINI_API_KEY=...
Provider Env var Default model
anthropic ANTHROPIC_API_KEY claude-sonnet-4-20250514
openai OPENAI_API_KEY gpt-4o
groq GROQ_API_KEY llama-3.3-70b-versatile
gemini GEMINI_API_KEY gemini-2.5-flash
openrouter OPENROUTER_API_KEY google/gemini-2.5-flash:free
deepseek DEEPSEEK_API_KEY deepseek-chat
qwen DASHSCOPE_API_KEY qwen-turbo

If the primary provider fails, tapeback automatically tries the next available provider (any provider with an API key set).

PII masking

By default, recording, transcription and diarization stay on your machine. Summarization is the usual off-machine path: a text request to an LLM provider. If that bothers you, leave summarization off (TAPEBACK_SUMMARIZE=false) or turn on masking.

Opt-in remote STT is a second outbound path. With TAPEBACK_STT_BACKEND=openai, meeting audio is uploaded to OpenAI. PII masking cannot apply there — it only rewrites text before summarization. Keep the default (local) unless you accept that trade-off.

TAPEBACK_MASK_PII=true

Emails and phone numbers are then replaced with [EMAIL_1] / [PHONE_1] placeholders before the transcript is sent — including on the retry and on every fallback provider — and the real values are restored in the summary saved to your vault. The transcript on disk is never masked.

Add the names yourself. Whisper writes what people say, and what people say aloud in meetings is names, not email addresses — so on a typical transcript the two built-in rules match nothing at all. The part that carries is your own list:

TAPEBACK_MASK_TERMS=Ivan Petrov,Ivan,Acme Corp,Project Gemini

Matching is case-insensitive and word-bounded (Ann is not masked inside Anna), and the longest term wins, so listing both Ivan Petrov and Ivan is fine. Terms that collide with a transcript label (You, Other, Speaker 1) are refused with a warning — masking those would break speaker attribution and protect nothing.

Matching is literal. A term is masked in exactly the forms you list. In an inflected language every form needs its own entry (Ivan, Ivana, Ivanu) — tapeback does not guess them, because guessing would silently rewrite unrelated words. TAPEBACK_MASK_PII=true is a floor, not a guarantee that the provider sees nothing personal.

Check it before you trust it. --show-masked prints exactly what would be sent and stops there — no request, no API key needed:

tapeback summarize notes.md --show-masked
# stderr: Masked: EMAIL 1, TERM 4
# stdout: the text that would go to the provider

The tally counts distinct values, not occurrences. The payload goes to stdout and the tally to stderr, so --show-masked > sent.txt gives you something to diff.

CLI reference

tapeback start [NAME]              Start recording (Ctrl+C to stop)
tapeback stop                      Stop recording from another terminal
tapeback tray                      System tray icon
tapeback process <FILE> [--name N] Transcribe an existing audio file
tapeback summarize <FILE>          Add LLM summary to transcript
tapeback status                    Show recording status and settings
tapeback start --no-live           # one-shot override: skip live transcription even if TAPEBACK_LIVE=true
tapeback start --no-diarize        # skip speaker identification
tapeback start --no-summarize      # skip LLM summary
tapeback process meeting.mp3 --name "weekly-standup"
tapeback summarize notes.md --provider gemini --model gemini-2.5-pro
tapeback summarize notes.md --show-masked   # print what would be sent, send nothing

Output format

Stereo recordings produce two transcript sections:

  • Transcript — raw Whisper output with channel-based labels (You / Other)
  • Diarized Transcript — speaker-identified output (You / Speaker 1 / Speaker 2 / ...)

Words where Whisper is uncertain (probability < 0.5) are shown in italics.

---
date: 2026-03-23
time: "14:30"
duration: "01:23:45"
language: en
audio: "[[attachments/audio/2026-03-23_14-30-00.wav]]"
tags:
  - meeting
  - transcript
---

## Summary

Brief overview of the meeting.

### Action Items

- [ ] **You:** Send the report by Friday
- [ ] **Speaker 1:** Review the PR

### Key Decisions

- Use PostgreSQL instead of MongoDB

---
# Meeting 2026-03-23 14:30

**Duration:** 1h 23m 45s | **Language:** en

---

## Transcript

[00:00:01] **You:** Hello, let's start with the *backend* changes.

[00:01:23] **Other:** Sure, I have the slides ready.

---

## Diarized Transcript

[00:00:01] **You:** Hello, let's start with the *backend* changes.

[00:01:23] **Speaker 1:** Sure, I have the slides ready.

[00:02:45] **Speaker 1:** Can we move on to the frontend?

Configuration reference

All settings via environment variables (prefix TAPEBACK_) or ~/.config/tapeback/.env file.

Core

Variable Default Description
TAPEBACK_VAULT_PATH ~/tapeback Path to output directory (Obsidian vault)
TAPEBACK_MEETINGS_DIR meetings Subdirectory for meeting notes
TAPEBACK_ATTACHMENTS_DIR attachments/audio Subdirectory for audio files

Transcription

Variable Default Description
TAPEBACK_STT_BACKEND local local (faster-whisper on this machine) or a remote backend id. Today: openai (OpenAI Audio Transcriptions API). Remote STT requires tapeback[stt] (or tapeback[llm], which also installs the openai SDK) and TAPEBACK_STT_API_KEY (falls back to OPENAI_API_KEY, or TAPEBACK_LLM_API_KEY with TAPEBACK_LLM_PROVIDER=openai); meeting audio then leaves the machine
TAPEBACK_STT_MODEL (backend default) Model for the active STT backend. Local default large-v3-turbo (tiny/base/small/medium/large-v3-turbo). OpenAI default whisper-1 (timestamps + local pyannote; size-limited chunks only). Also: gpt-transcribe (modern text quality; local diarize skipped; soft target ≤600s per upload, hard API cap 1500s), gpt-4o-transcribe-diarize (remote speakers; local diarize skipped; soft target ≤600s, hard API cap 1400s). Speaker labels from gpt-4o-transcribe-diarize may not stay consistent across separately uploaded chunks on long recordings. Long meetings are sliced under the 25 MiB size cap, the soft target, and the hard API duration cap. Deprecated alias: TAPEBACK_WHISPER_MODEL
TAPEBACK_STT_API_KEY (empty) API key for remote STT. Falls back to OPENAI_API_KEY then the LLM OpenAI key
TAPEBACK_STT_CONCURRENCY 4 Max parallel uploads when a long recording is sliced (not used for the diarize model, which uploads slices sequentially)
TAPEBACK_STT_TIMEOUT 900 Read/write timeout (seconds) for each remote STT HTTP request. Connect timeout is fixed at 10s. Needs to be long enough for diarize slices (~10 min of audio often needs several minutes of server time). App-level retries are separate from the OpenAI SDK (SDK retries are disabled).
TAPEBACK_STT_MAX_RETRIES 5 App-level retries per chunk after a timeout, connection error, or HTTP 408/429/5xx/529
TAPEBACK_STT_RETRY_BASE_DELAY 5 Exponential backoff base between remote STT retries (capped at 60s)
TAPEBACK_STT_HEARTBEAT_SECONDS 15 While an upload is in flight, emit a status line this often so a long API wait does not look hung
TAPEBACK_STT_FFMPEG_TIMEOUT 120 Wall-clock timeout for ffmpeg encode/slice used to prepare remote uploads
TAPEBACK_LANGUAGE auto Language code (auto for auto-detection, or en, ru, fr, etc.)
TAPEBACK_DEVICE cuda cuda or cpu. Local backend only
TAPEBACK_GPU_TELEMETRY true Sample GPU clocks/temperature during transcription and print a one-line summary per stage. Observation only — tapeback never changes clock or power caps. No-op without nvidia-smi or on cpu
TAPEBACK_RESUME_CACHE true Reuse a channel already transcribed from the same audio with the same output-affecting settings, so an interrupted run does not redo finished work. Changing the model, glossary, language or any decoding setting invalidates it
TAPEBACK_RESUME_CACHE_DIR (XDG) Where reusable channel results go. Default ~/.local/share/tapeback/resume
TAPEBACK_ISOLATE_TRANSCRIPTION true Run transcription in a child process. A CUDA out-of-memory permanently leaks VRAM inside the process it happens in — a child can simply exit and the kernel reclaims it. Costs one process start and model load per run; set false to transcribe in-process
TAPEBACK_MIN_FREE_VRAM_MIB 1200 Use the CPU rather than attempting a CUDA load below this much free VRAM. The smallest measured configuration needs ~1115 MiB
TAPEBACK_THERMAL_CLAMP_CHECK true Look for a GPU thermal clamp before each transcription stage. One nvidia-smi query, retaken per stage — which is what lets a run return to the GPU once the card recovers. See "Transcription is suddenly very slow" below
TAPEBACK_THERMAL_CLAMP_WAIT 0 Seconds to wait for the clamp to release before giving up on the GPU for this stage. Default is not to wait: the clamp clears on system idle and the shortest release measured was 451 s, so any tolerable wait rarely succeeds while the CPU would already be transcribing. Raise it only if the machine will genuinely be idle
TAPEBACK_THERMAL_CLAMP_CPU_FALLBACK true Transcribe on CPU when the clamp has not released. A clamped GPU measured ~8× slower than the CPU, so falling back is the fast path, not a degradation
TAPEBACK_STAGE_PAUSE_SECONDS 0 Idle gap after each transcription stage, to shed heat instead of driving the chassis into a clamp. Try 60 on a laptop that clamps during long recordings
TAPEBACK_RUN_LOG true Write one JSON record per run (settings used, every status line, outcome) so a failed or interrupted run can be diagnosed afterwards. Never contains credentials
TAPEBACK_RUN_LOG_DIR (XDG) Where run records go. Default ~/.local/share/tapeback/runs (honours XDG_DATA_HOME). Oldest records are pruned past 200
TAPEBACK_COMPUTE_TYPE auto auto, int8_float16, float16, int8, or float32. auto → int8_float16 on CUDA, int8 on CPU. int8_float16 is both faster and smaller than float16 — measured on a GTX 1650 Ti with large-v3-turbo: 14.16× vs 3.90× real time, 1115 MiB vs 2139 MiB, with no quality cost. ctranslate2 falls back on its own if your GPU lacks the type
TAPEBACK_BEAM_SIZE 4 Whisper beam search width (lower = faster, slightly less accurate)
TAPEBACK_TEMPERATURE [0.0, 0.2, 0.4, 0.6, 0.8, 1.0] Temperature fallback ladder. The high steps break Whisper out of hallucination loops on noisy audio — don't shorten unless your input is clean (shortening can cause repeat loops: slower and worse)
TAPEBACK_BATCH_SIZE 0 Batched inference (faster-whisper BatchedInferencePipeline) — processes VAD segments in parallel, several× faster on GPU. 0 = off; try 8. On small GPUs (≤4 GB) it may OOM even at 4 — use 2, pair with TAPEBACK_COMPUTE_TYPE=int8, or keep 0. OOM falls back to CPU automatically. Batching silently ignores no_speech_threshold, condition_on_previous_text and all but the first temperature value — tapeback warns which ones when you enable it
TAPEBACK_CHUNK_LENGTH 30 Seconds of audio consumed per decode window. Do not lower this to fight hallucinations. Whisper's encoder is fixed at 30 s and every window is zero-padded back to it, so a smaller value does not make a pass cheaper — it only needs more of them. Measured on a 145 s file: 2 → 390.6 s, 7 → 116.8 s, 30 → 41.4 s
TAPEBACK_NO_SPEECH_THRESHOLD 0.4 Whisper silence-rejection threshold (lower = more aggressive; suppresses training-data hallucinations on pauses)
TAPEBACK_LANGUAGE_DETECTION_SEGMENTS 1 Segments probed before deciding the language; raise (e.g. 4) if a channel that starts silent gets the wrong language
TAPEBACK_MULTILINGUAL false Per-segment language detection for mixed-language recordings (code-switching). Less stable than a fixed TAPEBACK_LANGUAGE
TAPEBACK_HALLUCINATION_SILENCE_THRESHOLD (off) Seconds; skip silent gaps when a hallucination is detected. ⚠ Triggers per-segment re-processing — can be much slower on pause-heavy channels (e.g. the mic). Leave off unless it measurably helps your audio
TAPEBACK_HOTWORDS (project glossary) Comma-separated terms decoding is biased towards — see src/tapeback/glossary.py. Whisper mangles English terms embedded in Russian speech ("tapeback" → "ты пупа ты бэк"); with the glossary it comes back as "tapeback". Replace with your own domain vocabulary; empty disables the bias. Keep it under ~223 tokens (~670 characters) — faster-whisper silently truncates beyond that, so extra terms stop working with no warning
TAPEBACK_VAD_FILTER true Silero voice-activity detection before decoding. Load-bearing: it is a large part of why Whisper stopped hallucinating on silence. Turning it off is not a speed optimisation
TAPEBACK_CONDITION_ON_PREVIOUS_TEXT false Feed the previous window's output back as a prompt. Whisper's own default is true; off here because it makes repeat loops much stickier
TAPEBACK_PAUSE_THRESHOLD 1.0 Seconds; split segments on silence gaps >= this
TAPEBACK_GATE_MIC_SILENCE true Silence the mic channel where you're only listening (mic quiet / monitor dominant) before transcription, so Whisper doesn't loop on the pauses. Dual-channel pipeline only

Live transcription

Variable Default Description
TAPEBACK_LIVE false Enable live transcription during recording (opt-in; competes with the post-recording pipeline for GPU memory on small cards)
TAPEBACK_LIVE_INTERVAL 60 Seconds between transcription cycles
TAPEBACK_LIVE_OVERLAP 2.0 Seconds of overlap between chunks
TAPEBACK_LIVE_MIN_CHUNK 5.0 Minimum new audio (seconds) to trigger transcription

Audio

Variable Default Description
TAPEBACK_MONITOR_SOURCE auto PulseAudio monitor source name
TAPEBACK_MIC_SOURCE auto PulseAudio mic source name
TAPEBACK_SAMPLE_RATE 48000 Recording sample rate
TAPEBACK_SESSIONS_DIR (XDG) Where recordings in progress live, about 1.6 GB per hour until processed. Default ~/.local/state/tapeback/sessions. An absolute path: ~ is not expanded. Must be private to you (mode 0700); tapeback refuses one other users can reach. A session that failed to process stays here, so it can be recovered
TAPEBACK_TIMEZONE (system) Time zone for naming a recording, and so for the note's file name, date and time. An IANA name such as Europe/Madrid, or UTC. Unset: the machine's own zone

Speaker diarization

Variable Default Description
TAPEBACK_DIARIZE true Enable speaker diarization
TAPEBACK_HF_TOKEN (empty) HuggingFace token (setup)
TAPEBACK_MAX_SPEAKERS (auto) Maximum number of speakers
TAPEBACK_CLUSTERING_THRESHOLD (pyannote default) Speaker-clustering threshold passed to pyannote. Lower splits speakers more readily, higher merges them
TAPEBACK_SPECTRAL_MERGE_THRESHOLD 0.96 Spectral speaker merging (0 = off; lower merges more aggressively)

LLM summarization

Variable Default Description
TAPEBACK_SUMMARIZE true Enable LLM summarization
TAPEBACK_MASK_PII false Mask emails and phone numbers before sending (details)
TAPEBACK_MASK_TERMS (empty) Comma-separated names/terms to mask too (needs MASK_PII)
TAPEBACK_LLM_PROVIDER anthropic Primary provider (list)
TAPEBACK_LLM_API_KEY (empty) API key (or use provider-specific env var)
TAPEBACK_LLM_MODEL (provider default) Override model name

Troubleshooting

Reading the processing output

Processing reports what it is doing, how long each stage took, and where Whisper actually ran:

Stage 'load channels' took 0.7s
Splitting channels...
Stage 'split' took 82.0s
Stage 'gate mic' took 2.3s
Transcribing (this may take a few minutes)...
Stage 'load model' took 2.3s
Whisper: large-v3-turbo in an isolated worker
Whisper: large-v3-turbo on cuda/int8_float16
  transcribe monitor: 47% (14:58 / 31:40)
  transcribe monitor: 100% (31:36 / 31:40)
Stage 'transcribe monitor' took 322.1s
Pausing 60s to let the GPU cool...
Stage 'transcribe mic' took 106.9s
GPU: sm 1035 MHz avg / 300 min, max 87°C, 1436 MiB peak, throttled 56% of 78 samples

The monitor channel is transcribed first so its detected language can be reused for the microphone, which is mostly silence while you are listening and guesses badly on its own.

Lines worth watching:

  • Whisper: <model> on <device>/<compute type> — printed by the worker once it has resolved where it will actually run. If it says cpu/int8 when you expect cuda, look just above it for the reason: a thermal clamp, too little free VRAM, or the CUDA 13 library problem below. With batching enabled the line also shows batch_size=N.
  • transcribe monitor: NN% — progress through the audio, printed every 10 seconds. If the percentage stops advancing, the model is stuck in a repeat loop on that channel rather than working.
  • GPU: sm … throttled NN% — a high throttled share together with a low minimum clock means the card was held back by heat or its power limit, not by the model. This line is a report of what already happened; the decision is made before each stage, and a card found clamped is skipped in favour of the CPU. Disable the reporting with TAPEBACK_GPU_TELEMETRY=false.

A run failed or you interrupted it — what happened?

Every run writes a JSON record to ~/.local/share/tapeback/runs/:

ls -t ~/.local/share/tapeback/runs/ | head
jq '{outcome, error, config: .config.chunk_length}' ~/.local/share/tapeback/runs/<file>.json

It holds the settings the run actually used, every status line it printed, and how it ended (completed / aborted / failed, with the error for the last one). Useful when a transcript looks wrong and you need to know which configuration produced it, or when a run died and the terminal is long gone. Credentials are never recorded — the stored settings are an explicit allow-list. Disable with TAPEBACK_RUN_LOG=false.

Transcription is suddenly very slow, and the GPU is not even hot

On laptops where the CPU and GPU share one heatsink, the embedded controller responds to a hot system by starving the GPU. Measured on a GTX 1650 Ti Mobile during a long batch:

enforced.power.limit  5 W        (factory default: 50 W)
clocks.sm             300 MHz    (maximum: 2100 MHz)
GPU temperature       74 °C      (its own target is 87 °C — the GPU is fine)
CPU package           93 °C      (this is what the controller is reacting to)

The GPU is not overheating; it is being cut to a tenth of its power budget to protect a shared cooler. Three things follow.

It clears on system idle, not on the GPU cooling down. Measured: ~450 s of idle after moderate heating, still latched after 900 s once the chassis was saturated, and still latched an hour later with the GPU down to 72 °C while a browser kept the CPU busy. If you are using the machine, it will not release — so waiting for it is not a strategy, and TAPEBACK_THERMAL_CLAMP_WAIT defaults to 0.

The CPU is faster in this state — 2.39× real time against 0.31× on the clamped GPU. tapeback checks before each stage and moves to the CPU, saying so. Disable with TAPEBACK_THERMAL_CLAMP_CPU_FALLBACK=false.

A run that fell back is not stuck there. The check is retaken for every stage, each of which runs in its own worker process, so a clamp that clears between channels puts the next one back on the GPU.

You can tell a clamped card from a merely busy one without any load: a healthy GPU reports only GpuIdle when idle, a clamped one still reports a thermal reason.

nvidia-smi --query-gpu=clocks_event_reasons.active,enforced.power.limit --format=csv,noheader
# 0x0000000000000001, 50.00 W   -> healthy
# 0x0000000000000024,  5.00 W   -> clamped

To avoid it rather than react to it, set TAPEBACK_STAGE_PAUSE_SECONDS=60 so long recordings shed heat between channels. The larger lever is the CPU, not the GPU — these machines often ship with a sustained CPU power limit well above the chip's class, and capping it (or disabling turbo) keeps the whole system out of the clamp. That needs root, so tapeback does not do it for you.

GPU transcription falls back to CPU on CUDA 13 systems

If the log shows Warning: CUDA runtime error, falling back to CPU: Library libcublas.so.12 is not found, your system has CUDA 13 (e.g. recent Arch) but faster-whisper's ctranslate2 backend is built against CUDA 12 and can't find libcublas.so.12 / libcudnn.so.9. Diarization (PyTorch) still uses the GPU, but transcription drops to slow CPU.

Install the CUDA 12 runtime libraries — tapeback preloads them automatically:

# Arch (AUR) — recommended:
yay -S tapeback-cuda

# Ubuntu/Debian (.deb): install the matching tapeback-cuda_*.deb

# pip / uv install (your own environment):
uv pip install nvidia-cublas-cu12 nvidia-cudnn-cu12

# AUR/.deb done manually (bundled venv): for Arch use /opt/tapeback/bin/pip,
# for .deb use /opt/tapeback/venv/bin/pip
sudo /opt/tapeback/bin/pip install nvidia-cublas-cu12 nvidia-cudnn-cu12

They install alongside the CUDA 13 libraries without conflict. No LD_LIBRARY_PATH needed: when TAPEBACK_DEVICE=cuda (the default), tapeback finds and preloads them on startup. If they're not installed, transcription falls back to CPU with the message above.

Wrong language or repeated/garbled text on the "You" channel

If the note's language: is wrong (e.g. ja for an English meeting) or the You channel is full of repeats (Do you hear me? Do you hear me?…) or foreign script, the mic channel started with silence while you were listening, and Whisper guessed the language from that silence and hallucinated.

Fixes, in order of reliability:

  • Pin the language if you know it: TAPEBACK_LANGUAGE=ru (or en, …). English terms inside another language still transcribe fine.
  • Probe more speech before deciding: TAPEBACK_LANGUAGE_DETECTION_SEGMENTS=4.
  • Mixed-language meetings (real code-switching): TAPEBACK_MULTILINGUAL=true.
  • Suppress silence hallucinations: TAPEBACK_HALLUCINATION_SILENCE_THRESHOLD=2.0.

Uninstall

# Arch Linux
yay -R tapeback tapeback-tray tapeback-diarize tapeback-llm tapeback-cuda

# pip / uv
uv tool uninstall tapeback

# Remove cached ML models (~2-5 GB)
# Skip if you use HuggingFace for other projects
rm -rf ~/.cache/huggingface/

Roadmap

  • Speaker profiles: learn and remember recurring speakers across meetings
  • Multi-language meetings: detect and handle language switches mid-meeting
  • Windows support: WASAPI loopback capture

Support

If you find tapeback useful, consider a small donation:

USDT (TRC-20) ADA (Cardano)
TAECw9FebnoSN2n3H2Fk9Bv5aA8fwpCuBB addr1q9tqg2g8wxpxawsrvea84lms3ampuda0ygzawuxq77sxwr48mxj2vq2rzd4nsmhpdhy6lftp30tz78tetzr29mtvkqmsskrmp7

License

Apache-2.0. See LICENSE.

Metadata

Release files for tapeback 0.9.10

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tapeback 0.9.10
File Size Uploaded
tapeback-0.9.10.tar.gz 712.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tapeback 0.9.10
File Interpreter ABI Platform
tapeback-0.9.10-py3-none-any.whl Python 3 none any Details

Total release size: 837.9 kB

Release files / tapeback-0.9.10.tar.gz

Download URL tapeback-0.9.10.tar.gz
Size 712.7 kB
Tags Source
SHA-256 checksum
How to use checksums
3804991bb6b5ec625e2b856484b7bc0531f4be5a4a9927df4f6c4dabe785f531
BLAKE2b-256 checksum
How to use checksums
8f81929ee4b1bf04bfd396bcded474fb56933b5ae62d86046c4bf2864a05e14c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / tapeback-0.9.10-py3-none-any.whl

Download URL tapeback-0.9.10-py3-none-any.whl
Size 125.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6abe57dbc54ab86603c71c59d1fc01ee2a7245d51d9d690b0d17ab6b79efc088
BLAKE2b-256 checksum
How to use checksums
df984e2d8c5bd636ed0730eca7763017d0c203369a95cbd0886020ce64bf6f98
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release history Release notifications | RSS feed

0.10.0

2 release files

This release

0.9.10 This release

2 release files

0.9.9

2 release files

0.9.8

2 release files

0.9.7

2 release files

0.9.6

2 release files

0.9.5

2 release files

0.9.4

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.9

2 release files

0.8.8

2 release files

0.8.7

2 release files

0.8.6

2 release files

0.8.5

2 release files

0.8.4

2 release files

0.8.3

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page