synapsai-python 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.
- synapsai/__init__.py +16 -0
- synapsai/client.py +628 -0
- synapsai/exceptions.py +64 -0
- synapsai/logging.py +70 -0
- synapsai/processing.py +100 -0
- synapsai/resources/__init__.py +59 -0
- synapsai/resources/audio.py +495 -0
- synapsai/resources/chat.py +199 -0
- synapsai/resources/classifications.py +454 -0
- synapsai/resources/completions.py +162 -0
- synapsai/resources/embeddings.py +207 -0
- synapsai/resources/feature_extraction.py +80 -0
- synapsai/resources/fill_mask.py +88 -0
- synapsai/resources/images.py +560 -0
- synapsai/resources/models.py +80 -0
- synapsai/resources/question_answering.py +257 -0
- synapsai/resources/rerank.py +90 -0
- synapsai/resources/videos.py +303 -0
- synapsai/types/__init__.py +144 -0
- synapsai/types/audio.py +177 -0
- synapsai/types/classifications.py +228 -0
- synapsai/types/common.py +56 -0
- synapsai/types/completion.py +244 -0
- synapsai/types/embeddings.py +68 -0
- synapsai/types/feature_extraction.py +31 -0
- synapsai/types/fill_mask.py +38 -0
- synapsai/types/images.py +172 -0
- synapsai/types/models.py +28 -0
- synapsai/types/question_answering.py +127 -0
- synapsai/types/rerank.py +40 -0
- synapsai/types/videos.py +92 -0
- synapsai/utils.py +32 -0
- synapsai_python-0.1.0.dist-info/METADATA +312 -0
- synapsai_python-0.1.0.dist-info/RECORD +37 -0
- synapsai_python-0.1.0.dist-info/WHEEL +5 -0
- synapsai_python-0.1.0.dist-info/licenses/LICENSE +201 -0
- synapsai_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Copyright 2026 SynapsAI Technologies Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Question Answering resource handlers
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import TYPE_CHECKING, Optional, Union, List, Dict
|
|
20
|
+
|
|
21
|
+
from ..types.question_answering import (
|
|
22
|
+
DocumentQuestionAnsweringRequest,
|
|
23
|
+
DocumentQuestionAnsweringResponse,
|
|
24
|
+
QuestionAnsweringRequest,
|
|
25
|
+
QuestionAnsweringResponse,
|
|
26
|
+
TableQuestionAnsweringRequest,
|
|
27
|
+
TableQuestionAnsweringResponse,
|
|
28
|
+
VisualQuestionAnsweringRequest,
|
|
29
|
+
VisualQuestionAnsweringResponse,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from ..client import SynapsAI, AsyncSynapsAI
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class QuestionAnsweringResource:
|
|
37
|
+
"""Question Answering resource handler"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, client: "SynapsAI"):
|
|
40
|
+
self._client = client
|
|
41
|
+
|
|
42
|
+
def document(
|
|
43
|
+
self,
|
|
44
|
+
model: str,
|
|
45
|
+
image,
|
|
46
|
+
question: str,
|
|
47
|
+
word_boxes: Optional[List] = None,
|
|
48
|
+
top_k: Optional[int] = None,
|
|
49
|
+
doc_stride: Optional[int] = None,
|
|
50
|
+
max_answer_len: Optional[int] = None,
|
|
51
|
+
max_seq_len: Optional[int] = None,
|
|
52
|
+
max_question_len: Optional[int] = None,
|
|
53
|
+
handle_impossible_answer: Optional[bool] = None,
|
|
54
|
+
lang: Optional[str] = None,
|
|
55
|
+
tesseract_config: Optional[str] = None,
|
|
56
|
+
timeout: Optional[float] = None,
|
|
57
|
+
) -> DocumentQuestionAnsweringResponse:
|
|
58
|
+
request_data = self._client._build_request(
|
|
59
|
+
model=model,
|
|
60
|
+
image=image,
|
|
61
|
+
question=question,
|
|
62
|
+
word_boxes=word_boxes,
|
|
63
|
+
top_k=top_k,
|
|
64
|
+
doc_stride=doc_stride,
|
|
65
|
+
max_answer_len=max_answer_len,
|
|
66
|
+
max_seq_len=max_seq_len,
|
|
67
|
+
max_question_len=max_question_len,
|
|
68
|
+
handle_impossible_answer=handle_impossible_answer,
|
|
69
|
+
lang=lang,
|
|
70
|
+
tesseract_config=tesseract_config,
|
|
71
|
+
timeout=timeout,
|
|
72
|
+
)
|
|
73
|
+
endpoint = "question-answering/document"
|
|
74
|
+
response = self._client._post(endpoint, json_data=request_data)
|
|
75
|
+
return DocumentQuestionAnsweringResponse.model_validate(response.json())
|
|
76
|
+
|
|
77
|
+
def text(
|
|
78
|
+
self,
|
|
79
|
+
model: str,
|
|
80
|
+
question: Union[str, List[str]],
|
|
81
|
+
context: Union[str, List[str]],
|
|
82
|
+
top_k: Optional[int] = None,
|
|
83
|
+
doc_stride: Optional[int] = None,
|
|
84
|
+
max_answer_len: Optional[int] = None,
|
|
85
|
+
max_seq_len: Optional[int] = None,
|
|
86
|
+
max_question_len: Optional[int] = None,
|
|
87
|
+
handle_impossible_answer: Optional[bool] = None,
|
|
88
|
+
align_to_words: Optional[bool] = None,
|
|
89
|
+
) -> QuestionAnsweringResponse:
|
|
90
|
+
request_data = self._client._build_request(
|
|
91
|
+
model=model,
|
|
92
|
+
question=question,
|
|
93
|
+
context=context,
|
|
94
|
+
top_k=top_k,
|
|
95
|
+
doc_stride=doc_stride,
|
|
96
|
+
max_answer_len=max_answer_len,
|
|
97
|
+
max_seq_len=max_seq_len,
|
|
98
|
+
max_question_len=max_question_len,
|
|
99
|
+
handle_impossible_answer=handle_impossible_answer,
|
|
100
|
+
align_to_words=align_to_words,
|
|
101
|
+
)
|
|
102
|
+
endpoint = "question-answering"
|
|
103
|
+
response = self._client._post(endpoint, json_data=request_data)
|
|
104
|
+
return QuestionAnsweringResponse.model_validate(response.json())
|
|
105
|
+
|
|
106
|
+
def table(
|
|
107
|
+
self,
|
|
108
|
+
model: str,
|
|
109
|
+
table: Dict,
|
|
110
|
+
query: Union[str, List[str]],
|
|
111
|
+
sequential: Optional[bool] = None,
|
|
112
|
+
padding: Optional[Union[bool, str]] = None,
|
|
113
|
+
truncation: Optional[Union[bool, str]] = None,
|
|
114
|
+
) -> TableQuestionAnsweringResponse:
|
|
115
|
+
request_data = self._client._build_request(
|
|
116
|
+
model=model,
|
|
117
|
+
table=table,
|
|
118
|
+
query=query,
|
|
119
|
+
sequential=sequential,
|
|
120
|
+
padding=padding,
|
|
121
|
+
truncation=truncation,
|
|
122
|
+
)
|
|
123
|
+
endpoint = "question-answering/table"
|
|
124
|
+
response = self._client._post(endpoint, json_data=request_data)
|
|
125
|
+
return TableQuestionAnsweringResponse.model_validate(response.json())
|
|
126
|
+
|
|
127
|
+
def visual(
|
|
128
|
+
self,
|
|
129
|
+
model: str,
|
|
130
|
+
image,
|
|
131
|
+
question: Union[str, List[str]],
|
|
132
|
+
top_k: Optional[int] = None,
|
|
133
|
+
timeout: Optional[float] = None,
|
|
134
|
+
) -> VisualQuestionAnsweringResponse:
|
|
135
|
+
request_data = self._client._build_request(
|
|
136
|
+
model=model,
|
|
137
|
+
image=image,
|
|
138
|
+
question=question,
|
|
139
|
+
top_k=top_k,
|
|
140
|
+
timeout=timeout,
|
|
141
|
+
)
|
|
142
|
+
endpoint = "question-answering/visual"
|
|
143
|
+
response = self._client._post(endpoint, json_data=request_data)
|
|
144
|
+
return VisualQuestionAnsweringResponse.model_validate(response.json())
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class AsyncQuestionAnsweringResource:
|
|
148
|
+
"""Async Question Answering resource handler"""
|
|
149
|
+
|
|
150
|
+
def __init__(self, client: "AsyncSynapsAI"):
|
|
151
|
+
self._client = client
|
|
152
|
+
|
|
153
|
+
async def document(
|
|
154
|
+
self,
|
|
155
|
+
model: str,
|
|
156
|
+
image,
|
|
157
|
+
question: str,
|
|
158
|
+
word_boxes: Optional[List] = None,
|
|
159
|
+
top_k: Optional[int] = None,
|
|
160
|
+
doc_stride: Optional[int] = None,
|
|
161
|
+
max_answer_len: Optional[int] = None,
|
|
162
|
+
max_seq_len: Optional[int] = None,
|
|
163
|
+
max_question_len: Optional[int] = None,
|
|
164
|
+
handle_impossible_answer: Optional[bool] = None,
|
|
165
|
+
lang: Optional[str] = None,
|
|
166
|
+
tesseract_config: Optional[str] = None,
|
|
167
|
+
timeout: Optional[float] = None,
|
|
168
|
+
) -> DocumentQuestionAnsweringResponse:
|
|
169
|
+
request_data = self._client._build_request(
|
|
170
|
+
model=model,
|
|
171
|
+
image=image,
|
|
172
|
+
question=question,
|
|
173
|
+
word_boxes=word_boxes,
|
|
174
|
+
top_k=top_k,
|
|
175
|
+
doc_stride=doc_stride,
|
|
176
|
+
max_answer_len=max_answer_len,
|
|
177
|
+
max_seq_len=max_seq_len,
|
|
178
|
+
max_question_len=max_question_len,
|
|
179
|
+
handle_impossible_answer=handle_impossible_answer,
|
|
180
|
+
lang=lang,
|
|
181
|
+
tesseract_config=tesseract_config,
|
|
182
|
+
timeout=timeout,
|
|
183
|
+
)
|
|
184
|
+
endpoint = "question-answering/document"
|
|
185
|
+
response = await self._client._post(endpoint, json_data=request_data)
|
|
186
|
+
return DocumentQuestionAnsweringResponse.model_validate(response.json())
|
|
187
|
+
|
|
188
|
+
async def text(
|
|
189
|
+
self,
|
|
190
|
+
model: str,
|
|
191
|
+
question: Union[str, List[str]],
|
|
192
|
+
context: Union[str, List[str]],
|
|
193
|
+
top_k: Optional[int] = None,
|
|
194
|
+
doc_stride: Optional[int] = None,
|
|
195
|
+
max_answer_len: Optional[int] = None,
|
|
196
|
+
max_seq_len: Optional[int] = None,
|
|
197
|
+
max_question_len: Optional[int] = None,
|
|
198
|
+
handle_impossible_answer: Optional[bool] = None,
|
|
199
|
+
align_to_words: Optional[bool] = None,
|
|
200
|
+
) -> QuestionAnsweringResponse:
|
|
201
|
+
request_data = self._client._build_request(
|
|
202
|
+
model=model,
|
|
203
|
+
question=question,
|
|
204
|
+
context=context,
|
|
205
|
+
top_k=top_k,
|
|
206
|
+
doc_stride=doc_stride,
|
|
207
|
+
max_answer_len=max_answer_len,
|
|
208
|
+
max_seq_len=max_seq_len,
|
|
209
|
+
max_question_len=max_question_len,
|
|
210
|
+
handle_impossible_answer=handle_impossible_answer,
|
|
211
|
+
align_to_words=align_to_words,
|
|
212
|
+
)
|
|
213
|
+
endpoint = "question-answering"
|
|
214
|
+
response = await self._client._post(endpoint, json_data=request_data)
|
|
215
|
+
return QuestionAnsweringResponse.model_validate(response.json())
|
|
216
|
+
|
|
217
|
+
async def table(
|
|
218
|
+
self,
|
|
219
|
+
model: str,
|
|
220
|
+
table: Dict,
|
|
221
|
+
query: Union[str, List[str]],
|
|
222
|
+
sequential: Optional[bool] = None,
|
|
223
|
+
padding: Optional[Union[bool, str]] = None,
|
|
224
|
+
truncation: Optional[Union[bool, str]] = None,
|
|
225
|
+
) -> TableQuestionAnsweringResponse:
|
|
226
|
+
request_data = self._client._build_request(
|
|
227
|
+
model=model,
|
|
228
|
+
table=table,
|
|
229
|
+
query=query,
|
|
230
|
+
sequential=sequential,
|
|
231
|
+
padding=padding,
|
|
232
|
+
truncation=truncation,
|
|
233
|
+
)
|
|
234
|
+
endpoint = "question-answering/table"
|
|
235
|
+
response = await self._client._post(endpoint, json_data=request_data)
|
|
236
|
+
return TableQuestionAnsweringResponse.model_validate(response.json())
|
|
237
|
+
|
|
238
|
+
async def visual(
|
|
239
|
+
self,
|
|
240
|
+
model: str,
|
|
241
|
+
image,
|
|
242
|
+
question: Union[str, List[str]],
|
|
243
|
+
top_k: Optional[int] = None,
|
|
244
|
+
timeout: Optional[float] = None,
|
|
245
|
+
) -> VisualQuestionAnsweringResponse:
|
|
246
|
+
request_data = self._client._build_request(
|
|
247
|
+
model=model,
|
|
248
|
+
image=image,
|
|
249
|
+
question=question,
|
|
250
|
+
top_k=top_k,
|
|
251
|
+
timeout=timeout,
|
|
252
|
+
)
|
|
253
|
+
endpoint = "question-answering/visual"
|
|
254
|
+
response = await self._client._post(endpoint, json_data=request_data)
|
|
255
|
+
return VisualQuestionAnsweringResponse.model_validate(response.json())
|
|
256
|
+
|
|
257
|
+
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Copyright 2026 SynapsAI Technologies Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Text ranking resource handlers
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import TYPE_CHECKING, List
|
|
20
|
+
|
|
21
|
+
from ..types.rerank import RerankResponse
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from ..client import SynapsAI, AsyncSynapsAI
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RerankResource:
|
|
28
|
+
"""Text ranking resource handler"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, client: "SynapsAI"):
|
|
31
|
+
self._client = client
|
|
32
|
+
|
|
33
|
+
def create(
|
|
34
|
+
self,
|
|
35
|
+
model: str,
|
|
36
|
+
query: str,
|
|
37
|
+
documents: List[str],
|
|
38
|
+
top_n: int | None = None,
|
|
39
|
+
max_tokens_per_doc: int = 4096,
|
|
40
|
+
**kwargs,
|
|
41
|
+
) -> RerankResponse:
|
|
42
|
+
"""Rank documents against a query using a text-ranking model."""
|
|
43
|
+
|
|
44
|
+
request_data = self._client._build_request(
|
|
45
|
+
model=model,
|
|
46
|
+
query=query,
|
|
47
|
+
documents=documents,
|
|
48
|
+
top_n=top_n,
|
|
49
|
+
max_tokens_per_doc=max_tokens_per_doc,
|
|
50
|
+
**kwargs,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
endpoint = "rerank"
|
|
54
|
+
|
|
55
|
+
response = self._client._post(endpoint, json_data=request_data)
|
|
56
|
+
response_data = response.json()
|
|
57
|
+
return RerankResponse.model_validate(response_data)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AsyncRerankResource:
|
|
61
|
+
"""Async text ranking resource handler"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, client: "AsyncSynapsAI"):
|
|
64
|
+
self._client = client
|
|
65
|
+
|
|
66
|
+
async def create(
|
|
67
|
+
self,
|
|
68
|
+
model: str,
|
|
69
|
+
query: str,
|
|
70
|
+
documents: List[str],
|
|
71
|
+
top_n: int | None = None,
|
|
72
|
+
max_tokens_per_doc: int = 4096,
|
|
73
|
+
**kwargs,
|
|
74
|
+
) -> RerankResponse:
|
|
75
|
+
"""Rank documents against a query using a text-ranking model."""
|
|
76
|
+
|
|
77
|
+
request_data = self._client._build_request(
|
|
78
|
+
model=model,
|
|
79
|
+
query=query,
|
|
80
|
+
documents=documents,
|
|
81
|
+
top_n=top_n,
|
|
82
|
+
max_tokens_per_doc=max_tokens_per_doc,
|
|
83
|
+
**kwargs,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
endpoint = "rerank"
|
|
87
|
+
|
|
88
|
+
response = await self._client._post(endpoint, json_data=request_data)
|
|
89
|
+
response_data = response.json()
|
|
90
|
+
return RerankResponse.model_validate(response_data)
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Copyright 2026 SynapsAI Technologies Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Video resource handlers
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import base64
|
|
21
|
+
import os
|
|
22
|
+
import time
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import TYPE_CHECKING, AsyncIterator, Iterator, Optional, Union, Tuple
|
|
25
|
+
from urllib.parse import urlencode
|
|
26
|
+
|
|
27
|
+
from ..processing import process_image_input
|
|
28
|
+
|
|
29
|
+
from ..exceptions import APIError
|
|
30
|
+
from ..types.videos import (
|
|
31
|
+
Video,
|
|
32
|
+
VideoContentVariant,
|
|
33
|
+
VideoDeleteResponse,
|
|
34
|
+
VideoInputReference,
|
|
35
|
+
VideoStatus,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from ..client import AsyncSynapsAI, SynapsAI
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class VideoContentResponse:
|
|
43
|
+
"""Streaming binary response for video content downloads."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, response, content_type: str):
|
|
46
|
+
self._response = response
|
|
47
|
+
self.content_type = content_type
|
|
48
|
+
|
|
49
|
+
def iter_bytes(self, chunk_size: int = 8192) -> Iterator[bytes]:
|
|
50
|
+
for chunk in self._response.iter_bytes(chunk_size=chunk_size):
|
|
51
|
+
if chunk:
|
|
52
|
+
yield chunk
|
|
53
|
+
|
|
54
|
+
def write_to_file(self, file_path) -> None:
|
|
55
|
+
if hasattr(file_path, "write"):
|
|
56
|
+
for chunk in self.iter_bytes():
|
|
57
|
+
file_path.write(chunk)
|
|
58
|
+
else:
|
|
59
|
+
with Path(file_path).open("wb") as f:
|
|
60
|
+
for chunk in self.iter_bytes():
|
|
61
|
+
f.write(chunk)
|
|
62
|
+
|
|
63
|
+
def read(self) -> bytes:
|
|
64
|
+
return b"".join(self.iter_bytes())
|
|
65
|
+
|
|
66
|
+
def __enter__(self) -> "VideoContentResponse":
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
70
|
+
close = getattr(self._response, "close", None)
|
|
71
|
+
if callable(close):
|
|
72
|
+
close()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class AsyncVideoContentResponse:
|
|
76
|
+
"""Async streaming binary response for video content downloads."""
|
|
77
|
+
|
|
78
|
+
def __init__(self, response, content_type: str):
|
|
79
|
+
self._response = response
|
|
80
|
+
self.content_type = content_type
|
|
81
|
+
|
|
82
|
+
async def iter_bytes(self, chunk_size: int = 8192) -> AsyncIterator[bytes]:
|
|
83
|
+
async for chunk in self._response.aiter_bytes(chunk_size=chunk_size):
|
|
84
|
+
if chunk:
|
|
85
|
+
yield chunk
|
|
86
|
+
|
|
87
|
+
async def write_to_file(self, file_path) -> None:
|
|
88
|
+
if hasattr(file_path, "write"):
|
|
89
|
+
async for chunk in self.iter_bytes():
|
|
90
|
+
file_path.write(chunk)
|
|
91
|
+
else:
|
|
92
|
+
with Path(file_path).open("wb") as f:
|
|
93
|
+
async for chunk in self.iter_bytes():
|
|
94
|
+
f.write(chunk)
|
|
95
|
+
|
|
96
|
+
async def read(self) -> bytes:
|
|
97
|
+
return b"".join([chunk async for chunk in self.iter_bytes()])
|
|
98
|
+
|
|
99
|
+
async def __aenter__(self) -> "AsyncVideoContentResponse":
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
103
|
+
aclose = getattr(self._response, "aclose", None)
|
|
104
|
+
if callable(aclose):
|
|
105
|
+
await aclose()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class VideosResource:
|
|
109
|
+
"""Video generation resource"""
|
|
110
|
+
|
|
111
|
+
TERMINAL_STATUSES = {VideoStatus.COMPLETED.value, VideoStatus.FAILED.value}
|
|
112
|
+
|
|
113
|
+
def __init__(self, client: "SynapsAI"):
|
|
114
|
+
self._client = client
|
|
115
|
+
|
|
116
|
+
def _build_content_endpoint(
|
|
117
|
+
self,
|
|
118
|
+
video_id: str,
|
|
119
|
+
variant: Optional[Union[str, VideoContentVariant]] = None,
|
|
120
|
+
) -> str:
|
|
121
|
+
endpoint = f"videos/{video_id}/content"
|
|
122
|
+
if variant is None:
|
|
123
|
+
return endpoint
|
|
124
|
+
query = urlencode({"variant": getattr(variant, "value", variant)})
|
|
125
|
+
return f"{endpoint}?{query}"
|
|
126
|
+
|
|
127
|
+
def create(
|
|
128
|
+
self,
|
|
129
|
+
model: str,
|
|
130
|
+
prompt: str,
|
|
131
|
+
input_reference: Optional[VideoInputReference] = None,
|
|
132
|
+
seconds: int = 4,
|
|
133
|
+
size: Optional[Union[str, Tuple[int, int]]] = "1280x720",
|
|
134
|
+
fps: int = 16,
|
|
135
|
+
num_inference_steps: int = 25,
|
|
136
|
+
guidance_scale: float = 5.0,
|
|
137
|
+
**kwargs,
|
|
138
|
+
) -> Video:
|
|
139
|
+
request_data = self._client._build_request(
|
|
140
|
+
prompt=prompt,
|
|
141
|
+
input_reference=process_image_input(input_reference),
|
|
142
|
+
model=model,
|
|
143
|
+
seconds=seconds,
|
|
144
|
+
size=size,
|
|
145
|
+
fps=fps,
|
|
146
|
+
num_inference_steps=num_inference_steps,
|
|
147
|
+
guidance_scale=guidance_scale,
|
|
148
|
+
**kwargs,
|
|
149
|
+
)
|
|
150
|
+
response = self._client._post("videos", json_data=request_data)
|
|
151
|
+
return Video.model_validate(response.json())
|
|
152
|
+
|
|
153
|
+
def retrieve(self, video_id: str) -> Video:
|
|
154
|
+
response = self._client._get(f"videos/{video_id}")
|
|
155
|
+
return Video.model_validate(response.json())
|
|
156
|
+
|
|
157
|
+
def delete(self, video_id: str) -> VideoDeleteResponse:
|
|
158
|
+
response = self._client._delete(f"videos/{video_id}")
|
|
159
|
+
return VideoDeleteResponse.model_validate(response.json())
|
|
160
|
+
|
|
161
|
+
def download_content(
|
|
162
|
+
self,
|
|
163
|
+
video_id: str,
|
|
164
|
+
variant: Optional[Union[str, VideoContentVariant]] = None,
|
|
165
|
+
) -> VideoContentResponse:
|
|
166
|
+
endpoint = self._build_content_endpoint(video_id=video_id, variant=variant)
|
|
167
|
+
response = self._client._get_stream(endpoint)
|
|
168
|
+
content_type = response.headers.get("content-type", "application/octet-stream")
|
|
169
|
+
return VideoContentResponse(response, content_type=content_type)
|
|
170
|
+
|
|
171
|
+
def create_and_poll(
|
|
172
|
+
self,
|
|
173
|
+
model: str,
|
|
174
|
+
prompt: str,
|
|
175
|
+
input_reference: Optional[VideoInputReference] = None,
|
|
176
|
+
seconds: int = 4,
|
|
177
|
+
size: Optional[Union[str, Tuple[int, int]]] = "1280x720",
|
|
178
|
+
poll_interval: float = 2.0,
|
|
179
|
+
timeout: Optional[float] = None,
|
|
180
|
+
fps: int = 16,
|
|
181
|
+
num_inference_steps: int = 25,
|
|
182
|
+
guidance_scale: float = 5.0,
|
|
183
|
+
**kwargs,
|
|
184
|
+
) -> Video:
|
|
185
|
+
video = self.create(
|
|
186
|
+
prompt=prompt,
|
|
187
|
+
input_reference=input_reference,
|
|
188
|
+
model=model,
|
|
189
|
+
seconds=seconds,
|
|
190
|
+
size=size,
|
|
191
|
+
fps=fps,
|
|
192
|
+
num_inference_steps=num_inference_steps,
|
|
193
|
+
guidance_scale=guidance_scale,
|
|
194
|
+
**kwargs,
|
|
195
|
+
)
|
|
196
|
+
started_at = time.monotonic()
|
|
197
|
+
|
|
198
|
+
while getattr(video.status, "value", video.status) not in self.TERMINAL_STATUSES:
|
|
199
|
+
if timeout is not None and time.monotonic() - started_at >= timeout:
|
|
200
|
+
raise APIError(f"Timed out while waiting for video '{video.id}' to complete")
|
|
201
|
+
time.sleep(poll_interval)
|
|
202
|
+
video = self.retrieve(video.id)
|
|
203
|
+
|
|
204
|
+
return video
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class AsyncVideosResource:
|
|
208
|
+
"""Async video generation resource"""
|
|
209
|
+
|
|
210
|
+
TERMINAL_STATUSES = {VideoStatus.COMPLETED.value, VideoStatus.FAILED.value}
|
|
211
|
+
|
|
212
|
+
def __init__(self, client: "AsyncSynapsAI"):
|
|
213
|
+
self._client = client
|
|
214
|
+
|
|
215
|
+
def _build_content_endpoint(
|
|
216
|
+
self,
|
|
217
|
+
video_id: str,
|
|
218
|
+
variant: Optional[Union[str, VideoContentVariant]] = None,
|
|
219
|
+
) -> str:
|
|
220
|
+
endpoint = f"videos/{video_id}/content"
|
|
221
|
+
if variant is None:
|
|
222
|
+
return endpoint
|
|
223
|
+
query = urlencode({"variant": getattr(variant, "value", variant)})
|
|
224
|
+
return f"{endpoint}?{query}"
|
|
225
|
+
|
|
226
|
+
async def create(
|
|
227
|
+
self,
|
|
228
|
+
model: str,
|
|
229
|
+
prompt: str,
|
|
230
|
+
input_reference: Optional[VideoInputReference] = None,
|
|
231
|
+
seconds: Optional[int] = 4,
|
|
232
|
+
size: Optional[Union[str, Tuple[int, int]]] = "1280x720",
|
|
233
|
+
fps: int = 16,
|
|
234
|
+
num_inference_steps: int = 25,
|
|
235
|
+
guidance_scale: float = 5.0,
|
|
236
|
+
**kwargs,
|
|
237
|
+
) -> Video:
|
|
238
|
+
request_data = self._client._build_request(
|
|
239
|
+
prompt=prompt,
|
|
240
|
+
input_reference=process_image_input(input_reference),
|
|
241
|
+
model=model,
|
|
242
|
+
seconds=seconds,
|
|
243
|
+
size=size,
|
|
244
|
+
fps=fps,
|
|
245
|
+
num_inference_steps=num_inference_steps,
|
|
246
|
+
guidance_scale=guidance_scale,
|
|
247
|
+
**kwargs,
|
|
248
|
+
)
|
|
249
|
+
response = await self._client._post("videos", json_data=request_data)
|
|
250
|
+
return Video.model_validate(response.json())
|
|
251
|
+
|
|
252
|
+
async def retrieve(self, video_id: str) -> Video:
|
|
253
|
+
response = await self._client._get(f"videos/{video_id}")
|
|
254
|
+
return Video.model_validate(response.json())
|
|
255
|
+
|
|
256
|
+
async def delete(self, video_id: str) -> VideoDeleteResponse:
|
|
257
|
+
response = await self._client._delete(f"videos/{video_id}")
|
|
258
|
+
return VideoDeleteResponse.model_validate(response.json())
|
|
259
|
+
|
|
260
|
+
async def download_content(
|
|
261
|
+
self,
|
|
262
|
+
video_id: str,
|
|
263
|
+
variant: Optional[Union[str, VideoContentVariant]] = None,
|
|
264
|
+
) -> AsyncVideoContentResponse:
|
|
265
|
+
endpoint = self._build_content_endpoint(video_id=video_id, variant=variant)
|
|
266
|
+
response = await self._client._get_stream(endpoint)
|
|
267
|
+
content_type = response.headers.get("content-type", "application/octet-stream")
|
|
268
|
+
return AsyncVideoContentResponse(response, content_type=content_type)
|
|
269
|
+
|
|
270
|
+
async def create_and_poll(
|
|
271
|
+
self,
|
|
272
|
+
model: str,
|
|
273
|
+
prompt: str,
|
|
274
|
+
input_reference: Optional[VideoInputReference] = None,
|
|
275
|
+
seconds: Optional[int] = 4,
|
|
276
|
+
size: Optional[Union[str, Tuple[int, int]]] = "1280x720",
|
|
277
|
+
fps: int = 16,
|
|
278
|
+
num_inference_steps: int = 25,
|
|
279
|
+
guidance_scale: float = 5.0,
|
|
280
|
+
poll_interval: float = 2.0,
|
|
281
|
+
timeout: Optional[float] = None,
|
|
282
|
+
**kwargs,
|
|
283
|
+
) -> Video:
|
|
284
|
+
video = await self.create(
|
|
285
|
+
prompt=prompt,
|
|
286
|
+
input_reference=input_reference,
|
|
287
|
+
model=model,
|
|
288
|
+
seconds=seconds,
|
|
289
|
+
size=size,
|
|
290
|
+
fps=fps,
|
|
291
|
+
num_inference_steps=num_inference_steps,
|
|
292
|
+
guidance_scale=guidance_scale,
|
|
293
|
+
**kwargs,
|
|
294
|
+
)
|
|
295
|
+
started_at = time.monotonic()
|
|
296
|
+
|
|
297
|
+
while getattr(video.status, "value", video.status) not in self.TERMINAL_STATUSES:
|
|
298
|
+
if timeout is not None and time.monotonic() - started_at >= timeout:
|
|
299
|
+
raise APIError(f"Timed out while waiting for video '{video.id}' to complete")
|
|
300
|
+
await asyncio.sleep(poll_interval)
|
|
301
|
+
video = await self.retrieve(video.id)
|
|
302
|
+
|
|
303
|
+
return video
|