audiopod 1.1.0__tar.gz → 1.1.1__tar.gz
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-1.1.0 → audiopod-1.1.1}/CHANGELOG.md +79 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/PKG-INFO +13 -5
- {audiopod-1.1.0 → audiopod-1.1.1}/README.md +12 -4
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/__init__.py +1 -1
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/models.py +21 -6
- audiopod-1.1.1/audiopod/services/translation.py +196 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/examples/basic_usage.py +42 -6
- {audiopod-1.1.0 → audiopod-1.1.1}/pyproject.toml +1 -1
- audiopod-1.1.0/audiopod/services/translation.py +0 -81
- {audiopod-1.1.0 → audiopod-1.1.1}/LICENSE +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/MANIFEST.in +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/cli.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/client.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/config.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/exceptions.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/py.typed +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/services/__init__.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/services/base.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/services/credits.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/services/denoiser.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/services/karaoke.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/services/music.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/services/speaker.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/services/stem_extraction.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/services/transcription.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod/services/voice.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/audiopod.egg-info/SOURCES.txt +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/examples/README.md +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/requirements.txt +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/setup.cfg +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/setup.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/tests/test_end_to_end_integration.py +0 -0
- {audiopod-1.1.0 → audiopod-1.1.1}/tests/test_sdk_api_compatibility.py +0 -0
|
@@ -5,6 +5,85 @@ All notable changes to the AudioPod Python SDK will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.1] - 2024-12-15
|
|
9
|
+
|
|
10
|
+
### 🔧 Translation Service Fixes
|
|
11
|
+
|
|
12
|
+
This release fixes the translation service to use the proper speech-to-speech translation endpoint and adds enhanced functionality.
|
|
13
|
+
|
|
14
|
+
### ✨ Added
|
|
15
|
+
|
|
16
|
+
- **Speech-to-Speech Translation**: Now uses the correct `/api/v1/translation/translate/speech` endpoint
|
|
17
|
+
- Preserves original speaker voice characteristics during translation
|
|
18
|
+
- Supports both audio and video file translation
|
|
19
|
+
- Maintains speaker separation in multi-speaker content
|
|
20
|
+
|
|
21
|
+
- **URL-Based Translation**: Support for translating audio/video from URLs
|
|
22
|
+
- Direct media URL support (YouTube, audio links, etc.)
|
|
23
|
+
- No need to download files locally first
|
|
24
|
+
|
|
25
|
+
- **Enhanced Translation Job Management**:
|
|
26
|
+
- `list_translation_jobs()` - List translation history with pagination
|
|
27
|
+
- `retry_translation()` - Retry failed translation jobs
|
|
28
|
+
- `delete_translation_job()` - Delete translation jobs
|
|
29
|
+
- `translate_speech()` - Alias method for clearer API
|
|
30
|
+
|
|
31
|
+
### 🔧 Fixed
|
|
32
|
+
|
|
33
|
+
- **Translation Endpoint**: Changed from generic `/translate` to speech-specific `/translate/speech`
|
|
34
|
+
- **API Schema Alignment**: Request and response formats now match the actual API
|
|
35
|
+
- **Response Model**: Updated `TranslationResult` to include all API response fields:
|
|
36
|
+
- `translated_audio_url` - Direct URL to translated audio
|
|
37
|
+
- `video_output_url` - Translated video output (when applicable)
|
|
38
|
+
- `transcript_urls` - Transcript files in multiple formats
|
|
39
|
+
- `display_name` - Original file display name
|
|
40
|
+
- `is_video` - Whether the input was a video file
|
|
41
|
+
|
|
42
|
+
### 🏗️ Improved
|
|
43
|
+
|
|
44
|
+
- **Better Error Handling**: Enhanced validation for file vs URL inputs
|
|
45
|
+
- **Backward Compatibility**: Maintained `audio_output_url` property for existing code
|
|
46
|
+
- **Enhanced Examples**: Updated documentation and examples to show new features
|
|
47
|
+
- **Type Safety**: Improved type hints and validation
|
|
48
|
+
|
|
49
|
+
### 📚 Documentation
|
|
50
|
+
|
|
51
|
+
- **Updated Examples**: `basic_usage.py` now demonstrates speech-to-speech translation
|
|
52
|
+
- **README Updates**: Corrected API usage examples with proper endpoint usage
|
|
53
|
+
- **Method Documentation**: Enhanced docstrings with accurate parameter descriptions
|
|
54
|
+
|
|
55
|
+
### 🚀 Usage Examples
|
|
56
|
+
|
|
57
|
+
#### Fixed Speech Translation
|
|
58
|
+
```python
|
|
59
|
+
# Speech-to-speech translation (preserves voice characteristics)
|
|
60
|
+
translation = client.translation.translate_speech(
|
|
61
|
+
audio_file="english_speech.wav",
|
|
62
|
+
target_language="es", # Spanish
|
|
63
|
+
source_language="en", # Optional - auto-detect
|
|
64
|
+
wait_for_completion=True
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# URL-based translation
|
|
68
|
+
url_translation = client.translation.translate_speech(
|
|
69
|
+
url="https://example.com/audio.mp3",
|
|
70
|
+
target_language="fr", # French
|
|
71
|
+
wait_for_completion=True
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Job management
|
|
75
|
+
jobs = client.translation.list_translation_jobs(limit=10)
|
|
76
|
+
retry_job = client.translation.retry_translation(failed_job_id)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 🔄 Migration Notes
|
|
80
|
+
|
|
81
|
+
- **No Breaking Changes**: Existing `translate_audio()` method continues to work
|
|
82
|
+
- **Enhanced Functionality**: Now uses proper speech-to-speech endpoint automatically
|
|
83
|
+
- **New Properties**: Additional response fields available in `TranslationResult`
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
8
87
|
## [1.1.0] - 2024-01-15
|
|
9
88
|
|
|
10
89
|
### 🎉 Major API Compatibility Update
|
|
@@ -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
|
|
@@ -90,17 +90,25 @@ print(f"Transcript: {transcript.transcript}")
|
|
|
90
90
|
print(f"Detected {len(transcript.segments)} speakers")
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
####
|
|
93
|
+
#### Speech-to-Speech Translation
|
|
94
94
|
|
|
95
95
|
```python
|
|
96
|
-
# Translate
|
|
97
|
-
translation = client.translation.
|
|
96
|
+
# Translate speech while preserving voice characteristics
|
|
97
|
+
translation = client.translation.translate_speech(
|
|
98
98
|
audio_file="path/to/english_audio.wav",
|
|
99
99
|
target_language="es", # Spanish
|
|
100
|
+
source_language="en", # English (optional - auto-detect)
|
|
100
101
|
wait_for_completion=True
|
|
101
102
|
)
|
|
102
103
|
|
|
103
|
-
print(f"Translated audio URL: {translation.
|
|
104
|
+
print(f"Translated audio URL: {translation.translated_audio_url}")
|
|
105
|
+
|
|
106
|
+
# Or translate from URL
|
|
107
|
+
url_translation = client.translation.translate_speech(
|
|
108
|
+
url="https://example.com/audio.mp3",
|
|
109
|
+
target_language="fr", # French
|
|
110
|
+
wait_for_completion=True
|
|
111
|
+
)
|
|
104
112
|
```
|
|
105
113
|
|
|
106
114
|
### Async Support
|
|
@@ -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
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Translation Service - Speech-to-speech translation operations
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Union
|
|
6
|
+
from .base import BaseService
|
|
7
|
+
from ..models import Job, TranslationResult
|
|
8
|
+
from ..exceptions import ValidationError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TranslationService(BaseService):
|
|
12
|
+
"""Service for speech-to-speech translation operations"""
|
|
13
|
+
|
|
14
|
+
def translate_audio(
|
|
15
|
+
self,
|
|
16
|
+
audio_file: Optional[str] = None,
|
|
17
|
+
url: Optional[str] = None,
|
|
18
|
+
target_language: str = "en",
|
|
19
|
+
source_language: Optional[str] = None,
|
|
20
|
+
wait_for_completion: bool = False,
|
|
21
|
+
timeout: int = 900
|
|
22
|
+
) -> Union[Job, TranslationResult]:
|
|
23
|
+
"""
|
|
24
|
+
Translate speech from audio/video file to another language while preserving voice characteristics
|
|
25
|
+
|
|
26
|
+
Args:
|
|
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)
|
|
31
|
+
wait_for_completion: Whether to wait for completion
|
|
32
|
+
timeout: Maximum time to wait
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Job object or translation result
|
|
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
|
+
|
|
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
|
+
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
|
+
if source_language:
|
|
58
|
+
data["source_language"] = source_language
|
|
59
|
+
|
|
60
|
+
if self.async_mode:
|
|
61
|
+
return self._async_translate_audio(files, data, wait_for_completion, timeout)
|
|
62
|
+
else:
|
|
63
|
+
response = self.client.request(
|
|
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
|
|
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
|
+
|
|
108
|
+
if wait_for_completion:
|
|
109
|
+
completed_job = await self._async_wait_for_completion(job.id, timeout)
|
|
110
|
+
return TranslationResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
111
|
+
|
|
112
|
+
return job
|
|
113
|
+
|
|
114
|
+
def get_translation_job(self, job_id: int) -> TranslationResult:
|
|
115
|
+
"""Get translation job details"""
|
|
116
|
+
if self.async_mode:
|
|
117
|
+
return self._async_get_translation_job(job_id)
|
|
118
|
+
else:
|
|
119
|
+
response = self.client.request("GET", f"/api/v1/translation/translations/{job_id}")
|
|
120
|
+
return TranslationResult.from_dict(response)
|
|
121
|
+
|
|
122
|
+
async def _async_get_translation_job(self, job_id: int) -> TranslationResult:
|
|
123
|
+
"""Async version of get_translation_job"""
|
|
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
|
+
|
|
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}")
|
|
@@ -227,8 +227,8 @@ def transcription_example(client):
|
|
|
227
227
|
|
|
228
228
|
|
|
229
229
|
def translation_example(client):
|
|
230
|
-
"""Demonstrate
|
|
231
|
-
print("\n🌍 Translation Example")
|
|
230
|
+
"""Demonstrate speech-to-speech translation"""
|
|
231
|
+
print("\n🌍 Speech Translation Example")
|
|
232
232
|
print("=" * 50)
|
|
233
233
|
|
|
234
234
|
audio_file = "examples/english_speech.wav"
|
|
@@ -236,23 +236,59 @@ def translation_example(client):
|
|
|
236
236
|
if not Path(audio_file).exists():
|
|
237
237
|
print(f"⚠️ Audio file not found: {audio_file}")
|
|
238
238
|
print(" Please provide an English audio file to test translation")
|
|
239
|
+
print(" Alternatively, you can use a URL - see example below")
|
|
240
|
+
|
|
241
|
+
# Show URL-based translation example
|
|
242
|
+
try:
|
|
243
|
+
print("\n🔄 Trying URL-based translation example...")
|
|
244
|
+
|
|
245
|
+
translation_job = client.translation.translate_speech(
|
|
246
|
+
url="https://example.com/sample_english.wav", # Example URL
|
|
247
|
+
target_language="es", # Spanish
|
|
248
|
+
source_language="en", # English
|
|
249
|
+
wait_for_completion=False # Don't wait for this example
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
print(f"📋 URL translation job started: {translation_job.id}")
|
|
253
|
+
print(" Note: This is just an example - replace with a real audio URL")
|
|
254
|
+
|
|
255
|
+
except AudioPodError as e:
|
|
256
|
+
print(f" Expected error (example URL): {e.message}")
|
|
257
|
+
|
|
239
258
|
return
|
|
240
259
|
|
|
241
260
|
try:
|
|
242
|
-
print(f"🔄 Translating
|
|
261
|
+
print(f"🔄 Translating speech to Spanish (preserving voice characteristics)...")
|
|
243
262
|
|
|
244
263
|
translation_job = client.translation.translate_audio(
|
|
245
264
|
audio_file=audio_file,
|
|
246
265
|
target_language="es", # Spanish
|
|
247
|
-
source_language="en", # English
|
|
266
|
+
source_language="en", # English (optional - auto-detect if not specified)
|
|
248
267
|
wait_for_completion=True,
|
|
249
268
|
timeout=900 # 15 minutes for translation
|
|
250
269
|
)
|
|
251
270
|
|
|
252
|
-
print("✅
|
|
253
|
-
print(f"🎵 Translated audio: {translation_job.
|
|
271
|
+
print("✅ Speech translation completed!")
|
|
272
|
+
print(f"🎵 Translated audio: {translation_job.translated_audio_url}")
|
|
273
|
+
print(f"🎬 Video output: {translation_job.video_output_url}")
|
|
254
274
|
print(f"📝 Source language: {translation_job.source_language}")
|
|
255
275
|
print(f"🎯 Target language: {translation_job.target_language}")
|
|
276
|
+
print(f"📄 Display name: {translation_job.display_name}")
|
|
277
|
+
|
|
278
|
+
if translation_job.transcript_urls:
|
|
279
|
+
print("📋 Transcript URLs:")
|
|
280
|
+
for format_type, url in translation_job.transcript_urls.items():
|
|
281
|
+
print(f" {format_type}: {url}")
|
|
282
|
+
|
|
283
|
+
# Demonstrate additional translation methods
|
|
284
|
+
print("\n🔄 Demonstrating job management...")
|
|
285
|
+
|
|
286
|
+
# List recent translation jobs
|
|
287
|
+
jobs = client.translation.list_translation_jobs(limit=5)
|
|
288
|
+
print(f"📋 Found {len(jobs)} recent translation jobs")
|
|
289
|
+
|
|
290
|
+
# Show job retry capability (don't actually retry)
|
|
291
|
+
print(f"🔄 Job retry available for failed jobs: translation.retry_translation({translation_job.job.id})")
|
|
256
292
|
|
|
257
293
|
except AudioPodError as e:
|
|
258
294
|
print(f"❌ Translation error: {e.message}")
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Translation Service - Audio/video translation operations
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from typing import Optional, Union
|
|
6
|
-
from .base import BaseService
|
|
7
|
-
from ..models import Job, TranslationResult
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class TranslationService(BaseService):
|
|
11
|
-
"""Service for audio and video translation operations"""
|
|
12
|
-
|
|
13
|
-
def translate_audio(
|
|
14
|
-
self,
|
|
15
|
-
audio_file: str,
|
|
16
|
-
target_language: str,
|
|
17
|
-
source_language: Optional[str] = None,
|
|
18
|
-
wait_for_completion: bool = False,
|
|
19
|
-
timeout: int = 900
|
|
20
|
-
) -> Union[Job, TranslationResult]:
|
|
21
|
-
"""
|
|
22
|
-
Translate audio to another language
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
audio_file: Path to audio file
|
|
26
|
-
target_language: Target language code
|
|
27
|
-
source_language: Source language (auto-detect if None)
|
|
28
|
-
wait_for_completion: Whether to wait for completion
|
|
29
|
-
timeout: Maximum time to wait
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
Job object or translation result
|
|
33
|
-
"""
|
|
34
|
-
target_language = self._validate_language_code(target_language)
|
|
35
|
-
if source_language:
|
|
36
|
-
source_language = self._validate_language_code(source_language)
|
|
37
|
-
|
|
38
|
-
files = self._prepare_file_upload(audio_file, "file")
|
|
39
|
-
data = {"target_language": target_language}
|
|
40
|
-
if source_language:
|
|
41
|
-
data["source_language"] = source_language
|
|
42
|
-
|
|
43
|
-
if self.async_mode:
|
|
44
|
-
return self._async_translate_audio(files, data, wait_for_completion, timeout)
|
|
45
|
-
else:
|
|
46
|
-
response = self.client.request(
|
|
47
|
-
"POST", "/api/v1/translation/translate", data=data, files=files
|
|
48
|
-
)
|
|
49
|
-
job = Job.from_dict(response)
|
|
50
|
-
|
|
51
|
-
if wait_for_completion:
|
|
52
|
-
completed_job = self._wait_for_completion(job.id, timeout)
|
|
53
|
-
return TranslationResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
54
|
-
|
|
55
|
-
return job
|
|
56
|
-
|
|
57
|
-
async def _async_translate_audio(self, files, data, wait_for_completion, timeout):
|
|
58
|
-
"""Async version of translate_audio"""
|
|
59
|
-
response = await self.client.request(
|
|
60
|
-
"POST", "/api/v1/translation/translate", data=data, files=files
|
|
61
|
-
)
|
|
62
|
-
job = Job.from_dict(response)
|
|
63
|
-
|
|
64
|
-
if wait_for_completion:
|
|
65
|
-
completed_job = await self._async_wait_for_completion(job.id, timeout)
|
|
66
|
-
return TranslationResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
67
|
-
|
|
68
|
-
return job
|
|
69
|
-
|
|
70
|
-
def get_translation_job(self, job_id: int) -> TranslationResult:
|
|
71
|
-
"""Get translation job details"""
|
|
72
|
-
if self.async_mode:
|
|
73
|
-
return self._async_get_translation_job(job_id)
|
|
74
|
-
else:
|
|
75
|
-
response = self.client.request("GET", f"/api/v1/translation/translations/{job_id}")
|
|
76
|
-
return TranslationResult.from_dict(response)
|
|
77
|
-
|
|
78
|
-
async def _async_get_translation_job(self, job_id: int) -> TranslationResult:
|
|
79
|
-
"""Async version of get_translation_job"""
|
|
80
|
-
response = await self.client.request("GET", f"/api/v1/translation/translations/{job_id}")
|
|
81
|
-
return TranslationResult.from_dict(response)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|