// Audiobook cast preview, Rehearser handoff, and chapter export UI. // Loaded after the casting pipeline and before audiobook.js; dependencies resolve at call time. (function (root) { // ── Editable attribution preview ───────────────────────────────────────────── async function audiobookOpenCurrentInRehearser() { const segs = _audiobook.segments || []; if (!segs.length) { toast('No cast to open', 'error'); return false; } // Build the record straight from the CURRENT in-memory segments and load // it directly — a prior version saved this to IndexedDB and then // re-fetched it by id before loading, which could hand back a stale // record (a read-after-write race, or a stale `_audiobook.rehId` left // over from an earlier pass) instead of the just-corrected data. Building // once and loading that same object removes the possibility entirely; // the DB write below is now just for persistence, not the source of what // gets loaded. if (typeof window.rehLoadRecord === 'function' && typeof _audiobookBuildRehRecord === 'function') { try { const rec = await _audiobookBuildRehRecord(segs); if (_audiobook.rehId) rec.id = _audiobook.rehId; document.getElementById('audiobook-preview')?.remove(); if (typeof navTo === 'function') navTo('s-rehearser'); window.rehLoadRecord(rec); // rehLoadRecord always lands on the Cast phase, but by the time you're // opening the Rehearser from the Audiobook pipeline, voices were already // assigned in Assign Voices — Cast there is pure redundant re-work. // Jump straight to Stage (phase 3), the line-by-line editing view. // showPhase() alone only toggles which phase
is visible — the // actual script/character panes are built by buildScriptPage(), same // as the Stage sub-tab's own click handler does. if (rehState.lines.length && typeof buildScriptPage === 'function') { buildScriptPage(); if (typeof showPhase === 'function') showPhase(3); if (typeof highlightCurrentLine === 'function') highlightCurrentLine(); } else if (typeof showPhase === 'function') showPhase(3); toast('Opened cast in Script Rehearser', 'success'); audiobookSaveAsRehearsal({ silent: true }).catch(() => {}); return true; } catch (err) { console.warn('[audiobook] failed to build rehearser record directly, falling back:', err); } } await audiobookSaveAsRehearsal({ silent: true }); const { script, emotions } = audiobookBuildScript(segs); await audiobookOpenInRehearser(script, (readerState.title || 'Audiobook'), emotions); if (rehState.lines.length && typeof buildScriptPage === 'function') { buildScriptPage(); if (typeof showPhase === 'function') showPhase(3); if (typeof highlightCurrentLine === 'function') highlightCurrentLine(); } else if (typeof showPhase === 'function') showPhase(3); return true; } function audiobookShowPreview() { audiobookOpenCurrentInRehearser(); return; const segs = _audiobook.segments || []; const roster = _audiobook.roster || []; document.getElementById('audiobook-preview')?.remove(); const ov = document.createElement('div'); ov.id = 'audiobook-preview'; ov.className = 'audiobook-overlay'; // datalist = Narrator + LLM roster + any speakers present in the segments (incl. Unknown) const speakerSet = [...new Set(['Narrator', ...roster, ...segs.filter(s => s.type === 'dialogue' && s.speaker).map(s => s.speaker)])]; const charCount = speakerSet.filter(n => !/^Unknown|Unbekannt/i.test(n)).length; const opts = speakerSet.map(n => `