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/exceptions.py
CHANGED
|
@@ -1,51 +1,29 @@
|
|
|
1
1
|
"""
|
|
2
|
-
AudioPod
|
|
2
|
+
AudioPod SDK Exceptions
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import Optional, Dict, Any
|
|
6
|
-
|
|
7
5
|
|
|
8
6
|
class AudioPodError(Exception):
|
|
9
|
-
"""Base exception for AudioPod
|
|
10
|
-
|
|
11
|
-
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
12
|
-
super().__init__(message)
|
|
13
|
-
self.message = message
|
|
14
|
-
self.details = details or {}
|
|
15
|
-
|
|
16
|
-
def __str__(self) -> str:
|
|
17
|
-
return self.message
|
|
7
|
+
"""Base exception for AudioPod SDK"""
|
|
8
|
+
pass
|
|
18
9
|
|
|
19
10
|
|
|
20
11
|
class AuthenticationError(AudioPodError):
|
|
21
|
-
"""Raised when
|
|
12
|
+
"""Raised when authentication fails"""
|
|
22
13
|
pass
|
|
23
14
|
|
|
24
15
|
|
|
25
16
|
class APIError(AudioPodError):
|
|
26
|
-
"""Raised when API returns an error
|
|
17
|
+
"""Raised when API returns an error"""
|
|
27
18
|
|
|
28
|
-
def __init__(
|
|
29
|
-
|
|
30
|
-
message: str,
|
|
31
|
-
status_code: Optional[int] = None,
|
|
32
|
-
details: Optional[Dict[str, Any]] = None
|
|
33
|
-
):
|
|
34
|
-
super().__init__(message, details)
|
|
19
|
+
def __init__(self, message: str, status_code: int = None):
|
|
20
|
+
super().__init__(message)
|
|
35
21
|
self.status_code = status_code
|
|
36
22
|
|
|
37
23
|
|
|
38
|
-
class RateLimitError(
|
|
39
|
-
"""Raised when
|
|
40
|
-
|
|
41
|
-
def __init__(
|
|
42
|
-
self,
|
|
43
|
-
message: str = "Rate limit exceeded",
|
|
44
|
-
retry_after: Optional[int] = None,
|
|
45
|
-
details: Optional[Dict[str, Any]] = None
|
|
46
|
-
):
|
|
47
|
-
super().__init__(message, status_code=429, details=details)
|
|
48
|
-
self.retry_after = retry_after
|
|
24
|
+
class RateLimitError(AudioPodError):
|
|
25
|
+
"""Raised when rate limit is exceeded"""
|
|
26
|
+
pass
|
|
49
27
|
|
|
50
28
|
|
|
51
29
|
class ValidationError(AudioPodError):
|
|
@@ -53,44 +31,11 @@ class ValidationError(AudioPodError):
|
|
|
53
31
|
pass
|
|
54
32
|
|
|
55
33
|
|
|
56
|
-
class
|
|
57
|
-
"""Raised when
|
|
34
|
+
class InsufficientBalanceError(AudioPodError):
|
|
35
|
+
"""Raised when wallet balance is insufficient"""
|
|
58
36
|
|
|
59
|
-
def __init__(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
details: Optional[Dict[str, Any]] = None
|
|
64
|
-
):
|
|
65
|
-
super().__init__(message, details=details)
|
|
66
|
-
self.job_id = job_id
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
class FileError(AudioPodError):
|
|
70
|
-
"""Raised when file operations fail"""
|
|
71
|
-
pass
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class NetworkError(AudioPodError):
|
|
75
|
-
"""Raised when network operations fail"""
|
|
76
|
-
pass
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
class TimeoutError(AudioPodError):
|
|
80
|
-
"""Raised when operations timeout"""
|
|
81
|
-
pass
|
|
82
|
-
|
|
37
|
+
def __init__(self, message: str, required_cents: int = None, available_cents: int = None):
|
|
38
|
+
super().__init__(message)
|
|
39
|
+
self.required_cents = required_cents
|
|
40
|
+
self.available_cents = available_cents
|
|
83
41
|
|
|
84
|
-
class InsufficientCreditsError(APIError):
|
|
85
|
-
"""Raised when user has insufficient credits"""
|
|
86
|
-
|
|
87
|
-
def __init__(
|
|
88
|
-
self,
|
|
89
|
-
message: str = "Insufficient credits",
|
|
90
|
-
credits_needed: Optional[int] = None,
|
|
91
|
-
credits_available: Optional[int] = None,
|
|
92
|
-
details: Optional[Dict[str, Any]] = None
|
|
93
|
-
):
|
|
94
|
-
super().__init__(message, status_code=402, details=details)
|
|
95
|
-
self.credits_needed = credits_needed
|
|
96
|
-
self.credits_available = credits_available
|
audiopod/services/__init__.py
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
"""
|
|
2
|
-
AudioPod
|
|
3
|
-
Service classes for different API endpoints
|
|
2
|
+
AudioPod SDK Services
|
|
4
3
|
"""
|
|
5
4
|
|
|
5
|
+
from .base import BaseService
|
|
6
6
|
from .voice import VoiceService
|
|
7
7
|
from .music import MusicService
|
|
8
8
|
from .transcription import TranscriptionService
|
|
9
9
|
from .translation import TranslationService
|
|
10
10
|
from .speaker import SpeakerService
|
|
11
11
|
from .denoiser import DenoiserService
|
|
12
|
-
from .karaoke import KaraokeService
|
|
13
12
|
from .credits import CreditService
|
|
14
13
|
from .stem_extraction import StemExtractionService
|
|
14
|
+
from .wallet import WalletService
|
|
15
15
|
|
|
16
16
|
__all__ = [
|
|
17
|
+
"BaseService",
|
|
17
18
|
"VoiceService",
|
|
18
|
-
"MusicService",
|
|
19
|
+
"MusicService",
|
|
19
20
|
"TranscriptionService",
|
|
20
21
|
"TranslationService",
|
|
21
22
|
"SpeakerService",
|
|
22
23
|
"DenoiserService",
|
|
23
|
-
"KaraokeService",
|
|
24
24
|
"CreditService",
|
|
25
|
-
"StemExtractionService"
|
|
25
|
+
"StemExtractionService",
|
|
26
|
+
"WalletService",
|
|
26
27
|
]
|
|
28
|
+
|
audiopod/services/base.py
CHANGED
|
@@ -1,213 +1,69 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Base
|
|
2
|
+
Base Service Class
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import os
|
|
6
5
|
import time
|
|
7
|
-
from typing import
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
from ..exceptions import ValidationError, FileError, ProcessingError
|
|
11
|
-
from ..models import Job, JobStatus
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from ..client import Client, AsyncClient
|
|
6
|
+
from typing import Any, Dict, Optional, Tuple, BinaryIO
|
|
15
7
|
|
|
16
8
|
|
|
17
9
|
class BaseService:
|
|
18
|
-
"""Base class for all
|
|
19
|
-
|
|
20
|
-
def __init__(self, client:
|
|
10
|
+
"""Base class for all services"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, client: Any, async_mode: bool = False):
|
|
21
13
|
self.client = client
|
|
22
14
|
self.async_mode = async_mode
|
|
23
|
-
|
|
24
|
-
def
|
|
25
|
-
""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
path = Path(file_path)
|
|
39
|
-
|
|
40
|
-
if not path.exists():
|
|
41
|
-
raise FileError(f"File not found: {file_path}")
|
|
42
|
-
|
|
43
|
-
if not self.client.config.validate_file_format(path.name, file_type):
|
|
44
|
-
supported_formats = (
|
|
45
|
-
self.client.config.supported_audio_formats
|
|
46
|
-
if file_type == "audio"
|
|
47
|
-
else self.client.config.supported_video_formats
|
|
48
|
-
)
|
|
49
|
-
raise FileError(
|
|
50
|
-
f"Unsupported {file_type} format. "
|
|
51
|
-
f"Supported formats: {', '.join(supported_formats)}"
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
# Check file size
|
|
55
|
-
file_size_mb = path.stat().st_size / (1024 * 1024)
|
|
56
|
-
if file_size_mb > self.client.config.max_file_size_mb:
|
|
57
|
-
raise FileError(
|
|
58
|
-
f"File too large: {file_size_mb:.1f}MB. "
|
|
59
|
-
f"Maximum size: {self.client.config.max_file_size_mb}MB"
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
return str(path.absolute())
|
|
63
|
-
|
|
64
|
-
def _prepare_file_upload(self, file_path: str, field_name: str = "file") -> Dict[str, Any]:
|
|
65
|
-
"""
|
|
66
|
-
Prepare file for upload
|
|
67
|
-
|
|
68
|
-
Args:
|
|
69
|
-
file_path: Path to the file
|
|
70
|
-
field_name: Form field name for the file
|
|
71
|
-
|
|
72
|
-
Returns:
|
|
73
|
-
Files dict for requests
|
|
74
|
-
"""
|
|
75
|
-
validated_path = self._validate_file(file_path)
|
|
76
|
-
|
|
77
|
-
with open(validated_path, 'rb') as f:
|
|
78
|
-
return {field_name: (Path(validated_path).name, f.read())}
|
|
79
|
-
|
|
15
|
+
|
|
16
|
+
def _prepare_file_upload(
|
|
17
|
+
self, file_path: str, field_name: str = "file"
|
|
18
|
+
) -> Dict[str, Tuple[str, BinaryIO, str]]:
|
|
19
|
+
"""Prepare file for upload."""
|
|
20
|
+
import mimetypes
|
|
21
|
+
|
|
22
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
|
23
|
+
mime_type = mime_type or "application/octet-stream"
|
|
24
|
+
|
|
25
|
+
file_handle = open(file_path, "rb")
|
|
26
|
+
filename = file_path.split("/")[-1]
|
|
27
|
+
|
|
28
|
+
return {field_name: (filename, file_handle, mime_type)}
|
|
29
|
+
|
|
80
30
|
def _wait_for_completion(
|
|
81
|
-
self,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
poll_interval: int = 5
|
|
85
|
-
) -> Job:
|
|
86
|
-
"""
|
|
87
|
-
Wait for job completion with polling
|
|
88
|
-
|
|
89
|
-
Args:
|
|
90
|
-
job_id: Job ID to wait for
|
|
91
|
-
timeout: Maximum time to wait in seconds
|
|
92
|
-
poll_interval: How often to check status in seconds
|
|
93
|
-
|
|
94
|
-
Returns:
|
|
95
|
-
Completed job
|
|
96
|
-
|
|
97
|
-
Raises:
|
|
98
|
-
ProcessingError: If job fails or times out
|
|
99
|
-
"""
|
|
31
|
+
self, job_id: int, timeout: int = 900, poll_interval: int = 5
|
|
32
|
+
) -> Dict[str, Any]:
|
|
33
|
+
"""Wait for job completion."""
|
|
100
34
|
start_time = time.time()
|
|
101
|
-
|
|
35
|
+
|
|
102
36
|
while time.time() - start_time < timeout:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if
|
|
107
|
-
return
|
|
108
|
-
elif
|
|
109
|
-
raise
|
|
110
|
-
|
|
111
|
-
job_id=str(job_id)
|
|
112
|
-
)
|
|
113
|
-
elif job.status == JobStatus.CANCELLED:
|
|
114
|
-
raise ProcessingError(
|
|
115
|
-
f"Job {job_id} was cancelled",
|
|
116
|
-
job_id=str(job_id)
|
|
117
|
-
)
|
|
118
|
-
|
|
37
|
+
response = self.client.request("GET", f"/api/v1/jobs/{job_id}")
|
|
38
|
+
|
|
39
|
+
status = response.get("status", "").upper()
|
|
40
|
+
if status == "COMPLETED":
|
|
41
|
+
return response
|
|
42
|
+
elif status in ["FAILED", "ERROR"]:
|
|
43
|
+
raise Exception(f"Job failed: {response.get('error_message', 'Unknown error')}")
|
|
44
|
+
|
|
119
45
|
time.sleep(poll_interval)
|
|
120
|
-
|
|
121
|
-
raise
|
|
122
|
-
|
|
123
|
-
job_id=str(job_id)
|
|
124
|
-
)
|
|
125
|
-
|
|
46
|
+
|
|
47
|
+
raise TimeoutError(f"Job {job_id} did not complete within {timeout} seconds")
|
|
48
|
+
|
|
126
49
|
async def _async_wait_for_completion(
|
|
127
|
-
self,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
poll_interval: int = 5
|
|
131
|
-
) -> Job:
|
|
132
|
-
"""
|
|
133
|
-
Async version of wait_for_completion
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
job_id: Job ID to wait for
|
|
137
|
-
timeout: Maximum time to wait in seconds
|
|
138
|
-
poll_interval: How often to check status in seconds
|
|
139
|
-
|
|
140
|
-
Returns:
|
|
141
|
-
Completed job
|
|
142
|
-
|
|
143
|
-
Raises:
|
|
144
|
-
ProcessingError: If job fails or times out
|
|
145
|
-
"""
|
|
50
|
+
self, job_id: int, timeout: int = 900, poll_interval: int = 5
|
|
51
|
+
) -> Dict[str, Any]:
|
|
52
|
+
"""Async wait for job completion."""
|
|
146
53
|
import asyncio
|
|
147
|
-
|
|
54
|
+
|
|
148
55
|
start_time = time.time()
|
|
149
|
-
|
|
56
|
+
|
|
150
57
|
while time.time() - start_time < timeout:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if
|
|
155
|
-
return
|
|
156
|
-
elif
|
|
157
|
-
raise
|
|
158
|
-
|
|
159
|
-
job_id=str(job_id)
|
|
160
|
-
)
|
|
161
|
-
elif job.status == JobStatus.CANCELLED:
|
|
162
|
-
raise ProcessingError(
|
|
163
|
-
f"Job {job_id} was cancelled",
|
|
164
|
-
job_id=str(job_id)
|
|
165
|
-
)
|
|
166
|
-
|
|
58
|
+
response = await self.client.request("GET", f"/api/v1/jobs/{job_id}")
|
|
59
|
+
|
|
60
|
+
status = response.get("status", "").upper()
|
|
61
|
+
if status == "COMPLETED":
|
|
62
|
+
return response
|
|
63
|
+
elif status in ["FAILED", "ERROR"]:
|
|
64
|
+
raise Exception(f"Job failed: {response.get('error_message', 'Unknown error')}")
|
|
65
|
+
|
|
167
66
|
await asyncio.sleep(poll_interval)
|
|
168
|
-
|
|
169
|
-
raise
|
|
170
|
-
|
|
171
|
-
job_id=str(job_id)
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
def _validate_language_code(self, language: str) -> str:
|
|
175
|
-
"""
|
|
176
|
-
Validate language code format
|
|
177
|
-
|
|
178
|
-
Args:
|
|
179
|
-
language: Language code to validate
|
|
180
|
-
|
|
181
|
-
Returns:
|
|
182
|
-
Validated language code
|
|
183
|
-
|
|
184
|
-
Raises:
|
|
185
|
-
ValidationError: If language code is invalid
|
|
186
|
-
"""
|
|
187
|
-
if not language or len(language) < 2:
|
|
188
|
-
raise ValidationError("Language code must be at least 2 characters")
|
|
189
|
-
|
|
190
|
-
# Convert to lowercase for consistency
|
|
191
|
-
return language.lower()
|
|
192
|
-
|
|
193
|
-
def _validate_text_input(self, text: str, max_length: int = 5000) -> str:
|
|
194
|
-
"""
|
|
195
|
-
Validate text input
|
|
196
|
-
|
|
197
|
-
Args:
|
|
198
|
-
text: Text to validate
|
|
199
|
-
max_length: Maximum allowed length
|
|
200
|
-
|
|
201
|
-
Returns:
|
|
202
|
-
Validated text
|
|
203
|
-
|
|
204
|
-
Raises:
|
|
205
|
-
ValidationError: If text is invalid
|
|
206
|
-
"""
|
|
207
|
-
if not text or not text.strip():
|
|
208
|
-
raise ValidationError("Text input cannot be empty")
|
|
209
|
-
|
|
210
|
-
if len(text) > max_length:
|
|
211
|
-
raise ValidationError(f"Text too long. Maximum length: {max_length} characters")
|
|
212
|
-
|
|
213
|
-
return text.strip()
|
|
67
|
+
|
|
68
|
+
raise TimeoutError(f"Job {job_id} did not complete within {timeout} seconds")
|
|
69
|
+
|
audiopod/services/credits.py
CHANGED
|
@@ -1,46 +1,42 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Credits Service - User credits and usage
|
|
2
|
+
Credits Service - User credits and usage (subscription credits)
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from typing import List, Dict, Any
|
|
6
6
|
from .base import BaseService
|
|
7
|
-
from ..models import CreditInfo
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class CreditService(BaseService):
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
"""
|
|
11
|
+
Service for managing subscription credits.
|
|
12
|
+
|
|
13
|
+
Note: For API wallet (USD-based billing), use client.wallet instead.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def get_balance(self) -> Dict[str, Any]:
|
|
17
|
+
"""Get subscription credit balance."""
|
|
15
18
|
if self.async_mode:
|
|
16
|
-
return self.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"""Async version of get_credit_balance"""
|
|
23
|
-
response = await self.client.request("GET", "/api/v1/credits")
|
|
24
|
-
return CreditInfo.from_dict(response)
|
|
25
|
-
|
|
19
|
+
return self._async_get_balance()
|
|
20
|
+
return self.client.request("GET", "/api/v1/credits")
|
|
21
|
+
|
|
22
|
+
async def _async_get_balance(self) -> Dict[str, Any]:
|
|
23
|
+
return await self.client.request("GET", "/api/v1/credits")
|
|
24
|
+
|
|
26
25
|
def get_usage_history(self) -> List[Dict[str, Any]]:
|
|
27
|
-
"""Get credit usage history"""
|
|
26
|
+
"""Get credit usage history."""
|
|
28
27
|
if self.async_mode:
|
|
29
28
|
return self._async_get_usage_history()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
return self.client.request("GET", "/api/v1/credits/usage")
|
|
30
|
+
|
|
33
31
|
async def _async_get_usage_history(self) -> List[Dict[str, Any]]:
|
|
34
|
-
"""Async version of get_usage_history"""
|
|
35
32
|
return await self.client.request("GET", "/api/v1/credits/usage")
|
|
36
|
-
|
|
37
|
-
def
|
|
38
|
-
"""Get credit multipliers for
|
|
33
|
+
|
|
34
|
+
def get_multipliers(self) -> Dict[str, float]:
|
|
35
|
+
"""Get credit multipliers for services."""
|
|
39
36
|
if self.async_mode:
|
|
40
|
-
return self.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
async def _async_get_credit_multipliers(self) -> Dict[str, float]:
|
|
45
|
-
"""Async version of get_credit_multipliers"""
|
|
37
|
+
return self._async_get_multipliers()
|
|
38
|
+
return self.client.request("GET", "/api/v1/credits/multipliers")
|
|
39
|
+
|
|
40
|
+
async def _async_get_multipliers(self) -> Dict[str, float]:
|
|
46
41
|
return await self.client.request("GET", "/api/v1/credits/multipliers")
|
|
42
|
+
|
audiopod/services/denoiser.py
CHANGED
|
@@ -1,51 +1,131 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Denoiser Service - Audio
|
|
2
|
+
Denoiser Service - Audio noise reduction
|
|
3
|
+
|
|
4
|
+
API Routes:
|
|
5
|
+
- POST /api/v1/denoiser/denoise - Denoise audio
|
|
6
|
+
- GET /api/v1/denoiser/jobs/{id} - Get job details
|
|
7
|
+
- GET /api/v1/denoiser/jobs - List jobs
|
|
8
|
+
- DELETE /api/v1/denoiser/jobs/{id} - Delete job
|
|
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, DenoiseResult
|
|
8
13
|
|
|
9
14
|
|
|
10
15
|
class DenoiserService(BaseService):
|
|
11
|
-
"""Service for audio
|
|
12
|
-
|
|
13
|
-
def
|
|
16
|
+
"""Service for audio noise reduction."""
|
|
17
|
+
|
|
18
|
+
def denoise(
|
|
14
19
|
self,
|
|
15
|
-
audio_file: str,
|
|
16
|
-
|
|
20
|
+
audio_file: Optional[str] = None,
|
|
21
|
+
url: Optional[str] = None,
|
|
22
|
+
mode: str = "balanced",
|
|
17
23
|
wait_for_completion: bool = False,
|
|
18
|
-
timeout: int =
|
|
19
|
-
) ->
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
timeout: int = 300,
|
|
25
|
+
) -> Dict[str, Any]:
|
|
26
|
+
"""
|
|
27
|
+
Remove noise from audio.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
audio_file: Path to local audio file
|
|
31
|
+
url: URL of audio file
|
|
32
|
+
mode: Denoise mode ("balanced", "studio", or "ultra")
|
|
33
|
+
wait_for_completion: Wait for completion
|
|
34
|
+
timeout: Max wait time in seconds
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Job dict with denoised audio URL when completed
|
|
38
|
+
"""
|
|
39
|
+
data = {"mode": mode}
|
|
40
|
+
if url:
|
|
41
|
+
data["url"] = url
|
|
42
|
+
|
|
43
|
+
files = self._prepare_file_upload(audio_file, "file") if audio_file else None
|
|
44
|
+
|
|
24
45
|
if self.async_mode:
|
|
25
|
-
return self._async_denoise(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
data=data, files=files
|
|
30
|
-
)
|
|
31
|
-
job = Job.from_dict(response)
|
|
32
|
-
|
|
33
|
-
if wait_for_completion:
|
|
34
|
-
completed_job = self._wait_for_completion(job.id, timeout)
|
|
35
|
-
return DenoiseResult.from_dict(completed_job.result or completed_job.__dict__)
|
|
36
|
-
|
|
37
|
-
return job
|
|
38
|
-
|
|
39
|
-
async def _async_denoise(self, files, data, wait_for_completion, timeout):
|
|
40
|
-
"""Async version of denoise_audio"""
|
|
41
|
-
response = await self.client.request(
|
|
42
|
-
"POST", "/api/v1/denoiser/denoise",
|
|
43
|
-
data=data, files=files
|
|
44
|
-
)
|
|
45
|
-
job = Job.from_dict(response)
|
|
46
|
-
|
|
46
|
+
return self._async_denoise(data, files, wait_for_completion, timeout)
|
|
47
|
+
|
|
48
|
+
response = self.client.request("POST", "/api/v1/denoiser/denoise", data=data, files=files)
|
|
49
|
+
|
|
47
50
|
if wait_for_completion:
|
|
48
|
-
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
job_id = response.get("id") or response.get("job_id")
|
|
52
|
+
return self._wait_for_denoise(job_id, timeout)
|
|
53
|
+
return response
|
|
54
|
+
|
|
55
|
+
async def _async_denoise(
|
|
56
|
+
self, data: Dict, files: Optional[Dict], wait_for_completion: bool, timeout: int
|
|
57
|
+
) -> Dict[str, Any]:
|
|
58
|
+
response = await self.client.request("POST", "/api/v1/denoiser/denoise", data=data, files=files)
|
|
59
|
+
if wait_for_completion:
|
|
60
|
+
job_id = response.get("id") or response.get("job_id")
|
|
61
|
+
return await self._async_wait_for_denoise(job_id, timeout)
|
|
62
|
+
return response
|
|
63
|
+
|
|
64
|
+
def get_job(self, job_id: int) -> Dict[str, Any]:
|
|
65
|
+
"""Get denoise job details and status."""
|
|
66
|
+
if self.async_mode:
|
|
67
|
+
return self._async_get_job(job_id)
|
|
68
|
+
return self.client.request("GET", f"/api/v1/denoiser/jobs/{job_id}")
|
|
69
|
+
|
|
70
|
+
async def _async_get_job(self, job_id: int) -> Dict[str, Any]:
|
|
71
|
+
return await self.client.request("GET", f"/api/v1/denoiser/jobs/{job_id}")
|
|
72
|
+
|
|
73
|
+
def list_jobs(self, skip: int = 0, limit: int = 50) -> List[Dict[str, Any]]:
|
|
74
|
+
"""List denoiser jobs."""
|
|
75
|
+
if self.async_mode:
|
|
76
|
+
return self._async_list_jobs(skip, limit)
|
|
77
|
+
return self.client.request(
|
|
78
|
+
"GET", "/api/v1/denoiser/jobs", params={"skip": skip, "limit": limit}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
async def _async_list_jobs(self, skip: int, limit: int) -> List[Dict[str, Any]]:
|
|
82
|
+
return await self.client.request(
|
|
83
|
+
"GET", "/api/v1/denoiser/jobs", params={"skip": skip, "limit": limit}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def delete_job(self, job_id: int) -> Dict[str, str]:
|
|
87
|
+
"""Delete a denoiser job."""
|
|
88
|
+
if self.async_mode:
|
|
89
|
+
return self._async_delete_job(job_id)
|
|
90
|
+
return self.client.request("DELETE", f"/api/v1/denoiser/jobs/{job_id}")
|
|
91
|
+
|
|
92
|
+
async def _async_delete_job(self, job_id: int) -> Dict[str, str]:
|
|
93
|
+
return await self.client.request("DELETE", f"/api/v1/denoiser/jobs/{job_id}")
|
|
94
|
+
|
|
95
|
+
def _wait_for_denoise(self, job_id: int, timeout: int) -> Dict[str, Any]:
|
|
96
|
+
"""Wait for denoise job completion."""
|
|
97
|
+
import time
|
|
98
|
+
start_time = time.time()
|
|
99
|
+
|
|
100
|
+
while time.time() - start_time < timeout:
|
|
101
|
+
job = self.get_job(job_id)
|
|
102
|
+
status = job.get("status", "").upper()
|
|
103
|
+
|
|
104
|
+
if status == "COMPLETED":
|
|
105
|
+
return job
|
|
106
|
+
elif status in ("FAILED", "ERROR"):
|
|
107
|
+
raise Exception(f"Denoising failed: {job.get('error_message', 'Unknown error')}")
|
|
108
|
+
|
|
109
|
+
time.sleep(3)
|
|
110
|
+
|
|
111
|
+
raise TimeoutError(f"Denoising {job_id} did not complete within {timeout} seconds")
|
|
112
|
+
|
|
113
|
+
async def _async_wait_for_denoise(self, job_id: int, timeout: int) -> Dict[str, Any]:
|
|
114
|
+
"""Async wait for denoise job completion."""
|
|
115
|
+
import asyncio
|
|
116
|
+
import time
|
|
117
|
+
start_time = time.time()
|
|
118
|
+
|
|
119
|
+
while time.time() - start_time < timeout:
|
|
120
|
+
job = await self.get_job(job_id)
|
|
121
|
+
status = job.get("status", "").upper()
|
|
122
|
+
|
|
123
|
+
if status == "COMPLETED":
|
|
124
|
+
return job
|
|
125
|
+
elif status in ("FAILED", "ERROR"):
|
|
126
|
+
raise Exception(f"Denoising failed: {job.get('error_message', 'Unknown error')}")
|
|
127
|
+
|
|
128
|
+
await asyncio.sleep(3)
|
|
129
|
+
|
|
130
|
+
raise TimeoutError(f"Denoising {job_id} did not complete within {timeout} seconds")
|
|
131
|
+
|