| import torch | |
| from pathlib import Path | |
| import json | |
| import os | |
| def load_config(): | |
| """Loads configuration from config.json. | |
| Returns: | |
| dict: The configuration loaded from the JSON file. | |
| Raises: | |
| FileNotFoundError: If the config.json file is not found. | |
| """ | |
| config_path = Path(__file__).parent.parent / "config" / "config.json" | |
| if not config_path.exists(): | |
| raise FileNotFoundError(f"Config file not found at {config_path}") | |
| with open(config_path) as f: | |
| return json.load(f) | |
| def get_available_voices(voices_dir): | |
| """Gets a list of available voice names without the .pt extension. | |
| Args: | |
| voices_dir (str): The path to the directory containing voice files. | |
| Returns: | |
| list: A list of voice names (strings). | |
| """ | |
| voices_dir = Path(voices_dir) | |
| if not voices_dir.exists(): | |
| return [] | |
| return [f.stem for f in voices_dir.glob("*.pt")] | |
| def validate_voice_name(voice_name, voices_dir): | |
| """Validates that a voice name exists in the voices directory. | |
| Args: | |
| voice_name (str): The name of the voice to validate. | |
| voices_dir (str): The path to the directory containing voice files. | |
| Returns: | |
| bool: True if the voice name is valid. | |
| Raises: | |
| ValueError: If the voice name is not found in the voices directory. | |
| """ | |
| available_voices = get_available_voices(voices_dir) | |
| if voice_name not in available_voices: | |
| raise ValueError( | |
| f"Voice '{voice_name}' not found. Available voices: {', '.join(available_voices)}" | |
| ) | |
| return True | |
| def load_voice(voice_name, voices_dir): | |
| """Loads a voice from the voices directory. | |
| Args: | |
| voice_name (str): The name of the voice to load. | |
| voices_dir (str): The path to the directory containing voice files. | |
| Returns: | |
| torch.Tensor: The loaded voice as a torch tensor. | |
| Raises: | |
| AssertionError: If the voices directory or voice file does not exist, or if the voice path is not a file. | |
| RuntimeError: If there is an error loading the voice file or converting it to a tensor. | |
| """ | |
| voices_dir = Path(voices_dir) | |
| assert voices_dir.exists(), f"Voices directory does not exist: {voices_dir}" | |
| assert voices_dir.is_dir(), f"Voices path is not a directory: {voices_dir}" | |
| validate_voice_name(voice_name, voices_dir) | |
| voice_path = voices_dir / f"{voice_name}.pt" | |
| assert voice_path.exists(), f"Voice file not found: {voice_path}" | |
| assert voice_path.is_file(), f"Voice path is not a file: {voice_path}" | |
| try: | |
| voice = torch.load(voice_path, weights_only=True) | |
| except Exception as e: | |
| raise RuntimeError(f"Error loading voice file {voice_path}: {str(e)}") | |
| if not isinstance(voice, torch.Tensor): | |
| try: | |
| voice = torch.tensor(voice) | |
| except Exception as e: | |
| raise RuntimeError(f"Could not convert voice to tensor: {str(e)}") | |
| return voice | |
| def quick_mix_voice(output_name, voices_dir, *voices, weights=None): | |
| """Mixes and saves voices with specified weights. | |
| Args: | |
| output_name (str): The name of the output mixed voice file (without extension). | |
| voices_dir (str): The path to the directory containing voice files. | |
| *voices (torch.Tensor): Variable number of voice tensors to mix. | |
| weights (list, optional): List of weights for each voice. Defaults to equal weights if None. | |
| Returns: | |
| torch.Tensor: The mixed voice as a torch tensor. | |
| Raises: | |
| ValueError: If no voices are provided, if the number of weights does not match the number of voices, or if the sum of weights is not positive. | |
| AssertionError: If the voices directory does not exist or is not a directory. | |
| """ | |
| voices_dir = Path(voices_dir) | |
| assert voices_dir.exists(), f"Voices directory does not exist: {voices_dir}" | |
| assert voices_dir.is_dir(), f"Voices path is not a directory: {voices_dir}" | |
| if not voices: | |
| raise ValueError("Must provide at least one voice") | |
| base_shape = voices[0].shape | |
| for i, voice in enumerate(voices): | |
| if not isinstance(voice, torch.Tensor): | |
| raise ValueError(f"Voice {i} is not a tensor") | |
| if voice.shape != base_shape: | |
| raise ValueError( | |
| f"Voice {i} has shape {voice.shape}, but expected {base_shape} (same as first voice)" | |
| ) | |
| if weights is None: | |
| weights = [1.0 / len(voices)] * len(voices) | |
| else: | |
| if len(weights) != len(voices): | |
| raise ValueError( | |
| f"Number of weights ({len(weights)}) must match number of voices ({len(voices)})" | |
| ) | |
| weights_sum = sum(weights) | |
| if weights_sum <= 0: | |
| raise ValueError("Sum of weights must be positive") | |
| weights = [w / weights_sum for w in weights] | |
| device = voices[0].device | |
| voices = [v.to(device) for v in voices] | |
| stacked = torch.stack(voices) | |
| weights = torch.tensor(weights, device=device) | |
| mixed = torch.zeros_like(voices[0]) | |
| for i, weight in enumerate(weights): | |
| mixed += stacked[i] * weight | |
| output_path = voices_dir / f"{output_name}.pt" | |
| torch.save(mixed, output_path) | |
| print(f"Created mixed voice: {output_name}.pt") | |
| return mixed | |
| def split_into_sentences(text): | |
| """Splits text into sentences using more robust rules. | |
| Args: | |
| text (str): The input text to split. | |
| Returns: | |
| list: A list of sentences (strings). | |
| """ | |
| import re | |
| text = text.strip() | |
| if not text: | |
| return [] | |
| abbreviations = { | |
| "Mr.": "Mr", | |
| "Mrs.": "Mrs", | |
| "Dr.": "Dr", | |
| "Ms.": "Ms", | |
| "Prof.": "Prof", | |
| "Sr.": "Sr", | |
| "Jr.": "Jr", | |
| "vs.": "vs", | |
| "etc.": "etc", | |
| "i.e.": "ie", | |
| "e.g.": "eg", | |
| "a.m.": "am", | |
| "p.m.": "pm", | |
| } | |
| for abbr, repl in abbreviations.items(): | |
| text = text.replace(abbr, repl) | |
| sentences = [] | |
| current = [] | |
| words = re.findall(r"\S+|\s+", text) | |
| for word in words: | |
| current.append(word) | |
| if re.search(r"[.!?]+$", word): | |
| if not re.match(r"^[A-Z][a-z]{1,2}$", word[:-1]): | |
| sentence = "".join(current).strip() | |
| if sentence: | |
| sentences.append(sentence) | |
| current = [] | |
| continue | |
| if current: | |
| sentence = "".join(current).strip() | |
| if sentence: | |
| sentences.append(sentence) | |
| for abbr, repl in abbreviations.items(): | |
| sentences = [s.replace(repl, abbr) for s in sentences] | |
| sentences = [s.strip() for s in sentences if s.strip()] | |
| final_sentences = [] | |
| for s in sentences: | |
| if len(s) > 200: | |
| parts = s.split(",") | |
| parts = [p.strip() for p in parts if p.strip()] | |
| if len(parts) > 1: | |
| final_sentences.extend(parts) | |
| else: | |
| final_sentences.append(s) | |
| else: | |
| final_sentences.append(s) | |
| return final_sentences | |