Files
TashaSkyUp-EternalKernelPyt…/modules/xttscli.py
T

220 lines
8.7 KiB
Python

import sys
import os
import argparse
import logging
import traceback
import torch
import torchaudio
import tempfile
from pathlib import Path
from TTS.tts.configs.xtts_config import XttsConfig
from TTS.tts.models.xtts import Xtts
XTTS_MODEL = None
def prepare_speaker_audio(speaker):
"""
Prepares the speaker audio file and returns the speaker file path.
For .wav and .mp3 files, it directly returns the file path for model processing.
"""
if speaker.endswith(".wav") or speaker.endswith(".mp3"):
print(f"Using speaker audio file: {speaker}")
return speaker, None # No precomputed speaker embedding
elif speaker.endswith(".pth"):
print(f"Loading preprocessed speaker embedding from .pth file: {speaker}")
speaker_embedding = torch.load(speaker)
return None, speaker_embedding # Return precomputed speaker embedding
else:
raise ValueError("Unsupported speaker file format. Please provide a .wav, .mp3, or .pth file.")
def run_tts(lang, tts_text, speaker_audio_file, speaker_embedding, device):
"""
Generates TTS using the XTTS model, either from speaker audio or precomputed speaker embedding.
"""
if XTTS_MODEL is None:
raise ValueError("You need to load the model.")
# Generate conditioning latents from speaker audio if provided
if speaker_audio_file is not None:
print(f"Using speaker audio file: {speaker_audio_file}")
gpt_cond_latent, speaker_embedding = XTTS_MODEL.get_conditioning_latents(
audio_path=speaker_audio_file,
gpt_cond_len=XTTS_MODEL.config.gpt_cond_len,
max_ref_length=XTTS_MODEL.config.max_ref_len,
sound_norm_refs=XTTS_MODEL.config.sound_norm_refs
)
elif speaker_embedding is not None:
raise ValueError("Precomputed speaker embeddings are not directly usable without gpt_cond_latent.")
# Perform inference
out = XTTS_MODEL.inference(
text=tts_text,
language=lang,
gpt_cond_latent=gpt_cond_latent,
speaker_embedding=speaker_embedding,
temperature=XTTS_MODEL.config.temperature,
length_penalty=XTTS_MODEL.config.length_penalty,
repetition_penalty=XTTS_MODEL.config.repetition_penalty,
top_k=XTTS_MODEL.config.top_k,
top_p=XTTS_MODEL.config.top_p,
enable_text_splitting=True
)
# Move the output tensor to CPU before saving
out["wav"] = torch.tensor(out["wav"]).unsqueeze(0).to("cpu") # Ensure tensor is on CPU
# Save the generated speech to a temporary file
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as fp:
out_path = fp.name
torchaudio.save(out_path, out["wav"], 24000) # Save the tensor to disk
return out_path
def clear_gpu_cache():
if torch.cuda.is_available():
torch.cuda.empty_cache()
def load_model(xtts_checkpoint, xtts_config, xtts_vocab, device):
"""
Loads the XTTS model from checkpoint, config, and vocab files.
"""
global XTTS_MODEL
clear_gpu_cache()
if not xtts_checkpoint or not xtts_config or not xtts_vocab:
raise ValueError("You need to set the XTTS checkpoint path, XTTS config path, and XTTS vocab path!")
config = XttsConfig()
config.load_json(xtts_config)
XTTS_MODEL = Xtts.init_from_config(config)
print("Loading XTTS model!")
XTTS_MODEL.load_checkpoint(config, checkpoint_path=xtts_checkpoint, vocab_path=xtts_vocab, use_deepspeed=False)
if "cuda" in device and torch.cuda.is_available():
XTTS_MODEL.cuda(device)
print(f"Model moved to GPU: {device}")
else:
print(f"Using device: {device}")
def main():
# Set up logging
log_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'xtts_cli.log')
logging.basicConfig(filename=log_file, level=logging.ERROR, format='%(asctime)s %(levelname)s %(message)s')
# Default paths for the config, checkpoint, and vocab
script_dir = os.path.dirname(os.path.abspath(__file__))
default_config_path = os.path.join(script_dir, 'config.json')
default_checkpoint_path = os.path.join(script_dir, 'model.pth')
default_vocab_path = os.path.join(script_dir, 'vocab.json')
default_speaker_path = os.path.join(script_dir, 'speaker.mp3') # Default speaker audio file
default_test_file_path = os.path.join(script_dir, 'test.speech') # Default text file for --test
parser = argparse.ArgumentParser(description="CLI for XTTS model loading and TTS generation.")
parser.add_argument('--checkpoint', default=default_checkpoint_path,
help=f"Path to XTTS checkpoint file. Default: {default_checkpoint_path}")
parser.add_argument('--config', default=default_config_path,
help=f"Path to XTTS config file. Default: {default_config_path}")
parser.add_argument('--vocab', default=default_vocab_path,
help=f"Path to XTTS vocab file. Default: {default_vocab_path}")
parser.add_argument('--speaker', default=default_speaker_path,
help=f"Path to speaker audio file (raw .wav, .mp3, or preprocessed .pth). Default: {default_speaker_path}")
parser.add_argument('--text', help="Text to synthesize.")
parser.add_argument('--file', help="File containing text to synthesize (one text per line).")
parser.add_argument('--lang', default='en', help="Language for the TTS.")
parser.add_argument('--folder', help="Path to save the output TTS file.")
parser.add_argument('--device', default='cuda:0', help="Device to run the model on. Default: cuda:0")
parser.add_argument('--test', action='store_true', help="Run a test TTS using the test.speech file.")
args = parser.parse_args()
try:
# Preprocess the speaker file if it's a .wav or .mp3 file
args.speaker, speaker_embedding = prepare_speaker_audio(args.speaker)
# Handle test case
if args.test:
print("Running test with default parameters...")
if not os.path.exists(default_test_file_path):
raise FileNotFoundError(f"The test file '{default_test_file_path}' does not exist. Please create it.")
args.file = default_test_file_path
args.folder = os.path.join(script_dir, 'test_output.wav')
print(f"Test file: {args.file}")
print(f"Output will be saved to: {args.folder}")
# Handle the --file argument
if args.file:
print(f"Reading text from file: {args.file}")
with open(args.file, 'r') as f:
args.text = f.read().splitlines()
elif not args.text:
raise ValueError("Text or a file with text must be provided unless running in test mode.")
# Load the XTTS model
load_model(args.checkpoint, args.config, args.vocab, args.device)
# Prepare to store generated files
all_output_files = []
# Process multiple lines of text (if from file)
if isinstance(args.text, list):
for i, tts_text in enumerate(args.text):
output_path = run_tts(
lang=args.lang,
tts_text=tts_text,
speaker_audio_file=args.speaker,
speaker_embedding=speaker_embedding,
device=args.device
)
output_file = f"{args.folder}_{i}.wav" if args.folder else f'./output_{i}.wav'
try:
os.rename(output_path, output_file)
except Exception as e:
import shutil
# try copy then delete
shutil.copy(output_path, output_file)
os.remove(output_path)
all_output_files.append(output_file)
else:
# Generate TTS for a single text input
output_path = run_tts(
lang=args.lang,
tts_text=args.text,
speaker_audio_file=args.speaker,
speaker_embedding=speaker_embedding,
device=args.device
)
output_file = args.folder if args.folder else './output.wav'
try:
os.rename(output_path, output_file)
except Exception as e:
import shutil
# try copy then delete
shutil.copy(output_path, output_file)
os.remove(output_path)
all_output_files.append(output_file)
# Final output format to match expected behavior
main_output_file = all_output_files[0]
print(f"Generated files: ({main_output_file}, {all_output_files})")
except Exception as e:
logging.error("An error occurred: %s", traceback.format_exc())
print(f"An error occurred. Check the log file at {log_file} for details.")
if __name__ == '__main__':
main()