diff --git a/短视频合成自动化/caches.db b/短视频合成自动化/caches.db index 437603b..ebc37cf 100644 Binary files a/短视频合成自动化/caches.db and b/短视频合成自动化/caches.db differ diff --git a/短视频合成自动化/drafts.py b/短视频合成自动化/drafts.py index dd7af32..f819bdc 100644 --- a/短视频合成自动化/drafts.py +++ b/短视频合成自动化/drafts.py @@ -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), - video_material_duration, + start=target_start + cumulative_duration, + duration=( + current_duration := min( + target_duration - cumulative_duration, + video_material_duration, + ) ), ), source_timerange=( @@ -307,8 +305,9 @@ class Drafts: video_segment.add_background_filling(**background_filling) # 向指定轨道添加视频或图片片段 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 diff --git a/短视频合成自动化/jiangying_manager.py b/短视频合成自动化/jiangying_manager.py index 9e8db27..4815c8e 100644 --- a/短视频合成自动化/jiangying_manager.py +++ b/短视频合成自动化/jiangying_manager.py @@ -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) - 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() - ).duration - ) + # 若包含添加字幕视频则使用字幕视频时长作为视频时长,否则使用背景视频时长作为视频时长 + if "add_subtitle_video" in configurations: + video_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 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.+_.+)$", + pattern=r"^.+?_(?P.+)$", 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( { diff --git a/短视频合成自动化/main.py b/短视频合成自动化/main.py index fc6dbcd..9e22211 100644 --- a/短视频合成自动化/main.py +++ b/短视频合成自动化/main.py @@ -14,5 +14,5 @@ if __name__ == "__main__": # 导出视频 jianying_manager.batch_create( - draft_counts=1, + draft_counts=200, )