volcengine-python-sdk-aicc 0.0.41__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.
- bytedance/__init__.py +4 -0
- bytedance/jeddak_secure_channel/__init__.py +28 -0
- bytedance/jeddak_secure_channel/client.py +305 -0
- bytedance/jeddak_secure_channel/config.py +171 -0
- bytedance/jeddak_secure_channel/consts.py +5 -0
- bytedance/jeddak_secure_channel/crypto/__init__.py +14 -0
- bytedance/jeddak_secure_channel/crypto/aes.py +174 -0
- bytedance/jeddak_secure_channel/crypto/envelope.py +395 -0
- bytedance/jeddak_secure_channel/crypto/rsa.py +164 -0
- bytedance/jeddak_secure_channel/error.py +131 -0
- bytedance/jeddak_secure_channel/log/__init__.py +23 -0
- bytedance/jeddak_secure_channel/log/config.py +31 -0
- bytedance/jeddak_secure_channel/log/logger.py +88 -0
- bytedance/jeddak_secure_channel/logger_factory.py +134 -0
- bytedance/jeddak_secure_channel/ra.py +772 -0
- bytedance/jeddak_secure_channel/server.py +745 -0
- bytedance/jeddak_secure_channel/service/__init__.py +16 -0
- bytedance/jeddak_secure_channel/service/eps.py +133 -0
- bytedance/jeddak_secure_channel/service/eps_models.py +47 -0
- bytedance/jeddak_secure_channel/service/ras.py +88 -0
- bytedance/jeddak_secure_channel/service/tks.py +51 -0
- bytedance/jeddak_secure_channel/service/tks_models.py +334 -0
- bytedance/jeddak_secure_channel/utils.py +469 -0
- bytedance/jeddak_secure_model/model_automation_tool.py +181 -0
- bytedance/jeddak_secure_model/model_encryption.py +416 -0
- bytedance/jeddak_secure_model/model_hub_admin.py +116 -0
- bytedance/jeddak_secure_model/pcc_utils.py +279 -0
- bytedance/jeddak_secure_model/rds_utils.py +63 -0
- bytedance/tks/attester.py +41 -0
- bytedance/tks/client.py +629 -0
- bytedance/tks/consts.py +122 -0
- bytedance/tks/crypto/aes.py +47 -0
- bytedance/tks/crypto/ec.py +39 -0
- bytedance/tks/exceptions.py +22 -0
- bytedance/volcengine_aicc_sdk/__init__.py +14 -0
- bytedance/volcengine_aicc_sdk/aicc_client.py +277 -0
- bytedance/volcengine_aicc_sdk/aicc_inference.py +64 -0
- bytedance/volcengine_aicc_sdk/ark_inference_client.py +343 -0
- bytedance/volcengine_aicc_sdk/asr_inference_client.py +158 -0
- bytedance/volcengine_aicc_sdk/asr_websocket_client.py +705 -0
- bytedance/volcengine_aicc_sdk/auc_inference_client.py +305 -0
- bytedance/volcengine_aicc_sdk/common/__init__.py +0 -0
- bytedance/volcengine_aicc_sdk/common/aes.py +136 -0
- bytedance/volcengine_aicc_sdk/common/constant.py +4 -0
- bytedance/volcengine_aicc_sdk/common/error.py +21 -0
- bytedance/volcengine_aicc_sdk/common/log/__init__.py +24 -0
- bytedance/volcengine_aicc_sdk/common/log/config.py +31 -0
- bytedance/volcengine_aicc_sdk/common/log/logger.py +88 -0
- bytedance/volcengine_aicc_sdk/common/tos_utils.py +341 -0
- bytedance/volcengine_aicc_sdk/common/utils.py +45 -0
- bytedance/volcengine_aicc_sdk/cu_inference_client.py +292 -0
- bytedance/volcengine_aicc_sdk/deepseek_inference_client.py +501 -0
- bytedance/volcengine_aicc_sdk/secure_http_client.py +406 -0
- bytedance/volcengine_aicc_sdk/seedance_inference_client.py +500 -0
- bytedance/volcengine_aicc_sdk/seedream_inference_client.py +213 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/METADATA +79 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/RECORD +60 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/WHEEL +5 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/licenses/LICENSE +202 -0
- volcengine_python_sdk_aicc-0.0.41.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
"""
|
|
2
|
+
seedance机密推理客户端
|
|
3
|
+
"""
|
|
4
|
+
__all__ = ["SeedanceClient"]
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import base64
|
|
8
|
+
import requests
|
|
9
|
+
import traceback
|
|
10
|
+
from urllib.parse import unquote
|
|
11
|
+
from cryptography.hazmat.primitives import serialization as crypto_serialization
|
|
12
|
+
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
13
|
+
from cryptography.hazmat.backends import default_backend as crypto_default_backend
|
|
14
|
+
from bytedance.jeddak_secure_channel.crypto import PrivateKey
|
|
15
|
+
from .secure_http_client import SecureHttpClient
|
|
16
|
+
from .common.tos_utils import TOSUtils
|
|
17
|
+
from .common.error import AICCException
|
|
18
|
+
from .common.log import logger
|
|
19
|
+
from .secure_http_client import AICCConfig
|
|
20
|
+
from .common.aes import decrypt_file
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SeedanceClient:
|
|
24
|
+
"""
|
|
25
|
+
DeepSeek机密推理客户端.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self,
|
|
29
|
+
base_url: str = "",
|
|
30
|
+
api_key: str = "",
|
|
31
|
+
endpoint: str = "",
|
|
32
|
+
aicc_config: AICCConfig = None,
|
|
33
|
+
**kwargs):
|
|
34
|
+
self.base_url = base_url
|
|
35
|
+
self.api_key = api_key
|
|
36
|
+
self.endpoint = endpoint
|
|
37
|
+
self.aicc_config = aicc_config
|
|
38
|
+
self.tos_utils = TOSUtils()
|
|
39
|
+
self.kwargs = kwargs
|
|
40
|
+
self.is_secure = self.kwargs.get("is_secure", True)
|
|
41
|
+
|
|
42
|
+
if aicc_config:
|
|
43
|
+
self.secure_http_client = SecureHttpClient(aicc_config=aicc_config)
|
|
44
|
+
else:
|
|
45
|
+
if self.is_secure:
|
|
46
|
+
aicc_config = AICCConfig(ra_url=base_url, auth_token=api_key)
|
|
47
|
+
self.secure_http_client = SecureHttpClient(aicc_config=aicc_config)
|
|
48
|
+
else:
|
|
49
|
+
self.secure_http_client = SecureHttpClient()
|
|
50
|
+
|
|
51
|
+
# 是否开启视频文件加密, 默认加密
|
|
52
|
+
self.is_enable_video_encrypt = str(self.kwargs.get("is_enable_video_encrypt", True)).lower() == "true"
|
|
53
|
+
|
|
54
|
+
# 视频文件的存储位置, 目前仅支持火山TOS
|
|
55
|
+
self.video_file_storage_location = self.kwargs.get("video_file_storage_location", "TOS").upper()
|
|
56
|
+
|
|
57
|
+
self.video_file_encrypt_pub_key_content = None
|
|
58
|
+
self.video_file_encrypt_priv_key_content = None
|
|
59
|
+
|
|
60
|
+
self.video_file_encrypt_priv_key: PrivateKey = None
|
|
61
|
+
|
|
62
|
+
self.timeout = self.kwargs.get("timeout", 120.0)
|
|
63
|
+
|
|
64
|
+
# API Endpoints
|
|
65
|
+
self.CREATE_TASK_URL = "/contents/generations/tasks"
|
|
66
|
+
self.QUERY_TASK_URL = "/contents/generations/tasks"
|
|
67
|
+
self.QUERY_TASK_LIST_URL = "/contents/generations/tasks"
|
|
68
|
+
self.DELETE_TASK_URL = "/contents/generations/tasks"
|
|
69
|
+
|
|
70
|
+
def _get_base_url(self):
|
|
71
|
+
"""
|
|
72
|
+
Get base URL with trailing slash removed
|
|
73
|
+
"""
|
|
74
|
+
return self.base_url.rstrip("/")
|
|
75
|
+
|
|
76
|
+
def set_video_file_encrypt_key(self, pub_key_path: str = "", priv_key_path: str = ""):
|
|
77
|
+
"""
|
|
78
|
+
Set video file encrypt key
|
|
79
|
+
Args:
|
|
80
|
+
pub_key_path: Public key for encrypt
|
|
81
|
+
priv_key_path: Private key for decrypt
|
|
82
|
+
"""
|
|
83
|
+
if os.path.exists(pub_key_path):
|
|
84
|
+
with open(pub_key_path, 'r') as f:
|
|
85
|
+
content = f.read()
|
|
86
|
+
if '-----BEGIN' in content and '-----END' in content:
|
|
87
|
+
self.video_file_encrypt_pub_key_content = content
|
|
88
|
+
|
|
89
|
+
if os.path.exists(priv_key_path):
|
|
90
|
+
with open(priv_key_path, 'r') as f:
|
|
91
|
+
content = f.read()
|
|
92
|
+
if '-----BEGIN' in content and '-----END' in content:
|
|
93
|
+
self.video_file_encrypt_priv_key_content = content
|
|
94
|
+
self.video_file_encrypt_priv_key = \
|
|
95
|
+
PrivateKey.from_private_pem(self.video_file_encrypt_priv_key_content)
|
|
96
|
+
|
|
97
|
+
def _generate_key_pair(self, pub_key_path: str = "", priv_key_path: str = ""):
|
|
98
|
+
"""
|
|
99
|
+
Generate RSA 4096 key pair and save to files
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
pub_key_path: Path to save public key
|
|
103
|
+
priv_key_path: Path to save private key
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Tuple of (public_key_path, private_key_path)
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
# Generate RSA key pair
|
|
110
|
+
key = rsa.generate_private_key(
|
|
111
|
+
backend=crypto_default_backend(),
|
|
112
|
+
public_exponent=65537,
|
|
113
|
+
key_size=4096
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Save private key
|
|
117
|
+
private_key = key.private_bytes(
|
|
118
|
+
crypto_serialization.Encoding.PEM,
|
|
119
|
+
crypto_serialization.PrivateFormat.PKCS8,
|
|
120
|
+
crypto_serialization.NoEncryption()
|
|
121
|
+
)
|
|
122
|
+
with open(priv_key_path, 'wb') as f:
|
|
123
|
+
f.write(private_key)
|
|
124
|
+
|
|
125
|
+
# Save public key
|
|
126
|
+
public_key = key.public_key().public_bytes(
|
|
127
|
+
crypto_serialization.Encoding.PEM,
|
|
128
|
+
crypto_serialization.PublicFormat.SubjectPublicKeyInfo
|
|
129
|
+
)
|
|
130
|
+
with open(pub_key_path, 'wb') as f:
|
|
131
|
+
f.write(public_key)
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logger.error(f"[Error] Failed to generate key pair: {e}")
|
|
134
|
+
raise AICCException("Failed to generate RSA key pair") from e
|
|
135
|
+
|
|
136
|
+
def generate_video_file_encrypt_key(self, pub_key_path: str = "", priv_key_path: str = ""):
|
|
137
|
+
"""
|
|
138
|
+
Generate video file encrypt key
|
|
139
|
+
Args:
|
|
140
|
+
pub_key_path: Public key for encrypt
|
|
141
|
+
priv_key_path: Private key for decrypt
|
|
142
|
+
"""
|
|
143
|
+
# 如果没有提供路径报错
|
|
144
|
+
if not pub_key_path or not priv_key_path:
|
|
145
|
+
raise ValueError("pub_key_path and priv_key_path must be provided")
|
|
146
|
+
|
|
147
|
+
self._generate_key_pair(pub_key_path, priv_key_path)
|
|
148
|
+
# self.set_video_file_encrypt_key(pub_key_path, priv_key_path)
|
|
149
|
+
|
|
150
|
+
def create_video_generation_task(self, data: dict, custom_header: dict = None) -> str:
|
|
151
|
+
"""
|
|
152
|
+
:param data:
|
|
153
|
+
:param custom_header:
|
|
154
|
+
:return:
|
|
155
|
+
"""
|
|
156
|
+
if not data:
|
|
157
|
+
raise AICCException("data must be provided")
|
|
158
|
+
|
|
159
|
+
if "model" not in data:
|
|
160
|
+
data["model"] = self.endpoint
|
|
161
|
+
|
|
162
|
+
base_url = self._get_base_url()
|
|
163
|
+
url = f"{base_url}{self.CREATE_TASK_URL}"
|
|
164
|
+
headers = {
|
|
165
|
+
"Content-Type": "application/json",
|
|
166
|
+
"Authorization": f"Bearer {self.api_key}"
|
|
167
|
+
}
|
|
168
|
+
if custom_header:
|
|
169
|
+
headers.update(custom_header)
|
|
170
|
+
|
|
171
|
+
# Add encryption headers if provided
|
|
172
|
+
if self.is_enable_video_encrypt:
|
|
173
|
+
if not self.video_file_encrypt_pub_key_content or not self.video_file_encrypt_priv_key_content:
|
|
174
|
+
raise AICCException("pub_key and priv_key must be provided when Is-Enable-Encrypt is 'true'")
|
|
175
|
+
|
|
176
|
+
headers["Enable-TOS-Content-Result-Encryption"] = str(self.is_enable_video_encrypt).lower()
|
|
177
|
+
headers["X-Encryption-Algorithm"] = "RSA_OAEP_4096_AES_256"
|
|
178
|
+
if self.video_file_encrypt_pub_key_content:
|
|
179
|
+
# Base64 encode the PEM public key for safe HTTP header transmission
|
|
180
|
+
# This avoids issues with newlines and special characters in headers
|
|
181
|
+
pk_base64 = base64.b64encode(self.video_file_encrypt_pub_key_content.encode()).decode()
|
|
182
|
+
headers["PK"] = pk_base64
|
|
183
|
+
|
|
184
|
+
# logger.info(f"[Request] Data: {json.dumps(data, ensure_ascii=False, indent=2)}")
|
|
185
|
+
|
|
186
|
+
response = self.secure_http_client.post(url, headers=headers, json=data, timeout=self.timeout)
|
|
187
|
+
if response.status_code == 200:
|
|
188
|
+
result = response.json()
|
|
189
|
+
task_id = result.get("id")
|
|
190
|
+
return task_id
|
|
191
|
+
else:
|
|
192
|
+
logger.error(f"[Error] url: {url}, response: {response.text}")
|
|
193
|
+
raise AICCException(f"create seedance task error: {response.text}")
|
|
194
|
+
|
|
195
|
+
def create_video_generation_task_by_message(self, message: list, custom_header: dict = None,
|
|
196
|
+
generate_audio: bool = True,
|
|
197
|
+
ratio: str = "16:9", duration: int = 11,
|
|
198
|
+
watermark: bool = False) -> str:
|
|
199
|
+
"""
|
|
200
|
+
message: List of messages to generate video
|
|
201
|
+
"""
|
|
202
|
+
if not message:
|
|
203
|
+
raise AICCException("message must be provided")
|
|
204
|
+
|
|
205
|
+
data = {
|
|
206
|
+
"model": self.endpoint,
|
|
207
|
+
"content": message,
|
|
208
|
+
"generate_audio": generate_audio,
|
|
209
|
+
"ratio": ratio,
|
|
210
|
+
"duration": duration,
|
|
211
|
+
"watermark": watermark
|
|
212
|
+
}
|
|
213
|
+
return self.create_video_generation_task(data, custom_header)
|
|
214
|
+
|
|
215
|
+
def query_video_generation_task(self, task_id: str) -> dict:
|
|
216
|
+
"""
|
|
217
|
+
Query video generation task
|
|
218
|
+
API: GET /api/v3/contents/generations/tasks/{task_id}
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
if not task_id:
|
|
222
|
+
raise AICCException("task_id must be provided")
|
|
223
|
+
|
|
224
|
+
base_url = self._get_base_url()
|
|
225
|
+
url = f"{base_url}{self.QUERY_TASK_URL}/{task_id}"
|
|
226
|
+
headers = {
|
|
227
|
+
"Content-Type": "application/json",
|
|
228
|
+
"Authorization": f"Bearer {self.api_key}"
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# logger.info(f"[Request] URL: {url}")
|
|
232
|
+
|
|
233
|
+
# response = self.secure_http_client.get(url, headers=headers, timeout=self.timeout)
|
|
234
|
+
response = self.secure_http_client.request(
|
|
235
|
+
"GET", url, headers=headers, content=b"{}", timeout=self.timeout
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
if response.status_code == 200:
|
|
239
|
+
result = response.json()
|
|
240
|
+
# logger.info(f"[Response] Body: {json.dumps(result, ensure_ascii=False, indent=2)}")
|
|
241
|
+
|
|
242
|
+
# status = result.get("status")
|
|
243
|
+
# if status == "succeeded":
|
|
244
|
+
# video_url = result.get("content", {}).get("video_url")
|
|
245
|
+
# print(f"[Video URL] {video_url}")
|
|
246
|
+
|
|
247
|
+
return result
|
|
248
|
+
else:
|
|
249
|
+
try:
|
|
250
|
+
result = response.json()
|
|
251
|
+
return result
|
|
252
|
+
except Exception as e:
|
|
253
|
+
logger.error(f"[Error] url: {url}, Exception={e}")
|
|
254
|
+
raise AICCException(f"Failed to query video generation task")
|
|
255
|
+
|
|
256
|
+
def query_video_generation_task_list(
|
|
257
|
+
self,
|
|
258
|
+
page_num: int = 0,
|
|
259
|
+
page_size: int = 0,
|
|
260
|
+
status: str = "",
|
|
261
|
+
task_ids: list = None,
|
|
262
|
+
model: str = "",
|
|
263
|
+
service_tier: str = ""
|
|
264
|
+
) -> dict:
|
|
265
|
+
"""
|
|
266
|
+
Query video generation task list
|
|
267
|
+
API: GET /api/v3/contents/generations/tasks
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
page_num: Page number (only added if > 0)
|
|
271
|
+
page_size: Page size (only added if > 0)
|
|
272
|
+
status: Filter by status (only added if not empty)
|
|
273
|
+
task_ids: Filter by task IDs (only added if not empty list)
|
|
274
|
+
model: Filter by model (only added if not empty)
|
|
275
|
+
service_tier: Filter by service tier (only added if not empty)
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Query result dict
|
|
279
|
+
"""
|
|
280
|
+
from urllib.parse import urlencode
|
|
281
|
+
|
|
282
|
+
base_url = self._get_base_url()
|
|
283
|
+
|
|
284
|
+
# Build query parameters (only add non-empty values)
|
|
285
|
+
params = []
|
|
286
|
+
|
|
287
|
+
if page_num > 0:
|
|
288
|
+
params.append(f"page_num={page_num}")
|
|
289
|
+
|
|
290
|
+
if page_size > 0:
|
|
291
|
+
params.append(f"page_size={page_size}")
|
|
292
|
+
|
|
293
|
+
if status:
|
|
294
|
+
params.append(f"filter.status={status}")
|
|
295
|
+
|
|
296
|
+
if task_ids and len(task_ids) > 0:
|
|
297
|
+
for task_id in task_ids:
|
|
298
|
+
params.append(f"filter.task_ids={task_id}")
|
|
299
|
+
|
|
300
|
+
if model:
|
|
301
|
+
params.append(f"filter.model={model}")
|
|
302
|
+
|
|
303
|
+
if service_tier:
|
|
304
|
+
params.append(f"filter.service_tier={service_tier}")
|
|
305
|
+
|
|
306
|
+
# Build URL with query parameters
|
|
307
|
+
query_string = "&".join(params)
|
|
308
|
+
if query_string:
|
|
309
|
+
url = f"{base_url}{self.QUERY_TASK_LIST_URL}?{query_string}"
|
|
310
|
+
else:
|
|
311
|
+
url = f"{base_url}{self.QUERY_TASK_LIST_URL}"
|
|
312
|
+
|
|
313
|
+
headers = {
|
|
314
|
+
"Content-Type": "application/json",
|
|
315
|
+
"Authorization": f"Bearer {self.api_key}"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
response = self.secure_http_client.get(url, headers=headers, timeout=self.timeout)
|
|
319
|
+
try:
|
|
320
|
+
result = response.json()
|
|
321
|
+
return result
|
|
322
|
+
except Exception as e:
|
|
323
|
+
logger.error(f"query_video_generation_task_list error: {e}")
|
|
324
|
+
raise AICCException(f"Failed to query video generation task list")
|
|
325
|
+
|
|
326
|
+
def delete_video_generation_task(self, task_id: str):
|
|
327
|
+
"""
|
|
328
|
+
Delete video generation task
|
|
329
|
+
API: DELETE /api/v3/contents/generations/tasks/{task_id}
|
|
330
|
+
"""
|
|
331
|
+
if not task_id:
|
|
332
|
+
raise AICCException("task_id must be provided")
|
|
333
|
+
|
|
334
|
+
base_url = self._get_base_url()
|
|
335
|
+
url = f"{base_url}{self.DELETE_TASK_URL}/{task_id}"
|
|
336
|
+
headers = {
|
|
337
|
+
"Content-Type": "application/json",
|
|
338
|
+
"Authorization": f"Bearer {self.api_key}"
|
|
339
|
+
}
|
|
340
|
+
response = self.secure_http_client.delete(url, headers=headers, timeout=self.timeout)
|
|
341
|
+
try:
|
|
342
|
+
result = response.json()
|
|
343
|
+
return result
|
|
344
|
+
except Exception as e:
|
|
345
|
+
logger.error(f"delete_video_generation_task error: {e}")
|
|
346
|
+
raise AICCException(f"Failed to delete_video_generation_task")
|
|
347
|
+
|
|
348
|
+
def video_file_download(self, task_id: str, video_file_path: str) -> bool:
|
|
349
|
+
"""
|
|
350
|
+
Download and decrypt encrypted video
|
|
351
|
+
This function polls the task status until it succeeds, then downloads
|
|
352
|
+
and decrypts the video using the provided private key.
|
|
353
|
+
Args:
|
|
354
|
+
task_id: Video generation task ID
|
|
355
|
+
video_file_path: Directory to save decrypted video
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Path to decrypted video file, or None if failed
|
|
359
|
+
"""
|
|
360
|
+
if not task_id:
|
|
361
|
+
raise AICCException("task_id must be provided")
|
|
362
|
+
|
|
363
|
+
if not video_file_path:
|
|
364
|
+
raise AICCException("video_file_path must be provided")
|
|
365
|
+
|
|
366
|
+
result = self.query_video_generation_task(task_id)
|
|
367
|
+
if not result:
|
|
368
|
+
logger.error(f"[Error] Failed to query task,task_id={task_id}")
|
|
369
|
+
return False
|
|
370
|
+
|
|
371
|
+
status = result.get("status")
|
|
372
|
+
if status == "succeeded":
|
|
373
|
+
video_url = result.get("content", {}).get("video_url")
|
|
374
|
+
if not video_url:
|
|
375
|
+
logger.error("[Error] No video_url in response")
|
|
376
|
+
return False
|
|
377
|
+
|
|
378
|
+
return self.download_file_normal(video_url, video_file_path)
|
|
379
|
+
# encrypted_key = b""
|
|
380
|
+
# if self.is_enable_video_encrypt:
|
|
381
|
+
# encrypted_key = result.get("encrypted_key")
|
|
382
|
+
# if not encrypted_key:
|
|
383
|
+
# logger.error("[Error] No encrypted_key in response")
|
|
384
|
+
# return False
|
|
385
|
+
# if not self.video_file_encrypt_priv_key:
|
|
386
|
+
# logger.error("[Error] No private key set, cannot decrypt encrypted video")
|
|
387
|
+
# return False
|
|
388
|
+
# else:
|
|
389
|
+
# # Base64 decode the encrypted key before RSA decryption
|
|
390
|
+
# encrypted_key_bytes = base64.b64decode(encrypted_key)
|
|
391
|
+
# encrypted_key = self.video_file_encrypt_priv_key.decrypt(encrypted_key_bytes)
|
|
392
|
+
#
|
|
393
|
+
# logger.info(f"\n[Downloading] Starting download and decryption...")
|
|
394
|
+
#
|
|
395
|
+
# try:
|
|
396
|
+
# if self.video_file_storage_location == "TOS":
|
|
397
|
+
# self.tos_utils.download_file(tos_url=video_url, local_file_path=video_file_path,
|
|
398
|
+
# encrypted_key=encrypted_key)
|
|
399
|
+
# else:
|
|
400
|
+
# logger.error(f"[Error] Unsupported video storage location: {self.video_file_storage_location}")
|
|
401
|
+
# return False
|
|
402
|
+
#
|
|
403
|
+
# logger.info(f"\n[Success] Decrypted video saved to: {video_file_path}")
|
|
404
|
+
# return True
|
|
405
|
+
#
|
|
406
|
+
# except Exception as e:
|
|
407
|
+
# logger.error(f"[Error] Download and decryption failed: {str(e)}")
|
|
408
|
+
# return False
|
|
409
|
+
|
|
410
|
+
elif status == "failed":
|
|
411
|
+
error_info = result.get("error", "Unknown error")
|
|
412
|
+
logger.error(f"[Error] Task failed: {error_info}")
|
|
413
|
+
return False
|
|
414
|
+
|
|
415
|
+
elif status in ["pending", "running", "queued"]:
|
|
416
|
+
logger.info(f"[Waiting] Task is {status}, please wait for it to finish...")
|
|
417
|
+
return False
|
|
418
|
+
else:
|
|
419
|
+
logger.warning(f"[Warning] Unknown status: {status}")
|
|
420
|
+
return False
|
|
421
|
+
|
|
422
|
+
def video_file_download_by_url(self, video_url: str, video_file_path: str) -> bool:
|
|
423
|
+
"""
|
|
424
|
+
Download and decrypt encrypted video
|
|
425
|
+
This function polls the task status until it succeeds, then downloads
|
|
426
|
+
and decrypts the video using the provided private key.
|
|
427
|
+
Args:
|
|
428
|
+
video_url: Video video_url
|
|
429
|
+
video_file_path: Directory to save decrypted video
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
Path to decrypted video file, or None if failed
|
|
433
|
+
"""
|
|
434
|
+
if not video_url:
|
|
435
|
+
raise AICCException("video_url must be provided")
|
|
436
|
+
|
|
437
|
+
if not video_file_path:
|
|
438
|
+
raise AICCException("video_file_path must be provided")
|
|
439
|
+
|
|
440
|
+
logger.info(f"\n[Downloading] Starting download and decryption...")
|
|
441
|
+
|
|
442
|
+
try:
|
|
443
|
+
return self.download_file_normal(video_url, video_file_path)
|
|
444
|
+
except Exception as e:
|
|
445
|
+
logger.error(f"[Error] Download and decryption failed: {str(e)}")
|
|
446
|
+
return False
|
|
447
|
+
|
|
448
|
+
def download_file_normal(self, video_url: str, video_file_path: str) -> bool:
|
|
449
|
+
"""
|
|
450
|
+
Download video file
|
|
451
|
+
Args:
|
|
452
|
+
video_url: Video video_url
|
|
453
|
+
video_file_path: Directory to save video file
|
|
454
|
+
|
|
455
|
+
Returns:
|
|
456
|
+
Path to video file, or None if failed
|
|
457
|
+
"""
|
|
458
|
+
if not video_url:
|
|
459
|
+
raise AICCException("video_url must be provided")
|
|
460
|
+
|
|
461
|
+
if not video_file_path:
|
|
462
|
+
raise AICCException("video_file_path must be provided")
|
|
463
|
+
|
|
464
|
+
if os.path.exists(video_file_path) and os.path.getsize(video_file_path) > 0:
|
|
465
|
+
logger.error(f"[Error] File {video_file_path} already exists and is not empty")
|
|
466
|
+
return False
|
|
467
|
+
|
|
468
|
+
try:
|
|
469
|
+
response = requests.get(video_url, stream=True)
|
|
470
|
+
with open(video_file_path, "wb") as f:
|
|
471
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
472
|
+
f.write(chunk)
|
|
473
|
+
|
|
474
|
+
if self.is_enable_video_encrypt:
|
|
475
|
+
if not self.video_file_encrypt_priv_key:
|
|
476
|
+
logger.error("[Error] No private key set, cannot decrypt encrypted video")
|
|
477
|
+
return False
|
|
478
|
+
|
|
479
|
+
headers = response.headers
|
|
480
|
+
encrypted_key = headers.get("x-tos-meta-enc-dek")
|
|
481
|
+
# nonce = headers.get("x-tos-meta-x-tos-meta-base-nonce")
|
|
482
|
+
if not encrypted_key:
|
|
483
|
+
logger.error("[Error] No encrypted_key in response")
|
|
484
|
+
return False
|
|
485
|
+
|
|
486
|
+
try:
|
|
487
|
+
encrypted_key_bytes = base64.b64decode(unquote(encrypted_key))
|
|
488
|
+
encrypted_key = self.video_file_encrypt_priv_key.decrypt(encrypted_key_bytes)
|
|
489
|
+
tmp_file_path = video_file_path + ".tmp"
|
|
490
|
+
decrypt_file(encrypted_key, video_file_path, tmp_file_path, 'b')
|
|
491
|
+
os.remove(video_file_path)
|
|
492
|
+
os.rename(tmp_file_path, video_file_path)
|
|
493
|
+
except Exception as e:
|
|
494
|
+
logger.error(f"[Error] Failed to decode encrypted_key: {e}")
|
|
495
|
+
return False
|
|
496
|
+
except Exception as e:
|
|
497
|
+
logger.error(f"[Error] Download failed: {e}")
|
|
498
|
+
traceback.print_exc()
|
|
499
|
+
return False
|
|
500
|
+
return True
|