Metadata-Version: 2.4
Name: pretty-listener
Version: 0.1.1
Summary: Desktop speech listener for microphone and system audio.
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: PySide6>=6.6
Requires-Dist: PyJWT[crypto]<3,>=2.10
Requires-Dist: faster-whisper>=1.1
Requires-Dist: hf-xet>=1.1
Requires-Dist: huggingface_hub>=0.25
Requires-Dist: httpx[socks]>=0.27
Requires-Dist: keyring<27,>=25.6
Requires-Dist: llama-cpp-python>=0.3
Requires-Dist: numpy>=2.0
Requires-Dist: onnxruntime>=1.23
Requires-Dist: python-docx>=1.1
Requires-Dist: reportlab>=4
Requires-Dist: socksio>=1.0
Requires-Dist: webrtcvad>=2.0.10
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Provides-Extra: windows
Requires-Dist: PyAudioWPatch>=0.2.12.8; extra == "windows"
Provides-Extra: macos
Requires-Dist: soundcard>=0.4.6; extra == "macos"
Requires-Dist: pyobjc-framework-AVFoundation>=10.3; extra == "macos"
Requires-Dist: pyobjc-framework-CoreAudio>=10.3; extra == "macos"
Requires-Dist: pyobjc-framework-CoreMedia>=10.3; extra == "macos"
Requires-Dist: pyobjc-framework-ScreenCaptureKit>=10.3; extra == "macos"
Provides-Extra: build
Requires-Dist: dulwich>=0.22; extra == "build"
Requires-Dist: pillow>=10.4; extra == "build"
Requires-Dist: pyinstaller>=6.11; extra == "build"
Requires-Dist: pyinstaller-hooks-contrib>=2025.0; extra == "build"

# Pretty Listener

<p align="center">
  <img src="packaging/assets/pretty-listener-256.png" width="128" height="128" alt="Pretty Listener logo">
</p>

Pretty Listener is a desktop app that listens to microphone input and system output in parallel, detects speech segments, recognizes them locally with Whisper, stores transcript rows in SQLite, and displays the conversation as a chat. Linux uses PulseAudio/PipeWire through the PulseAudio interface; Windows 10/11 uses WASAPI capture; macOS 13+ uses CoreAudio for microphones and ScreenCaptureKit for system audio.

Local processing is the default and works without an account or network connection.
When Pretty Listener Cloud is configured, the account button opens OIDC Authorization
Code + PKCE login in the system browser. The refresh token is stored only in the OS
credential vault; if no secure vault is available, the login lasts for the current app
process only. Cloud mode uploads audio batches for transcription and sends the complete,
untruncated dialog transcript for summary generation. Quota, authentication, provider,
and sustained network failures offer an explicit switch back to local processing.

For local development, configure the backend and OIDC issuer before launching the app:

```bash
PRETTY_LISTENER_API_BASE_URL=http://127.0.0.1:8000 \
PRETTY_LISTENER_OIDC_ISSUER=http://localhost:8080/realms/pretty-listener \
PRETTY_LISTENER_OIDC_CLIENT_ID=pretty-listener-desktop \
pretty-listener
```

`http://` is accepted only for loopback hosts. A non-loopback deployment must use HTTPS;
`PRETTY_LISTENER_ALLOW_INSECURE_CLOUD_HTTP=1` is a development-only override. Native
release builders require production HTTPS endpoints and embed them as public deployment
metadata in `cloud-config.json`; installed users do not need launch-environment variables.
The file contains no token, client secret, API key, or other credential. Source builds
may override its fields with environment variables. Frozen signed builds reject endpoint
overrides unless `PRETTY_LISTENER_ALLOW_FROZEN_CLOUD_CONFIG_OVERRIDE=1` is set explicitly
for controlled diagnostics; even then, endpoints must remain HTTPS and
`PRETTY_LISTENER_ALLOW_INSECURE_CLOUD_HTTP` is ignored.
`PRETTY_LISTENER_CLOUD_TIMEOUT_SECONDS` optionally changes the API timeout (30 seconds by
default). Summary requests use a separate 180-second timeout, configurable with
`PRETTY_LISTENER_CLOUD_SUMMARY_TIMEOUT_SECONDS`. Browser sign-in itself uses a temporary
random loopback callback and can wait up to three minutes. The callback listener binds
only to `127.0.0.1`; the Keycloak desktop client must allow `http://127.0.0.1:*`. Pretty
Listener does not register or accept a custom OS URL protocol on Windows, Linux, or macOS.

## What MVP Does

