local-coze 0.0.1__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.
Files changed (58) hide show
  1. local_coze/__init__.py +110 -0
  2. local_coze/cli/__init__.py +3 -0
  3. local_coze/cli/chat.py +126 -0
  4. local_coze/cli/cli.py +34 -0
  5. local_coze/cli/constants.py +7 -0
  6. local_coze/cli/db.py +81 -0
  7. local_coze/cli/embedding.py +193 -0
  8. local_coze/cli/image.py +162 -0
  9. local_coze/cli/knowledge.py +195 -0
  10. local_coze/cli/search.py +198 -0
  11. local_coze/cli/utils.py +41 -0
  12. local_coze/cli/video.py +191 -0
  13. local_coze/cli/video_edit.py +888 -0
  14. local_coze/cli/voice.py +351 -0
  15. local_coze/core/__init__.py +25 -0
  16. local_coze/core/client.py +253 -0
  17. local_coze/core/config.py +58 -0
  18. local_coze/core/exceptions.py +67 -0
  19. local_coze/database/__init__.py +29 -0
  20. local_coze/database/client.py +170 -0
  21. local_coze/database/migration.py +342 -0
  22. local_coze/embedding/__init__.py +31 -0
  23. local_coze/embedding/client.py +350 -0
  24. local_coze/embedding/models.py +130 -0
  25. local_coze/image/__init__.py +19 -0
  26. local_coze/image/client.py +110 -0
  27. local_coze/image/models.py +163 -0
  28. local_coze/knowledge/__init__.py +19 -0
  29. local_coze/knowledge/client.py +148 -0
  30. local_coze/knowledge/models.py +45 -0
  31. local_coze/llm/__init__.py +25 -0
  32. local_coze/llm/client.py +317 -0
  33. local_coze/llm/models.py +48 -0
  34. local_coze/memory/__init__.py +14 -0
  35. local_coze/memory/client.py +176 -0
  36. local_coze/s3/__init__.py +12 -0
  37. local_coze/s3/client.py +580 -0
  38. local_coze/s3/models.py +18 -0
  39. local_coze/search/__init__.py +19 -0
  40. local_coze/search/client.py +183 -0
  41. local_coze/search/models.py +57 -0
  42. local_coze/video/__init__.py +17 -0
  43. local_coze/video/client.py +347 -0
  44. local_coze/video/models.py +39 -0
  45. local_coze/video_edit/__init__.py +23 -0
  46. local_coze/video_edit/examples.py +340 -0
  47. local_coze/video_edit/frame_extractor.py +176 -0
  48. local_coze/video_edit/models.py +362 -0
  49. local_coze/video_edit/video_edit.py +631 -0
  50. local_coze/voice/__init__.py +17 -0
  51. local_coze/voice/asr.py +82 -0
  52. local_coze/voice/models.py +86 -0
  53. local_coze/voice/tts.py +94 -0
  54. local_coze-0.0.1.dist-info/METADATA +636 -0
  55. local_coze-0.0.1.dist-info/RECORD +58 -0
  56. local_coze-0.0.1.dist-info/WHEEL +4 -0
  57. local_coze-0.0.1.dist-info/entry_points.txt +3 -0
  58. local_coze-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,183 @@
