Do not auto-attribute segments that fuse several speakers' turns (v1.20.26)

A trailing inquit names only the last speaker, so applying it to a block
holding multiple turns silently mis-credited the earlier one — a wrong
attribution is invisible where an Unknown is not. Such blocks are now skipped
by the grammar rules and left for the LLM pass or a manual split.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-08-12 10:44:34 +02:00
parent 4d6eb7bd78
commit 14ed0c89e7
5 changed files with 19 additions and 7 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.26] — 2026-08-12
### Fixed
- **Segments fusing two speakers' turns are no longer auto-attributed to one of them.** A trailing inquit only names the *last* speaker, so applying it to a block containing several turns ("…ausliefern und …" + "Er hat es nicht so gemeint" + ", unterbrach die blonde Perdia Lysandra.") silently mis-credited the earlier turn — worse than leaving it Unknown, because a wrong attribution is invisible while an Unknown is not. Blocks holding multiple turns are now skipped by the grammar rules and left for the LLM pass or manual splitting.
## [1.20.25] — 2026-08-12
### Fixed

View File

@ -1 +1 @@
1.20.25
1.20.26

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.25">
<meta name="app-version" content="1.20.26">
<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.25">
<link rel="stylesheet" href="/static/style.css?v=1.20.26">
<!-- ── 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.25"></script>
<script src="/static/loader.js?v=1.20.26"></script>
</body>
</html>

View File

@ -804,8 +804,15 @@ function audiobookResolveUnknowns(segs, prevTail, roster) {
// mistyped it as dialogue — confirmed live: the quote before such a segment
// stayed Unknown purely because the inquit carried the wrong type. Accept
// either type here and repair the type while we are at it.
const _nextIsInquitText = next && new RegExp('^\\W{0,3}(?:' + AB_SPEECH_VERBS + ')\\b').test(String(next.text || '').trim());
if (!who && (next?.type === 'narration' || _nextIsInquitText)) {
// A segment holding several turns separated by a blank line is TWO speakers
// fused into one ("...ausliefern und ...\n\nEr hat es nicht so gemeint" —
// Lysandra, then Perdia interrupting). A trailing inquit only names the LAST
// speaker, so applying it to the whole block silently mis-credits the earlier
// turn. Leaving it Unknown is the honest outcome: visibly unresolved beats
// confidently wrong, and the split can then be made by hand or by the LLM pass.
const _isFusedTurns = /\n\s*\n/.test(String(s.text || '').trim());
const _nextIsInquitText = !_isFusedTurns && next && new RegExp('^\\W{0,3}(?:' + AB_SPEECH_VERBS + ')\\b').test(String(next.text || '').trim());
if (!who && !_isFusedTurns && (next?.type === 'narration' || _nextIsInquitText)) {
const nt = String(next.text || '').trim();
// Split quote whose inquit uses a PRONOUN rather than a name:
// "»Ja, ungewöhnlich«" + ", antwortete er knapp." The rules below all