audiopod 1.4.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 -8
- audiopod/client.py +125 -233
- 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.4.0.dist-info → audiopod-2.1.0.dist-info}/WHEEL +1 -1
- audiopod/config.py +0 -17
- audiopod/services/__init__.py +0 -28
- audiopod/services/base.py +0 -69
- audiopod/services/credits.py +0 -42
- audiopod/services/denoiser.py +0 -131
- audiopod/services/music.py +0 -217
- audiopod/services/speaker.py +0 -134
- audiopod/services/stem_extraction.py +0 -168
- audiopod/services/transcription.py +0 -187
- audiopod/services/translation.py +0 -135
- audiopod/services/voice.py +0 -187
- audiopod/services/wallet.py +0 -235
- audiopod-1.4.0.dist-info/METADATA +0 -206
- audiopod-1.4.0.dist-info/RECORD +0 -20
- {audiopod-1.4.0.dist-info → audiopod-2.1.0.dist-info}/licenses/LICENSE +0 -0
- {audiopod-1.4.0.dist-info → audiopod-2.1.0.dist-info}/top_level.txt +0 -0
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Stem Extraction Service - Audio stem separation
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from typing import List, Optional, Dict, Any
|
|
6
|
-
from .base import BaseService
|
|
7
|
-
from ..exceptions import ValidationError
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class StemExtractionService(BaseService):
|
|
11
|
-
"""
|
|
12
|
-
Service for audio stem separation.
|
|
13
|
-
|
|
14
|
-
Example:
|
|
15
|
-
```python
|
|
16
|
-
from audiopod import Client
|
|
17
|
-
|
|
18
|
-
client = Client()
|
|
19
|
-
|
|
20
|
-
# Extract all stems
|
|
21
|
-
job = client.stem_extraction.extract_stems(
|
|
22
|
-
audio_file="song.mp3",
|
|
23
|
-
stem_types=["vocals", "drums", "bass", "other"],
|
|
24
|
-
wait_for_completion=True
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
# Download stems
|
|
28
|
-
for stem_name, url in job["download_urls"].items():
|
|
29
|
-
print(f"{stem_name}: {url}")
|
|
30
|
-
```
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
def extract_stems(
|
|
34
|
-
self,
|
|
35
|
-
audio_file: Optional[str] = None,
|
|
36
|
-
url: Optional[str] = None,
|
|
37
|
-
stem_types: Optional[List[str]] = None,
|
|
38
|
-
model_name: str = "htdemucs",
|
|
39
|
-
two_stems_mode: Optional[str] = None,
|
|
40
|
-
wait_for_completion: bool = False,
|
|
41
|
-
timeout: int = 900,
|
|
42
|
-
) -> Dict[str, Any]:
|
|
43
|
-
"""
|
|
44
|
-
Extract stems from audio.
|
|
45
|
-
|
|
46
|
-
Args:
|
|
47
|
-
audio_file: Path to local audio file
|
|
48
|
-
url: URL of audio file (alternative to audio_file)
|
|
49
|
-
stem_types: Stems to extract (e.g., ["vocals", "drums", "bass", "other"])
|
|
50
|
-
model_name: Model to use ("htdemucs" or "htdemucs_6s")
|
|
51
|
-
two_stems_mode: Two-stem mode ("vocals", "drums", or "bass")
|
|
52
|
-
wait_for_completion: Wait for job to complete
|
|
53
|
-
timeout: Max wait time in seconds
|
|
54
|
-
|
|
55
|
-
Returns:
|
|
56
|
-
Job dict with id, status, download_urls (when completed)
|
|
57
|
-
"""
|
|
58
|
-
if not audio_file and not url:
|
|
59
|
-
raise ValidationError("Provide audio_file or url")
|
|
60
|
-
|
|
61
|
-
if audio_file and url:
|
|
62
|
-
raise ValidationError("Provide audio_file or url, not both")
|
|
63
|
-
|
|
64
|
-
if stem_types is None:
|
|
65
|
-
stem_types = (
|
|
66
|
-
["vocals", "drums", "bass", "other", "piano", "guitar"]
|
|
67
|
-
if model_name == "htdemucs_6s"
|
|
68
|
-
else ["vocals", "drums", "bass", "other"]
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
data = {"stem_types": str(stem_types), "model_name": model_name}
|
|
72
|
-
|
|
73
|
-
if url:
|
|
74
|
-
data["url"] = url
|
|
75
|
-
|
|
76
|
-
if two_stems_mode:
|
|
77
|
-
data["two_stems_mode"] = two_stems_mode
|
|
78
|
-
|
|
79
|
-
files = self._prepare_file_upload(audio_file, "file") if audio_file else None
|
|
80
|
-
|
|
81
|
-
if self.async_mode:
|
|
82
|
-
return self._async_extract_stems(data, files, wait_for_completion, timeout)
|
|
83
|
-
|
|
84
|
-
response = self.client.request(
|
|
85
|
-
"POST", "/api/v1/stem-extraction/extract", data=data, files=files
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
if wait_for_completion:
|
|
89
|
-
return self._wait_for_stem_job(response["id"], timeout)
|
|
90
|
-
|
|
91
|
-
return response
|
|
92
|
-
|
|
93
|
-
async def _async_extract_stems(
|
|
94
|
-
self,
|
|
95
|
-
data: Dict[str, Any],
|
|
96
|
-
files: Optional[Dict[str, Any]],
|
|
97
|
-
wait_for_completion: bool,
|
|
98
|
-
timeout: int,
|
|
99
|
-
) -> Dict[str, Any]:
|
|
100
|
-
response = await self.client.request(
|
|
101
|
-
"POST", "/api/v1/stem-extraction/extract", data=data, files=files
|
|
102
|
-
)
|
|
103
|
-
if wait_for_completion:
|
|
104
|
-
return await self._async_wait_for_stem_job(response["id"], timeout)
|
|
105
|
-
return response
|
|
106
|
-
|
|
107
|
-
def get_job(self, job_id: int) -> Dict[str, Any]:
|
|
108
|
-
"""Get stem extraction job status."""
|
|
109
|
-
if self.async_mode:
|
|
110
|
-
return self._async_get_job(job_id)
|
|
111
|
-
return self.client.request("GET", f"/api/v1/stem-extraction/status/{job_id}")
|
|
112
|
-
|
|
113
|
-
async def _async_get_job(self, job_id: int) -> Dict[str, Any]:
|
|
114
|
-
return await self.client.request("GET", f"/api/v1/stem-extraction/status/{job_id}")
|
|
115
|
-
|
|
116
|
-
def list_jobs(self, skip: int = 0, limit: int = 50) -> List[Dict[str, Any]]:
|
|
117
|
-
"""List stem extraction jobs."""
|
|
118
|
-
if self.async_mode:
|
|
119
|
-
return self._async_list_jobs(skip, limit)
|
|
120
|
-
return self.client.request(
|
|
121
|
-
"GET", "/api/v1/stem-extraction/jobs", params={"skip": skip, "limit": limit}
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
async def _async_list_jobs(self, skip: int, limit: int) -> List[Dict[str, Any]]:
|
|
125
|
-
return await self.client.request(
|
|
126
|
-
"GET", "/api/v1/stem-extraction/jobs", params={"skip": skip, "limit": limit}
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
def delete_job(self, job_id: int) -> Dict[str, str]:
|
|
130
|
-
"""Delete a stem extraction job."""
|
|
131
|
-
if self.async_mode:
|
|
132
|
-
return self._async_delete_job(job_id)
|
|
133
|
-
return self.client.request("DELETE", f"/api/v1/stem-extraction/jobs/{job_id}")
|
|
134
|
-
|
|
135
|
-
async def _async_delete_job(self, job_id: int) -> Dict[str, str]:
|
|
136
|
-
return await self.client.request("DELETE", f"/api/v1/stem-extraction/jobs/{job_id}")
|
|
137
|
-
|
|
138
|
-
def _wait_for_stem_job(self, job_id: int, timeout: int = 900) -> Dict[str, Any]:
|
|
139
|
-
"""Wait for stem job completion."""
|
|
140
|
-
import time
|
|
141
|
-
|
|
142
|
-
start = time.time()
|
|
143
|
-
while time.time() - start < timeout:
|
|
144
|
-
job = self.get_job(job_id)
|
|
145
|
-
status = job.get("status", "").upper()
|
|
146
|
-
if status == "COMPLETED":
|
|
147
|
-
return job
|
|
148
|
-
elif status in ["FAILED", "ERROR"]:
|
|
149
|
-
raise Exception(f"Job failed: {job.get('error_message', 'Unknown')}")
|
|
150
|
-
time.sleep(5)
|
|
151
|
-
raise TimeoutError(f"Job {job_id} timed out after {timeout}s")
|
|
152
|
-
|
|
153
|
-
async def _async_wait_for_stem_job(self, job_id: int, timeout: int = 900) -> Dict[str, Any]:
|
|
154
|
-
"""Async wait for stem job completion."""
|
|
155
|
-
import asyncio
|
|
156
|
-
import time
|
|
157
|
-
|
|
158
|
-
start = time.time()
|
|
159
|
-
while time.time() - start < timeout:
|
|
160
|
-
job = await self.get_job(job_id)
|
|
161
|
-
status = job.get("status", "").upper()
|
|
162
|
-
if status == "COMPLETED":
|
|
163
|
-
return job
|
|
164
|
-
elif status in ["FAILED", "ERROR"]:
|
|
165
|
-
raise Exception(f"Job failed: {job.get('error_message', 'Unknown')}")
|
|
166
|
-
await asyncio.sleep(5)
|
|
167
|
-
raise TimeoutError(f"Job {job_id} timed out after {timeout}s")
|
|
168
|
-
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Transcription Service - Speech-to-text
|
|
3
|
-
|
|
4
|
-
API Routes:
|
|
5
|
-
- POST /api/v1/transcription/transcribe - Transcribe from URL
|
|
6
|
-
- POST /api/v1/transcription/transcribe-upload - Transcribe from file upload
|
|
7
|
-
- GET /api/v1/transcription/jobs/{id} - Get job details
|
|
8
|
-
- GET /api/v1/transcription/jobs - List jobs
|
|
9
|
-
- DELETE /api/v1/transcription/jobs/{id} - Delete job
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from typing import Optional, Dict, Any, List
|
|
13
|
-
from .base import BaseService
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class TranscriptionService(BaseService):
|
|
17
|
-
"""Service for speech-to-text transcription."""
|
|
18
|
-
|
|
19
|
-
def transcribe(
|
|
20
|
-
self,
|
|
21
|
-
audio_file: Optional[str] = None,
|
|
22
|
-
url: Optional[str] = None,
|
|
23
|
-
language: Optional[str] = None,
|
|
24
|
-
speaker_diarization: bool = False,
|
|
25
|
-
wait_for_completion: bool = False,
|
|
26
|
-
timeout: int = 600,
|
|
27
|
-
) -> Dict[str, Any]:
|
|
28
|
-
"""
|
|
29
|
-
Transcribe audio to text.
|
|
30
|
-
|
|
31
|
-
Args:
|
|
32
|
-
audio_file: Path to local audio file
|
|
33
|
-
url: URL of audio file (or list of URLs)
|
|
34
|
-
language: Language code (auto-detected if not provided)
|
|
35
|
-
speaker_diarization: Enable speaker separation
|
|
36
|
-
wait_for_completion: Wait for completion
|
|
37
|
-
timeout: Max wait time in seconds
|
|
38
|
-
|
|
39
|
-
Returns:
|
|
40
|
-
Job dict with transcript when completed
|
|
41
|
-
"""
|
|
42
|
-
if audio_file:
|
|
43
|
-
# File upload endpoint
|
|
44
|
-
data = {
|
|
45
|
-
"enable_speaker_diarization": speaker_diarization,
|
|
46
|
-
}
|
|
47
|
-
if language:
|
|
48
|
-
data["language"] = language
|
|
49
|
-
|
|
50
|
-
files = self._prepare_file_upload(audio_file, "files")
|
|
51
|
-
|
|
52
|
-
if self.async_mode:
|
|
53
|
-
return self._async_transcribe_upload(data, files, wait_for_completion, timeout)
|
|
54
|
-
|
|
55
|
-
response = self.client.request(
|
|
56
|
-
"POST", "/api/v1/transcription/transcribe-upload", data=data, files=files
|
|
57
|
-
)
|
|
58
|
-
else:
|
|
59
|
-
# URL-based endpoint
|
|
60
|
-
data = {
|
|
61
|
-
"source_urls": [url] if isinstance(url, str) else url,
|
|
62
|
-
"enable_speaker_diarization": speaker_diarization,
|
|
63
|
-
}
|
|
64
|
-
if language:
|
|
65
|
-
data["language"] = language
|
|
66
|
-
|
|
67
|
-
if self.async_mode:
|
|
68
|
-
return self._async_transcribe(data, wait_for_completion, timeout)
|
|
69
|
-
|
|
70
|
-
response = self.client.request(
|
|
71
|
-
"POST", "/api/v1/transcription/transcribe", json_data=data
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
if wait_for_completion:
|
|
75
|
-
job_id = response.get("id") or response.get("job_id")
|
|
76
|
-
return self._wait_for_transcription(job_id, timeout)
|
|
77
|
-
return response
|
|
78
|
-
|
|
79
|
-
async def _async_transcribe(
|
|
80
|
-
self, data: Dict, wait_for_completion: bool, timeout: int
|
|
81
|
-
) -> Dict[str, Any]:
|
|
82
|
-
response = await self.client.request(
|
|
83
|
-
"POST", "/api/v1/transcription/transcribe", json_data=data
|
|
84
|
-
)
|
|
85
|
-
if wait_for_completion:
|
|
86
|
-
job_id = response.get("id") or response.get("job_id")
|
|
87
|
-
return await self._async_wait_for_transcription(job_id, timeout)
|
|
88
|
-
return response
|
|
89
|
-
|
|
90
|
-
async def _async_transcribe_upload(
|
|
91
|
-
self, data: Dict, files: Dict, wait_for_completion: bool, timeout: int
|
|
92
|
-
) -> Dict[str, Any]:
|
|
93
|
-
response = await self.client.request(
|
|
94
|
-
"POST", "/api/v1/transcription/transcribe-upload", data=data, files=files
|
|
95
|
-
)
|
|
96
|
-
if wait_for_completion:
|
|
97
|
-
job_id = response.get("id") or response.get("job_id")
|
|
98
|
-
return await self._async_wait_for_transcription(job_id, timeout)
|
|
99
|
-
return response
|
|
100
|
-
|
|
101
|
-
def get_job(self, job_id: int) -> Dict[str, Any]:
|
|
102
|
-
"""Get transcription job details and status."""
|
|
103
|
-
if self.async_mode:
|
|
104
|
-
return self._async_get_job(job_id)
|
|
105
|
-
return self.client.request("GET", f"/api/v1/transcription/jobs/{job_id}")
|
|
106
|
-
|
|
107
|
-
async def _async_get_job(self, job_id: int) -> Dict[str, Any]:
|
|
108
|
-
return await self.client.request("GET", f"/api/v1/transcription/jobs/{job_id}")
|
|
109
|
-
|
|
110
|
-
def list_jobs(self, skip: int = 0, limit: int = 50) -> List[Dict[str, Any]]:
|
|
111
|
-
"""List transcription jobs."""
|
|
112
|
-
if self.async_mode:
|
|
113
|
-
return self._async_list_jobs(skip, limit)
|
|
114
|
-
return self.client.request(
|
|
115
|
-
"GET", "/api/v1/transcription/jobs", params={"skip": skip, "limit": limit}
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
async def _async_list_jobs(self, skip: int, limit: int) -> List[Dict[str, Any]]:
|
|
119
|
-
return await self.client.request(
|
|
120
|
-
"GET", "/api/v1/transcription/jobs", params={"skip": skip, "limit": limit}
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
def delete_job(self, job_id: int) -> Dict[str, str]:
|
|
124
|
-
"""Delete a transcription job."""
|
|
125
|
-
if self.async_mode:
|
|
126
|
-
return self._async_delete_job(job_id)
|
|
127
|
-
return self.client.request("DELETE", f"/api/v1/transcription/jobs/{job_id}")
|
|
128
|
-
|
|
129
|
-
async def _async_delete_job(self, job_id: int) -> Dict[str, str]:
|
|
130
|
-
return await self.client.request("DELETE", f"/api/v1/transcription/jobs/{job_id}")
|
|
131
|
-
|
|
132
|
-
def get_transcript(self, job_id: int, format: str = "json") -> Any:
|
|
133
|
-
"""
|
|
134
|
-
Get transcript content.
|
|
135
|
-
|
|
136
|
-
Args:
|
|
137
|
-
job_id: Job ID
|
|
138
|
-
format: Output format - 'json', 'txt', 'srt', 'vtt'
|
|
139
|
-
"""
|
|
140
|
-
if self.async_mode:
|
|
141
|
-
return self._async_get_transcript(job_id, format)
|
|
142
|
-
return self.client.request(
|
|
143
|
-
"GET", f"/api/v1/transcription/jobs/{job_id}/transcript", params={"format": format}
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
async def _async_get_transcript(self, job_id: int, format: str) -> Any:
|
|
147
|
-
return await self.client.request(
|
|
148
|
-
"GET", f"/api/v1/transcription/jobs/{job_id}/transcript", params={"format": format}
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
def _wait_for_transcription(self, job_id: int, timeout: int) -> Dict[str, Any]:
|
|
152
|
-
"""Wait for transcription job completion."""
|
|
153
|
-
import time
|
|
154
|
-
start_time = time.time()
|
|
155
|
-
|
|
156
|
-
while time.time() - start_time < timeout:
|
|
157
|
-
job = self.get_job(job_id)
|
|
158
|
-
status = job.get("status", "").upper()
|
|
159
|
-
|
|
160
|
-
if status == "COMPLETED":
|
|
161
|
-
return job
|
|
162
|
-
elif status in ("FAILED", "ERROR", "CANCELLED"):
|
|
163
|
-
raise Exception(f"Transcription failed: {job.get('error_message', 'Unknown error')}")
|
|
164
|
-
|
|
165
|
-
time.sleep(3)
|
|
166
|
-
|
|
167
|
-
raise TimeoutError(f"Transcription {job_id} did not complete within {timeout} seconds")
|
|
168
|
-
|
|
169
|
-
async def _async_wait_for_transcription(self, job_id: int, timeout: int) -> Dict[str, Any]:
|
|
170
|
-
"""Async wait for transcription job completion."""
|
|
171
|
-
import asyncio
|
|
172
|
-
import time
|
|
173
|
-
start_time = time.time()
|
|
174
|
-
|
|
175
|
-
while time.time() - start_time < timeout:
|
|
176
|
-
job = await self.get_job(job_id)
|
|
177
|
-
status = job.get("status", "").upper()
|
|
178
|
-
|
|
179
|
-
if status == "COMPLETED":
|
|
180
|
-
return job
|
|
181
|
-
elif status in ("FAILED", "ERROR", "CANCELLED"):
|
|
182
|
-
raise Exception(f"Transcription failed: {job.get('error_message', 'Unknown error')}")
|
|
183
|
-
|
|
184
|
-
await asyncio.sleep(3)
|
|
185
|
-
|
|
186
|
-
raise TimeoutError(f"Transcription {job_id} did not complete within {timeout} seconds")
|
|
187
|
-
|
audiopod/services/translation.py
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Translation Service - Audio/speech translation
|
|
3
|
-
|
|
4
|
-
API Routes:
|
|
5
|
-
- POST /api/v1/translation/translate/speech - Translate speech
|
|
6
|
-
- GET /api/v1/translation/translations/{id} - Get translation job
|
|
7
|
-
- GET /api/v1/translation/translations - List translations
|
|
8
|
-
- DELETE /api/v1/translation/translations/{id} - Delete translation
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from typing import Optional, Dict, Any, List
|
|
12
|
-
from .base import BaseService
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class TranslationService(BaseService):
|
|
16
|
-
"""Service for audio translation."""
|
|
17
|
-
|
|
18
|
-
def translate(
|
|
19
|
-
self,
|
|
20
|
-
audio_file: Optional[str] = None,
|
|
21
|
-
url: Optional[str] = None,
|
|
22
|
-
target_language: str = "en",
|
|
23
|
-
source_language: Optional[str] = None,
|
|
24
|
-
wait_for_completion: bool = False,
|
|
25
|
-
timeout: int = 900,
|
|
26
|
-
) -> Dict[str, Any]:
|
|
27
|
-
"""
|
|
28
|
-
Translate audio to another language.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
audio_file: Path to local audio file
|
|
32
|
-
url: URL of audio file
|
|
33
|
-
target_language: Target language code
|
|
34
|
-
source_language: Source language (auto-detected if not provided)
|
|
35
|
-
wait_for_completion: Wait for completion
|
|
36
|
-
timeout: Max wait time in seconds
|
|
37
|
-
|
|
38
|
-
Returns:
|
|
39
|
-
Job dict with translated audio URL when completed
|
|
40
|
-
"""
|
|
41
|
-
data = {"target_language": target_language}
|
|
42
|
-
if source_language:
|
|
43
|
-
data["source_language"] = source_language
|
|
44
|
-
if url:
|
|
45
|
-
data["url"] = url
|
|
46
|
-
|
|
47
|
-
files = self._prepare_file_upload(audio_file, "file") if audio_file else None
|
|
48
|
-
|
|
49
|
-
if self.async_mode:
|
|
50
|
-
return self._async_translate(data, files, wait_for_completion, timeout)
|
|
51
|
-
|
|
52
|
-
response = self.client.request("POST", "/api/v1/translation/translate/speech", data=data, files=files)
|
|
53
|
-
|
|
54
|
-
if wait_for_completion:
|
|
55
|
-
job_id = response.get("id") or response.get("job_id")
|
|
56
|
-
return self._wait_for_translation(job_id, timeout)
|
|
57
|
-
return response
|
|
58
|
-
|
|
59
|
-
async def _async_translate(
|
|
60
|
-
self, data: Dict, files: Optional[Dict], wait_for_completion: bool, timeout: int
|
|
61
|
-
) -> Dict[str, Any]:
|
|
62
|
-
response = await self.client.request("POST", "/api/v1/translation/translate/speech", data=data, files=files)
|
|
63
|
-
if wait_for_completion:
|
|
64
|
-
job_id = response.get("id") or response.get("job_id")
|
|
65
|
-
return await self._async_wait_for_translation(job_id, timeout)
|
|
66
|
-
return response
|
|
67
|
-
|
|
68
|
-
def get_job(self, job_id: int) -> Dict[str, Any]:
|
|
69
|
-
"""Get translation job details and status."""
|
|
70
|
-
if self.async_mode:
|
|
71
|
-
return self._async_get_job(job_id)
|
|
72
|
-
return self.client.request("GET", f"/api/v1/translation/translations/{job_id}")
|
|
73
|
-
|
|
74
|
-
async def _async_get_job(self, job_id: int) -> Dict[str, Any]:
|
|
75
|
-
return await self.client.request("GET", f"/api/v1/translation/translations/{job_id}")
|
|
76
|
-
|
|
77
|
-
def list_jobs(self, skip: int = 0, limit: int = 50) -> List[Dict[str, Any]]:
|
|
78
|
-
"""List translation jobs."""
|
|
79
|
-
if self.async_mode:
|
|
80
|
-
return self._async_list_jobs(skip, limit)
|
|
81
|
-
return self.client.request(
|
|
82
|
-
"GET", "/api/v1/translation/translations", params={"skip": skip, "limit": limit}
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
async def _async_list_jobs(self, skip: int, limit: int) -> List[Dict[str, Any]]:
|
|
86
|
-
return await self.client.request(
|
|
87
|
-
"GET", "/api/v1/translation/translations", params={"skip": skip, "limit": limit}
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
def delete_job(self, job_id: int) -> Dict[str, str]:
|
|
91
|
-
"""Delete a translation job."""
|
|
92
|
-
if self.async_mode:
|
|
93
|
-
return self._async_delete_job(job_id)
|
|
94
|
-
return self.client.request("DELETE", f"/api/v1/translation/translations/{job_id}")
|
|
95
|
-
|
|
96
|
-
async def _async_delete_job(self, job_id: int) -> Dict[str, str]:
|
|
97
|
-
return await self.client.request("DELETE", f"/api/v1/translation/translations/{job_id}")
|
|
98
|
-
|
|
99
|
-
def _wait_for_translation(self, job_id: int, timeout: int) -> Dict[str, Any]:
|
|
100
|
-
"""Wait for translation job completion."""
|
|
101
|
-
import time
|
|
102
|
-
start_time = time.time()
|
|
103
|
-
|
|
104
|
-
while time.time() - start_time < timeout:
|
|
105
|
-
job = self.get_job(job_id)
|
|
106
|
-
status = job.get("status", "").upper()
|
|
107
|
-
|
|
108
|
-
if status == "COMPLETED":
|
|
109
|
-
return job
|
|
110
|
-
elif status in ("FAILED", "ERROR"):
|
|
111
|
-
raise Exception(f"Translation failed: {job.get('error_message', 'Unknown error')}")
|
|
112
|
-
|
|
113
|
-
time.sleep(5)
|
|
114
|
-
|
|
115
|
-
raise TimeoutError(f"Translation {job_id} did not complete within {timeout} seconds")
|
|
116
|
-
|
|
117
|
-
async def _async_wait_for_translation(self, job_id: int, timeout: int) -> Dict[str, Any]:
|
|
118
|
-
"""Async wait for translation job completion."""
|
|
119
|
-
import asyncio
|
|
120
|
-
import time
|
|
121
|
-
start_time = time.time()
|
|
122
|
-
|
|
123
|
-
while time.time() - start_time < timeout:
|
|
124
|
-
job = await self.get_job(job_id)
|
|
125
|
-
status = job.get("status", "").upper()
|
|
126
|
-
|
|
127
|
-
if status == "COMPLETED":
|
|
128
|
-
return job
|
|
129
|
-
elif status in ("FAILED", "ERROR"):
|
|
130
|
-
raise Exception(f"Translation failed: {job.get('error_message', 'Unknown error')}")
|
|
131
|
-
|
|
132
|
-
await asyncio.sleep(5)
|
|
133
|
-
|
|
134
|
-
raise TimeoutError(f"Translation {job_id} did not complete within {timeout} seconds")
|
|
135
|
-
|
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
|
-
|