309 lines
11 KiB
Python
309 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
剪映草稿管理器模块
|
|
"""
|
|
|
|
# 列举导入模块
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
from random import choice
|
|
import shutil
|
|
import sys
|
|
|
|
from pyJianYingDraft import DraftFolder, VideoMaterial
|
|
|
|
from caches import Caches
|
|
from drafts import Drafts
|
|
|
|
sys.path.append(Path(__file__).parent.parent.as_posix())
|
|
|
|
|
|
DRAFT_GENERATE_SCHEMES = {
|
|
"存量_纯图_竖版": [
|
|
[
|
|
(
|
|
"按钮",
|
|
{
|
|
"clip_settings": [
|
|
{
|
|
"scale_x": 1.0,
|
|
"scale_y": 1.0,
|
|
},
|
|
], # 剪辑设置
|
|
},
|
|
)
|
|
],
|
|
[
|
|
(
|
|
"底纹",
|
|
{
|
|
"clip_settings": [
|
|
{
|
|
"scale_x": 1.0,
|
|
"scale_y": 1.0,
|
|
},
|
|
], # 剪辑设置
|
|
},
|
|
)
|
|
],
|
|
[
|
|
(
|
|
"箭头",
|
|
{
|
|
"clip_settings": [
|
|
{
|
|
"scale_x": 0.35,
|
|
"scale_y": 0.35,
|
|
"transform_x": 0,
|
|
"transform_y": -0.46,
|
|
},
|
|
], # 剪辑设置
|
|
},
|
|
)
|
|
],
|
|
[
|
|
(
|
|
"提示语",
|
|
{
|
|
"volume": [1.0], # 播放音量
|
|
"clip_settings": [
|
|
{
|
|
"scale_x": 1.0,
|
|
"scale_y": 1.0,
|
|
"transform_x": 0,
|
|
"transform_y": 0.05,
|
|
},
|
|
], # 剪辑设置
|
|
},
|
|
)
|
|
],
|
|
],
|
|
"存量_纯图_横版": [
|
|
[
|
|
(
|
|
"按钮",
|
|
{
|
|
"clip_settings": [
|
|
{
|
|
"rotation": 90,
|
|
},
|
|
], # 剪辑设置
|
|
},
|
|
)
|
|
],
|
|
[
|
|
(
|
|
"底纹",
|
|
{
|
|
"clip_settings": [
|
|
{
|
|
"rotation": 90,
|
|
},
|
|
], # 剪辑设置
|
|
},
|
|
)
|
|
],
|
|
],
|
|
}
|
|
|
|
|
|
class DraftsManager:
|
|
"""
|
|
剪映草稿管理器
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
project_name: str,
|
|
drafts_folder_path: str = r"E:\JianYingPro Drafts",
|
|
):
|
|
"""
|
|
初始化剪映草稿管理器
|
|
:param project_name: 项目名称
|
|
:param drafts_folder_path: 剪映草稿文件夹路径,默认为 E:\\JianYingPro Drafts
|
|
"""
|
|
|
|
# 项目名称
|
|
self.project_name = project_name
|
|
# 草稿生成方案
|
|
self.draft_generate_scheme: list[list[tuple[str, dict]]] = (
|
|
DRAFT_GENERATE_SCHEMES.get(self.project_name, [])
|
|
)
|
|
if not self.draft_generate_scheme:
|
|
raise RuntimeError(f"未就 {self.project_name} 配置草稿生成方案")
|
|
|
|
# 构建此项目素材仓库路径
|
|
self.material_vault_path = Path(rf"E:\jianying\materials\{project_name}")
|
|
# 检查此项目素材仓库是否存在,若不存在则抛出异常
|
|
if not self.material_vault_path.exists():
|
|
raise RuntimeError("素材仓库不存在")
|
|
|
|
self.materials: dict[str, list[Path]] = (
|
|
{}
|
|
) # 键为素材文件夹名称,值为素材路径列表
|
|
# 初始化项目素材
|
|
self.init_materials()
|
|
|
|
# 构建草稿文件夹路径
|
|
self.drafts_folder_path = Path(drafts_folder_path)
|
|
# 检查草稿文件夹是否存在,若不存在则抛出异常
|
|
if not self.drafts_folder_path.exists():
|
|
raise RuntimeError("草稿文件夹不存在")
|
|
|
|
# 初始化草稿文件夹管理器
|
|
self.drafts_folder = DraftFolder(self.drafts_folder_path.as_posix())
|
|
|
|
# 实例化缓存(用于检查草稿是否存在缓存,若存在则重新生成)
|
|
self.caches = Caches()
|
|
|
|
def init_materials(self) -> None:
|
|
"""
|
|
初始化项目素材
|
|
:return: None
|
|
"""
|
|
# 草稿生成方案第一层为轨道元信息,第二层为片段元信息。其中,轨道元信息包括若干片段元信息;片段元信息包括素材文件夹名称与配置元信息
|
|
for track_metadata in self.draft_generate_scheme:
|
|
for segment_metadata in track_metadata:
|
|
material_folder_name, _ = segment_metadata
|
|
# 构建素材文件夹路径
|
|
material_folder_path = self.material_vault_path / material_folder_name
|
|
# 若素材文件夹不存在则抛出异常
|
|
if not material_folder_path.exists():
|
|
raise RuntimeError(f"素材文件夹 {material_folder_name} 不存在")
|
|
|
|
# 遍历素材文件夹内所有素材路径
|
|
self.materials[material_folder_name] = [
|
|
material_path
|
|
for material_path in material_folder_path.glob(
|
|
"*"
|
|
) # 不递归遍历素材文件夹内文件夹
|
|
if material_path.is_file()
|
|
and not material_path.is_symlink()
|
|
and material_path.suffix.lower() in {".mp4", ".mov", ".png"}
|
|
] # 暂仅支持素材路径后缀如上
|
|
|
|
for material_folder_name in self.materials.keys():
|
|
# 统计素材文件夹内素材路径后缀数
|
|
suffix_counts = len(
|
|
{p.suffix.lower() for p in self.materials[material_folder_name]}
|
|
)
|
|
if suffix_counts > 1:
|
|
raise RuntimeError(
|
|
f"素材文件夹 {material_folder_name} 内存在多种素材路径后缀"
|
|
)
|
|
|
|
def batch_create(
|
|
self,
|
|
draft_counts: int,
|
|
video_width: int = 1080,
|
|
video_height: int = 1920,
|
|
video_fps: int = 30,
|
|
) -> None:
|
|
"""
|
|
批量创建草稿
|
|
:param draft_counts: 草稿数
|
|
:param video_width: 视频宽度(单位为像素),默认为 1080
|
|
:param video_height: 视频高度(单位为像素),默认为 1920
|
|
:param video_fps: 视频帧率(单位为帧/秒),默认为 30
|
|
:return: 无
|
|
"""
|
|
index = 1 # 草稿索引
|
|
while True:
|
|
draft_generate_scheme: list[dict] = [] # 草稿生成方案
|
|
video_duration = 0 # 视频时长
|
|
for track_idx, track_metadata in enumerate(self.draft_generate_scheme):
|
|
# 生成轨道名称
|
|
track_name = f"track{track_idx:02d}"
|
|
for segment_metadata in track_metadata:
|
|
# 获取素材文件夹名称、配置元信息
|
|
material_folder_name, config_metadata = segment_metadata
|
|
# 随机选择素材路径
|
|
material_path = choice(self.materials[material_folder_name])
|
|
# 初始化片段生成方案
|
|
segment_generate_scheme = {
|
|
"track_name": track_name,
|
|
"material_path": material_path,
|
|
**{k: choice(v) for k, v in config_metadata.items()},
|
|
}
|
|
|
|
# 素材路径后缀
|
|
material_path_suffix = material_path.suffix.lower()
|
|
# 若为第一轨道则将所有素材的时长作为视频时长,其他轨道则将第一片段的目标时长设置为视频时长
|
|
if track_idx == 0:
|
|
if material_path_suffix not in [".mp4", ".mov", ".png"]:
|
|
raise RuntimeError(
|
|
f"第一轨道中素材 {material_path.name} 的类型必须为 mp4, mov, png"
|
|
)
|
|
|
|
video_duration += VideoMaterial(
|
|
path=material_path.as_posix()
|
|
).duration
|
|
|
|
else:
|
|
segment_generate_scheme["target_timerange"] = (
|
|
0,
|
|
video_duration,
|
|
) # 暂仅考虑一个轨道包括一个片段
|
|
|
|
# 将片段生成方案添加至草稿生成方案
|
|
draft_generate_scheme.append(segment_generate_scheme)
|
|
|
|
# 生成剪映草稿名称
|
|
draft_name = (
|
|
hashlib.md5(
|
|
json.dumps(
|
|
obj=draft_generate_scheme,
|
|
cls=self.caches.JSONEncoder,
|
|
sort_keys=True,
|
|
ensure_ascii=False,
|
|
).encode("utf-8")
|
|
)
|
|
.hexdigest()
|
|
.upper()
|
|
) # MD5哈希值的大写十六进制作为草稿名称
|
|
# 若存在相同草稿名称的缓存则跳过
|
|
if self.caches.has_same_draft_name(draft_name):
|
|
continue
|
|
|
|
print(f"正在创建草稿 {draft_name}({index}/{draft_counts})...")
|
|
|
|
# 创建剪映草稿
|
|
draft = Drafts(
|
|
materials_vault_path=self.material_vault_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 segment_generate_scheme in draft_generate_scheme:
|
|
# 获取素材路径后缀
|
|
material_path_suffix = segment_generate_scheme[
|
|
"material_path"
|
|
].suffix.lower()
|
|
# 根据素材路径后缀匹配具体操作
|
|
match material_path_suffix:
|
|
case ".mp4" | ".mov" | ".png":
|
|
print("-> 正在添加视频片段...", end="")
|
|
draft.add_video_segment(**segment_generate_scheme)
|
|
print("已完成")
|
|
|
|
print("-> 正在保存草稿...", end="")
|
|
draft.save()
|
|
print("已完成")
|
|
|
|
# 缓存草稿名称和所有片段配置
|
|
self.caches.update(
|
|
draft_name,
|
|
draft_generate_scheme,
|
|
)
|
|
|
|
print("已完成")
|
|
print()
|
|
|
|
index += 1
|
|
if index > draft_counts:
|
|
break
|