webscout 8.3.5__py3-none-any.whl → 8.3.7__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.

Potentially problematic release.


This version of webscout might be problematic. Click here for more details.

Files changed (159) hide show
  1. webscout/AIutel.py +2 -0
  2. webscout/Bard.py +12 -6
  3. webscout/DWEBS.py +66 -57
  4. webscout/Provider/{UNFINISHED → AISEARCH}/PERPLEXED_search.py +34 -74
  5. webscout/Provider/AISEARCH/__init__.py +18 -11
  6. webscout/Provider/AISEARCH/scira_search.py +3 -1
  7. webscout/Provider/Aitopia.py +2 -3
  8. webscout/Provider/Andi.py +3 -3
  9. webscout/Provider/ChatGPTClone.py +1 -1
  10. webscout/Provider/ChatSandbox.py +1 -0
  11. webscout/Provider/Cloudflare.py +1 -1
  12. webscout/Provider/Cohere.py +1 -0
  13. webscout/Provider/Deepinfra.py +13 -10
  14. webscout/Provider/ExaAI.py +1 -1
  15. webscout/Provider/ExaChat.py +1 -80
  16. webscout/Provider/Flowith.py +6 -1
  17. webscout/Provider/Gemini.py +7 -5
  18. webscout/Provider/GeminiProxy.py +1 -0
  19. webscout/Provider/GithubChat.py +4 -1
  20. webscout/Provider/Groq.py +1 -1
  21. webscout/Provider/HeckAI.py +8 -4
  22. webscout/Provider/Jadve.py +23 -38
  23. webscout/Provider/K2Think.py +308 -0
  24. webscout/Provider/Koboldai.py +8 -186
  25. webscout/Provider/LambdaChat.py +2 -4
  26. webscout/Provider/Nemotron.py +3 -4
  27. webscout/Provider/Netwrck.py +6 -8
  28. webscout/Provider/OLLAMA.py +1 -0
  29. webscout/Provider/OPENAI/Cloudflare.py +6 -7
  30. webscout/Provider/OPENAI/FalconH1.py +2 -7
  31. webscout/Provider/OPENAI/FreeGemini.py +6 -8
  32. webscout/Provider/OPENAI/{monochat.py → K2Think.py} +180 -77
  33. webscout/Provider/OPENAI/NEMOTRON.py +3 -6
  34. webscout/Provider/OPENAI/PI.py +5 -4
  35. webscout/Provider/OPENAI/Qwen3.py +2 -3
  36. webscout/Provider/OPENAI/README.md +2 -1
  37. webscout/Provider/OPENAI/TogetherAI.py +52 -57
  38. webscout/Provider/OPENAI/TwoAI.py +3 -4
  39. webscout/Provider/OPENAI/__init__.py +17 -56
  40. webscout/Provider/OPENAI/ai4chat.py +313 -303
  41. webscout/Provider/OPENAI/base.py +9 -29
  42. webscout/Provider/OPENAI/chatgpt.py +7 -2
  43. webscout/Provider/OPENAI/chatgptclone.py +4 -7
  44. webscout/Provider/OPENAI/chatsandbox.py +84 -59
  45. webscout/Provider/OPENAI/deepinfra.py +12 -6
  46. webscout/Provider/OPENAI/e2b.py +60 -8
  47. webscout/Provider/OPENAI/flowith.py +4 -3
  48. webscout/Provider/OPENAI/generate_api_key.py +48 -0
  49. webscout/Provider/OPENAI/heckai.py +4 -1
  50. webscout/Provider/OPENAI/netwrck.py +9 -12
  51. webscout/Provider/OPENAI/refact.py +274 -0
  52. webscout/Provider/OPENAI/scirachat.py +6 -0
  53. webscout/Provider/OPENAI/textpollinations.py +3 -14
  54. webscout/Provider/OPENAI/toolbaz.py +14 -10
  55. webscout/Provider/OpenGPT.py +1 -1
  56. webscout/Provider/Openai.py +150 -402
  57. webscout/Provider/PI.py +1 -0
  58. webscout/Provider/Perplexitylabs.py +1 -2
  59. webscout/Provider/QwenLM.py +107 -89
  60. webscout/Provider/STT/__init__.py +17 -2
  61. webscout/Provider/{Llama3.py → Sambanova.py} +9 -10
  62. webscout/Provider/StandardInput.py +1 -1
  63. webscout/Provider/TTI/__init__.py +18 -12
  64. webscout/Provider/TTI/bing.py +14 -2
  65. webscout/Provider/TTI/together.py +10 -9
  66. webscout/Provider/TTS/README.md +0 -1
  67. webscout/Provider/TTS/__init__.py +18 -11
  68. webscout/Provider/TTS/base.py +479 -159
  69. webscout/Provider/TTS/deepgram.py +409 -156
  70. webscout/Provider/TTS/elevenlabs.py +425 -111
  71. webscout/Provider/TTS/freetts.py +317 -140
  72. webscout/Provider/TTS/gesserit.py +192 -128
  73. webscout/Provider/TTS/murfai.py +248 -113
  74. webscout/Provider/TTS/openai_fm.py +347 -129
  75. webscout/Provider/TTS/speechma.py +620 -586
  76. webscout/Provider/TeachAnything.py +1 -0
  77. webscout/Provider/TextPollinationsAI.py +5 -15
  78. webscout/Provider/TogetherAI.py +136 -142
  79. webscout/Provider/TwoAI.py +53 -309
  80. webscout/Provider/TypliAI.py +2 -1
  81. webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +1 -1
  82. webscout/Provider/UNFINISHED/VercelAIGateway.py +339 -0
  83. webscout/Provider/Venice.py +2 -1
  84. webscout/Provider/VercelAI.py +1 -0
  85. webscout/Provider/WiseCat.py +2 -1
  86. webscout/Provider/WrDoChat.py +2 -1
  87. webscout/Provider/__init__.py +18 -174
  88. webscout/Provider/ai4chat.py +1 -1
  89. webscout/Provider/akashgpt.py +7 -10
  90. webscout/Provider/cerebras.py +194 -38
  91. webscout/Provider/chatglm.py +170 -83
  92. webscout/Provider/cleeai.py +1 -2
  93. webscout/Provider/deepseek_assistant.py +1 -1
  94. webscout/Provider/elmo.py +1 -1
  95. webscout/Provider/geminiapi.py +1 -1
  96. webscout/Provider/granite.py +1 -1
  97. webscout/Provider/hermes.py +1 -3
  98. webscout/Provider/julius.py +1 -0
  99. webscout/Provider/learnfastai.py +1 -1
  100. webscout/Provider/llama3mitril.py +1 -1
  101. webscout/Provider/llmchat.py +1 -1
  102. webscout/Provider/llmchatco.py +1 -1
  103. webscout/Provider/meta.py +3 -3
  104. webscout/Provider/oivscode.py +2 -2
  105. webscout/Provider/scira_chat.py +51 -124
  106. webscout/Provider/searchchat.py +1 -0
  107. webscout/Provider/sonus.py +1 -1
  108. webscout/Provider/toolbaz.py +15 -11
  109. webscout/Provider/turboseek.py +31 -22
  110. webscout/Provider/typefully.py +2 -1
  111. webscout/Provider/x0gpt.py +1 -0
  112. webscout/Provider/yep.py +2 -1
  113. webscout/conversation.py +22 -20
  114. webscout/sanitize.py +14 -10
  115. webscout/scout/README.md +20 -23
  116. webscout/scout/core/crawler.py +125 -38
  117. webscout/scout/core/scout.py +26 -5
  118. webscout/tempid.py +6 -0
  119. webscout/version.py +1 -1
  120. webscout/webscout_search.py +13 -6
  121. webscout/webscout_search_async.py +10 -8
  122. webscout/yep_search.py +13 -5
  123. {webscout-8.3.5.dist-info → webscout-8.3.7.dist-info}/METADATA +3 -1
  124. {webscout-8.3.5.dist-info → webscout-8.3.7.dist-info}/RECORD +132 -155
  125. webscout/Provider/AllenAI.py +0 -440
  126. webscout/Provider/Blackboxai.py +0 -793
  127. webscout/Provider/FreeGemini.py +0 -250
  128. webscout/Provider/Glider.py +0 -225
  129. webscout/Provider/Hunyuan.py +0 -283
  130. webscout/Provider/MCPCore.py +0 -322
  131. webscout/Provider/MiniMax.py +0 -207
  132. webscout/Provider/OPENAI/BLACKBOXAI.py +0 -1045
  133. webscout/Provider/OPENAI/MiniMax.py +0 -298
  134. webscout/Provider/OPENAI/autoproxy.py +0 -1067
  135. webscout/Provider/OPENAI/c4ai.py +0 -394
  136. webscout/Provider/OPENAI/copilot.py +0 -305
  137. webscout/Provider/OPENAI/glider.py +0 -330
  138. webscout/Provider/OPENAI/mcpcore.py +0 -431
  139. webscout/Provider/OPENAI/multichat.py +0 -378
  140. webscout/Provider/Reka.py +0 -214
  141. webscout/Provider/TTS/sthir.py +0 -94
  142. webscout/Provider/UNFINISHED/fetch_together_models.py +0 -90
  143. webscout/Provider/asksteve.py +0 -220
  144. webscout/Provider/copilot.py +0 -422
  145. webscout/Provider/freeaichat.py +0 -294
  146. webscout/Provider/koala.py +0 -182
  147. webscout/Provider/lmarena.py +0 -198
  148. webscout/Provider/monochat.py +0 -275
  149. webscout/Provider/multichat.py +0 -375
  150. webscout/Provider/scnet.py +0 -244
  151. webscout/Provider/talkai.py +0 -194
  152. /webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +0 -0
  153. /webscout/Provider/{Qodo.py → UNFINISHED/Qodo.py} +0 -0
  154. /webscout/Provider/{XenAI.py → UNFINISHED/XenAI.py} +0 -0
  155. /webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +0 -0
  156. {webscout-8.3.5.dist-info → webscout-8.3.7.dist-info}/WHEEL +0 -0
  157. {webscout-8.3.5.dist-info → webscout-8.3.7.dist-info}/entry_points.txt +0 -0
  158. {webscout-8.3.5.dist-info → webscout-8.3.7.dist-info}/licenses/LICENSE.md +0 -0
  159. {webscout-8.3.5.dist-info → webscout-8.3.7.dist-info}/top_level.txt +0 -0
