pycapcut 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 (88) hide show
  1. pyJianYingDraft/__init__.py +276 -0
  2. pyJianYingDraft/animation.py +140 -0
  3. pyJianYingDraft/assets/__init__.py +45 -0
  4. pyJianYingDraft/assets/draft_content_template.json +125 -0
  5. pyJianYingDraft/assets/draft_meta_info.json +67 -0
  6. pyJianYingDraft/audio_segment.py +225 -0
  7. pyJianYingDraft/draft_content_template.json +119 -0
  8. pyJianYingDraft/draft_folder.py +153 -0
  9. pyJianYingDraft/effect_segment.py +36 -0
  10. pyJianYingDraft/exceptions.py +23 -0
  11. pyJianYingDraft/jianying_controller.py +230 -0
  12. pyJianYingDraft/keyframe.py +94 -0
  13. pyJianYingDraft/local_materials.py +198 -0
  14. pyJianYingDraft/metadata/__init__.py +51 -0
  15. pyJianYingDraft/metadata/animation_meta.py +689 -0
  16. pyJianYingDraft/metadata/audio_effect_meta.py +367 -0
  17. pyJianYingDraft/metadata/audio_scene_effect.py +252 -0
  18. pyJianYingDraft/metadata/effect_meta.py +191 -0
  19. pyJianYingDraft/metadata/filter_meta.py +603 -0
  20. pyJianYingDraft/metadata/font_meta.py +341 -0
  21. pyJianYingDraft/metadata/mask_meta.py +15 -0
  22. pyJianYingDraft/metadata/speech_to_song.py +12 -0
  23. pyJianYingDraft/metadata/text_intro.py +152 -0
  24. pyJianYingDraft/metadata/text_loop.py +105 -0
  25. pyJianYingDraft/metadata/text_outro.py +105 -0
  26. pyJianYingDraft/metadata/tone_effect.py +107 -0
  27. pyJianYingDraft/metadata/transition_meta.py +733 -0
  28. pyJianYingDraft/metadata/video_character_effect.py +1913 -0
  29. pyJianYingDraft/metadata/video_effect_meta.py +9619 -0
  30. pyJianYingDraft/metadata/video_group_animation.py +131 -0
  31. pyJianYingDraft/metadata/video_intro.py +103 -0
  32. pyJianYingDraft/metadata/video_outro.py +80 -0
  33. pyJianYingDraft/metadata/video_scene_effect.py +7706 -0
  34. pyJianYingDraft/script_file.py +808 -0
  35. pyJianYingDraft/segment.py +256 -0
  36. pyJianYingDraft/template_mode.py +221 -0
  37. pyJianYingDraft/text_segment.py +436 -0
  38. pyJianYingDraft/time_util.py +99 -0
  39. pyJianYingDraft/track.py +138 -0
  40. pyJianYingDraft/util.py +58 -0
  41. pyJianYingDraft/video_segment.py +526 -0
  42. pycapcut/__init__.py +276 -0
  43. pycapcut/animation.py +140 -0
  44. pycapcut/assets/__init__.py +45 -0
  45. pycapcut/assets/draft_content_template.json +132 -0
  46. pycapcut/assets/draft_meta_info.json +73 -0
  47. pycapcut/audio_segment.py +225 -0
  48. pycapcut/draft_folder.py +153 -0
  49. pycapcut/effect_segment.py +36 -0
  50. pycapcut/exceptions.py +23 -0
  51. pycapcut/jianying_controller.py +230 -0
  52. pycapcut/keyframe.py +94 -0
  53. pycapcut/local_materials.py +198 -0
  54. pycapcut/metadata/__init__.py +51 -0
  55. pycapcut/metadata/audio_scene_effect.py +252 -0
  56. pycapcut/metadata/effect_meta.py +191 -0
  57. pycapcut/metadata/filter_meta.py +603 -0
  58. pycapcut/metadata/font_meta.py +341 -0
  59. pycapcut/metadata/mask_meta.py +15 -0
  60. pycapcut/metadata/speech_to_song.py +12 -0
  61. pycapcut/metadata/text_intro.py +152 -0
  62. pycapcut/metadata/text_loop.py +105 -0
  63. pycapcut/metadata/text_outro.py +105 -0
  64. pycapcut/metadata/tone_effect.py +107 -0
  65. pycapcut/metadata/transition_meta.py +733 -0
  66. pycapcut/metadata/video_character_effect.py +1913 -0
  67. pycapcut/metadata/video_group_animation.py +131 -0
  68. pycapcut/metadata/video_intro.py +103 -0
  69. pycapcut/metadata/video_outro.py +80 -0
  70. pycapcut/metadata/video_scene_effect.py +7706 -0
  71. pycapcut/script_file.py +808 -0
  72. pycapcut/segment.py +256 -0
  73. pycapcut/template_mode.py +221 -0
  74. pycapcut/text_segment.py +436 -0
  75. pycapcut/time_util.py +99 -0
  76. pycapcut/track.py +138 -0
  77. pycapcut/util.py +58 -0
  78. pycapcut/video_segment.py +526 -0
  79. pycapcut-0.0.1.dist-info/METADATA +77 -0
  80. pycapcut-0.0.1.dist-info/RECORD +88 -0
  81. pycapcut-0.0.1.dist-info/WHEEL +5 -0
  82. pycapcut-0.0.1.dist-info/top_level.txt +1 -0
  83. tools/__init__.py +17 -0
  84. tools/build_release.py +270 -0
  85. tools/code_generator.py +247 -0
  86. tools/extract_all.py +256 -0
  87. tools/extractor_base.py +132 -0
  88. tools/jianying_extractor.py +262 -0
