audiopod 1.4.0__tar.gz → 1.5.0__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.
Files changed (26) hide show
  1. {audiopod-1.4.0 → audiopod-1.5.0}/PKG-INFO +1 -1
  2. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/__init__.py +5 -1
  3. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/client.py +6 -0
  4. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/services/__init__.py +2 -0
  5. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/services/denoiser.py +9 -4
  6. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/services/stem_extraction.py +130 -11
  7. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/services/transcription.py +23 -0
  8. audiopod-1.5.0/audiopod/services/video.py +329 -0
  9. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod.egg-info/PKG-INFO +1 -1
  10. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod.egg-info/SOURCES.txt +1 -0
  11. {audiopod-1.4.0 → audiopod-1.5.0}/pyproject.toml +1 -1
  12. {audiopod-1.4.0 → audiopod-1.5.0}/LICENSE +0 -0
  13. {audiopod-1.4.0 → audiopod-1.5.0}/README.md +0 -0
  14. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/config.py +0 -0
  15. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/exceptions.py +0 -0
  16. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/services/base.py +0 -0
  17. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/services/credits.py +0 -0
  18. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/services/music.py +0 -0
  19. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/services/speaker.py +0 -0
  20. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/services/translation.py +0 -0
  21. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/services/voice.py +0 -0
  22. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod/services/wallet.py +0 -0
  23. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod.egg-info/dependency_links.txt +0 -0
  24. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod.egg-info/requires.txt +0 -0
  25. {audiopod-1.4.0 → audiopod-1.5.0}/audiopod.egg-info/top_level.txt +0 -0
  26. {audiopod-1.4.0 → audiopod-1.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: audiopod
3
- Version: 1.4.0
3
+ Version: 1.5.0
4
4
  Summary: AudioPod SDK for Python - Professional Audio Processing powered by AI
5
5
  Author-email: AudioPod AI <support@audiopod.ai>
6
6
  License: MIT
@@ -3,7 +3,7 @@ AudioPod SDK for Python
3
3
  Professional Audio Processing powered by AI
4
4
  """
5
5
 
6
- __version__ = "1.3.0"
6
+ __version__ = "1.5.0"
7
7
 
8
8
  from .client import Client, AsyncClient
9
9
  from .exceptions import (
@@ -15,9 +15,13 @@ from .exceptions import (
15
15
  InsufficientBalanceError,
16
16
  )
17
17
 
18
+ # Alias for consistency with documentation and Node.js SDK
19
+ AudioPod = Client
20
+
18
21
  __all__ = [
19
22
  "Client",
20
23
  "AsyncClient",
24
+ "AudioPod", # Alias for Client
21
25
  "AudioPodError",
22
26
  "AuthenticationError",
23
27
  "APIError",
@@ -24,6 +24,7 @@ from .services import (
24
24
  CreditService,
25
25
  StemExtractionService,
26
26
  WalletService,
27
+ VideoService,
27
28
  )
28
29
 
29
30
  logger = logging.getLogger(__name__)
@@ -75,6 +76,7 @@ class BaseClient:
75
76
  def _get_headers(self) -> Dict[str, str]:
76
77
  return {
77
78
  "Authorization": f"Bearer {self.api_key}",
79
+ "X-API-Key": self.api_key,
78
80
  "Content-Type": "application/json",
79
81
  "User-Agent": f"audiopod-python/{self.config.version}",
80
82
  "Accept": "application/json",
@@ -155,7 +157,9 @@ class Client(BaseClient):
155
157
  self.denoiser = DenoiserService(self)
156
158
  self.credits = CreditService(self)
157
159
  self.stem_extraction = StemExtractionService(self)
160
+ self.stems = self.stem_extraction # Alias for consistency with Node.js SDK
158
161
  self.wallet = WalletService(self)
162
+ self.video = VideoService(self)
159
163
 
160
164
  def request(
161
165
  self,
@@ -232,7 +236,9 @@ class AsyncClient(BaseClient):
232
236
  self.denoiser = DenoiserService(self, async_mode=True)
233
237
  self.credits = CreditService(self, async_mode=True)
234
238
  self.stem_extraction = StemExtractionService(self, async_mode=True)
239
+ self.stems = self.stem_extraction # Alias for consistency with Node.js SDK
235
240
  self.wallet = WalletService(self, async_mode=True)
241
+ self.video = VideoService(self, async_mode=True)
236
242
 
237
243
  @property
238
244
  def session(self) -> aiohttp.ClientSession:
@@ -12,6 +12,7 @@ from .denoiser import DenoiserService
12
12
  from .credits import CreditService
13
13
  from .stem_extraction import StemExtractionService
14
14
  from .wallet import WalletService
15
+ from .video import VideoService
15
16
 
16
17
  __all__ = [
17
18
  "BaseService",
@@ -24,5 +25,6 @@ __all__ = [
24
25
  "CreditService",
25
26
  "StemExtractionService",
26
27
  "WalletService",
28
+ "VideoService",
27
29
  ]
28
30
 
@@ -17,25 +17,30 @@ class DenoiserService(BaseService):
17
17
 
18
18
  def denoise(
19
19
  self,
20
- audio_file: Optional[str] = None,
20
+ file: Optional[str] = None,
21
21
  url: Optional[str] = None,
22
22
  mode: str = "balanced",
23
- wait_for_completion: bool = False,
23
+ wait_for_completion: bool = True,
24
24
  timeout: int = 300,
25
+ # Legacy parameter name
26
+ audio_file: Optional[str] = None,
25
27
  ) -> Dict[str, Any]:
26
28
  """
27
29
  Remove noise from audio.
28
30
 
29
31
  Args:
30
- audio_file: Path to local audio file
32
+ file: Path to local audio file
31
33
  url: URL of audio file
32
34
  mode: Denoise mode ("balanced", "studio", or "ultra")
33
- wait_for_completion: Wait for completion
35
+ wait_for_completion: Wait for completion (default: True)
34
36
  timeout: Max wait time in seconds
35
37
 
36
38
  Returns:
37
39
  Job dict with denoised audio URL when completed
38
40
  """
41
+ # Support both 'file' and legacy 'audio_file' parameter names
42
+ audio_file = file or audio_file
43
+
39
44
  data = {"mode": mode}
40
45
  if url:
41
46
  data["url"] = url
@@ -2,11 +2,16 @@
2
2
  Stem Extraction Service - Audio stem separation
3
3
  """
4
4
 
5
- from typing import List, Optional, Dict, Any
5
+ from typing import List, Optional, Dict, Any, Literal
6
6
  from .base import BaseService
7
7
  from ..exceptions import ValidationError
8
8
 
9
9
 
10
+ # Valid separation modes for the new API
11
+ StemMode = Literal["single", "two", "four", "six", "producer", "studio", "mastering"]
12
+ SingleStem = Literal["vocals", "drums", "bass", "guitar", "piano", "other", "instrumental"]
13
+
14
+
10
15
  class StemExtractionService(BaseService):
11
16
  """
12
17
  Service for audio stem separation.
@@ -17,19 +22,131 @@ class StemExtractionService(BaseService):
17
22
 
18
23
  client = Client()
19
24
 
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
+ # Simple mode-based extraction (recommended)
26
+ result = client.stem_extraction.separate(
27
+ url="https://youtube.com/watch?v=VIDEO_ID",
28
+ mode="six"
29
+ )
30
+ for stem, url in result["download_urls"].items():
31
+ print(f"{stem}: {url}")
32
+
33
+ # Or extract only vocals
34
+ result = client.stem_extraction.separate(
35
+ file="song.mp3",
36
+ mode="single",
37
+ stem="vocals"
25
38
  )
26
-
27
- # Download stems
28
- for stem_name, url in job["download_urls"].items():
29
- print(f"{stem_name}: {url}")
30
39
  ```
31
40
  """
32
41
 
42
+ def separate(
43
+ self,
44
+ file: Optional[str] = None,
45
+ url: Optional[str] = None,
46
+ mode: StemMode = "four",
47
+ stem: Optional[SingleStem] = None,
48
+ wait_for_completion: bool = True,
49
+ timeout: int = 900,
50
+ ) -> Dict[str, Any]:
51
+ """
52
+ Separate audio into stems using simple mode selection.
53
+
54
+ Args:
55
+ file: Path to local audio file
56
+ url: URL of audio/video (YouTube, SoundCloud, direct link)
57
+ mode: Separation mode:
58
+ - "single": Extract one stem (specify stem param)
59
+ - "two": Vocals + Instrumental
60
+ - "four": Vocals, Drums, Bass, Other (default)
61
+ - "six": Vocals, Drums, Bass, Guitar, Piano, Other
62
+ - "producer": 8 stems with kick, snare, hihat
63
+ - "studio": 12 stems for professional mixing
64
+ - "mastering": 16 stems maximum detail
65
+ stem: For mode="single", which stem to extract
66
+ wait_for_completion: Wait for job to complete (default: True)
67
+ timeout: Max wait time in seconds
68
+
69
+ Returns:
70
+ Job dict with id, status, download_urls (when completed)
71
+ """
72
+ if not file and not url:
73
+ raise ValidationError("Provide file or url")
74
+
75
+ if file and url:
76
+ raise ValidationError("Provide file or url, not both")
77
+
78
+ if mode == "single" and not stem:
79
+ raise ValidationError(
80
+ "stem parameter required for mode='single'. "
81
+ "Options: vocals, drums, bass, guitar, piano, other, instrumental"
82
+ )
83
+
84
+ data = {"mode": mode}
85
+ if stem:
86
+ data["stem"] = stem
87
+ if url:
88
+ data["url"] = url
89
+
90
+ files = self._prepare_file_upload(file, "file") if file else None
91
+
92
+ if self.async_mode:
93
+ return self._async_separate(data, files, wait_for_completion, timeout)
94
+
95
+ response = self.client.request(
96
+ "POST", "/api/v1/stem-extraction/api/extract", data=data, files=files
97
+ )
98
+
99
+ if wait_for_completion:
100
+ return self._wait_for_stem_job(response["id"], timeout)
101
+
102
+ return response
103
+
104
+ async def _async_separate(
105
+ self,
106
+ data: Dict[str, Any],
107
+ files: Optional[Dict[str, Any]],
108
+ wait_for_completion: bool,
109
+ timeout: int,
110
+ ) -> Dict[str, Any]:
111
+ response = await self.client.request(
112
+ "POST", "/api/v1/stem-extraction/api/extract", data=data, files=files
113
+ )
114
+ if wait_for_completion:
115
+ return await self._async_wait_for_stem_job(response["id"], timeout)
116
+ return response
117
+
118
+ def extract(
119
+ self,
120
+ file: Optional[str] = None,
121
+ url: Optional[str] = None,
122
+ mode: StemMode = "four",
123
+ stem: Optional[SingleStem] = None,
124
+ ) -> Dict[str, Any]:
125
+ """
126
+ Submit stem extraction job (returns immediately without waiting).
127
+
128
+ Use wait_for_completion() to poll for results.
129
+ """
130
+ return self.separate(file=file, url=url, mode=mode, stem=stem, wait_for_completion=False)
131
+
132
+ def wait_for_completion(self, job_id: int, timeout: int = 900) -> Dict[str, Any]:
133
+ """Wait for stem extraction job to complete."""
134
+ return self._wait_for_stem_job(job_id, timeout)
135
+
136
+ def status(self, job_id: int) -> Dict[str, Any]:
137
+ """Get stem extraction job status (alias for get_job)."""
138
+ return self.get_job(job_id)
139
+
140
+ def modes(self) -> Dict[str, Any]:
141
+ """Get available separation modes."""
142
+ if self.async_mode:
143
+ return self._async_modes()
144
+ return self.client.request("GET", "/api/v1/stem-extraction/modes")
145
+
146
+ async def _async_modes(self) -> Dict[str, Any]:
147
+ return await self.client.request("GET", "/api/v1/stem-extraction/modes")
148
+
149
+ # Legacy method - kept for backward compatibility
33
150
  def extract_stems(
34
151
  self,
35
152
  audio_file: Optional[str] = None,
@@ -41,7 +158,9 @@ class StemExtractionService(BaseService):
41
158
  timeout: int = 900,
42
159
  ) -> Dict[str, Any]:
43
160
  """
44
- Extract stems from audio.
161
+ Extract stems from audio (legacy method).
162
+
163
+ For new code, use separate() instead which uses the simpler mode-based API.
45
164
 
46
165
  Args:
47
166
  audio_file: Path to local audio file
@@ -16,6 +16,25 @@ from .base import BaseService
16
16
  class TranscriptionService(BaseService):
17
17
  """Service for speech-to-text transcription."""
18
18
 
19
+ def create(
20
+ self,
21
+ 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
+ """Alias for transcribe() - matches Node.js SDK."""
29
+ return self.transcribe(
30
+ audio_file=file,
31
+ url=url,
32
+ language=language,
33
+ speaker_diarization=speaker_diarization,
34
+ wait_for_completion=wait_for_completion,
35
+ timeout=timeout,
36
+ )
37
+
19
38
  def transcribe(
20
39
  self,
21
40
  audio_file: Optional[str] = None,
@@ -148,6 +167,10 @@ class TranscriptionService(BaseService):
148
167
  "GET", f"/api/v1/transcription/jobs/{job_id}/transcript", params={"format": format}
149
168
  )
150
169
 
170
+ def wait_for_completion(self, job_id: int, timeout: int = 600) -> Dict[str, Any]:
171
+ """Wait for transcription job completion (matches Node.js SDK)."""
172
+ return self._wait_for_transcription(job_id, timeout)
173
+
151
174
  def _wait_for_transcription(self, job_id: int, timeout: int) -> Dict[str, Any]:
152
175
  """Wait for transcription job completion."""
153
176
  import time
@@ -0,0 +1,329 @@
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")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: audiopod
3
- Version: 1.4.0
3
+ Version: 1.5.0
4
4
  Summary: AudioPod SDK for Python - Professional Audio Processing powered by AI
5
5
  Author-email: AudioPod AI <support@audiopod.ai>
6
6
  License: MIT
@@ -19,5 +19,6 @@ audiopod/services/speaker.py
19
19
  audiopod/services/stem_extraction.py
20
20
  audiopod/services/transcription.py
21
21
  audiopod/services/translation.py
22
+ audiopod/services/video.py
22
23
  audiopod/services/voice.py
23
24
  audiopod/services/wallet.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "audiopod"
7
- version = "1.4.0"
7
+ version = "1.5.0"
8
8
  description = "AudioPod SDK for Python - Professional Audio Processing powered by AI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
File without changes
File without changes
File without changes
File without changes