日常更新

from NUC
This commit is contained in:
liubiren 2026-01-04 22:02:43 +08:00
parent e8a0f5d414
commit e6bb9efb14
6 changed files with 612 additions and 403 deletions

View File

@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
"""
剪映脚本生成自动化
"""
from capcut import GenerateDraft
# 编导方案1
def direct():
"""生成剪映草稿"""
# 实例化
draft = GenerateDraft(
name="demo2",
) # 需要
# 根据脚本生成文本和音频字幕
draft.add_subtitle(
script="所有人今天准备狂点外卖是真的0.1元起一杯的霸王茶姬还外卖到家怎么能不来一杯呢现在淘宝闪购给大家发福利最高22元无门槛红包官方链接就在下方奶茶脑袋快冲"
)
# 为背景视频添加视频轨道并添加视频片段
draft.add_video(
track_name="background",
name="background.mp4",
clip_settings={"scale_x": 2.5, "scale_y": 2.5},
)
# 为logo添加视频轨道并添加图片片段
draft.add_video(
track_name="logo",
name="logo.png",
)
# 为免责声明添加视频轨道并添加图片片段
draft.add_text(
track_name="disclaimer",
content="支付需谨慎谨防诈骗\n仅限支付宝用户,详情以活动为准",
)
draft.add_sticker(track_name="sticker", resource_id="7026858083393588487")
# 保存草稿
draft.save()
if __name__ == "__main__":
execute_workflow()

View File

