日常更新

from NUC
This commit is contained in:
liubiren 2025-12-25 21:49:22 +08:00
parent 00caffb1c6
commit 5300d95622
11 changed files with 428 additions and 290 deletions

View File

@ -0,0 +1,428 @@
# -*- coding: utf-8 -*-
"""
剪映脚本生成自动化
"""
import asyncio
import uuid
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
import edge_tts
import pycapcut as capcut
from mutagen.mp3 import MP3
from pycapcut import trange
from pycapcut.keyframe import KeyframeProperty
from pycapcut.segment import ClipSettings
class EdgeTTS:
"""
edge在线语音合成
"""
# 中文音色
TIMBRES = {
"女声-晓晓": "zh-CN-XiaoxiaoNeural",
"女声-晓辰": "zh-CN-XiaochenNeural",
"女声-晓倩": "zh-CN-XiaoqianNeural",
}
def __init__(
self,
materials_path: Path,
):
"""
初始化语音合成器
:param materials_path: 素材文件夹路径
"""
# 素材文件夹路径
self.materials_path = materials_path
def generate_audio(
self,
content: str,
timbre: Optional[str] = "女声-晓晓",
rate: str = "+0%",
volume: str = "+0%",
) -> Tuple[str, int]:
"""
根据文本内容合成语音并返回音频素材名称
:param content: 文本内容
:param timbre: 音色名称例如女声-晓晓
:param rate: 语速
:param volume: 音量
:return 音频素材名称和持续时长
"""
# noinspection PyBroadException
try:
# 异步处理:根据文本内容合成语音并保存为音频素材
async def _async_generate_audio():
# 实例化Communicate
communicator = edge_tts.Communicate(
text=content,
voice=self.TIMBRES[timbre],
rate=rate,
volume=volume,
)
# 音频素材名称
name = f"{uuid.uuid4().hex[-16:].upper()}.mp3"
# 音频素材路径
audio_path = self.materials_path / name
await communicator.save(audio_path := audio_path.as_posix())
# 音频持续时长(单位为微妙)
duration = int(round(MP3(audio_path).info.length * 1000000))
return name, duration
# 同步调用异步逻辑,对外暴露纯同步接口
return asyncio.run(_async_generate_audio())
except Exception as exception:
raise RuntimeError(
f"根据文本内容合成语音并保存为音频素材发声异常:{str(exception)}"
)
class GenerateDraft:
"""生成剪映脚本"""
# noinspection PyShadowingNames
def __init__(
self,
name: str,
video_width: int = 1080,
video_height: int = 1920,
video_fps: int = 30,
drafts_path: str = r"C:\Users\admin\AppData\Local\JianyingPro\User Data\Projects\com.lveditor.draft",
materials_path: str = r"C:\Users\admin\PycharmProjects\Python\剪映脚本生成自动化\materials",
):
"""
初始化草稿
:param name: 草稿名称
:param video_width: 视频宽度
:param video_height: 视频高度
:param video_fps: 视频帧率
:param drafts_path: 剪映草稿文件夹路径
:param materials_path: 素材文件夹路径
"""
print("正在生成剪映脚本...")
# noinspection PyBroadException
try:
# 草稿名称
self.name = name
# 视频宽度和高度
self.video_width, self.video_height = video_width, video_height
# 视频帧率
self.video_fps = video_fps
# 剪映草稿文件夹路径
self.drafts_path = drafts_path
# 素材文件夹路径
self.materials_path = Path(materials_path) # 转为path对象
# 检查素材文件夹是否存在,若不存在则抛出异常
if not self.materials_path.exists():
raise FileNotFoundError(f"素材文件夹不存在")
# 初始化草稿文件夹管理器
self.draft_folder = capcut.DraftFolder(self.drafts_path)
# 新建草稿
self.draft = self.draft_folder.create_draft(
draft_name=self.name,
width=self.video_width,
height=self.video_height,
fps=self.video_fps,
allow_replace=True, # 允许覆盖
)
self.draft.add_track(track_type=capcut.TrackType.video)
# 音频持续时长
self.audio_duration = 0
# 实例化EdgeTTS
self.synthesizer = EdgeTTS(self.materials_path)
except Exception as exception:
print(f"发生异常:{str(exception)}")
raise
def _get_material(self, name: str) -> str:
"""
获取素材
:param name: 素材名称
:return 素材路径
"""
# 素材路径
material_path = self.materials_path / name
if not material_path.exists():
raise FileNotFoundError(f"素材文件不存在")
return material_path.as_posix()
def _add_audio(
self,
name: str,
target_timerange: Tuple[Optional[int, str], Optional[int, str]],
source_timerange: Optional[Tuple[str, str]] = None,
speed: Optional[float] = None,
volume: float = 1.0,
fade: Optional[Tuple[str, str]] = None,
) -> None:
"""
添加音频片段
:param name: 音频素材名称
:param target_timerange: 音频素材在轨道上的范围包括开始时间和持续时长
:param source_timerange: 截取音频素材范围包括开始时间和持续时长默认根据音频素材开始时间根据播放速度截取与音频素材持续时长等长的部分
:param speed: 播放速度
:param volume: 播放音量
:param fade: 淡入淡出设置
:return:
"""
print(f"正在添加音频片段 {name}...", end="")
try:
# 构建音频片段
audio_segment = capcut.AudioSegment(
material=self._get_material(name),
target_timerange=trange(*target_timerange),
source_timerange=(
trange(*source_timerange) if source_timerange else None
),
speed=speed,
volume=volume,
)
# 添加淡入淡出效果
if fade:
audio_segment.add_fade(*fade)
self.draft.add_segment(audio_segment)
print("已完成")
return
except Exception as exception:
print(f"发生异常:{str(exception)}")
raise
def _add_video(
self,
name: str,
target_timerange: Tuple[Optional[int, str], Optional[int, str]],
source_timerange: Optional[
Tuple[Optional[int, str], Optional[int, str]],
] = None,
speed: Optional[float] = None,
volume: float = 1.0,
clip_settings: Optional[Dict[str, Any]] = None,
keyframes: Optional[List[Tuple[KeyframeProperty, str, float]]] = None,
animation: Optional[Dict[str, Any]] = None,
transition: Optional[Dict[str, Any]] = None,
background_filling: Optional[Tuple[str, Any]] = None,
) -> None:
"""
添加视频/图片片段
:param name: 视频/图片素材名称
:param target_timerange: 视频素材在轨道上的范围包括开始时间和持续时长
:param source_timerange: 截取视频素材范围包括开始时间和持续时长
:param speed: 播放速度
:param volume: 播放音量
:param clip_settings: 图像调节设置
:param keyframes: 关键帧设置
:param animation: 动画设置
:param transition: 转场设置
:param background_filling: 背景填充设置
:return:
"""
print(f"正在添加视频/图片片段 {name}...", end="")
try:
# 构建视频片段
video_segment = capcut.VideoSegment(
material=self._get_material(name),
target_timerange=trange(*target_timerange),
source_timerange=(
trange(*source_timerange) if source_timerange else None
),
speed=speed,
volume=volume,
clip_settings=ClipSettings(**clip_settings) if clip_settings else None,
)
# (视频素材)添加关键帧
if keyframes:
# noinspection PyShadowingBuiltins
for property, time, value in keyframes:
video_segment.add_keyframe(property, time, value)
# (视频素材)添加动画
if animation:
video_segment.add_animation(**animation)
# (视频素材)添加转场
if transition:
video_segment.add_transition(**transition)
# (图片素材)添加背景填充
if background_filling:
video_segment.add_background_filling(*background_filling)
self.draft.add_segment(video_segment)
print("已完成")
return
except Exception as exception:
print(f"发生异常:{str(exception)}")
raise
def _add_text(
self,
content: str,
timerange: Tuple[Optional[int, str], Optional[int, str]],
border: Optional[Dict[str, Any]] = None,
background: Optional[Dict[str, Any]] = None,
font: Optional[str] = None,
style: Optional[Dict[str, Any]] = None,
clip_settings: Optional[Dict[str, Any]] = None,
bubble: Optional[Dict[str, Any]] = None,
effect: Optional[str] = None,
animation: Optional[Dict[str, Any]] = None,
) -> None:
"""
构建文本片段
:param content: 文本内容
:param timerange: 文本素材在轨道上的范围包括开始时间和持续时长
:param border: 文本描边设置
:param background: 文本背景设置
:param font: 字体类型
:param style: 字体样式
:param clip_settings: 图像调节设置
:param bubble: 气泡设置
:param effect: 花字设置
:param animation: 动画设置
:return: 不返回
"""
print(f"正在添加文本片段 {content}...", end="")
try:
# 创建文字片段
text_segment = capcut.TextSegment(
text=content,
timerange=trange(*timerange),
border=capcut.TextBorder(**border) if border else None,
background=capcut.TextBackground(**background) if border else None,
font=capcut.FontType(font) if font else None,
style=capcut.TextStyle(**style) if style else None,
clip_settings=(
capcut.ClipSettings(**clip_settings) if clip_settings else None
),
)
# 添加气泡
if bubble:
text_segment.add_bubble(**bubble)
# 添加花字可先保存预设再在C:/Users/admin/AppData/Local/JianyingPro/User Data/Presets/Text_V2/预设文本?.textpreset获取花字resource_id
if effect:
text_segment.add_effect(effect)
# 添加动画
if animation:
text_segment.add_animation(**animation)
self.draft.add_segment(text_segment)
print("已完成")
return
except Exception as exception:
print(f"发生异常:{str(exception)}")
raise
def _save(self) -> None:
"""保存草稿"""
try:
self.draft.save()
print("草稿保存成功")
except Exception as exception:
print(f"发生异常:{str(exception)}")
raise
def create_audio_and_text(
self,
script: str,
timbre: Optional[str] = "女声-晓晓",
rate: str = "+25%",
volume: str = "+0%",
):
"""
根据脚本合成音频和文本素材
:param script: 脚本
:param timbre: 声音音色
:param rate: 语速
:param volume: 音量
:return:
"""
print("正在根据脚本合成音频和文本素材...")
# 添加音频和文本轨道
self.draft.add_track(track_type=capcut.TrackType.audio)
self.draft.add_track(track_type=capcut.TrackType.text)
start = 0
for content in script.split(""):
# 根据文本内容合成语音并返回音频素材名称
name, duration = self.synthesizer.generate_audio(
content, timbre, rate, volume
)
# 构建音频片段
self._add_audio(name=name, target_timerange=(start, duration))
# 构建文本片段
self._add_text(
content=content,
timerange=(start, duration),
style={"size": 12.0, "align": 1}, # 字号为12对齐方式为水平居中
clip_settings={"transform_y": -0.6}, # 垂直位移
effect="6896137858998930701", # 第二行第三列花字
)
start += duration
self.audio_duration = start
def create_video(
self,
name: str,
target_timerange: Tuple[Optional[int, str], Optional[int, str]],
source_timerange: Optional[
Tuple[Optional[int, str], Optional[int, str]],
] = None,
speed: Optional[float] = None,
volume: float = 1.0,
clip_settings: Optional[Dict[str, Any]] = None,
keyframes: Optional[List[Tuple[KeyframeProperty, str, float]]] = None,
animation: Optional[Dict[str, Any]] = None,
transition: Optional[Dict[str, Any]] = None,
background_filling: Optional[Tuple[str, Any]] = None,
):
"""
根据脚本合成音频和文本素材
:param script: 脚本
:param timbre: 声音音色
:param rate: 语速
:param volume: 音量
:return:
"""
print("正在根据脚本合成音频和文本素材...")
# 添加音频和文本轨道
# ======================== 调用示例(使用抽象后的方法) ========================
def execute_workflow():
"""生成剪映草稿"""
# 实例化
draft = GenerateDraft(
name="demo2",
)
# 根据脚本合成音频和文本素材
draft.generate_audio_and_text(
script="所有人今天准备狂点外卖是真的0.1元起一杯的霸王茶姬还外卖到家怎么能不来一杯呢现在淘宝闪购给大家发福利最高22元无门槛红包官方链接就在下方奶茶脑袋快冲"
)
# 构建背景视频片段
draft._add_video(name="background.mp4", target_timerange=(0, draft.audio_duration))
# draft._add_video(name="arrow.gif", target_timerange=(0, draft.audio_duration))
# 保存草稿
draft._save()
if __name__ == "__main__":
execute_workflow()

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Binary file not shown.

View File

@ -1,290 +0,0 @@
# -*- 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: 文字颜色RGB0-1
position_y: 文字Y轴位置-0.8为屏幕下方
outro_animation: 出场动画None则不添加
anim_duration: 动画时长
bubble_id: 气泡效果IDNone则不添加
effect_id: 花字效果IDNone则不添加
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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB