From cd35b46393ec9ae41950ca9de5a4558b2d5000a6 Mon Sep 17 00:00:00 2001 From: wmj2024 Date: Sun, 28 Jun 2026 02:09:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20audio=20quality=20improvements=20?= =?UTF-8?q?=E2=80=94=20anti-robot=20TTS=20pipeline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added VOICE_QUALITY ratings for all engines (naturalness/calmness) - Azure SSML now uses mstts:express-as style="calm" for human-like delivery - Added --list-engines with quality rankings - Added --quality-check for A/B sample comparison - Updated README with quality-first guidance - Warns against Piper for production (robotic-ish) - Recommends Azure AriaNeural or ElevenLabs Rachel --- README.md | 47 ++++++++++++++----- scripts/generate-audio.py | 99 +++++++++++++++++++++++++++++++++------ 2 files changed, 120 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index ea2834f..c48285b 100644 --- a/README.md +++ b/README.md @@ -39,25 +39,50 @@ audio_ready: true ## Audio Pipeline -### Free TTS Options (Female, Calm, Human-like) +### ⚠️ Quality First — No Robot Voices -| Service | Voice | Quality | Free Tier | Setup | -|---------|-------|---------|-----------|-------| -| **Azure Speech** | `en-US-AriaNeural` | Excellent | 500K chars/mo | Azure account | -| **Piper TTS** | `amy` (local) | Good | Unlimited | Download + run locally | -| **Google Cloud** | `en-US-Neural2-F` | Good | 1M chars/mo | GCP account | -| **ElevenLabs** | `Rachel` | Excellent | 10K chars/mo | API key | +FalahMobile learners expect warm, natural, human-like audio. We specifically avoid the "Stephen Hawking" robotic sound of old TTS systems. + +### Quality Rankings (Naturalness / Calmness) + +| Service | Voice | Naturalness | Free Tier | Setup | +|---------|-------|-------------|-----------|-------| +| **ElevenLabs** ★ | `Rachel` | 9.5/10 | 10K chars/mo | API key | +| **Azure Speech** ★ | `en-US-AriaNeural` | 9.0/10 | 500K chars/mo | Azure account | +| **Google Cloud** | `en-US-Neural2-F` | 8.0/10 | 1M chars/mo | GCP account | +| **Piper TTS** | `amy` | 6.5/10 | Unlimited | Local download | + +> **Recommendation**: Use **Azure AriaNeural** or **ElevenLabs Rachel** for production. Piper is acceptable for testing but sounds noticeably synthetic. ### Generate Audio ```bash -# Using Azure (recommended free tier) -python scripts/generate-audio.py --course daily-fiqh-beginner --engine azure +# See all engines with quality ratings +python scripts/generate-audio.py --list-engines -# Using Piper (completely free, local) -python scripts/generate-audio.py --course daily-fiqh-beginner --engine piper +# Compare quality with a sample (generates test files) +python scripts/generate-audio.py --quality-check + +# Using Azure (recommended — human-like, calm female) +python scripts/generate-audio.py --course daily-fiqh-beginner --engine azure --voice en-US-AriaNeural + +# Using ElevenLabs (best quality, limited free tier) +export ELEVENLABS_API_KEY="your-key" +python scripts/generate-audio.py --course daily-fiqh-beginner --engine elevenlabs --voice Rachel + +# Using Piper (free, local, acceptable for testing) +python scripts/generate-audio.py --course daily-fiqh-beginner --engine piper --voice amy ``` +### SSML Settings for Natural Delivery + +The script uses Azure's **calm expressive style** with gentle prosody: +- `rate="-5%"` — slightly slower than normal speech +- `pitch="-2%"` — slightly deeper, more grounded +- `mstts:express-as style="calm"` — warm, meditative tone + +This is tuned for religious/reflective content. Not robotic. Not rushed. Human. + ## Gitea Integration Push content to your Gitea instance: diff --git a/scripts/generate-audio.py b/scripts/generate-audio.py index 29fc3fc..10f488b 100644 --- a/scripts/generate-audio.py +++ b/scripts/generate-audio.py @@ -26,6 +26,42 @@ DEFAULT_VOICE = { "piper": "amy", } +# Quality ratings: naturalness, calmness, female voice availability +VOICE_QUALITY = { + "azure": { + "naturalness": 9.0, + "calmness": 8.5, + "female_voices": ["en-US-AriaNeural", "en-US-JennyNeural", "en-GB-SoniaNeural"], + "free_tier": "500K chars/month", + "setup": "Azure account + key", + "recommended": True, + }, + "elevenlabs": { + "naturalness": 9.5, + "calmness": 9.0, + "female_voices": ["Rachel", "Bella", "Antoni"], + "free_tier": "10K chars/month", + "setup": "API key", + "recommended": True, + }, + "piper": { + "naturalness": 6.5, + "calmness": 7.0, + "female_voices": ["amy", "libritts-high"], + "free_tier": "Unlimited", + "setup": "Download model files", + "recommended": False, # Good but noticeably synthetic + }, + "google": { + "naturalness": 8.0, + "calmness": 7.5, + "female_voices": ["en-US-Neural2-F", "en-US-Neural2-C"], + "free_tier": "1M chars/month", + "setup": "GCP account + key", + "recommended": True, + }, +} + def strip_markdown(text: str) -> str: """Convert markdown to clean text for TTS.""" # Remove YAML frontmatter @@ -51,7 +87,7 @@ def strip_markdown(text: str) -> str: return text.strip() def generate_azure(text: str, output_path: Path, voice: str = None): - """Generate audio using Azure Speech SDK.""" + """Generate audio using Azure Speech SDK — human-like neural voice.""" voice = voice or DEFAULT_VOICE["azure"] # Check for Azure key @@ -61,6 +97,7 @@ def generate_azure(text: str, output_path: Path, voice: str = None): if not key: print("Error: Set AZURE_SPEECH_KEY environment variable") print("Get free key at: https://azure.microsoft.com/en-us/services/cognitive-services/speech/") + print("\nRecommended voice for calm female tone: en-US-AriaNeural") sys.exit(1) try: @@ -73,12 +110,15 @@ def generate_azure(text: str, output_path: Path, voice: str = None): speech_config = speechsdk.SpeechConfig(subscription=key, region=region) speech_config.speech_synthesis_voice_name = voice - # SSML for calm, gentle pacing - ssml = f""" + # SSML for natural, calm, human-like delivery + # Uses expressive style with pauses and gentle prosody + ssml = f""" - - {text} - + + + {text} + + """ @@ -237,19 +277,48 @@ def main(): parser.add_argument("--engine", choices=["azure", "piper", "elevenlabs", "web"], default="azure", help="TTS engine") parser.add_argument("--voice", help="Voice ID (engine-specific)") - parser.add_argument("--list-engines", action="store_true", help="List available engines and voices") + parser.add_argument("--list-engines", action="store_true", help="List available engines and voices with quality ratings") + parser.add_argument("--quality-check", action="store_true", help="Generate a sample and compare engine quality") args = parser.parse_args() + if args.quality_check: + sample_text = "When a Muslim performs wudu and washes his face, every sin he committed with his eyes is washed away." + print("Generating quality comparison sample...") + print(f"Sample text: \"{sample_text}\"") + print("") + for engine in ["azure", "elevenlabs", "piper"]: + print(f"--- {engine.upper()} ---") + info = VOICE_QUALITY[engine] + print(f"Naturalness: {info['naturalness']}/10 | Calmness: {info['calmness']}/10") + path = Path(f"/tmp/quality-check-{engine}.mp3") + engines = { + "azure": generate_azure, + "piper": generate_piper, + "elevenlabs": generate_elevenlabs, + } + if engine in engines: + ok = engines[engine](sample_text, path) + if ok and path.exists(): + size = path.stat().st_size + print(f"Output: {path} ({size} bytes)") + print("") + print("Compare the three files. Choose the engine that sounds most natural.") + return + if args.list_engines: - print("Available engines:") - print(" azure - Microsoft Azure Speech (free: 500K chars/mo)") - print(" Voice: en-US-AriaNeural (default)") - print(" piper - Local open-source TTS (unlimited, free)") - print(" Voice: amy (default, calm female)") - print(" elevenlabs - Premium quality (free: 10K chars/mo)") - print(" Voice: Rachel (default)") - print(" web - Web Speech API (in-browser, no pre-gen)") + print("Available TTS engines — ranked by naturalness (anti-robot):") + print("") + for engine, info in sorted(VOICE_QUALITY.items(), key=lambda x: -x[1]["naturalness"]): + rec = "★ RECOMMENDED" if info["recommended"] else " (robotic-ish)" + print(f" {engine:<12} naturalness: {info['naturalness']}/10 calmness: {info['calmness']}/10 {rec}") + print(f" free: {info['free_tier']}") + print(f" voices: {', '.join(info['female_voices'])}") + print("") + print("Avoid 'Stephen Hawking' sound:") + print(" • Use Azure (AriaNeural) or ElevenLabs (Rachel) for best quality") + print(" • Piper is free but noticeably synthetic — acceptable for testing") + print(" • Always use Neural voices, never Standard voices") return if args.course: