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()