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
@@ -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
+ }