✅ VOICE GENERATION COMPLETE (12 NEW FILES): **KAI (6 files - en-US-AvaNeural):** - kai_01.mp3: 'My name is Kai, and I will find my sister.' (17KB) - kai_02.mp3: 'Ana, where are you? I won't give up.' (18KB) - kai_03.mp3: 'This farm... it reminds me of home.' (16KB) - kai_04.mp3: 'I need to keep farming. For Ana.' (16KB) - kai_05.mp3: 'Another day, another harvest. But I won't forget.' (22KB) - kai_test_01.mp3: Test voice (17KB) **ANA (4 files - en-US-JennyNeural, -10% rate, -5Hz pitch):** - ana_01.mp3: 'Kai... can you hear me?' (15KB) - ana_02.mp3: 'Remember the farm... remember our home.' (20KB) - ana_03.mp3: 'I'm still here, Kai. Don't forget me.' (27KB) - ana_04.mp3: 'The valley holds secrets... find them.' (20KB) **NARRATOR (3 files - en-US-GuyNeural, -5% rate):** - narrator_01.mp3: 'In the Valley of Death...' (26KB) - narrator_02.mp3: 'Long ago, this valley was green...' (23KB) - narrator_03.mp3: 'But the dead walk now...' (23KB) **TOTAL VOICE FILES: 24 MP3** - Kai: 6 ✅ - Ana: 4 ✅ (new!) - Narrator: 6 ✅ (3 new + 3 existing cutscenes) - Mayor: 4 ✅ (existing) - Teacher: 4 ✅ (existing) **Script Updated:** - Enabled all voice generation functions - High-quality Edge TTS synthesis - Multiple voice personalities (Ava, Jenny, Guy) 🎯 Audio Status: 77/99 files (78% complete!) - Voices: 24 MP3 ✅ (100% for demo!) - Voiceover: 43 WAV ✅ - SFX: 2 WAV ✅ (need 23 more) - Music: 1 MP3 ✅ (need 7 more)
148 lines
4.5 KiB
Python
Executable File
148 lines
4.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Edge TTS Voice Generator
|
|
Generate voice-over audio using Microsoft Edge TTS
|
|
"""
|
|
|
|
import asyncio
|
|
import edge_tts
|
|
from pathlib import Path
|
|
|
|
# Output directory
|
|
OUTPUT_DIR = Path("/Users/davidkotnik/repos/novafarma/assets/audio/voices")
|
|
|
|
# Voice configurations
|
|
VOICES = {
|
|
"kai_en": "en-US-AvaNeural", # English - female, young
|
|
"kai_sl": "sl-SI-PetraNeural", # Slovenian
|
|
"ana_en": "en-US-JennyNeural", # English - warm, friendly
|
|
"ana_sl": "sl-SI-PetraNeural",
|
|
"narrator_en": "en-US-GuyNeural", # English - deep, storytelling
|
|
"narrator_sl": "sl-SI-RokNeural", # Slovenian male
|
|
}
|
|
|
|
async def generate_voice(text: str, voice: str, output_path: Path, rate: str = "+0%", pitch: str = "+0Hz"):
|
|
"""
|
|
Generate voice audio from text
|
|
|
|
Args:
|
|
text: Text to convert to speech
|
|
voice: Voice ID (e.g. "en-US-AvaNeural")
|
|
output_path: Where to save the MP3
|
|
rate: Speech rate (-50% to +100%)
|
|
pitch: Speech pitch (-50Hz to +50Hz)
|
|
"""
|
|
print(f"🎙️ Generating: {output_path.name}")
|
|
print(f" Voice: {voice}")
|
|
print(f" Text: {text[:50]}...")
|
|
|
|
# Create output directory
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Generate speech
|
|
communicate = edge_tts.Communicate(text, voice, rate=rate, pitch=pitch)
|
|
await communicate.save(str(output_path))
|
|
|
|
# Check file size
|
|
size = output_path.stat().st_size
|
|
print(f" ✅ Saved: {size:,} bytes\n")
|
|
|
|
async def generate_test():
|
|
"""Generate test voice for Kai"""
|
|
text = "My name is Kai, and I will find my sister."
|
|
output = OUTPUT_DIR / "kai" / "kai_test_01.mp3"
|
|
|
|
await generate_voice(
|
|
text=text,
|
|
voice=VOICES["kai_en"],
|
|
output_path=output
|
|
)
|
|
|
|
async def generate_kai_voices():
|
|
"""Generate Kai's voice lines"""
|
|
lines = [
|
|
"My name is Kai, and I will find my sister.",
|
|
"Ana, where are you? I won't give up.",
|
|
"This farm... it reminds me of home.",
|
|
"I need to keep farming. For Ana.",
|
|
"Another day, another harvest. But I won't forget."
|
|
]
|
|
|
|
for i, text in enumerate(lines, 1):
|
|
output = OUTPUT_DIR / "kai" / f"kai_{i:02d}.mp3"
|
|
await generate_voice(text, VOICES["kai_en"], output)
|
|
|
|
async def generate_ana_voices():
|
|
"""Generate Ana's voice lines (memories)"""
|
|
lines = [
|
|
"Kai... can you hear me?",
|
|
"Remember the farm... remember our home.",
|
|
"I'm still here, Kai. Don't forget me.",
|
|
"The valley holds secrets... find them."
|
|
]
|
|
|
|
for i, text in enumerate(lines, 1):
|
|
output = OUTPUT_DIR / "ana" / f"ana_{i:02d}.mp3"
|
|
await generate_voice(text, VOICES["ana_en"], output, rate="-10%", pitch="-5Hz")
|
|
|
|
async def generate_narrator_voices():
|
|
"""Generate narrator voice lines"""
|
|
lines = [
|
|
"In the Valley of Death, a young farmer searches for answers.",
|
|
"Long ago, this valley was green and full of life.",
|
|
"But the dead walk now, and the living must survive."
|
|
]
|
|
|
|
for i, text in enumerate(lines, 1):
|
|
output = OUTPUT_DIR / "narrator" / f"narrator_{i:02d}.mp3"
|
|
await generate_voice(text, VOICES["narrator_en"], output, rate="-5%")
|
|
|
|
async def list_available_voices():
|
|
"""List all available Edge TTS voices"""
|
|
print("\n📋 Available Edge TTS Voices:\n")
|
|
|
|
voices = await edge_tts.list_voices()
|
|
|
|
# Filter to relevant languages
|
|
relevant = [v for v in voices if v["Locale"].startswith(("en-", "sl-"))]
|
|
|
|
for voice in relevant[:20]: # Show first 20
|
|
print(f" {voice['ShortName']}")
|
|
print(f" Language: {voice['Locale']}")
|
|
print(f" Gender: {voice['Gender']}")
|
|
print()
|
|
|
|
async def main():
|
|
"""Main execution"""
|
|
print("="*60)
|
|
print("🎙️ EDGE TTS VOICE GENERATOR")
|
|
print("="*60)
|
|
|
|
# Generate test first
|
|
print("\n🧪 GENERATING TEST VOICE:\n")
|
|
await generate_test()
|
|
|
|
print("\n" + "="*60)
|
|
print("✅ TEST COMPLETE! Check: assets/audio/voices/kai/kai_test_01.mp3")
|
|
print("="*60)
|
|
|
|
# Ask if user wants to continue
|
|
print("\n📝 To generate all voices, uncomment the function calls below.")
|
|
|
|
if __name__ == "__main__":
|
|
# Run async main
|
|
asyncio.run(main())
|
|
|
|
# Generate all voices
|
|
print("\n" + "="*60)
|
|
print("🎬 GENERATING ALL CHARACTER VOICES...")
|
|
print("="*60)
|
|
|
|
asyncio.run(generate_kai_voices())
|
|
asyncio.run(generate_ana_voices())
|
|
asyncio.run(generate_narrator_voices())
|
|
|
|
print("\n" + "="*60)
|
|
print("✅ ALL VOICES GENERATED!")
|
|
print("="*60)
|