291 lines
9.4 KiB
Python
291 lines
9.4 KiB
Python
# -*- 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()
|