Lore & Play

Transcribe your sessions on your own computer

Free, often faster than uploading, and the recording never leaves your machine. Set-up, the commands, and how the result gets into your campaign.

1.Why you might prefer this

You can upload a recording and we will transcribe it for you. This page is the other way of doing it, and for a lot of tables it is the better one.

  • Your table's audio never leaves your computer. Nothing is uploaded, to us or to anyone else. If your table includes children — many do — this is not a small thing.
  • It is free. No credits, no provider bill, no per-minute cost. Electricity and an evening.
  • On a modern GPU it is faster than uploading would be, and it stays fast for a back catalogue: twenty sessions is one overnight run, not twenty uploads.
  • You can tune it. Feeding the model your invented proper nouns (section 6) fixes the single most annoying failure — *Whisperwood* coming back as *whisper wood* four hundred times — and no hosted service lets you do that.

The honest cost: an afternoon of set-up the first time, and a machine you are willing to leave running. If that is not a trade you want to make, upload the recording instead and skip this page entirely — the result is the same transcript in the same place.

Recording your table is a decision everyone at it should be part of. That applies just as much when the audio never leaves your desk. Tell your players it is running, and agree what happens to it afterwards.

2.What you need, and how long it takes

The model that matters is large-v3, and it needs about 4.7 GB of VRAM in float16. Any NVIDIA card with 6 GB or more runs it comfortably; 8 GB and up leaves room for speaker labelling later.

Your machineWhat to runRoughly
NVIDIA GPU, 8 GB+ VRAMlarge-v3, batched25–40× real time — a four-hour session in about 8 minutes
NVIDIA GPU, 6 GB VRAMlarge-v3, standard10–20× real time — a four-hour session in 15–25 minutes
NVIDIA GPU, 4 GB VRAMlarge-v3-turbo, or int8_float16Workable. Test the quality on your table first
Apple Siliconwhisper.cpp with MetalUsable. Slower than a discrete GPU, far faster than a CPU
CPU onlywhisper.cpp, a smaller model1–3× real time. A four-hour session is most of a day
Treat a back catalogue as an overnight job, not a coffee break. Eighty hours of audio is four to eight hours of work for a mid-range GPU, and adding speaker labels roughly doubles it. It is still free, still faster than uploading eighty hours anywhere, and it runs while you sleep.

You will also need Python 3.11 — not 3.12 or 3.13, because the speaker-labelling dependencies lag behind — and ffmpeg on your PATH.

3.Which tool to use

ToolWhat it gives youUse it when
faster-whisperText with timestamps. Fast, simple, few dependenciesStart here. It is the whole job if you don't need speaker names
WhisperXThe above plus speaker labelsSPEAKER_00, SPEAKER_01You want to know who said what. Most DMs eventually do
whisper.cppCPU and Apple Silicon friendly, minimal installNo NVIDIA GPU
Install `faster-whisper` first and get one file working before you add anything. Speaker labelling brings a second set of models, an account, and a token. Debugging both at once is how people give up on this.

4.Set-up

Windows

powershell
# 1. An isolated environment, so none of this touches your system Python
py -3.11 -m venv C:\whisper-env
C:\whisper-env\Scripts\Activate.ps1

# 2. PyTorch with CUDA (check pytorch.org for the current cu-version)
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu121

# 3. Confirm the GPU is actually visible - this must print True and your card
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"

# 4. The transcriber
pip install faster-whisper

# 5. ffmpeg, if you don't have it
winget install ffmpeg
If step 3 prints `False`, stop there. Everything after it will quietly fall back to the CPU and take twenty times longer — and you will not find out until an overnight run is still going at lunchtime. Update your NVIDIA driver and reinstall torch with the matching CUDA version.

Linux

bash
python3.11 -m venv ~/whisper-env
source ~/whisper-env/bin/activate
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu121
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"
pip install faster-whisper
sudo apt install ffmpeg        # or dnf / pacman, to taste

macOS (Apple Silicon)

There is no CUDA on a Mac, so faster-whisper runs on the CPU and is slow. Use whisper.cpp, which is built for Metal:

bash
brew install whisper-cpp ffmpeg

# Fetch the model once (about 3 GB)
whisper-cpp-download-ggml-model large-v3

