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.
- local_coze/__init__.py +110 -0
- local_coze/cli/__init__.py +3 -0
- local_coze/cli/chat.py +126 -0
- local_coze/cli/cli.py +34 -0
- local_coze/cli/constants.py +7 -0
- local_coze/cli/db.py +81 -0
- local_coze/cli/embedding.py +193 -0
- local_coze/cli/image.py +162 -0
- local_coze/cli/knowledge.py +195 -0
- local_coze/cli/search.py +198 -0
- local_coze/cli/utils.py +41 -0
- local_coze/cli/video.py +191 -0
- local_coze/cli/video_edit.py +888 -0
- local_coze/cli/voice.py +351 -0
- local_coze/core/__init__.py +25 -0
- local_coze/core/client.py +253 -0
- local_coze/core/config.py +58 -0
- local_coze/core/exceptions.py +67 -0
- local_coze/database/__init__.py +29 -0
- local_coze/database/client.py +170 -0
- local_coze/database/migration.py +342 -0
- local_coze/embedding/__init__.py +31 -0
- local_coze/embedding/client.py +350 -0
- local_coze/embedding/models.py +130 -0
- local_coze/image/__init__.py +19 -0
- local_coze/image/client.py +110 -0
- local_coze/image/models.py +163 -0
- local_coze/knowledge/__init__.py +19 -0
- local_coze/knowledge/client.py +148 -0
- local_coze/knowledge/models.py +45 -0
- local_coze/llm/__init__.py +25 -0
- local_coze/llm/client.py +317 -0
- local_coze/llm/models.py +48 -0
- local_coze/memory/__init__.py +14 -0
- local_coze/memory/client.py +176 -0
- local_coze/s3/__init__.py +12 -0
- local_coze/s3/client.py +580 -0
- local_coze/s3/models.py +18 -0
- local_coze/search/__init__.py +19 -0
- local_coze/search/client.py +183 -0
- local_coze/search/models.py +57 -0
- local_coze/video/__init__.py +17 -0
- local_coze/video/client.py +347 -0
- local_coze/video/models.py +39 -0
- local_coze/video_edit/__init__.py +23 -0
- local_coze/video_edit/examples.py +340 -0
- local_coze/video_edit/frame_extractor.py +176 -0
- local_coze/video_edit/models.py +362 -0
- local_coze/video_edit/video_edit.py +631 -0
- local_coze/voice/__init__.py +17 -0
- local_coze/voice/asr.py +82 -0
- local_coze/voice/models.py +86 -0
- local_coze/voice/tts.py +94 -0
- local_coze-0.0.1.dist-info/METADATA +636 -0
- local_coze-0.0.1.dist-info/RECORD +58 -0
- local_coze-0.0.1.dist-info/WHEEL +4 -0
- local_coze-0.0.1.dist-info/entry_points.txt +3 -0
- local_coze-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""
|
|
2
|
+
视频编辑功能示例代码
|
|
3
|
+
|
|
4
|
+
本文件包含了 video_edit 模块的各种使用示例,包括:
|
|
5
|
+
1. 视频帧提取(关键帧、固定间隔、固定数量)
|
|
6
|
+
2. 视频剪辑(裁剪、拼接)
|
|
7
|
+
3. 字幕处理(添加字幕、音频转字幕)
|
|
8
|
+
4. 音频处理(提取音频、合成音视频)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from local_coze.video_edit import (
|
|
12
|
+
FrameExtractorClient,
|
|
13
|
+
VideoEditClient,
|
|
14
|
+
SubtitleConfig,
|
|
15
|
+
FontPosConfig,
|
|
16
|
+
TextItem,
|
|
17
|
+
OutputSync,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def example_extract_by_key_frame():
|
|
22
|
+
"""示例1:按关键帧提取视频帧"""
|
|
23
|
+
print("=" * 50)
|
|
24
|
+
print("示例1:按关键帧提取视频帧")
|
|
25
|
+
print("=" * 50)
|
|
26
|
+
|
|
27
|
+
custom_headers = {"x-run-mode": "test_run"}
|
|
28
|
+
client = FrameExtractorClient(custom_headers=custom_headers)
|
|
29
|
+
|
|
30
|
+
response = client.extract_by_key_frame(
|
|
31
|
+
url="https://example.com/video.mp4"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
print(f"提取的关键帧数量: {len(response.data)}")
|
|
35
|
+
for i, frame in enumerate(response.data[:3]):
|
|
36
|
+
print(f" 帧 {i + 1}: 时间={frame.time}s, URL={frame.url}")
|
|
37
|
+
print()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def example_extract_by_interval():
|
|
41
|
+
"""示例2:按固定时间间隔提取视频帧"""
|
|
42
|
+
print("=" * 50)
|
|
43
|
+
print("示例2:按固定时间间隔提取视频帧")
|
|
44
|
+
print("=" * 50)
|
|
45
|
+
|
|
46
|
+
custom_headers = {"x-run-mode": "test_run"}
|
|
47
|
+
client = FrameExtractorClient(custom_headers=custom_headers)
|
|
48
|
+
|
|
49
|
+
response = client.extract_by_interval(
|
|
50
|
+
url="https://example.com/video.mp4",
|
|
51
|
+
interval=2.0
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
print(f"每 2 秒提取一帧,共提取: {len(response.data)} 帧")
|
|
55
|
+
for i, frame in enumerate(response.data[:3]):
|
|
56
|
+
print(f" 帧 {i + 1}: 时间={frame.time}s, URL={frame.url}")
|
|
57
|
+
print()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def example_extract_by_count():
|
|
61
|
+
"""示例3:按固定数量提取视频帧"""
|
|
62
|
+
print("=" * 50)
|
|
63
|
+
print("示例3:按固定数量提取视频帧")
|
|
64
|
+
print("=" * 50)
|
|
65
|
+
|
|
66
|
+
custom_headers = {"x-run-mode": "test_run"}
|
|
67
|
+
client = FrameExtractorClient(custom_headers=custom_headers)
|
|
68
|
+
|
|
69
|
+
response = client.extract_by_count(
|
|
70
|
+
url="https://example.com/video.mp4",
|
|
71
|
+
count=10
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
print(f"均匀提取 10 帧")
|
|
75
|
+
for i, frame in enumerate(response.data):
|
|
76
|
+
print(f" 帧 {i + 1}: 时间={frame.time}s, URL={frame.url}")
|
|
77
|
+
print()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def example_video_trim():
|
|
81
|
+
"""示例4:视频裁剪"""
|
|
82
|
+
print("=" * 50)
|
|
83
|
+
print("示例4:视频裁剪")
|
|
84
|
+
print("=" * 50)
|
|
85
|
+
|
|
86
|
+
custom_headers = {"x-run-mode": "test_run"}
|
|
87
|
+
client = VideoEditClient(custom_headers=custom_headers)
|
|
88
|
+
|
|
89
|
+
response = client.video_trim(
|
|
90
|
+
video="https://example.com/video.mp4",
|
|
91
|
+
start_time=10.0,
|
|
92
|
+
end_time=30.0,
|
|
93
|
+
url_expire=3600
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
print(f"裁剪视频: 从 10 秒到 30 秒")
|
|
97
|
+
print(f"结果 URL: {response.url}")
|
|
98
|
+
print()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def example_concat_videos():
|
|
102
|
+
"""示例5:视频拼接"""
|
|
103
|
+
print("=" * 50)
|
|
104
|
+
print("示例5:视频拼接")
|
|
105
|
+
print("=" * 50)
|
|
106
|
+
|
|
107
|
+
custom_headers = {"x-run-mode": "test_run"}
|
|
108
|
+
client = VideoEditClient(custom_headers=custom_headers)
|
|
109
|
+
|
|
110
|
+
video_list = [
|
|
111
|
+
"https://example.com/video1.mp4",
|
|
112
|
+
"https://example.com/video2.mp4",
|
|
113
|
+
"https://example.com/video3.mp4"
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
response = client.concat_videos(
|
|
117
|
+
video_list=video_list,
|
|
118
|
+
url_expire=3600
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
print(f"拼接 {len(video_list)} 个视频")
|
|
122
|
+
print(f"结果 URL: {response.url}")
|
|
123
|
+
print()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def example_add_subtitles_with_text():
|
|
127
|
+
"""示例6:添加字幕(使用文本列表)"""
|
|
128
|
+
print("=" * 50)
|
|
129
|
+
print("示例6:添加字幕(使用文本列表)")
|
|
130
|
+
print("=" * 50)
|
|
131
|
+
|
|
132
|
+
custom_headers = {"x-run-mode": "test_run"}
|
|
133
|
+
client = VideoEditClient(custom_headers=custom_headers)
|
|
134
|
+
|
|
135
|
+
subtitle_config = SubtitleConfig(
|
|
136
|
+
font_pos_config=FontPosConfig(
|
|
137
|
+
pos_x="100",
|
|
138
|
+
pos_y="900",
|
|
139
|
+
width="1720",
|
|
140
|
+
height="100"
|
|
141
|
+
),
|
|
142
|
+
font_size=48,
|
|
143
|
+
font_color="#FFFFFF",
|
|
144
|
+
background_color="#000000",
|
|
145
|
+
background_alpha=0.5
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
text_list = [
|
|
149
|
+
TextItem(start_time=0.0, end_time=3.0, text="你好,世界!"),
|
|
150
|
+
TextItem(start_time=3.0, end_time=6.0, text="欢迎使用视频编辑功能!"),
|
|
151
|
+
TextItem(start_time=6.0, end_time=9.0, text="这是一个字幕示例。"),
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
response = client.add_subtitles(
|
|
155
|
+
video="https://example.com/video.mp4",
|
|
156
|
+
subtitle_config=subtitle_config,
|
|
157
|
+
text_list=text_list,
|
|
158
|
+
url_expire=3600
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
print(f"添加了 {len(text_list)} 条字幕")
|
|
162
|
+
print(f"结果 URL: {response.url}")
|
|
163
|
+
print()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def example_add_subtitles_with_file():
|
|
167
|
+
"""示例7:添加字幕(使用字幕文件)"""
|
|
168
|
+
print("=" * 50)
|
|
169
|
+
print("示例7:添加字幕(使用字幕文件)")
|
|
170
|
+
print("=" * 50)
|
|
171
|
+
|
|
172
|
+
custom_headers = {"x-run-mode": "test_run"}
|
|
173
|
+
client = VideoEditClient(custom_headers=custom_headers)
|
|
174
|
+
|
|
175
|
+
subtitle_config = SubtitleConfig(
|
|
176
|
+
font_pos_config=FontPosConfig(
|
|
177
|
+
pos_x="0",
|
|
178
|
+
pos_y="800",
|
|
179
|
+
width="1920",
|
|
180
|
+
height="200"
|
|
181
|
+
),
|
|
182
|
+
font_size=36,
|
|
183
|
+
font_color="#FFFF00"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
response = client.add_subtitles(
|
|
187
|
+
video="https://example.com/video.mp4",
|
|
188
|
+
subtitle_config=subtitle_config,
|
|
189
|
+
subtitle_url="https://example.com/subtitle.srt",
|
|
190
|
+
url_expire=3600
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
print(f"使用字幕文件添加字幕")
|
|
194
|
+
print(f"结果 URL: {response.url}")
|
|
195
|
+
print()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def example_audio_to_subtitle():
|
|
199
|
+
"""示例8:音频转字幕"""
|
|
200
|
+
print("=" * 50)
|
|
201
|
+
print("示例8:音频转字幕")
|
|
202
|
+
print("=" * 50)
|
|
203
|
+
|
|
204
|
+
custom_headers = {"x-run-mode": "test_run"}
|
|
205
|
+
client = VideoEditClient(custom_headers=custom_headers)
|
|
206
|
+
|
|
207
|
+
response = client.audio_to_subtitle(
|
|
208
|
+
audio="https://example.com/audio.mp3",
|
|
209
|
+
url_expire=3600
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
print(f"音频转字幕完成")
|
|
213
|
+
print(f"字幕文件 URL: {response.url}")
|
|
214
|
+
print()
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def example_audio_extract():
|
|
218
|
+
"""示例9:提取视频音频"""
|
|
219
|
+
print("=" * 50)
|
|
220
|
+
print("示例9:提取视频音频")
|
|
221
|
+
print("=" * 50)
|
|
222
|
+
|
|
223
|
+
custom_headers = {"x-run-mode": "test_run"}
|
|
224
|
+
client = VideoEditClient(custom_headers=custom_headers)
|
|
225
|
+
|
|
226
|
+
response = client.extract_audio(
|
|
227
|
+
video="https://example.com/video.mp4",
|
|
228
|
+
url_expire=3600
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
print(f"提取音频完成")
|
|
232
|
+
print(f"音频文件 URL: {response.url}")
|
|
233
|
+
print()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def example_compile_video_audio():
|
|
237
|
+
"""示例10:合成视频和音频"""
|
|
238
|
+
print("=" * 50)
|
|
239
|
+
print("示例10:合成视频和音频")
|
|
240
|
+
print("=" * 50)
|
|
241
|
+
|
|
242
|
+
custom_headers = {"x-run-mode": "test_run"}
|
|
243
|
+
client = VideoEditClient(custom_headers=custom_headers)
|
|
244
|
+
|
|
245
|
+
response = client.compile_video_audio(
|
|
246
|
+
video="https://example.com/video_no_audio.mp4",
|
|
247
|
+
audio="https://example.com/audio.mp3",
|
|
248
|
+
url_expire=3600
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
print(f"合成视频和音频完成")
|
|
252
|
+
print(f"结果 URL: {response.url}")
|
|
253
|
+
print()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def example_async_operations():
|
|
257
|
+
"""示例11:异步操作示例"""
|
|
258
|
+
print("=" * 50)
|
|
259
|
+
print("示例11:异步操作示例")
|
|
260
|
+
print("=" * 50)
|
|
261
|
+
|
|
262
|
+
import asyncio
|
|
263
|
+
|
|
264
|
+
async def async_example():
|
|
265
|
+
custom_headers = {"x-run-mode": "test_run"}
|
|
266
|
+
frame_client = FrameExtractorClient(custom_headers=custom_headers)
|
|
267
|
+
edit_client = VideoEditClient(custom_headers=custom_headers)
|
|
268
|
+
|
|
269
|
+
frame_response = await frame_client.extract_by_key_frame_async(
|
|
270
|
+
url="https://example.com/video.mp4"
|
|
271
|
+
)
|
|
272
|
+
print(f"异步提取关键帧: {len(frame_response.data)} 帧")
|
|
273
|
+
|
|
274
|
+
trim_response = await edit_client.video_trim_async(
|
|
275
|
+
video="https://example.com/video.mp4",
|
|
276
|
+
start_time=5.0,
|
|
277
|
+
end_time=15.0
|
|
278
|
+
)
|
|
279
|
+
print(f"异步裁剪视频: {trim_response.url}")
|
|
280
|
+
|
|
281
|
+
asyncio.run(async_example())
|
|
282
|
+
print()
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def example_with_output_sync():
|
|
286
|
+
"""示例12:使用同步输出配置"""
|
|
287
|
+
print("=" * 50)
|
|
288
|
+
print("示例12:使用同步输出配置")
|
|
289
|
+
print("=" * 50)
|
|
290
|
+
|
|
291
|
+
custom_headers = {"x-run-mode": "test_run"}
|
|
292
|
+
client = VideoEditClient(custom_headers=custom_headers)
|
|
293
|
+
|
|
294
|
+
output_sync = OutputSync(
|
|
295
|
+
bucket="my-bucket",
|
|
296
|
+
key="output/trimmed_video.mp4"
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
response = client.video_trim(
|
|
300
|
+
video="https://example.com/video.mp4",
|
|
301
|
+
start_time=0.0,
|
|
302
|
+
end_time=10.0,
|
|
303
|
+
output_sync=output_sync
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
print(f"视频已同步到指定位置")
|
|
307
|
+
print(f"结果 URL: {response.url}")
|
|
308
|
+
print()
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def main():
|
|
312
|
+
"""运行所有示例"""
|
|
313
|
+
print("\n" + "=" * 50)
|
|
314
|
+
print("视频编辑功能示例集合")
|
|
315
|
+
print("=" * 50 + "\n")
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
example_extract_by_key_frame()
|
|
319
|
+
example_extract_by_interval()
|
|
320
|
+
example_extract_by_count()
|
|
321
|
+
example_video_trim()
|
|
322
|
+
example_concat_videos()
|
|
323
|
+
example_add_subtitles_with_text()
|
|
324
|
+
example_add_subtitles_with_file()
|
|
325
|
+
example_audio_to_subtitle()
|
|
326
|
+
example_audio_extract()
|
|
327
|
+
example_compile_video_audio()
|
|
328
|
+
example_async_operations()
|
|
329
|
+
example_with_output_sync()
|
|
330
|
+
|
|
331
|
+
print("=" * 50)
|
|
332
|
+
print("所有示例运行完成!")
|
|
333
|
+
print("=" * 50)
|
|
334
|
+
|
|
335
|
+
except Exception as e:
|
|
336
|
+
print(f"\n运行示例时出错: {e}")
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
if __name__ == "__main__":
|
|
340
|
+
main()
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Dict, Optional
|
|
3
|
+
|
|
4
|
+
from cozeloop.decorator import observe
|
|
5
|
+
from coze_coding_utils.runtime_ctx.context import Context
|
|
6
|
+
|
|
7
|
+
from ..core.client import BaseClient
|
|
8
|
+
from ..core.config import Config
|
|
9
|
+
from ..core.exceptions import APIError
|
|
10
|
+
from .models import (
|
|
11
|
+
FrameExtractorByIntervalRequest,
|
|
12
|
+
FrameExtractorByKeyFrameRequest,
|
|
13
|
+
FrameExtractorResponse,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FrameExtractorClient(BaseClient):
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
config: Optional[Config] = None,
|
|
21
|
+
ctx: Optional[Context] = None,
|
|
22
|
+
custom_headers: Optional[Dict[str, str]] = None,
|
|
23
|
+
verbose: bool = False,
|
|
24
|
+
):
|
|
25
|
+
super().__init__(config, ctx, custom_headers, verbose)
|
|
26
|
+
self.base_url = self.config.base_url
|
|
27
|
+
|
|
28
|
+
@observe
|
|
29
|
+
def extract_by_key_frame(self, url: str) -> FrameExtractorResponse:
|
|
30
|
+
"""
|
|
31
|
+
按关键帧抽取视频帧
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
url: 视频 URL 链接
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
FrameExtractorResponse: 抽帧结果响应
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
APIError: 当抽帧失败时
|
|
41
|
+
"""
|
|
42
|
+
request = FrameExtractorByKeyFrameRequest(url=url)
|
|
43
|
+
|
|
44
|
+
response = self._request(
|
|
45
|
+
method="POST",
|
|
46
|
+
url=f"{self.base_url}/api/v1/integration/video_editing_utils?tool_name=frame_extractor_by_key_frame",
|
|
47
|
+
json=request.to_api_request(),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if response.get("code") != 0:
|
|
51
|
+
raise APIError(
|
|
52
|
+
f"关键帧抽取失败: {response.get('message', 'Unknown error')}",
|
|
53
|
+
code=response.get("code"),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return FrameExtractorResponse(**response)
|
|
57
|
+
|
|
58
|
+
@observe
|
|
59
|
+
def extract_by_interval(self, url: str, interval_ms: int) -> FrameExtractorResponse:
|
|
60
|
+
"""
|
|
61
|
+
等时抽帧,从视频开始,每隔 interval_ms 时间抽帧一次。
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
url: 视频 URL 链接
|
|
65
|
+
interval_ms: 间隔抽帧时间,单位: ms
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
FrameExtractorResponse: 抽帧结果响应
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
APIError: 当抽帧失败时
|
|
72
|
+
"""
|
|
73
|
+
request = FrameExtractorByIntervalRequest(url=url, interval_ms=interval_ms)
|
|
74
|
+
|
|
75
|
+
response = self._request(
|
|
76
|
+
method="POST",
|
|
77
|
+
url=f"{self.base_url}/api/v1/integration/video_editing_utils?tool_name=frame_extractor_by_interval",
|
|
78
|
+
json=request.to_api_request(),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if response.get("code") != 0:
|
|
82
|
+
raise APIError(
|
|
83
|
+
f"间隔抽帧失败: {response.get('message', 'Unknown error')}",
|
|
84
|
+
code=response.get("code"),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return FrameExtractorResponse(**response)
|
|
88
|
+
|
|
89
|
+
@observe
|
|
90
|
+
def extract_by_count(self, url: str, count: int) -> FrameExtractorResponse:
|
|
91
|
+
"""
|
|
92
|
+
定数抽帧,根据视频时长/抽取帧数计算间隔动态抽帧。
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
url: 视频 URL 链接
|
|
96
|
+
count: 抽取多少个帧
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
FrameExtractorResponse: 抽帧结果响应
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
APIError: 当抽帧失败时
|
|
103
|
+
"""
|
|
104
|
+
from .models import FrameExtractorByInterval
|
|
105
|
+
|
|
106
|
+
request = FrameExtractorByInterval(url=url, count=count)
|
|
107
|
+
|
|
108
|
+
response = self._request(
|
|
109
|
+
method="POST",
|
|
110
|
+
url=f"{self.base_url}/api/v1/integration/video_editing_utils?tool_name=frame_extractor_by_count",
|
|
111
|
+
json=request.to_api_request(),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if response.get("code") != 0:
|
|
115
|
+
raise APIError(
|
|
116
|
+
f"按数量抽帧失败: {response.get('message', 'Unknown error')}",
|
|
117
|
+
code=response.get("code"),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return FrameExtractorResponse(**response)
|
|
121
|
+
|
|
122
|
+
async def extract_by_key_frame_async(self, url: str) -> FrameExtractorResponse:
|
|
123
|
+
"""
|
|
124
|
+
按关键帧抽取视频帧(异步版本)
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
url: 视频 URL 链接
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
FrameExtractorResponse: 抽帧结果响应
|
|
131
|
+
|
|
132
|
+
Raises:
|
|
133
|
+
APIError: 当抽帧失败时
|
|
134
|
+
"""
|
|
135
|
+
loop = asyncio.get_event_loop()
|
|
136
|
+
return await loop.run_in_executor(None, self.extract_by_key_frame, url)
|
|
137
|
+
|
|
138
|
+
async def extract_by_interval_async(
|
|
139
|
+
self, url: str, interval_ms: int
|
|
140
|
+
) -> FrameExtractorResponse:
|
|
141
|
+
"""
|
|
142
|
+
等时抽帧,从视频开始,每隔 interval_ms 时间抽帧一次(异步版本)
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
url: 视频 URL 链接
|
|
146
|
+
interval_ms: 间隔抽帧时间,单位: ms
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
FrameExtractorResponse: 抽帧结果响应
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
APIError: 当抽帧失败时
|
|
153
|
+
"""
|
|
154
|
+
loop = asyncio.get_event_loop()
|
|
155
|
+
return await loop.run_in_executor(
|
|
156
|
+
None, self.extract_by_interval, url, interval_ms
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
async def extract_by_count_async(
|
|
160
|
+
self, url: str, count: int
|
|
161
|
+
) -> FrameExtractorResponse:
|
|
162
|
+
"""
|
|
163
|
+
定数抽帧,根据视频时长/抽取帧数计算间隔动态抽帧(异步版本)
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
url: 视频 URL 链接
|
|
167
|
+
count: 抽取多少个帧
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
FrameExtractorResponse: 抽帧结果响应
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
APIError: 当抽帧失败时
|
|
174
|
+
"""
|
|
175
|
+
loop = asyncio.get_event_loop()
|
|
176
|
+
return await loop.run_in_executor(None, self.extract_by_count, url, count)
|