98 lines
3.4 KiB
Python
98 lines
3.4 KiB
Python
"""
|
|
Language Map Generator for YouTube Auto Dub.
|
|
|
|
This script fetches the latest available voices from Microsoft Edge TTS
|
|
and generates a `language_map.json` file compatible with the
|
|
Multi-Speaker Diarization system.
|
|
|
|
It groups voices into 'male' and 'female' lists (pools) for every language,
|
|
enabling the engine to rotate voices for different speakers automatically.
|
|
|
|
Usage: python latest_langmap_generate.py
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import edge_tts
|
|
from pathlib import Path
|
|
from typing import Dict, List, Any
|
|
|
|
# Define path relative to project root (assuming this script is in root or src)
|
|
# Adjust BASE_DIR if you move this script.
|
|
BASE_DIR = Path(__file__).resolve().parent
|
|
LANG_MAP_FILE = BASE_DIR / "language_map.json"
|
|
|
|
async def generate_lang_map() -> None:
|
|
print("[*] Connecting to Microsoft Edge TTS API...")
|
|
|
|
try:
|
|
# Fetch all available voices
|
|
voices = await edge_tts.list_voices()
|
|
except Exception as e:
|
|
print(f"[!] CRITICAL: Failed to fetch voices: {e}")
|
|
return
|
|
|
|
print(f"[*] Processing {len(voices)} raw voice entries...")
|
|
|
|
# Structure: { "vi": { "name": "vi-VN", "voices": { "male": [], "female": [] } } }
|
|
lang_map: Dict[str, Any] = {}
|
|
|
|
for v in voices:
|
|
# 1. FILTER: Strict quality control - Neural voices only
|
|
if "Neural" not in v["ShortName"]:
|
|
continue
|
|
|
|
# 2. EXTRACT: Parse metadata
|
|
short_name = v["ShortName"] # e.g., "vi-VN-NamMinhNeural"
|
|
locale = v["Locale"] # e.g., "vi-VN"
|
|
gender = v["Gender"].lower() # "male" or "female"
|
|
|
|
# ISO Language Code (e.g., 'vi' from 'vi-VN')
|
|
lang_code = locale.split('-')[0]
|
|
|
|
# 3. INITIALIZE: Create structure if language not seen before
|
|
if lang_code not in lang_map:
|
|
lang_map[lang_code] = {
|
|
"name": locale, # Store locale as a friendly name reference
|
|
"voices": {
|
|
"male": [],
|
|
"female": []
|
|
}
|
|
}
|
|
|
|
# 4. POPULATE: Add voice to the specific gender pool
|
|
# This creates the "List" structure required by engines.py
|
|
target_list = lang_map[lang_code]["voices"].get(gender)
|
|
|
|
# Handle case where gender might be undefined or new
|
|
if target_list is None:
|
|
lang_map[lang_code]["voices"][gender] = []
|
|
target_list = lang_map[lang_code]["voices"][gender]
|
|
|
|
if short_name not in target_list:
|
|
target_list.append(short_name)
|
|
|
|
# 5. OPTIMIZE: Remove languages with empty voice lists (optional cleanup)
|
|
final_map = {
|
|
k: v for k, v in lang_map.items()
|
|
if v["voices"]["male"] or v["voices"]["female"]
|
|
}
|
|
|
|
# 6. SAVE: Write to JSON
|
|
try:
|
|
with open(LANG_MAP_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(final_map, f, ensure_ascii=False, indent=2)
|
|
|
|
print(f"\n[+] SUCCESS! Generated configuration for {len(final_map)} languages.")
|
|
print(f" File saved to: {LANG_MAP_FILE}")
|
|
|
|
# Preview a specific language (e.g., Vietnamese)
|
|
if "vi" in final_map:
|
|
print("\n[*] Preview (Vietnamese):")
|
|
print(json.dumps(final_map["vi"], indent=2))
|
|
|
|
except Exception as e:
|
|
print(f"[!] ERROR: Failed to write JSON file: {e}")
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(generate_lang_map()) |