Skip to main content

Overview

Léigh Dom — Irish for "read to me", a sibling of abair ("say") and fotheidil ("subtitle") — turns a document into narrated Irish audio. Upload a PDF, EPUB, DOCX, ODT, RTF, HTML, Markdown or plain-text file, pick a voice, and get back a stitched WAV, the extracted text, timed subtitles, and optionally an MP4 of the reading page with the spoken sentence lit up.

What it does

  • Reads eight formats, each with its own extractor. The text is normalised before it is spoken, and is downloadable in its own right.
  • Survives restarts mid-document. Every chunk is written to disk as it arrives, so a seven-hour job resumes rather than starting again.
  • Streams progress live over Server-Sent Events, so a long job shows real movement instead of a spinner.
  • Lets the transcript be edited before or after synthesis. Unchanged paragraphs keep their existing audio; only what was actually edited is re-synthesised.
  • Exports four ways — WAV, text, subtitles (SRT or VTT), and video.

Voices

Seven Piper voices, a static list in lib/voices.ts mirrored from abair_website/data/synthesis.ts. The HTS voices are deliberately not carried over, and the catalogue is not fetched at run time — a voice added, removed or renamed on the ABAIR website has to be mirrored there or it will not appear.

VoiceDialectVoice code
SibéalConnemaraga_CO_snc_piper
ÁineUlsterga_UL_anb_piper
DónallUlsterga_UL_doc_piper
ColmMunsterga_MU_cmg_piper
NeasaMunsterga_MU_nnc_piper
DannyMunster (heritage)ga_MU_dms_piper
FianaitMunster — An Rinnga_MU_ar_fnm_piper

Each has a ~4-second preview in public/voice-previews/, all speaking the same phrase so they can be compared. A missing one falls back to live synthesis, so it costs latency rather than breaking the picker.

Access

Sign-in is delegated to ABAIR Auth over the redirect flow, the same one Fotheidil uses — Léigh Dom owns no login form and stores no passwords. It has no roles: it reads nothing from auth_user_roles (unlike the CUL staff portal, see Authorization & Permissions), so any signed-in ABAIR account may use it.

SurfaceSigned out
/, /terms, /privacyPublic
/library, /library/[id]Rendered, with a sign-in prompt and no data
/api/jobs*401 {"error":"unauthorized"} — JSON, not an HTML login page, because the callers are fetch
/api/voices, /api/voice-previews/*, /api/capabilities, /api/healthPublic — the landing page's voice sampler needs them

Ownership is enforced per query rather than per route, and somebody else's document id returns 404, not 403, so the id is never confirmed to exist.

Limits

LimitValueSet by
Upload size25 MiBMAX_DOC_BYTES
Normalised text300,000 characters (≈7 h of audio)MAX_TEXT_CHARS
Concurrent upstream TTS requests1, process-wideTTS_GLOBAL_CONCURRENCY
Documents being extracted and chunked at once2 across all users, no per-user limitRUNNER_PREPARE_SLOTS
Documents synthesising at once2 across all users, 1 per userRUNNER_JOB_SLOTS
Video renders at once1, niced to 15RENDER_JOB_SLOTS, RENDER_NICE
Retention30 days, then rows and artefacts are deletedRETENTION_DAYS

The concurrency of 1 is deliberate: the synthesis backend renders serially, and on the same input 8 of 8 chunks succeeded one at a time against 1 of 8 at concurrency 2. Raising it makes throughput worse. Retention is a disk budget rather than a policy — see Deployment.

What it is not

  • Not a document reader. It produces files; in-browser playback is for checking the result.
  • Not a speech engine. Every second of audio comes from synthesis.abair.ie — there is no local model and no fallback voice.
  • Not shared. No sharing, no team library, no public links.
  • Not durable storage. Documents are swept after 30 days and the volume is not backed up.
DescriptionLink
Productionhttps://leighdom.abair.ie
GitHub repositoryhttps://github.com/phonlab-tcd/leigh-dom
Speech backendhttps://synthesis.abair.ie
Sign-inhttps://auth.abair.ie
Health probehttps://leighdom.abair.ie/api/health

Where to go next

If you're…Start here
Working on the pipeline or the job runnerArchitecture
Wondering why a document is stuckArchitecture → failure modes
Shipping a changeDeployment
Deploying it somewhere newDeployment → adding it to a fresh estate

Last updated 2026-09-16