Read Aloud (new "Vorlesen" tab):
- PDF (real page render + overlay highlight) / TXT reader with live word
highlighting, voice + speed, per-sentence synthesis-state colours, zoom
(fit-width/height, two-page, ±), resume, and a server-side book library
(syncs across devices; per-unit MP3 audio fetched on demand).
Book -> multi-speaker audiobook:
- "Cast as audiobook" attributes dialogue to characters via the LLM
(guillemet/quote-style aware, turn-taking, recent-context), with a
deterministic speech-tag fallback. Editable preview, non-blocking live
casting panel, then auto-saved as a reopenable Script Rehearser play.
- Audiobook export: synthesise every cast line -> one MP3 per chapter.
Character sheets:
- LLM-extracted, self-filling RPG-style sheets (with page+quote sources)
in both Read Aloud and the Rehearser.
Also: MP3 storage + per-page/sentence export, voice-library "Precompute
embeddings" pre-warm, German "Vorlesen" i18n + flag language toggle,
large-PDF performance (lazy raster, buffer/canvas eviction, yielded parse),
and the Seed Finder changelog entry.
New: routes/reader.py, POST /api/attribute-dialogue, POST /api/character-sheets,
static/js/{reader,audiobook,character-sheets}.js, static/sections/s-reader.html.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
148 lines
10 KiB
HTML
148 lines
10 KiB
HTML
<div class="section-head">
|
||
<span class="section-icon"><span class="mdi mdi-book-open-page-variant-outline"></span></span>
|
||
<div class="section-title">
|
||
<h2>Read Aloud</h2>
|
||
<p>Import a PDF or text document, pick a voice and speed, then have it read to you while the word being spoken is highlighted.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tab-content" id="tab-reader">
|
||
|
||
<!-- ① Import + controls ─────────────────────────────────────────── -->
|
||
<div class="card">
|
||
<div class="reader-controls">
|
||
<div class="field reader-ctl">
|
||
<label>Backend</label>
|
||
<select id="reader-backend-select" aria-label="TTS backend"><option value="">Checking backends…</option></select>
|
||
</div>
|
||
<div class="field reader-ctl">
|
||
<label>Voice</label>
|
||
<div class="reader-voice-pick">
|
||
<select id="reader-voice-select" aria-label="Voice"><option value="">— fetch or pick a voice —</option></select>
|
||
<button class="btn-secondary btn-sm" id="reader-fetch-voices-btn" title="Fetch available voices"><span class="mdi mdi-refresh"></span></button>
|
||
</div>
|
||
</div>
|
||
<div class="field reader-ctl">
|
||
<label>Speed <span id="reader-speed-label" class="reader-speed-label">1.0×</span></label>
|
||
<input type="range" id="reader-speed" min="0.5" max="2" step="0.05" value="1" aria-label="Reading speed">
|
||
</div>
|
||
<div class="field reader-ctl">
|
||
<label>Voice consistency</label>
|
||
<select id="reader-chunk-mode" title="Synthesise larger chunks so the voice stays steady across a passage">
|
||
<option value="sentence">Per sentence (responsive)</option>
|
||
<option value="paragraph">Per paragraph (steadier)</option>
|
||
<option value="page">Per page (steadiest)</option>
|
||
</select>
|
||
</div>
|
||
<div class="field reader-ctl reader-ctl-grow">
|
||
<label>Tone / style <span class="note">(optional)</span></label>
|
||
<input type="text" id="reader-instruct" placeholder="e.g. calm, warm narration" autocomplete="off">
|
||
</div>
|
||
<div class="field reader-ctl reader-ctl-sm">
|
||
<label>Seed <span class="note">(blank = auto)</span></label>
|
||
<input type="number" id="reader-seed" placeholder="auto" autocomplete="off">
|
||
</div>
|
||
<div class="field reader-ctl reader-ctl-sm">
|
||
<label>Temperature <span class="note">(0–1)</span></label>
|
||
<input type="number" id="reader-temp" min="0" max="1.5" step="0.05" placeholder="default" autocomplete="off">
|
||
</div>
|
||
<label class="reader-ctl reader-norm-toggle" title="Even out loudness differences between chunks during playback">
|
||
<input type="checkbox" id="reader-normalize" checked> Normalise loudness
|
||
</label>
|
||
<label class="btn-primary reader-import-btn" title="Import a PDF or text file">
|
||
<span class="mdi mdi-file-upload-outline"></span> Import
|
||
<input type="file" id="reader-file-input" accept=".pdf,.txt,.md" style="display:none">
|
||
</label>
|
||
</div>
|
||
<div class="reader-backend-hint" id="reader-backend-hint"></div>
|
||
</div>
|
||
|
||
<!-- Saved books library ─────────────────────────────────────────── -->
|
||
<div class="card reader-library-card" id="reader-library-card" hidden>
|
||
<div class="reader-lib-head">
|
||
<h2 style="margin:0"><span class="mdi mdi-bookshelf"></span> My books</h2>
|
||
<span class="note">Saved documents with their synthesised audio — reopen to continue.</span>
|
||
</div>
|
||
<div class="reader-lib-list" id="reader-lib-list"></div>
|
||
</div>
|
||
|
||
<!-- ② Drag & drop / empty state ─────────────────────────────────── -->
|
||
<div class="reader-dropzone" id="reader-dropzone">
|
||
<span class="mdi mdi-cloud-upload-outline reader-dropzone-icon"></span>
|
||
<strong>Drag & drop a document here</strong>
|
||
<span class="note">.pdf · .txt · .md — or use the Import button above</span>
|
||
</div>
|
||
|
||
<!-- ③ Transport ─────────────────────────────────────────────────── -->
|
||
<div class="reader-transport" id="reader-transport" hidden>
|
||
<div class="reader-tb-group">
|
||
<button class="reh-tb-btn" id="reader-prev" title="Previous sentence"><span class="mdi mdi-skip-previous"></span></button>
|
||
<button class="reh-tb-btn reh-tb-play" id="reader-play" title="Play / pause"><span class="mdi mdi-play"></span></button>
|
||
<button class="reh-tb-btn" id="reader-stop" title="Stop"><span class="mdi mdi-stop"></span></button>
|
||
<button class="reh-tb-btn" id="reader-next" title="Next sentence"><span class="mdi mdi-skip-next"></span></button>
|
||
</div>
|
||
<div class="reader-progress-wrap">
|
||
<div class="reader-progress-track"><div class="reader-progress-fill" id="reader-progress-fill"></div></div>
|
||
<span class="reader-progress-label" id="reader-progress-label">0 / 0</span>
|
||
</div>
|
||
<span class="reader-doc-title" id="reader-doc-title"></span>
|
||
</div>
|
||
|
||
<!-- ④ Synthesise-ahead bar ──────────────────────────────────────── -->
|
||
<div class="reader-synthbar" id="reader-synthbar" hidden>
|
||
<button class="btn-primary btn-sm" id="reader-synth-all" title="Pre-synthesise the chosen range for gap-free reading">
|
||
<span class="mdi mdi-lightning-bolt"></span> Synthesise <span id="reader-synth-scope">all</span>
|
||
</button>
|
||
<span class="reader-page-range" id="reader-page-range" hidden>
|
||
<span class="note">pages</span>
|
||
<input type="number" id="reader-page-from" min="1" value="1" class="reader-page-inp" aria-label="From page">
|
||
<span class="note">–</span>
|
||
<input type="number" id="reader-page-to" min="1" value="1" class="reader-page-inp" aria-label="To page">
|
||
</span>
|
||
<button class="btn-secondary btn-sm" id="reader-select-toggle" title="Click sentences to mark a start and an end"><span class="mdi mdi-cursor-default-click-outline" id="reader-select-icon"></span> <span id="reader-select-label">Select range</span></button>
|
||
<span class="reader-sel-hint" id="reader-sel-hint" hidden></span>
|
||
<span class="reader-sel-info" id="reader-sel-info" hidden>
|
||
<span id="reader-sel-text">selection</span>
|
||
<button class="reader-sel-clear" id="reader-sel-clear" title="Clear selection">✕</button>
|
||
</span>
|
||
<button class="btn-secondary btn-sm" id="reader-save-lib" title="Save this document + its synthesised audio to your library"><span class="mdi mdi-content-save-outline"></span> Save to library</button>
|
||
<button class="btn-secondary btn-sm" id="reader-audiobook-btn" title="Let an LLM detect characters & dialogue in this scope, then open it in the Script Rehearser to cast a voice per character"><span class="mdi mdi-drama-masks"></span> Cast as audiobook</button>
|
||
<button class="btn-secondary btn-sm" id="reader-charsheets-btn" title="Extract actor-facing RPG-style character sheets (with page + quote sources) from this scope"><span class="mdi mdi-account-details-outline"></span> Character sheets</button>
|
||
<span class="reader-export">
|
||
<select id="reader-export-mode" class="reader-page-inp" style="width:auto" aria-label="Export granularity" title="Choose how to group the exported MP3 files">
|
||
<option value="page">1 MP3 / page</option>
|
||
<option value="sentence">1 MP3 / sentence</option>
|
||
</select>
|
||
<button class="btn-secondary btn-sm" id="reader-export-btn" title="Download the synthesised audio as MP3 (synthesises any missing sentences first)"><span class="mdi mdi-download"></span> Export MP3</button>
|
||
</span>
|
||
<div class="reader-synth-prog" id="reader-synth-prog" hidden>
|
||
<div class="reader-synth-track"><div class="reader-synth-fill" id="reader-synth-fill"></div></div>
|
||
<span class="reader-synth-label" id="reader-synth-label">0 / 0</span>
|
||
<button class="btn-secondary btn-sm" id="reader-synth-cancel">Cancel</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ⑤ Zoom toolbar + status legend (PDF) ────────────────────────── -->
|
||
<div class="reader-toolbar" id="reader-toolbar" hidden>
|
||
<div class="reader-zoom" id="reader-zoom">
|
||
<button class="btn-secondary btn-sm" id="reader-zoom-fitw" title="Fit width"><span class="mdi mdi-arrow-expand-horizontal"></span> Fit width</button>
|
||
<button class="btn-secondary btn-sm" id="reader-zoom-fith" title="Fit height"><span class="mdi mdi-arrow-expand-vertical"></span> Fit height</button>
|
||
<button class="btn-secondary btn-sm" id="reader-zoom-two" title="Two pages side by side"><span class="mdi mdi-book-open-outline"></span> Two pages</button>
|
||
<span class="reader-zoom-sep"></span>
|
||
<button class="btn-secondary btn-sm" id="reader-zoom-out" title="Zoom out"><span class="mdi mdi-minus"></span></button>
|
||
<span class="reader-zoom-pct" id="reader-zoom-pct">100%</span>
|
||
<button class="btn-secondary btn-sm" id="reader-zoom-in" title="Zoom in"><span class="mdi mdi-plus"></span></button>
|
||
</div>
|
||
<div class="reader-legend">
|
||
<span class="reader-leg reader-leg-pending">Not synthesised</span>
|
||
<span class="reader-leg reader-leg-synth">Synthesising</span>
|
||
<span class="reader-leg reader-leg-ready">Ready</span>
|
||
<span class="reader-leg reader-leg-reading">Reading</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ⑥ Document viewport ─────────────────────────────────────────── -->
|
||
<div class="reader-doc" id="reader-doc"></div>
|
||
|
||
</div><!-- /tab-reader -->
|