> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kair.is/llms.txt
> Use this file to discover all available pages before exploring further.

# Transcription

> How audio is transcribed — backends, diarisation, limits, and configuration.

## Backends

Two transcription backends are available. Admins can switch between them at runtime via the System page in the web app — no code deployment needed.

### Local (Whisper.cpp)

Runs entirely on the worker machine. Audio never leaves your infrastructure.

* Model files must be present in the `ai_models/` volume
* The worker must restart to load the model (switching *back* to local from Foresight requires a restart)
* Accuracy depends on the model size loaded

### Foresight (remote inference)

Audio is posted to an OpenAI-compatible endpoint (`{base_url}/audio/transcriptions`). Results come back in `verbose_json` format with word-level timestamps.

* Faster and generally more accurate than local models
* Requires network access to the Foresight cluster
* `base_url` and `api_key` are set in `config.yml` — not editable from the admin UI (security boundary)
* The realtime model and final model can be set separately from the admin UI

**Size limit:** Foresight rejects uploads larger than **200 MiB**. Long sessions must use chunked upload — a single-file upload of a multi-hour recording will fail.

## Diarisation (speaker labelling)

Diarisation assigns a speaker label (`S1`, `S2`, etc.) to each transcript segment so you know who said what.

| Backend   | Diarisation behaviour                                           |
| --------- | --------------------------------------------------------------- |
| Local     | pyannote runs locally; labels speakers as `S1`, `S2`, etc.      |
| Foresight | Foresight's `SPEAKER_xx` labels are normalised to `S{n}` scheme |

**Important:** Diarisation only runs on the **final** full-audio transcription — never on individual chunks. Enabling diarisation on a chunk-based recording has no effect until finalisation.

When Foresight diarisation is enabled, pyannote still runs locally to compute **voiceprint embeddings** for cross-session speaker identity — but its labels are mapped to match Foresight's output.

If diarisation is disabled, segments still get speaker labels from Foresight but no identity matching runs.

## Runtime configuration (admin)

Admins can change the following at runtime from **Admin → System → Transcription**:

| Setting                  | Options               | Notes                                         |
| ------------------------ | --------------------- | --------------------------------------------- |
| Transcription client     | `local` / `foresight` | Takes effect per-job                          |
| Foresight realtime model | string                | Model used for chunk transcription            |
| Foresight final model    | string                | Model used for the full-session transcription |
| Diarisation              | on / off              | Foresight only; final pass only               |

`base_url` and `api_key` are config-file only — not exposed in the admin UI.

## Transcript format

Transcripts are stored as structured segments:

```json theme={null}
{
  "segments": [
    {
      "speaker": "S1",
      "start": 0.0,
      "end": 4.2,
      "text": "Welcome everyone, let's get started."
    },
    {
      "speaker": "S2",
      "start": 4.5,
      "end": 9.1,
      "text": "Thanks for having us."
    }
  ]
}
```

Speaker labels are stable within a session. Cross-session identity (knowing `S1` in session A is the same person as `S1` in session B) requires voiceprint matching, which runs when diarisation is enabled.

## Re-running failed transcription

Individual failed chunk jobs can be re-run from **Admin → Jobs → Transcriptions**. Select the session, expand the chunk group, and click "Re-run" on the failed chunk.
