feat: Enhance voice formula parsing and validation, implement voice asset caching, and add tests for new functionality

This commit is contained in:
JB
2025-10-09 13:37:36 -07:00
parent 6bd301b707
commit f0b6976d12
7 changed files with 404 additions and 44 deletions
+46 -22
View File
@@ -1,4 +1,6 @@
import re
from typing import List, Tuple
from abogen.constants import VOICES_INTERNAL
@@ -15,38 +17,56 @@ def get_new_voice(pipeline, formula, use_gpu):
raise ValueError(f"Failed to create voice: {str(e)}")
# Parse the formula and get the combined voice tensor
def parse_voice_formula(pipeline, formula):
if not formula.strip():
def parse_formula_terms(formula: str) -> List[Tuple[str, float]]:
if not formula or not formula.strip():
raise ValueError("Empty voice formula")
# Initialize the weighted sum
weighted_sum = None
total_weight = calculate_sum_from_formula(formula)
# Split the formula into terms
voices = formula.split("+")
for term in voices:
# Parse each term (format: "voice_name*0.333")
voice_name, weight = term.strip().split("*")
weight = float(weight.strip())
# normalize the weight
weight /= total_weight if total_weight > 0 else 1.0
terms: List[Tuple[str, float]] = []
for segment in formula.split("+"):
part = segment.strip()
if not part:
continue
if "*" not in part:
raise ValueError("Each component must be in the form voice*weight")
voice_name, raw_weight = part.split("*", 1)
voice_name = voice_name.strip()
# Get the voice tensor
if voice_name not in VOICES_INTERNAL:
raise ValueError(f"Unknown voice: {voice_name}")
try:
weight = float(raw_weight.strip())
except ValueError as exc:
raise ValueError(f"Invalid weight for {voice_name}") from exc
if weight <= 0:
raise ValueError(f"Weight for {voice_name} must be positive")
terms.append((voice_name, weight))
if not terms:
raise ValueError("Voice weights must sum to a positive value")
return terms
def parse_voice_formula(pipeline, formula):
terms = parse_formula_terms(formula)
total_weight = sum(weight for _, weight in terms)
if total_weight <= 0:
raise ValueError("Voice weights must sum to a positive value")
weighted_sum = None
for voice_name, weight in terms:
normalized_weight = weight / total_weight if total_weight > 0 else weight
voice_tensor = pipeline.load_single_voice(voice_name)
# Add to weighted sum
if weighted_sum is None:
weighted_sum = weight * voice_tensor
weighted_sum = normalized_weight * voice_tensor
else:
weighted_sum += weight * voice_tensor
weighted_sum += normalized_weight * voice_tensor
if weighted_sum is None:
raise ValueError("Voice formula produced no components")
return weighted_sum
@@ -55,3 +75,7 @@ def calculate_sum_from_formula(formula):
weights = re.findall(r"\* *([\d.]+)", formula)
total_sum = sum(float(weight) for weight in weights)
return total_sum
def extract_voice_ids(formula: str) -> List[str]:
return [voice for voice, _ in parse_formula_terms(formula)]