feat: Add Piper voice download helpers and guides
- Created download_piper_voices.py for automated download attempts - Created download_piper_voices.sh as alternative bash script - Created EASY_PIPER_SETUP.md with simple 3-step instructions - Created PIPER_DOWNLOAD_GUIDE.md with visual step-by-step guide - Includes troubleshooting, checklists, and pro tips Makes Piper setup much easier for users with multiple guide options.
This commit is contained in:
219
backend/download_piper_voices.py
Executable file
219
backend/download_piper_voices.py
Executable file
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple script to download Piper TTS voice models.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
import ssl
|
||||
|
||||
# Create SSL context that doesn't verify (for development)
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Models directory
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
MODELS_DIR = os.path.join(SCRIPT_DIR, "piper_models")
|
||||
os.makedirs(MODELS_DIR, exist_ok=True)
|
||||
|
||||
# Available voices with direct download links
|
||||
VOICES = {
|
||||
"1": {
|
||||
"name": "en_US-lessac-medium",
|
||||
"description": "Male, American, Medium Quality",
|
||||
"model_url": "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/en_US-lessac-medium.onnx",
|
||||
"config_url": "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/en_US-lessac-medium.onnx.json",
|
||||
},
|
||||
"2": {
|
||||
"name": "en_US-amy-medium",
|
||||
"description": "Female, American, Medium Quality",
|
||||
"model_url": "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/en_US-amy-medium.onnx",
|
||||
"config_url": "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/en_US-amy-medium.onnx.json",
|
||||
},
|
||||
"3": {
|
||||
"name": "en_US-libritts-high",
|
||||
"description": "Neutral, American, High Quality",
|
||||
"model_url": "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/en_US-libritts-high.onnx",
|
||||
"config_url": "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_US/en_US-libritts-high.onnx.json",
|
||||
},
|
||||
"4": {
|
||||
"name": "en_GB-alan-medium",
|
||||
"description": "Male, British, Medium Quality",
|
||||
"model_url": "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/en_GB-alan-medium.onnx",
|
||||
"config_url": "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/en_GB-alan-medium.onnx.json",
|
||||
},
|
||||
"5": {
|
||||
"name": "en_GB-alba-medium",
|
||||
"description": "Female, British, Medium Quality",
|
||||
"model_url": "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/en_GB-alba-medium.onnx",
|
||||
"config_url": "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/en_GB-alba-medium.onnx.json",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def download_file(url, dest_path):
|
||||
"""Download a file with progress indication."""
|
||||
try:
|
||||
print(f" Downloading from: {url}")
|
||||
|
||||
# Use urllib with SSL context
|
||||
with urllib.request.urlopen(url, context=ssl_context, timeout=120) as response:
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
|
||||
with open(dest_path, 'wb') as f:
|
||||
downloaded = 0
|
||||
chunk_size = 8192
|
||||
|
||||
while True:
|
||||
chunk = response.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
|
||||
f.write(chunk)
|
||||
downloaded += len(chunk)
|
||||
|
||||
if total_size > 0:
|
||||
percent = (downloaded / total_size) * 100
|
||||
print(f"\r Progress: {percent:.1f}% ({downloaded}/{total_size} bytes)", end='')
|
||||
|
||||
print() # New line after progress
|
||||
|
||||
# Verify file size
|
||||
file_size = os.path.getsize(dest_path)
|
||||
if file_size < 1000:
|
||||
print(f" ✗ Download failed (file too small: {file_size} bytes)")
|
||||
os.remove(dest_path)
|
||||
return False
|
||||
|
||||
print(f" ✓ Downloaded: {file_size:,} bytes")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ✗ Error: {e}")
|
||||
if os.path.exists(dest_path):
|
||||
os.remove(dest_path)
|
||||
return False
|
||||
|
||||
|
||||
def download_voice(voice_key):
|
||||
"""Download a voice model and config."""
|
||||
voice = VOICES[voice_key]
|
||||
name = voice["name"]
|
||||
|
||||
model_path = os.path.join(MODELS_DIR, f"{name}.onnx")
|
||||
config_path = os.path.join(MODELS_DIR, f"{name}.onnx.json")
|
||||
|
||||
# Check if already exists
|
||||
if os.path.exists(model_path) and os.path.exists(config_path):
|
||||
print(f" ✓ {name} - Already downloaded")
|
||||
return True
|
||||
|
||||
print(f"\n Downloading: {name}")
|
||||
print(f" Description: {voice['description']}")
|
||||
|
||||
# Download model
|
||||
print(" [1/2] Model file (.onnx)...")
|
||||
if not download_file(voice["model_url"], model_path):
|
||||
return False
|
||||
|
||||
# Download config
|
||||
print(" [2/2] Config file (.onnx.json)...")
|
||||
if not download_file(voice["config_url"], config_path):
|
||||
return False
|
||||
|
||||
print(f" ✓ {name} - Complete!")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("Piper TTS Voice Model Downloader")
|
||||
print("=" * 60)
|
||||
print(f"\nModels directory: {MODELS_DIR}\n")
|
||||
|
||||
print("Available voices:\n")
|
||||
for key, voice in VOICES.items():
|
||||
print(f" {key}) {voice['name']}")
|
||||
print(f" {voice['description']}")
|
||||
print()
|
||||
|
||||
print("Quick options:")
|
||||
print(" a) Download all voices (recommended)")
|
||||
print(" s) Download starter pack (1, 2, 4)")
|
||||
print(" q) Quit")
|
||||
print()
|
||||
|
||||
choice = input("Enter your choice (1-5, a, s, or q): ").strip().lower()
|
||||
|
||||
if choice == 'q':
|
||||
print("Exiting...")
|
||||
return
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Downloading...")
|
||||
print("=" * 60)
|
||||
|
||||
success_count = 0
|
||||
total_count = 0
|
||||
|
||||
if choice == 'a':
|
||||
# Download all
|
||||
for key in VOICES.keys():
|
||||
total_count += 1
|
||||
if download_voice(key):
|
||||
success_count += 1
|
||||
elif choice == 's':
|
||||
# Starter pack
|
||||
for key in ['1', '2', '4']:
|
||||
total_count += 1
|
||||
if download_voice(key):
|
||||
success_count += 1
|
||||
elif choice in VOICES:
|
||||
# Single voice
|
||||
total_count += 1
|
||||
if download_voice(choice):
|
||||
success_count += 1
|
||||
else:
|
||||
print(f"Invalid choice: {choice}")
|
||||
return
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Download Summary")
|
||||
print("=" * 60)
|
||||
print(f"\nSuccessfully downloaded: {success_count}/{total_count} voices")
|
||||
|
||||
# List downloaded files
|
||||
print(f"\nFiles in {MODELS_DIR}:")
|
||||
try:
|
||||
files = sorted(os.listdir(MODELS_DIR))
|
||||
if files:
|
||||
for f in files:
|
||||
size = os.path.getsize(os.path.join(MODELS_DIR, f))
|
||||
print(f" {f} ({size:,} bytes)")
|
||||
else:
|
||||
print(" (empty)")
|
||||
except Exception as e:
|
||||
print(f" Error listing files: {e}")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Next Steps")
|
||||
print("=" * 60)
|
||||
print("\n1. Test Piper installation:")
|
||||
print(" python test_piper_direct.py")
|
||||
print("\n2. Start the backend:")
|
||||
print(" uvicorn main:app --reload")
|
||||
print("\n3. In the UI, click 'Piper TTS' button")
|
||||
print("\n4. Select a Piper voice and generate!")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nDownload cancelled by user.")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n\nError: {e}")
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user