@ -1,287 +0,0 @@
# -*- coding: utf-8 -*-
import random
from pathlib import Path
from typing import Any, Dict, Literal
from capcut import CapCutDraft
class WorkFlow:
"""
生成 CapCut草稿的工作流支持
1初始化素材文件夹内所有素材
2就工作流添加工作
3基于工作流生成草稿
"""
# noinspection PyShadowingNames
def __init__(
self,
materials_folder_path: str,
video_width: int = 1080,
video_height: int = 1920,
video_fps: int = 30,
):
"""
初始化
:param materials_folder_path: 素材文件夹路径
:param video_width: 视频宽度默认为 1080像素
:param video_height: 视频高度默认为 1920像素
:param video_fps: 视频帧率单位为帧/默认为 30
"""
print("正在初始化 DraftsGenerator...", end="")
# noinspection PyBroadException
try:
self.materials_folder_path = Path(materials_folder_path)
if not self.materials_folder_path.exists():
raise RuntimeError("素材文件夹路径不存在")
# 构建项目名称
self.project_name = self.materials_folder_path.stem
# 初始化工作流
self.workflow = [
"add_subtitles",
"add_background_video",
"add_logo",
"add_statement",
"add_sticker",
"save",
]
self.video_width = video_width
self.video_height = video_height
self.video_fps = video_fps
# 初始化素材文件夹内所有素材
self.materials = {}
self._init_materials()
# 初始化所有工作配置
self.configurations = {
"add_subtitles": {
"text": self.materials["subtitles_text"],
"timbre": [
"zh-CN-XiaoxiaoNeural",
"zh-CN-XiaoyiNeural",
"zh-CN-YunjianNeural",
"zh-CN-YunxiNeural",
],
"style": [{"size": 8}, {"size": 10}],
"effect": [
{"effect_id": "7127561998556089631"},
{"effect_id": "7166467215410187552"},
{"effect_id": "6896138122774498567"},
],
},
"add_background_video": {
"track_name": ["background_video"],
"material_path": self.materials["background_video_material_path"],
"clip_settings": [
None,
{
"transform_x": 0.2,
},
{
"transform_x": -0.2,
},
{
"transform_y": 0.2,
},
{
"transform_y": -0.2,
},
],
},
"add_logo": {
"track_name": ["logo"],
"material_path": self.materials["logo_material_path"],
"clip_settings": [
{
"scale_x": 0.2,
"scale_y": 0.2,
"transform_x": -0.68,
"transform_y": 0.82,
},
{
"scale_x": 0.2,
"scale_y": 0.2,
"transform_x": 0,
"transform_y": 0.82,
},
{
"scale_x": 0.2,
"scale_y": 0.2,
"transform_x": 0.68,
"transform_y": 0.82,
},
],
},
"add_statement": {
"track_name": ["statement"],
"text": self.materials["statement_text"],
"border": [
{"width": 40.0},
{"width": 50.0},
{"width": 60.0},
], # 描边宽度
"style": [{"size": 8.0, "align": 1}], # 文本样式
"clip_settings": [
{
"transform_y": -0.8,
}
], # 图像调节设置
},
"add_sticker": {
"track_name": ["sticker"],
"resource_id": ["7026858083393588487"],
"clip_settings": [
{
"scale_x": 0.2,
"scale_y": 0.2,
"transform_x": -0.75,
"transform_y": -0.78,
}
], # 图像调节设置
},
}
print("已完成")
except Exception as exception:
raise RuntimeError(f"发生异常:{str(exception)}")
def _init_materials(self) -> None:
"""
初始化素材文件夹内所有素材
:return:
"""
# 字幕文本
subtitles_path = self.materials_folder_path / "字幕文本.txt"
if subtitles_path.exists() and subtitles_path.is_file():
with open(subtitles_path, "r", encoding="utf-8") as file:
subtitles_text = file.readlines()
if not subtitles_text:
raise RuntimeError("字幕文本为空")
self.materials["subtitles_text"] = subtitles_text
else:
raise RuntimeError("字幕文本不存在")
# 背景视频
background_videos_path = self.materials_folder_path / "背景视频"
if background_videos_path.exists() and background_videos_path.is_dir():
background_video_material_path = [
file_path
for file_path in background_videos_path.rglob("*.mp4")
if file_path.is_file()
]
if not background_video_material_path:
raise RuntimeError("背景视频为空")
self.materials["background_video_material_path"] = (
background_video_material_path
)
else:
raise RuntimeError("背景视频文件夹不存在")
# 标识
logo_path = self.materials_folder_path / "标识.png"
if logo_path.exists() and logo_path.is_file():
self.materials["logo_material_path"] = [logo_path] # 有且只有一张标识
else:
raise RuntimeError("标识不存在")
# 声明文本
statement_path = self.materials_folder_path / "声明文本.txt"
if statement_path.exists() and statement_path.is_file():
with open(statement_path, "r", encoding="utf-8") as file:
statement_text = file.readlines()
if not statement_text:
raise RuntimeError("声明文本为空")
self.materials["statement_text"] = statement_text
else:
raise RuntimeError("声明不存在")
def add_work(
self,
work: Literal[
"add_subtitles",
"add_background_video",
"add_logo",
"add_statement",
"add_sticker",
"save",
],
) -> None:
"""
就工作流添加工作
:param work: 工作目前仅支持添加字幕添加背景视频添加标识添加声明添加贴纸和保存
:return:
"""
self.workflow.append(work)
def generate(
self,
counts: int = 1,
) -> None:
"""
基于工作流生成草稿
:param counts: 生成 CapCut草稿数
:return:
"""
for idx in range(counts):
draft_name = self.project_name + f"{idx + 1:03d}"
# 实例化 CapCutDraft
draft = CapCutDraft(
draft_name=draft_name,
video_width=self.video_width,
video_height=self.video_height,
video_fps=self.video_fps,
materials_folder_path=self.materials_folder_path,
)
for work in self.workflow:
match work:
# 添加字幕
case "add_subtitles":
draft.add_subtitles(**self._random(work=work))
# 添加背景视频
case "add_background_video":
draft.add_video_segment(**self._random(work=work))
# 添加标识
case "add_logo":
draft.add_video_segment(**self._random(work=work))
# 添加声明
case "add_statement":
draft.add_text_segment(**self._random(work=work))
# 添加贴纸
case "add_sticker":
draft.add_sticker(**self._random(work=work))
# 将草稿保存至 CapCut草稿文件夹内
case "save":
draft.save()
def _random(
self,
work: Literal[
"add_subtitles",
"add_background_video",
"add_logo",
"add_statement",
"add_sticker",
],
) -> Dict[str, Any]:
"""
随机获取工作配置
:param work: 工作包括添加字幕添加背景视频添加标识添加声明和添加贴纸
:return: 工作配置
"""
return {
key: random.choice(value)
for key, value in self.configurations[work].items()
}
a = WorkFlow(materials_folder_path=r"E:\projects\251225")
a.generate(counts=5)

