aghilTQ commited on
Commit
e9abd23
Β·
verified Β·
1 Parent(s): e38225d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +157 -229
app.py CHANGED
@@ -5,7 +5,6 @@ import asyncio
5
  import tempfile
6
  import os
7
  import re
8
- import json
9
 
10
  # Get all available voices
11
  async def get_voices():
@@ -16,10 +15,8 @@ async def get_voices():
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):
@@ -29,100 +26,93 @@ def create_interactive_text(text):
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
  """
@@ -137,7 +127,7 @@ async def text_to_speech(text, voice, rate, pitch):
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)
@@ -146,220 +136,158 @@ async def text_to_speech(text, voice, rate, pitch):
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)
 
5
  import tempfile
6
  import os
7
  import re
 
8
 
9
  # Get all available voices
10
  async def get_voices():
 
15
  def split_text_into_words(text):
16
  if not text.strip():
17
  return []
18
+ words = re.findall(r'\S+', text)
19
+ return words
 
 
20
 
21
  # Create interactive HTML with clickable words
22
  def create_interactive_text(text):
 
26
  words = split_text_into_words(text)
27
 
28
  html = f"""
29
+ <div id="interactive-text-container" style="line-height: 1.8; font-size: 16px; padding: 20px; border: 2px solid #2196f3; border-radius: 12px; background: linear-gradient(135deg, #f8fdff 0%, #e3f2fd 100%); margin: 10px 0;">
30
  <div style="margin-bottom: 15px; padding: 10px; background: rgba(33, 150, 243, 0.1); border-radius: 8px; text-align: center;">
31
  <p style="margin: 0; font-weight: bold; color: #1976d2; font-size: 14px;">
32
+ 🎯 Click on any word below to start reading from that position
33
  </p>
34
  </div>
35
+ <div id="clickable-text" style="line-height: 1.6;">
36
  """
37
 
38
+ # Reconstruct text with clickable words
39
+ text_with_indices = ""
40
+ word_index = 0
41
+ current_pos = 0
42
+
43
+ for match in re.finditer(r'(\S+)(\s*)', text):
44
+ word = match.group(1)
45
+ space = match.group(2)
46
+
47
+ text_with_indices += f'''<span class="clickable-word"
48
+ data-index="{word_index}"
49
+ data-position="{current_pos}"
50
+ style="cursor: pointer; padding: 1px 2px; border-radius: 3px; transition: all 0.2s; display: inline-block; margin: 1px;"
51
+ onmouseover="this.style.backgroundColor='#bbdefb'; this.style.transform='scale(1.05)';"
52
+ onmouseout="this.style.backgroundColor='transparent'; this.style.transform='scale(1)';"
53
+ onclick="handleWordClick({current_pos}, {word_index})">{word}</span>{space}'''
54
+
55
+ word_index += 1
56
+ current_pos = match.end()
57
 
58
+ html += text_with_indices
59
  html += """
60
  </div>
61
  </div>
62
 
63
  <script>
64
+ const originalText = `""" + text.replace('`', '\\`').replace('\\', '\\\\') + """`;
 
 
 
 
65
 
66
+ function handleWordClick(position, wordIndex) {
67
+ console.log('Word clicked at position:', position, 'word index:', wordIndex);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
+ // Get text from clicked position
70
+ const textFromPosition = originalText.substring(position);
71
+ console.log('Text from position:', textFromPosition.substring(0, 50) + '...');
72
 
73
+ // Visual feedback - highlight from clicked word onwards
74
+ const allWords = document.querySelectorAll('.clickable-word');
75
 
76
+ // Reset all highlights
77
+ allWords.forEach((word, index) => {
78
+ if (index >= wordIndex) {
79
+ word.style.backgroundColor = '#c8e6c9';
80
+ word.style.fontWeight = 'bold';
81
+ } else {
82
+ word.style.backgroundColor = 'transparent';
83
+ word.style.fontWeight = 'normal';
84
+ }
85
+ });
86
 
87
+ // Trigger Gradio event by updating the textbox value
88
+ const event = new CustomEvent('wordClicked', {
89
  detail: {
90
+ text: textFromPosition,
91
+ position: position,
92
+ wordIndex: wordIndex
 
 
93
  }
94
  });
95
 
96
+ // Store in window for access by Gradio
97
+ window.clickedText = textFromPosition;
98
+ window.clickedPosition = position;
99
+
100
+ // Dispatch custom event
101
  document.dispatchEvent(event);
102
+
103
+ // Also try to find and update hidden textbox directly
104
+ setTimeout(() => {
105
+ const hiddenTextarea = document.querySelector('textarea[data-testid="textbox"]:not([style*="display: block"])');
106
+ if (hiddenTextarea) {
107
+ hiddenTextarea.value = textFromPosition;
108
+ hiddenTextarea.dispatchEvent(new Event('input', { bubbles: true }));
109
+ }
110
+ }, 100);
111
  }
