Cascade-Edge / app.py
VSPAN's picture
Update app.py
92f766b verified
raw
history blame
8.52 kB
import gradio as gr
import edge_tts
import asyncio
import tempfile
import re
import emoji
from flask import Flask, request, jsonify, render_template_string
app = Flask(__name__)
# Функция для очистки текста от нежелательных символов и эмодзи
def clean_text(text):
# Удаление указанных символов
text = re.sub(r'[*_~><]', '', text)
# Удаление эмодзи
text = emoji.replace_emoji(text, replace='')
return text
# Get all available voices
async def get_voices():
voices = await edge_tts.list_voices()
return {f"{v['ShortName']} - {v['Locale']} ({v['Gender']})": v['ShortName'] for v in voices}
# Text-to-speech function
async def text_to_speech(text, voice, rate, pitch):
if not text.strip():
return None, "Пожалуйста, введите текст для озвучки."
if not voice:
return None, "Пожалуйста, выберите голос."
# Очистка текста
text = clean_text(text)
voice_short_name = voice.split(" - ")[0]
rate_str = f"{rate:+d}%"
pitch_str = f"{pitch:+d}Hz"
communicate = edge_tts.Communicate(text, voice_short_name, rate=rate_str, pitch=pitch_str)
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
tmp_path = tmp_file.name
try:
await communicate.save(tmp_path)
except Exception as e:
return None, f"Произошла ошибка при конвертации текста в речь: {str(e)}"
return tmp_path, None
# HTML шаблон
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Озвучка с музыкальной атмосферой</title>
<style>
body {
background-color: white;
color: #FF6347; /* Томатный оранжевый */
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
header {
background-color: #FF6347;
color: white;
padding: 10px 20px;
text-align: center;
}
.container {
padding: 20px;
}
.audio-control {
position: fixed;
bottom: 20px;
left: 20px;
background-color: white;
border: 1px solid #FF6347;
padding: 10px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
button {
background-color: #FF6347;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin-top: 10px;
}
button:hover {
background-color: #FF4500; /* Темный томатный оранжевый */
}
</style>
</head>
<body>
<header>
<h1>Озвучка с музыкальной атмосферой</h1>
</header>
<div class="container">
<textarea id="text-input" rows="5" placeholder="Введите текст для озвучки"></textarea>
<br>
<select id="voice-select">
<!-- Опции будут заполнены через JavaScript -->
</select>
<br>
<label for="rate-slider">Скорость озвучки:</label>
<input type="range" id="rate-slider" min="-50" max="50" value="0" step="1">
<br>
<label for="pitch-slider">Тон озвучки:</label>
<input type="range" id="pitch-slider" min="-20" max="20" value="0" step="1">
<br>
<button onclick="generateAudio()">Озвучить</button>
<br>
<audio id="audio-player" controls style="display:none;"></audio>
</div>
<div class="audio-control">
<label for="atmosphere-select">Атмосфера:</label>
<select id="atmosphere-select">
<option value="dynamic">Динамичная</option>
<option value="calm">Спокойная</option>
</select>
<br>
<button onclick="toggleMusic()">Выключить музыку</button>
<audio id="background-music" loop></audio>
</div>
<script>
let voices = [];
let currentMusic = null;
document.addEventListener('DOMContentLoaded', () => {
fetch('/get_voices')
.then(response => response.json())
.then(data => {
voices = data;
const voiceSelect = document.getElementById('voice-select');
Object.keys(voices).forEach(key => {
const option = document.createElement('option');
option.value = voices[key];
option.textContent = key;
voiceSelect.appendChild(option);
});
});
const atmosphereSelect = document.getElementById('atmosphere-select');
atmosphereSelect.addEventListener('change', () => {
changeMusic(atmosphereSelect.value);
});
changeMusic(atmosphereSelect.value); // Устанавливаем музыку по умолчанию
});
function generateAudio() {
const text = document.getElementById('text-input').value;
const voice = document.getElementById('voice-select').value;
const rate = document.getElementById('rate-slider').value;
const pitch = document.getElementById('pitch-slider').value;
if (!text.trim()) {
alert('Пожалуйста, введите текст для озвучки.');
return;
}
if (!voice) {
alert('Пожалуйста, выберите голос.');
return;
}
fetch('/tts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ text, voice, rate, pitch })
})
.then(response => response.json())
.then(data => {
if (data.warning) {
alert(data.warning);
} else {
const audioPlayer = document.getElementById('audio-player');
audioPlayer.src = data.audio;
audioPlayer.style.display = 'block';
audioPlayer.play();
audioPlayer.addEventListener('ended', () => {
document.getElementById('background-music').play();
});
}
});
}
function changeMusic(atmosphere) {
const backgroundMusic = document.getElementById('background-music');
backgroundMusic.src = `/music/${atmosphere}.mp3`;
backgroundMusic.play();
currentMusic = atmosphere;
}
function toggleMusic() {
const backgroundMusic = document.getElementById('background-music');
if (backgroundMusic.paused) {
backgroundMusic.play();
document.querySelector('.audio-control button').textContent = 'Выключить музыку';
} else {
backgroundMusic.pause();
document.querySelector('.audio-control button').textContent = 'Включить музыку';
}
}
</script>
</body>
</html>
"""
@app.route('/get_voices', methods=['GET'])
async def get_voices_route():
voices = await get_voices()
return jsonify(voices)
@app.route('/tts', methods=['POST'])
async def tts_route():
data = request.get_json()
text = data.get('text', '')
voice = data.get('voice', '')
rate = data.get('rate', 0)
pitch = data.get('pitch', 0)
audio, warning = await text_to_speech(text, voice, rate, pitch)
if warning:
return jsonify({'warning': warning})
else:
return jsonify({'audio': audio})
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)
@app.route('/music/<path:path>')
def serve_music(path):
return app.send_static_file(f'music/{path}')
if __name__ == "__main__":
app.run(debug=True)