Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,257 +5,361 @@ import asyncio
|
|
| 5 |
import tempfile
|
| 6 |
import os
|
| 7 |
import re
|
|
|
|
| 8 |
|
| 9 |
# Get all available voices
|
| 10 |
async def get_voices():
|
| 11 |
voices = await edge_tts.list_voices()
|
| 12 |
return {f"{v['ShortName']} - {v['Locale']} ({v['Gender']})": v['ShortName'] for v in voices}
|
| 13 |
|
| 14 |
-
# Split text into
|
| 15 |
-
def
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
result = []
|
| 19 |
-
current_segment = ""
|
| 20 |
-
|
| 21 |
-
for i, segment in enumerate(segments):
|
| 22 |
-
if segment.strip():
|
| 23 |
-
if re.match(r'^[.!?]+$', segment):
|
| 24 |
-
current_segment += segment
|
| 25 |
-
if current_segment.strip():
|
| 26 |
-
result.append(current_segment.strip())
|
| 27 |
-
current_segment = ""
|
| 28 |
-
else:
|
| 29 |
-
current_segment += segment
|
| 30 |
-
|
| 31 |
-
if current_segment.strip():
|
| 32 |
-
result.append(current_segment.strip())
|
| 33 |
|
| 34 |
-
|
|
|
|
|
|
|
| 35 |
|
| 36 |
-
# Create interactive HTML with clickable
|
| 37 |
def create_interactive_text(text):
|
| 38 |
if not text.strip():
|
| 39 |
-
return ""
|
| 40 |
|
| 41 |
-
|
| 42 |
-
html_parts = []
|
| 43 |
|
| 44 |
-
|
| 45 |
-
<div id="interactive-text" style="line-height:
|
| 46 |
-
<
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
for i,
|
| 50 |
-
|
| 51 |
-
f'<span class="
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
| 56 |
|
| 57 |
-
|
|
|
|
| 58 |
</div>
|
|
|
|
| 59 |
<script>
|
| 60 |
-
let
|
| 61 |
-
let
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
-
function
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
| 66 |
}
|
| 67 |
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
return;
|
| 72 |
}
|
| 73 |
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
-
// Get text from selected
|
| 80 |
-
const
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
detail: {
|
| 85 |
-
text:
|
| 86 |
-
voice:
|
| 87 |
-
rate:
|
| 88 |
-
pitch:
|
| 89 |
startIndex: startIndex
|
| 90 |
}
|
| 91 |
});
|
|
|
|
| 92 |
document.dispatchEvent(event);
|
| 93 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
</script>
|
| 95 |
-
"""
|
| 96 |
|
| 97 |
-
return
|
| 98 |
|
| 99 |
# Text-to-speech function
|
| 100 |
async def text_to_speech(text, voice, rate, pitch):
|
| 101 |
if not text.strip():
|
| 102 |
-
return None,
|
| 103 |
if not voice:
|
| 104 |
-
return None,
|
| 105 |
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
-
#
|
| 116 |
@spaces.GPU
|
| 117 |
-
def
|
| 118 |
-
|
| 119 |
|
| 120 |
-
# Create interactive text
|
| 121 |
interactive_html = create_interactive_text(text)
|
| 122 |
|
| 123 |
-
# Prepare
|
| 124 |
-
|
| 125 |
|
| 126 |
-
# Add
|
| 127 |
-
if
|
|
|
|
| 128 |
interactive_html += f"""
|
| 129 |
<script>
|
| 130 |
setTimeout(() => {{
|
| 131 |
-
|
| 132 |
-
|
|
|
|
| 133 |
</script>
|
| 134 |
"""
|
| 135 |
|
| 136 |
-
return
|
| 137 |
|
| 138 |
-
#
|
| 139 |
@spaces.GPU
|
| 140 |
-
def
|
| 141 |
-
if not
|
| 142 |
-
return None,
|
| 143 |
|
| 144 |
-
|
| 145 |
-
return
|
| 146 |
|
| 147 |
-
# Create Gradio application
|
| 148 |
async def create_demo():
|
| 149 |
voices = await get_voices()
|
| 150 |
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
with gr.Row():
|
| 165 |
-
with gr.Column(scale=
|
|
|
|
|
|
|
| 166 |
text_input = gr.Textbox(
|
| 167 |
-
label="
|
| 168 |
-
lines=
|
| 169 |
-
placeholder="
|
|
|
|
| 170 |
)
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
)
|
| 178 |
|
| 179 |
with gr.Row():
|
| 180 |
-
rate_slider = gr.Slider(
|
| 181 |
-
|
| 182 |
-
maximum=50,
|
| 183 |
-
value=0,
|
| 184 |
-
label="Speech Rate (%)",
|
| 185 |
-
step=1
|
| 186 |
-
)
|
| 187 |
-
pitch_slider = gr.Slider(
|
| 188 |
-
minimum=-20,
|
| 189 |
-
maximum=20,
|
| 190 |
-
value=0,
|
| 191 |
-
label="Pitch (Hz)",
|
| 192 |
-
step=1
|
| 193 |
-
)
|
| 194 |
|
| 195 |
generate_btn = gr.Button(
|
| 196 |
-
"π΅ Generate
|
| 197 |
-
variant="primary",
|
| 198 |
size="lg"
|
| 199 |
)
|
|
|
|
|
|
|
| 200 |
|
| 201 |
-
with gr.Column(scale=
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
)
|
| 206 |
-
partial_audio_output = gr.Audio(
|
| 207 |
-
label="π― Partial Audio (from clicked position)",
|
| 208 |
-
type="filepath"
|
| 209 |
-
)
|
| 210 |
|
| 211 |
# Interactive text display
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
value="<p style='color: #666; font-style: italic;'>
|
| 215 |
)
|
| 216 |
|
| 217 |
-
# Warning display
|
| 218 |
-
warning_display = gr.Markdown(visible=False)
|
| 219 |
-
|
| 220 |
# Hidden components for partial audio generation
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
|
| 226 |
# Event handlers
|
| 227 |
generate_btn.click(
|
| 228 |
-
fn=
|
| 229 |
inputs=[text_input, voice_dropdown, rate_slider, pitch_slider],
|
| 230 |
-
outputs=[
|
| 231 |
)
|
| 232 |
|
| 233 |
-
# JavaScript
|
| 234 |
demo.load(js="""
|
| 235 |
-
function
|
| 236 |
-
|
| 237 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
|
| 248 |
-
//
|
| 249 |
-
|
| 250 |
});
|
| 251 |
}
|
| 252 |
|
| 253 |
-
|
| 254 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
return demo
|
| 257 |
|
| 258 |
# Run the application
|
| 259 |
if __name__ == "__main__":
|
| 260 |
demo = asyncio.run(create_demo())
|
| 261 |
-
demo.launch()
|
|
|
|
| 5 |
import tempfile
|
| 6 |
import os
|
| 7 |
import re
|
| 8 |
+
import json
|
| 9 |
|
| 10 |
# Get all available voices
|
| 11 |
async def get_voices():
|
| 12 |
voices = await edge_tts.list_voices()
|
| 13 |
return {f"{v['ShortName']} - {v['Locale']} ({v['Gender']})": v['ShortName'] for v in voices}
|
| 14 |
|
| 15 |
+
# Split text into words for precise clicking
|
| 16 |
+
def split_text_into_words(text):
|
| 17 |
+
if not text.strip():
|
| 18 |
+
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
+
# Split by words while preserving spaces and punctuation
|
| 21 |
+
words = re.findall(r'\S+|\s+', text)
|
| 22 |
+
return [word for word in words if word.strip()]
|
| 23 |
|
| 24 |
+
# Create interactive HTML with clickable words
|
| 25 |
def create_interactive_text(text):
|
| 26 |
if not text.strip():
|
| 27 |
+
return "<p style='color: #666; font-style: italic;'>Enter text and generate audio to see interactive text here...</p>"
|
| 28 |
|
| 29 |
+
words = split_text_into_words(text)
|
|
|
|
| 30 |
|
| 31 |
+
html = f"""
|
| 32 |
+
<div id="interactive-text-container" style="line-height: 2; font-size: 16px; padding: 20px; border: 2px solid #e1f5fe; border-radius: 12px; background: linear-gradient(135deg, #f8fdff 0%, #e3f2fd 100%); box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
|
| 33 |
+
<div style="margin-bottom: 15px; padding: 10px; background: rgba(33, 150, 243, 0.1); border-radius: 8px; text-align: center;">
|
| 34 |
+
<p style="margin: 0; font-weight: bold; color: #1976d2; font-size: 14px;">
|
| 35 |
+
π― Click anywhere in the text below to start reading from that position
|
| 36 |
+
</p>
|
| 37 |
+
</div>
|
| 38 |
+
<div id="clickable-text" style="cursor: text; user-select: text;">
|
| 39 |
+
"""
|
| 40 |
|
| 41 |
+
for i, word in enumerate(words):
|
| 42 |
+
if word.strip(): # Only make actual words clickable, not spaces
|
| 43 |
+
html += f'''<span class="word-segment" data-index="{i}"
|
| 44 |
+
style="cursor: pointer; padding: 2px 1px; border-radius: 3px; transition: all 0.2s ease; display: inline;"
|
| 45 |
+
onmouseover="this.style.backgroundColor='#bbdefb'; this.style.transform='scale(1.05)';"
|
| 46 |
+
onmouseout="this.style.backgroundColor='transparent'; this.style.transform='scale(1)';"
|
| 47 |
+
onclick="playFromWord({i})">{word}</span>'''
|
| 48 |
+
else:
|
| 49 |
+
html += word # Preserve spaces as-is
|
| 50 |
|
| 51 |
+
html += """
|
| 52 |
+
</div>
|
| 53 |
</div>
|
| 54 |
+
|
| 55 |
<script>
|
| 56 |
+
let allWords = [];
|
| 57 |
+
let currentVoice = '';
|
| 58 |
+
let currentRate = 0;
|
| 59 |
+
let currentPitch = 0;
|
| 60 |
+
let isGenerating = false;
|
| 61 |
|
| 62 |
+
function updateWordsData(words, voice, rate, pitch) {
|
| 63 |
+
allWords = words;
|
| 64 |
+
currentVoice = voice;
|
| 65 |
+
currentRate = rate;
|
| 66 |
+
currentPitch = pitch;
|
| 67 |
+
console.log('Updated words data:', words.length, 'words');
|
| 68 |
}
|
| 69 |
|
| 70 |
+
function highlightFromWord(startIndex) {
|
| 71 |
+
// Remove previous highlights
|
| 72 |
+
const segments = document.querySelectorAll('.word-segment');
|
| 73 |
+
segments.forEach(s => {
|
| 74 |
+
s.style.backgroundColor = 'transparent';
|
| 75 |
+
s.style.fontWeight = 'normal';
|
| 76 |
+
});
|
| 77 |
+
|
| 78 |
+
// Highlight from clicked word onwards
|
| 79 |
+
for (let i = startIndex; i < segments.length; i++) {
|
| 80 |
+
segments[i].style.backgroundColor = '#c8e6c9';
|
| 81 |
+
segments[i].style.fontWeight = 'bold';
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
function playFromWord(startIndex) {
|
| 86 |
+
if (allWords.length === 0) {
|
| 87 |
+
alert('Please generate the full audio first!');
|
| 88 |
return;
|
| 89 |
}
|
| 90 |
|
| 91 |
+
if (isGenerating) {
|
| 92 |
+
alert('Audio is being generated, please wait...');
|
| 93 |
+
return;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
console.log('Playing from word index:', startIndex);
|
| 97 |
+
|
| 98 |
+
// Highlight the selected portion
|
| 99 |
+
highlightFromWord(startIndex);
|
| 100 |
|
| 101 |
+
// Get text from selected word onwards
|
| 102 |
+
const textFromWord = allWords.slice(startIndex).join('');
|
| 103 |
|
| 104 |
+
console.log('Text from word:', textFromWord.substring(0, 100) + '...');
|
| 105 |
+
|
| 106 |
+
// Trigger the partial audio generation
|
| 107 |
+
isGenerating = true;
|
| 108 |
+
|
| 109 |
+
// Find the gradio interface elements and trigger the partial generation
|
| 110 |
+
const event = new CustomEvent('generatePartialAudio', {
|
| 111 |
detail: {
|
| 112 |
+
text: textFromWord,
|
| 113 |
+
voice: currentVoice,
|
| 114 |
+
rate: currentRate,
|
| 115 |
+
pitch: currentPitch,
|
| 116 |
startIndex: startIndex
|
| 117 |
}
|
| 118 |
});
|
| 119 |
+
|
| 120 |
document.dispatchEvent(event);
|
| 121 |
}
|
| 122 |
+
|
| 123 |
+
// Reset generation flag when audio is ready
|
| 124 |
+
function resetGenerationFlag() {
|
| 125 |
+
isGenerating = false;
|
| 126 |
+
}
|
| 127 |
</script>
|
| 128 |
+
"""
|
| 129 |
|
| 130 |
+
return html
|
| 131 |
|
| 132 |
# Text-to-speech function
|
| 133 |
async def text_to_speech(text, voice, rate, pitch):
|
| 134 |
if not text.strip():
|
| 135 |
+
return None, "Please enter text to convert."
|
| 136 |
if not voice:
|
| 137 |
+
return None, "Please select a voice."
|
| 138 |
|
| 139 |
+
try:
|
| 140 |
+
voice_short_name = voice.split(" - ")[0]
|
| 141 |
+
rate_str = f"{rate:+d}%"
|
| 142 |
+
pitch_str = f"{pitch:+d}Hz"
|
| 143 |
+
communicate = edge_tts.Communicate(text, voice_short_name, rate=rate_str, pitch=pitch_str)
|
| 144 |
+
|
| 145 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
|
| 146 |
+
tmp_path = tmp_file.name
|
| 147 |
+
await communicate.save(tmp_path)
|
| 148 |
+
|
| 149 |
+
return tmp_path, "Audio generated successfully!"
|
| 150 |
+
except Exception as e:
|
| 151 |
+
return None, f"Error generating audio: {str(e)}"
|
| 152 |
|
| 153 |
+
# Main TTS interface
|
| 154 |
@spaces.GPU
|
| 155 |
+
def generate_full_audio(text, voice, rate, pitch):
|
| 156 |
+
audio_path, message = asyncio.run(text_to_speech(text, voice, rate, pitch))
|
| 157 |
|
| 158 |
+
# Create interactive text
|
| 159 |
interactive_html = create_interactive_text(text)
|
| 160 |
|
| 161 |
+
# Prepare words data for JavaScript
|
| 162 |
+
words = split_text_into_words(text) if text.strip() else []
|
| 163 |
|
| 164 |
+
# Add script to update JavaScript variables
|
| 165 |
+
if words and audio_path:
|
| 166 |
+
words_json = json.dumps(words).replace('"', '\\"')
|
| 167 |
interactive_html += f"""
|
| 168 |
<script>
|
| 169 |
setTimeout(() => {{
|
| 170 |
+
updateWordsData({json.dumps(words)}, "{voice}", {rate}, {pitch});
|
| 171 |
+
resetGenerationFlag();
|
| 172 |
+
}}, 500);
|
| 173 |
</script>
|
| 174 |
"""
|
| 175 |
|
| 176 |
+
return audio_path, interactive_html, message
|
| 177 |
|
| 178 |
+
# Partial audio generation (triggered by JavaScript)
|
| 179 |
@spaces.GPU
|
| 180 |
+
def generate_partial_audio(partial_text, voice, rate, pitch):
|
| 181 |
+
if not partial_text.strip():
|
| 182 |
+
return None, "No text to convert."
|
| 183 |
|
| 184 |
+
audio_path, message = asyncio.run(text_to_speech(partial_text, voice, rate, pitch))
|
| 185 |
+
return audio_path, message
|
| 186 |
|
| 187 |
+
# Create the Gradio application
|
| 188 |
async def create_demo():
|
| 189 |
voices = await get_voices()
|
| 190 |
|
| 191 |
+
with gr.Blocks(
|
| 192 |
+
title="Interactive Text-to-Speech",
|
| 193 |
+
theme=gr.themes.Soft(),
|
| 194 |
+
css="""
|
| 195 |
+
.gradio-container {
|
| 196 |
+
max-width: 1200px !important;
|
| 197 |
+
}
|
| 198 |
+
#interactive-text-container {
|
| 199 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 200 |
+
}
|
| 201 |
+
"""
|
| 202 |
+
) as demo:
|
| 203 |
+
|
| 204 |
+
# Header
|
| 205 |
+
gr.Markdown("""
|
| 206 |
+
# π€ Interactive Text-to-Speech System
|
| 207 |
+
### Click anywhere in your text to start reading from that exact position!
|
| 208 |
+
""")
|
| 209 |
|
| 210 |
with gr.Row():
|
| 211 |
+
with gr.Column(scale=1):
|
| 212 |
+
# Input section
|
| 213 |
+
gr.Markdown("### π Input Settings")
|
| 214 |
text_input = gr.Textbox(
|
| 215 |
+
label="Enter Your Text",
|
| 216 |
+
lines=6,
|
| 217 |
+
placeholder="Type or paste your text here. After generating audio, you'll be able to click anywhere in the text to start reading from that position...",
|
| 218 |
+
value="Welcome to the interactive text-to-speech system. This amazing technology allows you to click on any word in this text and start playing the audio from that exact position. Try clicking anywhere after generating the audio!"
|
| 219 |
)
|
| 220 |
|
| 221 |
+
voice_dropdown = gr.Dropdown(
|
| 222 |
+
choices=list(voices.keys()),
|
| 223 |
+
label="Select Voice",
|
| 224 |
+
value=list(voices.keys())[0] if voices else ""
|
| 225 |
+
)
|
|
|
|
| 226 |
|
| 227 |
with gr.Row():
|
| 228 |
+
rate_slider = gr.Slider(-50, 50, 0, step=1, label="Speed (%)")
|
| 229 |
+
pitch_slider = gr.Slider(-20, 20, 0, step=1, label="Pitch (Hz)")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
|
| 231 |
generate_btn = gr.Button(
|
| 232 |
+
"π΅ Generate Interactive Audio",
|
| 233 |
+
variant="primary",
|
| 234 |
size="lg"
|
| 235 |
)
|
| 236 |
+
|
| 237 |
+
status_message = gr.Textbox(label="Status", interactive=False)
|
| 238 |
|
| 239 |
+
with gr.Column(scale=1):
|
| 240 |
+
# Output section
|
| 241 |
+
gr.Markdown("### π Audio Output")
|
| 242 |
+
full_audio = gr.Audio(label="Full Audio", type="filepath")
|
| 243 |
+
partial_audio = gr.Audio(label="Partial Audio (from clicked position)", type="filepath")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
|
| 245 |
# Interactive text display
|
| 246 |
+
gr.Markdown("### π― Interactive Text")
|
| 247 |
+
interactive_display = gr.HTML(
|
| 248 |
+
value="<p style='color: #666; font-style: italic; text-align: center; padding: 40px;'>Enter text above and click 'Generate Interactive Audio' to start!</p>"
|
| 249 |
)
|
| 250 |
|
|
|
|
|
|
|
|
|
|
| 251 |
# Hidden components for partial audio generation
|
| 252 |
+
hidden_text = gr.Textbox(visible=False)
|
| 253 |
+
hidden_voice = gr.Textbox(visible=False)
|
| 254 |
+
hidden_rate = gr.Number(visible=False)
|
| 255 |
+
hidden_pitch = gr.Number(visible=False)
|
| 256 |
|
| 257 |
# Event handlers
|
| 258 |
generate_btn.click(
|
| 259 |
+
fn=generate_full_audio,
|
| 260 |
inputs=[text_input, voice_dropdown, rate_slider, pitch_slider],
|
| 261 |
+
outputs=[full_audio, interactive_display, status_message]
|
| 262 |
)
|
| 263 |
|
| 264 |
+
# JavaScript for handling partial audio generation
|
| 265 |
demo.load(js="""
|
| 266 |
+
function setupPartialAudioGeneration() {
|
| 267 |
+
let partialAudioComponent = null;
|
| 268 |
+
let statusComponent = null;
|
| 269 |
+
|
| 270 |
+
// Find components
|
| 271 |
+
setTimeout(() => {
|
| 272 |
+
const audioElements = document.querySelectorAll('audio');
|
| 273 |
+
if (audioElements.length > 1) {
|
| 274 |
+
partialAudioComponent = audioElements[1]; // Second audio component
|
| 275 |
+
}
|
| 276 |
|
| 277 |
+
const textInputs = document.querySelectorAll('textarea');
|
| 278 |
+
statusComponent = textInputs[textInputs.length - 1]; // Status textbox
|
| 279 |
+
}, 1000);
|
| 280 |
+
|
| 281 |
+
document.addEventListener('generatePartialAudio', async function(e) {
|
| 282 |
+
const detail = e.detail;
|
| 283 |
+
console.log('Generating partial audio for:', detail.text.substring(0, 50) + '...');
|
| 284 |
|
| 285 |
+
try {
|
| 286 |
+
// Show status
|
| 287 |
+
if (statusComponent) {
|
| 288 |
+
statusComponent.value = `π΅ Generating audio from selected position...`;
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
// Make API call to generate partial audio
|
| 292 |
+
const response = await fetch('/api/predict', {
|
| 293 |
+
method: 'POST',
|
| 294 |
+
headers: {
|
| 295 |
+
'Content-Type': 'application/json',
|
| 296 |
+
},
|
| 297 |
+
body: JSON.stringify({
|
| 298 |
+
data: [detail.text, detail.voice, detail.rate, detail.pitch],
|
| 299 |
+
fn_index: 1 // Index for partial audio generation function
|
| 300 |
+
})
|
| 301 |
+
});
|
| 302 |
+
|
| 303 |
+
if (response.ok) {
|
| 304 |
+
const result = await response.json();
|
| 305 |
+
console.log('Partial audio generated successfully');
|
| 306 |
+
|
| 307 |
+
// Update status
|
| 308 |
+
if (statusComponent) {
|
| 309 |
+
statusComponent.value = `β
Audio generated from selected position!`;
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
// Trigger audio update
|
| 313 |
+
const audioUpdateEvent = new CustomEvent('updatePartialAudio', {
|
| 314 |
+
detail: { audioPath: result.data[0] }
|
| 315 |
+
});
|
| 316 |
+
document.dispatchEvent(audioUpdateEvent);
|
| 317 |
+
|
| 318 |
+
} else {
|
| 319 |
+
console.error('Failed to generate partial audio');
|
| 320 |
+
if (statusComponent) {
|
| 321 |
+
statusComponent.value = `β Failed to generate audio from selected position`;
|
| 322 |
+
}
|
| 323 |
+
}
|
| 324 |
+
} catch (error) {
|
| 325 |
+
console.error('Error:', error);
|
| 326 |
+
if (statusComponent) {
|
| 327 |
+
statusComponent.value = `β Error: ${error.message}`;
|
| 328 |
+
}
|
| 329 |
+
}
|
| 330 |
|
| 331 |
+
// Reset generation flag
|
| 332 |
+
resetGenerationFlag();
|
| 333 |
});
|
| 334 |
}
|
| 335 |
|
| 336 |
+
setupPartialAudioGeneration();
|
| 337 |
""")
|
| 338 |
+
|
| 339 |
+
# Add a second function for partial audio generation
|
| 340 |
+
def handle_partial_generation(text, voice, rate, pitch):
|
| 341 |
+
return generate_partial_audio(text, voice, rate, pitch)
|
| 342 |
+
|
| 343 |
+
# Create a hidden interface for partial audio generation
|
| 344 |
+
partial_interface = gr.Interface(
|
| 345 |
+
fn=handle_partial_generation,
|
| 346 |
+
inputs=[
|
| 347 |
+
gr.Textbox(visible=False),
|
| 348 |
+
gr.Textbox(visible=False),
|
| 349 |
+
gr.Number(visible=False),
|
| 350 |
+
gr.Number(visible=False)
|
| 351 |
+
],
|
| 352 |
+
outputs=[
|
| 353 |
+
gr.Audio(visible=False),
|
| 354 |
+
gr.Textbox(visible=False)
|
| 355 |
+
],
|
| 356 |
+
live=False,
|
| 357 |
+
allow_flagging=False
|
| 358 |
+
)
|
| 359 |
|
| 360 |
return demo
|
| 361 |
|
| 362 |
# Run the application
|
| 363 |
if __name__ == "__main__":
|
| 364 |
demo = asyncio.run(create_demo())
|
| 365 |
+
demo.launch(share=True)
|