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,256 @@
1
+ """定义片段基类及部分比较通用的属性类"""
2
+
3
+ import uuid
4
+ from typing import Optional, Dict, List, Any, Union
5
+
6
+ from .animation import SegmentAnimations
7
+ from .time_util import Timerange, tim
8
+ from .keyframe import KeyframeList, KeyframeProperty
9
+
10
+ class BaseSegment:
11
+ """片段基类"""
12
+
13
+ segment_id: str
14
+ """片段全局id, 由程序自动生成"""
15
+ material_id: str
16
+ """使用的素材id"""
17
+ target_timerange: Timerange
18
+ """片段在轨道上的时间范围"""
19
+
20
+ common_keyframes: List[KeyframeList]
21
+ """各属性的关键帧列表"""
22
+
23
+ def __init__(self, material_id: str, target_timerange: Timerange):
24
+ self.segment_id = uuid.uuid4().hex
25
+ self.material_id = material_id
26
+ self.target_timerange = target_timerange
27
+
28
+ self.common_keyframes = []
29
+
30
+ @property
31
+ def start(self) -> int:
32
+ """片段开始时间, 单位为微秒"""
33
+ return self.target_timerange.start
34
+ @start.setter
35
+ def start(self, value: int):
36
+ self.target_timerange.start = value
37
+
38
+ @property
39
+ def duration(self) -> int:
40
+ """片段持续时间, 单位为微秒"""
41
+ return self.target_timerange.duration
42
+ @duration.setter
43
+ def duration(self, value: int):
44
+ self.target_timerange.duration = value
45
+
46
+ @property
47
+ def end(self) -> int:
48
+ """片段结束时间, 单位为微秒"""
49
+ return self.target_timerange.end
50
+
51
+ def overlaps(self, other: "BaseSegment") -> bool:
52
+ """判断是否与另一个片段有重叠"""
53
+ return self.target_timerange.overlaps(other.target_timerange)
54
+
55
+ def export_json(self) -> Dict[str, Any]:
56
+ """返回通用于各种片段的属性"""
57
+ return {
58
+ "enable_adjust": True,
59
+ "enable_color_correct_adjust": False,
60
+ "enable_color_curves": True,
61
+ "enable_color_match_adjust": False,
62
+ "enable_color_wheels": True,
63
+ "enable_lut": True,
64
+ "enable_smart_color_adjust": False,
65
+ "last_nonzero_volume": 1.0,
66
+ "reverse": False,
67
+ "track_attribute": 0,
68
+ "track_render_index": 0,
69
+ "visible": True,
70
+ # 写入自定义字段
71
+ "id": self.segment_id,
72
+ "material_id": self.material_id,
73
+ "target_timerange": self.target_timerange.export_json(),
74
+
75
+ "common_keyframes": [kf_list.export_json() for kf_list in self.common_keyframes],
76
+ "keyframe_refs": [], # 意义不明
77
+ }
78
+
79
+ class Speed:
80
+ """播放速度对象, 目前只支持固定速度"""
81
+
82
+ global_id: str
83
+ """全局id, 由程序自动生成"""
84
+ speed: float
85
+ """播放速度"""
86
+
87
+ def __init__(self, speed: float):
88
+ self.global_id = uuid.uuid4().hex
89
+ self.speed = speed
90
+
91
+ def export_json(self) -> Dict[str, Any]:
92
+ return {
93
+ "curve_speed": None,
94
+ "id": self.global_id,
95
+ "mode": 0,
96
+ "speed": self.speed,
97
+ "type": "speed"
98
+ }
99
+
100
+ class ClipSettings:
101
+ """素材片段的图像调节设置"""
102
+
103
+ alpha: float
104
+ """图像不透明度, 0-1"""
105
+ flip_horizontal: bool
106
+ """是否水平翻转"""
107
+ flip_vertical: bool
108
+ """是否垂直翻转"""
109
+ rotation: float
110
+ """顺时针旋转的**角度**, 可正可负"""
111
+ scale_x: float
112
+ """水平缩放比例"""
113
+ scale_y: float
114
+ """垂直缩放比例"""
115
+ transform_x: float
116
+ """水平位移, 单位为半个画布宽"""
117
+ transform_y: float
118
+ """垂直位移, 单位为半个画布高"""
119
+
120
+ def __init__(self, *, alpha: float = 1.0,
121
+ flip_horizontal: bool = False, flip_vertical: bool = False,
122
+ rotation: float = 0.0,
123
+ scale_x: float = 1.0, scale_y: float = 1.0,
124
+ transform_x: float = 0.0, transform_y: float = 0.0):
125
+ """初始化图像调节设置, 默认不作任何图像变换
126
+
127
+ Args:
128
+ alpha (float, optional): 图像不透明度, 0-1. 默认为1.0.
129
+ flip_horizontal (bool, optional): 是否水平翻转. 默认为False.
130
+ flip_vertical (bool, optional): 是否垂直翻转. 默认为False.
131
+ rotation (float, optional): 顺时针旋转的**角度**, 可正可负. 默认为0.0.
132
+ scale_x (float, optional): 水平缩放比例. 默认为1.0.
133
+ scale_y (float, optional): 垂直缩放比例. 默认为1.0.
134
+ transform_x (float, optional): 水平位移, 单位为半个画布宽. 默认为0.0.
135
+ transform_y (float, optional): 垂直位移, 单位为半个画布高. 默认为0.0.
136
+ 参考: 剪映导入的字幕似乎取此值为-0.8
137
+ """
138
+ self.alpha = alpha
139
+ self.flip_horizontal, self.flip_vertical = flip_horizontal, flip_vertical
140
+ self.rotation = rotation
141
+ self.scale_x, self.scale_y = scale_x, scale_y
142
+ self.transform_x, self.transform_y = transform_x, transform_y
143
+
144
+ def export_json(self) -> Dict[str, Any]:
145
+ clip_settings_json = {
146
+ "alpha": self.alpha,
147
+ "flip": {"horizontal": self.flip_horizontal, "vertical": self.flip_vertical},
148
+ "rotation": self.rotation,
149
+ "scale": {"x": self.scale_x, "y": self.scale_y},
150
+ "transform": {"x": self.transform_x, "y": self.transform_y}
151
+ }
152
+ return clip_settings_json
153
+
154
+ class MediaSegment(BaseSegment):
155
+ """媒体片段基类"""
156
+
157
+ source_timerange: Optional[Timerange]
158
+ """截取的素材片段的时间范围, 对贴纸而言不存在"""
159
+ speed: Speed
160
+ """播放速度设置"""
161
+ volume: float
162
+ """音量"""
163
+
164
+ extra_material_refs: List[str]
165
+ """附加的素材id列表, 用于链接动画/特效等"""
166
+
167
+ def __init__(self, material_id: str, source_timerange: Optional[Timerange], target_timerange: Timerange, speed: float, volume: float):
168
+ super().__init__(material_id, target_timerange)
169
+
170
+ self.source_timerange = source_timerange
171
+ self.speed = Speed(speed)
172
+ self.volume = volume
173
+
174
+ self.extra_material_refs = [self.speed.global_id]
175
+
176
+ def export_json(self) -> Dict[str, Any]:
177
+ """返回通用于音频和视频片段的默认属性"""
178
+ ret = super().export_json()
179
+ ret.update({
180
+ "source_timerange": self.source_timerange.export_json() if self.source_timerange else None,
181
+ "speed": self.speed.speed,
182
+ "volume": self.volume,
183
+ "extra_material_refs": self.extra_material_refs,
184
+ })
185
+ return ret
186
+
187
+ class VisualSegment(MediaSegment):
188
+ """视觉片段基类,用于处理所有可见片段(视频、贴纸、文本)的共同属性和行为"""
189
+
190
+ clip_settings: ClipSettings
191
+ """图像调节设置, 其效果可被关键帧覆盖"""
192
+
193
+ uniform_scale: bool
194
+ """是否锁定XY轴缩放比例"""
195
+
196
+ animations_instance: Optional[SegmentAnimations]
197
+ """动画实例, 可能为空
198
+
199
+ 在放入轨道时自动添加到素材列表中
200
+ """
201
+
202
+ def __init__(self, material_id: str, source_timerange: Optional[Timerange], target_timerange: Timerange,
203
+ speed: float, volume: float, *, clip_settings: Optional[ClipSettings]):
204
+ """初始化视觉片段基类
205
+
206
+ Args:
207
+ material_id (`str`): 素材id
208
+ source_timerange (`Timerange`, optional): 截取的素材片段的时间范围
209
+ target_timerange (`Timerange`): 片段在轨道上的目标时间范围
210
+ speed (`float`): 播放速度
211
+ volume (`float`): 音量
212
+ clip_settings (`ClipSettings`, optional): 图像调节设置, 默认不作任何变换
213
+ """
214
+ super().__init__(material_id, source_timerange, target_timerange, speed, volume)
215
+
216
+ self.clip_settings = clip_settings if clip_settings is not None else ClipSettings()
217
+ self.uniform_scale = True
218
+ self.animations_instance = None
219
+
220
+ def add_keyframe(self, _property: KeyframeProperty, time_offset: Union[int, str], value: float) -> "VisualSegment":
221
+ """为给定属性创建一个关键帧, 并自动加入到关键帧列表中
222
+
223
+ Args:
224
+ _property (`KeyframeProperty`): 要控制的属性
225
+ time_offset (`int` or `str`): 关键帧的时间偏移量, 单位为微秒. 若传入字符串则会调用`tim()`函数进行解析.
226
+ value (`float`): 属性在`time_offset`处的值
227
+
228
+ Raises:
229
+ `ValueError`: 试图同时设置`uniform_scale`以及`scale_x`或`scale_y`其中一者
230
+ """
231
+ if (_property == KeyframeProperty.scale_x or _property == KeyframeProperty.scale_y) and self.uniform_scale:
232
+ self.uniform_scale = False
233
+ elif _property == KeyframeProperty.uniform_scale:
234
+ if not self.uniform_scale:
235
+ raise ValueError("已设置 scale_x 或 scale_y 时, 不能再设置 uniform_scale")
236
+ _property = KeyframeProperty.scale_x
237
+
238
+ if isinstance(time_offset, str): time_offset = tim(time_offset)
239
+
240
+ for kf_list in self.common_keyframes:
241
+ if kf_list.keyframe_property == _property:
242
+ kf_list.add_keyframe(time_offset, value)
243
+ return self
244
+ kf_list = KeyframeList(_property)
245
+ kf_list.add_keyframe(time_offset, value)
246
+ self.common_keyframes.append(kf_list)
247
+ return self
248
+
249
+ def export_json(self) -> Dict[str, Any]:
250
+ """导出通用于所有视觉片段的JSON数据"""
251
+ json_dict = super().export_json()
252
+ json_dict.update({
253
+ "clip": self.clip_settings.export_json(),
254
+ "uniform_scale": {"on": self.uniform_scale, "value": 1.0},
255
+ })
256
+ return json_dict
@@ -0,0 +1,221 @@
1
+ """与模板模式相关的类及函数等"""
2
+
3
+ from enum import Enum
4
+ from copy import deepcopy
5
+
6
+ from . import util
7
+ from . import exceptions
8
+ from .time_util import Timerange
9
+ from .segment import BaseSegment
10
+ from .track import BaseTrack, TrackType
11
+ from .local_materials import VideoMaterial, AudioMaterial
12
+
13
+ from typing import List, Dict, Any
14
+
15
+ class ShrinkMode(Enum):
16
+ """处理替换素材时素材变短情况的方法"""
17
+
18
+ cut_head = "cut_head"
19
+ """裁剪头部, 即后移片段起始点"""
20
+ cut_tail = "cut_tail"
21
+ """裁剪尾部, 即前移片段终止点"""
22
+
23
+ cut_tail_align = "cut_tail_align"
24
+ """裁剪尾部并消除间隙, 即前移片段终止点, 后续片段也依次前移"""
25
+
26
+ shrink = "shrink"
27
+ """保持中间点不变, 两端点向中间靠拢"""
28
+
29
+ class ExtendMode(Enum):
30
+ """处理替换素材时素材变长情况的方法"""
31
+
32
+ cut_material_tail = "cut_material_tail"
33
+ """裁剪素材尾部(覆盖`source_timerange`参数), 使得片段维持原长不变, 此方法总是成功"""
34
+
35
+ extend_head = "extend_head"
36
+ """延伸头部, 即尝试前移片段起始点, 与前续片段重合时失败"""
37
+ extend_tail = "extend_tail"
38
+ """延伸尾部, 即尝试后移片段终止点, 与后续片段重合时失败"""
39
+
40
+ push_tail = "push_tail"
41
+ """延伸尾部, 若有必要则依次后移后续片段, 此方法总是成功"""
42
+
43
+ class ImportedSegment(BaseSegment):
44
+ """导入的片段"""
45
+
46
+ raw_data: Dict[str, Any]
47
+ """原始json数据"""
48
+
49
+ __DATA_ATTRS = ["material_id", "target_timerange"]
50
+ def __init__(self, json_data: Dict[str, Any]):
51
+ self.raw_data = deepcopy(json_data)
52
+
53
+ util.assign_attr_with_json(self, self.__DATA_ATTRS, json_data)
54
+
55
+ def export_json(self) -> Dict[str, Any]:
56
+ json_data = deepcopy(self.raw_data)
57
+ json_data.update(util.export_attr_to_json(self, self.__DATA_ATTRS))
58
+ return json_data
59
+
60
+ class ImportedMediaSegment(ImportedSegment):
61
+ """导入的视频/音频片段"""
62
+
63
+ source_timerange: Timerange
64
+ """片段取用的素材时间范围"""
65
+
66
+ __DATA_ATTRS = ["source_timerange"]
67
+ def __init__(self, json_data: Dict[str, Any]):
68
+ super().__init__(json_data)
69
+
70
+ util.assign_attr_with_json(self, self.__DATA_ATTRS, json_data)
71
+
72
+ def export_json(self) -> Dict[str, Any]:
73
+ json_data = super().export_json()
74
+ json_data.update(util.export_attr_to_json(self, self.__DATA_ATTRS))
75
+ return json_data
76
+
77
+
78
+ class ImportedTrack(BaseTrack):
79
+ """模板模式下导入的轨道"""
80
+
81
+ raw_data: Dict[str, Any]
82
+ """原始轨道数据"""
83
+
84
+ def __init__(self, json_data: Dict[str, Any]):
85
+ self.track_type = TrackType.from_name(json_data["type"])
86
+ self.name = json_data["name"]
87
+ self.track_id = json_data["id"]
88
+ self.render_index = max([int(seg["render_index"]) for seg in json_data["segments"]], default=0)
89
+
90
+ self.raw_data = deepcopy(json_data)
91
+
92
+ def export_json(self) -> Dict[str, Any]:
93
+ ret = deepcopy(self.raw_data)
94
+ ret.update({
95
+ "name": self.name,
96
+ "id": self.track_id
97
+ })
98
+ return ret
99
+
100
+ class EditableTrack(ImportedTrack):
101
+ """模板模式下导入且可修改的轨道(音视频及文本轨道)"""
102
+
103
+ segments: List[ImportedSegment]
104
+ """该轨道包含的片段列表"""
105
+
106
+ def __len__(self):
107
+ return len(self.segments)
108
+
109
+ @property
110
+ def start_time(self) -> int:
111
+ """轨道起始时间, 微秒"""
112
+ if len(self.segments) == 0:
113
+ return 0
114
+ return self.segments[0].target_timerange.start
115
+
116
+ @property
117
+ def end_time(self) -> int:
118
+ """轨道结束时间, 微秒"""
119
+ if len(self.segments) == 0:
120
+ return 0
121
+ return self.segments[-1].target_timerange.end
122
+
123
+ def export_json(self) -> Dict[str, Any]:
124
+ ret = super().export_json()
125
+ # 为每个片段写入render_index
126
+ segment_exports = [seg.export_json() for seg in self.segments]
127
+ for seg in segment_exports:
128
+ seg["render_index"] = self.render_index
129
+ ret["segments"] = segment_exports
130
+ return ret
131
+
132
+ class ImportedTextTrack(EditableTrack):
133
+ """模板模式下导入的文本轨道"""
134
+
135
+ def __init__(self, json_data: Dict[str, Any]):
136
+ super().__init__(json_data)
137
+ self.segments = [ImportedSegment(seg) for seg in json_data["segments"]]
138
+
139
+ class ImportedMediaTrack(EditableTrack):
140
+ """模板模式下导入的音频/视频轨道"""
141
+
142
+ segments: List[ImportedMediaSegment]
143
+ """该轨道包含的片段列表"""
144
+
145
+ def __init__(self, json_data: Dict[str, Any]):
146
+ super().__init__(json_data)
147
+ self.segments = [ImportedMediaSegment(seg) for seg in json_data["segments"]]
148
+
149
+ def check_material_type(self, material: object) -> bool:
150
+ """检查素材类型是否与轨道类型匹配"""
151
+ if self.track_type == TrackType.video and isinstance(material, VideoMaterial):
152
+ return True
153
+ if self.track_type == TrackType.audio and isinstance(material, AudioMaterial):
154
+ return True
155
+ return False
156
+
157
+ def process_timerange(self, seg_index: int, src_timerange: Timerange,
158
+ shrink: ShrinkMode, extend: List[ExtendMode]) -> None:
159
+ """处理素材替换的时间范围变更"""
160
+ seg = self.segments[seg_index]
161
+ new_duration = src_timerange.duration
162
+
163
+ # 时长变短
164
+ delta_duration = abs(new_duration - seg.duration)
165
+ if new_duration < seg.duration:
166
+ if shrink == ShrinkMode.cut_head:
167
+ seg.start += delta_duration
168
+ elif shrink == ShrinkMode.cut_tail:
169
+ seg.duration -= delta_duration
170
+ elif shrink == ShrinkMode.cut_tail_align:
171
+ seg.duration -= delta_duration
172
+ for i in range(seg_index+1, len(self.segments)): # 后续片段也依次前移相应值(保持间隙)
173
+ self.segments[i].start -= delta_duration
174
+ elif shrink == ShrinkMode.shrink:
175
+ seg.duration -= delta_duration
176
+ seg.start += delta_duration // 2
177
+ else:
178
+ raise ValueError(f"Unsupported shrink mode: {shrink}")
179
+ # 时长变长
180
+ elif new_duration > seg.duration:
181
+ success_flag = False
182
+ prev_seg_end = int(0) if seg_index == 0 else self.segments[seg_index-1].target_timerange.end
183
+ next_seg_start = int(1e15) if seg_index == len(self.segments)-1 else self.segments[seg_index+1].start
184
+ for mode in extend:
185
+ if mode == ExtendMode.extend_head:
186
+ if seg.start - delta_duration >= prev_seg_end:
187
+ seg.start -= delta_duration
188
+ success_flag = True
189
+ elif mode == ExtendMode.extend_tail:
190
+ if seg.target_timerange.end + delta_duration <= next_seg_start:
191
+ seg.duration += delta_duration
192
+ success_flag = True
193
+ elif mode == ExtendMode.push_tail:
194
+ shift_duration = max(0, seg.target_timerange.end + delta_duration - next_seg_start)
195
+ seg.duration += delta_duration
196
+ if shift_duration > 0: # 有必要时后移后续片段
197
+ for i in range(seg_index+1, len(self.segments)):
198
+ self.segments[i].start += shift_duration
199
+ success_flag = True
200
+ elif mode == ExtendMode.cut_material_tail:
201
+ src_timerange.duration = seg.duration
202
+ success_flag = True
203
+ else:
204
+ raise ValueError(f"Unsupported extend mode: {mode}")
205
+
206
+ if success_flag:
207
+ break
208
+ if not success_flag:
209
+ raise exceptions.ExtensionFailed(f"未能将片段延长至 {new_duration}μs, 尝试过以下方法: {extend}")
210
+
211
+ # 写入素材时间范围
212
+ seg.source_timerange = src_timerange
213
+
214
+ def import_track(json_data: Dict[str, Any]) -> ImportedTrack:
215
+ """导入轨道"""
216
+ track_type = TrackType.from_name(json_data["type"])
217
+ if not track_type.value.allow_modify:
218
+ return ImportedTrack(json_data)
219
+ if track_type == TrackType.text:
220
+ return ImportedTextTrack(json_data)
221
+ return ImportedMediaTrack(json_data)