View File

@ -3,32 +3,28 @@
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, Union
import pycapcut as capcut
from pycapcut import trange
from pycapcut.keyframe import KeyframeProperty
from pycapcut.segment import ClipSettings
import pyJianYingDraft
from edgetts import EdgeTTS
class CapCutDraft:
class JianYingDraft:
"""
封装pyCapCut支持
封装 pyJianYing中生成草稿的相关功能支持
1向指定文本轨道添加文本片段
2向指定音频轨道添加音频片段
3向指定视频轨道添加视频或图片片段
4向指定贴纸轨道添加贴纸片段
5根据文本逐段合成语音生成文本和语音字幕
6将草稿保存至 CapCut草稿文件夹内
导出尚未支持
6将草稿保存至剪映草稿文件夹内
"""
# noinspection PyShadowingNames
def __init__(
self,
draft_name: str,
materials_folder_path: Union[str, Path],
capcut_folder_path: str = r"C:\Users\admin\AppData\Local\JianyingPro\User Data\Projects\com.lveditor.draft",
materials_folder_path: Path,
drafts_folder_path: str = r"E:\JianyingPro Drafts",
allow_replace: bool = True,
video_width: int = 1080,
video_height: int = 1920,
@ -36,7 +32,7 @@ class CapCutDraft:
):
"""
初始化
:param capcut_folder_path: CapCut草稿文件夹路径
:param drafts_folder_path: 剪映草稿文件夹路径
:param draft_name: 草稿名称
:param allow_replace: 是否允许覆盖同名草稿
:param video_width: 视频宽度默认为 1080像素
@ -44,33 +40,24 @@ class CapCutDraft:
:param video_fps: 视频帧率单位为帧/默认为 30
:param materials_folder_path: 素材文件夹路径
"""
print("正在初始化 CapCutDraft...", end="")
# noinspection PyBroadException
try:
# 初始化草稿文件夹管理器
self.draft_folder = capcut.DraftFolder(capcut_folder_path)
self.draft_folder = pyJianYingDraft.DraftFolder(drafts_folder_path)
# 新建草稿
self.draft = self.draft_folder.create_draft(
draft_name=draft_name,
allow_replace=allow_replace,
width=video_width,
height=video_height,
fps=video_fps,
allow_replace=allow_replace,
)
# 草稿持续时长
# 草稿持续时长(单位为毫秒)
self.draft_duration = 0
self.materials_folder_path = (
materials_folder_path
if isinstance(materials_folder_path, Path)
else Path(materials_folder_path)
)
if not self.materials_folder_path.exists():
raise RuntimeError("素材文件夹路径不存在")
self.materials_folder_path = materials_folder_path
print("已完成")
except Exception as exception:
raise RuntimeError(f"发生异常:{str(exception)}")
@ -105,27 +92,30 @@ class CapCutDraft:
:param animation: 动画设置默认为无
:return:
"""
print(f"正在向 {track_name}文本轨道添加文本片段...", end="")
try:
if add_track:
# 添加文本轨道
self.draft.add_track(
track_type=capcut.TrackType.text,
track_type=pyJianYingDraft.TrackType.text,
track_name=track_name,
)
# 构建文本片段
text_segment = capcut.TextSegment(
text_segment = pyJianYingDraft.TextSegment(
text=text.replace("\\n", "\n"),
timerange=trange(
timerange=pyJianYingDraft.trange(
*(timerange if timerange else (0, self.draft_duration))
),
font=capcut.FontType(font) if font else None,
style=capcut.TextStyle(**style) if style else None,
border=capcut.TextBorder(**border) if border else None,
background=capcut.TextBackground(**background) if background else None,
font=pyJianYingDraft.FontType(font) if font else None,
style=pyJianYingDraft.TextStyle(**style) if style else None,
border=pyJianYingDraft.TextBorder(**border) if border else None,
background=(
pyJianYingDraft.TextBackground(**background) if background else None
),
clip_settings=(
capcut.ClipSettings(**clip_settings) if clip_settings else None
pyJianYingDraft.ClipSettings(**clip_settings)
if clip_settings
else None
),
)
# 添加气泡
@ -144,8 +134,7 @@ class CapCutDraft:
# 向指定文本轨道添加文本片段
self.draft.add_segment(segment=text_segment, track_name=track_name)
print("已完成")
return
except Exception:
raise
@ -174,19 +163,18 @@ class CapCutDraft:
:param fade: 淡入淡出设置默认为无
:return:
"""
print(f"正在向 {track_name}音频轨道添加音频片段...", end="")
try:
if add_track:
# 添加音频轨道
self.draft.add_track(
track_type=capcut.TrackType.video,
track_type=pyJianYingDraft.TrackType.video,
track_name=track_name,
)
# 构建音频片段
audio_segment = capcut.AudioSegment(
audio_segment = pyJianYingDraft.AudioSegment(
material=material_path.as_posix(),
target_timerange=trange(
target_timerange=pyJianYingDraft.trange(
*(
target_timerange
if target_timerange
@ -194,7 +182,9 @@ class CapCutDraft:
)
),
source_timerange=(
trange(*source_timerange) if source_timerange else None
pyJianYingDraft.trange(*source_timerange)
if source_timerange
else None
),
speed=speed,
volume=volume,
@ -205,8 +195,7 @@ class CapCutDraft:
# 向指定音频轨道添加音频片段
self.draft.add_segment(segment=audio_segment, track_name=track_name)
print("已完成")
return
except Exception:
raise
@ -223,7 +212,9 @@ class CapCutDraft:
speed: Optional[float] = 1.0,
volume: Optional[float] = 1.0,
clip_settings: Optional[Dict[str, Any]] = None,
keyframes: Optional[List[Tuple[KeyframeProperty, str, float]]] = None,
keyframes: Optional[
List[Tuple[pyJianYingDraft.keyframe, Union[str, int], float]]
] = None,
animation: Optional[Dict[str, Any]] = None,
transition: Optional[Dict[str, Any]] = None,
background_filling: Optional[Tuple[str, Any]] = None,
@ -243,11 +234,10 @@ class CapCutDraft:
:param background_filling: 背景填充设置默认为无
:return:
"""
print(f"正在向 {track_name}视频轨道添加视频/图片片段...", end="")
try:
# 添加视频轨道
self.draft.add_track(
track_type=capcut.TrackType.video,
track_type=pyJianYingDraft.TrackType.video,
track_name=track_name,
)
@ -257,35 +247,41 @@ class CapCutDraft:
)
# 视频素材
video_material = capcut.VideoMaterial(path=material_path.as_posix())
video_material = pyJianYingDraft.VideoMaterial(
path=material_path.as_posix()
)
# 视频素材的持续时长
video_material_duration = video_material.duration
duration = 0 # 已添加视频素材的持续时长
while duration < target_duration:
# 构建视频或图片片段
video_segment = capcut.VideoSegment(
video_segment = pyJianYingDraft.VideoSegment(
material=video_material,
target_timerange=trange(
target_timerange=pyJianYingDraft.trange(
start=duration,
duration=min(
(target_duration - duration), video_material_duration
),
),
source_timerange=(
trange(*source_timerange) if source_timerange else None
pyJianYingDraft.trange(*source_timerange)
if source_timerange
else None
),
speed=speed,
volume=volume,
clip_settings=(
ClipSettings(**clip_settings) if clip_settings else None
pyJianYingDraft.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)
for property, offset, value in keyframes:
video_segment.add_keyframe(property, offset, value)
# 添加动画
if animation:
@ -304,8 +300,6 @@ class CapCutDraft:
duration += video_material_duration
print("已完成")
return
except Exception:
raise
@ -326,18 +320,17 @@ class CapCutDraft:
:param clip_settings: 图像调节设置默认为无
:return:
"""
print(f"正在向 {track_name}贴纸轨道添加贴纸片段...", end="")
try:
# 添加贴纸轨道
self.draft.add_track(
track_type=capcut.TrackType.sticker,
track_type=pyJianYingDraft.TrackType.sticker,
track_name=track_name,
)
# 构建贴纸
sticker_segment = capcut.StickerSegment(
sticker_segment = pyJianYingDraft.StickerSegment(
resource_id=resource_id, # 可先将贴纸保存为我的预设再在C:\Users\admin\AppData\Local\JianyingPro\User Data\Presets\Combination\Presets\我的预设?\preset_draft获取
target_timerange=trange(
target_timerange=pyJianYingDraft.trange(
*(
target_timerange
if target_timerange
@ -345,13 +338,15 @@ class CapCutDraft:
)
),
clip_settings=(
capcut.ClipSettings(**clip_settings) if clip_settings else None
pyJianYingDraft.ClipSettings(**clip_settings)
if clip_settings
else None
),
)
# 向指定贴纸轨道添加贴纸片段
self.draft.add_segment(segment=sticker_segment, track_name=track_name)
print("已完成")
except Exception:
raise
@ -378,15 +373,14 @@ class CapCutDraft:
:param effect: 花字设置默认为无
:return:
"""
print("正在添加字幕...")
# 添加文本轨道
self.draft.add_track(
track_type=capcut.TrackType.text,
track_type=pyJianYingDraft.TrackType.text,
track_name=(text_track_name := "subtitles(text)"),
)
# 添加音频轨道
self.draft.add_track(
track_type=capcut.TrackType.audio,
track_type=pyJianYingDraft.TrackType.audio,
track_name=(audio_track_name := "subtitles(audio)"),
)
@ -415,7 +409,7 @@ class CapCutDraft:
**(style or {}),
},
clip_settings={
"transform_y": -0.5,
"transform_y": -0.75,
**(clip_settings or {}),
},
effect=effect,
@ -426,20 +420,17 @@ class CapCutDraft:
add_track=False,
material_path=file_path,
target_timerange=(start, duration),
volume=1.5,
)
start += duration
# 更新草稿持续时长
self.draft_duration = start
print("已完成")
return
def save(self) -> None:
"""将草稿保存至 CapCut草稿文件夹内"""
print("正在将草稿保存至 CapCut草稿文件夹内...", end="")
"""将草稿保存至剪映草稿文件夹内"""
try:
self.draft.save()
print("已完成")
return
except Exception:
raise

View File

@ -0,0 +1,534 @@
# -*- coding: utf-8 -*-
import random
import subprocess
import time
from pathlib import Path
from typing import Any, Dict, Optional
import pyJianYingDraft
import win32con
import win32gui
from draft import JianYingDraft
class JianYingExport:
"""
封装 pyJianYing中导出草稿的相关功能支持
1初始化素材文件夹内所有素材
2就工作流添加工作
3基于工作流生成草稿
"""
# noinspection PyShadowingNames
def __init__(
self,
materials_folder_path: str,
jianying_program_path: str = r"E:\JianyingPro\5.9.0.11632\JianyingPro.exe",
draft_counts: int = 10,
video_width: int = 1080,
video_height: int = 1920,
video_fps: int = 30,
):
"""
初始化
:param jianying_program_path: 剪映执行程序路径
:param materials_folder_path: 素材文件夹路径
:param draft_counts: 草稿数默认为 10
:param video_width: 视频宽度默认为 1080像素
:param video_height: 视频高度默认为 1920像素
:param video_fps: 视频帧率单位为帧/默认为 30
"""
# noinspection PyBroadException
try:
self.jianying_program_path = Path(jianying_program_path)
if not self.jianying_program_path.exists():
raise RuntimeError("剪映执行程序路径不存在")
# 初始化剪映专业版进程
self.jianying_process = None
self.materials_folder_path = Path(materials_folder_path)
if not self.materials_folder_path.exists():
raise RuntimeError("素材文件夹路径不存在")
# 初始化导出文件夹路径
self.exports_folder_path = Path(
self.materials_folder_path.as_posix().replace("materials", "exports")
)
self.exports_folder_path.mkdir(exist_ok=True)
self.materials = {}
# 初始化素材文件夹内所有素材
self._init_materials()
# 构建项目名称
self.project_name = self.materials_folder_path.stem
# 初始化工作流,其目的是按照工作流和工作配置拼接素材,先生成草稿再导出
self.workflow = [
"add_subtitles",
"add_background_video",
"add_statement",
"add_sticker1",
"add_sticker2",
"save",
]
# 初始化工作配置
self.configuration = {
"add_subtitles": {
"text": self.materials["subtitles_text"],
"timbre": [
"zh-CN-XiaoxiaoNeural",
"zh-CN-XiaoyiNeural",
"zh-CN-YunjianNeural",
"zh-CN-YunxiNeural",
"zh-CN-YunxiaNeural",
"zh-CN-YunyangNeural",
], # 音色
"style": [
{"size": 9.0},
{"size": 10.0},
{"size": 11.0},
], # 字体样式
"effect": [
{"effect_id": "7127561998556089631"},
{"effect_id": "7166467215410187552"},
{"effect_id": "6896138122774498567"},
{"effect_id": "7166469374507765031"},
{"effect_id": "6896137924853763336"},
{"effect_id": "6896137990788091143"},
{"effect_id": "7127614731187211551"},
{"effect_id": "7127823362356743461"},
{"effect_id": "7127653467555990821"},
{"effect_id": "7127828216647011592"},
], # 花字设置
}, # 添加字幕工作配置
"add_background_video": {
"track_name": ["background_video"],
"material_path": self.materials["background_video_material_path"],
"volume": [0.3, 0.4, 0.5],
"clip_settings": [
None,
{
"transform_x": 0.1,
},
{
"transform_x": 0.2,
},
{
"transform_x": -0.1,
},
{
"transform_x": -0.2,
},
{
"transform_y": 0.1,
},
{
"transform_y": 0.2,
},
{
"transform_y": -0.1,
},
{
"transform_y": -0.2,
},
{
"transform_x": 0.1,
"transform_y": 0.1,
},
{
"transform_x": 0.1,
"transform_y": -0.1,
},
{
"transform_x": -0.1,
"transform_y": 0.1,
},
{
"transform_x": -0.1,
"transform_y": -0.1,
},
{
"transform_x": 0.2,
"transform_y": 0.2,
},
{
"transform_x": 0.2,
"transform_y": -0.2,
},
{
"transform_x": -0.2,
"transform_y": 0.2,
},
{
"transform_x": -0.2,
"transform_y": -0.2,
},
], # 图像调节设置
}, # 添加背景视频工作配置
"add_logo": {
"track_name": ["logo"],
"material_path": self.materials["logo_material_path"],
"clip_settings": [
{
"scale_x": 0.2,
"scale_y": 0.2,
"transform_x": -0.78,
"transform_y": 0.82,
},
{
"scale_x": 0.2,
"scale_y": 0.2,
"transform_x": -0.68,
"transform_y": 0.82,
},
{
"scale_x": 0.2,
"scale_y": 0.2,
"transform_x": 0,
"transform_y": 0.82,
},
{
"scale_x": 0.2,
"scale_y": 0.2,
"transform_x": 0.68,
"transform_y": 0.82,
},
{
"scale_x": 0.2,
"scale_y": 0.2,
"transform_x": 0.78,
"transform_y": 0.82,
},
],
}, # 添加标识工作配置
"add_statement": {
"track_name": ["statement"],
"text": self.materials["statement_text"],
"style": [
{"size": 6.0, "align": 1, "vertical": True},
{"size": 7.0, "align": 1, "vertical": True},
{"size": 8.0, "align": 1, "vertical": True},
], # 文本样式
"border": [
{"width": 40.0},
{"width": 44.0},
{"width": 50.0},
{"width": 55.0},
{"width": 60.0},
], # 描边宽度
"clip_settings": [
{
"transform_x": -0.80,
},
{
"transform_x": -0.82,
},
{
"transform_x": -0.84,
},
], # 图像调节设置
}, # 添加声明工作配置
"add_sticker1": {
"track_name": ["sticker1"],
"resource_id": [
"7110124379568098568",
"7019687632804334861",
"6895933678262750478",
"7010558788675652900",
"7026858083393588487",
"7222940306558209336",
"7120543009489341727",
"6939830545673227557",
"6939826722451754271",
"7210221631132749093",
"7138432572488453408",
"7137700067338620192",
"6895924436822674696",
"7134644683506044163",
"7062539853430279437",
],
"clip_settings": [
{
"scale_x": 0.75,
"scale_y": 0.75,
"transform_x": -0.75,
"transform_y": 0.75,
},
{
"scale_x": 0.75,
"scale_y": 0.75,
"transform_y": 0.75,
},
{
"scale_x": 0.75,
"scale_y": 0.75,
"transform_x": 0.75,
"transform_y": 0.75,
},
], # 图像调节设置
}, # 添加贴纸工作配置1不包含箭头类
"add_sticker2": {
"track_name": ["sticker2"],
"resource_id": [
"7143078914989018379",
"7142870400358255905",
"7185568038027103544",
"7024342011440319781",
"7205042602184363322",
],
"clip_settings": [
{
"scale_x": 0.75,
"scale_y": 0.75,
"transform_x": -0.8,
"transform_y": -0.62,
},
], # 图像调节设置
}, # 添加贴纸工作配置2箭头类
}
self.video_width, self.video_height = video_width, video_height
self.video_fps = video_fps
self.draft_counts = draft_counts
# 初始化批量生成草稿的所有草稿名称,用于批量导出
self.draft_names = []
except Exception as exception:
raise RuntimeError(f"发生异常:{str(exception)}")
def _init_materials(self) -> None:
"""
初始化素材文件夹内所有素材
:return:
"""
# 字幕文本
subtitles_path = self.materials_folder_path / "字幕文本.txt"
if subtitles_path.exists() and subtitles_path.is_file():
with open(subtitles_path, "r", encoding="utf-8") as file:
subtitles_text = file.readlines()
if not subtitles_text:
raise RuntimeError("字幕文本为空")
self.materials["subtitles_text"] = subtitles_text
else:
raise RuntimeError("字幕文本不存在")
# 背景视频
background_videos_path = self.materials_folder_path / "背景视频"
if background_videos_path.exists() and background_videos_path.is_dir():
background_video_material_path = [
file_path
for file_path in background_videos_path.rglob("*.mp4")
if file_path.is_file()
]
if not background_video_material_path:
raise RuntimeError("背景视频为空")
self.materials["background_video_material_path"] = (
background_video_material_path
)
else:
raise RuntimeError("背景视频文件夹不存在")
# 标识
logo_path = self.materials_folder_path / "标识.png"
if logo_path.exists() and logo_path.is_file():
self.materials["logo_material_path"] = [logo_path] # 有且只有一张标识
else:
raise RuntimeError("标识不存在")
# 声明文本
statement_path = self.materials_folder_path / "声明文本.txt"
if statement_path.exists() and statement_path.is_file():
with open(statement_path, "r", encoding="utf-8") as file:
statement_text = file.readlines()
if not statement_text:
raise RuntimeError("声明文本为空")
self.materials["statement_text"] = statement_text
else:
raise RuntimeError("声明不存在")
def _generate(
self,
) -> None:
"""
按照工作流和工作配置拼接素材生成草稿
:return:
"""
for idx in range(self.draft_counts):
# 构建草稿名称
draft_name = self.project_name + f"{idx + 1:03d}"
print(f"正在合成短视频 {draft_name} {idx + 1}/{self.draft_counts}...")
# 实例化 JianYingDraft
draft = JianYingDraft(
draft_name=draft_name,
video_width=self.video_width,
video_height=self.video_height,
video_fps=self.video_fps,
materials_folder_path=self.materials_folder_path,
)
for work in self.workflow:
match work:
case "add_subtitles":
print("-> 正在添加字幕...", end="")
draft.add_subtitles(**self._random(work=work))
print("已完成")
# 添加背景视频
case "add_background_video":
print("-> 正在添加背景视频...", end="")
draft.add_video_segment(**self._random(work=work))
print("已完成")
# 添加标识
case "add_logo":
print("-> 正在添加标识...", end="")
draft.add_video_segment(**self._random(work=work))
print("已完成")
# 添加声明
case "add_statement":
print("-> 正在添加声明...", end="")
draft.add_text_segment(**self._random(work=work))
print("已完成")
# 添加贴纸
case _ if work.startswith("add_sticker"):
print("-> 正在添加贴纸...", end="")
draft.add_sticker(**self._random(work=work))
print("已完成")
# 将草稿保存至剪映草稿文件夹内
case "save":
print("-> 正在将草稿保存至剪映草稿文件夹内...", end="")
draft.save()
print("已完成")
self.draft_names.append(draft_name)
print("已完成")
print()
# 按照草稿名称倒叙排序
self.draft_names.sort(reverse=True)
def _random(
self,
work: str,
) -> Dict[str, Any]:
"""
随机化工作配置项
:param work: 工作包括添加字幕添加背景视频添加标识添加声明和添加贴纸
:return: 工作配置
"""
return {
key: random.choice(value) for key, value in self.configuration[work].items()
}
def export(self):
"""
导出草稿
"""
# 按照工作流和工作配置拼接素材,生成草稿
self._generate()
# 启动剪映专业版进程
self._start_process()
time.sleep(10)
# 初始化剪映控制器
jianying_controller = pyJianYingDraft.JianyingController()
for draft_name in self.draft_names:
print(f"正在导出 {draft_name}...")
if (self.exports_folder_path / f"{draft_name}.mp4").is_file():
print("存在相同名称的草稿,跳过")
continue
jianying_controller.export_draft(
draft_name=draft_name, output_path=self.exports_folder_path.as_posix()
)
print("已完成")
print()
# 关闭剪映专业版进程
self._close_process()
def _start_process(self, timeout: int = 60) -> None:
"""
启动剪映专业版进程
:param timeout: 最大等待时间单位为秒默认为 60
:return:
"""
try:
# 关闭剪映专业版进程
self._close_process()
# 非堵塞方法
self.jianying_process = subprocess.Popen(
args=self.jianying_program_path.as_posix(),
shell=True, # 适配 Windows路径中的空格
stdout=subprocess.DEVNULL, # 重定向
stderr=subprocess.DEVNULL,
creationflags=subprocess.CREATE_NEW_CONSOLE,
)
start_time = time.time()
while time.time() - start_time < timeout:
# 定位剪映执行程序窗口
if self._locate_window() is not None:
print(f"已启动剪映专业版进程PID {self.jianying_process.pid}")
return
time.sleep(2)
raise RuntimeError("启动超时")
except Exception as exception:
raise RuntimeError(f"启动剪映专业版进程发生异常:{str(exception)}")
def _close_process(self, timeout: int = 60) -> None:
"""
关闭剪映专业版进程
:param timeout: 最大等待时间单位为秒默认为 60
:return:
"""
try:
# 定位剪映执行程序窗口
window_handle = self._locate_window()
if window_handle is not None:
# 请求关闭剪映执行程序窗口
win32gui.SendMessage(window_handle, win32con.WM_CLOSE, 0, 0)
start_time = time.time()
while time.time() - start_time < timeout:
if not win32gui.IsWindow(window_handle):
print("已关闭剪映专业版进程")
return
else:
time.sleep(2)
raise RuntimeError("关闭超时")
except Exception as exception:
raise RuntimeError(f"关闭剪映专业版进程发生异常:{str(exception)}")
@staticmethod
def _locate_window() -> Optional[int]:
"""
定位剪映执行程序窗口
:return: 剪映执行程序窗口句柄
"""
window_handle = None
def callback(handle, _):
"""
遍历所有窗口的回调函数
"""
# 初始化窗口句柄
nonlocal window_handle
# 获取窗口标题
window_text = win32gui.GetWindowText(handle)
# 检查窗口是否可见且窗口标题为剪映专业版
if win32gui.IsWindowVisible(handle) and window_text == "剪映专业版":
window_handle = handle
return False
return True
# 遍历所有顶层窗口
win32gui.EnumWindows(callback, None)
return window_handle

View File

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
"""
短视频合成自动化
"""
from export import JianYingExport
if __name__ == "__main__":
# 实例化 JianYingExport
jianying_export = JianYingExport(
materials_folder_path=r"E:\jianying\materials\260104",
draft_counts=1,
)
# 导出草稿
jianying_export.export()