112
 
113
+ // Function to get clicked text (called by Gradio)
114
+ function getClickedText() {
115
+ return window.clickedText || '';
116
  }
117
  </script>
118
  """
 
127
  return None, "Please select a voice."
128
 
129
  try:
130
+ voice_short_name = voice.split(" - ")[0] if " - " in voice else voice
131
  rate_str = f"{rate:+d}%"
132
  pitch_str = f"{pitch:+d}Hz"
133
  communicate = edge_tts.Communicate(text, voice_short_name, rate=rate_str, pitch=pitch_str)
 
136
  tmp_path = tmp_file.name
137
  await communicate.save(tmp_path)
138
 
139
+ return tmp_path, "βœ… Audio generated! Click any word in the text below to start from that position."
140
  except Exception as e:
141
+ return None, f"❌ Error: {str(e)}"
142
 
143
+ # Generate full audio
144
  @spaces.GPU
145
  def generate_full_audio(text, voice, rate, pitch):
146
+ if not text.strip():
147
+ return None, "", "Please enter some text first."
148
+ if not voice:
149
+ return None, "", "Please select a voice."
150
 
151
+ audio_path, message = asyncio.run(text_to_speech(text, voice, rate, pitch))
152
  interactive_html = create_interactive_text(text)
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  return audio_path, interactive_html, message
155
 
156
+ # Generate partial audio - this will be called when text is clicked
157
+ @spaces.GPU
158
+ def generate_partial_audio(clicked_text, voice, rate, pitch):
159
+ if not clicked_text or not clicked_text.strip():
160
+ return None, "No text was clicked."
161
+
162
+ print(f"Generating partial audio for: {clicked_text[:50]}...") # Debug
163
+
164
+ audio_path, message = asyncio.run(text_to_speech(clicked_text, voice, rate, pitch))
165
 
166
+ if audio_path:
167
+ return audio_path, f"🎡 Playing from clicked position: '{clicked_text[:30]}...'"
168
+ else:
169
+ return None, f"❌ Failed to generate audio: {message}"
170
+
171
+ # Function to handle click events (bridge between JS and Python)
172
+ def handle_text_click(full_text, voice, rate, pitch):
173
+ # This function will be called by JavaScript
174
+ # JavaScript will pass the clicked text through a hidden component
175
+ return "Click detected - processing..."
176
 
177
  # Create the Gradio application
178
  async def create_demo():
179
  voices = await get_voices()
180
+ voice_choices = list(voices.keys())
181
 
182
  with gr.Blocks(
183
+ title="Interactive Text-to-Speech",
184
+ theme=gr.themes.Soft()
 
 
 
 
 
 
 
 
185
  ) as demo:
186
 
 
187
  gr.Markdown("""
