veed 0.1.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.
- veed/__init__.py +92 -0
- veed/_errors.py +139 -0
- veed/_transport.py +258 -0
- veed/_types.py +37 -0
- veed/_version.py +3 -0
- veed/client.py +119 -0
- veed/py.typed +0 -0
- veed/resources/__init__.py +12 -0
- veed/resources/cleanAudio.py +258 -0
- veed/resources/cleanAudioVideo.py +228 -0
- veed/resources/dubbing.py +239 -0
- veed/resources/dubbingPro.py +212 -0
- veed/resources/fabric.py +242 -0
- veed/resources/lipsync20.py +231 -0
- veed/resources/videoBackgroundRemoval.py +250 -0
- veed/resources/videoBackgroundRemovalFast.py +215 -0
- veed/resources/videoBackgroundRemovalGreenScreen.py +207 -0
- veed-0.1.0.dist-info/METADATA +94 -0
- veed-0.1.0.dist-info/RECORD +21 -0
- veed-0.1.0.dist-info/WHEEL +4 -0
- veed-0.1.0.dist-info/licenses/LICENSE +21 -0
veed/__init__.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Code generated from the VEED OpenAPI spec. DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
from ._errors import (
|
|
4
|
+
APIConnectionError,
|
|
5
|
+
APIError,
|
|
6
|
+
AuthenticationError,
|
|
7
|
+
InternalServerError,
|
|
8
|
+
InvalidRequestError,
|
|
9
|
+
JobCancelledError,
|
|
10
|
+
JobFailedError,
|
|
11
|
+
NotFoundError,
|
|
12
|
+
PermissionDeniedError,
|
|
13
|
+
RateLimitError,
|
|
14
|
+
VeedError,
|
|
15
|
+
WaitTimeoutError,
|
|
16
|
+
)
|
|
17
|
+
from ._types import (
|
|
18
|
+
JOB_STATUS_CANCELLED,
|
|
19
|
+
JOB_STATUS_COMPLETED,
|
|
20
|
+
JOB_STATUS_FAILED,
|
|
21
|
+
JOB_STATUS_PROCESSING,
|
|
22
|
+
File,
|
|
23
|
+
JobErrorDetail,
|
|
24
|
+
JobStatus,
|
|
25
|
+
)
|
|
26
|
+
from ._version import VERSION
|
|
27
|
+
from .client import AsyncVeed, Veed
|
|
28
|
+
from .resources.cleanAudio import (
|
|
29
|
+
AsyncCleanAudio,
|
|
30
|
+
CleanAudio,
|
|
31
|
+
CleanAudioJob,
|
|
32
|
+
CleanAudioJobError,
|
|
33
|
+
CleanAudioJobErrorCode,
|
|
34
|
+
CleanAudio,
|
|
35
|
+
CleanAudioOutputFormat,
|
|
36
|
+
)
|
|
37
|
+
from .resources.cleanAudioVideo import (
|
|
38
|
+
AsyncCleanAudioVideo,
|
|
39
|
+
CleanAudioVideo,
|
|
40
|
+
CleanAudioVideoJob,
|
|
41
|
+
CleanAudioVideo,
|
|
42
|
+
)
|
|
43
|
+
from .resources.dubbing import (
|
|
44
|
+
AsyncDubbing,
|
|
45
|
+
Dubbing,
|
|
46
|
+
DubbingJob,
|
|
47
|
+
DubbingJobError,
|
|
48
|
+
DubbingJobErrorCode,
|
|
49
|
+
Dubbing,
|
|
50
|
+
)
|
|
51
|
+
from .resources.dubbingPro import (
|
|
52
|
+
AsyncDubbingPro,
|
|
53
|
+
DubbingPro,
|
|
54
|
+
)
|
|
55
|
+
from .resources.fabric import (
|
|
56
|
+
AsyncFabric,
|
|
57
|
+
Fabric,
|
|
58
|
+
FabricJob,
|
|
59
|
+
FabricJobError,
|
|
60
|
+
FabricJobErrorCode,
|
|
61
|
+
FabricVideo,
|
|
62
|
+
FabricResolution,
|
|
63
|
+
)
|
|
64
|
+
from .resources.lipsync20 import (
|
|
65
|
+
AsyncLipsync20,
|
|
66
|
+
Lipsync20,
|
|
67
|
+
Lipsync20Job,
|
|
68
|
+
Lipsync20JobError,
|
|
69
|
+
Lipsync20JobErrorCode,
|
|
70
|
+
Lipsync20Video,
|
|
71
|
+
)
|
|
72
|
+
from .resources.videoBackgroundRemoval import (
|
|
73
|
+
AsyncVideoBackgroundRemoval,
|
|
74
|
+
VideoBackgroundRemoval,
|
|
75
|
+
VideoBackgroundRemovalJob,
|
|
76
|
+
VideoBackgroundRemovalJobError,
|
|
77
|
+
VideoBackgroundRemovalJobErrorCode,
|
|
78
|
+
VideoBackgroundRemovalFiles,
|
|
79
|
+
VideoBackgroundRemovalOutputCodec,
|
|
80
|
+
)
|
|
81
|
+
from .resources.videoBackgroundRemovalFast import (
|
|
82
|
+
AsyncVideoBackgroundRemovalFast,
|
|
83
|
+
VideoBackgroundRemovalFast,
|
|
84
|
+
VideoBackgroundRemovalFastOutputCodec,
|
|
85
|
+
)
|
|
86
|
+
from .resources.videoBackgroundRemovalGreenScreen import (
|
|
87
|
+
AsyncVideoBackgroundRemovalGreenScreen,
|
|
88
|
+
VideoBackgroundRemovalGreenScreen,
|
|
89
|
+
VideoBackgroundRemovalGreenScreenOutputCodec,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
__version__ = VERSION
|
veed/_errors.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Code generated from the VEED OpenAPI spec. DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, List, Optional
|
|
6
|
+
|
|
7
|
+
# The server chooses Retry-After and no timeout covers a sleep, so an unclamped
|
|
8
|
+
# header is a remote hang.
|
|
9
|
+
MAX_RETRY_AFTER = 60.0
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class VeedError(Exception):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class APIConnectionError(VeedError):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class APIError(VeedError):
|
|
21
|
+
"""HTTP-layer error: the request itself was rejected. For submits this
|
|
22
|
+
means no job was created (per the API contract)."""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
*,
|
|
27
|
+
status_code: int,
|
|
28
|
+
code: str,
|
|
29
|
+
message: str,
|
|
30
|
+
request_id: str,
|
|
31
|
+
details: Optional[List[Any]] = None,
|
|
32
|
+
retry_after: float = 0.0,
|
|
33
|
+
) -> None:
|
|
34
|
+
self.status_code = status_code
|
|
35
|
+
self.code = code
|
|
36
|
+
self.message = message
|
|
37
|
+
self.request_id = request_id
|
|
38
|
+
self.details = details or []
|
|
39
|
+
self.retry_after = retry_after
|
|
40
|
+
super().__init__(f"veed: {status_code} {code}: {message} (request_id: {request_id})")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class InvalidRequestError(APIError):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AuthenticationError(APIError):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class PermissionDeniedError(APIError):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class NotFoundError(APIError):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class RateLimitError(APIError):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class InternalServerError(APIError):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
_STATUS_MAP = {
|
|
68
|
+
400: InvalidRequestError,
|
|
69
|
+
401: AuthenticationError,
|
|
70
|
+
403: PermissionDeniedError,
|
|
71
|
+
404: NotFoundError,
|
|
72
|
+
422: InvalidRequestError,
|
|
73
|
+
429: RateLimitError,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def error_from_response(resp: Any) -> APIError:
|
|
78
|
+
status = resp.status_code
|
|
79
|
+
cls = _STATUS_MAP.get(status)
|
|
80
|
+
if cls is None:
|
|
81
|
+
cls = InternalServerError if status >= 500 else APIError
|
|
82
|
+
code = ""
|
|
83
|
+
message = ""
|
|
84
|
+
details = None
|
|
85
|
+
request_id = resp.headers.get("X-Request-ID", "")
|
|
86
|
+
try:
|
|
87
|
+
body = resp.json().get("error") or {}
|
|
88
|
+
code = body.get("code", "")
|
|
89
|
+
message = body.get("message", "")
|
|
90
|
+
details = body.get("details")
|
|
91
|
+
request_id = body.get("request_id") or request_id
|
|
92
|
+
except Exception:
|
|
93
|
+
message = resp.text[:200]
|
|
94
|
+
retry_after = 0.0
|
|
95
|
+
ra = resp.headers.get("Retry-After")
|
|
96
|
+
if ra and ra.isdigit():
|
|
97
|
+
retry_after = min(float(ra), MAX_RETRY_AFTER)
|
|
98
|
+
return cls(
|
|
99
|
+
status_code=status,
|
|
100
|
+
code=code,
|
|
101
|
+
message=message,
|
|
102
|
+
request_id=request_id,
|
|
103
|
+
details=details,
|
|
104
|
+
retry_after=retry_after,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class JobFailedError(VeedError):
|
|
109
|
+
"""Job-layer failure: the job was accepted, but rendering failed. Raised
|
|
110
|
+
by wait/generate, not as an HTTP error. `code` holds the model-specific
|
|
111
|
+
failure code and `job` the final job object."""
|
|
112
|
+
|
|
113
|
+
def __init__(self, *, job_id: str, code: str, message: str, details: Any = None, job: Any = None) -> None:
|
|
114
|
+
self.job_id = job_id
|
|
115
|
+
self.code = code
|
|
116
|
+
self.message = message
|
|
117
|
+
self.details = details or []
|
|
118
|
+
self.job = job
|
|
119
|
+
super().__init__(f"veed: job {job_id} failed ({code}): {message}")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class JobCancelledError(VeedError):
|
|
123
|
+
def __init__(self, *, job_id: str, job: Any = None) -> None:
|
|
124
|
+
self.job_id = job_id
|
|
125
|
+
self.job = job
|
|
126
|
+
super().__init__(f"veed: job {job_id} was cancelled")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class WaitTimeoutError(VeedError):
|
|
130
|
+
"""wait/generate gave up before the job reached a terminal state. The job
|
|
131
|
+
may still finish server-side; resume with wait(job_id)."""
|
|
132
|
+
|
|
133
|
+
def __init__(self, *, job_id: str, timeout: float, job: Any = None) -> None:
|
|
134
|
+
self.job_id = job_id
|
|
135
|
+
self.timeout = timeout
|
|
136
|
+
self.job = job
|
|
137
|
+
super().__init__(
|
|
138
|
+
f"veed: timed out after {timeout}s waiting for job {job_id} (the job may still finish; resume with wait)"
|
|
139
|
+
)
|
veed/_transport.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# Code generated from the VEED OpenAPI spec. DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
import random
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any, Awaitable, Callable, Optional, TypeVar
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
from ._errors import (
|
|
15
|
+
APIConnectionError,
|
|
16
|
+
APIError,
|
|
17
|
+
JobCancelledError,
|
|
18
|
+
JobFailedError,
|
|
19
|
+
RateLimitError,
|
|
20
|
+
VeedError,
|
|
21
|
+
WaitTimeoutError,
|
|
22
|
+
error_from_response,
|
|
23
|
+
)
|
|
24
|
+
from ._version import VERSION
|
|
25
|
+
|
|
26
|
+
DEFAULT_BASE_URL = "https://api.veed.io"
|
|
27
|
+
DEFAULT_WAIT_TIMEOUT = 900.0
|
|
28
|
+
|
|
29
|
+
T = TypeVar("T")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class RequestOptions:
|
|
34
|
+
store_io: Optional[bool] = None
|
|
35
|
+
media_expiration_seconds: Optional[int] = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _backoff(attempt: int) -> float:
|
|
39
|
+
return random.random() * min(0.5 * (2**attempt), 8.0)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _retryable(status: int) -> bool:
|
|
43
|
+
# POST retries are safe too: a rejected submit never creates a job.
|
|
44
|
+
return status in (408, 429) or status >= 500
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class BaseTransport:
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
api_key: Optional[str],
|
|
52
|
+
base_url: Optional[str],
|
|
53
|
+
timeout: float,
|
|
54
|
+
max_retries: int,
|
|
55
|
+
store_io: Optional[bool] = None,
|
|
56
|
+
media_expiration_seconds: Optional[int] = None,
|
|
57
|
+
) -> None:
|
|
58
|
+
self.api_key = api_key or os.environ.get("VEED_API_KEY") or ""
|
|
59
|
+
if not self.api_key:
|
|
60
|
+
raise VeedError(
|
|
61
|
+
"veed: missing API key: pass api_key=... or set VEED_API_KEY (create keys in your VEED workspace)"
|
|
62
|
+
)
|
|
63
|
+
self.base_url = (base_url or os.environ.get("VEED_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
|
|
64
|
+
self.timeout = timeout
|
|
65
|
+
self.max_retries = max_retries
|
|
66
|
+
self.store_io = store_io
|
|
67
|
+
self.media_expiration_seconds = media_expiration_seconds
|
|
68
|
+
|
|
69
|
+
def _headers(self, options: Optional[RequestOptions]) -> dict:
|
|
70
|
+
headers = {
|
|
71
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
72
|
+
"Accept": "application/json",
|
|
73
|
+
"User-Agent": f"veed-sdk-python/{VERSION}",
|
|
74
|
+
}
|
|
75
|
+
store_io = self.store_io
|
|
76
|
+
media_expiration = self.media_expiration_seconds
|
|
77
|
+
if options is not None:
|
|
78
|
+
if options.store_io is not None:
|
|
79
|
+
store_io = options.store_io
|
|
80
|
+
if options.media_expiration_seconds is not None:
|
|
81
|
+
media_expiration = options.media_expiration_seconds
|
|
82
|
+
if store_io is not None:
|
|
83
|
+
headers["X-Veed-Store-IO"] = "1" if store_io else "0"
|
|
84
|
+
if media_expiration is not None:
|
|
85
|
+
headers["X-Veed-Media-Expiration-Seconds"] = str(media_expiration)
|
|
86
|
+
return headers
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def _unwrap(resp: httpx.Response) -> Any:
|
|
90
|
+
payload = resp.json()
|
|
91
|
+
if not isinstance(payload, dict) or "data" not in payload:
|
|
92
|
+
raise APIError(
|
|
93
|
+
status_code=resp.status_code,
|
|
94
|
+
code="",
|
|
95
|
+
message="response is missing the data envelope",
|
|
96
|
+
request_id=resp.headers.get("X-Request-ID", ""),
|
|
97
|
+
)
|
|
98
|
+
return payload["data"]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class SyncTransport(BaseTransport):
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
*,
|
|
105
|
+
api_key: Optional[str] = None,
|
|
106
|
+
base_url: Optional[str] = None,
|
|
107
|
+
timeout: float = 60.0,
|
|
108
|
+
max_retries: int = 2,
|
|
109
|
+
store_io: Optional[bool] = None,
|
|
110
|
+
media_expiration_seconds: Optional[int] = None,
|
|
111
|
+
http_client: Optional[httpx.Client] = None,
|
|
112
|
+
) -> None:
|
|
113
|
+
super().__init__(
|
|
114
|
+
api_key=api_key,
|
|
115
|
+
base_url=base_url,
|
|
116
|
+
timeout=timeout,
|
|
117
|
+
max_retries=max_retries,
|
|
118
|
+
store_io=store_io,
|
|
119
|
+
media_expiration_seconds=media_expiration_seconds,
|
|
120
|
+
)
|
|
121
|
+
self._client = http_client or httpx.Client(timeout=timeout)
|
|
122
|
+
|
|
123
|
+
def close(self) -> None:
|
|
124
|
+
self._client.close()
|
|
125
|
+
|
|
126
|
+
def request(self, method: str, path: str, *, json_body: Any = None, options: Optional[RequestOptions] = None) -> Any:
|
|
127
|
+
attempt = 0
|
|
128
|
+
while True:
|
|
129
|
+
try:
|
|
130
|
+
resp = self._client.request(
|
|
131
|
+
method, self.base_url + path, json=json_body, headers=self._headers(options)
|
|
132
|
+
)
|
|
133
|
+
except httpx.HTTPError as exc:
|
|
134
|
+
if attempt < self.max_retries:
|
|
135
|
+
time.sleep(_backoff(attempt))
|
|
136
|
+
attempt += 1
|
|
137
|
+
continue
|
|
138
|
+
raise APIConnectionError(f"veed: request failed: {exc}") from exc
|
|
139
|
+
if resp.status_code < 300:
|
|
140
|
+
return self._unwrap(resp)
|
|
141
|
+
err = error_from_response(resp)
|
|
142
|
+
if _retryable(resp.status_code) and attempt < self.max_retries:
|
|
143
|
+
time.sleep(err.retry_after if err.retry_after else _backoff(attempt))
|
|
144
|
+
attempt += 1
|
|
145
|
+
continue
|
|
146
|
+
raise err
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class AsyncTransport(BaseTransport):
|
|
150
|
+
def __init__(
|
|
151
|
+
self,
|
|
152
|
+
*,
|
|
153
|
+
api_key: Optional[str] = None,
|
|
154
|
+
base_url: Optional[str] = None,
|
|
155
|
+
timeout: float = 60.0,
|
|
156
|
+
max_retries: int = 2,
|
|
157
|
+
store_io: Optional[bool] = None,
|
|
158
|
+
media_expiration_seconds: Optional[int] = None,
|
|
159
|
+
http_client: Optional[httpx.AsyncClient] = None,
|
|
160
|
+
) -> None:
|
|
161
|
+
super().__init__(
|
|
162
|
+
api_key=api_key,
|
|
163
|
+
base_url=base_url,
|
|
164
|
+
timeout=timeout,
|
|
165
|
+
max_retries=max_retries,
|
|
166
|
+
store_io=store_io,
|
|
167
|
+
media_expiration_seconds=media_expiration_seconds,
|
|
168
|
+
)
|
|
169
|
+
self._client = http_client or httpx.AsyncClient(timeout=timeout)
|
|
170
|
+
|
|
171
|
+
async def close(self) -> None:
|
|
172
|
+
await self._client.aclose()
|
|
173
|
+
|
|
174
|
+
async def request(
|
|
175
|
+
self, method: str, path: str, *, json_body: Any = None, options: Optional[RequestOptions] = None
|
|
176
|
+
) -> Any:
|
|
177
|
+
attempt = 0
|
|
178
|
+
while True:
|
|
179
|
+
try:
|
|
180
|
+
resp = await self._client.request(
|
|
181
|
+
method, self.base_url + path, json=json_body, headers=self._headers(options)
|
|
182
|
+
)
|
|
183
|
+
except httpx.HTTPError as exc:
|
|
184
|
+
if attempt < self.max_retries:
|
|
185
|
+
await asyncio.sleep(_backoff(attempt))
|
|
186
|
+
attempt += 1
|
|
187
|
+
continue
|
|
188
|
+
raise APIConnectionError(f"veed: request failed: {exc}") from exc
|
|
189
|
+
if resp.status_code < 300:
|
|
190
|
+
return self._unwrap(resp)
|
|
191
|
+
err = error_from_response(resp)
|
|
192
|
+
if _retryable(resp.status_code) and attempt < self.max_retries:
|
|
193
|
+
await asyncio.sleep(err.retry_after if err.retry_after else _backoff(attempt))
|
|
194
|
+
attempt += 1
|
|
195
|
+
continue
|
|
196
|
+
raise err
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _inspect(job: Any, job_id: str) -> Optional[VeedError]:
|
|
200
|
+
if job.status == "FAILED":
|
|
201
|
+
error = getattr(job, "error", None)
|
|
202
|
+
return JobFailedError(
|
|
203
|
+
job_id=job_id,
|
|
204
|
+
code=error.code if error else "",
|
|
205
|
+
message=error.message if error else "",
|
|
206
|
+
details=error.details if error else None,
|
|
207
|
+
job=job,
|
|
208
|
+
)
|
|
209
|
+
if job.status == "CANCELLED":
|
|
210
|
+
return JobCancelledError(job_id=job_id, job=job)
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def wait_for_job(get_job: Callable[[], T], job_id: str, poll_interval: float, timeout: float) -> T:
|
|
215
|
+
deadline = time.monotonic() + timeout
|
|
216
|
+
last: Optional[T] = None
|
|
217
|
+
while True:
|
|
218
|
+
sleep_s = poll_interval
|
|
219
|
+
try:
|
|
220
|
+
job = get_job()
|
|
221
|
+
last = job
|
|
222
|
+
if job.status == "COMPLETED": # type: ignore[attr-defined]
|
|
223
|
+
return job
|
|
224
|
+
terminal = _inspect(job, job_id)
|
|
225
|
+
if terminal is not None:
|
|
226
|
+
raise terminal
|
|
227
|
+
except RateLimitError as exc:
|
|
228
|
+
# The job keeps rendering server-side, so a rate-limited poll is
|
|
229
|
+
# not fatal to the wait.
|
|
230
|
+
sleep_s = exc.retry_after or poll_interval
|
|
231
|
+
remaining = deadline - time.monotonic()
|
|
232
|
+
if remaining <= 0:
|
|
233
|
+
raise WaitTimeoutError(job_id=job_id, timeout=timeout, job=last)
|
|
234
|
+
# Or a poll answered with Retry-After decides the wait's real length.
|
|
235
|
+
time.sleep(min(sleep_s, remaining))
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
async def async_wait_for_job(
|
|
239
|
+
get_job: Callable[[], Awaitable[T]], job_id: str, poll_interval: float, timeout: float
|
|
240
|
+
) -> T:
|
|
241
|
+
deadline = time.monotonic() + timeout
|
|
242
|
+
last: Optional[T] = None
|
|
243
|
+
while True:
|
|
244
|
+
sleep_s = poll_interval
|
|
245
|
+
try:
|
|
246
|
+
job = await get_job()
|
|
247
|
+
last = job
|
|
248
|
+
if job.status == "COMPLETED": # type: ignore[attr-defined]
|
|
249
|
+
return job
|
|
250
|
+
terminal = _inspect(job, job_id)
|
|
251
|
+
if terminal is not None:
|
|
252
|
+
raise terminal
|
|
253
|
+
except RateLimitError as exc:
|
|
254
|
+
sleep_s = exc.retry_after or poll_interval
|
|
255
|
+
remaining = deadline - time.monotonic()
|
|
256
|
+
if remaining <= 0:
|
|
257
|
+
raise WaitTimeoutError(job_id=job_id, timeout=timeout, job=last)
|
|
258
|
+
await asyncio.sleep(min(sleep_s, remaining))
|
veed/_types.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Code generated from the VEED OpenAPI spec. DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict
|
|
8
|
+
|
|
9
|
+
# Kept as str (not an Enum/Literal) so a new server-side status never breaks
|
|
10
|
+
# deserialization in old SDK versions.
|
|
11
|
+
JobStatus = str
|
|
12
|
+
|
|
13
|
+
JOB_STATUS_PROCESSING = "PROCESSING"
|
|
14
|
+
JOB_STATUS_COMPLETED = "COMPLETED"
|
|
15
|
+
JOB_STATUS_FAILED = "FAILED"
|
|
16
|
+
JOB_STATUS_CANCELLED = "CANCELLED"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class VeedModel(BaseModel):
|
|
20
|
+
model_config = ConfigDict(extra="allow")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class File(VeedModel):
|
|
24
|
+
"""A downloadable media file produced by a job."""
|
|
25
|
+
|
|
26
|
+
url: str
|
|
27
|
+
content_type: Optional[str] = None
|
|
28
|
+
file_name: Optional[str] = None
|
|
29
|
+
file_size: Optional[int] = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class JobErrorDetail(VeedModel):
|
|
33
|
+
"""One structured entry of a job-level failure."""
|
|
34
|
+
|
|
35
|
+
type: str
|
|
36
|
+
message: str
|
|
37
|
+
field: Optional[str] = None
|
veed/_version.py
ADDED
veed/client.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Code generated from the VEED OpenAPI spec. DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from ._transport import AsyncTransport, SyncTransport
|
|
10
|
+
from .resources import (
|
|
11
|
+
AsyncCleanAudio,
|
|
12
|
+
CleanAudio,
|
|
13
|
+
AsyncCleanAudioVideo,
|
|
14
|
+
CleanAudioVideo,
|
|
15
|
+
AsyncDubbing,
|
|
16
|
+
Dubbing,
|
|
17
|
+
AsyncDubbingPro,
|
|
18
|
+
DubbingPro,
|
|
19
|
+
AsyncFabric,
|
|
20
|
+
Fabric,
|
|
21
|
+
AsyncLipsync20,
|
|
22
|
+
Lipsync20,
|
|
23
|
+
AsyncVideoBackgroundRemoval,
|
|
24
|
+
VideoBackgroundRemoval,
|
|
25
|
+
AsyncVideoBackgroundRemovalFast,
|
|
26
|
+
VideoBackgroundRemovalFast,
|
|
27
|
+
AsyncVideoBackgroundRemovalGreenScreen,
|
|
28
|
+
VideoBackgroundRemovalGreenScreen,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Veed:
|
|
33
|
+
"""VEED API client. Construct once with your API key and reuse; safe to
|
|
34
|
+
share across threads.
|
|
35
|
+
|
|
36
|
+
client = veed.Veed() # reads VEED_API_KEY
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
api_key: Optional[str] = None,
|
|
43
|
+
base_url: Optional[str] = None,
|
|
44
|
+
timeout: float = 60.0,
|
|
45
|
+
max_retries: int = 2,
|
|
46
|
+
store_io: Optional[bool] = None,
|
|
47
|
+
media_expiration_seconds: Optional[int] = None,
|
|
48
|
+
http_client: Optional[httpx.Client] = None,
|
|
49
|
+
) -> None:
|
|
50
|
+
self._transport = SyncTransport(
|
|
51
|
+
api_key=api_key,
|
|
52
|
+
base_url=base_url,
|
|
53
|
+
timeout=timeout,
|
|
54
|
+
max_retries=max_retries,
|
|
55
|
+
store_io=store_io,
|
|
56
|
+
media_expiration_seconds=media_expiration_seconds,
|
|
57
|
+
http_client=http_client,
|
|
58
|
+
)
|
|
59
|
+
self.cleanAudio = CleanAudio(self._transport)
|
|
60
|
+
self.cleanAudioVideo = CleanAudioVideo(self._transport)
|
|
61
|
+
self.dubbing = Dubbing(self._transport)
|
|
62
|
+
self.dubbingPro = DubbingPro(self._transport)
|
|
63
|
+
self.fabric = Fabric(self._transport)
|
|
64
|
+
self.lipsync20 = Lipsync20(self._transport)
|
|
65
|
+
self.videoBackgroundRemoval = VideoBackgroundRemoval(self._transport)
|
|
66
|
+
self.videoBackgroundRemovalFast = VideoBackgroundRemovalFast(self._transport)
|
|
67
|
+
self.videoBackgroundRemovalGreenScreen = VideoBackgroundRemovalGreenScreen(self._transport)
|
|
68
|
+
|
|
69
|
+
def close(self) -> None:
|
|
70
|
+
self._transport.close()
|
|
71
|
+
|
|
72
|
+
def __enter__(self) -> "Veed":
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
def __exit__(self, *exc_info: object) -> None:
|
|
76
|
+
self.close()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class AsyncVeed:
|
|
80
|
+
"""Async VEED API client (asyncio + httpx.AsyncClient)."""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
*,
|
|
85
|
+
api_key: Optional[str] = None,
|
|
86
|
+
base_url: Optional[str] = None,
|
|
87
|
+
timeout: float = 60.0,
|
|
88
|
+
max_retries: int = 2,
|
|
89
|
+
store_io: Optional[bool] = None,
|
|
90
|
+
media_expiration_seconds: Optional[int] = None,
|
|
91
|
+
http_client: Optional[httpx.AsyncClient] = None,
|
|
92
|
+
) -> None:
|
|
93
|
+
self._transport = AsyncTransport(
|
|
94
|
+
api_key=api_key,
|
|
95
|
+
base_url=base_url,
|
|
96
|
+
timeout=timeout,
|
|
97
|
+
max_retries=max_retries,
|
|
98
|
+
store_io=store_io,
|
|
99
|
+
media_expiration_seconds=media_expiration_seconds,
|
|
100
|
+
http_client=http_client,
|
|
101
|
+
)
|
|
102
|
+
self.cleanAudio = AsyncCleanAudio(self._transport)
|
|
103
|
+
self.cleanAudioVideo = AsyncCleanAudioVideo(self._transport)
|
|
104
|
+
self.dubbing = AsyncDubbing(self._transport)
|
|
105
|
+
self.dubbingPro = AsyncDubbingPro(self._transport)
|
|
106
|
+
self.fabric = AsyncFabric(self._transport)
|
|
107
|
+
self.lipsync20 = AsyncLipsync20(self._transport)
|
|
108
|
+
self.videoBackgroundRemoval = AsyncVideoBackgroundRemoval(self._transport)
|
|
109
|
+
self.videoBackgroundRemovalFast = AsyncVideoBackgroundRemovalFast(self._transport)
|
|
110
|
+
self.videoBackgroundRemovalGreenScreen = AsyncVideoBackgroundRemovalGreenScreen(self._transport)
|
|
111
|
+
|
|
112
|
+
async def close(self) -> None:
|
|
113
|
+
await self._transport.close()
|
|
114
|
+
|
|
115
|
+
async def __aenter__(self) -> "AsyncVeed":
|
|
116
|
+
return self
|
|
117
|
+
|
|
118
|
+
async def __aexit__(self, *exc_info: object) -> None:
|
|
119
|
+
await self.close()
|
veed/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Code generated from the VEED OpenAPI spec. DO NOT EDIT.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from .cleanAudio import CleanAudio, AsyncCleanAudio
|
|
5
|
+
from .cleanAudioVideo import CleanAudioVideo, AsyncCleanAudioVideo
|
|
6
|
+
from .dubbing import Dubbing, AsyncDubbing
|
|
7
|
+
from .dubbingPro import DubbingPro, AsyncDubbingPro
|
|
8
|
+
from .fabric import Fabric, AsyncFabric
|
|
9
|
+
from .lipsync20 import Lipsync20, AsyncLipsync20
|
|
10
|
+
from .videoBackgroundRemoval import VideoBackgroundRemoval, AsyncVideoBackgroundRemoval
|
|
11
|
+
from .videoBackgroundRemovalFast import VideoBackgroundRemovalFast, AsyncVideoBackgroundRemovalFast
|
|
12
|
+
from .videoBackgroundRemovalGreenScreen import VideoBackgroundRemovalGreenScreen, AsyncVideoBackgroundRemovalGreenScreen
|