Spaces:
Sleeping
Sleeping
import os | |
import edge_tts | |
import asyncio | |
from flask import Flask, render_template, request, send_from_directory, jsonify | |
app = Flask(__name__) | |
AUDIO_DIR = os.path.join(os.getcwd(), 'generated_audio') | |
os.makedirs(AUDIO_DIR, exist_ok=True) | |
voices = { | |
'Multilingual': { | |
'Andrew': {'voice_id': 'en-US-AndrewMultilingualNeural', 'styles': ['default']}, | |
'Ava': {'voice_id': 'en-US-AvaMultilingualNeural', 'styles': ['default']}, | |
'Brian': {'voice_id': 'en-US-BrianMultilingualNeural', 'styles': ['default']}, | |
'Emma': {'voice_id': 'en-US-EmmaMultilingualNeural', 'styles': ['default']}, | |
'Florian': {'voice_id': 'de-DE-FlorianMultilingualNeural', 'styles': ['default']}, | |
'Giuseppe': {'voice_id': 'it-IT-GiuseppeMultilingualNeural', 'styles': ['default']}, | |
'Hyunsu': {'voice_id': 'ko-KR-HyunsuMultilingualNeural', 'styles': ['default']}, | |
'Remy': {'voice_id': 'fr-FR-RemyMultilingualNeural', 'styles': ['default']}, | |
'Seraphina': {'voice_id': 'de-DE-SeraphinaMultilingualNeural', 'styles': ['default']}, | |
'Thalita': {'voice_id': 'pt-BR-ThalitaMultilingualNeural', 'styles': ['default']}, | |
'Vivienne': {'voice_id': 'fr-FR-VivienneMultilingualNeural', 'styles': ['default']} | |
}, | |
'Arabic': { | |
'Fatima': {'voice_id': 'ar-AE-FatimaNeural', 'styles': ['default']}, | |
'Hamdan': {'voice_id': 'ar-AE-HamdanNeural', 'styles': ['default']}, | |
'Ali': {'voice_id': 'ar-BH-AliNeural', 'styles': ['default']}, | |
'Laila': {'voice_id': 'ar-BH-LailaNeural', 'styles': ['default']}, | |
'Salma': {'voice_id': 'ar-EG-SalmaNeural', 'styles': ['default']}, | |
'Shakir': {'voice_id': 'ar-EG-ShakirNeural', 'styles': ['default']} | |
}, | |
'Chinese': { | |
'Xiaoxiao': {'voice_id': 'zh-CN-XiaoxiaoNeural', 'styles': ['default']}, | |
'Xiaoyi': {'voice_id': 'zh-CN-XiaoyiNeural', 'styles': ['default']}, | |
'Yunjian': {'voice_id': 'zh-CN-YunjianNeural', 'styles': ['default']}, | |
'Yunxi': {'voice_id': 'zh-CN-YunxiNeural', 'styles': ['default']}, | |
'Yunxia': {'voice_id': 'zh-CN-YunxiaNeural', 'styles': ['default']}, | |
'Yunyang': {'voice_id': 'zh-CN-YunyangNeural', 'styles': ['default']} | |
}, | |
'English': { | |
'Ana': {'voice_id': 'en-US-AnaNeural', 'styles': ['default']}, | |
'Andrew': {'voice_id': 'en-US-AndrewNeural', 'styles': ['default']}, | |
'Aria': {'voice_id': 'en-US-AriaNeural', 'styles': ['default']}, | |
'Ava': {'voice_id': 'en-US-AvaNeural', 'styles': ['default']}, | |
'Brian': {'voice_id': 'en-US-BrianNeural', 'styles': ['default']}, | |
'Christopher': {'voice_id': 'en-US-ChristopherNeural', 'styles': ['default']}, | |
'Emma': {'voice_id': 'en-US-EmmaNeural', 'styles': ['default']}, | |
'Eric': {'voice_id': 'en-US-EricNeural', 'styles': ['default']}, | |
'Guy': {'voice_id': 'en-US-GuyNeural', 'styles': ['default']}, | |
'Jenny': {'voice_id': 'en-US-JennyNeural', 'styles': ['default']}, | |
'Michelle': {'voice_id': 'en-US-MichelleNeural', 'styles': ['default']}, | |
'Roger': {'voice_id': 'en-US-RogerNeural', 'styles': ['default']}, | |
'Steffan': {'voice_id': 'en-US-SteffanNeural', 'styles': ['default']} | |
}, | |
'French': { | |
'Denise': {'voice_id': 'fr-FR-DeniseNeural', 'styles': ['default']}, | |
'Eloise': {'voice_id': 'fr-FR-EloiseNeural', 'styles': ['default']}, | |
'Henri': {'voice_id': 'fr-FR-HenriNeural', 'styles': ['default']}, | |
'Charline': {'voice_id': 'fr-BE-CharlineNeural', 'styles': ['default']}, | |
'Gerard': {'voice_id': 'fr-BE-GerardNeural', 'styles': ['default']}, | |
'Antoine': {'voice_id': 'fr-CA-AntoineNeural', 'styles': ['default']}, | |
'Jean': {'voice_id': 'fr-CA-JeanNeural', 'styles': ['default']}, | |
'Sylvie': {'voice_id': 'fr-CA-SylvieNeural', 'styles': ['default']}, | |
'Thierry': {'voice_id': 'fr-CA-ThierryNeural', 'styles': ['default']} | |
}, | |
'German': { | |
'Amala': {'voice_id': 'de-DE-AmalaNeural', 'styles': ['default']}, | |
'Conrad': {'voice_id': 'de-DE-ConradNeural', 'styles': ['default']}, | |
'Katja': {'voice_id': 'de-DE-KatjaNeural', 'styles': ['default']}, | |
'Killian': {'voice_id': 'de-DE-KillianNeural', 'styles': ['default']} | |
}, | |
'Hindi': { | |
'Madhur': {'voice_id': 'hi-IN-MadhurNeural', 'styles': ['default']}, | |
'Swara': {'voice_id': 'hi-IN-SwaraNeural', 'styles': ['default']} | |
}, | |
'Italian': { | |
'Diego': {'voice_id': 'it-IT-DiegoNeural', 'styles': ['default']}, | |
'Elsa': {'voice_id': 'it-IT-ElsaNeural', 'styles': ['default']}, | |
'Isabella': {'voice_id': 'it-IT-IsabellaNeural', 'styles': ['default']} | |
}, | |
'Japanese': { | |
'Keita': {'voice_id': 'ja-JP-KeitaNeural', 'styles': ['default']}, | |
'Nanami': {'voice_id': 'ja-JP-NanamiNeural', 'styles': ['default']} | |
}, | |
'Korean': { | |
'InJoon': {'voice_id': 'ko-KR-InJoonNeural', 'styles': ['default']}, | |
'SunHi': {'voice_id': 'ko-KR-SunHiNeural', 'styles': ['default']} | |
}, | |
'Portuguese': { | |
'Antonio': {'voice_id': 'pt-BR-AntonioNeural', 'styles': ['default']}, | |
'Francisca': {'voice_id': 'pt-BR-FranciscaNeural', 'styles': ['default']}, | |
'Duarte': {'voice_id': 'pt-PT-DuarteNeural', 'styles': ['default']}, | |
'Raquel': {'voice_id': 'pt-PT-RaquelNeural', 'styles': ['default']} | |
}, | |
'Russian': { | |
'Dmitry': {'voice_id': 'ru-RU-DmitryNeural', 'styles': ['default']}, | |
'Svetlana': {'voice_id': 'ru-RU-SvetlanaNeural', 'styles': ['default']} | |
}, | |
'Spanish': { | |
'Alvaro': {'voice_id': 'es-ES-AlvaroNeural', 'styles': ['default']}, | |
'Elvira': {'voice_id': 'es-ES-ElviraNeural', 'styles': ['default']}, | |
'Ximena': {'voice_id': 'es-ES-XimenaNeural', 'styles': ['default']}, | |
'Dalia': {'voice_id': 'es-MX-DaliaNeural', 'styles': ['default']}, | |
'Jorge': {'voice_id': 'es-MX-JorgeNeural', 'styles': ['default']}, | |
'Alonso': {'voice_id': 'es-US-AlonsoNeural', 'styles': ['default']}, | |
'Paloma': {'voice_id': 'es-US-PalomaNeural', 'styles': ['default']} | |
} | |
} | |
def index(): | |
return render_template('index.html', voices=voices) | |
def get_voice_info(): | |
voice_id = request.args.get('voice_id') | |
language = request.args.get('language') | |
if language in voices and voice_id in voices[language]: | |
return jsonify(voices[language][voice_id]) | |
return jsonify({'error': 'Voice not found'}) | |
async def generate_audio(): | |
text = request.form.get('text') | |
language = request.form.get('language') | |
voice = request.form.get('voice') | |
style = request.form.get('style', 'default') | |
# Format rate properly with % symbol | |
rate_value = request.form.get('rate', '0') | |
rate = f"{'+' if int(rate_value) >= 0 else ''}{rate_value}%" | |
# Format volume properly with % symbol | |
volume_value = request.form.get('volume', '0') | |
volume = f"{'+' if int(volume_value) >= 0 else ''}{volume_value}%" | |
# Format pitch properly with Hz | |
pitch_value = request.form.get('pitch', '0') | |
pitch = f"{'+' if int(pitch_value) >= 0 else ''}{pitch_value}Hz" | |
if not all([text, language, voice]): | |
return jsonify({'error': 'Missing required parameters', 'success': False}) | |
try: | |
voice_info = voices[language][voice] | |
voice_id = voice_info['voice_id'] | |
if style != 'default': | |
voice_id = f"{voice_id}(Style={style})" | |
communicate = edge_tts.Communicate( | |
text, | |
voice_id, | |
rate=rate, | |
volume=volume, | |
pitch=pitch | |
) | |
audio_filename = f"{language}_{voice}_{hash(text)}.mp3" | |
audio_filepath = os.path.join(AUDIO_DIR, audio_filename) | |
await communicate.save(audio_filepath) | |
return jsonify({ | |
'success': True, | |
'audio_path': f'/generated_audio/{audio_filename}', | |
'filename': audio_filename | |
}) | |
except Exception as e: | |
return jsonify({'error': str(e), 'success': False}) | |
def serve_audio(filename): | |
return send_from_directory(AUDIO_DIR, filename) | |
def download_audio(filename): | |
return send_from_directory(AUDIO_DIR, filename, as_attachment=True) | |
if __name__ == '__main__': | |
port = int(os.environ.get('PORT', 7860)) # Use PORT from environment or default to 7860 | |
app.run(debug=False, host='0.0.0.0', port=port) | |