188
+ # 🎀 Interactive Text-to-Speech
189
+ ## Click anywhere in your text to start reading from that position!
190
  """)
191
 
192
  with gr.Row():
193
  with gr.Column(scale=1):
194
+ gr.Markdown("### πŸ“ Input")
 
195
  text_input = gr.Textbox(
196
+ label="Your Text",
197
  lines=6,
198
+ value="Hello and welcome to the interactive text-to-speech system. This incredible technology allows you to click on any word in this sentence and the system will start reading from exactly that position. Try it out by first generating the audio, then clicking anywhere in this text to hear it read from that point!"
 
199
  )
200
 
201
  voice_dropdown = gr.Dropdown(
202
+ choices=voice_choices,
203
+ label="Voice",
204
+ value=voice_choices[0] if voice_choices else None
205
  )
206
 
207
  with gr.Row():
208
+ rate_slider = gr.Slider(-50, 50, 0, step=5, label="Speed (%)")
209
+ pitch_slider = gr.Slider(-20, 20, 0, step=2, label="Pitch (Hz)")
210
 
211
+ generate_btn = gr.Button("🎡 Generate Interactive Audio", variant="primary", size="lg")
 
 
 
 
212
 
 
 
213
  with gr.Column(scale=1):
 
214
  gr.Markdown("### πŸ”Š Audio Output")
215
+ full_audio = gr.Audio(label="Full Audio")
216
+ partial_audio = gr.Audio(label="Audio from Clicked Position")
217
+ status_box = gr.Textbox(label="Status", interactive=False, lines=2)
218
 
219
+ gr.Markdown("### 🎯 Interactive Text (Click any word)")
 
220
  interactive_display = gr.HTML(
221
+ value="<div style='text-align: center; padding: 40px; color: #666;'>πŸ“ Generate audio first to see clickable text</div>"
222
  )
223
 
224
+ # Hidden textbox to capture clicked text
225
+ clicked_text_box = gr.Textbox(visible=False, interactive=True)
226
+
227
+ # Create a polling mechanism to check for clicks
228
+ def check_for_clicks(current_clicked_text, voice, rate, pitch):
229
+ if current_clicked_text and current_clicked_text.strip():
230
+ # Generate audio for the clicked text
231
+ return generate_partial_audio(current_clicked_text, voice, rate, pitch)
232
+ return None, "Waiting for text click..."
233
 
234
  # Event handlers
235
  generate_btn.click(
236
  fn=generate_full_audio,
237
  inputs=[text_input, voice_dropdown, rate_slider, pitch_slider],
238
+ outputs=[full_audio, interactive_display, status_box]
239
  )
240
 
241
+ # Monitor the hidden textbox for changes (when JavaScript updates it)
242
+ clicked_text_box.change(
243
+ fn=check_for_clicks,
244
+ inputs=[clicked_text_box, voice_dropdown, rate_slider, pitch_slider],
245
+ outputs=[partial_audio, status_box]
246
+ )
247
+
248
+ # Add JavaScript to bridge the gap
249
  demo.load(js="""
250
+ function setupClickHandler() {
251
+ console.log('Setting up click handler...');
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
+ // Function to update the hidden textbox when text is clicked
254
+ document.addEventListener('wordClicked', function(e) {
255
+ console.log('Word clicked event received:', e.detail);
256
 
257
+ // Find the hidden textbox and update its value
258
+ setTimeout(() => {
259
+ const textareas = document.querySelectorAll('textarea');
260
+ const hiddenTextarea = Array.from(textareas).find(ta =>
261
+ ta.style.display === 'none' ||
262
+ ta.parentElement.style.display === 'none' ||
263
+ ta.closest('[style*="display: none"]')
264
+ );
 
 
 
 
 
 
 
 
 
265
 
266
+ if (hiddenTextarea) {
267
+ console.log('Found hidden textarea, updating with:', e.detail.text.substring(0, 50));
268
+ hiddenTextarea.value = e.detail.text;
 
 
 
 
 
269
 
270
+ // Trigger the change event
271
+ const event = new Event('input', { bubbles: true });
272
+ hiddenTextarea.dispatchEvent(event);
 
 
273
 
274
+ // Also try blur event
275
+ setTimeout(() => {
276
+ hiddenTextarea.dispatchEvent(new Event('blur', { bubbles: true }));
277
+ }, 100);
278
  } else {
279
+ console.log('Hidden textarea not found');
 
 
 
280
  }
281
+ }, 200);
 
 
 
 
 
 
 
 
282
  });
283
  }
284
 
285
+ setupClickHandler();
286
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
  return demo
289
 
290
  # Run the application
291
  if __name__ == "__main__":
292
  demo = asyncio.run(create_demo())
293
+ demo.launch(share=True, debug=True)