mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 21:50:28 +02:00
feat: Enhance voice formula parsing and validation, implement voice asset caching, and add tests for new functionality
This commit is contained in:
+46
-22
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user