audiopod 1.5.0__py3-none-any.whl → 2.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.
- audiopod/__init__.py +13 -12
- audiopod/client.py +124 -238
- audiopod/exceptions.py +27 -19
- audiopod/resources/__init__.py +23 -0
- audiopod/resources/denoiser.py +116 -0
- audiopod/resources/music.py +166 -0
- audiopod/resources/speaker.py +132 -0
- audiopod/resources/stems.py +267 -0
- audiopod/resources/transcription.py +205 -0
- audiopod/resources/voice.py +139 -0
- audiopod/resources/wallet.py +110 -0
- audiopod-2.1.0.dist-info/METADATA +205 -0
- audiopod-2.1.0.dist-info/RECORD +16 -0
- {audiopod-1.5.0.dist-info → audiopod-2.1.0.dist-info}/WHEEL +1 -1
- audiopod/config.py +0 -17
- audiopod/services/__init__.py +0 -30
- audiopod/services/base.py +0 -69
- audiopod/services/credits.py +0 -42
- audiopod/services/denoiser.py +0 -136
- audiopod/services/music.py +0 -217
- audiopod/services/speaker.py +0 -134
- audiopod/services/stem_extraction.py +0 -287
- audiopod/services/transcription.py +0 -210
- audiopod/services/translation.py +0 -135
- audiopod/services/video.py +0 -329
- audiopod/services/voice.py +0 -187
- audiopod/services/wallet.py +0 -235
- audiopod-1.5.0.dist-info/METADATA +0 -206
- audiopod-1.5.0.dist-info/RECORD +0 -21
- {audiopod-1.5.0.dist-info → audiopod-2.1.0.dist-info}/licenses/LICENSE +0 -0
- {audiopod-1.5.0.dist-info → audiopod-2.1.0.dist-info}/top_level.txt +0 -0
audiopod/services/video.py
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Video Service - Video generation
|
|
3
|
-
|
|
4
|
-
API Routes:
|
|
5
|
-
- POST /api/v2/video/karaoke - Create karaoke video
|
|
6
|
-
- POST /api/v2/video/music-video - Create AI music video
|
|
7
|
-
- POST /api/v2/video/lyric-video - Create lyric video
|
|
8
|
-
- GET /api/v2/video/{id} - Get job status
|
|
9
|
-
- GET /api/v2/video/{id}/download - Get download URLs
|
|
10
|
-
- DELETE /api/v2/video/{id} - Delete job
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from typing import Optional, Dict, Any, List, Literal
|
|
14
|
-
from .base import BaseService
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
VideoType = Literal["karaoke", "music_video", "lyric_video"]
|
|
18
|
-
AspectRatio = Literal["16:9", "9:16", "1:1"]
|
|
19
|
-
SceneGeneration = Literal["flux", "veo3", "static"]
|
|
20
|
-
AnimationStyle = Literal["typewriter", "fade-word", "slide-up", "bounce", "wave", "zoom"]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class VideoService(BaseService):
|
|
24
|
-
"""Service for video generation."""
|
|
25
|
-
|
|
26
|
-
def create_karaoke(
|
|
27
|
-
self,
|
|
28
|
-
audio_file: Optional[str] = None,
|
|
29
|
-
url: Optional[str] = None,
|
|
30
|
-
aspect_ratio: AspectRatio = "16:9",
|
|
31
|
-
scene_generation: SceneGeneration = "flux",
|
|
32
|
-
key_shift: int = 0,
|
|
33
|
-
style: Optional[Dict[str, Any]] = None,
|
|
34
|
-
webhook_url: Optional[str] = None,
|
|
35
|
-
wait_for_completion: bool = False,
|
|
36
|
-
timeout: int = 600,
|
|
37
|
-
) -> Dict[str, Any]:
|
|
38
|
-
"""
|
|
39
|
-
Create a karaoke video from audio.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
audio_file: Path to local audio file
|
|
43
|
-
url: URL of audio/video (YouTube, etc.)
|
|
44
|
-
aspect_ratio: Video aspect ratio
|
|
45
|
-
scene_generation: Background generation method
|
|
46
|
-
key_shift: Pitch shift in semitones (-12 to 12)
|
|
47
|
-
style: Style configuration (colors, font, etc.)
|
|
48
|
-
webhook_url: URL for completion notification
|
|
49
|
-
wait_for_completion: Wait for video to finish
|
|
50
|
-
timeout: Max wait time in seconds
|
|
51
|
-
|
|
52
|
-
Returns:
|
|
53
|
-
Job dict with id, status, video_url (when completed)
|
|
54
|
-
"""
|
|
55
|
-
if audio_file:
|
|
56
|
-
return self._create_from_upload(
|
|
57
|
-
"karaoke", audio_file, aspect_ratio, scene_generation,
|
|
58
|
-
key_shift, webhook_url, wait_for_completion, timeout
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
data = {
|
|
62
|
-
"audio_url": url,
|
|
63
|
-
"aspect_ratio": aspect_ratio,
|
|
64
|
-
"scene_generation": scene_generation,
|
|
65
|
-
"key_shift": key_shift,
|
|
66
|
-
}
|
|
67
|
-
if style:
|
|
68
|
-
data["style"] = style
|
|
69
|
-
if webhook_url:
|
|
70
|
-
data["webhook_url"] = webhook_url
|
|
71
|
-
|
|
72
|
-
if self.async_mode:
|
|
73
|
-
return self._async_create("karaoke", data, wait_for_completion, timeout)
|
|
74
|
-
|
|
75
|
-
response = self.client.request("POST", "/api/v2/video/karaoke", json_data=data)
|
|
76
|
-
|
|
77
|
-
if wait_for_completion:
|
|
78
|
-
return self._wait_for_video(response["id"], timeout)
|
|
79
|
-
return response
|
|
80
|
-
|
|
81
|
-
def create_music_video(
|
|
82
|
-
self,
|
|
83
|
-
audio_file: Optional[str] = None,
|
|
84
|
-
url: Optional[str] = None,
|
|
85
|
-
music_job_id: Optional[str] = None,
|
|
86
|
-
aspect_ratio: AspectRatio = "16:9",
|
|
87
|
-
scene_generation: SceneGeneration = "flux",
|
|
88
|
-
scene_count: int = 10,
|
|
89
|
-
include_lyrics: bool = True,
|
|
90
|
-
style: Optional[Dict[str, Any]] = None,
|
|
91
|
-
webhook_url: Optional[str] = None,
|
|
92
|
-
wait_for_completion: bool = False,
|
|
93
|
-
timeout: int = 900,
|
|
94
|
-
) -> Dict[str, Any]:
|
|
95
|
-
"""
|
|
96
|
-
Create an AI music video.
|
|
97
|
-
|
|
98
|
-
Args:
|
|
99
|
-
audio_file: Path to local audio file
|
|
100
|
-
url: URL of audio file
|
|
101
|
-
music_job_id: AudioPod music generation job ID
|
|
102
|
-
aspect_ratio: Video aspect ratio
|
|
103
|
-
scene_generation: Scene generation method (flux or veo3)
|
|
104
|
-
scene_count: Number of scenes to generate (5-20)
|
|
105
|
-
include_lyrics: Overlay lyrics on video
|
|
106
|
-
style: Style configuration
|
|
107
|
-
webhook_url: URL for completion notification
|
|
108
|
-
wait_for_completion: Wait for video to finish
|
|
109
|
-
timeout: Max wait time in seconds
|
|
110
|
-
|
|
111
|
-
Returns:
|
|
112
|
-
Job dict with video_url when completed
|
|
113
|
-
"""
|
|
114
|
-
data = {
|
|
115
|
-
"aspect_ratio": aspect_ratio,
|
|
116
|
-
"scene_generation": scene_generation,
|
|
117
|
-
"scene_count": scene_count,
|
|
118
|
-
"include_lyrics": include_lyrics,
|
|
119
|
-
}
|
|
120
|
-
if url:
|
|
121
|
-
data["audio_url"] = url
|
|
122
|
-
if music_job_id:
|
|
123
|
-
data["music_job_id"] = music_job_id
|
|
124
|
-
if style:
|
|
125
|
-
data["style"] = style
|
|
126
|
-
if webhook_url:
|
|
127
|
-
data["webhook_url"] = webhook_url
|
|
128
|
-
|
|
129
|
-
if self.async_mode:
|
|
130
|
-
return self._async_create("music-video", data, wait_for_completion, timeout)
|
|
131
|
-
|
|
132
|
-
response = self.client.request("POST", "/api/v2/video/music-video", json_data=data)
|
|
133
|
-
|
|
134
|
-
if wait_for_completion:
|
|
135
|
-
return self._wait_for_video(response["id"], timeout)
|
|
136
|
-
return response
|
|
137
|
-
|
|
138
|
-
def create_lyric_video(
|
|
139
|
-
self,
|
|
140
|
-
audio_file: Optional[str] = None,
|
|
141
|
-
url: Optional[str] = None,
|
|
142
|
-
lyrics: Optional[str] = None,
|
|
143
|
-
aspect_ratio: AspectRatio = "16:9",
|
|
144
|
-
animation_style: AnimationStyle = "fade-word",
|
|
145
|
-
background_color: str = "#1a1a2e",
|
|
146
|
-
accent_color: str = "#e94560",
|
|
147
|
-
particles_enabled: bool = True,
|
|
148
|
-
background_image_url: Optional[str] = None,
|
|
149
|
-
webhook_url: Optional[str] = None,
|
|
150
|
-
wait_for_completion: bool = False,
|
|
151
|
-
timeout: int = 600,
|
|
152
|
-
) -> Dict[str, Any]:
|
|
153
|
-
"""
|
|
154
|
-
Create a kinetic typography lyric video.
|
|
155
|
-
|
|
156
|
-
Args:
|
|
157
|
-
audio_file: Path to local audio file
|
|
158
|
-
url: URL of audio file
|
|
159
|
-
lyrics: Custom lyrics (auto-transcribed if not provided)
|
|
160
|
-
aspect_ratio: Video aspect ratio
|
|
161
|
-
animation_style: Text animation style
|
|
162
|
-
background_color: Background color
|
|
163
|
-
accent_color: Accent/highlight color
|
|
164
|
-
particles_enabled: Enable particle effects
|
|
165
|
-
background_image_url: Optional background image
|
|
166
|
-
webhook_url: URL for completion notification
|
|
167
|
-
wait_for_completion: Wait for video to finish
|
|
168
|
-
timeout: Max wait time in seconds
|
|
169
|
-
|
|
170
|
-
Returns:
|
|
171
|
-
Job dict with video_url when completed
|
|
172
|
-
"""
|
|
173
|
-
data = {
|
|
174
|
-
"aspect_ratio": aspect_ratio,
|
|
175
|
-
"style": {
|
|
176
|
-
"animation_style": animation_style,
|
|
177
|
-
"background_color": background_color,
|
|
178
|
-
"accent_color": accent_color,
|
|
179
|
-
"particles_enabled": particles_enabled,
|
|
180
|
-
},
|
|
181
|
-
}
|
|
182
|
-
if url:
|
|
183
|
-
data["audio_url"] = url
|
|
184
|
-
if lyrics:
|
|
185
|
-
data["lyrics"] = lyrics
|
|
186
|
-
if background_image_url:
|
|
187
|
-
data["background_image_url"] = background_image_url
|
|
188
|
-
if webhook_url:
|
|
189
|
-
data["webhook_url"] = webhook_url
|
|
190
|
-
|
|
191
|
-
if self.async_mode:
|
|
192
|
-
return self._async_create("lyric-video", data, wait_for_completion, timeout)
|
|
193
|
-
|
|
194
|
-
response = self.client.request("POST", "/api/v2/video/lyric-video", json_data=data)
|
|
195
|
-
|
|
196
|
-
if wait_for_completion:
|
|
197
|
-
return self._wait_for_video(response["id"], timeout)
|
|
198
|
-
return response
|
|
199
|
-
|
|
200
|
-
def get_job(self, job_id: str) -> Dict[str, Any]:
|
|
201
|
-
"""Get video job status."""
|
|
202
|
-
if self.async_mode:
|
|
203
|
-
return self._async_get_job(job_id)
|
|
204
|
-
return self.client.request("GET", f"/api/v2/video/{job_id}")
|
|
205
|
-
|
|
206
|
-
async def _async_get_job(self, job_id: str) -> Dict[str, Any]:
|
|
207
|
-
return await self.client.request("GET", f"/api/v2/video/{job_id}")
|
|
208
|
-
|
|
209
|
-
def get_download_urls(self, job_id: str) -> Dict[str, Any]:
|
|
210
|
-
"""Get download URLs for completed video."""
|
|
211
|
-
if self.async_mode:
|
|
212
|
-
return self._async_get_download(job_id)
|
|
213
|
-
return self.client.request("GET", f"/api/v2/video/{job_id}/download")
|
|
214
|
-
|
|
215
|
-
async def _async_get_download(self, job_id: str) -> Dict[str, Any]:
|
|
216
|
-
return await self.client.request("GET", f"/api/v2/video/{job_id}/download")
|
|
217
|
-
|
|
218
|
-
def delete_job(self, job_id: str) -> Dict[str, str]:
|
|
219
|
-
"""Delete a video job."""
|
|
220
|
-
if self.async_mode:
|
|
221
|
-
return self._async_delete(job_id)
|
|
222
|
-
return self.client.request("DELETE", f"/api/v2/video/{job_id}")
|
|
223
|
-
|
|
224
|
-
async def _async_delete(self, job_id: str) -> Dict[str, str]:
|
|
225
|
-
return await self.client.request("DELETE", f"/api/v2/video/{job_id}")
|
|
226
|
-
|
|
227
|
-
def list_jobs(
|
|
228
|
-
self,
|
|
229
|
-
video_type: Optional[VideoType] = None,
|
|
230
|
-
limit: int = 20,
|
|
231
|
-
offset: int = 0,
|
|
232
|
-
) -> Dict[str, Any]:
|
|
233
|
-
"""List video jobs."""
|
|
234
|
-
params = {"limit": limit, "offset": offset}
|
|
235
|
-
if video_type:
|
|
236
|
-
params["type"] = video_type
|
|
237
|
-
|
|
238
|
-
if self.async_mode:
|
|
239
|
-
return self._async_list(params)
|
|
240
|
-
return self.client.request("GET", "/api/v2/video/", params=params)
|
|
241
|
-
|
|
242
|
-
async def _async_list(self, params: Dict) -> Dict[str, Any]:
|
|
243
|
-
return await self.client.request("GET", "/api/v2/video/", params=params)
|
|
244
|
-
|
|
245
|
-
def _create_from_upload(
|
|
246
|
-
self,
|
|
247
|
-
video_type: str,
|
|
248
|
-
audio_file: str,
|
|
249
|
-
aspect_ratio: str,
|
|
250
|
-
scene_generation: str,
|
|
251
|
-
key_shift: int,
|
|
252
|
-
webhook_url: Optional[str],
|
|
253
|
-
wait_for_completion: bool,
|
|
254
|
-
timeout: int,
|
|
255
|
-
) -> Dict[str, Any]:
|
|
256
|
-
"""Create video from file upload."""
|
|
257
|
-
data = {
|
|
258
|
-
"aspect_ratio": aspect_ratio,
|
|
259
|
-
"scene_generation": scene_generation,
|
|
260
|
-
"key_shift": str(key_shift),
|
|
261
|
-
}
|
|
262
|
-
if webhook_url:
|
|
263
|
-
data["webhook_url"] = webhook_url
|
|
264
|
-
|
|
265
|
-
files = self._prepare_file_upload(audio_file, "file")
|
|
266
|
-
|
|
267
|
-
response = self.client.request(
|
|
268
|
-
"POST", f"/api/v2/video/{video_type}/upload", data=data, files=files
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
if wait_for_completion:
|
|
272
|
-
return self._wait_for_video(response["id"], timeout)
|
|
273
|
-
return response
|
|
274
|
-
|
|
275
|
-
async def _async_create(
|
|
276
|
-
self,
|
|
277
|
-
endpoint: str,
|
|
278
|
-
data: Dict,
|
|
279
|
-
wait_for_completion: bool,
|
|
280
|
-
timeout: int,
|
|
281
|
-
) -> Dict[str, Any]:
|
|
282
|
-
response = await self.client.request(
|
|
283
|
-
"POST", f"/api/v2/video/{endpoint}", json_data=data
|
|
284
|
-
)
|
|
285
|
-
if wait_for_completion:
|
|
286
|
-
return await self._async_wait_for_video(response["id"], timeout)
|
|
287
|
-
return response
|
|
288
|
-
|
|
289
|
-
def _wait_for_video(self, job_id: str, timeout: int) -> Dict[str, Any]:
|
|
290
|
-
"""Wait for video job completion."""
|
|
291
|
-
import time
|
|
292
|
-
start = time.time()
|
|
293
|
-
|
|
294
|
-
while time.time() - start < timeout:
|
|
295
|
-
job = self.get_job(job_id)
|
|
296
|
-
status = job.get("status", "")
|
|
297
|
-
|
|
298
|
-
if status == "completed":
|
|
299
|
-
# Get download URLs
|
|
300
|
-
download = self.get_download_urls(job_id)
|
|
301
|
-
job.update(download)
|
|
302
|
-
return job
|
|
303
|
-
elif status == "failed":
|
|
304
|
-
raise Exception(f"Video generation failed: {job.get('error', 'Unknown')}")
|
|
305
|
-
|
|
306
|
-
time.sleep(5)
|
|
307
|
-
|
|
308
|
-
raise TimeoutError(f"Video {job_id} timed out after {timeout}s")
|
|
309
|
-
|
|
310
|
-
async def _async_wait_for_video(self, job_id: str, timeout: int) -> Dict[str, Any]:
|
|
311
|
-
"""Async wait for video job completion."""
|
|
312
|
-
import asyncio
|
|
313
|
-
import time
|
|
314
|
-
start = time.time()
|
|
315
|
-
|
|
316
|
-
while time.time() - start < timeout:
|
|
317
|
-
job = await self.get_job(job_id)
|
|
318
|
-
status = job.get("status", "")
|
|
319
|
-
|
|
320
|
-
if status == "completed":
|
|
321
|
-
download = await self.get_download_urls(job_id)
|
|
322
|
-
job.update(download)
|
|
323
|
-
return job
|
|
324
|
-
elif status == "failed":
|
|
325
|
-
raise Exception(f"Video generation failed: {job.get('error', 'Unknown')}")
|
|
326
|
-
|
|
327
|
-
await asyncio.sleep(5)
|
|
328
|
-
|
|
329
|
-
raise TimeoutError(f"Video {job_id} timed out after {timeout}s")
|
audiopod/services/voice.py
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Voice Service - Voice cloning and text-to-speech
|
|
3
|
-
|
|
4
|
-
API Routes:
|
|
5
|
-
- GET /api/v1/voice/voice-profiles - List all voices
|
|
6
|
-
- GET /api/v1/voice/voices/{id}/status - Get voice details
|
|
7
|
-
- POST /api/v1/voice/voice-profiles - Create voice clone
|
|
8
|
-
- DELETE /api/v1/voice/voices/{id} - Delete voice
|
|
9
|
-
- POST /api/v1/voice/voices/{id}/generate - Generate TTS
|
|
10
|
-
- GET /api/v1/voice/tts-jobs/{id}/status - Get TTS job status
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from typing import Optional, Dict, Any, List, Union
|
|
14
|
-
from .base import BaseService
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class VoiceService(BaseService):
|
|
18
|
-
"""Service for voice cloning and text-to-speech."""
|
|
19
|
-
|
|
20
|
-
def list_voices(
|
|
21
|
-
self,
|
|
22
|
-
skip: int = 0,
|
|
23
|
-
limit: int = 100,
|
|
24
|
-
include_public: bool = True,
|
|
25
|
-
) -> List[Dict[str, Any]]:
|
|
26
|
-
"""List available voices (both custom and public)."""
|
|
27
|
-
params = {
|
|
28
|
-
"skip": skip,
|
|
29
|
-
"limit": limit,
|
|
30
|
-
"include_public": str(include_public).lower(),
|
|
31
|
-
}
|
|
32
|
-
if self.async_mode:
|
|
33
|
-
return self._async_list_voices(params)
|
|
34
|
-
return self.client.request("GET", "/api/v1/voice/voice-profiles", params=params)
|
|
35
|
-
|
|
36
|
-
async def _async_list_voices(self, params: Dict) -> List[Dict[str, Any]]:
|
|
37
|
-
return await self.client.request("GET", "/api/v1/voice/voice-profiles", params=params)
|
|
38
|
-
|
|
39
|
-
def get_voice(self, voice_id: Union[int, str]) -> Dict[str, Any]:
|
|
40
|
-
"""Get voice details by ID or UUID."""
|
|
41
|
-
if self.async_mode:
|
|
42
|
-
return self._async_get_voice(voice_id)
|
|
43
|
-
return self.client.request("GET", f"/api/v1/voice/voices/{voice_id}/status")
|
|
44
|
-
|
|
45
|
-
async def _async_get_voice(self, voice_id: Union[int, str]) -> Dict[str, Any]:
|
|
46
|
-
return await self.client.request("GET", f"/api/v1/voice/voices/{voice_id}/status")
|
|
47
|
-
|
|
48
|
-
def create_voice(
|
|
49
|
-
self,
|
|
50
|
-
name: str,
|
|
51
|
-
audio_file: str,
|
|
52
|
-
description: Optional[str] = None,
|
|
53
|
-
) -> Dict[str, Any]:
|
|
54
|
-
"""Create a new voice clone from an audio file."""
|
|
55
|
-
files = self._prepare_file_upload(audio_file, "file")
|
|
56
|
-
data = {"name": name}
|
|
57
|
-
if description:
|
|
58
|
-
data["description"] = description
|
|
59
|
-
|
|
60
|
-
if self.async_mode:
|
|
61
|
-
return self._async_create_voice(data, files)
|
|
62
|
-
return self.client.request("POST", "/api/v1/voice/voice-profiles", data=data, files=files)
|
|
63
|
-
|
|
64
|
-
async def _async_create_voice(self, data: Dict, files: Dict) -> Dict[str, Any]:
|
|
65
|
-
return await self.client.request("POST", "/api/v1/voice/voice-profiles", data=data, files=files)
|
|
66
|
-
|
|
67
|
-
def delete_voice(self, voice_id: Union[int, str]) -> Dict[str, str]:
|
|
68
|
-
"""Delete a voice by ID or UUID."""
|
|
69
|
-
if self.async_mode:
|
|
70
|
-
return self._async_delete_voice(voice_id)
|
|
71
|
-
return self.client.request("DELETE", f"/api/v1/voice/voices/{voice_id}")
|
|
72
|
-
|
|
73
|
-
async def _async_delete_voice(self, voice_id: Union[int, str]) -> Dict[str, str]:
|
|
74
|
-
return await self.client.request("DELETE", f"/api/v1/voice/voices/{voice_id}")
|
|
75
|
-
|
|
76
|
-
def generate_speech(
|
|
77
|
-
self,
|
|
78
|
-
voice_id: Union[int, str],
|
|
79
|
-
text: str,
|
|
80
|
-
speed: float = 1.0,
|
|
81
|
-
language: str = "en",
|
|
82
|
-
audio_format: str = "mp3",
|
|
83
|
-
wait_for_completion: bool = False,
|
|
84
|
-
timeout: int = 300,
|
|
85
|
-
) -> Dict[str, Any]:
|
|
86
|
-
"""
|
|
87
|
-
Generate speech from text using a voice.
|
|
88
|
-
|
|
89
|
-
Args:
|
|
90
|
-
voice_id: Voice ID (int) or UUID (str) to use for generation
|
|
91
|
-
text: Text to convert to speech
|
|
92
|
-
speed: Speech speed (0.25 to 4.0, default 1.0)
|
|
93
|
-
language: Language code (default "en")
|
|
94
|
-
audio_format: Output format - mp3, wav, ogg (default "mp3")
|
|
95
|
-
wait_for_completion: If True, poll until job completes
|
|
96
|
-
timeout: Max seconds to wait for completion
|
|
97
|
-
|
|
98
|
-
Returns:
|
|
99
|
-
Job info dict with job_id, status, etc.
|
|
100
|
-
If wait_for_completion=True, includes output_url when done.
|
|
101
|
-
"""
|
|
102
|
-
data = {
|
|
103
|
-
"input_text": text,
|
|
104
|
-
"speed": speed,
|
|
105
|
-
"language": language,
|
|
106
|
-
"audio_format": audio_format,
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if self.async_mode:
|
|
110
|
-
return self._async_generate_speech(voice_id, data, wait_for_completion, timeout)
|
|
111
|
-
|
|
112
|
-
response = self.client.request(
|
|
113
|
-
"POST",
|
|
114
|
-
f"/api/v1/voice/voices/{voice_id}/generate",
|
|
115
|
-
data=data,
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
if wait_for_completion:
|
|
119
|
-
job_id = response.get("job_id") or response.get("id")
|
|
120
|
-
return self._wait_for_job_completion(job_id, timeout)
|
|
121
|
-
return response
|
|
122
|
-
|
|
123
|
-
async def _async_generate_speech(
|
|
124
|
-
self, voice_id: Union[int, str], data: Dict, wait_for_completion: bool, timeout: int
|
|
125
|
-
) -> Dict[str, Any]:
|
|
126
|
-
response = await self.client.request(
|
|
127
|
-
"POST",
|
|
128
|
-
f"/api/v1/voice/voices/{voice_id}/generate",
|
|
129
|
-
data=data,
|
|
130
|
-
)
|
|
131
|
-
if wait_for_completion:
|
|
132
|
-
job_id = response.get("job_id") or response.get("id")
|
|
133
|
-
return await self._async_wait_for_job_completion(job_id, timeout)
|
|
134
|
-
return response
|
|
135
|
-
|
|
136
|
-
def get_job_status(self, job_id: int) -> Dict[str, Any]:
|
|
137
|
-
"""
|
|
138
|
-
Get TTS job status.
|
|
139
|
-
|
|
140
|
-
Args:
|
|
141
|
-
job_id: The job ID returned from generate_speech
|
|
142
|
-
|
|
143
|
-
Returns:
|
|
144
|
-
Job status dict with status, progress, output_url (when completed), etc.
|
|
145
|
-
"""
|
|
146
|
-
if self.async_mode:
|
|
147
|
-
return self._async_get_job_status(job_id)
|
|
148
|
-
return self.client.request("GET", f"/api/v1/voice/tts-jobs/{job_id}/status")
|
|
149
|
-
|
|
150
|
-
async def _async_get_job_status(self, job_id: int) -> Dict[str, Any]:
|
|
151
|
-
return await self.client.request("GET", f"/api/v1/voice/tts-jobs/{job_id}/status")
|
|
152
|
-
|
|
153
|
-
def _wait_for_job_completion(self, job_id: int, timeout: int) -> Dict[str, Any]:
|
|
154
|
-
"""Poll job status until completion or timeout."""
|
|
155
|
-
import time
|
|
156
|
-
start_time = time.time()
|
|
157
|
-
|
|
158
|
-
while time.time() - start_time < timeout:
|
|
159
|
-
status = self.get_job_status(job_id)
|
|
160
|
-
|
|
161
|
-
if status.get("status") in ("completed", "COMPLETED"):
|
|
162
|
-
return status
|
|
163
|
-
elif status.get("status") in ("failed", "FAILED", "error", "ERROR"):
|
|
164
|
-
raise Exception(f"Job failed: {status.get('error_message', 'Unknown error')}")
|
|
165
|
-
|
|
166
|
-
time.sleep(2)
|
|
167
|
-
|
|
168
|
-
raise TimeoutError(f"Job {job_id} did not complete within {timeout} seconds")
|
|
169
|
-
|
|
170
|
-
async def _async_wait_for_job_completion(self, job_id: int, timeout: int) -> Dict[str, Any]:
|
|
171
|
-
"""Async poll job status until completion or timeout."""
|
|
172
|
-
import asyncio
|
|
173
|
-
import time
|
|
174
|
-
start_time = time.time()
|
|
175
|
-
|
|
176
|
-
while time.time() - start_time < timeout:
|
|
177
|
-
status = await self.get_job_status(job_id)
|
|
178
|
-
|
|
179
|
-
if status.get("status") in ("completed", "COMPLETED"):
|
|
180
|
-
return status
|
|
181
|
-
elif status.get("status") in ("failed", "FAILED", "error", "ERROR"):
|
|
182
|
-
raise Exception(f"Job failed: {status.get('error_message', 'Unknown error')}")
|
|
183
|
-
|
|
184
|
-
await asyncio.sleep(2)
|
|
185
|
-
|
|
186
|
-
raise TimeoutError(f"Job {job_id} did not complete within {timeout} seconds")
|
|
187
|
-
|