audiopod 1.2.0__py3-none-any.whl → 1.4.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 +10 -64
- audiopod/client.py +143 -172
- audiopod/config.py +4 -50
- audiopod/exceptions.py +16 -71
- audiopod/services/__init__.py +8 -6
- audiopod/services/base.py +51 -195
- audiopod/services/credits.py +26 -30
- audiopod/services/denoiser.py +120 -40
- audiopod/services/music.py +180 -485
- audiopod/services/speaker.py +117 -36
- audiopod/services/stem_extraction.py +130 -142
- audiopod/services/transcription.py +159 -184
- audiopod/services/translation.py +109 -170
- audiopod/services/voice.py +141 -424
- audiopod/services/wallet.py +235 -0
- audiopod-1.4.0.dist-info/METADATA +206 -0
- audiopod-1.4.0.dist-info/RECORD +20 -0
- {audiopod-1.2.0.dist-info → audiopod-1.4.0.dist-info}/WHEEL +1 -1
- audiopod/cli.py +0 -285
- audiopod/models.py +0 -250
- audiopod/py.typed +0 -2
- audiopod/services/karaoke.py +0 -61
- audiopod-1.2.0.dist-info/METADATA +0 -454
- audiopod-1.2.0.dist-info/RECORD +0 -24
- audiopod-1.2.0.dist-info/entry_points.txt +0 -2
- {audiopod-1.2.0.dist-info → audiopod-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {audiopod-1.2.0.dist-info → audiopod-1.4.0.dist-info}/top_level.txt +0 -0
audiopod/services/translation.py
CHANGED
|
@@ -1,196 +1,135 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Translation Service -
|
|
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
|
|
3
9
|
"""
|
|
4
10
|
|
|
5
|
-
from typing import Optional,
|
|
11
|
+
from typing import Optional, Dict, Any, List
|
|
6
12
|
from .base import BaseService
|
|
7
|
-
from ..models import Job, TranslationResult
|
|
8
|
-
from ..exceptions import ValidationError
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
class TranslationService(BaseService):
|
|
12
|
-
"""Service for
|
|
13
|
-
|
|
14
|
-
def
|
|
16
|
+
"""Service for audio translation."""
|
|
17
|
+
|
|
18
|
+
def translate(
|
|
15
19
|
self,
|
|
16
20
|
audio_file: Optional[str] = None,
|
|
17
21
|
url: Optional[str] = None,
|
|
18
22
|
target_language: str = "en",
|
|
19
23
|
source_language: Optional[str] = None,
|
|
20
24
|
wait_for_completion: bool = False,
|
|
21
|
-
timeout: int = 900
|
|
22
|
-
) ->
|
|
25
|
+
timeout: int = 900,
|
|
26
|
+
) -> Dict[str, Any]:
|
|
23
27
|
"""
|
|
24
|
-
Translate
|
|
25
|
-
|
|
28
|
+
Translate audio to another language.
|
|
29
|
+
|
|
26
30
|
Args:
|
|
27
|
-
audio_file: Path to audio
|
|
28
|
-
url:
|
|
29
|
-
target_language: Target language code
|
|
30
|
-
source_language: Source language
|
|
31
|
-
wait_for_completion:
|
|
32
|
-
timeout:
|
|
33
|
-
|
|
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
|
+
|
|
34
38
|
Returns:
|
|
35
|
-
Job
|
|
39
|
+
Job dict with translated audio URL when completed
|
|
36
40
|
"""
|
|
37
|
-
if not audio_file and not url:
|
|
38
|
-
raise ValidationError("Either audio_file or url must be provided")
|
|
39
|
-
|
|
40
|
-
if audio_file and url:
|
|
41
|
-
raise ValidationError("Provide either audio_file or url, not both")
|
|
42
|
-
|
|
43
|
-
target_language = self._validate_language_code(target_language)
|
|
44
|
-
if source_language:
|
|
45
|
-
source_language = self._validate_language_code(source_language)
|
|
46
|
-
|
|
47
|
-
# Prepare request data
|
|
48
|
-
files = {}
|
|
49
41
|
data = {"target_language": target_language}
|
|
50
|
-
|
|
51
|
-
if audio_file:
|
|
52
|
-
files = self._prepare_file_upload(audio_file, "file")
|
|
53
|
-
|
|
54
|
-
if url:
|
|
55
|
-
data["url"] = url
|
|
56
|
-
|
|
57
42
|
if source_language:
|
|
58
43
|
data["source_language"] = source_language
|
|
59
|
-
|
|
44
|
+
if url:
|
|
45
|
+
data["url"] = url
|
|
46
|
+
|
|
47
|
+
files = self._prepare_file_upload(audio_file, "file") if audio_file else None
|
|
48
|
+
|
|
60
49
|
if self.async_mode:
|
|
61
|
-
return self.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"/api/v1/translation/translate/speech", # FIXED: Use correct speech-to-speech endpoint
|
|
66
|
-
data=data,
|
|
67
|
-
files=files if files else None
|
|
68
|
-
)
|
|
69
|
-
job = Job.from_dict(response)
|
|
70
|
-
|
|
71
|
-
if wait_for_completion:
|
|
72
|
-
completed_job = self._wait_for_completion(job.id, timeout)
|
|
73
|
-
return TranslationResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
74
|
-
|
|
75
|
-
return job
|
|
76
|
-
|
|
77
|
-
def translate_speech(
|
|
78
|
-
self,
|
|
79
|
-
audio_file: Optional[str] = None,
|
|
80
|
-
url: Optional[str] = None,
|
|
81
|
-
target_language: str = "en",
|
|
82
|
-
source_language: Optional[str] = None,
|
|
83
|
-
wait_for_completion: bool = False,
|
|
84
|
-
timeout: int = 900
|
|
85
|
-
) -> Union[Job, TranslationResult]:
|
|
86
|
-
"""
|
|
87
|
-
Alias for translate_audio - more descriptive method name for speech translation
|
|
88
|
-
"""
|
|
89
|
-
return self.translate_audio(
|
|
90
|
-
audio_file=audio_file,
|
|
91
|
-
url=url,
|
|
92
|
-
target_language=target_language,
|
|
93
|
-
source_language=source_language,
|
|
94
|
-
wait_for_completion=wait_for_completion,
|
|
95
|
-
timeout=timeout
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
async def _async_translate_audio(self, files, data, wait_for_completion, timeout):
|
|
99
|
-
"""Async version of translate_audio"""
|
|
100
|
-
response = await self.client.request(
|
|
101
|
-
"POST",
|
|
102
|
-
"/api/v1/translation/translate/speech", # FIXED: Use correct speech-to-speech endpoint
|
|
103
|
-
data=data,
|
|
104
|
-
files=files if files else None
|
|
105
|
-
)
|
|
106
|
-
job = Job.from_dict(response)
|
|
107
|
-
|
|
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
|
+
|
|
108
54
|
if wait_for_completion:
|
|
109
|
-
|
|
110
|
-
return
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"""
|
|
124
|
-
response = await self.client.request("GET", f"/api/v1/translation/translations/{job_id}")
|
|
125
|
-
return TranslationResult.from_dict(response)
|
|
126
|
-
|
|
127
|
-
def list_translation_jobs(
|
|
128
|
-
self,
|
|
129
|
-
skip: int = 0,
|
|
130
|
-
limit: int = 50
|
|
131
|
-
) -> list:
|
|
132
|
-
"""
|
|
133
|
-
List translation jobs
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
skip: Number of jobs to skip (pagination offset)
|
|
137
|
-
limit: Maximum number of jobs to return (max 100)
|
|
138
|
-
|
|
139
|
-
Returns:
|
|
140
|
-
List of translation jobs
|
|
141
|
-
"""
|
|
142
|
-
params = {
|
|
143
|
-
"skip": skip,
|
|
144
|
-
"limit": min(limit, 100) # API max is 100
|
|
145
|
-
}
|
|
146
|
-
|
|
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."""
|
|
147
70
|
if self.async_mode:
|
|
148
|
-
return self.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return [TranslationResult.from_dict(job_data) for job_data in response]
|
|
157
|
-
|
|
158
|
-
def retry_translation(self, job_id: int) -> Job:
|
|
159
|
-
"""
|
|
160
|
-
Retry a failed translation job
|
|
161
|
-
|
|
162
|
-
Args:
|
|
163
|
-
job_id: ID of the failed translation job to retry
|
|
164
|
-
|
|
165
|
-
Returns:
|
|
166
|
-
New job object for the retry attempt
|
|
167
|
-
"""
|
|
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."""
|
|
168
79
|
if self.async_mode:
|
|
169
|
-
return self.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
async def
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def
|
|
180
|
-
"""
|
|
181
|
-
Delete a translation job
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
job_id: ID of the translation job to delete
|
|
185
|
-
|
|
186
|
-
Returns:
|
|
187
|
-
Deletion confirmation
|
|
188
|
-
"""
|
|
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."""
|
|
189
92
|
if self.async_mode:
|
|
190
|
-
return self.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
async def _async_delete_translation_job(self, job_id: int) -> dict:
|
|
195
|
-
"""Async version of delete_translation_job"""
|
|
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]:
|
|
196
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
|
+
|