deva-tts 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
deva_tts/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ from .engine import start_engine,stop_engine,feed_chunk,set_voice
2
+
3
+ __all__ = [
4
+ "start_engine",
5
+ "stop_engine",
6
+ "feed_chunk",
7
+ "set_voice"
8
+ ]
deva_tts/engine.py ADDED
@@ -0,0 +1,155 @@
1
+ import asyncio
2
+ import re
3
+ import edge_tts
4
+ import pyaudio
5
+ import miniaudio
6
+ from .utils import clean_text
7
+ import aiohttp
8
+
9
+ _background_tasks = []
10
+
11
+
12
+ _VOICE = "en-US-EmmaMultilingualNeural"
13
+ OUTPUT_SAMPLE_RATE = 24000
14
+ CHANNELS = 1
15
+
16
+ # Initialize PyAudio
17
+ p = pyaudio.PyAudio()
18
+ audio_stream = p.open(
19
+ format=pyaudio.paInt16,
20
+ channels=CHANNELS,
21
+ rate=OUTPUT_SAMPLE_RATE,
22
+ output=True
23
+ )
24
+
25
+ _chunk_queue = asyncio.Queue()
26
+ _sentence_queue = asyncio.Queue()
27
+ _pcm_queue = asyncio.Queue()
28
+
29
+ def set_voice(voice : str = "en-US-EmmaMultilingualNeural"):
30
+ """allows users to change the TTS voice model"""
31
+ global _VOICE
32
+ try :
33
+ test = edge_tts.Communicate("hello", voice)
34
+ _VOICE = voice
35
+ except ValueError as e:
36
+ print(f"[INVALID VOICE NAME] : {voice}")
37
+ print("falling back to default voice")
38
+
39
+ async def feed_chunk(chunk):
40
+ if chunk != None or chunk != "":
41
+ await _chunk_queue.put(chunk)
42
+
43
+ async def text_parser():
44
+ sentence_end = re.compile(r'[^.!?]+[.!?](?=\s|$)')
45
+ buffer = ""
46
+ buffer_list = []
47
+ target = 1
48
+
49
+ while True:
50
+ chunk = await _chunk_queue.get()
51
+ if not chunk:
52
+ _chunk_queue.task_done()
53
+ await _sentence_queue.put(" ".join(buffer_list).strip())
54
+ break
55
+ buffer += chunk
56
+
57
+ while True:
58
+ match = sentence_end.match(buffer)
59
+ if not match:
60
+ break
61
+ sentence = match.group().strip()
62
+ sentence = clean_text(sentence)
63
+ buffer = buffer[match.end():].strip()
64
+
65
+ if sentence:
66
+ buffer_list.append(sentence)
67
+ if len(buffer_list) == target or target == 0:
68
+ print(f"[parser] sentence found: {sentence}")
69
+ await _sentence_queue.put(" ".join(buffer_list[:target]).strip())
70
+ buffer_list = buffer_list[target:]
71
+ if target < 5:
72
+ target += 1
73
+ await _sentence_queue.put(None)
74
+
75
+ async def tts_worker():
76
+
77
+ while True:
78
+ sentence = await _sentence_queue.get()
79
+ if sentence == None:
80
+ await _pcm_queue.put(None)
81
+ _sentence_queue.task_done()
82
+ break
83
+ print(f"[TTS] generating audio for: {sentence}")
84
+ communicate = edge_tts.Communicate(sentence, _VOICE)
85
+ mp3_buffer = b""
86
+ try :
87
+ async for chunk in communicate.stream():
88
+ if chunk["type"] == "audio":
89
+ mp3_buffer += chunk["data"]
90
+ if mp3_buffer:
91
+ decoded = miniaudio.decode(
92
+ mp3_buffer,
93
+ output_format=miniaudio.SampleFormat.SIGNED16,
94
+ nchannels=CHANNELS,
95
+ sample_rate=OUTPUT_SAMPLE_RATE
96
+ )
97
+ pcm_bytes = bytes(decoded.samples)
98
+ await _pcm_queue.put(pcm_bytes)
99
+
100
+ except aiohttp.client_exceptions.WSServerHandshakeError as e:
101
+ print(f"{e.status} : ")
102
+ if e.status == 403:
103
+ print("="*60 + "\n")
104
+ print(f"status : {e.status}")
105
+ print("="*60)
106
+ print("[DEVA_TTS_ERROR] CONNECTION BLOCKED")
107
+ print("="*60)
108
+ print("To fix this instantly, run this command in your terminal:")
109
+ print("\n python -m pip install --upgrade edge-tts\n")
110
+ print("="*60 + "\n")
111
+ else:
112
+ print("\n[DEVA_TTS] network error")
113
+ _sentence_queue.task_done()
114
+ continue
115
+ except asyncio.CancelledError:
116
+ print("\n[TTS] Download violently interrupted. Shutting down worker.")
117
+ break
118
+ _sentence_queue.task_done()
119
+ await _pcm_queue.put(None)
120
+
121
+ async def audio_playback():
122
+
123
+ loop = asyncio.get_running_loop()
124
+ chunk_size = 8192
125
+ try:
126
+ while True:
127
+ pcm_data = await _pcm_queue.get()
128
+ if not pcm_data:
129
+ _pcm_queue.task_done()
130
+ break
131
+
132
+ print(f"[Player] Playing back sentence audio segment ({len(pcm_data)} bytes)...")
133
+ for i in range(0,len(pcm_data),chunk_size):
134
+ sliced_data = pcm_data[i: i+chunk_size]
135
+ await loop.run_in_executor(None,audio_stream.write,sliced_data)
136
+ _pcm_queue.task_done()
137
+ except asyncio.CancelledError as e:
138
+ print("stoping the process")
139
+ finally:
140
+ print("\n[System] Shutting down audio streams...")
141
+ audio_stream.stop_stream()
142
+ audio_stream.close()
143
+ p.terminate()
144
+ print("[System] Done.")
145
+
146
+
147
+ async def start_engine():
148
+ print("the engine is starting....")
149
+ _background_tasks.append(asyncio.create_task(text_parser()))
150
+ _background_tasks.append(asyncio.create_task(tts_worker()))
151
+ _background_tasks.append(asyncio.create_task(audio_playback()))
152
+
153
+ async def stop_engine():
154
+ await _chunk_queue.put(None)
155
+ await asyncio.gather(*_background_tasks)
deva_tts/utils.py ADDED
@@ -0,0 +1,10 @@
1
+ import re
2
+ import emoji
3
+
4
+
5
+ def clean_text(text : str) -> str:
6
+
7
+ text = re.sub(r'[`*#~|]','',text)
8
+ text = emoji.replace_emoji(text,'')
9
+ text = re.sub(r'https?://\S+|www\.\S+','',text)
10
+ return text
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: deva_tts
3
+ Version: 0.1.0
4
+ Summary: A real-time, streaming text-to-speech engine.
5
+ Requires-Dist: edge-tts
6
+ Requires-Dist: pyaudio
7
+ Requires-Dist: miniaudio
8
+ Requires-Dist: emoji
9
+ Dynamic: requires-dist
10
+ Dynamic: summary
@@ -0,0 +1,7 @@
1
+ deva_tts/__init__.py,sha256=6L9qJJvIJYaRupkD7QCKnGm74c9PHfl8JmyCRUQmsFU,160
2
+ deva_tts/engine.py,sha256=8FnSUI9i4y4fsCFeO3mhRUmWK4kLn_e2MI8xmA8f3j0,5223
3
+ deva_tts/utils.py,sha256=rkhUIYd25NAP33hXXYFL9kNHTUfzWDm4TUeCtLDOwmg,219
4
+ deva_tts-0.1.0.dist-info/METADATA,sha256=SaBI7i6r9wjaOno_NDhlTXrSWEZWB7OWPg_yGH4traI,250
5
+ deva_tts-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
6
+ deva_tts-0.1.0.dist-info/top_level.txt,sha256=Lq8h-sBoyHMN0UhjFspiP7T_p5FjmOv0LaVC89RC-ZU,9
7
+ deva_tts-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ deva_tts