This commit is contained in:
liubiren 2026-03-30 20:37:08 +08:00
parent a6a7cdc85c
commit af44e24d60
4 changed files with 187 additions and 102 deletions

Binary file not shown.

View File

@ -221,7 +221,7 @@ class Drafts:
self,
track_name: str,
material_path: Path,
target_timerange: Optional[Tuple[int, Optional[int]]] = None,
target_timerange: Optional[Tuple[int, int]] = None,
source_timerange: Optional[Tuple[int, int]] = None,
speed: float = 1.0,
volume: float = 1.0,
@ -253,14 +253,11 @@ class Drafts:
video_material = VideoMaterial(path=material_path.as_posix())
# 视频素材的持续时长
video_material_duration = video_material.duration
# 目标持续时间
target_duration = (
tim(target_duration)
if (target_timerange and (target_duration := target_timerange[1]))
else (
video_material_duration if target_timerange else self.video_duration
)
) # 若视频或图片素材在轨道上的范围为空则将视频素材持续时长作为目标持续时长,若视频或图片素材在轨道上的范围中持续时长为空则将视频素材持续时长作为目标持续时长
if target_timerange:
target_start, target_duration = target_timerange
# 若视频或图片素材在轨道上的范围为空则将视频素材持续时长作为目标持续时长
else:
target_start, target_duration = 0, self.video_duration
# 添加视频轨道
self.draft.add_track(
@ -274,11 +271,12 @@ class Drafts:
video_segment = VideoSegment(
material=video_material,
target_timerange=trange(
start=cumulative_duration
+ (target_timerange[0] if target_timerange else 0),
duration=min(
(target_duration - cumulative_duration),
start=target_start + cumulative_duration,
duration=(
current_duration := min(
target_duration - cumulative_duration,
video_material_duration,
)
),
),
source_timerange=(
@ -308,7 +306,8 @@ class Drafts:
# 向指定轨道添加视频或图片片段
self.draft.add_segment(segment=video_segment, track_name=track_name)
cumulative_duration += video_material_duration
# 更新累计持续时长
cumulative_duration += current_duration
except Exception as exception:
raise RuntimeError(str(exception)) from exception

View File

@ -11,7 +11,7 @@ import random
import re
import shutil
import sys
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional
from pyJianYingDraft import DraftFolder, VideoMaterial
@ -91,12 +91,12 @@ class JianYingManager:
# 构建字幕视频文件夹路径
subtitle_video_folder_path = self.materials_folder_path / "字幕视频"
if subtitle_video_folder_path.exists() and subtitle_video_folder_path.is_dir():
materials["subtitle_video_paths"] = [
materials["subtitle_video_path"] = [
subtitle_video_path
for subtitle_video_path in subtitle_video_folder_path.rglob("*.mov")
]
else:
materials["subtitle_video_paths"] = []
materials["subtitle_video_path"] = []
# 构建背景视频文件夹路径
background_video_folder_path = self.materials_folder_path / "背景视频"
@ -104,22 +104,35 @@ class JianYingManager:
background_video_folder_path.exists()
and background_video_folder_path.is_dir()
):
materials["background_video_paths"] = [
materials["background_video_path"] = [
background_video_path
for background_video_path in background_video_folder_path.rglob("*.mp4")
]
else:
materials["background_video_paths"] = []
materials["background_video_path"] = []
# 构建达人视频文件夹路径
kol_video_folder_path = self.materials_folder_path / "达人视频"
if kol_video_folder_path.exists() and kol_video_folder_path.is_dir():
materials["kol_video_paths"] = [
kol_video_path
for kol_video_path in kol_video_folder_path.rglob("*.mp4")
# 构建中贴视频文件夹路径
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["kol_video_paths"] = []
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 / "背景音频"
@ -127,12 +140,12 @@ class JianYingManager:
background_audio_folder_path.exists()
and background_audio_folder_path.is_dir()
):
materials["background_audio_paths"] = [
materials["background_audio_path"] = [
background_audio_path
for background_audio_path in background_audio_folder_path.rglob("*.mp3")
]
else:
materials["background_audio_paths"] = []
materials["background_audio_path"] = []
# 构建标识图片路径
logo_image_path = self.materials_folder_path / "标识图片.png"
@ -194,25 +207,26 @@ class JianYingManager:
"add_sticker_arrow",
], # 默认工作流,先根据字幕文本合成字幕音频并生成字幕,再叠加背景视频、声明视频、非箭头贴纸视频和箭头贴纸视频
"淘宝闪购": [
"add_subtitle_video",
"add_background_video",
"add_background_audio",
"add_subtitle_video",
"add_statement_video",
], # 淘宝闪购,先根据字幕视频获取其持续时长并作为成品持续时长,再叠加背景视频、背景音频和生成视频
"淘宝闪购_达人": [
"add_background_video",
"add_background_audio",
"add_subtitle_video",
"add_kol_video",
"add_mid_roll_video",
"add_post_roll_video",
"add_logo_video",
"add_statement_video",
], # 淘宝闪购_达人第一段先根据字幕视频获取前5秒作为持续时长再叠加背景视频、背景音频第二段背景视频达人拼接第一段和第二段再叠加声明视频
], # 淘宝闪购_达人先就背景视频、背景音频、字幕视频截取前 5 秒,再添加中贴视频、后贴视频、标识视频、声明视频和生成视频
}
# 默认以素材文件夹名称为工作流名称
workflow_name = self.materials_folder_path.stem
# 工作流
workflow = workflows.get(workflow_name)
# 工作流配置
workflow = workflows.get(workflow_name, None)
if not workflow:
raise RuntimeError(f"未配置该工作流 {workflow_name}")
raise RuntimeError(f"该工作流 {workflow_name} 未配置")
# 节点配置模板
configurations = {
@ -230,14 +244,14 @@ class JianYingManager:
], # 花字设置
}, # 生成字幕工作配置
"add_subtitle_video": {
"material_path": self.materials["subtitle_video_paths"],
"material_path": self.materials["subtitle_video_path"],
"volume": [1.0], # 播放音量
"clip_settings": [
None,
], # 图像调节设置
}, # 添加字幕视频工作配置
"add_background_video": {
"material_path": self.materials["background_video_paths"],
"material_path": self.materials["background_video_path"],
"volume": [1.0], # 播放音量
"clip_settings": [
{
@ -246,8 +260,8 @@ class JianYingManager:
},
], # 图像调节设置
}, # 添加背景视频工作配置
"add_kol_video": {
"material_path": self.materials["kol_video_paths"],
"add_mid_roll_video": {
"material_path": self.materials["mid_roll_video_path"],
"volume": [1.0], # 播放音量
"clip_settings": [
{
@ -255,9 +269,19 @@ class JianYingManager:
"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_paths"],
"material_path": self.materials["background_audio_path"],
"volume": [0.6], # 播放音量
}, # 添加背景音频工作配置
"add_logo_image": {
@ -347,41 +371,6 @@ class JianYingManager:
], # 图像调节设置
}, # 添加箭头贴纸工作配置
}
# 达人视频特殊处理:字幕视频、背景视频和背景音频素材持续时长为 5秒叠加达人视频
if workflow_name == "淘宝闪购_达人":
configurations.update(
{
node: {
**configurations[node],
"target_timerange": [
(0, 5_000_000),
],
}
for node in [
"add_subtitle_video",
"add_background_video",
"add_background_audio",
]
}
)
configurations.update(
{
node: {
**configurations[node],
"target_timerange": [
(5_000_000, None),
],
}
for node in [
"add_kol_video",
]
}
)
# 若包含添加背景音频节点则在添加背景视频时其播放音量设置为 0
if "add_background_audio" in workflow:
configurations["add_background_video"]["volume"] = [0.0]
return {node_name: configurations[node_name] for node_name in workflow}
def batch_create(
@ -390,6 +379,7 @@ class JianYingManager:
video_width: int = 1080,
video_height: int = 1920,
video_fps: int = 30,
duration_capture: Optional[int] = None,
) -> None:
"""
批量创建草稿
@ -397,24 +387,44 @@ class JianYingManager:
:param video_width: 视频宽度单位为像素默认为 1080
:param video_height: 视频高度单位为像素默认为 1920
:param video_fps: 视频帧率单位为帧/默认为 30
:param duration_capture: 截取背景视频时长单位为秒默认为 None
:return:
"""
draft_index = 1 # 草稿索引
while True:
# 获取节点配置
configurations = self._get_configurations()
configurations = self._get_configurations(duration_capture=duration_capture)
# 若包含添加字幕视频则使用字幕视频时长作为视频时长,否则使用背景视频时长作为视频时长
if "add_subtitle_video" in configurations:
video_duration = VideoMaterial(
path=configurations["add_background_video"]["material_path"].as_posix()
).duration # 默认将背景视频素材持续时长作为视频持续时长(单位为微秒)
# 达人视频特殊处理:固定 5秒加上达人视频素材持续时长
if "add_kol_video" in configurations:
video_duration = (
5_000_000
+ VideoMaterial(
path=configurations["add_kol_video"]["material_path"].as_posix()
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 duration_capture:
video_duration = duration_capture * 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(
@ -444,26 +454,31 @@ class JianYingManager:
print("-> 正在根据字幕文本合成字幕音频并生成字幕...", end="")
draft.generate_subtitle(**configurations[node_name])
print("已完成")
# 添加字幕视频
case "add_subtitle_video":
print("-> 正在添加字幕视频...", end="")
draft.add_video_segment(**configurations[node_name])
print("已完成")
# 添加背景视频
case "add_background_video":
print("-> 正在添加背景视频...", end="")
draft.add_video_segment(**configurations[node_name])
print("已完成")
# 添加达人视频
case "add_kol_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="")
@ -510,9 +525,11 @@ class JianYingManager:
def _get_configurations(
self,
duration_capture: Optional[int] = None,
) -> Dict[str, Any]:
"""
获取节点配置
:param duration_capture: 截取背景视频时长单位为秒默认为 None
:return: 节点配置
"""
configurations = {}
@ -532,13 +549,82 @@ class JianYingManager:
matched.group("track_name")
if (
matched := re.match(
pattern=r"^.+_(?P<track_name>.+_.+)$",
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 duration_capture:
background_video_duration = duration_capture * 1_000_000
# 若包含添加字幕音频则在添加背景视频时其播放音量设置为 0
if "add_subtitle_audio" 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(
{

View File

@ -14,5 +14,5 @@ if __name__ == "__main__":
# 导出视频
jianying_manager.batch_create(
draft_counts=1,
draft_counts=200,
)