# Transcribe, writing an .srt beside the audio
whisper-cpp -m ggml-large-v3.bin -f session-07.wav -l en -osrt
whisper.cpp wants 16 kHz mono WAV. Convert first with ffmpeg -i session-07.m4a -ar 16000 -ac 1 session-07.wav — and it is worth doing on any platform, because speech needs very little bitrate and the file gets much smaller.

5.The ten-minute test — do this before anything long

Take one messy stretch of a real session: several people talking over each other, dice in the background, someone eating crisps. Not your clearest recording — your most typical one.

bash
ffmpeg -i session-07.m4a -ss 00:20:00 -t 00:10:00 -c copy test10.m4a

Then run both candidate models against it and read the output yourself:

python
from faster_whisper import WhisperModel
import time

AUDIO = r"test10.m4a"     # the ten-minute clip you just cut
LANG  = "en"              # "el" Greek, "en" English, or None to auto-detect

for size in ["large-v3-turbo", "large-v3"]:
    print(f"\n{'='*60}\n{size}\n{'='*60}")
    model = WhisperModel(size, device="cuda", compute_type="float16")
    t0 = time.time()
    segments, info = model.transcribe(AUDIO, language=LANG, beam_size=5, vad_filter=True)
    text = " ".join(s.text for s in segments)
    dt = time.time() - t0
    print(f"took {dt:.0f}s for {info.duration:.0f}s of audio  ->  {info.duration/dt:.1f}x real time")
    print(text[:1500])
    del model

This is the only test that matters, and the thing being tested is your ear, not a number. Look for:

  • Are the invented names right? Whisper mangles anything it has never seen, and how badly tells you which model you need.
  • Does it cope when two people talk at once? Every table does this constantly.
  • Are the quieter voices transcribed or dropped? Children and anyone sitting away from the microphone are the ones that vanish.

If large-v3-turbo is good enough, use it — it is roughly twice as fast. If it is not, and you have the VRAM, there is no reason to compromise.

6.The one tweak worth making: teach it your proper nouns

initial_prompt feeds the model a list of words to expect. It is the difference between *Opal Silvermist* and *opal silver mist*, four hundred times over, and it is the single highest-value line in any of these scripts.

python
PROMPT = ("Whisperwood, Eldoria, Opal Silvermist, Kalvius Draxil, "
          "Maren Oakbarrel, Elira Wren, Greyveil, Daskwood, Aqualora")

model.transcribe(AUDIO, language="en", initial_prompt=PROMPT, ...)
Build the list from your own notes: player character names, the recurring NPCs, your major places and factions. Twenty or thirty words is plenty — it is a hint, not a dictionary. If you have already imported your campaign, your Codex is the list.

7.Doing a whole back catalogue

Point this at the folder holding your recordings. It writes an .srt and a .txt beside each file, and skips anything already done, so you can stop it and restart it safely.

python
"""Transcribe every session recording in a folder.
Writes <name>.srt and <name>.txt beside each audio file.
Safe to re-run: files that already have an .srt are skipped."""

from faster_whisper import WhisperModel
from pathlib import Path
import time, sys

ROOT      = Path(r"C:\campaign\recordings")   # the folder holding your recordings
MODEL     = "large-v3"        # or "large-v3-turbo" if the test in section 5 said it's fine
LANGUAGE  = "en"              # "el" Greek - "en" English - None to auto-detect
AUDIO_EXT = {".m4a", ".mp3", ".wav", ".ogg", ".opus", ".flac", ".mp4", ".mkv"}

# Your world's proper nouns. See section 6 - this is the single highest-value line here.
PROMPT = "Whisperwood, Eldoria, Opal Silvermist, Maren Oakbarrel, Greyveil"

def ts(seconds: float) -> str:
    h, rem = divmod(seconds, 3600); m, s = divmod(rem, 60)
    return f"{int(h):02}:{int(m):02}:{int(s):02},{int((s%1)*1000):03}"