pycapcut/util.py ADDED
@@ -0,0 +1,58 @@
1
+ """辅助函数,主要与模板模式有关"""
2
+
3
+ import inspect
4
+
5
+ from typing import Union, Type
6
+ from typing import List, Dict, Any
7
+
8
+ JsonExportable = Union[int, float, bool, str, List["JsonExportable"], Dict[str, "JsonExportable"]]
9
+
10
+ def provide_ctor_defaults(cls: Type) -> Dict[str, Any]:
11
+ """为构造函数提供默认值,以绕开构造函数的参数限制"""
12
+
13
+ signature = inspect.signature(cls.__init__)
14
+ provided_defaults: Dict[str, Any] = {}
15
+
16
+ for name, param in signature.parameters.items():
17
+ if name == 'self': continue
18
+ if param.default is not inspect.Parameter.empty: continue
19
+
20
+ if param.annotation is int or param.annotation is float:
21
+ provided_defaults[name] = 0
22
+ elif param.annotation is str:
23
+ provided_defaults[name] = ""
24
+ elif param.annotation is bool:
25
+ provided_defaults[name] = False
26
+ else:
27
+ raise ValueError(f"Unsupported parameter type: {param.annotation}")
28
+
29
+ return provided_defaults
30
+
31
+ def assign_attr_with_json(obj: object, attrs: List[str], json_data: Dict[str, Any]):
32
+ """根据json数据赋值给指定的对象属性
33
+
34
+ 若有复杂类型,则尝试调用其`import_json`方法进行构造
35
+ """
36
+ type_hints: Dict[str, Type] = {}
37
+ for cls in obj.__class__.__mro__:
38
+ if '__annotations__' in cls.__dict__:
39
+ type_hints.update(cls.__annotations__)
40
+
41
+ for attr in attrs:
42
+ if hasattr(type_hints[attr], 'import_json'):
43
+ obj.__setattr__(attr, type_hints[attr].import_json(json_data[attr]))
44
+ else:
45
+ obj.__setattr__(attr, type_hints[attr](json_data[attr]))
46
+
47
+ def export_attr_to_json(obj: object, attrs: List[str]) -> Dict[str, JsonExportable]:
48
+ """将对象属性导出为json数据
49
+
50
+ 若有复杂类型,则尝试调用其`export_json`方法进行导出
51
+ """
52
+ json_data: Dict[str, Any] = {}
53
+ for attr in attrs:
54
+ if hasattr(getattr(obj, attr), 'export_json'):
55
+ json_data[attr] = getattr(obj, attr).export_json()
56
+ else:
57
+ json_data[attr] = getattr(obj, attr)
58
+ return json_data
@@ -0,0 +1,526 @@
1
+ """定义视频片段及其相关类
2
+
3
+ 包含图像调节设置、动画效果、特效、转场等相关类
4
+ """
5
+
6
+ import uuid
7
+ from copy import deepcopy
8
+
9
+ from typing import Optional, Literal, Union
10
+ from typing import Dict, List, Tuple, Any
11
+
12
+ from .time_util import tim, Timerange
13
+ from .segment import VisualSegment, ClipSettings
14
+ from .local_materials import VideoMaterial
15
+ from .animation import SegmentAnimations, VideoAnimation
16
+
17
+ from .metadata import EffectMeta, EffectParamInstance
18
+ from .metadata import MaskMeta, MaskType, FilterType, TransitionType
19
+ from .metadata import IntroType, OutroType, GroupAnimationType
20
+ from .metadata import VideoSceneEffectType, VideoCharacterEffectType
21
+
22
+ class Mask:
23
+ """蒙版对象"""
24
+
25
+ mask_meta: MaskMeta
26
+ """蒙版元数据"""
27
+ global_id: str
28
+ """蒙版全局id, 由程序自动生成"""
29
+
30
+ center_x: float
31
+ """蒙版中心x坐标, 以半素材宽为单位"""
32
+ center_y: float
33
+ """蒙版中心y坐标, 以半素材高为单位"""
34
+ width: float
35
+ height: float
36
+ aspect_ratio: float
37
+
38
+ rotation: float
39
+ invert: bool
40
+ feather: float
41
+ """羽化程度, 0-1"""
42
+ round_corner: float
43
+ """矩形蒙版的圆角, 0-1"""
44
+
45
+ def __init__(self, mask_meta: MaskMeta,
46
+ cx: float, cy: float, w: float, h: float,
47
+ ratio: float, rot: float, inv: bool, feather: float, round_corner: float):
48
+ self.mask_meta = mask_meta
49
+ self.global_id = uuid.uuid4().hex
50
+
51
+ self.center_x, self.center_y = cx, cy
52
+ self.width, self.height = w, h
53
+ self.aspect_ratio = ratio
54
+
55
+ self.rotation = rot
56
+ self.invert = inv
57
+ self.feather = feather
58
+ self.round_corner = round_corner
59
+
60
+ def export_json(self) -> Dict[str, Any]:
61
+ return {
62
+ "config": {
63
+ "aspectRatio": self.aspect_ratio,
64
+ "centerX": self.center_x,
65
+ "centerY": self.center_y,
66
+ "feather": self.feather,
67
+ "height": self.height,
68
+ "invert": self.invert,
69
+ "rotation": self.rotation,
70
+ "roundCorner": self.round_corner,
71
+ "width": self.width
72
+ },
73
+ "id": self.global_id,
74
+ "name": self.mask_meta.name,
75
+ "platform": "all",
76
+ "position_info": "",
77
+ "resource_type": self.mask_meta.resource_type,
78
+ "resource_id": self.mask_meta.resource_id,
79
+ "type": "mask"
80
+ # 不导出path字段
81
+ }
82
+
83
+ class VideoEffect:
84
+ """视频特效素材"""
85
+
86
+ name: str
87
+ """特效名称"""
88
+ global_id: str
89
+ """特效全局id, 由程序自动生成"""
90
+ effect_id: str
91
+ """某种特效id, 由剪映本身提供"""
92
+ resource_id: str
93
+ """资源id, 由剪映本身提供"""
94
+
95
+ effect_type: Literal["video_effect", "face_effect"]
96
+ apply_target_type: Literal[0, 2]
97
+ """应用目标类型, 0: 片段, 2: 全局"""
98
+
99
+ adjust_params: List[EffectParamInstance]
100
+
101
+ def __init__(self, effect_meta: Union[VideoSceneEffectType, VideoCharacterEffectType],
102
+ params: Optional[List[Optional[float]]] = None, *,
103
+ apply_target_type: Literal[0, 2] = 0):
104
+ """根据给定的特效元数据及参数列表构造一个视频特效对象, params的范围是0~100"""
105
+
106
+ self.name = effect_meta.value.name
107
+ self.global_id = uuid.uuid4().hex
108
+ self.effect_id = effect_meta.value.effect_id
109
+ self.resource_id = effect_meta.value.resource_id
110
+ self.adjust_params = []
111
+
112
+ if isinstance(effect_meta, VideoSceneEffectType):
113
+ self.effect_type = "video_effect"
114
+ elif isinstance(effect_meta, VideoCharacterEffectType):
115
+ self.effect_type = "face_effect"
116
+ else:
117
+ raise TypeError("Invalid effect meta type %s" % type(effect_meta))
118
+ self.apply_target_type = apply_target_type
119
+
120
+ self.adjust_params = effect_meta.value.parse_params(params)
121
+
122
+ def export_json(self) -> Dict[str, Any]:
123
+ return {
124
+ "adjust_params": [param.export_json() for param in self.adjust_params],
125
+ "apply_target_type": self.apply_target_type,
126
+ "apply_time_range": None,
127
+ "category_id": "", # 一律设为空
128
+ "category_name": "", # 一律设为空
129
+ "common_keyframes": [],
130
+ "disable_effect_faces": [],
131
+ "effect_id": self.effect_id,
132
+ "formula_id": "",
133
+ "id": self.global_id,
134
+ "name": self.name,
135
+ "platform": "all",
136
+ "render_index": 11000,
137
+ "resource_id": self.resource_id,
138
+ "source_platform": 0,
139
+ "time_range": None,
140
+ "track_render_index": 0,
141
+ "type": self.effect_type,
142
+ "value": 1.0,
143
+ "version": ""
144
+ # 不导出path、request_id和algorithm_artifact_path字段
145
+ }
146
+
147
+ class Filter:
148
+ """滤镜素材"""
149
+
150
+ global_id: str
151
+ """滤镜全局id, 由程序自动生成"""
152
+
153
+ effect_meta: EffectMeta
154
+ """滤镜的元数据"""
155
+ intensity: float
156
+ """滤镜强度(滤镜的唯一参数)"""
157
+
158
+ apply_target_type: Literal[0, 2]
159
+ """应用目标类型, 0: 片段, 2: 全局"""
160
+
161
+ def __init__(self, meta: EffectMeta, intensity: float, *,
162
+ apply_target_type: Literal[0, 2] = 0):
163
+ """根据给定的滤镜元数据及强度构造滤镜素材对象"""
164
+
165
+ self.global_id = uuid.uuid4().hex
166
+ self.effect_meta = meta
167
+ self.intensity = intensity
168
+ self.apply_target_type = apply_target_type
169
+
170
+ def export_json(self) -> Dict[str, Any]:
171
+ return {
172
+ "adjust_params": [],
173
+ "algorithm_artifact_path": "",
174
+ "apply_target_type": self.apply_target_type,
175
+ "bloom_params": None,
176
+ "category_id": "", # 一律设为空
177
+ "category_name": "", # 一律设为空
178
+ "color_match_info": {
179
+ "source_feature_path": "",
180
+ "target_feature_path": "",
181
+ "target_image_path": ""
182
+ },
183
+ "effect_id": self.effect_meta.effect_id,
184
+ "enable_skin_tone_correction": False,
185
+ "exclusion_group": [],
186
+ "face_adjust_params": [],
187
+ "formula_id": "",
188
+ "id": self.global_id,
189
+ "intensity_key": "",
190
+ "multi_language_current": "",
191
+ "name": self.effect_meta.name,
192
+ "panel_id": "",
193
+ "platform": "all",
194
+ "resource_id": self.effect_meta.resource_id,
195
+ "source_platform": 1,
196
+ "sub_type": "none",
197
+ "time_range": None,
198
+ "type": "filter",
199
+ "value": self.intensity,
200
+ "version": ""
201
+ # 不导出path和request_id
202
+ }
203
+
204
+ class Transition:
205
+ """转场对象"""
206
+
207
+ name: str
208
+ """转场名称"""
209
+ global_id: str
210
+ """转场全局id, 由程序自动生成"""
211
+ effect_id: str
212
+ """转场效果id, 由剪映本身提供"""
213
+ resource_id: str
214
+ """资源id, 由剪映本身提供"""
215
+
216
+ duration: int
217
+ """转场持续时间, 单位为微秒"""
218
+ is_overlap: bool
219
+ """是否与上一个片段重叠(?)"""
220
+
221
+ def __init__(self, effect_meta: TransitionType, duration: Optional[int] = None):
222
+ """根据给定的转场元数据及持续时间构造一个转场对象"""
223
+ self.name = effect_meta.value.name
224
+ self.global_id = uuid.uuid4().hex
225
+ self.effect_id = effect_meta.value.effect_id
226
+ self.resource_id = effect_meta.value.resource_id
227
+
228
+ self.duration = duration if duration is not None else effect_meta.value.default_duration
229
+ self.is_overlap = effect_meta.value.is_overlap
230
+
231
+ def export_json(self) -> Dict[str, Any]:
232
+ return {
233
+ "category_id": "", # 一律设为空
234
+ "category_name": "", # 一律设为空
235
+ "duration": self.duration,
236
+ "effect_id": self.effect_id,
237
+ "id": self.global_id,
238
+ "is_overlap": self.is_overlap,
239
+ "name": self.name,
240
+ "platform": "all",
241
+ "resource_id": self.resource_id,
242
+ "type": "transition"
243
+ # 不导出path和request_id字段
244
+ }
245
+
246
+ class BackgroundFilling:
247
+ """背景填充对象"""
248
+
249
+ global_id: str
250
+ """背景填充全局id, 由程序自动生成"""
251
+ fill_type: Literal["canvas_blur", "canvas_color"]
252
+ """背景填充类型"""
253
+ blur: float
254
+ """模糊程度, 0-1"""
255
+ color: str
256
+ """背景颜色, 格式为'#RRGGBBAA'"""
257
+
258
+ def __init__(self, fill_type: Literal["canvas_blur", "canvas_color"], blur: float, color: str):
259
+ self.global_id = uuid.uuid4().hex
260
+ self.fill_type = fill_type
261
+ self.blur = blur
262
+ self.color = color
263
+
264
+ def export_json(self) -> Dict[str, Any]:
265
+ return {
266
+ "id": self.global_id,
267
+ "type": self.fill_type,
268
+ "blur": self.blur,
269
+ "color": self.color,
270
+ "source_platform": 0,
271
+ }
272
+
273
+ class VideoSegment(VisualSegment):
274
+ """安放在轨道上的一个视频/图片片段"""
275
+
276
+ material_instance: VideoMaterial
277
+ """素材实例"""
278
+ material_size: Tuple[int, int]
279
+ """素材尺寸"""
280
+
281
+ effects: List[VideoEffect]
282
+ """特效列表
283
+
284
+ 在放入轨道时自动添加到素材列表中
285
+ """
286
+ filters: List[Filter]
287
+ """滤镜列表
288
+
289
+ 在放入轨道时自动添加到素材列表中
290
+ """
291
+ mask: Optional[Mask]
292
+ """蒙版实例, 可能为空
293
+
294
+ 在放入轨道时自动添加到素材列表中
295
+ """
296
+ transition: Optional[Transition]
297
+ """转场实例, 可能为空
298
+
299
+ 在放入轨道时自动添加到素材列表中
300
+ """
301
+ background_filling: Optional[BackgroundFilling]
302
+ """背景填充实例, 可能为空
303
+
304
+ 在放入轨道时自动添加到素材列表中
305
+ """
306
+
307
+ def __init__(self, material: Union[VideoMaterial, str], target_timerange: Timerange, *,
308
+ source_timerange: Optional[Timerange] = None, speed: Optional[float] = None, volume: float = 1.0,
309
+ clip_settings: Optional[ClipSettings] = None):
310
+ """利用给定的视频/图片素材构建一个轨道片段, 并指定其时间信息及图像调节设置
311
+
312
+ Args:
313
+ material (`VideoMaterial` or `str`): 素材实例或素材路径, 若为路径则自动构造素材实例(此时不能指定`cropSettings`参数)
314
+ target_timerange (`Timerange`): 片段在轨道上的目标时间范围
315
+ source_timerange (`Timerange`, optional): 截取的素材片段的时间范围, 默认从开头根据`speed`截取与`target_timerange`等长的一部分
316
+ speed (`float`, optional): 播放速度, 默认为1.0. 此项与`source_timerange`同时指定时, 将覆盖`target_timerange`中的时长
317
+ volume (`float`, optional): 音量, 默认为1.0
318
+ clip_settings (`ClipSettings`, optional): 图像调节设置, 默认不作任何变换
319
+
320
+ Raises:
321
+ `ValueError`: 指定的或计算出的`source_timerange`超出了素材的时长范围
322
+ """
323
+ if isinstance(material, str):
324
+ material = VideoMaterial(material)
325
+
326
+ if source_timerange is not None and speed is not None:
327
+ target_timerange = Timerange(target_timerange.start, round(source_timerange.duration / speed))
328
+ elif source_timerange is not None and speed is None:
329
+ speed = source_timerange.duration / target_timerange.duration
330
+ else: # source_timerange is None
331
+ speed = speed if speed is not None else 1.0
332
+ source_timerange = Timerange(0, round(target_timerange.duration * speed))
333
+
334
+ if source_timerange.end > material.duration:
335
+ raise ValueError(f"截取的素材时间范围 {source_timerange} 超出了素材时长({material.duration})")
336
+
337
+ super().__init__(material.material_id, source_timerange, target_timerange, speed, volume, clip_settings=clip_settings)
338
+
339
+ self.material_instance = deepcopy(material)
340
+ self.material_size = (material.width, material.height)
341
+ self.effects = []
342
+ self.filters = []
343
+ self.transition = None
344
+ self.mask = None
345
+ self.background_filling = None
346
+
347
+ def add_animation(self, animation_type: Union[IntroType, OutroType, GroupAnimationType],
348
+ duration: Optional[Union[int, str]] = None) -> "VideoSegment":
349
+ """将给定的入场/出场/组合动画添加到此片段的动画列表中
350
+
351
+ Args:
352
+ animation_type (`IntroType`, `OutroType`, or `GroupAnimationType`): 动画类型
353
+ duration (`int` or `str`, optional): 动画持续时间, 单位为微秒. 若传入字符串则会调用`tim()`函数进行解析.
354
+ 若不指定则使用动画类型定义的默认值. 理论上只适用于入场和出场动画.
355
+ """
356
+ if duration is not None:
357
+ duration = tim(duration)
358
+ if isinstance(animation_type, IntroType):
359
+ start = 0
360
+ duration = duration or animation_type.value.duration
361
+ elif isinstance(animation_type, OutroType):
362
+ duration = duration or animation_type.value.duration
363
+ start = self.target_timerange.duration - duration
364
+ elif isinstance(animation_type, GroupAnimationType):
365
+ start = 0
366
+ duration = duration or self.target_timerange.duration
367
+ else:
368
+ raise TypeError("Invalid animation type %s" % type(animation_type))
369
+
370
+ if self.animations_instance is None:
371
+ self.animations_instance = SegmentAnimations()
372
+ self.extra_material_refs.append(self.animations_instance.animation_id)
373
+
374
+ self.animations_instance.add_animation(VideoAnimation(animation_type, start, duration))
375
+
376
+ return self
377
+
378
+ def add_effect(self, effect_type: Union[VideoSceneEffectType, VideoCharacterEffectType],
379
+ params: Optional[List[Optional[float]]] = None) -> "VideoSegment":
380
+ """为视频片段添加一个作用于整个片段的特效
381
+
382
+ Args:
383
+ effect_type (`VideoSceneEffectType` or `VideoCharacterEffectType`): 特效类型
384
+ params (`List[Optional[float]]`, optional): 特效参数列表, 参数列表中未提供或为None的项使用默认值.
385
+ 参数取值范围(0~100)与剪映中一致. 某个特效类型有何参数以及具体参数顺序以枚举类成员的annotation为准.
386
+
387
+ Raises:
388
+ `ValueError`: 提供的参数数量超过了该特效类型的参数数量, 或参数值超出范围.
389
+ """
390
+ if params is not None and len(params) > len(effect_type.value.params):
391
+ raise ValueError("为音频效果 %s 传入了过多的参数" % effect_type.value.name)
392
+
393
+ effect_inst = VideoEffect(effect_type, params)
394
+ self.effects.append(effect_inst)
395
+ self.extra_material_refs.append(effect_inst.global_id)
396
+
397
+ return self
398
+
399
+ def add_filter(self, filter_type: FilterType, intensity: float = 100.0) -> "VideoSegment":
400
+ """为视频片段添加一个滤镜
401
+
402
+ Args:
403
+ filter_type (`FilterType`): 滤镜类型
404
+ intensity (`float`, optional): 滤镜强度(0-100), 仅当所选滤镜能够调节强度时有效. 默认为100.
405
+ """
406
+ filter_inst = Filter(filter_type.value, intensity / 100.0) # 转化为0~1范围
407
+ self.filters.append(filter_inst)
408
+ self.extra_material_refs.append(filter_inst.global_id)
409
+
410
+ return self
411
+
412
+ def add_mask(self, mask_type: MaskType, *, center_x: float = 0.0, center_y: float = 0.0, size: float = 0.5,
413
+ rotation: float = 0.0, feather: float = 0.0, invert: bool = False,
414
+ rect_width: Optional[float] = None, round_corner: Optional[float] = None) -> "VideoSegment":
415
+ """为视频片段添加蒙版
416
+
417
+ Args:
418
+ mask_type (`MaskType`): 蒙版类型
419
+ center_x (`float`, optional): 蒙版中心点X坐标(以素材的像素为单位), 默认设置在素材中心
420
+ center_y (`float`, optional): 蒙版中心点Y坐标(以素材的像素为单位), 默认设置在素材中心
421
+ size (`float`, optional): 蒙版的"主要尺寸"(镜面的可视部分高度/圆形直径/爱心高度等), 以占素材高度的比例表示, 默认为0.5
422
+ rotation (`float`, optional): 蒙版顺时针旋转的**角度**, 默认不旋转
423
+ feather (`float`, optional): 蒙版的羽化参数, 取值范围0~100, 默认无羽化
424
+ invert (`bool`, optional): 是否反转蒙版, 默认不反转
425
+ rect_width (`float`, optional): 矩形蒙版的宽度, 仅在蒙版类型为矩形时允许设置, 以占素材宽度的比例表示, 默认与`size`相同
426
+ round_corner (`float`, optional): 矩形蒙版的圆角参数, 仅在蒙版类型为矩形时允许设置, 取值范围0~100, 默认为0
427
+
428
+ Raises:
429
+ `ValueError`: 试图添加多个蒙版或不正确地设置了`rect_width`及`round_corner`
430
+ """
431
+
432
+ if self.mask is not None:
433
+ raise ValueError("当前片段已有蒙版, 不能再添加新的蒙版")
434
+ if (rect_width is not None or round_corner is not None) and mask_type != MaskType.矩形:
435
+ raise ValueError("`rect_width` 以及 `round_corner` 仅在蒙版类型为矩形时允许设置")
436
+ if rect_width is None and mask_type == MaskType.矩形:
437
+ rect_width = size
438
+ if round_corner is None:
439
+ round_corner = 0
440
+
441
+ width = rect_width or size * self.material_size[1] * mask_type.value.default_aspect_ratio / self.material_size[0]
442
+ self.mask = Mask(mask_type.value, center_x / (self.material_size[0] / 2), center_y / (self.material_size[1] / 2),
443
+ w=width, h=size, ratio=mask_type.value.default_aspect_ratio,
444
+ rot=rotation, inv=invert, feather=feather/100, round_corner=round_corner/100)
445
+ self.extra_material_refs.append(self.mask.global_id)
446
+ return self
447
+
448
+ def add_transition(self, transition_type: TransitionType, *, duration: Optional[Union[int, str]] = None) -> "VideoSegment":
449
+ """为视频片段添加转场, 注意转场应当添加在**前面的**片段上
450
+
451
+ Args:
452
+ transition_type (`TransitionType`): 转场类型
453
+ duration (`int` or `str`, optional): 转场持续时间, 单位为微秒. 若传入字符串则会调用`tim()`函数进行解析. 若不指定则使用转场类型定义的默认值.
454
+
455
+ Raises:
456
+ `ValueError`: 试图添加多个转场.
457
+ """
458
+ if self.transition is not None:
459
+ raise ValueError("当前片段已有转场, 不能再添加新的转场")
460
+ if isinstance(duration, str): duration = tim(duration)
461
+
462
+ self.transition = Transition(transition_type, duration)
463
+ self.extra_material_refs.append(self.transition.global_id)
464
+ return self
465
+
466
+ def add_background_filling(self, fill_type: Literal["blur", "color"], blur: float = 0.0625, color: str = "#00000000") -> "VideoSegment":
467
+ """为视频片段添加背景填充
468
+
469
+ 注意: 背景填充仅对底层视频轨道上的片段生效
470
+
471
+ Args:
472
+ fill_type (`blur` or `color`): 填充类型, `blur`表示模糊, `color`表示颜色.
473
+ blur (`float`, optional): 模糊程度, 0.0-1.0. 仅在`fill_type`为`blur`时有效. 剪映中的四档模糊数值分别为0.0625, 0.375, 0.75和1.0, 默认为0.0625.
474
+ color (`str`, optional): 填充颜色, 格式为'#RRGGBBAA'. 仅在`fill_type`为`color`时有效.
475
+
476
+ Raises:
477
+ `ValueError`: 当前片段已有背景填充效果或`fill_type`无效.
478
+ """
479
+ if self.background_filling is not None:
480
+ raise ValueError("当前片段已有背景填充效果")
481
+
482
+ if fill_type == "blur":
483
+ self.background_filling = BackgroundFilling("canvas_blur", blur, color)
484
+ elif fill_type == "color":
485
+ self.background_filling = BackgroundFilling("canvas_color", blur, color)
486
+ else:
487
+ raise ValueError(f"无效的背景填充类型 {fill_type}")
488
+
489
+ self.extra_material_refs.append(self.background_filling.global_id)
490
+ return self
491
+
492
+ def export_json(self) -> Dict[str, Any]:
493
+ json_dict = super().export_json()
494
+ json_dict.update({
495
+ "hdr_settings": {"intensity": 1.0, "mode": 1, "nits": 1000},
496
+ })
497
+ return json_dict
498
+
499
+ class StickerSegment(VisualSegment):
500
+ """安放在轨道上的一个贴纸片段"""
501
+
502
+ resource_id: str
503
+ """贴纸资源id"""
504
+
505
+ def __init__(self, resource_id: str, target_timerange: Timerange, *, clip_settings: Optional[ClipSettings] = None):
506
+ """根据贴纸resource_id构建一个贴纸片段, 并指定其时间信息及图像调节设置
507
+
508
+ 片段创建完成后, 可通过`ScriptFile.add_segment`方法将其添加到轨道中
509
+
510
+ Args:
511
+ resource_id (`str`): 贴纸resource_id, 可通过`ScriptFile.inspect_material`从模板中获取
512
+ target_timerange (`Timerange`): 片段在轨道上的目标时间范围
513
+ clip_settings (`ClipSettings`, optional): 图像调节设置, 默认不作任何变换
514
+ """
515
+ super().__init__(uuid.uuid4().hex, None, target_timerange, 1.0, 1.0, clip_settings=clip_settings)
516
+ self.resource_id = resource_id
517
+
518
+ def export_material(self) -> Dict[str, Any]:
519
+ """创建极简的贴纸素材对象, 以此不再单独定义贴纸素材类"""
520
+ return {
521
+ "id": self.material_id,
522
+ "resource_id": self.resource_id,
523
+ "sticker_id": self.resource_id,
524
+ "source_platform": 1,
525
+ "type": "sticker",
526
+ }
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.2
2
+ Name: pycapcut
3
+ Version: 0.0.1
4
+ Summary: 轻量、灵活、易上手的Python CapCut草稿生成及导出工具,构建全自动化视频剪辑/混剪流水线
5
+ Home-page: https://github.com/GuanYixuan/pycapcut
6
+ Author: gary318
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Topic :: Multimedia :: Video
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: pymediainfo
13
+ Requires-Dist: imageio
14
+ Requires-Dist: uiautomation>=2; sys_platform == "win32"
15
+ Dynamic: author
16
+ Dynamic: classifier
17
+ Dynamic: description
18
+ Dynamic: description-content-type
19
+ Dynamic: home-page
20
+ Dynamic: requires-dist
21
+ Dynamic: requires-python
22
+ Dynamic: summary
23
+
24
+ ### 轻量、灵活、易上手的Python CapCut草稿生成及导出工具,构建全自动视频剪辑/混剪流水线!
25
+
26
+ > 🧪 本项目正在从[PyJianYingDraft](https://github.com/GuanYixuan/pyJianYingDraft)进行迁移,欢迎⭐️此项目保持关注!
27
+
28
+ > 📢 欢迎加入[Discord服务器](https://discord.gg/WfHgGQvhyW)进行用法或新功能的讨论
29
+
30
+ ## 使用思路
31
+ ![使用思路](https://github.com/GuanYixuan/pyJianYingDraft/raw/main/readme_assets/%E4%BD%BF%E7%94%A8%E6%80%9D%E8%B7%AF.jpg)
32
+
33
+ # 功能清单
34
+
35
+ ### 模板模式
36
+ - ☑️ [加载](#加载模板)(未加密的)`draft_content.json`文件作为模板
37
+ - ☑️ [替换音视频片段的素材](#根据名称替换素材)
38
+ - ☑️ [修改文本片段的文本内容](#替换文本片段的内容)
39
+ - ☑️ [将模板草稿中的音视频/文本轨道整体导入到另一草稿中](#导入模板草稿中的轨道)
40
+ - ☑️ [提取模板中出现的贴纸/气泡/花字等元信息](#提取素材元数据)
41
+
42
+ ### 批量导出
43
+ > ⚠️ 本模块正在迁移中,若有兴趣可参见[PyJianYingDraft中相应部分](https://github.com/GuanYixuan/pyJianYingDraft?tab=readme-ov-file#%E6%89%B9%E9%87%8F%E5%AF%BC%E5%87%BA%E8%8D%89%E7%A8%BF)
44
+
45
+ - ☑️ 控制剪映打开指定草稿
46
+ - ☑️ [导出草稿至指定位置](#批量导出草稿)
47
+ - ☑️ 调节导出分辨率和帧率
48
+
49
+ ### 视频与图片
50
+ > ⚠️ 本模块与特效/滤镜/动画等相关的功能正在迁移中,若有兴趣可参见[PyJianYingDraft中相应部分](https://github.com/GuanYixuan/pyJianYingDraft?tab=readme-ov-file#%E8%A7%86%E9%A2%91%E4%B8%8E%E5%9B%BE%E7%89%87)
51
+
52
+ - ☑️ 添加本地视频/图片素材,并[自定义片段的时间、持续时长或播放速度](#素材截取与整体变速)
53
+ - ☑️ [视频整体调节](#视频整体调节)(旋转、缩放、亮度等)以及[关键帧生成](#关键帧)
54
+ - ☑️ 视频片段的[入场/出场/组合动画](#添加片段动画)
55
+ - ☑️ 添加[蒙版](#蒙版)、[片段特效](#添加片段特效)和[滤镜](#添加片段滤镜)
56
+ - ☑️ 视频背景填充[(示例代码)](demo.py)
57
+ ### 贴纸
58
+ - ☑️ 根据元信息[添加贴纸](#提取素材元数据)
59
+ - ☑️ 贴纸的[关键帧](#关键帧)生成
60
+ ### 音频
61
+ - ☑️ 添加本地音频素材,并[自定义片段的时间、持续时长或播放速度](#素材截取与整体变速)
62
+ - ☑️ 调整淡入淡出时长[(示例代码)](demo.py),调整音量[(示例代码)](demo.py)及其[关键帧](#关键帧)
63
+ - ☑️ 添加音频片段的[场景音效果](#添加片段特效),并设置参数
64
+ ### 轨道
65
+ - ☑️ [添加轨道](#多轨道操作)以及[将片段添加到指定轨道](#多轨道操作)
66
+ - ☑️ 自定义视频/滤镜/特效轨道的[层级关系](#多轨道操作)
67
+ ### 特效、滤镜和转场
68
+ - ☑️ 吸附于片段上的[特效](#添加片段特效)、[滤镜](#添加片段滤镜)和[动画](#添加片段动画)
69
+ - ☑️ 位于[独立轨道的特效和滤镜](#独立轨道上的特效和滤镜)
70
+ - ☑️ 添加转场[(示例代码)](demo.py),并自定义其时长
71
+ ### 文本及字幕
72
+ - ☑️ [添加文本、设置字体及样式](#添加文本)、修改文本片段的[位置及旋转设置](#视频整体调节)
73
+ - ☑️ 文本的[关键帧](#关键帧)以及[动画](#添加片段动画)
74
+ - ☑️ 文字描边和文字背景
75
+ - ☑️ 文字气泡效果和花字效果[(示例代码)](demo.py)
76
+ - ☑️ 文本[自动换行](#文本自动换行),支持设置最大行宽
77
+ - ☑️ [导入`.srt`文件](#导入字幕)生成字幕并批量设置格式