Spaces:
Running on Zero
Running on Zero
Upload app.py
Browse files
app.py
CHANGED
|
@@ -565,17 +565,20 @@ def _take_score(i, med):
|
|
| 565 |
return s
|
| 566 |
|
| 567 |
def synth_best(gen_fn, text, attempts=1, asr_fn=None, sr=XTTS_SR, seed=1234,
|
| 568 |
-
accept_sim=0.90, secs_fn=None, lang="ar", guard_retries=2):
|
| 569 |
"""gen_fn(seed:int) → float32 wav. Returns (wav, info, n_generated).
|
| 570 |
attempts==1 : cheap self-healing guard — regenerate (≤ guard_retries) only if the
|
| 571 |
-
take is obviously broken (silent / absurd speech-rate / clipped).
|
| 572 |
-
attempts>1 : best-of-N ranked by ASR similarity + speaker similarity + duration.
|
|
|
|
|
|
|
|
|
|
| 573 |
multi = attempts > 1
|
| 574 |
takes, k = [], 0
|
| 575 |
while True:
|
| 576 |
wav = trim_take(gen_fn(seed + 7919 * k), sr)
|
| 577 |
info = take_info(wav, sr, text, asr_fn if multi else None, lang)
|
| 578 |
-
if
|
| 579 |
try: info["secs"] = float(secs_fn(wav, sr))
|
| 580 |
except Exception: info["secs"] = None
|
| 581 |
takes.append((wav, info)); k += 1
|
|
@@ -583,7 +586,8 @@ def synth_best(gen_fn, text, attempts=1, asr_fn=None, sr=XTTS_SR, seed=1234,
|
|
| 583 |
if k >= attempts: break
|
| 584 |
if info["ok"] and info["sim"] is not None and info["sim"] >= accept_sim: break
|
| 585 |
else:
|
| 586 |
-
|
|
|
|
| 587 |
if len(takes) == 1: return takes[0][0], takes[0][1], 1
|
| 588 |
med = float(np.median([i["dur"] for _, i in takes]))
|
| 589 |
best = max(takes, key=lambda t: _take_score(t[1], med))
|
|
@@ -1938,7 +1942,12 @@ def _run_tts_quality(model, text, refs, lang, base_params, out_path, job_id,
|
|
| 1938 |
|
| 1939 |
d = _model_defaults(model)
|
| 1940 |
try:
|
| 1941 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1942 |
except Exception as ex: # scoring is a bonus — never block generation
|
| 1943 |
print(f"[!] speaker-similarity scoring disabled: {ex}", flush=True)
|
| 1944 |
secs_fn = None
|
|
@@ -1968,10 +1977,16 @@ def _run_tts_quality(model, text, refs, lang, base_params, out_path, job_id,
|
|
| 1968 |
secs_fn=secs_fn, lang=lang)
|
| 1969 |
retries += _n - 1
|
| 1970 |
wav = _residual_stretch(wav, resid)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1971 |
if use_var and emo != "neutral":
|
| 1972 |
-
wav = apply_phrase_dynamics(wav, vq.XTTS_SR, emo, variation * 0.5)
|
| 1973 |
gap = EMOTION_DELTAS.get(emo, EMOTION_DELTAS["neutral"])[3]
|
| 1974 |
-
emo_factor = 1.0 + (gap / 110.0 - 1.0) * 0.5 * min(variation, 1.0) if use_var else 1.0
|
| 1975 |
takes.append(wav); pauses.append(pz * scale * emo_factor)
|
| 1976 |
job_progress[job_id]["current"] = i + 1
|
| 1977 |
|
|
|
|
| 565 |
return s
|
| 566 |
|
| 567 |
def synth_best(gen_fn, text, attempts=1, asr_fn=None, sr=XTTS_SR, seed=1234,
|
| 568 |
+
accept_sim=0.90, secs_fn=None, lang="ar", guard_retries=2, min_secs=0.45):
|
| 569 |
"""gen_fn(seed:int) → float32 wav. Returns (wav, info, n_generated).
|
| 570 |
attempts==1 : cheap self-healing guard — regenerate (≤ guard_retries) only if the
|
| 571 |
+
take is obviously broken (silent / absurd speech-rate / clipped / off-speaker).
|
| 572 |
+
attempts>1 : best-of-N ranked by ASR similarity + speaker similarity + duration.
|
| 573 |
+
`secs_fn` (speaker-encoder cosine similarity) is a single extra forward pass — cheap
|
| 574 |
+
enough to run even when attempts==1, and it is the only check that catches a take
|
| 575 |
+
that drifted away from the reference speaker's timbre (silence/clip/rate checks don't)."""
|
| 576 |
multi = attempts > 1
|
| 577 |
takes, k = [], 0
|
| 578 |
while True:
|
| 579 |
wav = trim_take(gen_fn(seed + 7919 * k), sr)
|
| 580 |
info = take_info(wav, sr, text, asr_fn if multi else None, lang)
|
| 581 |
+
if secs_fn is not None and len(wav) > int(0.3 * sr):
|
| 582 |
try: info["secs"] = float(secs_fn(wav, sr))
|
| 583 |
except Exception: info["secs"] = None
|
| 584 |
takes.append((wav, info)); k += 1
|
|
|
|
| 586 |
if k >= attempts: break
|
| 587 |
if info["ok"] and info["sim"] is not None and info["sim"] >= accept_sim: break
|
| 588 |
else:
|
| 589 |
+
secs_ok = info.get("secs") is None or info["secs"] >= min_secs
|
| 590 |
+
if (info["ok"] and info["clip"] < 0.005 and secs_ok) or k > guard_retries: break
|
| 591 |
if len(takes) == 1: return takes[0][0], takes[0][1], 1
|
| 592 |
med = float(np.median([i["dur"] for _, i in takes]))
|
| 593 |
best = max(takes, key=lambda t: _take_score(t[1], med))
|
|
|
|
| 1942 |
|
| 1943 |
d = _model_defaults(model)
|
| 1944 |
try:
|
| 1945 |
+
# One speaker-encoder forward pass per take — cheap, so run it even at attempts==1;
|
| 1946 |
+
# this used to be gated behind attempts>1, which meant the default UI setting
|
| 1947 |
+
# (attempts=1) never actually checked whether a chunk still sounded like the
|
| 1948 |
+
# reference speaker. That gap is the main source of "sounds like a different
|
| 1949 |
+
# speaker sometimes" on normal (non best-of-N) generations.
|
| 1950 |
+
secs_fn = _secs_fn(model, lat_spk)
|
| 1951 |
except Exception as ex: # scoring is a bonus — never block generation
|
| 1952 |
print(f"[!] speaker-similarity scoring disabled: {ex}", flush=True)
|
| 1953 |
secs_fn = None
|
|
|
|
| 1977 |
secs_fn=secs_fn, lang=lang)
|
| 1978 |
retries += _n - 1
|
| 1979 |
wav = _residual_stretch(wav, resid)
|
| 1980 |
+
# `conf` (how confident analyze_phrase_emotion was) was computed above but never
|
| 1981 |
+
# reached these two effects — they used to apply full-strength dynamics/pause
|
| 1982 |
+
# changes even on a low-confidence (often false-positive) keyword match, which is
|
| 1983 |
+
# what made ordinary neutral sentences suddenly jump in loudness/pacing next to
|
| 1984 |
+
# their neighbours. Scaling by `conf` makes weak matches barely audible and keeps
|
| 1985 |
+
# strong matches (question marks, "!!", clear keywords) at full strength.
|
| 1986 |
if use_var and emo != "neutral":
|
| 1987 |
+
wav = apply_phrase_dynamics(wav, vq.XTTS_SR, emo, variation * 0.5 * conf)
|
| 1988 |
gap = EMOTION_DELTAS.get(emo, EMOTION_DELTAS["neutral"])[3]
|
| 1989 |
+
emo_factor = 1.0 + (gap / 110.0 - 1.0) * 0.5 * min(variation, 1.0) * conf if use_var else 1.0
|
| 1990 |
takes.append(wav); pauses.append(pz * scale * emo_factor)
|
| 1991 |
job_progress[job_id]["current"] = i + 1
|
| 1992 |
|