def main():
    files = sorted(p for p in ROOT.rglob("*") if p.suffix.lower() in AUDIO_EXT)
    if not files:
        sys.exit(f"No audio found under {ROOT}")
    print(f"{len(files)} file(s) found. Loading {MODEL}...")
    model = WhisperModel(MODEL, device="cuda", compute_type="float16")

    for i, audio in enumerate(files, 1):
        srt = audio.with_suffix(".srt")
        txt = audio.with_suffix(".txt")
        if srt.exists():
            print(f"[{i}/{len(files)}] skip (already done): {audio.name}")
            continue

        print(f"[{i}/{len(files)}] {audio.name}")
        t0 = time.time()
        segments, info = model.transcribe(
            str(audio), language=LANGUAGE, beam_size=5,
            vad_filter=True, vad_parameters={"min_silence_duration_ms": 500},
            initial_prompt=PROMPT,
        )

        lines, plain = [], []
        for n, seg in enumerate(segments, 1):
            lines.append(f"{n}\n{ts(seg.start)} --> {ts(seg.end)}\n{seg.text.strip()}\n")
            plain.append(seg.text.strip())
            if n % 100 == 0:
                print(f"    ...{n} segments, {seg.end/60:.0f} min in", flush=True)

        srt.write_text("\n".join(lines), encoding="utf-8")
        txt.write_text("\n".join(plain), encoding="utf-8")
        dt = time.time() - t0
        print(f"    done in {dt/60:.1f} min  ({info.duration/dt:.1f}x real time)")

    print("\nAll finished.")

if __name__ == "__main__":
    main()
powershell
C:\whisper-env\Scripts\Activate.ps1
python transcribe-campaign.py
The `.srt` files are the output that matters. They carry timestamps, and the timestamps come with them into your session transcript — see section 9.

8.Speaker labels (optional, and second)

This is the step that turns a wall of text into *“Marina: …”*. It needs WhisperX, a free Hugging Face account, and a token you generate yourself.

bash
pip install whisperx

You must also accept the terms for two pyannote models on their Hugging Face pages, then pass your own token:

bash
whisperx session-07.m4a \
  --model large-v3 --language en \
  --diarize --min_speakers 4 --max_speakers 8 \
  --hf_token YOUR_TOKEN_HERE \
  --output_format srt --output_dir .
The labels are consistent within one file and not across files. SPEAKER_00 in session 3 is not the same person as SPEAKER_00 in session 4. Lore & Play remembers what you map each label to per campaign, so you name them once per file and the names stick.

On 8 GB of VRAM or less you may need to let WhisperX load the transcription and speaker models one after the other rather than together. If it runs out of memory, that is the first thing to change.

9.Getting the result into your campaign

Everything above produces files. Here is where each one goes.

One session you already have in Lore & Play

  1. Open the session and go to the Transcript tab.
  2. Press Import a transcript and choose the .srt.
  3. The timestamps and any speaker labels come through with it. Map SPEAKER_00 to a real name once and it is remembered for the rest of the campaign.

A whole back catalogue you are bringing over

  1. Go to Import my campaign.
  2. Add the .srt files along with your written notes — same button, as many as you like.
  3. Name them so the date and number are in the filename (s07-2026-05-22.srt) and your sessions arrive dated and in the order you played them.
Import the `.srt`, not the `.txt`. They hold the same words, but the .srt carries the timing, and the timing is what lets the assistant find “the bit where they met the harbourmaster” instead of handing you four hours of prose.

10.When it goes wrong

What you seeWhyWhat to do
torch.cuda.is_available() prints FalseCPU-only torch, or an old driverReinstall torch with the right cu index and update the NVIDIA driver. Check this before an overnight run, not after
Very slow, GPU sitting idleIt fell back to the CPUSame as above
CUDA out of memoryModel too large, or speaker labelling loaded alongside itUse compute_type="int8_float16", or run the speaker pass separately
Invented names come back mangledExpected — the model has never seen themExtend initial_prompt (section 6)
Long silences transcribed as invented speechWhisper hallucinating on silencevad_filter=True is already on; raise min_silence_duration_ms
Greek transcribed in Latin lettersLanguage auto-detected wrongSet language="el" explicitly
A table that switches between two languagesOne language is chosen per fileTranscribe twice and keep the better result, or split the audio
The .srt won't importAlmost always an encoding problemMake sure it is saved as UTF-8. The scripts here already do

If the transcript is good but the import is not doing what you expect, that is our end rather than yours — the DM manual covers the transcript tools, and the campaign importer shows you everything it read before anything is saved.