audiopod 1.1.0__py3-none-any.whl → 1.1.1__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 +1 -1
- audiopod/models.py +21 -6
- audiopod/services/translation.py +126 -11
- {audiopod-1.1.0.dist-info → audiopod-1.1.1.dist-info}/METADATA +13 -5
- {audiopod-1.1.0.dist-info → audiopod-1.1.1.dist-info}/RECORD +9 -9
- {audiopod-1.1.0.dist-info → audiopod-1.1.1.dist-info}/WHEEL +0 -0
- {audiopod-1.1.0.dist-info → audiopod-1.1.1.dist-info}/entry_points.txt +0 -0
- {audiopod-1.1.0.dist-info → audiopod-1.1.1.dist-info}/licenses/LICENSE +0 -0
- {audiopod-1.1.0.dist-info → audiopod-1.1.1.dist-info}/top_level.txt +0 -0
audiopod/__init__.py
CHANGED
audiopod/models.py
CHANGED
|
@@ -151,13 +151,18 @@ class MusicGenerationResult:
|
|
|
151
151
|
|
|
152
152
|
@dataclass
|
|
153
153
|
class TranslationResult:
|
|
154
|
-
"""
|
|
154
|
+
"""Speech translation job result"""
|
|
155
155
|
job: Job
|
|
156
156
|
source_language: Optional[str] = None
|
|
157
157
|
target_language: Optional[str] = None
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
display_name: Optional[str] = None
|
|
159
|
+
audio_output_path: Optional[str] = None
|
|
160
|
+
video_output_path: Optional[str] = None
|
|
160
161
|
transcript_path: Optional[str] = None
|
|
162
|
+
translated_audio_url: Optional[str] = None
|
|
163
|
+
video_output_url: Optional[str] = None
|
|
164
|
+
transcript_urls: Optional[Dict[str, str]] = None
|
|
165
|
+
is_video: bool = False
|
|
161
166
|
|
|
162
167
|
@classmethod
|
|
163
168
|
def from_dict(cls, data: Dict[str, Any]) -> 'TranslationResult':
|
|
@@ -166,10 +171,20 @@ class TranslationResult:
|
|
|
166
171
|
job=Job.from_dict(data),
|
|
167
172
|
source_language=data.get('source_language'),
|
|
168
173
|
target_language=data.get('target_language'),
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
display_name=data.get('display_name'),
|
|
175
|
+
audio_output_path=data.get('audio_output_path'),
|
|
176
|
+
video_output_path=data.get('video_output_path'),
|
|
177
|
+
transcript_path=data.get('transcript_path'),
|
|
178
|
+
translated_audio_url=data.get('translated_audio_url'),
|
|
179
|
+
video_output_url=data.get('video_output_url'),
|
|
180
|
+
transcript_urls=data.get('transcript_urls'),
|
|
181
|
+
is_video=data.get('is_video', False)
|
|
172
182
|
)
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def audio_output_url(self) -> Optional[str]:
|
|
186
|
+
"""Backward compatibility property - returns translated_audio_url"""
|
|
187
|
+
return self.translated_audio_url
|
|
173
188
|
|
|
174
189
|
|
|
175
190
|
@dataclass
|
audiopod/services/translation.py
CHANGED
|
@@ -1,42 +1,59 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Translation Service -
|
|
2
|
+
Translation Service - Speech-to-speech translation operations
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from typing import Optional, Union
|
|
6
6
|
from .base import BaseService
|
|
7
7
|
from ..models import Job, TranslationResult
|
|
8
|
+
from ..exceptions import ValidationError
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class TranslationService(BaseService):
|
|
11
|
-
"""Service for
|
|
12
|
+
"""Service for speech-to-speech translation operations"""
|
|
12
13
|
|
|
13
14
|
def translate_audio(
|
|
14
15
|
self,
|
|
15
|
-
audio_file: str,
|
|
16
|
-
|
|
16
|
+
audio_file: Optional[str] = None,
|
|
17
|
+
url: Optional[str] = None,
|
|
18
|
+
target_language: str = "en",
|
|
17
19
|
source_language: Optional[str] = None,
|
|
18
20
|
wait_for_completion: bool = False,
|
|
19
21
|
timeout: int = 900
|
|
20
22
|
) -> Union[Job, TranslationResult]:
|
|
21
23
|
"""
|
|
22
|
-
Translate audio to another language
|
|
24
|
+
Translate speech from audio/video file to another language while preserving voice characteristics
|
|
23
25
|
|
|
24
26
|
Args:
|
|
25
|
-
audio_file: Path to audio file
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
audio_file: Path to audio/video file (required if no URL)
|
|
28
|
+
url: Direct media URL (required if no file)
|
|
29
|
+
target_language: Target language code (ISO 639-1, e.g., 'es' for Spanish)
|
|
30
|
+
source_language: Source language code (auto-detect if None)
|
|
28
31
|
wait_for_completion: Whether to wait for completion
|
|
29
32
|
timeout: Maximum time to wait
|
|
30
33
|
|
|
31
34
|
Returns:
|
|
32
35
|
Job object or translation result
|
|
33
36
|
"""
|
|
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
|
+
|
|
34
43
|
target_language = self._validate_language_code(target_language)
|
|
35
44
|
if source_language:
|
|
36
45
|
source_language = self._validate_language_code(source_language)
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
# Prepare request data
|
|
48
|
+
files = {}
|
|
39
49
|
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
|
+
|
|
40
57
|
if source_language:
|
|
41
58
|
data["source_language"] = source_language
|
|
42
59
|
|
|
@@ -44,7 +61,10 @@ class TranslationService(BaseService):
|
|
|
44
61
|
return self._async_translate_audio(files, data, wait_for_completion, timeout)
|
|
45
62
|
else:
|
|
46
63
|
response = self.client.request(
|
|
47
|
-
"POST",
|
|
64
|
+
"POST",
|
|
65
|
+
"/api/v1/translation/translate/speech", # FIXED: Use correct speech-to-speech endpoint
|
|
66
|
+
data=data,
|
|
67
|
+
files=files if files else None
|
|
48
68
|
)
|
|
49
69
|
job = Job.from_dict(response)
|
|
50
70
|
|
|
@@ -53,11 +73,35 @@ class TranslationService(BaseService):
|
|
|
53
73
|
return TranslationResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
54
74
|
|
|
55
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
|
+
)
|
|
56
97
|
|
|
57
98
|
async def _async_translate_audio(self, files, data, wait_for_completion, timeout):
|
|
58
99
|
"""Async version of translate_audio"""
|
|
59
100
|
response = await self.client.request(
|
|
60
|
-
"POST",
|
|
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
|
|
61
105
|
)
|
|
62
106
|
job = Job.from_dict(response)
|
|
63
107
|
|
|
@@ -79,3 +123,74 @@ class TranslationService(BaseService):
|
|
|
79
123
|
"""Async version of get_translation_job"""
|
|
80
124
|
response = await self.client.request("GET", f"/api/v1/translation/translations/{job_id}")
|
|
81
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
|
+
|
|
147
|
+
if self.async_mode:
|
|
148
|
+
return self._async_list_translation_jobs(params)
|
|
149
|
+
else:
|
|
150
|
+
response = self.client.request("GET", "/api/v1/translation/translations", params=params)
|
|
151
|
+
return [TranslationResult.from_dict(job_data) for job_data in response]
|
|
152
|
+
|
|
153
|
+
async def _async_list_translation_jobs(self, params: dict) -> list:
|
|
154
|
+
"""Async version of list_translation_jobs"""
|
|
155
|
+
response = await self.client.request("GET", "/api/v1/translation/translations", params=params)
|
|
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
|
+
"""
|
|
168
|
+
if self.async_mode:
|
|
169
|
+
return self._async_retry_translation(job_id)
|
|
170
|
+
else:
|
|
171
|
+
response = self.client.request("POST", f"/api/v1/translation/translations/{job_id}/retry")
|
|
172
|
+
return Job.from_dict(response)
|
|
173
|
+
|
|
174
|
+
async def _async_retry_translation(self, job_id: int) -> Job:
|
|
175
|
+
"""Async version of retry_translation"""
|
|
176
|
+
response = await self.client.request("POST", f"/api/v1/translation/translations/{job_id}/retry")
|
|
177
|
+
return Job.from_dict(response)
|
|
178
|
+
|
|
179
|
+
def delete_translation_job(self, job_id: int) -> dict:
|
|
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
|
+
"""
|
|
189
|
+
if self.async_mode:
|
|
190
|
+
return self._async_delete_translation_job(job_id)
|
|
191
|
+
else:
|
|
192
|
+
return self.client.request("DELETE", f"/api/v1/translation/translations/{job_id}")
|
|
193
|
+
|
|
194
|
+
async def _async_delete_translation_job(self, job_id: int) -> dict:
|
|
195
|
+
"""Async version of delete_translation_job"""
|
|
196
|
+
return await self.client.request("DELETE", f"/api/v1/translation/translations/{job_id}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: audiopod
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: Professional Audio Processing API Client for Python
|
|
5
5
|
Home-page: https://github.com/audiopod-ai/audiopod-python
|
|
6
6
|
Author: AudioPod AI
|
|
@@ -142,17 +142,25 @@ print(f"Transcript: {transcript.transcript}")
|
|
|
142
142
|
print(f"Detected {len(transcript.segments)} speakers")
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
####
|
|
145
|
+
#### Speech-to-Speech Translation
|
|
146
146
|
|
|
147
147
|
```python
|
|
148
|
-
# Translate
|
|
149
|
-
translation = client.translation.
|
|
148
|
+
# Translate speech while preserving voice characteristics
|
|
149
|
+
translation = client.translation.translate_speech(
|
|
150
150
|
audio_file="path/to/english_audio.wav",
|
|
151
151
|
target_language="es", # Spanish
|
|
152
|
+
source_language="en", # English (optional - auto-detect)
|
|
152
153
|
wait_for_completion=True
|
|
153
154
|
)
|
|
154
155
|
|
|
155
|
-
print(f"Translated audio URL: {translation.
|
|
156
|
+
print(f"Translated audio URL: {translation.translated_audio_url}")
|
|
157
|
+
|
|
158
|
+
# Or translate from URL
|
|
159
|
+
url_translation = client.translation.translate_speech(
|
|
160
|
+
url="https://example.com/audio.mp3",
|
|
161
|
+
target_language="fr", # French
|
|
162
|
+
wait_for_completion=True
|
|
163
|
+
)
|
|
156
164
|
```
|
|
157
165
|
|
|
158
166
|
### Async Support
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
audiopod/__init__.py,sha256=
|
|
1
|
+
audiopod/__init__.py,sha256=UsB5ET6nUy1Upx8wCiB17mMErdF3RvEHfAr51-pPPGQ,1790
|
|
2
2
|
audiopod/cli.py,sha256=ZYzAQ3UpoYuOEWivMwMneJUf2z8DGGYTx1Nb6yRfdVY,9339
|
|
3
3
|
audiopod/client.py,sha256=67oPSInSNssJpTR00ZuYSdk9lbx5KiRnDQw8UYKNVsA,11742
|
|
4
4
|
audiopod/config.py,sha256=fuGtbuES4tXdHwqQqoZa5izCH6nVfFRP06D8eK1Cg10,1683
|
|
5
5
|
audiopod/exceptions.py,sha256=c3Ym2tWyRE1kemVkXDaXFcfP3h6AokhKcUcCBImwGes,2386
|
|
6
|
-
audiopod/models.py,sha256=
|
|
6
|
+
audiopod/models.py,sha256=R70iMqKDZfLtTB9FQ7KrFLBi-bFA5-FrS-5eMOtfK1o,8517
|
|
7
7
|
audiopod/py.typed,sha256=ixa8YukDZ3kLo0WsFJRGohLMyHzbMur1ALmmASML2cs,64
|
|
8
8
|
audiopod/services/__init__.py,sha256=9Ycl9VVscwwY42joBCSL67v8DrITW2T2QyuaokbpehM,653
|
|
9
9
|
audiopod/services/base.py,sha256=mNbziYy2KsWWZrdHFlTl9pKLvoQUW-ZkkJuVWfCVP74,6731
|
|
@@ -14,11 +14,11 @@ audiopod/services/music.py,sha256=hDPjTSj-gAeEWBVB7PRPQrptMHJyQTz8e9p-p7yaRPI,20
|
|
|
14
14
|
audiopod/services/speaker.py,sha256=OPSOwArfrGXVzRgciS13n1QsCJSK1PB-Mz6VgwxuHAA,1866
|
|
15
15
|
audiopod/services/stem_extraction.py,sha256=3ibMFKFR25xKHpVs3WGMNriZ88sB5PriFNa_s2Bvon4,6026
|
|
16
16
|
audiopod/services/transcription.py,sha256=HyH6WpGWZsggYxIvt2dhB6_5UHaigk3XwXsVgarWzcE,7565
|
|
17
|
-
audiopod/services/translation.py,sha256=
|
|
17
|
+
audiopod/services/translation.py,sha256=oUU82c61CeAt13lzlWx8S-9xEgYlskwX8bLMbQw2Ni8,7396
|
|
18
18
|
audiopod/services/voice.py,sha256=_IHv3zU3k184kfijxr1QRBenrIpmhhPOBS96DddZ8yw,13456
|
|
19
|
-
audiopod-1.1.
|
|
20
|
-
audiopod-1.1.
|
|
21
|
-
audiopod-1.1.
|
|
22
|
-
audiopod-1.1.
|
|
23
|
-
audiopod-1.1.
|
|
24
|
-
audiopod-1.1.
|
|
19
|
+
audiopod-1.1.1.dist-info/licenses/LICENSE,sha256=hqEjnOaGNbnLSBxbtbC7WQVREU2vQI8FmwecCiZlMfA,1068
|
|
20
|
+
audiopod-1.1.1.dist-info/METADATA,sha256=UG_csntfzscrZTjy-2v-lCTlS7-VSEM6WEwUw26hSYc,11217
|
|
21
|
+
audiopod-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
22
|
+
audiopod-1.1.1.dist-info/entry_points.txt,sha256=uLcNDzXuOXnJAz9j91TDGayVjjZ7-ZiHBGDydqNUErU,47
|
|
23
|
+
audiopod-1.1.1.dist-info/top_level.txt,sha256=M6yyOFFNpLdH4i1AMRqJZLRIgfpg1NvrQVmnPd8A6N8,9
|
|
24
|
+
audiopod-1.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|