@@ -1,156 +1,409 @@
1
- import time
2
- import requests
3
- import pathlib
4
- import base64
5
- import tempfile
6
- from io import BytesIO
7
- from webscout import exceptions
8
- from concurrent.futures import ThreadPoolExecutor, as_completed
9
- from webscout.litagent import LitAgent
10
- from . import utils
11
- from .base import BaseTTSProvider
12
-
13
- class DeepgramTTS(BaseTTSProvider):
14
- """
15
- Text-to-speech provider using the DeepgramTTS API.
16
- """
17
- # Request headers
18
- headers: dict[str, str] = {
19
- "User-Agent": LitAgent().random()
20
- }
21
- all_voices: dict[str, str] = {
22
- "Asteria": "aura-asteria-en", "Arcas": "aura-arcas-en", "Luna": "aura-luna-en",
23
- "Zeus": "aura-zeus-en", "Orpheus": "aura-orpheus-en", "Angus": "aura-angus-en",
24
- "Athena": "aura-athena-en", "Helios": "aura-helios-en", "Hera": "aura-hera-en",
25
- "Orion": "aura-orion-en", "Perseus": "aura-perseus-en", "Stella": "aura-stella-en"
26
- }
27
-
28
- def __init__(self, timeout: int = 20, proxies: dict = None):
29
- """Initializes the DeepgramTTS TTS client."""
30
- super().__init__()
31
- self.session = requests.Session()
32
- self.session.headers.update(self.headers)
33
- if proxies:
34
- self.session.proxies.update(proxies)
35
- self.timeout = timeout
36
-
37
- def tts(self, text: str, voice: str = "Brian", verbose: bool = True) -> str:
38
- """
39
- Converts text to speech using the DeepgramTTS API and saves it to a file.
40
-
41
- Args:
42
- text (str): The text to convert to speech
43
- voice (str): The voice to use for TTS (default: "Brian")
44
- verbose (bool): Whether to print progress messages (default: True)
45
-
46
- Returns:
47
- str: Path to the generated audio file
48
-
49
- Raises:
50
- AssertionError: If the specified voice is not available
51
- requests.RequestException: If there's an error communicating with the API
52
- RuntimeError: If there's an error processing the audio
53
- """
54
- assert (
55
- voice in self.all_voices
56
- ), f"Voice '{voice}' not one of [{', '.join(self.all_voices.keys())}]"
57
-
58
- url = "https://deepgram.com/api/ttsAudioGeneration"
59
- filename = pathlib.Path(tempfile.mktemp(suffix=".mp3", dir=self.temp_dir))
60
-
61
- # Split text into sentences using the utils module
62
- sentences = utils.split_sentences(text)
63
- if verbose:
64
- for index, sen in enumerate(sentences):
65
- print(f"[debug] Sentence {index}: {sen}")
66
-
67
- def generate_audio_for_chunk(part_text: str, part_number: int):
68
- """
69
- Generate audio for a single chunk of text.
70
-
71
- Args:
72
- part_text (str): The text chunk to convert
73
- part_number (int): The chunk number for ordering
74
-
75
- Returns:
76
- tuple: (part_number, audio_data)
77
-
78
- Raises:
79
- requests.RequestException: If there's an API error
80
- """
81
- max_retries = 3
82
- retry_count = 0
83
-
84
- while retry_count < max_retries:
85
- try:
86
- payload = {"text": part_text, "model": self.all_voices[voice]}
87
- response = self.session.post(
88
- url=url,
89
- headers=self.headers,
90
- json=payload,
91
- stream=True,
92
- timeout=self.timeout
93
- )
94
- response.raise_for_status()
95
-
96
- response_data = response.json().get('data')
97
- if response_data:
98
- audio_data = base64.b64decode(response_data)
99
- if verbose:
100
- print(f"[debug] Chunk {part_number} processed successfully")
101
- return part_number, audio_data
102
-
103
- if verbose:
104
- print(f"[debug] No data received for chunk {part_number}. Attempt {retry_count + 1}/{max_retries}")
105
-
106
- except requests.RequestException as e:
107
- if verbose:
108
- print(f"[debug] Error processing chunk {part_number}: {str(e)}. Attempt {retry_count + 1}/{max_retries}")
109
- if retry_count == max_retries - 1:
110
- raise
111
-
112
- retry_count += 1
113
- time.sleep(1)
114
-
115
- raise RuntimeError(f"Failed to generate audio for chunk {part_number} after {max_retries} attempts")
116
-
117
- try:
118
- # Using ThreadPoolExecutor to handle requests concurrently
119
- with ThreadPoolExecutor() as executor:
120
- futures = {
121
- executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
122
- for chunk_num, sentence in enumerate(sentences, start=1)
123
- }
124
-
125
- # Dictionary to store results with order preserved
126
- audio_chunks = {}
127
-
128
- for future in as_completed(futures):
129
- chunk_num = futures[future]
130
- try:
131
- part_number, audio_data = future.result()
132
- audio_chunks[part_number] = audio_data
133
- except Exception as e:
134
- raise RuntimeError(f"Failed to generate audio for chunk {chunk_num}: {str(e)}")
135
-
136
- # Combine all audio chunks in order
137
- with open(filename, 'wb') as f:
138
- for chunk_num in sorted(audio_chunks.keys()):
139
- f.write(audio_chunks[chunk_num])
140
-
141
- if verbose:
142
- print(f"[debug] Audio saved to {filename}")
143
- return str(filename)
144
-
145
- except Exception as e:
146
- print(f"[debug] Failed to generate audio: {str(e)}") if verbose else None
147
- raise RuntimeError(f"Failed to generate audio: {str(e)}")
148
-
149
- # Example usage
150
- if __name__ == "__main__":
151
- deepgram = DeepgramTTS()
152
- text = "This is a test of the DeepgramTTS text-to-speech API. It supports multiple sentences. Let's see how it works!"
153
-
154
- print("[debug] Generating audio...")
155
- audio_file = deepgram.tts(text, voice="Asteria")
156
- print(f"Audio saved to: {audio_file}")
1
+ ##################################################################################
2
+ ## Deepgram TTS Provider ##
3
+ ##################################################################################
4
+ import time
5
+ import requests
6
+ import pathlib
7
+ import base64
8
+ import tempfile
9
+ from io import BytesIO
10
+ from webscout import exceptions
11
+ from concurrent.futures import ThreadPoolExecutor, as_completed
12
+ from webscout.litagent import LitAgent
13
+ try:
14
+ from . import utils
15
+ from .base import BaseTTSProvider
16
+ except ImportError:
17
+ # Handle direct execution
18
+ import sys
19
+ import os
20
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
21
+ from webscout.Provider.TTS import utils
22
+ from webscout.Provider.TTS.base import BaseTTSProvider
23
+
24
+ class DeepgramTTS(BaseTTSProvider):
25
+ """
26
+ Text-to-speech provider using the Deepgram API with OpenAI-compatible interface.
27
+
28
+ This provider follows the OpenAI TTS API structure with support for:
29
+ - Multiple TTS models (gpt-4o-mini-tts, tts-1, tts-1-hd)
30
+ - Deepgram's Aura voices optimized for English
31
+ - Voice instructions for controlling speech aspects
32
+ - Multiple output formats
33
+ - Streaming support
34
+ """
35
+
36
+ # Request headers
37
+ headers: dict[str, str] = {
38
+ "User-Agent": LitAgent().random()
39
+ }
40
+
41
+ # Override supported models for Deepgram
42
+ SUPPORTED_MODELS = None
43
+
44
+ # Deepgram Aura voices (mapped to OpenAI-compatible names)
45
+ SUPPORTED_VOICES = [
46
+ "asteria", # aura-asteria-en - Clear and articulate female voice
47
+ "arcas", # aura-arcas-en - Warm male voice
48
+ "luna", # aura-luna-en - Gentle female voice
49
+ "zeus", # aura-zeus-en - Authoritative male voice
50
+ "orpheus", # aura-orpheus-en - Melodic male voice
51
+ "angus", # aura-angus-en - Scottish-accented male voice
52
+ "athena", # aura-athena-en - Professional female voice
53
+ "helios", # aura-helios-en - Bright male voice
54
+ "hera", # aura-hera-en - Mature female voice
55
+ "orion", # aura-orion-en - Deep male voice
56
+ "perseus", # aura-perseus-en - Young male voice
57
+ "stella" # aura-stella-en - Friendly female voice
58
+ ]
59
+
60
+ # Voice mapping for Deepgram API compatibility
61
+ voice_mapping = {
62
+ "asteria": "aura-asteria-en",
63
+ "arcas": "aura-arcas-en",
64
+ "luna": "aura-luna-en",
65
+ "zeus": "aura-zeus-en",
66
+ "orpheus": "aura-orpheus-en",
67
+ "angus": "aura-angus-en",
68
+ "athena": "aura-athena-en",
69
+ "helios": "aura-helios-en",
70
+ "hera": "aura-hera-en",
71
+ "orion": "aura-orion-en",
72
+ "perseus": "aura-perseus-en",
73
+ "stella": "aura-stella-en"
74
+ }
75
+
76
+ # Legacy voice mapping for backward compatibility
77
+ all_voices: dict[str, str] = {
78
+ "Asteria": "aura-asteria-en", "Arcas": "aura-arcas-en", "Luna": "aura-luna-en",
79
+ "Zeus": "aura-zeus-en", "Orpheus": "aura-orpheus-en", "Angus": "aura-angus-en",
80
+ "Athena": "aura-athena-en", "Helios": "aura-helios-en", "Hera": "aura-hera-en",
81
+ "Orion": "aura-orion-en", "Perseus": "aura-perseus-en", "Stella": "aura-stella-en"
82
+ }
83
+
84
+ def __init__(self, timeout: int = 20, proxies: dict = None):
85
+ """
86
+ Initialize the Deepgram TTS client.
87
+
88
+ Args:
89
+ timeout (int): Request timeout in seconds
90
+ proxies (dict): Proxy configuration
91
+ """
92
+ super().__init__()
93
+ self.api_url = "https://deepgram.com/api/ttsAudioGeneration"
94
+ self.session = requests.Session()
95
+ self.session.headers.update(self.headers)
96
+ if proxies:
97
+ self.session.proxies.update(proxies)
98
+ self.timeout = timeout
99
+ # Override defaults for Deepgram
100
+ self.default_voice = "asteria"
101
+
102
+ def tts(
103
+ self,
104
+ text: str,
105
+ model: str = "gpt-4o-mini-tts",
106
+ voice: str = "asteria",
107
+ response_format: str = "mp3",
108
+ instructions: str = None,
109
+ verbose: bool = True
110
+ ) -> str:
111
+ """
112
+ Convert text to speech using Deepgram API with OpenAI-compatible parameters.
113
+
114
+ Args:
115
+ text (str): The text to convert to speech (max 10,000 characters)
116
+ model (str): The TTS model to use (gpt-4o-mini-tts, tts-1, tts-1-hd)
117
+ voice (str): The voice to use for TTS (asteria, arcas, luna, zeus, etc.)
118
+ response_format (str): Audio format (mp3, opus, aac, flac, wav, pcm)
119
+ instructions (str): Voice instructions (not used by Deepgram but kept for compatibility)
120
+ verbose (bool): Whether to print debug information
121
+
122
+ Returns:
123
+ str: Path to the generated audio file
124
+
125
+ Raises:
126
+ ValueError: If input parameters are invalid
127
+ exceptions.FailedToGenerateResponseError: If there is an error generating or saving the audio
128
+ """
129
+ # Validate input parameters
130
+ if not text or not isinstance(text, str):
131
+ raise ValueError("Input text must be a non-empty string")
132
+ if len(text) > 10000:
133
+ raise ValueError("Input text exceeds maximum allowed length of 10,000 characters")
134
+
135
+ # Validate model, voice, and format using base class methods
136
+ model = self.validate_model(model)
137
+ voice = self.validate_voice(voice)
138
+ response_format = self.validate_format(response_format)
139
+
140
+ # Map voice to Deepgram API format
141
+ deepgram_voice = self.voice_mapping.get(voice, voice)
142
+
143
+ # Create temporary file with appropriate extension
144
+ file_extension = f".{response_format}" if response_format != "pcm" else ".wav"
145
+ filename = pathlib.Path(tempfile.mktemp(suffix=file_extension, dir=self.temp_dir))
146
+
147
+ # Split text into sentences using the utils module for better processing
148
+ sentences = utils.split_sentences(text)
149
+ if verbose:
150
+ print(f"[debug] Processing {len(sentences)} sentences")
151
+ print(f"[debug] Model: {model}")
152
+ print(f"[debug] Voice: {voice} -> {deepgram_voice}")
153
+ print(f"[debug] Format: {response_format}")
154
+
155
+ def generate_audio_for_chunk(part_text: str, part_number: int):
156
+ """
157
+ Generate audio for a single chunk of text.
158
+
159
+ Args:
160
+ part_text (str): The text chunk to convert
161
+ part_number (int): The chunk number for ordering
162
+
163
+ Returns:
164
+ tuple: (part_number, audio_data)
165
+
166
+ Raises:
167
+ requests.RequestException: If there's an API error
168
+ """
169
+ max_retries = 3
170
+ retry_count = 0
171
+
172
+ while retry_count < max_retries:
173
+ try:
174
+ payload = {
175
+ "text": part_text,
176
+ "model": deepgram_voice,
177
+ # Add model parameter for future Deepgram API compatibility
178
+ "tts_model": model
179
+ }
180
+ response = self.session.post(
181
+ url=self.api_url,
182
+ headers=self.headers,
183
+ json=payload,
184
+ stream=True,
185
+ timeout=self.timeout
186
+ )
187
+ response.raise_for_status()
188
+
189
+ response_data = response.json().get('data')
190
+ if response_data:
191
+ audio_data = base64.b64decode(response_data)
192
+ if verbose:
193
+ print(f"[debug] Chunk {part_number} processed successfully")
194
+ return part_number, audio_data
195
+
196
+ if verbose:
197
+ print(f"[debug] No data received for chunk {part_number}. Attempt {retry_count + 1}/{max_retries}")
198
+
199
+ except requests.RequestException as e:
200
+ if verbose:
201
+ print(f"[debug] Error processing chunk {part_number}: {str(e)}. Attempt {retry_count + 1}/{max_retries}")
202
+ if retry_count == max_retries - 1:
203
+ raise exceptions.FailedToGenerateResponseError(f"Failed to generate audio for chunk {part_number}: {str(e)}")
204
+
205
+ retry_count += 1
206
+ time.sleep(1)
207
+
208
+ raise exceptions.FailedToGenerateResponseError(f"Failed to generate audio for chunk {part_number} after {max_retries} attempts")
209
+
210
+ try:
211
+ # Using ThreadPoolExecutor to handle requests concurrently
212
+ with ThreadPoolExecutor() as executor:
213
+ futures = {
214
+ executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
215
+ for chunk_num, sentence in enumerate(sentences, start=1)
216
+ }
217
+
218
+ # Dictionary to store results with order preserved
219
+ audio_chunks = {}
220
+
221
+ for future in as_completed(futures):
222
+ chunk_num = futures[future]
223
+ try:
224
+ part_number, audio_data = future.result()
225
+ audio_chunks[part_number] = audio_data
226
+ except Exception as e:
227
+ raise exceptions.FailedToGenerateResponseError(f"Failed to generate audio for chunk {chunk_num}: {str(e)}")
228
+
229
+ # Combine all audio chunks in order
230
+ with open(filename, 'wb') as f:
231
+ for chunk_num in sorted(audio_chunks.keys()):
232
+ f.write(audio_chunks[chunk_num])
233
+
234
+ if verbose:
235
+ print(f"[debug] Speech generated successfully")
236
+ print(f"[debug] Audio saved to {filename}")
237
+
238
+ return str(filename)
239
+
240
+ except Exception as e:
241
+ if verbose:
242
+ print(f"[debug] Failed to generate audio: {str(e)}")
243
+ raise exceptions.FailedToGenerateResponseError(f"Failed to generate audio: {str(e)}")
244
+
245
+ def create_speech(
246
+ self,
247
+ input: str,
248
+ model: str = "gpt-4o-mini-tts",
249
+ voice: str = "asteria",
250
+ response_format: str = "mp3",
251
+ instructions: str = None,
252
+ verbose: bool = False
253
+ ) -> str:
254
+ """
255
+ OpenAI-compatible speech creation interface.
256
+
257
+ Args:
258
+ input (str): The text to convert to speech
259
+ model (str): The TTS model to use
260
+ voice (str): The voice to use
261
+ response_format (str): Audio format
262
+ instructions (str): Voice instructions (not used by Deepgram)
263
+ verbose (bool): Whether to print debug information
264
+
265
+ Returns:
266
+ str: Path to the generated audio file
267
+ """
268
+ return self.tts(
269
+ text=input,
270
+ model=model,
271
+ voice=voice,
272
+ response_format=response_format,
273
+ instructions=instructions,
274
+ verbose=verbose
275
+ )
276
+
277
+ def with_streaming_response(self):
278
+ """
279
+ Return a streaming response context manager (OpenAI-compatible).
280
+
281
+ Returns:
282
+ StreamingResponseContextManager: Context manager for streaming responses
283
+ """
284
+ return StreamingResponseContextManager(self)
285
+
286
+
287
+ class StreamingResponseContextManager:
288
+ """
289
+ Context manager for streaming TTS responses (OpenAI-compatible).
290
+ """
291
+
292
+ def __init__(self, tts_provider: DeepgramTTS):
293
+ self.tts_provider = tts_provider
294
+ self.audio_file = None
295
+
296
+ def create(
297
+ self,
298
+ input: str,
299
+ model: str = "gpt-4o-mini-tts",
300
+ voice: str = "asteria",
301
+ response_format: str = "mp3",
302
+ instructions: str = None
303
+ ):
304
+ """
305
+ Create speech with streaming capability.
306
+
307
+ Args:
308
+ input (str): The text to convert to speech
309
+ model (str): The TTS model to use
310
+ voice (str): The voice to use
311
+ response_format (str): Audio format
312
+ instructions (str): Voice instructions
313
+
314
+ Returns:
315
+ StreamingResponse: Streaming response object
316
+ """
317
+ self.audio_file = self.tts_provider.create_speech(
318
+ input=input,
319
+ model=model,
320
+ voice=voice,
321
+ response_format=response_format,
322
+ instructions=instructions
323
+ )
324
+ return StreamingResponse(self.audio_file)
325
+
326
+ def __enter__(self):
327
+ return self
328
+
329
+ def __exit__(self, exc_type, exc_val, exc_tb):
330
+ pass
331
+
332
+
333
+ class StreamingResponse:
334
+ """
335
+ Streaming response object for TTS audio (OpenAI-compatible).
336
+ """
337
+
338
+ def __init__(self, audio_file: str):
339
+ self.audio_file = audio_file
340
+
341
+ def __enter__(self):
342
+ """Enter the context manager."""
343
+ return self
344
+
345
+ def __exit__(self, exc_type, exc_val, exc_tb):
346
+ """Exit the context manager."""
347
+ pass
348
+
349
+ def stream_to_file(self, file_path: str, chunk_size: int = 1024):
350
+ """
351
+ Stream audio content to a file.
352
+
353
+ Args:
354
+ file_path (str): Destination file path
355
+ chunk_size (int): Size of chunks to read/write
356
+ """
357
+ import shutil
358
+ shutil.copy2(self.audio_file, file_path)
359
+
360
+ def iter_bytes(self, chunk_size: int = 1024):
361
+ """
362
+ Iterate over audio bytes in chunks.
363
+
364
+ Args:
365
+ chunk_size (int): Size of chunks to yield
366
+
367
+ Yields:
368
+ bytes: Audio data chunks
369
+ """
370
+ with open(self.audio_file, 'rb') as f:
371
+ while chunk := f.read(chunk_size):
372
+ yield chunk
373
+
374
+
375
+ # Example usage
376
+ if __name__ == "__main__":
377
+ # Example usage demonstrating OpenAI-compatible interface
378
+ deepgram = DeepgramTTS()
379
+
380
+ try:
381
+ # Basic usage
382
+ print("Testing basic speech generation...")
383
+ audio_file = deepgram.create_speech(
384
+ input="Today is a wonderful day to build something people love!",
385
+ model="gpt-4o-mini-tts",
386
+ voice="asteria",
387
+ instructions="Speak in a cheerful and positive tone."
388
+ )
389
+ print(f"Audio file generated: {audio_file}")
390
+
391
+ # Streaming usage
392
+ print("\nTesting streaming response...")
393
+ with deepgram.with_streaming_response().create(
394
+ input="This is a streaming test with Deepgram Aura voices.",
395
+ voice="luna",
396
+ response_format="wav"
397
+ ) as response:
398
+ response.stream_to_file("deepgram_streaming_test.wav")
399
+ print("Streaming audio saved to deepgram_streaming_test.wav")
400
+
401
+ # Legacy compatibility test
402
+ print("\nTesting legacy voice compatibility...")
403
+ legacy_audio = deepgram.tts("Testing legacy voice format.", voice="Asteria")
404
+ print(f"Legacy audio file generated: {legacy_audio}")
405
+
406
+ except exceptions.FailedToGenerateResponseError as e:
407
+ print(f"Error: {e}")
408
+ except Exception as e:
409
+ print(f"Unexpected error: {e}")