import os import random import re import gc from dataclasses import dataclass, replace from typing import Optional import numpy as np import torch from shared.utils.audio_cleaning import mute_isolated_transient_noise, trim_leading_noise_before_speech, trim_leading_transient_noise, trim_trailing_transient_noise from .ltx_audio_tts import LTXAudioTTSPipelineBase from .ltx_core.components.schedulers import LTX2Scheduler from .ltx_core.conditioning import AudioConditionByAppendedReferenceLatent from .ltx_pipelines.utils.constants import AUDIO_SAMPLE_RATE from .scenema_audio import _audio_tensor_to_numpy, _clean_spaces, _normalize_volume, _numpy_to_audio_tensor, _parse_speaker_options, _shorten_long_silence, _trim_leading_extra_words_tensor, _trim_silence DRAMABOX_DEFAULT_NEGATIVE_PROMPT = "worst quality, inconsistent, robotic, distorted, noise, static, muffled, unclear, unnatural, monotone" DRAMABOX_FPS = 25.0 DRAMABOX_DEFAULT_STEPS = 30 DRAMABOX_DEFAULT_DURATION_MULTIPLIER = 1.1 DRAMABOX_DEFAULT_REFERENCE_SECONDS = 10.0 DRAMABOX_DEFAULT_CFG_SCALE = 2.5 DRAMABOX_DEFAULT_STG_SCALE = 1.5 DRAMABOX_REFERENCE_PEAK_DB = -4.0 DRAMABOX_STG_BLOCK = 29 DRAMABOX_TRANSIENT_SILENCE_THRESHOLD = 0.006 DRAMABOX_ISOLATED_TRANSIENT_THRESHOLD = 0.01 DRAMABOX_TRANSIENT_MAX_SECONDS = 0.18 DRAMABOX_LEADING_TRANSIENT_MAX_SECONDS = 0.30 DRAMABOX_LEADING_SPEECH_THRESHOLD = 0.03 DRAMABOX_MAX_LEADING_SECONDS = 2.0 @dataclass class _DramaBoxSegment: prompt: str duration_s: float seed: int speaker: int = 1 expected_text: str = "" _LAUGH_VERBS = { r"\blaugh(?:s|ed|ing)?\b": 1.5, r"\bcackl(?:e|es|ed|ing)\b": 1.5, r"\bchuckl(?:e|es|ed|ing)\b": 1.0, r"\bgiggl(?:e|es|ed|ing)\b": 1.0, r"\bsnicker(?:s|ed|ing)?\b": 0.8, r"\bcru?el laugh\b": 1.5, } def _read_text_or_file(value, label: str) -> str: if value is None: return "" text = os.fspath(value) if isinstance(value, os.PathLike) else str(value) if os.path.isfile(text) and os.path.splitext(text)[1].lower() in {".txt", ".xml"}: with open(text, "r", encoding="utf-8") as reader: return reader.read() return text def _contextual_laugh_duration(text: str) -> float: short_mod = re.compile(r"^\s*(?:[a-z]+ly )?(?:briefly|shortly|once|quickly)", re.IGNORECASE) long_mod = re.compile( r"^\s*(?:[a-z]+ly )?(?:maniacally|heartily|uproariously|uncontrollably|hysterically|darkly|wickedly|evilly|loudly|long)|^\s*between phrases", re.IGNORECASE, ) total = 0.0 for pattern, base_duration in _LAUGH_VERBS.items(): for match in re.finditer(pattern, text, re.IGNORECASE): context = text[match.end() : match.end() + 40] if short_mod.match(context): total += base_duration * 0.4 elif long_mod.match(context): total += base_duration * 1.2 else: total += base_duration for quoted in re.findall(r'"([^"]+)"', text) + re.findall(r"'((?:[^']|'(?![\s.,!?)\]]))+)'", text): for run in re.findall(r"(?:h[ae]){3,}|(?:h[ae][ \-]?){3,}", quoted, re.IGNORECASE): syllables = len(re.findall(r"h[ae]", run, re.IGNORECASE)) total += 0.2 * max(syllables - 2, 0) return total def _estimate_nonverbal_duration(text: str) -> float: patterns = { r"\bsighs?\b": 0.8, r"\bshaky breath\b": 1.0, r"\bbreathing deeply\b": 1.0, r"\bgasps?\b": 0.5, r"\bburps?\b": 0.5, r"\byawns?\b": 1.0, r"\bpants?\b": 0.8, r"\bwheezes?\b": 0.8, r"\bcoughs?\b": 0.8, r"\bsniffles?\b": 0.5, r"\bsnorts?\b": 0.3, r"\bgroans?\b": 0.8, r"\blong pause\b": 1.0, r"\bpauses? briefly\b": 0.3, r"\bpauses?\b": 0.5, r"\bsilence\b": 1.0, r"\blets? the .{1,20} hang\b": 1.0, r"\blets? .{1,20} sink in\b": 1.0, r"\bslams?\b": 0.5, r"\bclaps?\b": 0.3, r"\bdraws? (?:his|her|a) sword\b": 0.5, r"\btakes? a (?:drag|swig|sip|drink)\b": 0.5, r"\bwhistles?\b": 1.0, r"\bhums?\b": 0.8, r"\bmutters?\b": 1.5, r"\bmumbles?\b": 1.0, r"\bwhispers?\b": 0.0, r"\bclears? (?:his|her) throat\b": 0.5, r"\bgulps?\b": 0.5, r"\bswallows?\b": 0.5, r"\bvoice (?:breaks?|cracks?|trembles?|drops?|rises?)\b": 0.5, r"\bsteadies? (?:him|her)self\b": 1.0, r"\bcatches? (?:his|her) breath\b": 1.0, r"\bcomposes? (?:him|her)self\b": 0.8, r"\bdemeanor shifts?\b": 0.5, r"\bsettles? in\b": 0.5, r"\bleans? in\b": 0.3, r"\bwipes? (?:his|her) eyes\b": 0.5, } extra = 0.0 for pattern, duration in patterns.items(): extra += duration * len(re.findall(pattern, text, re.IGNORECASE)) return extra + _contextual_laugh_duration(text) def estimate_speech_duration(text: str, speed: float = 1.0) -> float: quotes = re.findall(r'"([^"]+)"', text) if not quotes: quotes = re.findall(r"'((?:[^']|'(?![\s.,!?)\]]))+)'", text) quotes = [quote for quote in quotes if len(quote.split()) > 3] if quotes: spoken = " ".join(quotes) elif ":" in text: spoken = text.split(":", 1)[1].strip() else: spoken = text chars_per_second = 14.0 text_length = len(spoken) if text_length < 40: chars_per_second *= 0.6 elif text_length < 80: chars_per_second *= 0.8 chars_per_second *= speed duration = text_length / chars_per_second duration += (spoken.count(".") + spoken.count("!") + spoken.count("?")) * 0.3 duration += _estimate_nonverbal_duration(text) return max(3.0, round(duration + 2.0, 1)) def _normalize_speaker_id(value) -> int: try: match = re.search(r"\d+", str(value if value is not None else "1")) return max(1, int(match.group(0))) if match else 1 except Exception: return 1 def _has_speaker_headers(text: str) -> bool: return re.search(r"(?im)^\s*Speaker\s*\d+\s*(?:\{[^\n{}]*\})?\s*:", text or "") is not None def _speaker_prefix(speaker: int, attrs: dict) -> str: voice = _clean_spaces(attrs.get("voice", "")) gender = _clean_spaces(attrs.get("gender", "")).lower() scene = _clean_spaces(attrs.get("scene", "")) parts = [] if voice: parts.append(voice) elif gender == "female": parts.append("female speaker") elif gender == "male": parts.append("male speaker") elif speaker: parts.append(f"speaker {speaker}") if scene: parts.append(f"in {scene}") return ". ".join(parts) def _format_dramabox_segment_prompt(text: str, speaker: int, attrs: dict) -> str: text = _clean_spaces(text) if not text: return "" prefix = _speaker_prefix(speaker, attrs) if '"' not in text: spoken = text.strip(" .") text = f'says, "{spoken}."' return _clean_spaces(f"{prefix}. {text}" if prefix else text) def _extract_complete_quoted_speech(text: str) -> str: raw = str(text or "") if raw.count('"') < 2 or raw.count('"') % 2 != 0: return "" return _clean_spaces(" ".join(quote.strip() for quote in re.findall(r'"([^"]+)"', raw) if quote.strip())) def _parse_dramabox_segments(text: str) -> list[tuple[int, str, str]]: raw = str(text or "").strip() if not raw: return [] has_headers = _has_speaker_headers(raw) if not has_headers: return [(1, _format_dramabox_segment_prompt(line.strip(), 1, {}), _extract_complete_quoted_speech(line)) for line in raw.splitlines() if line.strip()] header = re.compile(r"^\s*Speaker\s*(\d+)\s*(\{[^\n{}]*\})?\s*:\s*(.*)$", re.IGNORECASE) speaker_attrs: dict[int, dict] = {} current_speaker = 1 segments: list[tuple[int, str, str]] = [] for line in raw.splitlines(): stripped = line.strip() if not stripped: continue match = header.match(stripped) if match: current_speaker = _normalize_speaker_id(match.group(1)) attrs = speaker_attrs.setdefault(current_speaker, {}) parsed = _parse_speaker_options(match.group(2)) if parsed: attrs.update(parsed) stripped = match.group(3).strip() if not stripped: continue attrs = speaker_attrs.setdefault(current_speaker, {}) expected_text = _extract_complete_quoted_speech(stripped) prompt = _format_dramabox_segment_prompt(stripped, current_speaker, attrs) if prompt: segments.append((current_speaker, prompt, expected_text)) return segments def _scale_segment_durations(durations: list[float], duration_seconds) -> list[float]: try: target_duration = float(duration_seconds or 0.0) except (TypeError, ValueError): target_duration = 0.0 if target_duration <= 0 or not durations: return durations if len(durations) == 1: return [target_duration] total = sum(durations) if total <= 0: return durations scale = target_duration / total return [max(1.0, round(duration * scale, 1)) for duration in durations] def _plan_dramabox_segments(text: str, seed: int, duration_seconds, duration_multiplier: float) -> list[_DramaBoxSegment]: parsed = _parse_dramabox_segments(text) durations = [max(1.0, round(estimate_speech_duration(prompt) * float(duration_multiplier), 1)) for _, prompt, _ in parsed] durations = _scale_segment_durations(durations, duration_seconds) return [ _DramaBoxSegment(prompt=prompt, duration_s=duration, seed=seed + index * 1000, speaker=speaker, expected_text=expected_text) for index, ((speaker, prompt, expected_text), duration) in enumerate(zip(parsed, durations)) ] def _clean_segment_audio(audio: torch.Tensor, sample_rate: int, debug: bool = False) -> torch.Tensor: original_device = audio.device original_dtype = audio.dtype audio_np = _audio_tensor_to_numpy(audio) audio_np = trim_leading_transient_noise(audio_np, sample_rate, max_transient_seconds=DRAMABOX_LEADING_TRANSIENT_MAX_SECONDS, threshold=DRAMABOX_TRANSIENT_SILENCE_THRESHOLD, debug=debug, label="DramaBox Audio") audio_np = trim_leading_noise_before_speech(audio_np, sample_rate, speech_threshold=DRAMABOX_LEADING_SPEECH_THRESHOLD, max_leading_seconds=DRAMABOX_MAX_LEADING_SECONDS, debug=debug, label="DramaBox Audio") audio_np = trim_trailing_transient_noise(audio_np, sample_rate, max_transient_seconds=DRAMABOX_TRANSIENT_MAX_SECONDS, threshold=DRAMABOX_TRANSIENT_SILENCE_THRESHOLD, debug=debug, label="DramaBox Audio") audio_np = _trim_silence(audio_np, sample_rate, max_silence=0.5) audio_np = _normalize_volume(audio_np) audio_np = mute_isolated_transient_noise(audio_np, sample_rate, max_transient_seconds=DRAMABOX_TRANSIENT_MAX_SECONDS, threshold=DRAMABOX_ISOLATED_TRANSIENT_THRESHOLD, debug=debug, label="DramaBox Audio") return _numpy_to_audio_tensor(audio_np).to(device=original_device, dtype=original_dtype).clamp_(-1.0, 1.0) def _concatenate_dramabox_segments(chunks: list[torch.Tensor], sample_rate: int, debug: bool = False) -> torch.Tensor: if not chunks: raise ValueError("No DramaBox Audio segments were generated.") processed = [_audio_tensor_to_numpy(chunk) for chunk in chunks] audio_np = np.concatenate(processed, axis=0) audio_np = _shorten_long_silence(audio_np, sample_rate, max_duration=0.8, target_duration=0.35, threshold_db=-30.0) audio_np = trim_trailing_transient_noise(audio_np, sample_rate, max_transient_seconds=DRAMABOX_TRANSIENT_MAX_SECONDS, threshold=DRAMABOX_TRANSIENT_SILENCE_THRESHOLD, debug=debug, label="DramaBox Audio") return _numpy_to_audio_tensor(audio_np).clamp_(-1.0, 1.0) def _load_dramabox_alignment_whisper(): from shared.deepy.transcription import _load_whisper_medium device = torch.device("cuda" if torch.cuda.is_available() else "cpu") alignment_whisper = _load_whisper_medium(device) alignment_heads = alignment_whisper.alignment_heads del alignment_whisper._buffers["alignment_heads"] object.__setattr__(alignment_whisper, "alignment_heads", alignment_heads) for module in alignment_whisper.modules(): if isinstance(module, torch.nn.LayerNorm): module._lock_dtype = torch.float32 alignment_whisper._offload_hooks = ["transcribe"] alignment_whisper._model_dtype = torch.float16 if device.type == "cuda" else torch.float32 alignment_whisper.eval().requires_grad_(False) return alignment_whisper class DramaBoxAudioPipeline(LTXAudioTTSPipelineBase): def __init__( self, model_weights_path: str, gemma_path: str, audio_vae_path: str, vocoder_path: str, text_projection_path: str, text_connector_path: str, config_path: str | None = None, device: torch.device | None = None, dtype: torch.dtype = torch.bfloat16, ) -> None: super().__init__( model_weights_path=model_weights_path, gemma_path=gemma_path, audio_vae_path=audio_vae_path, vocoder_path=vocoder_path, text_projection_path=text_projection_path, text_connector_path=text_connector_path, config_path=config_path, device=device, dtype=dtype, ) def _encode_fixed_reference_waveform(self, waveform: torch.Tensor, sample_rate: int, *, tail: bool = False): reference_seconds = DRAMABOX_DEFAULT_REFERENCE_SECONDS target_samples = max(1, int(round(float(reference_seconds) * sample_rate))) if waveform.shape[-1] > target_samples: waveform = waveform[:, -target_samples:] if tail else waveform[:, :target_samples] elif waveform.shape[-1] < target_samples: repeat = (target_samples // max(1, waveform.shape[-1])) + 1 waveform = waveform.repeat(1, repeat) waveform = waveform[:, :target_samples] target_peak = 10 ** (DRAMABOX_REFERENCE_PEAK_DB / 20.0) return self._encode_reference_waveform(waveform, sample_rate, max_seconds=reference_seconds, normalize_peak=target_peak) def _encode_voice_reference(self, input_waveform, input_waveform_sample_rate, audio_guide: str | None): waveform, sample_rate = self._waveform_from_input(input_waveform, input_waveform_sample_rate, audio_guide) if waveform is None or sample_rate <= 0: return None return self._encode_fixed_reference_waveform(waveform, sample_rate) def _encode_generated_tail_reference(self, audio: torch.Tensor, sample_rate: int): channels_first = audio.detach().cpu().float() if channels_first.ndim == 3: channels_first = channels_first.squeeze(0) if channels_first.ndim == 1: channels_first = channels_first.unsqueeze(0) return self._encode_fixed_reference_waveform(channels_first, sample_rate, tail=True) @staticmethod def _patch_long_clip_silence_prior(audio_state): latent = audio_state.latent if latent.shape[2] <= 513: return audio_state f0, f1 = 511, 514 span = f1 - f0 patched = latent.clone() for frame in (512, 513): amount = (frame - f0) / span patched[:, :, frame, :] = (1.0 - amount) * latent[:, :, f0, :] + amount * latent[:, :, f1, :] return replace(audio_state, latent=patched) def _target_duration(self, prompt: str, duration_seconds, duration_multiplier: float) -> float: try: explicit_duration = float(duration_seconds or 0) except (TypeError, ValueError): explicit_duration = 0.0 if explicit_duration > 0: return explicit_duration return max(1.0, round(estimate_speech_duration(prompt) * float(duration_multiplier), 1)) def _generate_segment_audio( self, segment: _DramaBoxSegment, negative_prompt: str, cfg_scale: float, stg_scale: float, rescale_scale: float, sampling_steps: int, ref_latent=None, callback=None, set_progress_status=None, status_extra: str = "", ) -> torch.Tensor | None: if set_progress_status is not None: set_progress_status(f"Encoding Prompt | {status_extra}" if status_extra else "Encoding Prompt") if cfg_scale > 1.0: audio_context, audio_context_n = self._encode_prompts([segment.prompt, negative_prompt]) else: audio_context = self._encode_prompt(segment.prompt) audio_context_n = None if self._interrupt or self._early_stop_requested(): return None audio_state, audio_tools = self._build_audio_state( segment.duration_s, DRAMABOX_FPS, torch.empty(0, dtype=torch.float32, device=self.device), segment.seed, ref_latent=ref_latent, reference_conditioner=AudioConditionByAppendedReferenceLatent, ) sigmas = LTX2Scheduler().execute(steps=max(1, int(sampling_steps or DRAMABOX_DEFAULT_STEPS)), latent=audio_state.latent).to(self.device) audio_state = self._generate_audio_euler( audio_context, sigmas, audio_state, audio_tools, audio_context_n=audio_context_n, cfg_scale=cfg_scale, stg_scale=stg_scale, stg_blocks=[DRAMABOX_STG_BLOCK], rescale_scale=rescale_scale, callback=callback, status_extra=status_extra, set_progress_status=set_progress_status, ) if audio_state is None: return None audio_state = self._patch_long_clip_silence_prior(audio_state) return self._decode_audio_state(audio_state, set_progress_status=set_progress_status, status_extra=status_extra) def _remove_unexpected_words( self, generated_segments: list[tuple[_DramaBoxSegment, torch.Tensor]], sample_rate: int, *, debug_prompt: bool = False, set_progress_status=None, ) -> list[tuple[_DramaBoxSegment, torch.Tensor]]: if not any(segment.expected_text for segment, _ in generated_segments): return generated_segments if set_progress_status is not None: set_progress_status("Loading Whisper Alignment") for model in (self.model, self.text_encoder, self.text_embedding_projection, self.video_embeddings_connector, self.audio_embeddings_connector, self.audio_encoder, self.audio_decoder, self.vocoder): self._unload_managed_model(model) alignment_whisper = _load_dramabox_alignment_whisper() processed: list[tuple[_DramaBoxSegment, torch.Tensor]] = [] try: for index, (segment, audio) in enumerate(generated_segments): if self._interrupt: processed.extend(generated_segments[index:]) break if not segment.expected_text: processed.append((segment, audio)) continue if set_progress_status is not None: set_progress_status(f"Removing Unexpected Words | Segment {index + 1}/{len(generated_segments)}") trimmed = _trim_leading_extra_words_tensor(alignment_whisper, audio, sample_rate, segment.expected_text, "en", debug_prompt=debug_prompt, label="DramaBox Audio") processed.append((segment, _clean_segment_audio(trimmed, sample_rate, debug=debug_prompt))) finally: self._unload_managed_model(alignment_whisper) try: alignment_whisper.to("cpu") except Exception: pass del alignment_whisper gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() return processed def generate( self, input_prompt: str, model_mode: Optional[str] = None, audio_guide: Optional[str] = None, *, alt_prompt: Optional[str] = None, image_start=None, image_end=None, input_frames=None, input_frames2=None, input_ref_images=None, input_ref_masks=None, input_masks=None, input_masks2=None, input_video=None, input_faces=None, input_custom=None, denoising_strength=None, masking_strength=None, prefix_frames_count=None, frame_num=None, batch_size=None, height=None, width=None, fit_into_canvas=None, shift=None, sample_solver=None, sampling_steps: int = DRAMABOX_DEFAULT_STEPS, guide_scale: float = DRAMABOX_DEFAULT_CFG_SCALE, guide2_scale=None, guide3_scale=None, switch_threshold=None, switch2_threshold=None, guide_phases=None, model_switch_phase=None, embedded_guidance_scale=None, n_prompt=None, seed: int = -1, callback=None, enable_RIFLEx=None, VAE_tile_size=None, joint_pass=None, perturbation_switch=None, perturbation_layers=None, perturbation_start=None, perturbation_end=None, apg_switch=None, cfg_star_switch=None, cfg_zero_step=None, alt_guide_scale=None, audio_cfg_scale=None, input_waveform=None, input_waveform_sample_rate=None, audio_guide2: Optional[str] = None, audio_prompt_type: str = "", audio_proj=None, audio_scale=None, audio_context_lens=None, context_scale=None, control_scale_alt=None, alt_scale=None, motion_amplitude=None, model_mode_override=None, causal_block_size=None, causal_attention=None, fps=None, overlapped_latents=None, return_latent_slice=None, overlap_noise=None, overlap_size=None, color_correction_strength=None, conditioning_latents_size=None, input_video_is_hdr=None, lora_dir=None, keep_frames_parsed=None, model_filename=None, model_type=None, loras_slists=None, NAG_scale=None, NAG_tau=None, NAG_alpha=None, speakers_bboxes=None, image_mode=None, video_prompt_type=None, window_no=None, offloadobj=None, set_header_text=None, pre_video_frame=None, prefix_video=None, original_input_ref_images=None, image_refs_relative_size=None, outpainting_dims=None, face_arc_embeds=None, custom_settings=None, temperature: float = 0.0, window_start_frame_no=None, input_video_strength=None, self_refiner_setting=None, self_refiner_plan=None, self_refiner_f_uncertainty=None, self_refiner_certain_percentage=None, duration_seconds: Optional[float] = None, pause_seconds: float = 0.0, top_p: float = 0.9, top_k: int = 50, set_progress_status=None, loras_selected=None, frames_relative_positions_list=None, frames_to_inject=None, verbose_level: int = 0, ) -> Optional[dict]: self._interrupt = False self._early_stop = False prompt = _read_text_or_file(input_prompt, "Prompt").strip() if not prompt: raise ValueError("Prompt text cannot be empty for DramaBox Audio.") seed = random.randrange(0, 2**31) if seed is None or int(seed) < 0 else int(seed) duration_multiplier = self._custom_float(custom_settings, "duration_multiplier", DRAMABOX_DEFAULT_DURATION_MULTIPLIER) stg_scale = DRAMABOX_DEFAULT_STG_SCALE if audio_cfg_scale is None else float(audio_cfg_scale) rescale_scale = 0.0 if alt_scale is None else float(alt_scale) cfg_scale = float(guide_scale) debug_prompt = verbose_level > 1 if set_progress_status is not None: set_progress_status("Planning Audio Segments") segments = _plan_dramabox_segments(prompt, seed, duration_seconds, duration_multiplier) if not segments: raise ValueError("DramaBox Audio prompt produced no segments.") negative_prompt = _read_text_or_file(n_prompt, "Negative prompt").strip() or DRAMABOX_DEFAULT_NEGATIVE_PROMPT audio_prompt_type = str(audio_prompt_type or "").upper() remove_unexpected_words = "0" in audio_prompt_type speaker_ref_latents = {} if "A" in audio_prompt_type or audio_guide is not None or input_waveform is not None: if set_progress_status is not None: set_progress_status("Encoding Speaker 1 Reference") speaker_ref_latents[1] = self._encode_voice_reference(input_waveform, input_waveform_sample_rate, audio_guide) if speaker_ref_latents[1] is None: raise ValueError("DramaBox Audio Speaker 1 reference mode requires a reference audio file.") if "B" in audio_prompt_type or audio_guide2 is not None: if set_progress_status is not None: set_progress_status("Encoding Speaker 2 Reference") speaker_ref_latents[2] = self._encode_voice_reference(None, None, audio_guide2) if speaker_ref_latents[2] is None: raise ValueError("DramaBox Audio Speaker 2 reference mode requires a second reference audio file.") if self._interrupt: return None duration = sum(segment.duration_s for segment in segments) if set_header_text is not None: set_header_text(f"DramaBox Audio - {len(segments)} segment{'s' if len(segments) != 1 else ''}, {duration:.1f}s") output_audio_sampling_rate = int(getattr(self.vocoder, "output_sampling_rate", AUDIO_SAMPLE_RATE)) generated_segments: list[tuple[_DramaBoxSegment, torch.Tensor]] = [] generated_ref_latents = {} anchored_ref_speakers = set(speaker_ref_latents) for index, segment in enumerate(segments): if self._interrupt: break if self._early_stop_requested() and generated_segments: break status_extra = f"Segment {index + 1}/{len(segments)}" ref_latent = speaker_ref_latents.get(segment.speaker) if ref_latent is None: ref_latent = generated_ref_latents.get(segment.speaker) audio = self._generate_segment_audio( segment, negative_prompt, cfg_scale, stg_scale, rescale_scale, sampling_steps, ref_latent=ref_latent, callback=callback, set_progress_status=set_progress_status, status_extra=status_extra, ) if audio is None: if generated_segments and (self._interrupt or self._early_stop_requested()): break return None if set_progress_status is not None: set_progress_status(f"Trimming Segment {index + 1}/{len(segments)}") audio = _clean_segment_audio(audio, output_audio_sampling_rate, debug=debug_prompt) generated_segments.append((segment, audio)) if segment.speaker not in anchored_ref_speakers and segment.speaker not in generated_ref_latents: generated_ref_latents[segment.speaker] = self._encode_generated_tail_reference(audio, output_audio_sampling_rate) if self._early_stop_requested(): break if not generated_segments: return None if remove_unexpected_words and not self._interrupt: generated_segments = self._remove_unexpected_words(generated_segments, output_audio_sampling_rate, debug_prompt=debug_prompt, set_progress_status=set_progress_status) if set_progress_status is not None: set_progress_status("Combining Audio Segments") audio = _concatenate_dramabox_segments([audio for _, audio in generated_segments], output_audio_sampling_rate, debug=debug_prompt) return {"x": audio, "audio_sampling_rate": output_audio_sampling_rate}