- Captures one OS-default source of each live role:
  - `self`: microphone/input sources
  - `remote`: output monitor/loopback sources
- Watches audio device changes and starts/stops capture workers while listening.
- Splits each stream independently with local Silero neural VAD; WebRTC VAD is kept as a fallback.
- Ends a live utterance after 1.5 seconds of silence.
- Shows two activity meters under the toolbar:
  - blue: aggregated audio inputs
  - red: aggregated audio outputs
- Shows a separate `Обработка` progress meter for file import and live batch processing.
- Transcribes live utterances with persistent `faster-whisper` Turbo on CPU/int8 by
  default; imported files have a separate model choice and default to Parakeet.
- Stores recognized text, timestamps, and the audio device name in SQLite.
- Stores separate call transcripts as named dialogs shown in the left sidebar; dialogs
  can be created, renamed, and permanently deleted.
- Imports media files or folders with `Распознать`, extracts their audio track with
  `ffmpeg`, and saves the recognized speech into dialogs.
- Splits remote/output speech into speaker turns when local voiceprints show a speaker
  change without waiting for the 1.5-second endpoint; timed words are assigned to
  those turns after one decode of the completed live batch.
- Adds local speaker labels for remote/output speech and stores lightweight voiceprints.
- Stores remote utterance voiceprints even when a phrase is too short or uncertain to
  receive a speaker label immediately, so later rebuild/repair can reuse that evidence.
- Shows remote speech on the left and self speech on the right.
- Uses the operating-system interface language by default; Russian, English, German,
  French, Italian, Spanish, and Portuguese can also be selected explicitly under
  `Settings -> Interface` and apply after restart. New user-facing features must add
  strings to every language catalog; the test suite checks catalog parity.
- Lets you rename, merge, or remove detected remote speaker labels in the
  `Собеседники` tab.
- Prepares structured Russian dialog summaries in the `Саммери` tab through a
  local embedded GGUF LLM backend and stores version history. Ollama remains
  available as an optional backend.

## Linux Prerequisites

Install Python tooling, PulseAudio utilities, and FFmpeg if they are missing:

```bash
sudo apt update
sudo apt install python3-venv python3-pip pulseaudio-utils ffmpeg libxcb-cursor0
```

If PySide6/Qt fails with `Could not load the Qt platform plugin "xcb"`, install
`libxcb-cursor0` and restart the app.

Dialog summaries work without Ollama by default. Pretty Listener downloads GGUF
models into:

```text
~/.local/share/pretty_listener/models/llm/
```

Model downloads use `huggingface_hub` with `hf_xet` high-performance mode first.
If Xet fails or makes no byte progress for 120 seconds, Pretty Listener switches to
parallel HTTP Range downloads. Completed 16 MB blocks and `.part.json` metadata are
kept, so Stop, a network failure, or an app restart does not discard completed work.
See the Hugging Face documentation for
[`hf_xet` environment variables](https://huggingface.co/docs/huggingface_hub/en/package_reference/environment_variables)
and the [download API](https://huggingface.co/docs/huggingface_hub/guides/download).

For higher Hugging Face request limits, you may set a local token before launching the
app. A token does not guarantee higher CDN bandwidth. Do not paste tokens into source
code or commits:

```bash
export HF_TOKEN=hf_your_token_here
pretty-listener
```

`HUGGINGFACE_HUB_TOKEN` is also supported. The token is passed to Hugging Face download
APIs for both STT and summary models and is never printed in logs.
If the default Hugging Face endpoint is slow or unreliable, you can point the app to a
compatible mirror/endpoint before launch:

```bash
PRETTY_LISTENER_HF_ENDPOINT=https://your-hf-endpoint.example pretty-listener
```

This sets `HF_ENDPOINT` for the process. The default download settings are `Авто` and
`Максимум скорости`: before a large transfer the app compares a 2 MB range through the
system proxy and directly, then uses the faster route. A difference below 10% keeps the
system proxy. The selected route is cached for 30 minutes and is checked again after
repeated network errors.

You can change the route and parallelism under `Настройки -> Загрузка моделей`.
`Максимум скорости` uses up to 8 HTTP ranges and 32 Xet ranges; `Баланс` and
`Стабильный` reduce concurrency. To force direct mode from the environment:

```bash
PRETTY_LISTENER_HF_DOWNLOAD_MODE=direct pretty-listener
```

During a model download the chat, search, and export remain available. `Stop` cancels
the current transfer without deleting partial data; starting it again shows and reuses
the saved progress. Only one model is downloaded at a time.

Ollama can still be used as an optional summary backend:

```bash
curl -fsSL https://ollama.com/install.sh | sh
ollama serve
ollama pull qwen3:4b
```

You can also pull any other Ollama tag from the summary catalog manually, then select
the Ollama backend in settings. For example:

```bash
ollama pull qwen3:8b-q4_K_M
ollama pull gemma3:4b-it-q4_K_M
ollama pull phi4-mini:3.8b-q4_K_M
```

Create a virtual environment and install the app:

```bash
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e .
```

## Windows Prerequisites

Use Windows 10/11, Python 3.10+, and install FFmpeg so `ffmpeg.exe` and
`ffprobe.exe` are available in `PATH` for media import.

Create a virtual environment and install the app with the Windows audio extra:

```powershell
py -3 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -e ".[windows]"
```

Pretty Listener records Windows microphones and system output through WASAPI.
If microphone capture does not start, check Windows privacy settings and allow
desktop apps to access the microphone. App data, logs, models, and SQLite are
stored under:

```text
%LOCALAPPDATA%\Pretty Listener\
```

## macOS Prerequisites

Use macOS 13 or newer and Python 3.10+ for development. Install the native audio
dependencies with the macOS extra:

```bash
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e ".[macos]"
```

On first live capture, allow Pretty Listener under `System Settings -> Privacy &
Security -> Microphone` and `Screen & System Audio Recording`. Microphones continue
to work if system-audio permission is denied. App data is stored under:

```text
~/Library/Application Support/Pretty Listener/
```

Run it:

```bash
pretty-listener
```

The first transcription can take a while while the selected local STT backend and
model are downloaded and verified.
The neural VAD model is bundled with `faster-whisper` and runs locally through ONNX Runtime.
The STT catalog is ordered as `tiny`, `base`, `small`, `medium`, `turbo`,
`Parakeet TDT v3 Q8`, `large`. Parakeet uses the separate `nemo-speech` backend
and is selected by default for imported files; existing saved model choices are
preserved. Live capture has a separate model setting and defaults to persistent
Whisper `turbo`. The current Parakeet TDT checkpoint is offline-only, so it is not
offered in the live-model selector.
Before a selected model is loaded, Pretty Listener shows a confirmation with the
approximate download size and memory use. The processing area shows engine (`XET` or
`HTTP`), route (`proxy` or `direct`), downloaded MB, speed, ETA, retries, and resumed
bytes. Loading an already downloaded model into memory does not expose a reliable
percent, so that stage is shown with elapsed time only.
Pretty Listener automatically prefers a CTranslate2-compatible CUDA GPU when one
is available; otherwise it falls back to `CPU · int8`. AMD/ROCm acceleration is
not enabled in this build, so AMD/Intel GPUs are diagnostic-only and use CPU
fallback.
`Auto` ranks multiple NVIDIA cards by currently free VRAM and uses a supported
memory-saving int8 CUDA type when the float16 estimate does not fit. It chooses CPU
when even that conservative estimate cannot fit. An explicit CUDA selection never
silently retries the same model on CPU: driver/runtime/load failures are reported.
Parakeet remains CPU-only for imported files and no longer resets the separate CUDA
preference used by live Whisper.
Linux and Windows packages require a host NVIDIA driver and CUDA 12 runtime with
cuBLAS 12 for that transcription path; CUDA libraries are not bundled. The pinned
CTranslate2 wheel is built without cuDNN, so this exact release does not require it.
Default Linux/Windows artifacts keep embedded GGUF summaries on CPU. Separate
`nvidia` artifacts contain a reviewed CUDA-enabled llama.cpp wheel and advertise that
support through their build capability. See
[`packaging/NVIDIA_RUNTIME.md`](packaging/NVIDIA_RUNTIME.md) for the exact runtime,
fallback, self-test, and release-flavor contract.
Use the `⚙` settings button in the top-right toolbar to choose the transcription
model, compute device (`Авто`, `CPU`, or a CUDA GPU), and the worker-process limit.
Those settings are saved between app launches; `Авто` keeps the CUDA-first resolver.
The same settings window has a separate `AI / Саммери` zone with two tabs. `Модели`
uses the embedded/local GGUF path and stays fully local. `API` uses an
OpenAI-compatible Chat Completions provider such as OpenAI/ChatGPT, DeepSeek, Grok/xAI,
OpenRouter, or a custom base URL. The active tab is the active summary backend.
API summaries send the selected dialog transcript, including timestamps and speaker
names, to the external provider. API keys are saved locally in `settings.json` and are
masked in the UI, but they are not encrypted.
If Ollama is installed or its local service is already responding, the settings window
also shows optional Ollama controls; otherwise Ollama is hidden and old saved Ollama
settings fall back to embedded GGUF. STT and GGUF use the same resumable model download
service. Exact GGUF filenames are downloaded directly; for the other catalog entries,
file discovery and transfer happen in the spawned download process using the selected
network route.
If you download a GGUF model yourself, use `Локальный GGUF` in the same settings zone
and select the `.gguf` file wherever it is stored. Pretty Listener saves only the file
path and does not copy the model into its own cache; if the file is moved, choose it
again. Keep the selected catalog model close to the external file size/family so the
context and memory estimates remain reasonable.
Next to the AI model selector, `Открыть GGUF` opens a direct GGUF download URL when
Pretty Listener knows a stable filename, otherwise it opens the model repository page.
`Копировать URL` copies the same URL. For pinned models such as Qwen3 4B and Gemma 3
4B, the direct link includes the exact repository revision and GGUF filename.
Summary models are listed by increasing resource cost with parameter counts, from
`Phi-4-mini 3.8B` and `Qwen3 4B` up to `Gemma 3 12B` and `Qwen3 14B`.
If a selected LLM looks too heavy for the detected RAM/VRAM, Pretty Listener warns
before applying the setting; the warning is not a hard block.
When you change the model in settings, Pretty Listener can ask whether to download and
prepare it immediately. If you skip that step, or if the download fails, the same
download/load preflight runs before `Start` or `Распознать`.
If your environment uses a SOCKS proxy, Pretty Listener normalizes proxy URLs such as
`socks://127.0.0.1:12334/` to `socks5://127.0.0.1:12334/` before HuggingFace downloads.
Text transcription runs in worker processes. In settings, `Процессы STT` is the
target maximum. Short and medium recordings use one whole-file transcription job.
Long recordings and the low-memory fallback start with one active turn job and then
ramp up one worker at a time until they reach that target. If a whole-file job runs out
of memory, Pretty Listener restarts the pool and retries the file as bounded turns. If
a turn pool is killed, it rolls back to the last stable worker count and requeues only
unsaved turns. Limit the target with:

```bash
PRETTY_LISTENER_TRANSCRIPTION_WORKERS=2 pretty-listener
```

For experiments only, you can disable the soft RAM warning and legacy safe
recommendation path:

```bash
PRETTY_LISTENER_ALLOW_UNSAFE_WORKERS=1 pretty-listener
```

## Development Checks

Run development commands from the `desktop` directory. The core unit tests avoid
GUI and Whisper model downloads:

```bash
python -m unittest discover -s tests
```

The executable entrypoint also exposes package diagnostics that never downloads a
model:

```bash
pretty-listener --version
pretty-listener --self-test --data-dir /tmp/pretty-listener-self-test
```

`benchmark_ami.py` runs the desktop analysis, storage, transcription, WER/cpWER, and
DER path against AMI audio with official word XML and an RTTM reference:

```bash
PRETTY_LISTENER_TRANSCRIPTION_WORKERS=1 \
python benchmark_ami.py \
  --wav /path/to/ES2004a_clip.wav \
  --ami-words-dir /path/to/ami/words \
  --rttm /path/to/ES2004a.rttm \
  --meeting-id ES2004a \
  --reference-offset 779.79 \
  --model turbo --device cpu --compute-type int8 --workers 1 \
  --json-out /tmp/pretty-listener-ami.json
```

## Production Releases

Release payloads use Python 3.11 and PyInstaller. PyInstaller is not a cross-compiler,
so each build must run natively on its target OS. Windows Authenticode signing and
RFC 3161 timestamp verification, plus macOS Developer ID signing, notarization, and
stapling, are implemented as mandatory fail-closed build steps. Public builds therefore
need the external certificate/private-key and Apple notary credentials documented in
[`packaging/RELEASE_CHECKLIST.md`](packaging/RELEASE_CHECKLIST.md).

The tracked icon master is used for the Qt window, taskbar/dock, README, installers,
desktop entry, and application bundles. To refresh its derived formats after replacing
the master, run:

```bash
python packaging/prepare_icons.py \
  --source packaging/assets/pretty-listener-1024.png \
  --destination packaging/assets
```

Release builders generate their own icon copies under `.build/` and do not modify the
tracked assets. Register the reviewed FFmpeg inputs as documented in
`packaging/vendor/README.md`; native builders then fetch the exact archive automatically.
The manifest binds archive size/hash, FFmpeg source revision/archive, build-recipe
revision/archive, build configuration, and every extracted executable. A release build
deliberately stops if any value or extracted byte does not match. Mutable
`latest`/nightly/snapshot URLs, GPL/nonfree FFmpeg configurations, missing Parakeet
license notices, and any blocked platform target are rejected before container or
virtual-environment setup. Check every native platform input explicitly with:

```bash
python3 packaging/release_tools.py check-release-inputs
```

The Linux-only builder validates only the Linux FFmpeg and linuxdeploy targets. It
omits the optional Parakeet native runtime while that target is unregistered and
records `bundled_parakeet_runtime=false`; Whisper remains available. Set
`PRETTY_LISTENER_INCLUDE_PARAKEET_RUNTIME=1` only after registering the reviewed
Linux runtime. That opt-in remains fail-closed.

Models are never bundled.

Every native build also needs the deployment's public cloud values. Put the actual values
in the release CI/terminal environment; do not commit deployment credentials or substitute
placeholder domains:

```bash
export PRETTY_LISTENER_RELEASE_API_BASE_URL="$PRODUCTION_API_BASE_URL"
export PRETTY_LISTENER_RELEASE_OIDC_ISSUER="$PRODUCTION_OIDC_ISSUER"
export PRETTY_LISTENER_RELEASE_OIDC_CLIENT_ID="pretty-listener-desktop"
```

PowerShell uses the same three environment-variable names. Builders validate HTTPS,
reject credentials, query strings, loopback and reserved placeholder hosts, then generate
this exact public schema and bundle it at the application resource root:

```json
{
  "schema_version": 1,
  "api_base_url": "<production HTTPS API endpoint>",
  "oidc_issuer": "<production HTTPS issuer>",
  "oidc_client_id": "pretty-listener-desktop"
}
```

Packaged `--self-test` reads the embedded resource. The finalizer rejects missing,
invalid, or different cloud metadata across native artifacts and records the agreed
public values in `release-manifest.json`.

Run the native builders from a clean working tree:

```powershell
# Windows x64, from PowerShell with Python 3.11 and Inno Setup 6
packaging\build-windows.ps1
```

```bash
# Linux x86_64; Docker or rootless Buildah uses a digest-pinned Ubuntu 22.04 image
packaging/build-linux.sh

# Optional NVIDIA summary flavor; requires the verified vendor wheel
PRETTY_LISTENER_SUMMARY_ACCELERATOR=cuda packaging/build-linux.sh

# macOS: run on each native architecture
packaging/build-macos.sh arm64
packaging/build-macos.sh x86_64
```

On Windows, pass `-Nvidia` to `packaging\build-windows.ps1` for the matching
optional flavor. The CUDA llama.cpp wheels, checksums, host runtime requirements, and
large artifact-size tradeoff are documented in
[`packaging/NVIDIA_RUNTIME.md`](packaging/NVIDIA_RUNTIME.md).

The Linux AppImage builder automatically fetches and verifies the immutable
`linuxdeploy` input described in `packaging/vendor/README.md`. Each builder runs the
complete test suite,
builds the platform payload, and executes packaged `--self-test` from the finished
setup/deb/AppImage/dmg or standalone executable. Linux package tests run Qt through
the real `xcb` plugin under Xvfb. Artifacts and visible `self-test-*.json` reports are
placed in `dist/release/<version>/`.

After copying all native artifacts and `self-test-*.json` reports into the same release
directory, finalize it:

```bash
packaging/finalize-release.sh
```

The finalizer requires all six artifacts, six successful frozen self-test reports, and
three verified native signing reports (Windows plus both macOS architectures),
then writes `SHA256SUMS` and `release-manifest.json`. The manifest also records the
verified FFmpeg and Parakeet binary/source URLs and hashes, the bundled cloud deployment
metadata, pinned linuxdeploy binary/source provenance, the identical source commit
reported by every artifact, and optional NVIDIA wheel provenance. It sets `signed: true`
only after every required signing report is present and hash-bound to the final artifact
bytes; missing reports or post-signing changes abort finalization. The expected artifacts
are:

```text
PrettyListener-<version>-windows-x64-setup.exe
PrettyListener-<version>-windows-x64.exe
pretty-listener_<version>_amd64.deb
PrettyListener-<version>-x86_64.AppImage
PrettyListener-<version>-macos-arm64.dmg
PrettyListener-<version>-macos-x86_64.dmg
```

When the optional NVIDIA flavor is built, the release directory also contains four
Linux/Windows artifacts with `nvidia` in their names and four matching self-test
reports. The finalizer includes them in checksums and provenance only as a complete
set; a partial NVIDIA flavor is rejected.

## Data

The SQLite database is created at:

```text
~/.local/share/pretty_listener/transcripts.sqlite3
```

Runtime errors and import diagnostics are written to:

```text
~/.local/share/pretty_listener/logs/pretty-listener.log
```

Raw audio is not persisted. Temporary WAV files are removed after transcription.

Each call should be recorded in its own dialog. Create a dialog with `Новый`, select it
in the left sidebar, then press the red circle `●`; new utterances are saved only into
the selected dialog. Use `Переименовать` to rename a dialog and `Удалить` to
permanently remove the dialog and its transcript rows. Existing transcript rows from
older versions are moved into a single `Старые записи` dialog during migration.
Use the search field on the right side of the `Чат` / `Собеседники` /
`Скачивание` / `Саммери` tab row to find words or phrases in the current dialog
and move between matches with the arrow buttons.

The `Саммери` tab prepares a structured Russian summary of the selected dialog:
`Кратко`, `Основные темы`, `Решения`, `Задачи`, `Открытые вопросы`, and
`Риски/важные замечания`. The default pipeline asks the model only for a constrained JSON
list of facts. Every accepted fact must cite an existing utterance ID and an exact quote;
unknown IDs, changed quotes, unsupported names/numbers, and instructions embedded in the
transcript are rejected. Negation, uncertainty, and the distinction between a decision,
task, question, risk, and neutral topic are checked against the specific cited clause
that supports the claim. Owners and deadlines are bound to that same clause. Completed
actions are marked as completed instead of being presented as future work; an explicitly
superseded decision is removed in favour of the later final decision. Duplicate and
complementary task facts are merged locally and the final six-section
Markdown is rendered deterministically, so there is no free-form reduce or polish pass
that can introduce a new claim.

Affordable local 3–4B models default to an explicit 8192-token context policy. Settings
can raise it up to 32768 tokens (or lower it to 6144). Input, output, and safety budgets
are derived from the selected context; at the default they are 7296 prompt tokens, 640
output tokens, and 256 safety tokens. The memory preview separately shows model weights,
the context-dependent KV cache, runtime workspace, peak use, and a safety reserve. The
embedded GGUF backend counts the fully formatted chat prompt with the model's own
tokenizer and checks the invariant again in the worker immediately before generation.
Backends whose tokenizer is unavailable use a conservative UTF-8 bound.
Long dialogs and single oversized utterances are split until every request satisfies the
same limit. A truncated or invalid JSON response gets one bounded corrective retry and
then a smaller chunk; it is never silently presented as a successful empty summary.
Final rendering uses no model tokens. These limits are shown in Settings.

The managed default Qwen3 4B and Gemma 3 4B GGUF files are pinned and checked by size
and SHA-256. A truncated
download is retained as a partial and resumed with HTTP Range instead of being trusted as
a ready model or downloaded again from byte zero.
The progress bar is determinate during summary generation: model loading is scaled into
the first part of the bar, fact extraction advances by chunk count, and local evidence
validation/rendering use the final milestones.
Each accepted summary is saved as a new version instead of overwriting older results.
The embedded GGUF backend runs the LLM in a separate worker process. A preflight blocks
configurations whose estimated peak does not fit, and load/generation OOM, a killed
worker, or critically low free RAM stops that worker instead of taking down the PySide
window. On Linux the disposable worker also asks the kernel to prefer it over the GUI as
the OOM victim.

The selected dialog can be exported from the `Скачивание` tab. Choose one or more
formats with checkboxes, set the time/speaker options, then press `Скачать` and select
an output folder. Time and speaker can also be disabled with `Не добавлять`.
Supported formats are `.txt`, `.md`, `.html`, `.csv`, `.pdf`, and `.docx`. Each
utterance is exported as one row:

```text
date time / speaker / text
```

To transcribe an existing recording, click `Распознать`, choose a media file or a
folder, and Pretty Listener will create dialog rows from the extracted audio track.
For a container with several audio streams, the file importer shows each stream's
language, title, codec, channel layout, and default flag. It uses only the default
stream automatically; alternate languages, commentary, and audio-description
streams are opt-in and are imported into separate dialogs. A stereo stream can also
be split explicitly into independent L/R dialogs when the recording really stores
separate microphones in its channels. Ordinary stereo is kept as one mono mix:
different channel waveforms alone are not proof of different speakers.
Imported dialogs are named from the file or folder plus the recognition date/time.
Supported inputs include common audio/video containers such as `.mkv`, `.mp4`,
`.mp3`, `.wav`, `.m4a`, `.ogg`, `.flac`, `.webm`, `.avi`, and `.mov`. Folder import
can create one dialog per file or one combined dialog for the folder. Imported media
is treated as remote/mixed call audio, so it appears on the left side of the chat and
uses the same speaker labels/voiceprints as system-output speech.
During import the app first analyzes local speaker turns. Recordings up to five minutes
are then decoded once with Whisper word timestamps and each word is attributed to the
overlapping speaker turn. This preserves language context across speaker changes and
avoids loading the same recording repeatedly. Longer recordings are transcribed in
bounded turns to cap memory use; the same fallback is automatic after a whole-file OOM.
Offline file import prefers the open `pyannote Community-1` pipeline. It keeps the
overlap-aware timeline for storage and uses Community-1's exclusive timeline to assign
each Whisper word to exactly one local speaker. Live capture continues to use the
lightweight ONNX pipeline. A completed remote live batch is decoded once with timed
words and attributed afterwards; speaker-turn boundaries no longer force separate
ASR decodes. Community-1 is gated by its publisher, so install it once
after accepting the conditions at
<https://huggingface.co/pyannote/speaker-diarization-community-1>:

```bash
hf auth login
pretty-listener --install-community-diarization
pretty-listener --verify-community-diarization
```

Both commands also write `community-diarization-report.json` in the Pretty Listener
data directory, so packaged windowed builds remain diagnosable when they have no
terminal output. Use `--community-report /path/to/report.json` to choose another
location. The optional runtime installer needs Python 3.10+ with `venv` on the host;
the application itself remains self-contained. Community-1 is unavailable on Intel
macOS with the pinned PyTorch/TorchCodec stack; Apple Silicon, Linux x86_64, and
Windows x86_64 are supported installation targets.

The token remains in Hugging Face's credential store and is not written to Pretty
Listener settings or logs. The installer pins revision
`3533c8cf8e369892e6b79ff1bf80f7b0286a54ee`, verifies the published weight hashes,
and installs `pyannote.audio` in a separate versioned runtime. Inference is offline
after installation and telemetry is disabled. If that runtime is absent or fails,
file import records a warning and falls back to the Silero/WeSpeaker pipeline.

Parakeet TDT v3 Q8 uses a separate pinned NeMo-Speech.cpp native runtime. The
first model preload installs both artifacts atomically. Source/development builds
can also install or audit them explicitly:

```bash
pretty-listener --install-parakeet
pretty-listener --verify-parakeet
```

Both commands write `parakeet-install-report.json` in the Pretty Listener data
directory even in a windowed packaged build. The public GGUF is fixed to revision
`541d1f99c6b0c3cd0b11a95167540bb8edefd82b` and SHA-256
`e3880d0aaaaf2c308ea2c35016b2b895c423eb3fda924c1b463d1c19b7f4d32e`.
Managed native files are tied to NeMo-Speech.cpp revision
`5be7bfb104802131e61fe679b3f1401b27270216` and verified before execution.
Frozen releases require a separately registered bundle for their exact OS and
architecture; an unsupported build fails closed instead of using an executable
from `PATH`. Automatic source compilation is currently validated only on Linux
x86_64 CPU and requires CMake 3.26+, Ninja, Git, a C++17 compiler, and the pinned
SentencePiece dependency build.

Pretty Listener also repairs obvious duplicate default speakers automatically when
their voiceprint profiles are nearly identical; manually renamed speakers are left
alone unless you merge them yourself.
The live and fallback speaker-turn cutter uses Silero VAD hysteresis, overlapping WeSpeaker ONNX embeddings,
speaker-change debounce, and a second clustering pass over the complete batch. A single
quiet 30 ms frame no longer cuts a speech region. Cutting is intentionally sticky: short
or weak voiceprint fragments are
kept with the current speaker unless there is strong evidence of a different stable
speaker. This produces more complete chat bubbles and fewer unlabeled short phrases,
at the cost of occasional conservative merges.
After each imported file, Pretty Listener runs a residual unknown repair pass. Confident
unknown voiceprints are matched against current-dialog speakers first; weak unknowns can
inherit a dominant nearby speaker when there is no stronger voiceprint evidence for
someone else.
The import progress bar is based on real processing stages: audio extraction, speech
scan, voiceprint windows, speaker-turn cutting, transcription, and saving. Whole-file
results are inserted in timestamp order after the decode completes. Long-file and
fallback turn jobs can finish in parallel; a `turn_index` reorder buffer guarantees
that SQLite row IDs and live chat insertion remain chronological even when worker 3
finishes before worker 1. During import, the transport buttons
switch to file control mode: `Ⅱ` pauses after currently running turn jobs finish, `●`
continues, and `■` force-stops an active whole-file job or stops queued turn work while
keeping already saved phrases.
If one turn fails, the error is logged and the remaining queued turns continue.

During live recording, the blue/red meters still mean "audio activity". Live uses a
1.5-second silence endpoint, captures at most one OS-default microphone (`self`) and
one OS-default monitor/loopback (`remote`), and keeps one active plus two pending
batches per device. Stop first closes capture, accepts the segmenter's final `flush()`
batch, and drains accepted work before returning to idle; the persistent Turbo worker
is kept warm for the next session. The separate `Обработка` meter shows finite
background jobs for completed batches, including speaker-turn analysis,
transcription progress, and the current processing queue.

Multichannel live input uses a phase-safe mono conversion: ordinary stereo is
downmixed, while an almost-silent side or destructive anti-phase cancellation selects
the stronger channel. A conservative waveform correlation guard removes a delayed,
gain-scaled copy of loopback audio from the microphone path. This guard prevents
obvious duplicates but is not full acoustic echo cancellation: if local speech and
loud speaker leakage occur in the same microphone frames, accurate double-talk
separation still requires an AEC implementation and suitable hardware routing.

Remote turn boundaries use the 256-dimensional WeSpeaker ResNet34-LM ONNX speaker
embedding. The approximately 27 MB model is downloaded once on first remote analysis
and cached under the Pretty Listener data directory; if it cannot be loaded, the v2
local extractor remains the fallback. Each new confirmed phrase stores both its neural
embedding and the compatible v2 MFCC/pitch voiceprint. The v2 vector links the first
neural sample to an existing speaker, then later calls can match that speaker directly
with WeSpeaker even when the microphone or channel changed. No raw audio is retained.
This is substantially stronger than the original lightweight-only clustering, but it
is still not biometric identification and cannot reliably separate strictly overlapping
speech.
Uncertain remote phrases still save their per-utterance voiceprint in
`utterance_voiceprint`. Confirmed speaker profiles are stored separately in
`speaker_voiceprint`; context-only assignments are never promoted into the confirmed
profile. Context repair runs after the complete file/live batch is inserted, not after
each out-of-order worker result, so incomplete neighbours cannot contaminate a speaker.

When upgrading from the first voiceprint version, Pretty Listener performs a one-time
speaker reset: transcript text and timestamps stay in `utterances`, but old speaker
labels, speakers, and v1 voiceprints are removed. New remote speaker labels are then
learned from v2 voiceprints.

The speaker tables are:

```text
dialog(id, title, created_at, updated_at)
utterances(..., dialog_id, source, device_name, speaker_id, started_at, ended_at, recognized_at, language, language_probability, text, duration_ms, allow_context_speaker_assignment)
speaker(id, display_name, canonical_speaker_id, created_at, updated_at)
speaker_voiceprint(id, speaker_id, embedding_model, embedding_version, vector_json, source_utterance_id, created_at)
utterance_voiceprint(id, utterance_id, embedding_model, embedding_version, vector_json, quality_json, created_at)
dialog_summary(id, dialog_id, model_name, model_label, prompt_version, summary_text, source_utterance_count, created_at, completed_at)
media_recording(id, dialog_id, job_id, media_path, relative_path, duration_seconds, diarization_backend, diarization_model_revision, created_at)
speaker_activity(id, recording_id, local_cluster_id, started_at, ended_at, is_exclusive, backend, model_revision)
recording_speaker(recording_id, local_cluster_id, speaker_id)
app_metadata(key, value, updated_at)
```

Remote utterances store `speaker_id`; self/microphone utterances do not. When several
detected speakers are merged in the `Собеседники` tab, old rows remain in the database,
but chat history resolves them through the canonical speaker and displays the chosen
name. `Удалить метку` removes the selected speaker label and confirmed speaker
voiceprints, but keeps the transcript text and per-utterance voiceprints.

## Audio Device Notes

On Linux, Pretty Listener uses:

```bash
pactl list short sources
pactl subscribe
parec --device <source>
```

If an audio device is connected after pressing Start, the app should pick it up within a couple of seconds. Suspended devices are still opened because PulseAudio can activate them once `parec` starts reading.

On Windows, Pretty Listener uses WASAPI input devices for `self` and WASAPI
loopback input devices for `remote`. Device changes are reconciled every couple
of seconds while listening. If one device fails to open, the other devices keep
running and the failed device is retried on a later reconciliation.
