696 lines
28 KiB
Python
696 lines
28 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
剪映草稿管理器模块
|
|
"""
|
|
|
|
# 列举导入模块
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import random
|
|
import re
|
|
import shutil
|
|
import sys
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from pyJianYingDraft import DraftFolder, VideoMaterial
|
|
|
|
from caches import Caches
|
|
from drafts import Drafts
|
|
|
|
sys.path.append(Path(__file__).parent.parent.as_posix())
|
|
|
|
|
|
class DraftsManager:
|
|
"""
|
|
剪映草稿管理器
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
materials_folder_path: str,
|
|
drafts_folder_path: str = r"E:\JianYingPro Drafts",
|
|
):
|
|
"""
|
|
初始化
|
|
:param materials_folder_path: 素材文件夹路径。其中,文件夹名称默认为工作流名称
|
|
:param drafts_folder_path: 剪映草稿文件夹路径,默认为 E:\\JianYingPro Drafts
|
|
"""
|
|
try:
|
|
# 初始化素材文件夹路径
|
|
self.materials_folder_path = Path(materials_folder_path)
|
|
if not self.materials_folder_path.exists():
|
|
raise RuntimeError("素材文件夹路径不存在")
|
|
|
|
# 初始化所有素材
|
|
self.materials = self._init_materials()
|
|
|
|
# 初始化导出视频文件夹路径
|
|
self.products_folder_path = Path(
|
|
materials_folder_path.replace("materials", "products")
|
|
)
|
|
# 若导出视频文件夹路径已存在则先删除
|
|
if self.products_folder_path.exists():
|
|
shutil.rmtree(self.products_folder_path)
|
|
self.products_folder_path.mkdir(parents=True)
|
|
|
|
# 初始化剪映草稿文件夹路径
|
|
self.drafts_folder_path = Path(drafts_folder_path)
|
|
if not self.drafts_folder_path.exists():
|
|
raise RuntimeError("剪映草稿文件夹路径不存在")
|
|
|
|
# 初始化节点配置
|
|
self.configurations = self._init_configurations()
|
|
|
|
# 初始化剪映草稿文件夹管理器
|
|
self.drafts_folder = DraftFolder(folder_path=drafts_folder_path)
|
|
|
|
# 实例化缓存
|
|
self.caches = Caches()
|
|
except Exception as exception:
|
|
raise RuntimeError(f"发生异常:{str(exception)}") from exception
|
|
|
|
def _init_materials(self) -> Dict[str, List[Any]]:
|
|
"""
|
|
初始化所有素材
|
|
:return: 所有素材
|
|
"""
|
|
materials = {}
|
|
# 构建字幕文本路径
|
|
subtitle_text_path = self.materials_folder_path / "字幕文本.txt"
|
|
if subtitle_text_path.exists() and subtitle_text_path.is_file():
|
|
with open(subtitle_text_path, "r", encoding="utf-8") as file:
|
|
# 字幕文本列表
|
|
subtitle_texts = file.readlines()
|
|
if not subtitle_texts:
|
|
raise RuntimeError("字幕文本为空")
|
|
materials["subtitle_texts"] = subtitle_texts
|
|
else:
|
|
materials["subtitle_texts"] = []
|
|
|
|
# 构建字幕视频文件夹路径
|
|
subtitle_video_folder_path = self.materials_folder_path / "字幕视频"
|
|
if subtitle_video_folder_path.exists() and subtitle_video_folder_path.is_dir():
|
|
materials["subtitle_video_path"] = [
|
|
subtitle_video_path
|
|
for subtitle_video_path in subtitle_video_folder_path.rglob("*.mov")
|
|
]
|
|
else:
|
|
materials["subtitle_video_path"] = []
|
|
|
|
# 构建背景视频文件夹路径
|
|
background_video_folder_path = self.materials_folder_path / "背景视频"
|
|
if (
|
|
background_video_folder_path.exists()
|
|
and background_video_folder_path.is_dir()
|
|
):
|
|
materials["background_video_path"] = [
|
|
background_video_path
|
|
for background_video_path in background_video_folder_path.rglob("*.mp4")
|
|
]
|
|
else:
|
|
materials["background_video_path"] = []
|
|
|
|
# 构建中贴视频文件夹路径
|
|
mid_roll_video_folder_path = self.materials_folder_path / "中贴视频"
|
|
if mid_roll_video_folder_path.exists() and mid_roll_video_folder_path.is_dir():
|
|
materials["mid_roll_video_path"] = [
|
|
mid_roll_video_path
|
|
for mid_roll_video_path in mid_roll_video_folder_path.rglob("*.mp4")
|
|
]
|
|
else:
|
|
materials["mid_roll_video_path"] = []
|
|
|
|
# 构建后贴视频文件夹路径
|
|
post_roll_video_folder_path = self.materials_folder_path / "后贴视频"
|
|
if (
|
|
post_roll_video_folder_path.exists()
|
|
and post_roll_video_folder_path.is_dir()
|
|
):
|
|
materials["post_roll_video_path"] = [
|
|
post_video_path
|
|
for post_video_path in post_roll_video_folder_path.rglob("*.mp4")
|
|
]
|
|
else:
|
|
materials["post_roll_video_path"] = []
|
|
|
|
# 构建背景音频文件夹路径
|
|
background_audio_folder_path = self.materials_folder_path / "背景音频"
|
|
if (
|
|
background_audio_folder_path.exists()
|
|
and background_audio_folder_path.is_dir()
|
|
):
|
|
materials["background_audio_path"] = [
|
|
background_audio_path
|
|
for background_audio_path in background_audio_folder_path.rglob("*.mp3")
|
|
]
|
|
else:
|
|
materials["background_audio_path"] = []
|
|
|
|
# 构建标识图片路径
|
|
logo_image_path = self.materials_folder_path / "标识图片.png"
|
|
if logo_image_path.exists() and logo_image_path.is_file():
|
|
materials["logo_image_path"] = [logo_image_path] # 有且只有一张标识图片
|
|
else:
|
|
materials["logo_image_path"] = []
|
|
|
|
# 构建标识视频文件夹路径
|
|
logo_video_folder_path = self.materials_folder_path / "标识视频"
|
|
if logo_video_folder_path.exists() and logo_video_folder_path.is_dir():
|
|
materials["logo_video_path"] = [
|
|
file_path for file_path in logo_video_folder_path.rglob("*.mov")
|
|
]
|
|
else:
|
|
materials["logo_video_path"] = []
|
|
|
|
# 构建声明文本路径
|
|
statement_text_path = self.materials_folder_path / "声明文本.txt"
|
|
if statement_text_path.exists() and statement_text_path.is_file():
|
|
with open(statement_text_path, "r", encoding="utf-8") as file:
|
|
# 声明文本列表
|
|
statement_texts = file.readlines()
|
|
if not statement_texts:
|
|
raise RuntimeError("声明文本为空")
|
|
materials["statement_texts"] = statement_texts
|
|
else:
|
|
materials["statement_texts"] = []
|
|
|
|
# 构建声明图片路径
|
|
statement_image_path = self.materials_folder_path / "声明图片.png"
|
|
if statement_image_path.exists() and statement_image_path.is_file():
|
|
materials["statement_image_path"] = [
|
|
statement_image_path
|
|
] # 有且只有一张声明图片
|
|
else:
|
|
materials["statement_image_path"] = []
|
|
|
|
# 构建声明视频文件夹路径
|
|
statement_video_folder_path = self.materials_folder_path / "声明视频"
|
|
if (
|
|
statement_video_folder_path.exists()
|
|
and statement_video_folder_path.is_dir()
|
|
):
|
|
materials["statement_video_path"] = [
|
|
statement_video_path
|
|
for statement_video_path in statement_video_folder_path.rglob("*.mov")
|
|
]
|
|
else:
|
|
materials["statement_video_path"] = []
|
|
|
|
return materials
|
|
|
|
def _init_configurations(
|
|
self,
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
初始化节点配置
|
|
:return: 节点配置
|
|
"""
|
|
# 已配置工作流
|
|
workflows = {
|
|
"默认工作流": [
|
|
"generate_subtitle",
|
|
"add_background_video",
|
|
"add_statement",
|
|
"add_sticker",
|
|
"add_sticker_arrow",
|
|
], # 默认工作流,先根据字幕文本合成字幕音频并生成字幕,再叠加背景视频、声明视频、非箭头贴纸视频和箭头贴纸视频
|
|
"淘宝闪购": [
|
|
"add_background_video",
|
|
"add_background_audio",
|
|
"add_subtitle_video",
|
|
"add_statement_video",
|
|
], # 淘宝闪购,先根据字幕视频获取其持续时长并作为成品持续时长,再叠加背景视频、背景音频和生成视频
|
|
"淘宝闪购_达人": [
|
|
"add_background_video",
|
|
"add_background_audio",
|
|
"add_subtitle_video",
|
|
"add_mid_roll_video",
|
|
"add_post_roll_video",
|
|
"add_logo_video",
|
|
"add_statement_video",
|
|
], # 先分别添加背景视频(其中背景视频叠加背景音频和字幕视频,支持设置截取背景视频持续时长)、中贴视频和后贴视频,最后叠加标识视频和声明视频
|
|
"存量": [
|
|
"add_background_video",
|
|
"add_statement_image",
|
|
], # 就背景视频添加声明图片
|
|
}
|
|
# 默认以素材文件夹名称为工作流名称
|
|
workflow_name = self.materials_folder_path.stem
|
|
# 工作流配置
|
|
workflow = workflows.get(workflow_name, None)
|
|
if not workflow:
|
|
raise RuntimeError(f"该工作流 {workflow_name} 未配置")
|
|
|
|
# 所有工作配置
|
|
configurations = {
|
|
"generate_subtitle": {
|
|
"texts": self.materials["subtitle_texts"],
|
|
"style": [
|
|
{"size": 10.0},
|
|
], # 字体样式
|
|
"effect": [
|
|
{"effect_id": "7127561998556089631"},
|
|
{"effect_id": "7166467215410187552"},
|
|
{"effect_id": "6896138122774498567"},
|
|
{"effect_id": "7166469374507765031"},
|
|
{"effect_id": "6896137924853763336"},
|
|
], # 花字设置
|
|
}, # 生成字幕工作配置
|
|
"add_subtitle_video": {
|
|
"material_path": self.materials["subtitle_video_path"],
|
|
"volume": [1.0], # 播放音量
|
|
"clip_settings": [
|
|
None,
|
|
], # 图像调节设置
|
|
}, # 添加字幕视频工作配置
|
|
"add_background_video": {
|
|
"material_path": self.materials["background_video_path"],
|
|
"volume": [1.0], # 播放音量
|
|
"clip_settings": [
|
|
{
|
|
"scale_x": 1.0,
|
|
"scale_y": 1.0,
|
|
},
|
|
], # 图像调节设置
|
|
}, # 添加背景视频工作配置
|
|
"add_mid_roll_video": {
|
|
"material_path": self.materials["mid_roll_video_path"],
|
|
"volume": [1.0], # 播放音量
|
|
"clip_settings": [
|
|
{
|
|
"scale_x": 1.0,
|
|
"scale_y": 1.0,
|
|
},
|
|
], # 图像调节设置
|
|
}, # 添加中贴视频工作配置
|
|
"add_post_roll_video": {
|
|
"material_path": self.materials["post_roll_video_path"],
|
|
"volume": [1.0], # 播放音量
|
|
"clip_settings": [
|
|
{
|
|
"scale_x": 1.0,
|
|
"scale_y": 1.0,
|
|
},
|
|
], # 图像调节设置
|
|
}, # 添加后贴视频工作配置
|
|
"add_background_audio": {
|
|
"material_path": self.materials["background_audio_path"],
|
|
"volume": [0.6], # 播放音量
|
|
}, # 添加背景音频工作配置
|
|
"add_logo_image": {
|
|
"material_path": self.materials["logo_image_path"],
|
|
"clip_settings": [
|
|
{
|
|
"scale_x": 0.2,
|
|
"scale_y": 0.2,
|
|
"transform_x": -0.78,
|
|
"transform_y": 0.82,
|
|
},
|
|
],
|
|
}, # 添加标识工作配置
|
|
"add_logo_video": {
|
|
"material_path": self.materials["logo_video_path"],
|
|
"volume": [1.0], # 播放音量
|
|
"clip_settings": [
|
|
None,
|
|
], # 图像调节设置
|
|
}, # 添加标识视频工作配置
|
|
"add_statement": {
|
|
"text": self.materials["statement_texts"],
|
|
"style": [
|
|
{"size": 6.0, "align": 1, "vertical": True},
|
|
], # 文本样式
|
|
"border": [
|
|
{"width": 35.0},
|
|
{"width": 40.0},
|
|
{"width": 45.0},
|
|
], # 描边宽度
|
|
"clip_settings": [
|
|
{
|
|
"transform_x": -0.82,
|
|
},
|
|
], # 图像调节设置
|
|
}, # 添加声明工作配置
|
|
"add_statement_image": {
|
|
"material_path": self.materials["statement_image_path"],
|
|
"clip_settings": [
|
|
{
|
|
"scale_x": 0.4,
|
|
"scale_y": 0.4,
|
|
"transform_x": 0.00,
|
|
"transform_y": -0.85,
|
|
},
|
|
],
|
|
}, # 添加声明图片工作配置
|
|
"add_statement_video": {
|
|
"material_path": self.materials["statement_video_path"],
|
|
"volume": [1.0], # 播放音量
|
|
"clip_settings": [
|
|
None,
|
|
], # 图像调节设置
|
|
}, # 添加声明视频工作配置
|
|
"add_sticker": {
|
|
"resource_id": [
|
|
"7110124379568098568",
|
|
"7019687632804334861",
|
|
"6895933678262750478",
|
|
"7010558788675652900",
|
|
"7026858083393588487",
|
|
],
|
|
"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,
|
|
},
|
|
], # 图像调节设置
|
|
}, # 添加非箭头贴纸工作配置
|
|
"add_sticker_arrow": {
|
|
"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,
|
|
},
|
|
], # 图像调节设置
|
|
}, # 添加箭头贴纸工作配置
|
|
}
|
|
return {node_name: configurations[node_name] for node_name in workflow}
|
|
|
|
def batch_create(
|
|
self,
|
|
draft_counts: int,
|
|
video_width: int = 1080,
|
|
video_height: int = 1920,
|
|
video_fps: int = 30,
|
|
capture_duration: Optional[int] = None,
|
|
) -> None:
|
|
"""
|
|
批量创建草稿
|
|
:param draft_counts: 草稿数
|
|
:param video_width: 视频宽度(单位为像素),默认为 1080
|
|
:param video_height: 视频高度(单位为像素),默认为 1920
|
|
:param video_fps: 视频帧率(单位为帧/秒),默认为 30
|
|
:param capture_duration: 截取背景视频时长(单位为秒),默认为 None
|
|
:return: 无
|
|
"""
|
|
draft_index = 1 # 草稿索引
|
|
while True:
|
|
# 获取工作配置
|
|
configurations = self._get_configurations(capture_duration=capture_duration)
|
|
|
|
# 若包含添加字幕视频则使用字幕视频时长作为视频时长,否则使用背景视频时长作为视频时长
|
|
if "add_subtitle_video" in configurations:
|
|
video_duration = min(
|
|
VideoMaterial(
|
|
path=configurations["add_background_video"][
|
|
"material_path"
|
|
].as_posix()
|
|
).duration,
|
|
VideoMaterial(
|
|
path=configurations["add_subtitle_video"][
|
|
"material_path"
|
|
].as_posix()
|
|
).duration,
|
|
)
|
|
else:
|
|
video_duration = video_duration = VideoMaterial(
|
|
path=configurations["add_background_video"][
|
|
"material_path"
|
|
].as_posix()
|
|
).duration
|
|
|
|
# 转换为微秒单位
|
|
if capture_duration:
|
|
video_duration = capture_duration * 1_000_000
|
|
|
|
# 添加中贴视频持续时长
|
|
if "add_mid_roll_video" in configurations:
|
|
video_duration += VideoMaterial(
|
|
path=configurations["add_mid_roll_video"][
|
|
"material_path"
|
|
].as_posix()
|
|
).duration
|
|
# 添加后贴视频持续时长
|
|
if "add_post_roll_video" in configurations:
|
|
video_duration += VideoMaterial(
|
|
path=configurations["add_post_roll_video"][
|
|
"material_path"
|
|
].as_posix()
|
|
).duration
|
|
|
|
# 生成剪映草稿名称
|
|
draft_name = self._generate_draft_name(
|
|
configurations=configurations,
|
|
)
|
|
# 若草稿名称已缓存则跳过
|
|
if self.caches.query(draft_name=draft_name):
|
|
continue
|
|
|
|
print(f"正在创建草稿 {draft_name}({draft_index}/{draft_counts})...")
|
|
|
|
# 创建剪映草稿
|
|
draft = Drafts(
|
|
materials_folder_path=self.materials_folder_path,
|
|
drafts_folder=self.drafts_folder,
|
|
draft_name=draft_name,
|
|
video_width=video_width,
|
|
video_height=video_height,
|
|
video_fps=video_fps,
|
|
video_duration=video_duration,
|
|
)
|
|
|
|
for node_name in configurations:
|
|
match node_name:
|
|
# 添加字幕
|
|
case "generate_subtitle":
|
|
print("-> 正在根据字幕文本合成字幕音频并生成字幕...", end="")
|
|
draft.generate_subtitle(**configurations[node_name])
|
|
print("已完成")
|
|
# 添加背景视频
|
|
case "add_background_video":
|
|
print("-> 正在添加背景视频...", end="")
|
|
draft.add_video_segment(**configurations[node_name])
|
|
print("已完成")
|
|
# 添加背景音频
|
|
case "add_background_audio":
|
|
print("-> 正在添加背景音频...", end="")
|
|
draft.add_audio_segment(**configurations[node_name])
|
|
print("已完成")
|
|
# 添加中贴视频
|
|
case "add_mid_roll_video":
|
|
print("-> 正在添加中贴视频...", end="")
|
|
draft.add_video_segment(**configurations[node_name])
|
|
print("已完成")
|
|
# 添加后贴视频
|
|
case "add_post_roll_video":
|
|
print("-> 正在添加后贴视频...", end="")
|
|
draft.add_video_segment(**configurations[node_name])
|
|
print("已完成")
|
|
# 添加字幕视频
|
|
case "add_subtitle_video":
|
|
print("-> 正在添加字幕视频...", end="")
|
|
draft.add_video_segment(**configurations[node_name])
|
|
print("已完成")
|
|
# 添加标识图片
|
|
case "add_logo_image":
|
|
print("-> 正在添加标识图片...", end="")
|
|
draft.add_video_segment(**configurations[node_name])
|
|
print("已完成")
|
|
# 添加标识视频
|
|
case "add_logo_video":
|
|
print("-> 正在添加标识视频...", end="")
|
|
draft.add_video_segment(**configurations[node_name])
|
|
print("已完成")
|
|
# 添加声明文本
|
|
case "add_statement":
|
|
print("-> 正在添加声明文本...", end="")
|
|
draft.add_text_segment(**configurations[node_name])
|
|
print("已完成")
|
|
# 添加声明图片
|
|
case "add_statement_image":
|
|
print("-> 正在添加声明图片...", end="")
|
|
draft.add_video_segment(**configurations[node_name])
|
|
print("已完成")
|
|
# 添加声明视频
|
|
case "add_statement_video":
|
|
print("-> 正在添加声明视频...", end="")
|
|
draft.add_video_segment(**configurations[node_name])
|
|
print("已完成")
|
|
# 添加贴纸
|
|
case _ if node_name.startswith("add_sticker"):
|
|
print("-> 正在添加贴纸...", end="")
|
|
draft.add_sticker(**configurations[node_name])
|
|
print("已完成")
|
|
# 保存草稿
|
|
case "save":
|
|
print("-> 正在保存草稿...", end="")
|
|
draft.save()
|
|
print("已完成")
|
|
|
|
# 缓存草稿名称和所有节点配置
|
|
self.caches.update(
|
|
draft_name=draft_name,
|
|
configurations=configurations,
|
|
)
|
|
|
|
print("已完成")
|
|
print()
|
|
|
|
draft_index += 1
|
|
if draft_index > draft_counts:
|
|
break
|
|
|
|
def _get_configurations(
|
|
self,
|
|
capture_duration: Optional[int] = None,
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
获取工作配置
|
|
:param capture_duration: 截取背景视频时长(单位为秒),默认为 None
|
|
:return: 工作配置
|
|
"""
|
|
configurations = {}
|
|
for node_name in self.configurations:
|
|
# 根据节点名称获取节点配置
|
|
configurations.update(
|
|
{
|
|
node_name: {
|
|
key: random.choice(value)
|
|
for key, value in self.configurations[node_name].items()
|
|
}
|
|
}
|
|
)
|
|
# 若非生成字幕则在工作流配置添加轨道名称
|
|
if node_name != "generate_subtitle":
|
|
configurations[node_name]["track_name"] = (
|
|
matched.group("track_name")
|
|
if (
|
|
matched := re.match(
|
|
pattern=r"^.+?_(?P<track_name>.+)$",
|
|
string=node_name,
|
|
)
|
|
)
|
|
else node_name
|
|
)
|
|
|
|
background_video_duration = VideoMaterial(
|
|
path=configurations["add_background_video"]["material_path"].as_posix()
|
|
).duration
|
|
if capture_duration:
|
|
background_video_duration = capture_duration * 1_000_000
|
|
|
|
# 若包含添加字幕视频则在添加背景视频时其播放音量设置为 0
|
|
if "add_subtitle_video" in configurations:
|
|
configurations["add_background_video"]["volume"] = 0.0
|
|
|
|
# 中贴视频特殊处理:背景视频、背景音频和字幕视频在轨道上的范围为 (0, 背景视频时长),中贴视频在轨道上的范围为 (背景视频时长, 中贴视频时长)
|
|
if "add_mid_roll_video" in configurations:
|
|
configurations.update(
|
|
{
|
|
node: {
|
|
**configurations[node],
|
|
"target_timerange": (
|
|
0,
|
|
background_video_duration,
|
|
),
|
|
}
|
|
for node in [
|
|
"add_background_video",
|
|
"add_background_audio",
|
|
"add_subtitle_video",
|
|
]
|
|
if node in configurations
|
|
}
|
|
)
|
|
configurations.update(
|
|
{
|
|
"add_mid_roll_video": {
|
|
**configurations["add_mid_roll_video"],
|
|
"target_timerange": (
|
|
background_video_duration,
|
|
VideoMaterial(
|
|
path=configurations["add_mid_roll_video"][
|
|
"material_path"
|
|
].as_posix()
|
|
).duration,
|
|
),
|
|
}
|
|
}
|
|
)
|
|
|
|
# 后贴视频特殊处理:后贴视频在轨道上的范围为 (背景视频时长 + 中贴视频时长, 后贴视频时长)
|
|
if "add_post_roll_video" in configurations:
|
|
configurations.update(
|
|
{
|
|
"add_post_roll_video": {
|
|
**configurations["add_post_roll_video"],
|
|
"target_timerange": (
|
|
(
|
|
background_video_duration
|
|
+ VideoMaterial(
|
|
path=configurations["add_mid_roll_video"][
|
|
"material_path"
|
|
].as_posix()
|
|
).duration
|
|
),
|
|
VideoMaterial(
|
|
path=configurations["add_post_roll_video"][
|
|
"material_path"
|
|
].as_posix()
|
|
).duration,
|
|
),
|
|
}
|
|
}
|
|
)
|
|
# 添加保存节点
|
|
configurations.update(
|
|
{
|
|
"save": {},
|
|
}
|
|
)
|
|
return configurations
|
|
|
|
def _generate_draft_name(
|
|
self,
|
|
configurations: Dict[str, Any],
|
|
) -> str:
|
|
"""
|
|
生成剪映草稿名称
|
|
:param configurations: 指定工作流所有节点配置
|
|
:return: 草稿名称
|
|
"""
|
|
return (
|
|
hashlib.md5(
|
|
json.dumps(
|
|
obj=configurations,
|
|
cls=self.caches.JSONEncoder,
|
|
sort_keys=True,
|
|
ensure_ascii=False,
|
|
).encode("utf-8")
|
|
) # 将工作流配置序列化
|
|
.hexdigest()
|
|
.upper() # MD5哈希值的大写十六进制作为草稿名称
|
|
)
|