# -*- coding: utf-8 -*- """ 营销短视频生成自动化 """ from pathlib import Path from typing import Optional, Tuple, Union import pycapcut as capcut from pycapcut import tim, trange class Draft: """剪映脚本生成器类""" # noinspection PyShadowingNames def __init__( self, draft_name: str, drafts_path: str = r"C:\Users\admin\AppData\Local\JianyingPro\User Data\Projects\com.lveditor.draft", stocks_path: Path = Path(__file__).parent / "materials", video_width: int = 1920, video_height: int = 1080, ): """ 初始化剪映草稿生成器 :param drafts_path: 草稿文件夹路径 :param stocks_path: 素材文件夹路径 :param draft_name: 草稿名称 :param video_width: 视频宽度 :param video_height: 视频高度 """ # 基础配置 self.drafts_path = drafts_path self.stocks_path = stocks_path self.draft_name = draft_name self.video_width, self.video_height = video_width, video_height # 尝试创建草稿文件夹和草稿 # noinspection PyBroadException try: self.draft_folder = capcut.DraftFolder(self.drafts_path) self.draft = self.draft_folder.create_draft( self.draft_name, self.video_width, self.video_height, allow_replace=True ) # 添加基础轨道:音频、视频、文本(图片/贴纸复用视频轨) self.draft.add_track(capcut.TrackType.audio) self.draft.add_track(capcut.TrackType.video) self.draft.add_track(capcut.TrackType.text) except: raise RuntimeError("创建草稿文件夹和草稿发生异常") # 检查素材文件夹是否存在,若不存在则抛出异常 if not self.stocks_path.exists(): raise FileNotFoundError(f"素材文件夹不存在") def _check_path(self, file_name: str) -> str: """ 检查文件是否存在,若存在则返回文件路径 :param file_name: 文件名称 :return 文件路径 """ file_path = self.stocks_path / file_name if not file_path.exists(): raise FileNotFoundError(f"素材文件不存在") return file_path.as_posix() def add_audio( self, file_name: str, start_time: str, duration: str, volume: float = 1.0, fade_in: str = "0s", fade_out: str = "0s", ) -> capcut.AudioSegment: """ 添加音频片段 :param file_name: 音频文件名称 :param start_time: 音频在轨道上的开始时间(如 "0s") :param duration: 音频持续时长(如 "1s") :param volume: 音量(如 1.0) :param fade_in: 淡入时长(如 “0s”) :param fade_out: 淡出时长(如 “0s”) Returns: capcut.AudioSegment """ try: # 创建音频片段 audio_segment = capcut.AudioSegment( self._check_path(file_name), trange(start_time, duration), volume=volume ) # 添加淡入淡出效果 audio_segment.add_fade(fade_in, fade_out) self.draft.add_segment(audio_segment) return audio_segment except Exception: raise RuntimeError("添加音频片段发生异常") def add_video( self, file_name: str, start_time: str, duration: str, animation: Optional[capcut.IntroType] = None, transition: Optional[capcut.TransitionType] = None, keyframes: Optional[list] = None, ) -> capcut.VideoSegment: """ 添加视频片段 :param file_name: 视频文件名称 :param start_time: 视频在轨道上的开始时间(如 "0s") :param duration: 视频持续时长(如 "1s") :param animation: 动画配置 :param transition: 转场配置 :param keyframes: 关键帧配置 :return capcut.VideoSegment """ try: # 创建视频片段 video_segment = capcut.VideoSegment( self._check_path(file_name), trange(start_time, duration) ) # 添加动画 if animation: video_segment.add_animation(animation) # 添加转场 if transition: video_segment.add_transition(transition) # 添加关键帧 if keyframes: # noinspection PyShadowingBuiltins for property, time, value in keyframes: video_segment.add_keyframe(property, time, value) self.draft.add_segment(video_segment) return video_segment except Exception: raise RuntimeError("添加视频片段发生异常") def add_image( self, file_name: str, start_time: Union[str, tim], duration: Optional[str] = None, background_filling: Tuple[str, float] = ("blur", 0.0625), ) -> capcut.VideoSegment: """ 添加图片/贴纸 :param file_name: 图片文件名称 :param start_time: 图片在轨道上的开始时间(如 "0s") :param duration: 图片持续时长(如 "1s") :param background_filling: 背景填充配置 :return: capcut.VideoSegment """ try: # 创建图片素材 image_material = capcut.VideoMaterial(self._check_path(file_name)) # 创建图片片段 image_segment = capcut.VideoSegment( image_material, trange(start_time, duration if duration else image_material.duration), ) # 若已设置图片持续时长则使用,否则使用视频持续时长 # 添加背景填充 if background_filling: image_segment.add_background_filling(*background_filling) self.draft.add_segment(image_segment) return image_segment except Exception: raise RuntimeError("添加图片/贴纸发生异常") def add_text( self, content: str, target_timerange: trange, font: capcut.FontType = capcut.FontType.悠然体, color: Tuple[float, float, float] = (1.0, 1.0, 0.0), position_y: float = -0.8, outro_animation: Optional[capcut.TextOutro] = capcut.TextOutro.故障闪动, anim_duration: tim = tim("1s"), bubble_id: Optional[str] = "7446997603268496646", effect_id: Optional[str] = "7336825073334078725", ) -> capcut.TextSegment: """ 添加文字片段 Args: content: 文字内容 target_timerange: 文字显示的时间范围(通常对齐视频片段) font: 字体类型 color: 文字颜色(RGB,0-1) position_y: 文字Y轴位置(-0.8为屏幕下方) outro_animation: 出场动画(None则不添加) anim_duration: 动画时长 bubble_id: 气泡效果ID(None则不添加) effect_id: 花字效果ID(None则不添加) Returns: capcut.TextSegment: 创建的文字片段对象 """ try: # 创建文字片段 text_segment = capcut.TextSegment( content, target_timerange, font=font, style=capcut.TextStyle(color=color), clip_settings=capcut.ClipSettings(transform_y=position_y), ) # 添加出场动画 if outro_animation: text_segment.add_animation(outro_animation, duration=anim_duration) # 添加气泡 if bubble_id: text_segment.add_bubble(bubble_id, bubble_id) # 添加花字 if effect_id: text_segment.add_effect(effect_id) self.draft.add_segment(text_segment) return text_segment except Exception: raise RuntimeError("添加文字片段发生异常") def save(self) -> None: """保存草稿""" try: self.draft.save() print("草稿保存成功") except Exception: raise RuntimeError(f"保存草稿发生异常") # ======================== 调用示例(使用抽象后的方法) ======================== def main(): """生成脚本""" # 实例化 draft = Draft( draft_name="demo2", ) # 添加音频 draft.add_audio( file_name="audio.mp3", start_time="0s", duration="5s", volume=0.6, ) # 添加视频 video_segment = draft.add_video( file_name="video.mp4", start_time="0s", duration="4.2s", keyframes=[ (capcut.KeyframeProperty.position_x, tim(0), -2), (capcut.KeyframeProperty.position_x, tim("0.5s"), 0), ], ) # 添加图片/贴纸 draft.add_image( file_name="sticker.gif", start_time=video_segment.end, # 视频结束位置开始 background_filling=("blur", 0.0625), ) # 添加文字 draft.add_text( content="抽象化后更易扩展!", target_timerange=video_segment.target_timerange, position_y=-0.5, # 微调位置 ) # 保存草稿 draft.save() if __name__ == "__main__": main()