Consolidate speaker labels after recast passes too, not only after casting (v1.20.34)

Consolidation ran only at cast completion, but recast/repair passes assign
speakers of their own and can re-introduce the variants just removed —
confirmed live: a freshly cast book gained "Alrik" alongside "Alrik von
Blautann" during auto-repair, splitting one character across two voices again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-08-12 14:57:56 +02:00
parent cd5c325265
commit 4d3e4f8e7c
5 changed files with 19 additions and 5 deletions

View File

@ -22,6 +22,11 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
- **Emotion instructions are now always written in English**, even for non-English voices (the spoken text and the native-accent clause stay in the book's own language). Confirmed by controlled A/B testing — same line, same voice, only the instruct language varying — that Qwen3-TTS follows English emotion instructions far more reliably: German instructs produced barely-differentiated output, while English instructs yield a clean, correctly-ordered prosodic gradient (whisper 128 Hz → sad 142 → neutral 179 → scared 203 → happy 225 → angry 269 Hz), with sensible duration changes too (sad slowest, scared fastest).
- **Fish-Speech generation parameters (`temperature` / `top_p` / `repetition_penalty`) were never forwarded.** Every Fish-Speech line synthesized at the server's fixed defaults, ignoring the app's per-backend stability settings — the only backend not routed through the shared `_apply_tts_extra_params` helper.
## [1.20.34] — 2026-08-12
### Fixed
- **Repair passes could re-introduce the speaker label variants the cast had just consolidated.** Consolidation ran only when casting finished, but every recast/repair pass assigns speakers of its own — confirmed live on a freshly cast book, which gained "Alrik" alongside "Alrik von Blautann" during auto-repair and would have split one character across two voices again. Consolidation now runs after each recast pass as well, and reports what it merged.
## [1.20.33] — 2026-08-12
### Added

View File

@ -1 +1 @@
1.20.33
1.20.34

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@
<meta name="format-detection" content="telephone=no">
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#2563EB">
<meta name="app-version" content="1.20.33">
<meta name="app-version" content="1.20.34">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/icon.svg">
@ -27,7 +27,7 @@
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/style.css?v=1.20.33">
<link rel="stylesheet" href="/static/style.css?v=1.20.34">
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
@ -378,7 +378,7 @@ window.toggleNavTree = function(treeId, chevronId) {
</script>
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
<script src="/static/loader.js?v=1.20.33"></script>
<script src="/static/loader.js?v=1.20.34"></script>
</body>
</html>

View File

@ -4786,6 +4786,15 @@ async function audiobookRecastUnknown(overrideUrl, overrideModel, options = {})
return;
}
// Repair passes assign speakers of their own, so they can re-introduce the
// label variants the cast-time consolidation just removed — confirmed live:
// a clean cast gained "Alrik" alongside "Alrik von Blautann" during
// auto-repair, which would have split one character across two voices again.
{ const _ident = _audiobookConsolidateSpeakerAliases(segs);
if (_ident.merged) {
console.info('[recast] consolidated', _ident.merged, 'speaker label variant(s),', _ident.moved, 'line(s)');
view.note(`Merged ${_ident.merged} duplicate character label${_ident.merged !== 1 ? 's' : ''} (${_ident.moved} lines).`);
} }
if (_audiobook.lastText) _abSaveDraft(_audiobook.segments || [], _audiobook.roster || [], _audiobook.lastText, _rcDone, _rcTotal);
const speakers = new Set(segs.filter(s => s.type === 'dialogue' && s.speaker).map(s => s.speaker));
const summary = `${speakers.size} character${speakers.size !== 1 ? 's' : ''} · ${segs.length} segments`;