diff --git a/README.md b/README.md index 05f19e9c2..b89c9426e 100644 --- a/README.md +++ b/README.md @@ -26,25 +26,46 @@ If you have trouble doing the download with the `python -m unidic download` you pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124 ``` +3. Prepare faster-whisper (optional for fast transcribing of audio files): + - Download cuda/cublas here [https://github.com/Purfview/whisper-standalone-win/releases/download/libs/cuBLAS.and.cuDNN_CUDA11_win_v2.7z](https://github.com/Purfview/whisper-standalone-win/releases/download/libs/cuBLAS.and.cuDNN_CUDA11_win_v2.7z), extract and place the 5 dll files directly into the `MeloTTS-Windows/melo/` folder + - To install faster-whisper (and prevent conflicts with it) run this from the conda window: +``` +pip install faster-whisper==0.9.0 +pip install transformers==4.30.2 huggingface_hub==0.16.4 +``` + 4. Run using: ``` melo-ui ``` # Local Training on Windows +## Preparing Dataset 1. In the `melo/data/example` folder, delete the example `metadata.list` file. -2. If you need to convert mp3 to wav, create a folder called `mp3s` in the example folder and copy all your mp3 files into the `mp3s` folder -3. With a conda window activated with the enviroment open in the `melo` folder, run `ConvertMp3toWav.bat` from the conda prompt. This will create a folder `data/example/wavs` with all of the converted wav files. -4. Create a transcript file by running `python transcript.py` which will create a `data/example/metadata.list` file. -5. Run `python preprocess_text.py --metadata data/example/metadata.list` to create the `train.list`, `config.json`, among other files in the `data/example` folder. -6. Modify `config.json` to change the batch size, epochs, learning rate, etc. -7. From the conda prompt run `train.bat` to start the training. -8. File will be created within the `data/example/config` folder with the checkpoints and other logging information. -9. To test out a checkpoint, run: `python infer.py --text "this is a test" -m "C:\ai\MeloTTS-Windows\melo\data\example\config\G_0.pth" -o output` changing the G_0 to the checkpoint you want to test with G_1000, G2000, etc. -10. When you want to use a checkpoint from the UI, create a `melo/custom` folder and copy the .pth and `config.json` file over from the `data/example/config`, rename the .pth to a user-friendly name, and launch the UI to see it in the custom voice dropdown. -11. To see the tensorboard, install `pip install tensorflow` -12. Run `tensorboard --logdir=data\example\config` -13. This will give you the local URL to view the tensorboard. +2. MeloTTS expects wav audio files (with a sample rate of 44100Hz). If you need to convert audio to wav format (with 44100Hz sample rate), create a folder called `audio` in the example folder and copy all your audio files into the `audio` folder +4. With a conda window activated with the enviroment open in the `melo` folder, run `ConvertAudiotoWav.bat` from the conda prompt. This will create a folder `data/example/wavs` with all of the converted wav files. +5. Create a transcript file by running `transcript_fast.bat` which will create a `data/example/metadata.list` file using faster-whisper. Alternately, you can run `python transcript.py` to use the original whisper. +6. Run `python preprocess_text.py --metadata data/example/metadata.list` to create the `train.list`, `config.json`, among other files in the `data/example` folder. +7. Modify `config.json` to change the batch size, epochs, learning rate, etc. + - ⚠️ **Important, If you plan to Resume Training Later:** + - The `eval_interval` setting determines how frequently your model is saved during training + - For example, if `eval_interval=1000`, the model saves only once every 1000 steps + - If you stop training between save points, any progress since the last save will be lost + - For safer training sessions that you may need to resume later, use a smaller `eval_interval` value + - You can also adjust `n_ckpts_to_keep` to limit the max models kept (if `n_ckpts_to_keep=5`, it will delete the oldest models when their are more than 5 saved models) +## Start Training +1. From the conda prompt run `train.bat` to start the training. +2. File will be created within the `data/example/config` folder with the checkpoints and other logging information. +3. To test out a checkpoint, run: `python infer.py --text "this is a test" -m "C:\ai\MeloTTS-Windows\melo\data\example\config\G_0.pth" -o output` changing the G_0 to the checkpoint you want to test with G_1000, G2000, etc. +4. When you want to use a checkpoint from the UI, create a `melo/custom` folder and copy the .pth and `config.json` file over from the `data/example/config`, rename the .pth to a user-friendly name, and launch the UI to see it in the custom voice dropdown. +5. To see the tensorboard, install `pip install tensorflow` +6. Run `tensorboard --logdir=data\example\config` +7. This will give you the local URL to view the tensorboard. +## Resuming Training +1. From the conda prompt run `train.bat` again to resume the training. The training will resume from the newest G_XXXX.pth file. +## Trimming Model +You can trim your model to make it a way smaller filesize (which will make it load faster during the model loading process). When testing, this made the model filesize about 66% smaller. Note the created trimmed model is for inference-only(using the model just to generate audio from text) and you won't be able to train it further. +1. Open `trim_models.bat` file in a text editor to change the directory to your G_XXXX.pth files and the save location, save the changes, then run `trim_models.bat` to create a trimmed model for inference only. # Original Readme:
diff --git a/melo/ConvertAudiotoWav.bat b/melo/ConvertAudiotoWav.bat new file mode 100644 index 000000000..ca893831c --- /dev/null +++ b/melo/ConvertAudiotoWav.bat @@ -0,0 +1,46 @@ +@echo off +setlocal enabledelayedexpansion + +rem Specify input and output folders here +set "INPUT_FOLDER=data\example\audio" +set "OUTPUT_FOLDER=data\example\wavs" + +rem Create output folder if it doesn't exist +if not exist "%OUTPUT_FOLDER%" mkdir "%OUTPUT_FOLDER%" + +rem Initialize counter for sequential naming +set /a counter=1 + +rem Loop through common audio formats that ffmpeg supports +for %%F in ( + "%INPUT_FOLDER%\*.mp3" + "%INPUT_FOLDER%\*.m4a" + "%INPUT_FOLDER%\*.wav" + "%INPUT_FOLDER%\*.ogg" + "%INPUT_FOLDER%\*.flac" + "%INPUT_FOLDER%\*.aac" + "%INPUT_FOLDER%\*.wma" + "%INPUT_FOLDER%\*.aiff" + "%INPUT_FOLDER%\*.aifc" + "%INPUT_FOLDER%\*.opus" + "%INPUT_FOLDER%\*.ape" + "%INPUT_FOLDER%\*.wv" + "%INPUT_FOLDER%\*.m4b" + "%INPUT_FOLDER%\*.mp2" + "%INPUT_FOLDER%\*.mp4" + "%INPUT_FOLDER%\*.mpc" + "%INPUT_FOLDER%\*.mka" + "%INPUT_FOLDER%\*.ac3" + "%INPUT_FOLDER%\*.dts" + "%INPUT_FOLDER%\*.amr" + "%INPUT_FOLDER%\*.au" + "%INPUT_FOLDER%\*.mid" +) do ( + rem Convert the file using ffmpeg + ffmpeg -i "%%F" -acodec pcm_s16le -ar 44100 "%OUTPUT_FOLDER%\!counter!.wav" + + rem Increment the counter + set /a counter+=1 +) + +echo Conversion complete. Check the '%OUTPUT_FOLDER%' folder for the converted files. diff --git a/melo/configs/config.json b/melo/configs/config.json index f93ce6660..8f7a3335a 100644 --- a/melo/configs/config.json +++ b/melo/configs/config.json @@ -1,7 +1,7 @@ { "train": { "log_interval": 200, - "eval_interval": 1000, + "eval_interval": 200, "seed": 52, "epochs": 10000, "learning_rate": 0.0003, @@ -10,7 +10,7 @@ 0.99 ], "eps": 1e-09, - "batch_size": 6, + "batch_size": 12, "fp16_run": false, "lr_decay": 0.999875, "segment_size": 16384, @@ -18,7 +18,8 @@ "warmup_epochs": 0, "c_mel": 45, "c_kl": 1.0, - "skip_optimizer": true + "skip_optimizer": false, + "keep_ckpts": 5 }, "data": { "training_files": "", diff --git a/melo/preprocess_text.py b/melo/preprocess_text.py index 8fdf87295..c190a5116 100644 --- a/melo/preprocess_text.py +++ b/melo/preprocess_text.py @@ -155,4 +155,4 @@ def main( logger.info("Preprocessing completed successfully") if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/melo/test_gpu_cpu_switching.py b/melo/test_gpu_cpu_switching.py new file mode 100644 index 000000000..2b6c3c37d --- /dev/null +++ b/melo/test_gpu_cpu_switching.py @@ -0,0 +1,187 @@ +import torch +import gc +from melo.api import TTS +import os +import melo.text.english_bert as bert +import psutil +import GPUtil + +def get_memory_usage(): + """Get current RAM and VRAM usage""" + ram = psutil.virtual_memory() + ram_usage = ram.used / (1024 ** 3) # Convert to GB + + gpu_memory = None + if torch.cuda.is_available(): + try: + gpus = GPUtil.getGPUs() + if gpus: + gpu = gpus[0] # Get first GPU + gpu_memory = { + 'used': gpu.memoryUsed, # MB + 'total': gpu.memoryTotal # MB + } + except Exception as e: + print(f"Error getting GPU stats: {e}") + + return ram_usage, gpu_memory + +def clear_gpu_memory(): + """Clear both GPU and RAM memory more aggressively""" + # First clear CUDA memory + if torch.cuda.is_available(): + for obj in gc.get_objects(): + try: + if torch.is_tensor(obj): + if obj.is_cuda: + del obj + except Exception: + pass + torch.cuda.synchronize() + torch.cuda.empty_cache() + + # Force Python garbage collection + gc.collect() + + # Optional: Force more aggressive garbage collection + for _ in range(2): + gc.collect() + + # Print memory stats to verify clearing + ram = psutil.virtual_memory() + print(f"\nAfter clearing - RAM Usage: {ram.used / (1024 ** 3):.2f} GB") + if torch.cuda.is_available(): + gpus = GPUtil.getGPUs() + if gpus: + gpu = gpus[0] + print(f"After clearing - VRAM Usage: {gpu.memoryUsed:.2f} MB / {gpu.memoryTotal:.2f} MB") + +def reset_bert_model(device): + """Reset the global BERT model in english_bert.py only when switching devices""" + current_device = None + + # Print memory usage before reset + ram_before, vram_before = get_memory_usage() + print(f"\nMemory before BERT reset:") + print(f"RAM Usage: {ram_before:.2f} GB") + if vram_before: + print(f"VRAM Usage: {vram_before['used']:.2f} MB / {vram_before['total']:.2f} MB") + + # Check current device of BERT model if it exists + if bert.model is not None: + current_device = next(bert.model.parameters()).device.type + + # If the model is already on the correct device, return early + if (current_device == 'cuda' and device == 'cuda') or \ + (current_device == 'cpu' and device == 'cpu'): + return + + # Explicitly move model to CPU before deletion if it's on CUDA + if current_device == 'cuda': + bert.model.cpu() + + # Delete the model and clear memory + del bert.model + clear_gpu_memory() + bert.model = None + + # Create proper dummy input for BERT initialization + dummy_text = "Hello world" + tokens = bert.tokenizer(dummy_text, return_tensors="pt") + word2ph = [1] * tokens["input_ids"].shape[1] + _ = bert.get_bert_feature(dummy_text, word2ph, device) + + # Print memory usage after reset + ram_after, vram_after = get_memory_usage() + print(f"\nMemory after BERT reset:") + print(f"RAM Usage: {ram_after:.2f} GB") + if vram_after: + print(f"VRAM Usage: {vram_after['used']:.2f} MB / {vram_after['total']:.2f} MB") + + print(f"BERT model reloaded: {current_device} -> {device}") + +import time # Add this to your imports + +def generate_speech(model, text, spk_id, device, output_path): + """Generate speech with specified device""" + try: + # Print memory usage before model movement + ram_before, vram_before = get_memory_usage() + print(f"\nMemory before moving model to {device}:") + print(f"RAM Usage: {ram_before:.2f} GB") + if vram_before: + print(f"VRAM Usage: {vram_before['used']:.2f} MB / {vram_before['total']:.2f} MB") + + # If moving from CUDA to CPU, first move model to CPU then clear CUDA memory + if device == "cpu" and next(model.parameters()).is_cuda: + model.cpu() + clear_gpu_memory() + else: + model = model.to(device) + + reset_bert_model(device) + spk_id = torch.tensor([spk_id], device=device) + + # Print memory usage after model movement + ram_after, vram_after = get_memory_usage() + print(f"\nMemory after moving model to {device}:") + print(f"RAM Usage: {ram_after:.2f} GB") + if vram_after: + print(f"VRAM Usage: {vram_after['used']:.2f} MB / {vram_after['total']:.2f} MB") + + # Add timing measurement + start_time = time.time() + model.tts_to_file(text, spk_id, output_path) + end_time = time.time() + conversion_time = end_time - start_time + print(f"\n----Text-to-speech conversion time on {device}: {conversion_time:.2f} seconds") + + finally: + clear_gpu_memory() + + +def alternate_gpu_cpu_inference(ckpt_path, text, language="EN", output_dir="outputs"): + """Alternate between GPU and CPU inference""" + os.makedirs(output_dir, exist_ok=True) + + # Initialize model + config_path = os.path.join(os.path.dirname(ckpt_path), 'config.json') + + # Sequence of devices to test + devices = ["cuda", "cpu", "cuda", "cpu"] + current_model = None + current_device = None + + for i, device in enumerate(devices): + # Skip if CUDA not available for GPU inference + if device == "cuda" and not torch.cuda.is_available(): + print(f"CUDA not available, skipping GPU inference {i+1}") + continue + + print(f"\nGenerating speech using {device.upper()} - Round {i+1}") + + # Only create new model instance if switching devices or first run + if current_model is None or current_device != device: + if current_model is not None: + del current_model + clear_gpu_memory() + + current_model = TTS(language=language, config_path=config_path, + ckpt_path=ckpt_path, device=device) + current_device = device + + # Get first speaker ID + spk_id = list(current_model.hps.data.spk2id.values())[0] + + output_path = os.path.join(output_dir, f"output_{i+1}_{device}.wav") + + # Generate speech + generate_speech(current_model, text, spk_id, device, output_path) + print(f"Generated: {output_path}") + +if __name__ == "__main__": + # Example usage + ckpt_path = "C:/Users/lyria/Documents/Scripts/python/melotts/melotts/MeloTTS-Windows/melo/data/example_fused3voices_7wavs/output_3voices_7wavs/G_2000.pth" + text = "This is a test of alternating between GPU and CPU inference." + + alternate_gpu_cpu_inference(ckpt_path, text) diff --git a/melo/test_resource_usage.py b/melo/test_resource_usage.py new file mode 100644 index 000000000..655298467 --- /dev/null +++ b/melo/test_resource_usage.py @@ -0,0 +1,168 @@ +#this script requires you to use: pip install psutil GPUtil +import os +import time +import psutil +import GPUtil +import torch +from melo.api import TTS +import numpy as np + +def get_system_usage(): + """Get current system resource usage""" + cpu_percent = psutil.cpu_percent(interval=0.1) + ram = psutil.virtual_memory() + ram_usage = ram.used / (1024 ** 3) # Convert to GB + + gpu_stats = None + if torch.cuda.is_available(): + try: + gpus = GPUtil.getGPUs() + if gpus: + gpu = gpus[0] # Get first GPU + gpu_stats = { + 'gpu_load': gpu.load * 100, # Convert to percentage + 'gpu_memory_used': gpu.memoryUsed, + 'gpu_memory_total': gpu.memoryTotal + } + except Exception as e: + print(f"Error getting GPU stats: {e}") + + return { + 'cpu_percent': cpu_percent, + 'ram_used_gb': ram_usage, + 'gpu_stats': gpu_stats + } + +def run_tts_test(text, device, output_filename, speaker_id=0): + """Run TTS test on specified device and monitor resources""" + print(f"\nRunning TTS test on {device.upper()}...") + + # Clear CUDA cache and ensure clean state + if torch.cuda.is_available(): + torch.cuda.empty_cache() + + # Initialize model on specified device + model = TTS(language="EN", device=device) + + # Get initial resource usage + initial_usage = get_system_usage() + print(f"Initial resource usage:") + print(f"CPU: {initial_usage['cpu_percent']:.1f}%") + print(f"RAM: {initial_usage['ram_used_gb']:.1f} GB") + if initial_usage['gpu_stats']: + print(f"GPU: {initial_usage['gpu_stats']['gpu_load']:.1f}%") + print(f"VRAM: {initial_usage['gpu_stats']['gpu_memory_used']:.1f} MB / " + f"{initial_usage['gpu_stats']['gpu_memory_total']:.1f} MB") + + # Run TTS + start_time = time.time() + try: + # Force BERT models to the correct device before TTS + if device == "cuda": + from melo.text.english_bert import model as bert_model + if bert_model is not None: + bert_model.to("cuda") + + audio = model.tts_to_file( + text=text, + speaker_id=speaker_id, + output_path=output_filename, + quiet=True + ) + + # Get peak resource usage + peak_usage = get_system_usage() + + end_time = time.time() + duration = end_time - start_time + + print(f"\nPeak resource usage:") + print(f"CPU: {peak_usage['cpu_percent']:.1f}%") + print(f"RAM: {peak_usage['ram_used_gb']:.1f} GB") + if peak_usage['gpu_stats']: + print(f"GPU: {peak_usage['gpu_stats']['gpu_load']:.1f}%") + print(f"VRAM: {peak_usage['gpu_stats']['gpu_memory_used']:.1f} MB / " + f"{peak_usage['gpu_stats']['gpu_memory_total']:.1f} MB") + + print(f"\nConversion completed in {duration:.2f} seconds") + print(f"Output saved to: {output_filename}") + + except Exception as e: + print(f"Error during TTS conversion: {e}") + + # Clean up + del model + if torch.cuda.is_available(): + torch.cuda.empty_cache() + +def main(): + # Create output directory + os.makedirs("tts_outputs", exist_ok=True) + + # Test text + text = "This is a test of the MeloTTS system in both GPU and CPU modes. How's the performance?" + + # Get starting resource usage (before using MeloTTS for inference) + peak_usage = get_system_usage() + + print(f"\nPeak resource usage before MeloTTS inference:") + print(f"--CPU: {peak_usage['cpu_percent']:.1f}%") + print(f"--RAM: {peak_usage['ram_used_gb']:.1f} GB") + if peak_usage['gpu_stats']: + print(f"--GPU: {peak_usage['gpu_stats']['gpu_load']:.1f}%") + print(f"--VRAM: {peak_usage['gpu_stats']['gpu_memory_used']:.1f} MB / " + f"{peak_usage['gpu_stats']['gpu_memory_total']:.1f} MB") + + # Run CPU tests + print("\n=== CPU Test - First Run (Warmup) ===") + run_tts_test( + text=text, + device="cpu", + output_filename="tts_outputs/cpu_output_1.wav" + ) + + print("\n=== CPU Test - Second Run (Measurement) ===") + run_tts_test( + text=text, + device="cpu", + output_filename="tts_outputs/cpu_output_2.wav" + ) + + print("\n=== CPU Test - Third Run (Extra Measurement) ===") + run_tts_test( + text=text, + device="cpu", + output_filename="tts_outputs/cpu_output_3.wav" + ) + + # Run GPU tests if available + if torch.cuda.is_available(): + # Force CUDA synchronization and cache clear before GPU test + torch.cuda.synchronize() + torch.cuda.empty_cache() + + print("\n=== GPU Test - First Run (Warmup) ===") + run_tts_test( + text=text, + device="cuda", + output_filename="tts_outputs/gpu_output_1.wav" + ) + + print("\n=== GPU Test - Second Run (Measurement) ===") + run_tts_test( + text=text, + device="cuda", + output_filename="tts_outputs/gpu_output_2.wav" + ) + + print("\n=== GPU Test - Third Run (Extra Measurement) ===") + run_tts_test( + text=text, + device="cuda", + output_filename="tts_outputs/gpu_output_3.wav" + ) + else: + print("\nGPU not available for testing") + +if __name__ == "__main__": + main() diff --git a/melo/transcript.py b/melo/transcript.py index f17f02809..8111eaf7a 100644 --- a/melo/transcript.py +++ b/melo/transcript.py @@ -8,7 +8,7 @@ LANGUAGE_MODEL = "EN-default" # Load the whisper model -model = whisper.load_model("base") +model = whisper.load_model("medium") # Get the list of WAV files in the input directory wav_files = [file for file in os.listdir(INPUT_FOLDER) if file.endswith(".wav")] @@ -37,4 +37,4 @@ line += "\n" transcript_file.write(line) -print(f"Transcription complete. Check '{OUTPUT_FILE}' for results.") \ No newline at end of file +print(f"Transcription complete. Check '{OUTPUT_FILE}' for results.") diff --git a/melo/transcript_fast.bat b/melo/transcript_fast.bat new file mode 100644 index 000000000..8bc6fd23a --- /dev/null +++ b/melo/transcript_fast.bat @@ -0,0 +1,4 @@ +conda activate melotts-win + +python transcript_fast.py + diff --git a/melo/transcript_fast.py b/melo/transcript_fast.py new file mode 100644 index 000000000..6d253b094 --- /dev/null +++ b/melo/transcript_fast.py @@ -0,0 +1,57 @@ +import os +import argparse +os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE' +from faster_whisper import WhisperModel + +def transcribe_folder(input_folder, output_file): + LANGUAGE_CODE = "EN" + LANGUAGE_MODEL = "EN-default" + + # Initialize the faster-whisper model + model = WhisperModel("medium", device="cuda", compute_type="int8_float16") + + # Get the list of WAV files in the input directory + wav_files = [file for file in os.listdir(input_folder) if file.endswith(".wav")] + + # Sort the WAV files in numeric order + wav_files = sorted(wav_files, key=lambda x: int(os.path.splitext(x)[0])) + + # Open a text file for writing the transcripts + with open(output_file, "w", encoding="utf-8") as transcript_file: + # Prepare the output path with forward slashes + output_path = input_folder.replace('\\', '/') + + # Iterate through each WAV file + for i, wav_file in enumerate(wav_files): + print(f"Transcribing: {wav_file}") + wav_path = os.path.join(input_folder, wav_file) + + segments, info = model.transcribe(wav_path) + transcribed_text = " ".join([segment.text for segment in segments]).strip() + + line = f"{output_path}/{wav_file}|{LANGUAGE_MODEL}|{LANGUAGE_CODE}|{transcribed_text}" + if i < len(wav_files) - 1: + line += "\n" + transcript_file.write(line) + + print(f"Transcription complete. Check '{output_file}' for results.") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Transcribe WAV files using faster-whisper') + parser.add_argument('--wavs_path', type=str, default="data\\example\\wavs", + help='Path to the directory containing WAV files (default: data\\example\\wavs)') + parser.add_argument('--metadata_path', type=str, default="data\\example\\metadata.list", + help='Path for the output metadata.list file (default: data\\example\\metadata.list)') + + args = parser.parse_args() + + # Check if the input directory exists + if not os.path.exists(args.wavs_path): + print(f"Error: Directory '{args.wavs_path}' does not exist.") + exit(1) + + # Create output directory if it doesn't exist + os.makedirs(os.path.dirname(args.metadata_path), exist_ok=True) + + # Run transcription + transcribe_folder(args.wavs_path, args.metadata_path) diff --git a/melo/trim_models.bat b/melo/trim_models.bat new file mode 100644 index 000000000..9e11e4f51 --- /dev/null +++ b/melo/trim_models.bat @@ -0,0 +1,3 @@ +conda activate melotts-win + +python trim_models.py "Path\To\Models\" diff --git a/melo/trim_models.py b/melo/trim_models.py new file mode 100644 index 000000000..af6672178 --- /dev/null +++ b/melo/trim_models.py @@ -0,0 +1,144 @@ +import os +import torch +import argparse +from pathlib import Path + +def clean_checkpoint(input_path): + """ + Convert a full MeloTTS training checkpoint to an inference-only checkpoint. + + Args: + input_path (Path): Path to the full checkpoint + """ + print(f"Processing checkpoint: {input_path}") + + # Load the checkpoint + checkpoint = torch.load(input_path, map_location='cpu') + + print("\nCheckpoint contents:") + print("Keys in checkpoint:", list(checkpoint.keys())) + + # Create clean checkpoint keeping only necessary components + clean_checkpoint = { + 'model': checkpoint['model'], # Keep the model state + 'iteration': checkpoint['iteration'], # Keep track of training iteration + 'learning_rate': checkpoint['learning_rate'], # Keep the learning rate + # Exclude the optimizer state + } + + # Generate output path with _trimmed suffix + output_path = input_path.parent / f"{input_path.stem}_trimmed{input_path.suffix}" + + # Save the clean checkpoint + print(f"\nSaving clean checkpoint to: {output_path}") + torch.save(clean_checkpoint, output_path) + + # Print size comparison + original_size = os.path.getsize(input_path) / (1024 * 1024) # MB + clean_size = os.path.getsize(output_path) / (1024 * 1024) # MB + + print(f"\nSize comparison:") + print(f"Original checkpoint: {original_size:.2f} MB") + print(f"Clean checkpoint: {clean_size:.2f} MB") + print(f"Size reduction: {((original_size - clean_size) / original_size * 100):.1f}%") + + # Verify the clean checkpoint was saved properly + if os.path.getsize(output_path) == 0: + raise Exception("Error: Output file is empty!") + + # Load the clean checkpoint to verify it's valid + try: + test_load = torch.load(output_path, map_location='cpu') + print("\nVerification successful - cleaned checkpoint contains:") + print("Keys in cleaned checkpoint:", list(test_load.keys())) + except Exception as e: + raise Exception(f"Error verifying cleaned checkpoint: {str(e)}") + +def should_process_file(filename): + """ + Check if the file should be processed based on filename patterns. + + Args: + filename (str): Name of the file to check + + Returns: + bool: True if file should be processed, False otherwise + """ + excluded_prefixes = ['D_', 'DUR_'] + return not any(filename.startswith(prefix) for prefix in excluded_prefixes) + +def process_directory(directory_path, debug=False): + """ + Process all eligible .pth files in the given directory. + + Args: + directory_path (str): Path to directory containing checkpoints + debug (bool): Whether to print debug information + """ + # Clean up the path string and convert to Path object + directory_path = directory_path.strip().strip('"').strip("'") + directory = Path(directory_path).resolve() + + if debug: + print(f"Attempting to process directory: {directory}") + + if not directory.exists(): + raise ValueError(f"Directory does not exist: {directory}") + + if not directory.is_dir(): + raise ValueError(f"Path is not a directory: {directory}") + + # Find all .pth files in the directory that don't start with excluded prefixes + pth_files = [f for f in directory.glob("*.pth") if should_process_file(f.name)] + + if not pth_files: + print(f"No eligible .pth files found in {directory}") + return + + print(f"Found {len(pth_files)} eligible .pth files to process") + + # Print files that will be skipped if debug is enabled + if debug: + all_pth_files = list(directory.glob("*.pth")) + skipped_files = [f for f in all_pth_files if not should_process_file(f.name)] + if skipped_files: + print("\nSkipping the following files:") + for f in skipped_files: + print(f"- {f.name}") + + success_count = 0 + for pth_file in pth_files: + try: + print(f"\nProcessing {pth_file.name}...") + clean_checkpoint(pth_file) + success_count += 1 + except Exception as e: + print(f"Error processing {pth_file.name}: {str(e)}") + if debug: + import traceback + traceback.print_exc() + + print(f"\nProcessing complete!") + print(f"Successfully processed {success_count} out of {len(pth_files)} files") + +def main(): + parser = argparse.ArgumentParser(description='Convert MeloTTS training checkpoints to inference-only checkpoints') + parser.add_argument('directory', type=str, help='Directory containing checkpoint files') + parser.add_argument('--debug', action='store_true', + help='Print additional debugging information') + + args = parser.parse_args() + + try: + process_directory(args.directory, args.debug) + except Exception as e: + print(f"\nError occurred: {str(e)}") + if args.debug: + import traceback + traceback.print_exc() + return 1 + + return 0 + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/melo/try.py b/melo/try.py new file mode 100644 index 000000000..5bb74760b --- /dev/null +++ b/melo/try.py @@ -0,0 +1,140 @@ +from melo.api import TTS +import sounddevice as sd +import numpy as np +from glob import glob +import os + +def get_ckpt_files(folder_path): + return [os.path.basename(f) for f in glob(os.path.join(folder_path, '*.pth'))] + +def get_config_path(ckpt_path): + """Get the corresponding config path for a checkpoint file""" + base_path = os.path.splitext(ckpt_path)[0] + config_options = [ + f"{base_path}.config.json", # try g_1000.config.json + f"{base_path}.json", # try g_1000.json + ] + + for config_path in config_options: + if os.path.exists(config_path): + return config_path + return None + +def load_custom_model(ckpt_file): + ckpt_path = os.path.join("custom", ckpt_file) + config_path = get_config_path(ckpt_path) + + if config_path is None: + print(f"Error: No config file found for {ckpt_file}") + print("Expected either:") + print(f"- {os.path.splitext(ckpt_file)[0]}.config.json") + print(f"- {os.path.splitext(ckpt_file)[0]}.json") + return None + + try: + return TTS(language="EN", config_path=config_path, ckpt_path=ckpt_path) + except Exception as e: + print(f"Error loading custom model: {e}") + return None + + +def get_speaker_names(model): + """Get mapping of speaker IDs to names""" + return model.hps.data.spk2id + +def list_available_models(): + models = { + 'EN': TTS(language='EN'), + 'ES': TTS(language='ES'), + 'FR': TTS(language='FR'), + 'ZH': TTS(language='ZH'), + 'JP': TTS(language='JP'), + 'KR': TTS(language='KR'), + } + return models + +def select_speaker(model): + """Let user select a speaker from available options""" + speakers = get_speaker_names(model) + + print("\nAvailable speakers:") + for name, id in speakers.items(): + print(f"- {name} (ID: {id})") + + while True: + speaker = input("\nEnter speaker name or ID: ") + # Check if input is a number + if speaker.isdigit(): + id_num = int(speaker) + # Check if ID exists in values + for name, spk_id in speakers.items(): + if spk_id == id_num: + return id_num + print("Invalid speaker ID. Please try again.") + # Check if input is a speaker name + elif speaker in speakers: + return speakers[speaker] + print("Invalid input. Enter either speaker name or ID number.") + +def main(): + print("Loading models...") + models = list_available_models() + + # Check for custom models + custom_folder = os.path.join("custom") + custom_models = get_ckpt_files(custom_folder) + + print("\nAvailable languages:") + for lang in models.keys(): + print(f"- {lang}") + + if custom_models: + print("\nAvailable custom models:") + for model in custom_models: + print(f"- {model}") + + use_custom = input("\nUse custom model? (y/n): ").lower() == 'y' + + if use_custom: + if not custom_models: + print("No custom models found in 'custom' directory") + return + + print("\nSelect custom model:") + for i, model in enumerate(custom_models): + print(f"{i+1}. {model}") + + try: + choice = int(input("Enter number: ")) - 1 + model = load_custom_model(custom_models[choice]) + if not model: + return + speaker_id = select_speaker(model) + except (ValueError, IndexError): + print("Invalid selection") + return + else: + lang = input("\nSelect language (EN/ES/FR/ZH/JP/KR): ").upper() + if lang not in models: + print("Invalid language selected") + return + + model = models[lang] + speaker_id = select_speaker(model) + + print("\nEnter text to speak (Type 'quit' to exit)") + while True: + text = input("\nText (Type 'quit' to exit): ") + if text.lower() == 'quit': + break + + try: + audio = model.tts_to_file(text, speaker_id, pbar=None, quiet=True) + sd.play(audio, model.hps.data.sampling_rate) + sd.wait() + + except Exception as e: + print(f"Error generating speech: {e}") + +if __name__ == "__main__": + main()