chore: add local maintenance utilities
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
Some checks are pending
Test and SonarQube / quality (push) Waiting to run
This commit is contained in:
parent
037f3dd74d
commit
b43bacc275
24
patch_changelog.py
Normal file
24
patch_changelog.py
Normal file
@ -0,0 +1,24 @@
|
||||
import re
|
||||
|
||||
with open('CHANGELOG.md', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
new_entry = """## [1.22.0] — 2026-08-23
|
||||
|
||||
### Added
|
||||
- **Audiobook Export Modal.** Introduced a comprehensive GUI for exporting audiobooks directly from the Rehearser.
|
||||
- **Granular Export Controls.** Users can now select specific chapters to export, define the output format (one MP3 per chapter vs. single book file), and configure custom server-side save locations.
|
||||
- **Pacing Configuration.** Added UI settings to adjust pacing with precise pause durations between paragraphs and chapters.
|
||||
- **Tone Warning Integration.** Warnings for out-of-bounds tone settings are now displayed within the export modal for safety before batch generation.
|
||||
|
||||
### Fixed
|
||||
- **UI Synchronization.** Fixed an issue where the Rehearser's green dots for cached audio lines would not synchronize reliably during navigation updates.
|
||||
- **Bundle Stability.** Addressed a script hoisting issue in the production bundle that caused silent initialization failures and a "Loading..." stall.
|
||||
|
||||
"""
|
||||
|
||||
content = content.replace("---\n\n## [1.21.1]", "---\n\n" + new_entry + "## [1.21.1]")
|
||||
|
||||
with open('CHANGELOG.md', 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
185
scratch/patch_audiobook.py
Normal file
185
scratch/patch_audiobook.py
Normal file
@ -0,0 +1,185 @@
|
||||
import re
|
||||
|
||||
with open('static/js/audiobook.js', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# 1. Change listener
|
||||
content = content.replace(
|
||||
"$('reh-tb-audiobook')?.addEventListener('click', audiobookExport);",
|
||||
"$('reh-tb-audiobook')?.addEventListener('click', audiobookExportDialog);"
|
||||
)
|
||||
|
||||
# 2. Rename audiobookExport to accept opts
|
||||
content = content.replace(
|
||||
"async function audiobookExport() {",
|
||||
"async function audiobookExport(opts = {}) {"
|
||||
)
|
||||
|
||||
# 3. Handle activeBuckets inside audiobookExport
|
||||
content = content.replace(
|
||||
"const allIdx = buckets.flatMap(b => b.idx);",
|
||||
"""let activeBuckets = buckets;
|
||||
if (opts.selectedChapterIndices) {
|
||||
activeBuckets = buckets.filter((_, i) => opts.selectedChapterIndices.includes(i));
|
||||
}
|
||||
const allIdx = activeBuckets.flatMap(b => b.idx);"""
|
||||
)
|
||||
|
||||
# 4. Modify the loop that creates files
|
||||
loop_find = """ for (let c = 0; c < buckets.length; c++) {
|
||||
const blobs = buckets[c].idx.map(i => wavClips.get(i)).filter(Boolean);"""
|
||||
|
||||
loop_replace = """ if (opts.format === 'single') {
|
||||
let masterBlobs = [];
|
||||
for (let c = 0; c < activeBuckets.length; c++) {
|
||||
const blobs = activeBuckets[c].idx.map(i => wavClips.get(i)).filter(Boolean);
|
||||
if (!blobs.length) continue;
|
||||
if (encMsg) encMsg.textContent = `Merging chapter ${c + 1} / ${activeBuckets.length}…`;
|
||||
let mergedWav = await mergeWavBlobs(blobs, pauses.paragraphMs);
|
||||
if (c > 0 && activeBuckets[c].title && (pauses.chapterMs > 0 || _abChapterSfxDataUrl())) {
|
||||
const ref = _parseWavBytes(new Uint8Array(await mergedWav.arrayBuffer())).fmt;
|
||||
const lead = [];
|
||||
const sfx = await _abChapterSfxWavBlob(ref);
|
||||
if (sfx) lead.push(sfx);
|
||||
if (pauses.chapterMs > 0) lead.push(_buildWavBlob(_silencePcmBytes(pauses.chapterMs, ref), ref));
|
||||
if (lead.length) mergedWav = await mergeWavBlobs([...lead, mergedWav]);
|
||||
}
|
||||
masterBlobs.push(mergedWav);
|
||||
}
|
||||
if (masterBlobs.length) {
|
||||
if (encMsg) encMsg.textContent = `Encoding full book to MP3…`;
|
||||
let finalWav = await mergeWavBlobs(masterBlobs, 0);
|
||||
let blob = finalWav;
|
||||
try {
|
||||
const encResp = await fetch('/api/audio/encode-mp3', { method: 'POST', body: finalWav });
|
||||
if (encResp.ok) blob = await encResp.blob();
|
||||
} catch (e) {}
|
||||
const ext = blob === finalWav ? 'wav' : 'mp3';
|
||||
const name = `${title}.${ext}`;
|
||||
if (typeof readerDownload === 'function') readerDownload(blob, name);
|
||||
try {
|
||||
let exportUrl = `/api/audiobook-export/${encodeURIComponent(bookForExport)}/${encodeURIComponent(name)}`;
|
||||
if (opts.folder) exportUrl += `?folder=${encodeURIComponent(opts.folder)}`;
|
||||
const r = await fetch(exportUrl, { method: 'POST', body: blob });
|
||||
if (r.ok) savedFiles.push({ name, url: exportUrl });
|
||||
} catch (e) {}
|
||||
files++;
|
||||
}
|
||||
} else {
|
||||
for (let c = 0; c < activeBuckets.length; c++) {
|
||||
const blobs = activeBuckets[c].idx.map(i => wavClips.get(i)).filter(Boolean);"""
|
||||
|
||||
content = content.replace(loop_find, loop_replace)
|
||||
|
||||
# 5. Fix references inside the else block
|
||||
content = content.replace(
|
||||
"encMsg.textContent = `Merging chapter ${c + 1} / ${buckets.length}…`;",
|
||||
"encMsg.textContent = `Merging chapter ${c + 1} / ${activeBuckets.length}…`;"
|
||||
)
|
||||
content = content.replace(
|
||||
"encMsg.textContent = `Encoding chapter ${c + 1} / ${buckets.length}…`;",
|
||||
"encMsg.textContent = `Encoding chapter ${c + 1} / ${activeBuckets.length}…`;"
|
||||
)
|
||||
content = content.replace(
|
||||
"if (c > 0 && buckets[c].title && (pauses.chapterMs > 0 || _abChapterSfxDataUrl())) {",
|
||||
"if (c > 0 && activeBuckets[c].title && (pauses.chapterMs > 0 || _abChapterSfxDataUrl())) {"
|
||||
)
|
||||
content = content.replace(
|
||||
"const ch = buckets[c].title ? ' ' + readerSafeName(buckets[c].title) : '';\n const name = (realChapters || buckets.length > 1) ? `${title} - ${String(c + 1).padStart(2, '0')}${ch}.${ext}` : `${title}.${ext}`;",
|
||||
"const ch = activeBuckets[c].title ? ' ' + readerSafeName(activeBuckets[c].title) : '';\n const name = (realChapters || activeBuckets.length > 1) ? `${title} - ${String(c + 1).padStart(2, '0')}${ch}.${ext}` : `${title}.${ext}`;"
|
||||
)
|
||||
content = content.replace(
|
||||
"if (r.ok) savedFiles.push({ name, url: `/api/audiobook-export/${encodeURIComponent(bookForExport)}/${encodeURIComponent(name)}` });",
|
||||
"if (r.ok) savedFiles.push({ name, url: exportUrl });"
|
||||
)
|
||||
# add folder query param in else block
|
||||
content = content.replace(
|
||||
"const r = await fetch(`/api/audiobook-export/${encodeURIComponent(bookForExport)}/${encodeURIComponent(name)}`, { method: 'POST', body: blob });",
|
||||
"let exportUrl = `/api/audiobook-export/${encodeURIComponent(bookForExport)}/${encodeURIComponent(name)}`;\n if (opts.folder) exportUrl += `?folder=${encodeURIComponent(opts.folder)}`;\n const r = await fetch(exportUrl, { method: 'POST', body: blob });"
|
||||
)
|
||||
|
||||
|
||||
# close the else block
|
||||
close_else = """ files++;
|
||||
await new Promise(r => setTimeout(r, 400)); // breathing room between chunk encode/uploads
|
||||
}
|
||||
} // end else
|
||||
"""
|
||||
content = content.replace(
|
||||
"await new Promise(r => setTimeout(r, 400)); // breathing room between chunk encode/uploads\n }",
|
||||
close_else
|
||||
)
|
||||
|
||||
# 6. Add audiobookExportDialog
|
||||
dialog_code = """
|
||||
window.audiobookExportDialog = function() {
|
||||
if (_audiobook.running) return;
|
||||
if (!window.rehState || !(rehState.lines || []).length) { toast('Open a script in the rehearser first', 'error'); return; }
|
||||
if (!rehState.backend) { toast('Select a TTS backend in the rehearser first', 'error'); return; }
|
||||
|
||||
const speakable = i => {
|
||||
const l = rehState.lines[i];
|
||||
if (!l || l.ignored || l.hidden) return false;
|
||||
if (l.type === 'dialog') { const c = rehState.cast[l.speaker]; return !!(c && c.voice && c.voice !== 'me'); }
|
||||
return !!(rehState.narratorVoice && (l.text || '').trim());
|
||||
};
|
||||
const buckets = [];
|
||||
let cur = null;
|
||||
rehState.lines.forEach((l, i) => {
|
||||
if (audiobookIsChapter(l)) { cur = { title: (typeof stripMarkdown === 'function' ? stripMarkdown(l.text) : l.text).trim().slice(0, 50), idx: [] }; buckets.push(cur); }
|
||||
if (speakable(i)) { if (!cur) { cur = { title: '', idx: [] }; buckets.push(cur); } cur.idx.push(i); }
|
||||
});
|
||||
|
||||
const listEl = document.getElementById('ab-export-chapter-list');
|
||||
if (listEl) {
|
||||
listEl.innerHTML = '';
|
||||
const countEl = document.getElementById('ab-export-chapter-count');
|
||||
if (countEl) countEl.textContent = buckets.length;
|
||||
buckets.forEach((b, i) => {
|
||||
const lbl = document.createElement('label');
|
||||
lbl.style.display = 'block';
|
||||
lbl.style.marginBottom = '4px';
|
||||
const chk = document.createElement('input');
|
||||
chk.type = 'checkbox';
|
||||
chk.checked = true;
|
||||
chk.value = i;
|
||||
chk.className = 'ab-export-chapter-cb';
|
||||
lbl.appendChild(chk);
|
||||
lbl.appendChild(document.createTextNode(' Chapter ' + (i+1) + (b.title ? ': ' + b.title : '')));
|
||||
listEl.appendChild(lbl);
|
||||
});
|
||||
}
|
||||
|
||||
const warn = document.getElementById('reh-tone-warn');
|
||||
const slot = document.getElementById('ab-export-tone-warn-slot');
|
||||
if (warn && slot) {
|
||||
warn.hidden = false;
|
||||
slot.appendChild(warn);
|
||||
}
|
||||
|
||||
if (typeof _abInitPauseUI === 'function') _abInitPauseUI();
|
||||
|
||||
const modal = document.getElementById('audiobook-export-modal');
|
||||
if (modal) modal.hidden = false;
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.getElementById('ab-export-modal-close')?.addEventListener('click', () => document.getElementById('audiobook-export-modal').hidden = true);
|
||||
document.getElementById('ab-export-modal-cancel')?.addEventListener('click', () => document.getElementById('audiobook-export-modal').hidden = true);
|
||||
document.getElementById('ab-export-modal-start')?.addEventListener('click', () => {
|
||||
document.getElementById('audiobook-export-modal').hidden = true;
|
||||
const formatEl = document.querySelector('input[name="ab_export_format"]:checked');
|
||||
const format = formatEl ? formatEl.value : 'chapter';
|
||||
const folderEl = document.getElementById('ab-export-server-folder');
|
||||
const folder = folderEl ? folderEl.value : '';
|
||||
const checkboxes = Array.from(document.querySelectorAll('.ab-export-chapter-cb'));
|
||||
const selectedChapterIndices = checkboxes.filter(cb => cb.checked).map(cb => parseInt(cb.value, 10));
|
||||
audiobookExport({ format, selectedChapterIndices, folder });
|
||||
});
|
||||
});
|
||||
"""
|
||||
|
||||
content += dialog_code
|
||||
|
||||
with open('static/js/audiobook.js', 'w') as f:
|
||||
f.write(content)
|
||||
51
scratch/patch_sync_dots.py
Normal file
51
scratch/patch_sync_dots.py
Normal file
@ -0,0 +1,51 @@
|
||||
import re
|
||||
|
||||
with open('static/js/rehearser.js', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# 1. In buildScriptPage, use serverCachedLines
|
||||
content = content.replace(
|
||||
"const isCached = rehState.synthCache.has(i);",
|
||||
"const isCached = rehState.synthCache.has(i) || (rehState.serverCachedLines && rehState.serverCachedLines.has(i));"
|
||||
)
|
||||
|
||||
# 2. Modify _lineAudioSyncDots to populate serverCachedLines and remove scanId
|
||||
old_sync_dots = """let _lineAudioSyncedFor = null; // `${book}::${lineCount}` — avoid re-running the same scan on every small re-render
|
||||
async function _lineAudioSyncDots() {
|
||||
const book = _lineAudioBookName();
|
||||
const scanId = `${book}::${rehState.lines.length}`;
|
||||
if (_lineAudioSyncedFor === scanId) return;
|
||||
_lineAudioSyncedFor = scanId;
|
||||
_ensureNarrator();"""
|
||||
|
||||
new_sync_dots = """let _lineAudioSyncedFor = null; // unused now, kept for compat
|
||||
async function _lineAudioSyncDots() {
|
||||
if (!rehState.serverCachedLines) rehState.serverCachedLines = new Set();
|
||||
const book = _lineAudioBookName();
|
||||
_ensureNarrator();"""
|
||||
|
||||
content = content.replace(old_sync_dots, new_sync_dots)
|
||||
|
||||
old_foreach = """ const { existing } = await r.json();
|
||||
(existing || []).forEach(key => {
|
||||
const idx = keyToIdx.get(key);
|
||||
if (idx == null) return;
|
||||
rehState.staleLines.delete(idx);
|
||||
_markSynthDot(idx, 'ok');
|
||||
});
|
||||
} catch (_) { /* dots just stay as-is; playback still checks the disk cache lazily either way */ }"""
|
||||
|
||||
new_foreach = """ const { existing } = await r.json();
|
||||
(existing || []).forEach(key => {
|
||||
const idx = keyToIdx.get(key);
|
||||
if (idx == null) return;
|
||||
rehState.staleLines.delete(idx);
|
||||
rehState.serverCachedLines.add(idx);
|
||||
_markSynthDot(idx, 'ok');
|
||||
});
|
||||
} catch (_) { /* dots just stay as-is; playback still checks the disk cache lazily either way */ }"""
|
||||
|
||||
content = content.replace(old_foreach, new_foreach)
|
||||
|
||||
with open('static/js/rehearser.js', 'w') as f:
|
||||
f.write(content)
|
||||
48
scratch/patch_sync_dots2.py
Normal file
48
scratch/patch_sync_dots2.py
Normal file
@ -0,0 +1,48 @@
|
||||
import re
|
||||
|
||||
with open('static/js/rehearser.js', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# 1. Revert buildScriptPage change
|
||||
content = content.replace(
|
||||
"const isCached = rehState.synthCache.has(i) || (rehState.serverCachedLines && rehState.serverCachedLines.has(i));",
|
||||
"const isCached = rehState.synthCache.has(i);"
|
||||
)
|
||||
|
||||
# 2. Revert serverCachedLines in _lineAudioSyncDots
|
||||
old_sync_dots = """let _lineAudioSyncedFor = null; // unused now, kept for compat
|
||||
async function _lineAudioSyncDots() {
|
||||
if (!rehState.serverCachedLines) rehState.serverCachedLines = new Set();
|
||||
const book = _lineAudioBookName();
|
||||
_ensureNarrator();"""
|
||||
|
||||
new_sync_dots = """let _lineAudioSyncedFor = null; // unused now, kept for compat
|
||||
async function _lineAudioSyncDots() {
|
||||
const book = _lineAudioBookName();
|
||||
_ensureNarrator();"""
|
||||
|
||||
content = content.replace(old_sync_dots, new_sync_dots)
|
||||
|
||||
old_foreach = """ const { existing } = await r.json();
|
||||
(existing || []).forEach(key => {
|
||||
const idx = keyToIdx.get(key);
|
||||
if (idx == null) return;
|
||||
rehState.staleLines.delete(idx);
|
||||
rehState.serverCachedLines.add(idx);
|
||||
_markSynthDot(idx, 'ok');
|
||||
});
|
||||
} catch (_) { /* dots just stay as-is; playback still checks the disk cache lazily either way */ }"""
|
||||
|
||||
new_foreach = """ const { existing } = await r.json();
|
||||
(existing || []).forEach(key => {
|
||||
const idx = keyToIdx.get(key);
|
||||
if (idx == null) return;
|
||||
rehState.staleLines.delete(idx);
|
||||
_markSynthDot(idx, 'ok');
|
||||
});
|
||||
} catch (_) { /* dots just stay as-is; playback still checks the disk cache lazily either way */ }"""
|
||||
|
||||
content = content.replace(old_foreach, new_foreach)
|
||||
|
||||
with open('static/js/rehearser.js', 'w') as f:
|
||||
f.write(content)
|
||||
22
scratch/test_bundle.js
Normal file
22
scratch/test_bundle.js
Normal file
@ -0,0 +1,22 @@
|
||||
const { JSDOM } = require("jsdom");
|
||||
const dom = new JSDOM(`<!DOCTYPE html><html><body><div id="status-message"></div></body></html>`);
|
||||
global.window = dom.window;
|
||||
global.document = dom.window.document;
|
||||
global.navigator = dom.window.navigator;
|
||||
global.location = dom.window.location;
|
||||
global.localStorage = { getItem: () => null, setItem: () => null, removeItem: () => null };
|
||||
global.fetch = () => Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
||||
global.$ = (id) => document.getElementById(id) || document.createElement('div');
|
||||
global.toast = () => {};
|
||||
global.escapeHtml = () => '';
|
||||
global.escHtml = () => '';
|
||||
global.debounce = () => {};
|
||||
global.audiobookIsChapter = () => false;
|
||||
global.initCloneSampleText = () => {};
|
||||
|
||||
try {
|
||||
require('../static/dist/main.min.js');
|
||||
console.log("Bundle executed successfully without top-level errors!");
|
||||
} catch (e) {
|
||||
console.error("Bundle error:", e);
|
||||
}
|
||||
13
scratch/test_cache.py
Normal file
13
scratch/test_cache.py
Normal file
@ -0,0 +1,13 @@
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import json
|
||||
|
||||
book = "Hohlbein Hennen - DSA - Das Jahr des Greifen 03 - Die Amazone"
|
||||
url = f"http://127.0.0.1:8010/api/line-audio/{urllib.parse.quote(book)}/check"
|
||||
data = json.dumps({"keys": ["c79dbe84b9ed7b1780891ba905951bb6", "ee3559287060ae3530fb9d7f088ff9c8"]}).encode('utf-8')
|
||||
req = urllib.request.Request(url, data=data, headers={'Content-Type': 'application/json'}, method='POST')
|
||||
try:
|
||||
with urllib.request.urlopen(req) as f:
|
||||
print(f.read().decode('utf-8'))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
67
scratch_paginate.js
Normal file
67
scratch_paginate.js
Normal file
@ -0,0 +1,67 @@
|
||||
// Printable content height inside a page (A4 minus vertical padding)
|
||||
const PAGE_CONTENT = 1027; // 1123 − 56 − 40
|
||||
const TITLE_SPACE = 64; // page-1 title block allowance
|
||||
|
||||
let pageNum = 0, page = null, used = 0;
|
||||
let isFirstPage = true;
|
||||
|
||||
// Pre-scan for the first PDF page number to start numbering correctly
|
||||
if (respectBreaks) {
|
||||
for (const b of blocks) {
|
||||
if (b.classList.contains('reh-block-pagebreak')) {
|
||||
const i = parseInt(b.dataset.index);
|
||||
const l = rehState.lines[i];
|
||||
if (l && l.page) {
|
||||
// If the pagebreak is for page N, the content before it is N or N-1 depending on how breaks are placed.
|
||||
// Usually a page break marker MEANS "Start of Page N".
|
||||
// If the first element is NOT a pagebreak, it means the content belongs to the page BEFORE the first pagebreak.
|
||||
// We'll just set pageNum to l.page - 1 (or 1 if it's 1).
|
||||
pageNum = Math.max(0, l.page - (b === blocks[0] ? 0 : 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newPage = (forcedNum) => {
|
||||
if (forcedNum !== undefined) pageNum = forcedNum;
|
||||
else pageNum++;
|
||||
page = document.createElement('div');
|
||||
page.className = 'reh-paper';
|
||||
const num = document.createElement('div');
|
||||
num.className = 'reh-paper-num';
|
||||
num.textContent = pageNum + '.';
|
||||
page.appendChild(num);
|
||||
used = 0;
|
||||
if (isFirstPage && titleEl) {
|
||||
const t = titleEl.cloneNode(true);
|
||||
t.style.display = '';
|
||||
page.appendChild(t);
|
||||
used += TITLE_SPACE;
|
||||
}
|
||||
isFirstPage = false;
|
||||
wrap.appendChild(page);
|
||||
};
|
||||
newPage();
|
||||
|
||||
for (const block of blocks) {
|
||||
// PDF page-break marker: always start a new page here
|
||||
if (respectBreaks && block.classList.contains('reh-block-pagebreak')) {
|
||||
const i = parseInt(block.dataset.index);
|
||||
const l = rehState.lines[i];
|
||||
newPage(l && l.page ? l.page : undefined);
|
||||
continue; // don't move the marker div into the page
|
||||
}
|
||||
|
||||
// Measure block height (must be in DOM to measure → append then check)
|
||||
page.appendChild(block);
|
||||
const cs = getComputedStyle(block);
|
||||
const h = block.offsetHeight + (parseFloat(cs.marginTop) || 0) + (parseFloat(cs.marginBottom) || 0);
|
||||
|
||||
// If adding this block overflows the page (and page already has content), move to a new page
|
||||
if (!respectBreaks && used + h > PAGE_CONTENT && used > (isFirstPage ? TITLE_SPACE : 0)) {
|
||||
newPage();
|
||||
page.appendChild(block);
|
||||
}
|
||||
used += h;
|
||||
}
|
||||
49
scratchpad_prefetch.js
Normal file
49
scratchpad_prefetch.js
Normal file
@ -0,0 +1,49 @@
|
||||
let _rehPrefetchRunning = false;
|
||||
async function _rehPrefetchLines(fromIndex) {
|
||||
if (_rehPrefetchRunning) return;
|
||||
_rehPrefetchRunning = true;
|
||||
try {
|
||||
let prefetched = 0;
|
||||
for (let i = fromIndex; i < rehState.lines.length && prefetched < 2; i++) {
|
||||
if (!rehState.playing) break;
|
||||
const l = rehState.lines[i];
|
||||
if (!l || l.ignored || l.hidden || l.type === 'pagebreak') continue;
|
||||
|
||||
const isDialog = l.type === 'dialog';
|
||||
const cast = isDialog ? (rehState.cast[l.speaker] || { voice: '' }) : null;
|
||||
if (isDialog && (!cast.voice || cast.voice === 'me')) continue;
|
||||
if (!isDialog && (!rehState.narratorVoice || !(l.text || '').trim() || rehState.skipDescriptions)) continue;
|
||||
|
||||
if (rehState.synthCache.has(i) && !rehState.staleLines.has(i)) {
|
||||
prefetched++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const voice = isDialog ? cast.voice : rehState.narratorVoice;
|
||||
const cleanTxt = stripMarkdown(l.text);
|
||||
const instruct = isDialog ? _buildInstruct((cast.instruct || '').trim(), l.emotion, voice) : '';
|
||||
const text = isDialog ? _rehInlineTone(cleanTxt, l.emotion) : cleanTxt;
|
||||
|
||||
const book = _lineAudioBookName();
|
||||
const cacheKey = await _lineAudioCacheKey(text, voice, instruct);
|
||||
let blob = await _lineAudioCacheGet(book, cacheKey);
|
||||
|
||||
if (!blob) {
|
||||
try {
|
||||
blob = await fetchTtsPreviewBlob(voice, text, 'wav', instruct, _ttsBackendForVoice(voice, rehState.backend));
|
||||
_lineAudioCachePut(book, cacheKey, blob);
|
||||
} catch(e) {
|
||||
console.warn('Prefetch failed for line', i, e);
|
||||
continue; // skip counting as prefetched if it failed
|
||||
}
|
||||
}
|
||||
|
||||
rehState.synthCache.set(i, blob);
|
||||
rehState.staleLines.delete(i);
|
||||
_markSynthDot(i, 'ok');
|
||||
prefetched++;
|
||||
}
|
||||
} finally {
|
||||
_rehPrefetchRunning = false;
|
||||
}
|
||||
}
|
||||
15
static/js/dump_state.js
Normal file
15
static/js/dump_state.js
Normal file
@ -0,0 +1,15 @@
|
||||
(function() {
|
||||
const data = {
|
||||
linesLength: window.rehState ? (window.rehState.lines || []).length : 'no rehState',
|
||||
linesSample: window.rehState ? (window.rehState.lines || []).slice(0, 5) : [],
|
||||
a4Display: document.getElementById('reh-a4-page') ? document.getElementById('reh-a4-page').style.display : 'no a4',
|
||||
wrapChildrenCount: document.querySelector('.reh-page-wrap') ? document.querySelector('.reh-page-wrap').children.length : 'no wrap',
|
||||
paperHeights: Array.from(document.querySelectorAll('.reh-paper')).map(p => p.offsetHeight),
|
||||
paperChildrenCount: Array.from(document.querySelectorAll('.reh-paper')).map(p => p.children.length),
|
||||
scriptLinesChildrenCount: document.getElementById('reh-script-lines') ? document.getElementById('reh-script-lines').children.length : 'no script-lines',
|
||||
pageMode: window._pageMode || 'unknown',
|
||||
skipDesc: window.rehState ? window.rehState.skipDescriptions : 'unknown'
|
||||
};
|
||||
console.log(JSON.stringify(data, null, 2));
|
||||
alert(JSON.stringify(data, null, 2));
|
||||
})();
|
||||
30
workflows/comfyui-character-portrait.json.bak
Normal file
30
workflows/comfyui-character-portrait.json.bak
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"1": {
|
||||
"class_type": "CheckpointLoaderSimple",
|
||||
"inputs": {"ckpt_name": "flux1-dev-fp8.safetensors"}
|
||||
},
|
||||
"2": {
|
||||
"class_type": "CLIPTextEncode",
|
||||
"inputs": {"text": "", "clip": ["1", 1]}
|
||||
},
|
||||
"3": {
|
||||
"class_type": "CLIPTextEncode",
|
||||
"inputs": {"text": "blurry, low resolution, distorted face, duplicate person, extra limbs, malformed hands, cropped head, readable text, logo, watermark", "clip": ["1", 1]}
|
||||
},
|
||||
"4": {
|
||||
"class_type": "EmptyLatentImage",
|
||||
"inputs": {"width": 1024, "height": 1024, "batch_size": 1}
|
||||
},
|
||||
"5": {
|
||||
"class_type": "KSampler",
|
||||
"inputs": {"seed": 0, "steps": 28, "cfg": 3.5, "sampler_name": "euler", "scheduler": "simple", "denoise": 1.0, "model": ["1", 0], "positive": ["2", 0], "negative": ["3", 0], "latent_image": ["4", 0]}
|
||||
},
|
||||
"6": {
|
||||
"class_type": "VAEDecode",
|
||||
"inputs": {"samples": ["5", 0], "vae": ["1", 2]}
|
||||
},
|
||||
"7": {
|
||||
"class_type": "SaveImage",
|
||||
"inputs": {"filename_prefix": "voice-creator/portrait", "images": ["6", 0]}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user