- Replace global method selection with intelligent per-junction analysis - Analyze each chunk boundary individually for optimal transition method - Add comprehensive junction analysis rules (sentence boundaries, commas, conjunctions, forced splits) - Implement per-junction audio processing with crossfade, silence, and concatenate methods - Enhance generation info reporting with detailed per-junction method breakdown - Add backward compatibility for silence_padding method alias - Fix tensor dimension mismatches in silence generation - Update method descriptions to reflect new per-junction capabilities The auto method now intelligently selects different combination methods for each junction based on text analysis, resulting in more natural audio transitions.
147 lines
6.0 KiB
Python
147 lines
6.0 KiB
Python
"""
|
|
Chunk Timing Utility - Modular chunk combination timing information for TTS engines
|
|
Provides standardized chunk timing info integration for audio generation results
|
|
"""
|
|
|
|
from typing import Dict, Any, List, Tuple, Optional
|
|
from .chunk_combiner import ChunkCombiner
|
|
|
|
|
|
class ChunkTimingHelper:
|
|
"""
|
|
Helper class to integrate chunk combination timing information into TTS engine generation results.
|
|
Provides consistent chunk timing info formatting across all engines.
|
|
"""
|
|
|
|
@staticmethod
|
|
def combine_audio_with_timing(audio_segments: List[Any],
|
|
combination_method: str = "auto",
|
|
silence_ms: int = 100,
|
|
crossfade_duration: float = 0.1,
|
|
sample_rate: int = 24000,
|
|
text_length: int = 0,
|
|
original_text: str = "",
|
|
text_chunks: List[str] = None,
|
|
combiner_func=None) -> Tuple[Any, Optional[Dict]]:
|
|
"""
|
|
Combine audio segments and return timing information.
|
|
|
|
Args:
|
|
audio_segments: List of audio segments to combine
|
|
combination_method: Method to use for combination
|
|
silence_ms: Silence duration in milliseconds
|
|
crossfade_duration: Crossfade duration in seconds
|
|
sample_rate: Audio sample rate
|
|
text_length: Original text length
|
|
original_text: Original text before chunking
|
|
text_chunks: List of text chunks
|
|
combiner_func: Optional custom combiner function (for engine-specific wrappers)
|
|
|
|
Returns:
|
|
Tuple of (combined_audio, timing_info_dict)
|
|
"""
|
|
if len(audio_segments) <= 1:
|
|
return audio_segments[0] if audio_segments else None, None
|
|
|
|
# Use ChunkCombiner directly or custom combiner function
|
|
if combiner_func:
|
|
# For engines that need custom audio format handling
|
|
combined_audio, chunk_info = combiner_func(
|
|
audio_segments=audio_segments,
|
|
combination_method=combination_method,
|
|
silence_ms=silence_ms,
|
|
crossfade_duration=crossfade_duration,
|
|
sample_rate=sample_rate,
|
|
text_length=text_length,
|
|
original_text=original_text,
|
|
text_chunks=text_chunks,
|
|
return_info=True
|
|
)
|
|
else:
|
|
# Direct ChunkCombiner usage for simple tensor lists
|
|
combined_audio, chunk_info = ChunkCombiner.combine_chunks(
|
|
audio_segments=audio_segments,
|
|
method=combination_method,
|
|
silence_ms=silence_ms,
|
|
crossfade_duration=crossfade_duration,
|
|
sample_rate=sample_rate,
|
|
text_length=text_length,
|
|
original_text=original_text,
|
|
text_chunks=text_chunks,
|
|
return_info=True
|
|
)
|
|
|
|
return combined_audio, chunk_info
|
|
|
|
@staticmethod
|
|
def enhance_generation_info(base_info: str, chunk_info: Optional[Dict]) -> str:
|
|
"""
|
|
Enhance generation info string with timing information.
|
|
|
|
Args:
|
|
base_info: Base generation info string
|
|
chunk_info: Chunk timing information dictionary
|
|
|
|
Returns:
|
|
Enhanced info string with timing details
|
|
"""
|
|
if not chunk_info:
|
|
return base_info
|
|
|
|
timing_info = ChunkCombiner.format_timing_info(chunk_info)
|
|
if timing_info:
|
|
return f"{base_info}\n\n{timing_info}"
|
|
|
|
return base_info
|
|
|
|
@staticmethod
|
|
def create_engine_combiner_wrapper(engine_combine_func):
|
|
"""
|
|
Create a wrapper for engine-specific combine functions that returns timing info.
|
|
|
|
Args:
|
|
engine_combine_func: Engine's existing combine function
|
|
|
|
Returns:
|
|
Wrapped function that can return timing info
|
|
"""
|
|
def wrapped_combiner(audio_segments, combination_method="auto", silence_ms=100,
|
|
crossfade_duration=0.1, sample_rate=24000, text_length=0,
|
|
original_text="", text_chunks=None, return_info=False, **kwargs):
|
|
|
|
if not return_info:
|
|
# Use original function for backward compatibility
|
|
return engine_combine_func(
|
|
audio_segments, combination_method, silence_ms,
|
|
text_length=text_length, **kwargs
|
|
)
|
|
|
|
# For timing info, we need to extract tensors and use ChunkCombiner
|
|
if hasattr(audio_segments[0], 'get'):
|
|
# Handle dict format (like Higgs Audio)
|
|
tensors = [seg["waveform"] for seg in audio_segments]
|
|
else:
|
|
# Handle direct tensor format
|
|
tensors = audio_segments
|
|
|
|
combined_tensor, chunk_info = ChunkCombiner.combine_chunks(
|
|
audio_segments=tensors,
|
|
method=combination_method,
|
|
silence_ms=silence_ms,
|
|
crossfade_duration=crossfade_duration,
|
|
sample_rate=sample_rate,
|
|
text_length=text_length,
|
|
original_text=original_text,
|
|
text_chunks=text_chunks,
|
|
return_info=True
|
|
)
|
|
|
|
# Convert back to engine format if needed
|
|
if hasattr(audio_segments[0], 'get'):
|
|
combined_audio = {"waveform": combined_tensor, "sample_rate": sample_rate}
|
|
else:
|
|
combined_audio = combined_tensor
|
|
|
|
return combined_audio, chunk_info
|
|
|
|
return wrapped_combiner |