fix: allocate fix_duration across chunks to avoid N× duration blow-up

When gen_text is split into multiple chunks by chunk_text(), each chunk
was generated with the full-sentence fix_duration, producing ~N×target
seconds. Allocate fix_duration across chunks by UTF-8 byte weight
(compensating for cross-fade overlap) so each chunk only generates its
proportional share. fix_duration=None and single-chunk paths unchanged.
This commit is contained in:
foco
2026-07-23 10:45:25 +08:00
parent 91f499635c
commit 7a36057028
+23 -10
View File
@@ -474,7 +474,19 @@ def infer_batch_process(
if len(ref_text[-1].encode("utf-8")) == 1:
ref_text = ref_text + " "
def _infer_basic(gen_text):
# Allocate fix_duration across chunks to avoid each chunk using the full-sentence duration (N× blow-up)
if fix_duration is not None and len(gen_text_batches) > 1:
ref_audio_len_frames = audio.shape[-1] // hop_length
ref_sec = ref_audio_len_frames * hop_length / target_sample_rate
target_total = fix_duration - ref_sec
weights = [len(c.encode("utf-8")) for c in gen_text_batches]
total_w = sum(weights)
allocatable = target_total + cross_fade_duration * (len(gen_text_batches) - 1)
fix_durations = [ref_sec + allocatable * w / total_w for w in weights]
else:
fix_durations = [fix_duration] * len(gen_text_batches)
def _infer_basic(gen_text, fix_dur):
local_speed = speed
if len(gen_text.encode("utf-8")) < 10:
local_speed = 0.3
@@ -484,8 +496,8 @@ def infer_batch_process(
final_text_list = convert_char_to_pinyin(text_list)
ref_audio_len = audio.shape[-1] // hop_length
if fix_duration is not None:
duration = int(fix_duration * target_sample_rate / hop_length)
if fix_dur is not None:
duration = int(fix_dur * target_sample_rate / hop_length)
else:
# Calculate duration
ref_text_len = len(ref_text.encode("utf-8"))
@@ -519,26 +531,27 @@ def infer_batch_process(
return generated_wave, generated
def infer_single_process(gen_text):
generated_wave, generated = _infer_basic(gen_text)
def infer_single_process(gen_text, fix_dur):
generated_wave, generated = _infer_basic(gen_text, fix_dur)
generated_cpu = generated[0].cpu().numpy()
del generated
return generated_wave, generated_cpu
def infer_single_process_streaming(gen_text):
def infer_single_process_streaming(gen_text, fix_dur):
# for src/f5_tts/socket_server.py
generated_wave, generated = _infer_basic(gen_text)
generated_wave, generated = _infer_basic(gen_text, fix_dur)
del generated
for j in range(0, len(generated_wave), chunk_size):
yield generated_wave[j : j + chunk_size], target_sample_rate
if streaming:
for gen_text in progress.tqdm(gen_text_batches) if progress is not None else gen_text_batches:
for chunk in infer_single_process_streaming(gen_text):
batches_iter = progress.tqdm(gen_text_batches) if progress is not None else gen_text_batches
for gen_text, fix_dur in zip(batches_iter, fix_durations):
for chunk in infer_single_process_streaming(gen_text, fix_dur):
yield chunk
else:
with ThreadPoolExecutor() as executor:
futures = [executor.submit(infer_single_process, gen_text) for gen_text in gen_text_batches]
futures = [executor.submit(infer_single_process, gt, fd) for gt, fd in zip(gen_text_batches, fix_durations)]
for future in progress.tqdm(futures) if progress is not None else futures:
result = future.result()
if result: