aghilTQ commited on
Commit
044ac72
Β·
verified Β·
1 Parent(s): 56220ec

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +261 -0
  2. requirements.txt +2 -0
app.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import spaces
2
+ import gradio as gr
3
+ import edge_tts
4
+ 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 sentences for interactive clicking
15
+ def split_text_into_segments(text):
16
+ # Split by sentences, keeping punctuation
17
+ segments = re.split(r'([.!?]+)', text)
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
+ return [seg for seg in result if seg.strip()]
35
+
36
+ # Create interactive HTML with clickable text segments
37
+ def create_interactive_text(text):
38
+ if not text.strip():
39
+ return ""
40
+
41
+ segments = split_text_into_segments(text)
42
+ html_parts = []
43
+
44
+ html_parts.append("""
45
+ <div id="interactive-text" style="line-height: 1.8; font-size: 16px; padding: 20px; border: 1px solid #ddd; border-radius: 8px; background: #f9f9f9;">
46
+ <p style="margin-bottom: 15px; font-weight: bold; color: #555;">Click on any part of the text to start playback from that position:</p>
47
+ """)
48
+
49
+ for i, segment in enumerate(segments):
50
+ html_parts.append(
51
+ f'<span class="text-segment" data-index="{i}" style="cursor: pointer; padding: 2px 4px; margin: 1px; border-radius: 3px; transition: background-color 0.2s;" '
52
+ f'onmouseover="this.style.backgroundColor=\'#e3f2fd\'" '
53
+ f'onmouseout="this.style.backgroundColor=\'transparent\'" '
54
+ f'onclick="playFromSegment({i})">{segment} </span>'
55
+ )
56
+
57
+ html_parts.append("""
58
+ </div>
59
+ <script>
60
+ let currentSegments = [];
61
+ let currentSettings = {};
62
+
63
+ function updateSegments(segments, voice, rate, pitch) {
64
+ currentSegments = segments;
65
+ currentSettings = {voice: voice, rate: rate, pitch: pitch};
66
+ }
67
+
68
+ async function playFromSegment(startIndex) {
69
+ if (!currentSegments || currentSegments.length === 0) {
70
+ alert('Please generate the full audio first to enable interactive playback.');
71
+ return;
72
+ }
73
+
74
+ // Highlight clicked segment
75
+ const segments = document.querySelectorAll('.text-segment');
76
+ segments.forEach(s => s.style.backgroundColor = 'transparent');
77
+ segments[startIndex].style.backgroundColor = '#bbdefb';
78
+
79
+ // Get text from selected segment onwards
80
+ const textFromSegment = currentSegments.slice(startIndex).join(' ');
81
+
82
+ // Trigger partial audio generation
83
+ const event = new CustomEvent('playFromSegment', {
84
+ detail: {
85
+ text: textFromSegment,
86
+ voice: currentSettings.voice,
87
+ rate: currentSettings.rate,
88
+ pitch: currentSettings.pitch,
89
+ startIndex: startIndex
90
+ }
91
+ });
92
+ document.dispatchEvent(event);
93
+ }
94
+ </script>
95
+ """)
96
+
97
+ return ''.join(html_parts)
98
+
99
+ # Text-to-speech function
100
+ async def text_to_speech(text, voice, rate, pitch):
101
+ if not text.strip():
102
+ return None, gr.Warning("Please enter text to convert.")
103
+ if not voice:
104
+ return None, gr.Warning("Please select a voice.")
105
+
106
+ voice_short_name = voice.split(" - ")[0]
107
+ rate_str = f"{rate:+d}%"
108
+ pitch_str = f"{pitch:+d}Hz"
109
+ communicate = edge_tts.Communicate(text, voice_short_name, rate=rate_str, pitch=pitch_str)
110
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
111
+ tmp_path = tmp_file.name
112
+ await communicate.save(tmp_path)
113
+ return tmp_path, None
114
+
115
+ # Gradio interface function for full text
116
+ @spaces.GPU
117
+ def tts_interface(text, voice, rate, pitch):
118
+ audio, warning = asyncio.run(text_to_speech(text, voice, rate, pitch))
119
+
120
+ # Create interactive text display
121
+ interactive_html = create_interactive_text(text)
122
+
123
+ # Prepare segments for JavaScript
124
+ segments = split_text_into_segments(text) if text.strip() else []
125
+
126
+ # Add JavaScript to update segments
127
+ if segments:
128
+ interactive_html += f"""
129
+ <script>
130
+ setTimeout(() => {{
131
+ updateSegments({segments}, '{voice}', {rate}, {pitch});
132
+ }}, 100);
133
+ </script>
134
+ """
135
+
136
+ return audio, interactive_html, warning
137
+
138
+ # Gradio interface function for partial text (from clicked segment)
139
+ @spaces.GPU
140
+ def tts_partial_interface(text, voice, rate, pitch):
141
+ if not text or not voice:
142
+ return None, gr.Warning("Missing text or voice selection.")
143
+
144
+ audio, warning = asyncio.run(text_to_speech(text, voice, rate, pitch))
145
+ return audio, warning
146
+
147
+ # Create Gradio application
148
+ async def create_demo():
149
+ voices = await get_voices()
150
+
151
+ description = """
152
+ Experience the power of Voicecloning.be for text-to-speech conversion with interactive text playback.
153
+
154
+ **How to use:**
155
+ 1. Enter your text and select voice settings
156
+ 2. Click "Generate Full Audio" to create the complete audio
157
+ 3. Click on any part of the displayed text to start playback from that position
158
+ """
159
+
160
+ with gr.Blocks(title="Interactive Text-to-Speech", theme=gr.themes.Soft()) as demo:
161
+ gr.Markdown("# 🎀 Interactive Text-to-Speech")
162
+ gr.Markdown(description)
163
+
164
+ with gr.Row():
165
+ with gr.Column(scale=2):
166
+ text_input = gr.Textbox(
167
+ label="Input Text",
168
+ lines=8,
169
+ placeholder="Enter the text you want to convert to speech..."
170
+ )
171
+
172
+ with gr.Row():
173
+ voice_dropdown = gr.Dropdown(
174
+ choices=[""] + list(voices.keys()),
175
+ label="Select Voice",
176
+ value=""
177
+ )
178
+
179
+ with gr.Row():
180
+ rate_slider = gr.Slider(
181
+ minimum=-50,
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 Full Audio",
197
+ variant="primary",
198
+ size="lg"
199
+ )
200
+
201
+ with gr.Column(scale=2):
202
+ full_audio_output = gr.Audio(
203
+ label="πŸ”Š Full Audio",
204
+ type="filepath"
205
+ )
206
+ partial_audio_output = gr.Audio(
207
+ label="🎯 Partial Audio (from clicked position)",
208
+ type="filepath"
209
+ )
210
+
211
+ # Interactive text display
212
+ interactive_text_display = gr.HTML(
213
+ label="πŸ“ Interactive Text (Click to play from position)",
214
+ value="<p style='color: #666; font-style: italic;'>Generate audio first to see interactive text here...</p>"
215
+ )
216
+
217
+ # Warning display
218
+ warning_display = gr.Markdown(visible=False)
219
+
220
+ # Hidden components for partial audio generation
221
+ partial_text = gr.Textbox(visible=False)
222
+ partial_voice = gr.Textbox(visible=False)
223
+ partial_rate = gr.Number(visible=False)
224
+ partial_pitch = gr.Number(visible=False)
225
+
226
+ # Event handlers
227
+ generate_btn.click(
228
+ fn=tts_interface,
229
+ inputs=[text_input, voice_dropdown, rate_slider, pitch_slider],
230
+ outputs=[full_audio_output, interactive_text_display, warning_display]
231
+ )
232
+
233
+ # JavaScript to handle segment clicks
234
+ demo.load(js="""
235
+ function setupInteractiveText() {
236
+ document.addEventListener('playFromSegment', async function(e) {
237
+ const detail = e.detail;
238
+
239
+ // Update hidden inputs
240
+ const partialTextInput = document.querySelector('textarea[data-testid="textbox"]').parentElement.parentElement.parentElement.querySelector('textarea');
241
+ const partialVoiceInput = document.querySelector('input[type="text"]');
242
+
243
+ // Trigger partial audio generation through Gradio
244
+ // This is a simplified approach - in a full implementation, you'd need to properly integrate with Gradio's state management system
245
+ console.log('Playing from segment:', detail.startIndex);
246
+ console.log('Text:', detail.text);
247
+
248
+ // For now, show an alert with the functionality
249
+ alert(`Playing from segment ${detail.startIndex + 1}:\\n\\n"${detail.text.substring(0, 100)}${detail.text.length > 100 ? '...' : ''}"`);
250
+ });
251
+ }
252
+
253
+ setTimeout(setupInteractiveText, 1000);
254
+ """)
255
+
256
+ return demo
257
+
258
+ # Run the application
259
+ if __name__ == "__main__":
260
+ demo = asyncio.run(create_demo())
261
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ edge_tts==6.1.12
2
+ gradio==4.36.1