From 7748834a0fcc1bb3b3fd3d8131ab201941a9ca95 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Fri, 24 Apr 2026 19:07:58 +0200 Subject: [PATCH] =?UTF-8?q?fix(f5tts):=20Ref-WAV=20Preprocessing=20?= =?UTF-8?q?=E2=80=94=20Loudness=20+=20Silence-Trim?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F5-TTS reagiert empfindlich auf leise / verrauschte / zerhackte Referenzen — wir haben bisher nur auf 24kHz mono + 10s geclipped. Jetzt zusaetzlich: - silenceremove am Anfang (bis Speech einsetzt, <-50dB) - silenceremove am Ende (0.5s Stille nach letzter Speech = Cutoff) - loudnorm -16 LUFS (EBU R128) fuer konsistente Amplitude Damit sieht das Modell saubere, konstant laute Referenz-Audios statt kaputter Clips mit Ausklang oder leiser Aufnahme. Besonders bei Deutsch (wo F5TTS_v1_Base schwach ist) hilft jede Input-Konsistenz der Quali. Co-Authored-By: Claude Opus 4.7 (1M context) --- xtts/f5tts/bridge.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/xtts/f5tts/bridge.py b/xtts/f5tts/bridge.py index f1e9603..e409568 100644 --- a/xtts/f5tts/bridge.py +++ b/xtts/f5tts/bridge.py @@ -256,39 +256,48 @@ def voice_paths(name: str) -> tuple[Path, Path]: def normalize_ref_wav(src_wav: Path, max_seconds: float = REF_MAX_SECONDS) -> tuple[Path, bool]: """Bringt die Referenz-WAV in F5-TTS-freundliche Form: - 24kHz mono + max max_seconds Dauer. Original wird ueberschrieben wenn - Aenderungen noetig waren. + + * 24kHz mono + * max max_seconds Dauer + * Stille am Anfang + Ende abgeschnitten (silenceremove-Filter) + * Lautheit auf -16 LUFS normalisiert (loudnorm-Filter) damit + das Modell konsistente Amplituden sieht + + F5-TTS reagiert empfindlich auf leise / verrauschte / zerhackte + Referenzen. Konsistente, saubere Input-Lautheit hilft der Quali. Returns: (path, was_modified) — was_modified=True wenn die Datei wirklich geaendert wurde (Caller sollte dann den passenden .txt invalidieren). """ - try: - info = sf.info(str(src_wav)) - # Schon gut? Sample-Rate, Kanaele und Dauer passen? - if (info.samplerate == TARGET_SR and info.channels == 1 - and info.duration <= max_seconds + 0.1): - return src_wav, False - except Exception: - info = None - tmp_out = src_wav.with_suffix(".conv.wav") + # silenceremove am Anfang: bis -50dB gesprochen wird + # silenceremove am Ende: ueber -50dB rein, dann 0.5s stille als Cutoff + # loudnorm: EBU R128, Ziel -16 LUFS + af = ("silenceremove=start_periods=1:start_duration=0.05:start_threshold=-50dB," + "silenceremove=stop_periods=1:stop_duration=0.5:stop_threshold=-50dB," + "loudnorm=I=-16:TP=-1.5:LRA=11") cmd = ["ffmpeg", "-y", "-i", str(src_wav), + "-af", af, "-ar", str(TARGET_SR), "-ac", "1", "-t", str(max_seconds), "-f", "wav", str(tmp_out)] r = subprocess.run(cmd, capture_output=True, timeout=30) if r.returncode != 0: logger.warning("ffmpeg-Normalisierung von %s fehlgeschlagen: %s", - src_wav, r.stderr.decode(errors="replace")[:200]) + src_wav, r.stderr.decode(errors="replace")[:300]) try: tmp_out.unlink() except OSError: pass return src_wav, False os.replace(tmp_out, src_wav) - logger.info("Referenz-WAV normalisiert: %s (24kHz mono, max %.1fs)", - src_wav.name, max_seconds) + try: + info = sf.info(str(src_wav)) + logger.info("Referenz-WAV normalisiert: %s (%.1fs, %dHz mono, -16 LUFS, silence getrimmt)", + src_wav.name, info.duration, info.samplerate) + except Exception: + logger.info("Referenz-WAV normalisiert: %s", src_wav.name) return src_wav, True