Reject phantom speakers invented from German noun phrases (v1.20.35)
The caster emits fragments of noun phrases as characters — "in seinem Inneren" -> Inneren, "die Hand" -> Hand, "mein Geliebter" -> Geliebten, "der Wind" -> Wind. Each became a cast entry with its own voice while the real speaker's line was lost to a phantom; measured against a hand-corrected book this was a large part of why a fresh cast produced 73 speakers where the human had 62. Such lines are now reset to Unknown — visibly unresolved and still recoverable by the repair passes, rather than silently wrong. Applied to a real cast: 5 phantoms removed, 73 -> 68 speakers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
4d3e4f8e7c
commit
35cb21ad70
6
static/dist/main.min.js
vendored
6
static/dist/main.min.js
vendored
File diff suppressed because one or more lines are too long
@ -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.34">
|
||||
<meta name="app-version" content="1.20.35">
|
||||
<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.34">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.20.35">
|
||||
|
||||
|
||||
<!-- ── 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.34"></script>
|
||||
<script src="/static/loader.js?v=1.20.35"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1079,6 +1079,36 @@ function _abIdentityTokens(name) {
|
||||
while (prev !== n) { prev = n; n = n.replace(_AB_TITLE_PREFIX, ''); }
|
||||
return n.split(/[\s\-]+/).filter(Boolean);
|
||||
}
|
||||
// Speaker labels that are really fragments of a German noun phrase, not people:
|
||||
// "in seinem Inneren" -> Inneren, "die Hand" -> Hand, "mein Geliebter" ->
|
||||
// Geliebten, "der Wind" -> Wind. The caster emits these as characters, each then
|
||||
// gets a cast entry and its own voice, and the real speaker's line is lost to a
|
||||
// phantom — measured against a hand-corrected book, they were a large part of
|
||||
// why a fresh cast produced 73 speakers where the human had 62. A line credited
|
||||
// to one of these is better left Unknown: visibly unresolved, and still
|
||||
// recoverable by the repair passes, instead of silently wrong.
|
||||
const _AB_NOT_A_CHARACTER = new Set([
|
||||
'inneren','innere','herz','herzen','hand','hände','haenden','kopf','stimme','stimmen','wind','feuer','flamme','flammen',
|
||||
'dunkelheit','stille','schatten','licht','nacht','angst','schmerz','gedanke','gedanken','traum','träume','geliebten',
|
||||
'geliebter','geliebte','blondschopf','antwort','echo','ferne','nähe','naehe','tiefe','wasser','regen','donner','erde',
|
||||
'himmel','sonne','mond','tod','leben','seele','geist der','wahrheit','zweifel','hoffnung','erinnerung','vergangenheit'
|
||||
]);
|
||||
function _abIsImplausibleSpeaker(name) {
|
||||
const n = String(name || '').trim().toLowerCase().replace(/^(?:der|die|das|den|dem|ein|eine|einer|einem)\s+/, '');
|
||||
if (!n) return false;
|
||||
return _AB_NOT_A_CHARACTER.has(n);
|
||||
}
|
||||
function _audiobookDropPhantomSpeakers(segments) {
|
||||
let dropped = 0;
|
||||
const names = new Set();
|
||||
for (const s of segments) {
|
||||
if (s?.type !== 'dialogue') continue;
|
||||
if (_abIsImplausibleSpeaker(s.speaker)) { names.add(s.speaker); s.speaker = 'Unknown'; dropped++; }
|
||||
}
|
||||
return { dropped, names: [...names] };
|
||||
}
|
||||
window._audiobookDropPhantomSpeakers = _audiobookDropPhantomSpeakers;
|
||||
|
||||
function _audiobookConsolidateSpeakerAliases(segments) {
|
||||
const count = new Map();
|
||||
for (const s of segments) {
|
||||
@ -4790,6 +4820,8 @@ async function audiobookRecastUnknown(overrideUrl, overrideModel, options = {})
|
||||
// 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 _ph = _audiobookDropPhantomSpeakers(segs);
|
||||
if (_ph.dropped) console.info('[recast] dropped', _ph.dropped, 'phantom speaker line(s):', _ph.names.join(', ')); }
|
||||
{ const _ident = _audiobookConsolidateSpeakerAliases(segs);
|
||||
if (_ident.merged) {
|
||||
console.info('[recast] consolidated', _ident.merged, 'speaker label variant(s),', _ident.moved, 'line(s)');
|
||||
@ -5352,6 +5384,8 @@ async function audiobookCast(overrideUrl, overrideModel, resume) {
|
||||
const _mergedSegs = _audiobookMergeAdjacentSameSpeaker(_rejoined);
|
||||
allSegments.length = 0;
|
||||
allSegments.push(..._mergedSegs);
|
||||
{ const _ph = _audiobookDropPhantomSpeakers(allSegments);
|
||||
if (_ph.dropped) console.info('[cast] dropped', _ph.dropped, 'phantom speaker line(s):', _ph.names.join(', ')); }
|
||||
{ const _ident = _audiobookConsolidateSpeakerAliases(allSegments);
|
||||
if (_ident.merged) {
|
||||
console.info('[cast] consolidated', _ident.merged, 'speaker label variant(s),', _ident.moved, 'line(s)');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user