aghilTQ commited on
Commit
65faab7
·
verified ·
1 Parent(s): 16a8f49

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +152 -45
app.py CHANGED
@@ -1,52 +1,159 @@
 
 
1
  import asyncio
2
- from pyglet import app, clock, textinput
3
- from microsoft_edge_tts import EdgeTTS
4
-
5
- async def generate_speech(text):
6
- tts = EdgeTTS()
7
- speech = await tts.speak_async(text)
8
- return speech
9
-
10
- def handle_click(event):
11
- global current_text, current_position
12
- if event.button == 1: # Left mouse button
13
- current_position = event.x, event.y
14
- print(f"Clicked at {current_position}")
15
-
16
- async def update_text():
17
- global current_text, current_position
18
- while True:
19
- if current_text != original_text:
20
- await generate_speech(current_text)
21
- current_text = original_text
22
- await asyncio.sleep(0.5)
23
-
24
- async def main():
25
- global current_text, original_text
26
- window = app.Window(width=800, height=600)
27
-
28
- label = textinput.Label(
29
- text="Click anywhere to start",
30
- multiline=True,
31
- width=700,
32
- height=500,
33
- x=50, y=50
34
- )
35
- window.add_handlers(label)
36
- label.on_mouse_press = handle_click
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
- input_handler = textinput.TextInput()
39
- window.add_handlers(input_handler)
40
- input_handler.on_text_entry = lambda text: label.text = text
 
 
 
 
 
 
 
 
 
 
41
 
42
- current_text = ""
43
- original_text = ""
44
- current_position = None
 
 
45
 
46
- clock.schedule_interval(update_text, 0.5)
 
 
 
47
 
48
- async with window:
49
- await app.run()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  if __name__ == "__main__":
52
- asyncio.run(main())
 
1
+ import gradio as gr
2
+ import edge_tts
3
  import asyncio
4
+ from typing import Optional
5
+ import os
6
+
7
+ # Global variables to manage playback
8
+ current_playback_task = None
9
+ current_position = 0
10
+ stop_playback_flag = False
11
+
12
+ async def text_to_speech(text: str, play_from: int = 0, voice: str = "en-US-GuyNeural") -> tuple[str, str]:
13
+ global current_playback_task, current_position, stop_playback_flag
14
+
15
+ # Stop any ongoing playback
16
+ stop_playback()
17
+
18
+ # Get only the text from the specified position
19
+ text_to_read = text[play_from:]
20
+ current_position = play_from
21
+
22
+ # Create a new playback task
23
+ current_playback_task = asyncio.create_task(play_audio(text_to_read, voice))
24
+
25
+ return text, f"Playing from position {play_from}..."
26
+
27
+ async def play_audio(text: str, voice: str):
28
+ try:
29
+ communicate = edge_tts.Communicate(text, voice)
30
+ async for chunk in communicate.stream():
31
+ if stop_playback_flag:
32
+ break
33
+ except Exception as e:
34
+ print(f"Playback error: {e}")
35
+
36
+ def stop_playback():
37
+ global current_playback_task, stop_playback_flag
38
+ if current_playback_task:
39
+ stop_playback_flag = True
40
+ current_playback_task.cancel()
41
+ stop_playback_flag = False
42
+
43
+ def handle_word_click(word_index: int, text: str, voice: str) -> tuple[str, str]:
44
+ global current_position
45
+
46
+ # Convert word index to character position
47
+ words = text.split()
48
+ if word_index < len(words):
49
+ # Find the character position of the clicked word
50
+ char_pos = 0
51
+ for i, word in enumerate(words):
52
+ if i == word_index:
53
+ break
54
+ char_pos += len(word) + 1 # +1 for the space
55
+
56
+ # Start playback from this position
57
+ return asyncio.run(text_to_speech(text, char_pos, voice))
58
+ return text, "Invalid word selection"
59
+
60
+ async def get_voices():
61
+ voices = await edge_tts.list_voices()
62
+ return [v["ShortName"] for v in voices if v["Locale"].startswith("en-")]
63
 