1
+ from typing import Dict, List, Optional
2
+
3
+ from coze_coding_utils.runtime_ctx.context import Context
4
+ from cozeloop.decorator import observe
5
+
6
+ from ..core.client import BaseClient
7
+ from ..core.config import Config
8
+ from ..core.exceptions import APIError
9
+ from .models import ImageItem, SearchFilter, SearchRequest, SearchResponse, WebItem
10
+
11
+
12
+ def _convert_to_api_format(data: dict) -> dict:
13
+ result = {}
14
+ for key, value in data.items():
15
+ if value is None:
16
+ continue
17
+ if key == "query":
18
+ result["Query"] = value
19
+ elif key == "search_type":
20
+ result["SearchType"] = value
21
+ elif key == "count":
22
+ result["Count"] = value
23
+ elif key == "need_summary":
24
+ result["NeedSummary"] = value
25
+ elif key == "time_range":
26
+ result["TimeRange"] = value
27
+ elif key == "filter" and value:
28
+ result["Filter"] = {
29
+ "NeedContent": value.get("need_content", False),
30
+ "NeedUrl": value.get("need_url", False),
31
+ "Sites": value.get("sites"),
32
+ "BlockHosts": value.get("block_hosts"),
33
+ }
34
+ return result
35
+
36
+
37
+ def _convert_from_api_format(item: dict) -> dict:
38
+ result = {}
39
+ for key, value in item.items():
40
+ if key == "Id":
41
+ result["id"] = value
42
+ elif key == "SortId":
43
+ result["sort_id"] = value
44
+ elif key == "Title":
45
+ result["title"] = value
46
+ elif key == "SiteName":
47
+ result["site_name"] = value
48
+ elif key == "Url":
49
+ result["url"] = value
50
+ elif key == "Snippet":
51
+ result["snippet"] = value
52
+ elif key == "Summary":
53
+ result["summary"] = value
54
+ elif key == "Content":
55
+ result["content"] = value
56
+ elif key == "PublishTime":
57
+ result["publish_time"] = value
58
+ elif key == "LogoUrl":
59
+ result["logo_url"] = value
60
+ elif key == "RankScore":
61
+ result["rank_score"] = value
62
+ elif key == "AuthInfoDes":
63
+ result["auth_info_des"] = value
64
+ elif key == "AuthInfoLevel":
65
+ result["auth_info_level"] = value
66
+ elif key == "Image":
67
+ result["image"] = {
68
+ "url": value.get("Url"),
69
+ "width": value.get("Width"),
70
+ "height": value.get("Height"),
71
+ "shape": value.get("Shape"),
72
+ }
73
+ return result
74
+
75
+
76
+ class SearchClient(BaseClient):
77
+ def __init__(
78
+ self,
79
+ config: Optional[Config] = None,
80
+ ctx: Optional[Context] = None,
81
+ custom_headers: Optional[Dict[str, str]] = None,
82
+ verbose: bool = False,
83
+ ):
84
+ super().__init__(config, ctx, custom_headers, verbose)
85
+ self.base_url = self.config.base_url
86
+
87
+ @observe(name="web_search")
88
+ def search(
89
+ self,
90
+ query: str,
91
+ search_type: str = "web",
92
+ count: Optional[int] = 10,
93
+ need_content: Optional[bool] = False,
94
+ need_url: Optional[bool] = False,
95
+ sites: Optional[str] = None,
96
+ block_hosts: Optional[str] = None,
97
+ need_summary: Optional[bool] = True,
98
+ time_range: Optional[str] = None,
99
+ ) -> SearchResponse:
100
+ search_filter = SearchFilter(
101
+ need_content=need_content,
102
+ need_url=need_url,
103
+ sites=sites,
104
+ block_hosts=block_hosts,
105
+ )
106
+
107
+ request = SearchRequest(
108
+ query=query,
109
+ search_type=search_type,
110
+ count=count,
111
+ filter=search_filter,
112
+ need_summary=need_summary,
113
+ time_range=time_range,
114
+ )
115
+
116
+ api_request = _convert_to_api_format(request.model_dump(exclude_none=True))
117
+
118
+ response = self._request(
119
+ method="POST",
120
+ url=f"{self.base_url}/api/search_api/web_search",
121
+ json=api_request,
122
+ )
123
+
124
+ response_metadata = response.get("ResponseMetadata", {})
125
+ if response_metadata.get("Error"):
126
+ raise APIError(f"Search failed: {response_metadata.get('Error')}")
127
+
128
+ result = response.get("Result", {})
129
+
130
+ web_items = []
131
+ if result.get("WebResults"):
132
+ web_items = [
133
+ WebItem(**_convert_from_api_format(item))
134
+ for item in result.get("WebResults", [])
135
+ ]
136
+
137
+ image_items = []
138
+ if result.get("ImageResults"):
139
+ image_items = [
140
+ ImageItem(**_convert_from_api_format(item))
141
+ for item in result.get("ImageResults", [])
142
+ ]
143
+
144
+ summary = None
145
+ if result.get("Choices"):
146
+ summary = (
147
+ result.get("Choices", [{}])[0].get("Message", {}).get("Content", "")
148
+ )
149
+
150
+ return SearchResponse(
151
+ web_items=web_items, image_items=image_items, summary=summary
152
+ )
153
+
154
+ @observe(name="web_search_simple")
155
+ def web_search(
156
+ self,
157
+ query: str,
158
+ count: Optional[int] = 10,
159
+ need_summary: Optional[bool] = True,
160
+ ) -> SearchResponse:
161
+ return self.search(
162
+ query=query, search_type="web", count=count, need_summary=need_summary
163
+ )
164
+
165
+ @observe(name="web_search_with_summary")
166
+ def web_search_with_summary(
167
+ self,
168
+ query: str,
169
+ count: Optional[int] = 10,
170
+ ) -> SearchResponse:
171
+ return self.search(
172
+ query=query, search_type="web_summary", count=count, need_summary=True
173
+ )
174
+
175
+ @observe(name="image_search")
176
+ def image_search(
177
+ self,
178
+ query: str,
179
+ count: Optional[int] = 10,
180
+ ) -> SearchResponse:
181
+ return self.search(
182
+ query=query, search_type="image", count=count, need_summary=False
183
+ )
@@ -0,0 +1,57 @@
1
+ from typing import Optional, List, Literal
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ class ImageInfo(BaseModel):
6
+ url: str = Field(..., description="图片链接")
7
+ width: Optional[int] = Field(None, description="宽")
8
+ height: Optional[int] = Field(None, description="高")
9
+ shape: str = Field(..., description="横长方形/竖长方形/方形")
10
+
11
+
12
+ class WebItem(BaseModel):
13
+ id: str = Field(..., description="结果Id")
14
+ sort_id: int = Field(..., description="排序Id")
15
+ title: str = Field(..., description="标题")
16
+ site_name: Optional[str] = Field(None, description="站点名")
17
+ url: Optional[str] = Field(None, description="落地页")
18
+ snippet: str = Field(..., description="普通摘要")
19
+ summary: Optional[str] = Field(None, description="精准摘要")
20
+ content: Optional[str] = Field(None, description="正文")
21
+ publish_time: Optional[str] = Field(None, description="发布时间")
22
+ logo_url: Optional[str] = Field(None, description="落地页IconUrl链接")
23
+ rank_score: Optional[float] = Field(None, description="得分")
24
+ auth_info_des: str = Field(..., description="权威度描述")
25
+ auth_info_level: int = Field(..., description="权威度评级")
26
+
27
+
28
+ class ImageItem(BaseModel):
29
+ id: str = Field(..., description="结果Id")
30
+ sort_id: int = Field(..., description="排序Id")
31
+ title: Optional[str] = Field(None, description="标题")
32
+ site_name: Optional[str] = Field(None, description="站点名")
33
+ url: Optional[str] = Field(None, description="落地页")
34
+ publish_time: Optional[str] = Field(None, description="发布时间")
35
+ image: ImageInfo = Field(..., description="图片详情")
36
+
37
+
38
+ class SearchFilter(BaseModel):
39
+ need_content: Optional[bool] = Field(False, description="是否仅返回有正文的结果")
40
+ need_url: Optional[bool] = Field(False, description="是否仅返回原文链接的结果")
41
+ sites: Optional[str] = Field(None, description="指定搜索的Site范围")
42
+ block_hosts: Optional[str] = Field(None, description="指定屏蔽的搜索Site")
43
+
44
+
45
+ class SearchRequest(BaseModel):
46
+ query: str = Field(..., description="用户搜索query")
47
+ search_type: Literal["web", "web_summary", "image"] = Field("web", description="搜索类型")
48
+ count: Optional[int] = Field(10, description="返回条数")
49
+ filter: Optional[SearchFilter] = None
50
+ need_summary: Optional[bool] = Field(True, description="是否需要精准摘要")
51
+ time_range: Optional[str] = Field(None, description="指定搜索的发文时间")
52
+
53
+
54
+ class SearchResponse(BaseModel):
55
+ web_items: List[WebItem] = Field(default_factory=list, description="Web搜索结果")
56
+ image_items: List[ImageItem] = Field(default_factory=list, description="图片搜索结果")
57
+ summary: Optional[str] = Field(None, description="搜索结果摘要")
@@ -0,0 +1,17 @@
1
+ from .client import VideoGenerationClient
2
+ from .models import (
3
+ VideoGenerationRequest,
4
+ VideoGenerationTask,
5
+ TextContent,
6
+ ImageURLContent,
7
+ ImageURL
8
+ )
9
+
10
+ __all__ = [
11
+ "VideoGenerationClient",
12
+ "VideoGenerationRequest",
13
+ "VideoGenerationTask",
14
+ "TextContent",
15
+ "ImageURLContent",
16
+ "ImageURL"
17
+ ]
@@ -0,0 +1,347 @@
1
+ import asyncio
2
+ import logging
3
+ import time
4
+ from typing import Dict, List, Optional, Tuple, Union
5
+
6
+ from coze_coding_utils.runtime_ctx.context import Context
7
+ from cozeloop.decorator import observe
8
+
9
+ from ..core.client import BaseClient
10
+ from ..core.config import Config
11
+ from ..core.exceptions import APIError
12
+ from .models import (
13
+ ImageURLContent,
14
+ TextContent,
15
+ VideoGenerationRequest,
16
+ VideoGenerationTask,
17
+ )
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class VideoGenerationClient(BaseClient):
23
+ def __init__(
24
+ self,
25
+ config: Optional[Config] = None,
26
+ ctx: Optional[Context] = None,
27
+ custom_headers: Optional[Dict[str, str]] = None,
28
+ verbose: bool = False,
29
+ ):
30
+ super().__init__(config, ctx, custom_headers, verbose)
31
+ self.base_url = self.config.base_url
32
+
33
+ def _create_task(
34
+ self,
35
+ model: str,
36
+ content: List[Union[TextContent, ImageURLContent]],
37
+ resolution: Optional[str] = "720p",
38
+ ratio: Optional[str] = "16:9",
39
+ duration: Optional[int] = 5,
40
+ watermark: Optional[bool] = True,
41
+ seed: Optional[int] = None,
42
+ camerafixed: Optional[bool] = False,
43
+ generate_audio: Optional[bool] = True,
44
+ ) -> str:
45
+ request = VideoGenerationRequest(
46
+ model=model,
47
+ content=content,
48
+ resolution=resolution,
49
+ ratio=ratio,
50
+ duration=duration,
51
+ watermark=watermark,
52
+ seed=seed,
53
+ camerafixed=camerafixed,
54
+ generate_audio=generate_audio,
55
+ )
56
+
57
+ response = self._request(
58
+ method="POST",
59
+ url=f"{self.base_url}/api/v3/contents/generations/tasks",
60
+ json=request.model_dump(exclude_none=True),
61
+ )
62
+
63
+ return response.get("id")
64
+
65
+ def _get_task_status(self, task_id: str) -> VideoGenerationTask:
66
+ response = self._request(
67
+ method="GET",
68
+ url=f"{self.base_url}/api/v3/contents/generations/tasks/{task_id}",
69
+ )
70
+
71
+ return VideoGenerationTask(**response)
72
+
73
+ @observe(name="video_generation")
74
+ def video_generation(
75
+ self,
76
+ content_items: List[Union[TextContent, ImageURLContent]],
77
+ callback_url: Optional[str] = None,
78
+ return_last_frame: Optional[bool] = False,
79
+ model: str = "doubao-seedance-1-5-pro-251215",
80
+ max_wait_time: int = 900,
81
+ resolution: Optional[str] = "720p",
82
+ ratio: Optional[str] = "16:9",
83
+ duration: Optional[int] = 5,
84
+ watermark: Optional[bool] = True,
85
+ seed: Optional[int] = None,
86
+ camerafixed: Optional[bool] = False,
87
+ generate_audio: Optional[bool] = True,
88
+ ) -> Tuple[Optional[str], Dict, str]:
89
+ """
90
+ 视频生成函数(同步)。调用生视频模型,非大语言模型,仅用于生成视频
91
+
92
+ Args:
93
+ content_items: 输入给模型,生成视频的信息,支持文本信息和图片信息。
94
+ callback_url: 可选,填写本次生成任务结果的回调通知地址。当视频生成任务有状态变化时,方舟将向此地址推送 POST 请求。
95
+ return_last_frame: 可选,返回生成视频的尾帧图像。设置为 true 后,可获取视频的尾帧图像。
96
+ model: 模型名称,默认使用 doubao-seedance-1-5-pro-251215
97
+ max_wait_time: 最大等待时间(秒),默认 900 秒
98
+ resolution: 视频分辨率,可选 "480p" 或 "720p",默认 "720p"
99
+ ratio: 视频比例,可选 "16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "adaptive",默认 "16:9"
100
+ duration: 视频时长(秒),范围 4 到 12,默认 5,若是 -1,则由模型决定长度
101
+ watermark: 是否添加水印,默认 True
102
+ seed: 随机种子,用于复现生成结果
103
+ camerafixed: 是否固定摄像机位置,默认 False
104
+ generate_audio: 是否生成音频,默认 True
105
+
106
+ Returns:
107
+ Tuple[Optional[str], Dict, str]: (视频URL, 完整响应数据字典, 尾帧图像URL)
108
+ 完整响应数据示例:
109
+ {
110
+ "id": "cgt-2025******-****",
111
+ "model": "doubao-seedance-1-5-pro-251215",
112
+ "status": "succeeded",
113
+ "content": {
114
+ "video_url": "https://ark-content-generation-cn-beijing.tos-cn-beijing.volces.com/..."
115
+ },
116
+ "seed": 10,
117
+ "resolution": "720p",
118
+ "ratio": "16:9",
119
+ "duration": 5,
120
+ "framespersecond": 24,
121
+ "usage": {
122
+ "completion_tokens": 108900,
123
+ "total_tokens": 108900
124
+ },
125
+ "created_at": 1743414619,
126
+ "updated_at": 1743414673
127
+ }
128
+
129
+ Raises:
130
+ APIError: 当视频生成失败、超时或出现其他错误时
131
+ """
132
+ poll_interval = 5
133
+
134
+ request_data = {
135
+ "model": model,
136
+ "content": [item.model_dump() for item in content_items],
137
+ }
138
+
139
+ if callback_url:
140
+ request_data["callback_url"] = callback_url
141
+ if return_last_frame:
142
+ request_data["return_last_frame"] = return_last_frame
143
+
144
+ if resolution is not None:
145
+ request_data["resolution"] = resolution
146
+ if ratio is not None:
147
+ request_data["ratio"] = ratio
148
+ if duration is not None:
149
+ if duration < 4 or duration > 12:
150
+ # 兜底策略
151
+ duration = -1
152
+ request_data["duration"] = duration
153
+ if watermark is not None:
154
+ request_data["watermark"] = watermark
155
+ if seed is not None:
156
+ request_data["seed"] = seed
157
+ if camerafixed is not None:
158
+ request_data["camerafixed"] = camerafixed
159
+ if generate_audio is not None:
160
+ request_data["generate_audio"] = generate_audio
161
+
162
+ task_id = None
163
+ retry_count = 0
164
+ max_retries = 3
165
+
166
+ while retry_count < max_retries:
167
+ try:
168
+ response = self._request(
169
+ method="POST",
170
+ url=f"{self.base_url}/api/v3/contents/generations/tasks",
171
+ json=request_data,
172
+ )
173
+
174
+ task_id = response.get("id")
175
+ if not task_id:
176
+ raise APIError("创建视频生成任务失败:响应中缺少任务ID")
177
+
178
+ logger.info(f"视频生成任务创建成功,任务ID: {task_id}")
179
+ break
180
+
181
+ except APIError as e:
182
+ retry_count += 1
183
+ error_msg = str(e)
184
+ logger.error(
185
+ f"创建视频生成任务失败(尝试 {retry_count}/{max_retries}): {error_msg}"
186
+ )
187
+
188
+ if retry_count >= max_retries:
189
+ raise APIError(
190
+ f"创建视频生成任务失败,已重试{max_retries}次: {error_msg}"
191
+ )
192
+
193
+ if "rate limit" in error_msg.lower() or "429" in error_msg:
194
+ wait_time = min(2**retry_count, 10)
195
+ logger.warning(f"遇到速率限制,等待 {wait_time} 秒后重试...")
196
+ time.sleep(wait_time)
197
+ elif "timeout" in error_msg.lower():
198
+ logger.warning(f"请求超时,等待 2 秒后重试...")
199
+ time.sleep(2)
200
+ else:
201
+ raise
202
+
203
+ start_time = time.time()
204
+ poll_count = 0
205
+
206
+ while time.time() - start_time < max_wait_time:
207
+ poll_count += 1
208
+
209
+ try:
210
+ response = self._request(
211
+ method="GET",
212
+ url=f"{self.base_url}/api/v3/contents/generations/tasks/{task_id}",
213
+ )
214
+
215
+ status = response.get("status")
216
+ logger.debug(f"任务 {task_id} 状态检查 #{poll_count}: {status}")
217
+
218
+ if status == "succeeded":
219
+ video_url = response.get("content", {}).get("video_url")
220
+ last_frame_url = response.get("content", {}).get(
221
+ "last_frame_url", ""
222
+ )
223
+
224
+ if not video_url:
225
+ raise APIError(
226
+ f"视频生成成功但响应中缺少视频URL,任务ID: {task_id}"
227
+ )
228
+
229
+ logger.info(
230
+ f"视频生成成功,任务ID: {task_id}, 视频URL: {video_url}"
231
+ )
232
+ return video_url, response, last_frame_url
233
+
234
+ elif status == "failed":
235
+ error_message = response.get("error_message", "未知错误")
236
+ logger.error(
237
+ f"视频生成失败,任务ID: {task_id}, 错误: {error_message}"
238
+ )
239
+ raise APIError(f"视频生成失败: {error_message}")
240
+
241
+ elif status == "cancelled":
242
+ logger.warning(f"视频生成任务被取消,任务ID: {task_id}")
243
+ return None, response, ""
244
+
245
+ elif status in ["queued", "running"]:
246
+ time.sleep(poll_interval)
247
+ continue
248
+
249
+ else:
250
+ logger.warning(f"未知的任务状态: {status},任务ID: {task_id}")
251
+ time.sleep(poll_interval)
252
+ continue
253
+
254
+ except APIError as e:
255
+ error_msg = str(e)
256
+ logger.error(f"查询任务状态失败,任务ID: {task_id}, 错误: {error_msg}")
257
+
258
+ if "not found" in error_msg.lower() or "404" in error_msg:
259
+ raise APIError(f"任务不存在或已过期,任务ID: {task_id}")
260
+
261
+ if time.time() - start_time >= max_wait_time:
262
+ raise
263
+
264
+ time.sleep(poll_interval)
265
+ continue
266
+
267
+ elapsed_time = int(time.time() - start_time)
268
+ logger.error(f"视频生成超时,任务ID: {task_id}, 已等待: {elapsed_time}秒")
269
+ raise APIError(f"视频生成超时,已等待 {elapsed_time} 秒,任务ID: {task_id}")
270
+
271
+ async def video_generation_async(
272
+ self,
273
+ content_items: List[Union[TextContent, ImageURLContent]],
274
+ callback_url: Optional[str] = None,
275
+ return_last_frame: Optional[bool] = False,
276
+ model: str = "doubao-seedance-1-5-pro-251215",
277
+ max_wait_time: int = 900,
278
+ resolution: Optional[str] = "720p",
279
+ ratio: Optional[str] = "16:9",
280
+ duration: Optional[int] = 5,
281
+ watermark: Optional[bool] = True,
282
+ seed: Optional[int] = None,
283
+ camerafixed: Optional[bool] = False,
284
+ generate_audio: Optional[bool] = True,
285
+ ) -> Tuple[Optional[str], Dict, str]:
286
+ """
287
+ 视频生成函数(异步)。调用生视频模型,非大语言模型,仅用于生成视频
288
+
289
+ 适用于批量生成视频的场景,可以并发执行多个视频生成任务。
290
+
291
+ Example:
292
+ ```python
293
+ import asyncio
294
+
295
+ async def batch_generate():
296
+ prompts = ["小猫玩球", "日落海滩", "城市夜景"]
297
+ tasks = [
298
+ client.video_generation_async(
299
+ content_items=[TextContent(text=prompt)]
300
+ )
301
+ for prompt in prompts
302
+ ]
303
+ results = await asyncio.gather(*tasks)
304
+ return results
305
+
306
+ results = asyncio.run(batch_generate())
307
+ ```
308
+
309
+ Args:
310
+ content_items: 输入给模型,生成视频的信息,支持文本信息和图片信息。
311
+ callback_url: 可选,填写本次生成任务结果的回调通知地址。
312
+ return_last_frame: 可选,返回生成视频的尾帧图像。
313
+ model: 模型名称,默认使用 doubao-seedance-1-5-pro-251215
314
+ max_wait_time: 最大等待时间(秒),默认 900 秒
315
+ resolution: 视频分辨率,可选 "480p" 或 "720p",默认 "720p"
316
+ ratio: 视频比例,可选 "16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "adaptive",默认 "16:9"
317
+ duration: 视频时长(秒),范围 -1 到 12,默认 5
318
+ watermark: 是否添加水印,默认 True
319
+ seed: 随机种子,用于复现生成结果
320
+ camerafixed: 是否固定摄像机位置,默认 False
321
+ generate_audio: 是否生成音频,默认 True
322
+
323
+ Returns:
324
+ Tuple[Optional[str], Dict, str]: (视频URL, 完整响应数据字典, 尾帧图像URL)
325
+
326
+ Raises:
327
+ APIError: 当视频生成失败、超时或出现其他错误时
328
+ """
329
+ loop = asyncio.get_event_loop()
330
+ result = await loop.run_in_executor(
331
+ None,
332
+ lambda: self.video_generation(
333
+ content_items=content_items,
334
+ callback_url=callback_url,
335
+ return_last_frame=return_last_frame,
336
+ model=model,
337
+ max_wait_time=max_wait_time,
338
+ resolution=resolution,
339
+ ratio=ratio,
340
+ duration=duration,
341
+ watermark=watermark,
342
+ seed=seed,
343
+ camerafixed=camerafixed,
344
+ generate_audio=generate_audio,
345
+ ),
346
+ )
347
+ return result
@@ -0,0 +1,39 @@
1
+ from typing import List, Literal, Optional, Union
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class ImageURL(BaseModel):
7
+ url: str
8
+
9
+
10
+ class ImageURLContent(BaseModel):
11
+ type: Literal["image_url"] = "image_url"
12
+ image_url: ImageURL
13
+ role: Optional[Literal["first_frame", "last_frame", "reference_image"]] = None
14
+
15
+
16
+ class TextContent(BaseModel):
17
+ type: Literal["text"] = "text"
18
+ text: str
19
+
20
+
21
+ class VideoGenerationRequest(BaseModel):
22
+ model: str
23
+ content: List[Union[TextContent, ImageURLContent]]
24
+ resolution: Optional[Literal["480p", "720p", "1080p"]] = "720p"
25
+ ratio: Optional[
26
+ Literal["16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "adaptive"]
27
+ ] = "16:9"
28
+ duration: Optional[int] = Field(default=5, ge=-1, le=12)
29
+ watermark: Optional[bool] = True
30
+ seed: Optional[int] = None
31
+ camerafixed: Optional[bool] = False
32
+ generate_audio: Optional[bool] = True
33
+
34
+
35
+ class VideoGenerationTask(BaseModel):
36
+ id: str
37
+ status: Literal["processing", "completed", "failed"]
38
+ video_url: Optional[str] = None
39
+ error_message: Optional[str] = None
@@ -0,0 +1,23 @@
1
+ from .frame_extractor import FrameExtractorClient
2
+ from .video_edit import VideoEditClient
3
+ from .models import (
4
+ FrameExtractorResponse,
5
+ FrameChunk,
6
+ VideoEditResponse,
7
+ SubtitleConfig,
8
+ FontPosConfig,
9
+ TextItem,
10
+ OutputSync,
11
+ )
12
+
13
+ __all__ = [
14
+ "FrameExtractorClient",
15
+ "VideoEditClient",
16
+ "FrameExtractorResponse",
17
+ "FrameChunk",
18
+ "VideoEditResponse",
19
+ "SubtitleConfig",
20
+ "FontPosConfig",
21
+ "TextItem",
22
+ "OutputSync",
23
+ ]