Reduce XTTS tail hallucinations
This commit is contained in:
@@ -318,12 +318,12 @@ def clean_for_speech(text: str) -> str:
|
||||
|
||||
|
||||
def _split_chunk(text: str, limit: int = CHUNK_CHARS) -> list[str]:
|
||||
"""Return sentence-sized XTTS requests with a conservative hard ceiling.
|
||||
"""Return paragraph-sized XTTS requests with a conservative hard ceiling.
|
||||
|
||||
A sentence is deliberately never combined with the following sentence.
|
||||
Overlong sentences are split at clause boundaries first and at words only
|
||||
as a last resort. This preserves XTTS prosody without exposing it to an
|
||||
unbounded paragraph.
|
||||
Complete neighbouring sentences are combined while they fit. This avoids
|
||||
restarting the generative XTTS decoder after every short sentence, which
|
||||
can create invented tail syllables between sentences. Overlong sentences
|
||||
are split at clause boundaries first and at words only as a last resort.
|
||||
"""
|
||||
text = text.strip()
|
||||
if not text:
|
||||
@@ -331,38 +331,50 @@ def _split_chunk(text: str, limit: int = CHUNK_CHARS) -> list[str]:
|
||||
|
||||
sentences = re.split(r"(?<=[.!?])\s+|\s+(?=\d+[.)]\s)", text)
|
||||
chunks: list[str] = []
|
||||
current = ""
|
||||
for sentence in sentences:
|
||||
sentence = sentence.strip()
|
||||
if not sentence:
|
||||
continue
|
||||
if len(sentence) <= limit:
|
||||
chunks.append(sentence)
|
||||
candidate = f"{current} {sentence}".strip()
|
||||
if current and len(candidate) > limit:
|
||||
chunks.append(current)
|
||||
current = sentence
|
||||
else:
|
||||
current = candidate
|
||||
continue
|
||||
|
||||
if current:
|
||||
chunks.append(current)
|
||||
current = ""
|
||||
|
||||
# Retain commas in the preceding clause so XTTS can reproduce the
|
||||
# intended pause. Semicolons and colons were normalized earlier.
|
||||
clauses = re.split(r"(?<=,)\s+", sentence)
|
||||
current = ""
|
||||
long_current = ""
|
||||
for clause in clauses:
|
||||
clause = clause.strip()
|
||||
candidate = f"{current} {clause}".strip()
|
||||
if current and len(candidate) > limit:
|
||||
chunks.append(current)
|
||||
current = ""
|
||||
candidate = f"{long_current} {clause}".strip()
|
||||
if long_current and len(candidate) > limit:
|
||||
chunks.append(long_current)
|
||||
long_current = ""
|
||||
if len(clause) <= limit:
|
||||
current = f"{current} {clause}".strip()
|
||||
long_current = f"{long_current} {clause}".strip()
|
||||
continue
|
||||
|
||||
# A clause without a usable pause can still exceed the ceiling.
|
||||
for word in clause.split():
|
||||
candidate = f"{current} {word}".strip()
|
||||
if current and len(candidate) > limit:
|
||||
chunks.append(current)
|
||||
current = word
|
||||
candidate = f"{long_current} {word}".strip()
|
||||
if long_current and len(candidate) > limit:
|
||||
chunks.append(long_current)
|
||||
long_current = word
|
||||
else:
|
||||
current = candidate
|
||||
if current:
|
||||
chunks.append(current)
|
||||
long_current = candidate
|
||||
if long_current:
|
||||
chunks.append(long_current)
|
||||
if current:
|
||||
chunks.append(current)
|
||||
return chunks
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user