64
+ # JavaScript for interactive text
65
+ js = """
66
+ function splitText(text) {
67
+ if (!text) return '';
68
+ const words = text.split(' ');
69
+ return words.map((word, index) => {
70
+ return `<span class="word" data-index="${index}"
71
+ style="cursor:pointer; margin-right:5px;"
72
+ onmouseover="this.style.backgroundColor='#f0f0f0'"
73
+ onmouseout="this.style.backgroundColor='transparent'"
74
+ onclick="handleWordClick(this)">${word}</span>`;
75
+ }).join(' ');
76
+ }
77
 
78
+ function handleWordClick(element) {
79
+ const index = element.getAttribute('data-index');
80
+ document.getElementById('word-index').value = index;
81
+ document.getElementById('word-index').dispatchEvent(new Event('input'));
82
+ }
83
 
84
+ function updateTextDisplay(text) {
85
+ const container = document.getElementById('text-display');
86
+ container.innerHTML = splitText(text);
87
+ }
88
 
89
+ document.addEventListener('DOMContentLoaded', function() {
90
+ // Update display when text changes
91
+ const textInput = document.querySelector('#text-input textarea');
92
+ if (textInput) {
93
+ textInput.addEventListener('input', function() {
94
+ updateTextDisplay(this.value);
95
+ });
96
+ }
97
+ });
98
+ """
99
+
100
+ css = """
101
+ .word:hover { background-color: #f0f0f0; }
102
+ #text-display {
103
+ padding: 10px;
104
+ border: 1px solid #ccc;
105
+ border-radius: 5px;
106
+ min-height: 100px;
107
+ white-space: pre-wrap;
108
+ }
109
+ """
110
+
111
+ with gr.Blocks(js=js, css=css) as demo:
112
+ gr.Markdown("# Interactive TTS Service (edge-tts)")
113
+ gr.Markdown("Enter text below and click 'Speak' to hear it. Click on any word to start playback from that point.")
114
+
115
+ with gr.Row():
116
+ text_input = gr.Textbox(label="Input Text", lines=5, elem_id="text-input")
117
+ word_index = gr.Number(visible=False, elem_id="word-index")
118
+
119
+ with gr.Row():
120
+ text_display = gr.HTML(elem_id="text-display")
121
+
122
+ with gr.Row():
123
+ voice_dropdown = gr.Dropdown(label="Voice", choices=asyncio.run(get_voices()), value="en-US-GuyNeural")
124
+ rate_slider = gr.Slider(50, 200, value=100, label="Speed (%)")
125
+
126
+ with gr.Row():
127
+ speak_btn = gr.Button("Speak", variant="primary")
128
+ stop_btn = gr.Button("Stop")
129
+
130
+ status = gr.Textbox(label="Status", interactive=False)
131
+
132
+ # Event handlers - updated syntax for Gradio 5.32.0
133
+ text_input.change(
134
+ lambda x: x,
135
+ inputs=text_input,
136
+ outputs=text_display,
137
+ js="(x) => { updateTextDisplay(x); return x; }"
138
+ )
139
+
140
+ word_index.input(
141
+ handle_word_click,
142
+ inputs=[word_index, text_input, voice_dropdown],
143
+ outputs=[text_input, status]
144
+ )
145
+
146
+ speak_btn.click(
147
+ lambda text, voice: asyncio.run(text_to_speech(text, 0, voice)),
148
+ inputs=[text_input, voice_dropdown],
149
+ outputs=[text_input, status]
150
+ )
151
+
152
+ stop_btn.click(
153
+ stop_playback,
154
+ outputs=status,
155
+ js="() => { return 'Playback stopped'; }"
156
+ )
157
 
158
  if __name__ == "__main__":
159
+ demo.launch()