Exposes the `speed` parameter (0.5–2.0) on the TTS `/v1/audio/speech` request so audio is generated at the target tempo natively via the faster-qwen3-tts backend rather than post-processing. - Backend: `/api/tts-preview` now extracts and forwards a `speed` override (clamped 0.1–4.0) through the same extra-params mechanism already used for seed/temperature; backends that reject it fall back cleanly via `_post_tts_with_fallback`. - Try it out: "Native Speed" number input (0.5–2, step 0.05) added to the text card; value persists in localStorage per browser; passed as `extra` through `createTtsAudioSource` and `generateChunkedTts`. - Read aloud: "Native Speed" control added to the generation controls row alongside seed/temperature; included in `readerGenParams()` and saved/restored with library documents (each book tracks its own speed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
157 lines
12 KiB
HTML
157 lines
12 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">
|
||
|
||
<div id="reader-main-view">
|
||
<!-- ① Import + controls ─────────────────────────────────────────── -->
|
||
<div class="card" style="padding:14px">
|
||
<div class="engine-setup-row" style="margin-bottom:8px">
|
||
<div class="engine-setup-col" style="flex:1">
|
||
<label class="engine-setup-label"><span class="mdi mdi-account-voice"></span> Text to Speech <a href="#" class="engine-setup-link" onclick="navTo('s-llms'); navEnginesCat('tts'); return false;" title="Configure Text to Speech settings"><span class="mdi mdi-cog-outline"></span></a></label>
|
||
<div class="engine-setup-controls">
|
||
<select id="reader-backend-select" aria-label="TTS backend"><option value="">Checking...</option></select>
|
||
<button class="btn-secondary" id="reader-fetch-voices-btn" title="Fetch available voices"><span class="mdi mdi-refresh"></span></button>
|
||
<select id="reader-voice-select" aria-label="Voice"><option value="">— fetch voices —</option></select>
|
||
</div>
|
||
</div>
|
||
<div class="engine-setup-col no-border" style="flex:1">
|
||
<label class="engine-setup-label"><span class="mdi mdi-tune"></span> Synthesis Options</label>
|
||
<div class="engine-setup-controls" style="flex-wrap:nowrap">
|
||
<input type="text" id="reader-instruct" placeholder="Optional style / tone" autocomplete="off" style="width:140px">
|
||
<select id="reader-chunk-mode" title="Voice consistency" style="width:120px">
|
||
<option value="sentence">Per sentence</option>
|
||
<option value="paragraph">Per paragraph</option>
|
||
<option value="page">Per page</option>
|
||
</select>
|
||
<label class="reader-ctl reader-norm-toggle" title="Even out loudness" style="margin:0; font-size:11px">
|
||
<input type="checkbox" id="reader-normalize" checked> Norm
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="reader-controls" style="margin-top:12px; background:var(--bg); border:1px solid var(--border); padding:10px 12px; border-radius:8px">
|
||
<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 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>
|
||
<div class="field reader-ctl reader-ctl-sm">
|
||
<label>Native Speed <span class="note">(0.5–2)</span></label>
|
||
<input type="number" id="reader-tts-speed" min="0.5" max="2" step="0.05" value="1" placeholder="1.0" autocomplete="off">
|
||
</div>
|
||
<label class="btn-primary reader-import-btn" title="Import a PDF or text file" style="margin-left:auto">
|
||
<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" style="padding-left:14px; margin-top:8px"></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><!-- /reader-main-view -->
|
||
|
||
<!-- ⑦ Audiobook Casting Panel ──────────────────────────────────── -->
|
||
<div id="reader-audiobook-panel" hidden></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>
|
||
|
||
</div><!-- /tab-reader -->
|