Python/短视频合成自动化/draft.py

446 lines
17 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
生成草稿模块
"""
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, Union
import pyJianYingDraft
from edgetts import EdgeTTS
class JianYingDraft:
"""
封装 pyJianYing中生成草稿的相关功能支持
1、向指定文本轨道添加文本片段
2、向指定音频轨道添加音频片段
3、向指定视频轨道添加视频或图片片段
4、向指定贴纸轨道添加贴纸片段
5、根据文本逐段合成语音生成文本和语音字幕
6、将草稿保存至剪映草稿文件夹内
"""
def __init__(
self,
drafts_folder: pyJianYingDraft.DraftFolder,
draft_name: str,
materials_folder_path: Path,
allow_replace: bool = True,
video_width: int = 1080,
video_height: int = 1920,
video_fps: int = 30,
):
"""
初始化
:param drafts_folder: 剪映草稿文件夹管理器
:param draft_name: 草稿名称
:param allow_replace: 是否允许覆盖同名草稿(若不允许覆盖同名草稿需初始化剪映草稿文件夹)
:param video_width: 视频宽度,默认为 1080像素
:param video_height: 视频高度,默认为 1920像素
:param video_fps: 视频帧率(单位为帧/秒),默认为 30
:param materials_folder_path: 素材文件夹路径
"""
try:
# 新建草稿
self.draft = drafts_folder.create_draft(
draft_name=draft_name,
allow_replace=allow_replace,
width=video_width,
height=video_height,
fps=video_fps,
)
# 草稿持续时长(单位为毫秒)
self.draft_duration = 0
self.materials_folder_path = materials_folder_path
except Exception as exception:
raise RuntimeError(f"发生异常:{str(exception)}") from exception
def add_text_segment(
self,
track_name: str,
text: str,
add_track: bool = True,
timerange: Optional[Tuple[Union[int, str], Union[int, str]]] = None,
font: Optional[str] = None,
style: Optional[Dict[str, Any]] = None,
border: Optional[Dict[str, Any]] = None,
background: Optional[Dict[str, Any]] = None,
clip_settings: Optional[Dict[str, Any]] = None,
bubble: Optional[Dict[str, Any]] = None,
effect: Optional[Dict[str, Any]] = None,
animation: Optional[Dict[str, Any]] = None,
) -> None:
"""
向指定文本轨道添加文本片段
:param track_name: 轨道名称
:param add_track: 添加文本轨道,默认为是
:param text: 文本
:param timerange: 文本素材在轨道上的范围,包括开始时间和持续时长,默认为草稿持续时长
:param font: 字体,默认为系统
:param style: 字体样式,默认为字号 15对齐方式 左对齐
:param border: 文本描边设置,默认为无
:param background: 文本背景设置,默认为无
:param clip_settings: 图像调节设置,默认为移动至 (0, 0)
:param bubble: 气泡设置,默认为无
:param effect: 花字设置,默认为无
:param animation: 动画设置,默认为无
:return: 无
"""
try:
if add_track:
# 添加文本轨道
self.draft.add_track(
track_type=pyJianYingDraft.TrackType.text,
track_name=track_name,
)
# 构建文本片段
text_segment = pyJianYingDraft.TextSegment(
text=text.replace("\\n", "\n"),
timerange=pyJianYingDraft.trange(
*(timerange if timerange else (0, self.draft_duration))
),
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=(
pyJianYingDraft.ClipSettings(**clip_settings)
if clip_settings
else None
),
)
# 添加气泡
if bubble:
text_segment.add_bubble(**bubble)
# 添加花字
# 将花字保存预设后在C:/Users/admin/AppData/Local/JianyingPro/User Data/Presets/Text_V2/预设文本?.textpreset获取花字resource_id
if effect:
text_segment.add_effect(**effect)
# 添加动画
if animation:
text_segment.add_animation(**animation)
# 向指定文本轨道添加文本片段
self.draft.add_segment(segment=text_segment, track_name=track_name)
except Exception as exception:
raise RuntimeError(str(exception)) from exception
def add_audio_segment(
self,
track_name: str,
material_path: Path,
add_track: bool = True,
target_timerange: Optional[Tuple[Union[int, str], Union[int, str]]] = None,
source_timerange: Optional[Tuple[str, str]] = None,
speed: float = 1.0,
volume: float = 1.0,
fade: Optional[Tuple[str, str]] = None,
) -> None:
"""
向指定音频轨道添加音频片段
:param track_name: 轨道名称
:param add_track: 添加音频轨道,默认为是
:param material_path: 音频素材路径
:param target_timerange: 音频素材在轨道上的范围,包括开始时间和持续时长,默认为草稿持续时长
:param source_timerange: 截取音频素材范围,包括开始时间和持续时长,默认根据音频素材开始时间根据播放速度截取与音频素材持续时长等长的部分
:param speed: 播放速度,默认为 1.0
:param volume: 播放音量,默认为 1.0
:param fade: 淡入淡出设置,默认为无
:return: 无
"""
try:
# 音频素材
audio_material = pyJianYingDraft.AudioMaterial(
path=material_path.as_posix()
)
# 音频素材的持续时长
audio_material_duration = audio_material.duration
# 若草稿持续时长为0则将第一个音频素材持续时长作为草稿持续时长
# 获取持续时间
target_duration = pyJianYingDraft.time_util.tim(
(target_timerange if target_timerange else (0, self.draft_duration))[1]
)
if add_track:
# 添加音频轨道
self.draft.add_track(
track_type=pyJianYingDraft.TrackType.audio,
track_name=track_name,
)
duration = 0 # 已添加音频素材的持续时长
while duration < target_duration:
# 构建音频片段
audio_segment = pyJianYingDraft.AudioSegment(
material=audio_material,
target_timerange=pyJianYingDraft.trange(
start=duration,
duration=min(
(target_duration - duration), audio_material_duration
),
),
source_timerange=(
pyJianYingDraft.trange(*source_timerange)
if source_timerange
else None
),
speed=speed,
volume=volume,
)
# 添加淡入淡出
if fade:
audio_segment.add_fade(*fade)
# 向指定音频轨道添加音频片段
self.draft.add_segment(segment=audio_segment, track_name=track_name)
duration += audio_material_duration
except Exception as exception:
raise RuntimeError(str(exception)) from exception
def add_video_segment(
self,
track_name: str,
material_path: Path,
target_timerange: Optional[Tuple[Union[int, str], Union[int, str]]] = None,
source_timerange: Optional[Tuple[Union[int, str], Union[int, str]]] = None,
speed: float = 1.0,
volume: float = 1.0,
clip_settings: Optional[Dict[str, Any]] = None,
keyframes: Optional[
List[Tuple[pyJianYingDraft.KeyframeProperty, Union[str, int], float]]
] = None,
animation: Optional[Dict[str, Any]] = None,
transition: Optional[Dict[str, Any]] = None,
background_filling: Optional[Dict[str, Any]] = None,
) -> None:
"""
向指定视频轨道添加视频或图片片段
:param track_name: 轨道名称
:param material_path: 视频或图片素材路径
:param target_timerange: 视频或图片素材在轨道上的范围,包括开始时间和持续时长,默认为草稿持续时长
:param source_timerange: 截取视频或图片素材范围,包括开始时间和持续时长,默认为空
:param speed: 播放速度,默认为 1.0
:param volume: 播放音量,默认为 1.0
:param clip_settings: 图像调节设置,默认为无
:param keyframes: 关键帧设置,默认为无
:param animation: 动画设置,默认为无
:param transition: 转场设置,默认为无
:param background_filling: 背景填充设置,默认为无
:return: 无
"""
try:
# 视频素材
video_material = pyJianYingDraft.VideoMaterial(
path=material_path.as_posix()
)
# 视频素材的持续时长
video_material_duration = video_material.duration
# 若草稿持续时长为0则将第一个视频素材持续时长作为草稿持续时长
relative_index = 0
if not self.draft_duration:
relative_index = 1 # 视频轨道相对索引
self.draft_duration = video_material_duration
# 获取持续时间
target_duration = pyJianYingDraft.time_util.tim(
(target_timerange if target_timerange else (0, self.draft_duration))[1]
)
# 添加视频轨道
self.draft.add_track(
track_type=pyJianYingDraft.TrackType.video,
track_name=track_name,
relative_index=relative_index,
)
duration = 0 # 已添加视频素材的持续时长
while duration < target_duration:
# 构建视频或图片片段
video_segment = pyJianYingDraft.VideoSegment(
material=video_material,
target_timerange=pyJianYingDraft.trange(
start=duration,
duration=min(
(target_duration - duration), video_material_duration
),
),
source_timerange=(
pyJianYingDraft.trange(*source_timerange)
if source_timerange
else None
),
speed=speed,
volume=volume,
clip_settings=(
pyJianYingDraft.ClipSettings(**clip_settings)
if clip_settings
else None
),
)
# 添加关键帧
if keyframes:
for _property, offset, value in keyframes:
video_segment.add_keyframe(_property, offset, value)
# 添加动画
if animation:
video_segment.add_animation(**animation)
# 添加转场
if transition:
video_segment.add_transition(**transition)
# 添加背景填充
if background_filling:
video_segment.add_background_filling(**background_filling)
# 向指定视频轨道添加视频或图片片段
self.draft.add_segment(segment=video_segment, track_name=track_name)
duration += video_material_duration
except Exception as exception:
raise RuntimeError(str(exception)) from exception
def add_sticker(
self,
track_name: str,
resource_id: str,
target_timerange: Optional[Tuple[Union[int, str], Union[int, str]]] = None,
clip_settings: Optional[Dict[str, Any]] = None,
) -> None:
"""
向指定贴纸轨道添加贴纸片段
:param track_name: 轨道名称
:param resource_id: 贴纸资源标识
:param target_timerange: 贴纸在轨道上的范围,包括开始时间和持续时长,默认为草稿持续时长
:param clip_settings: 图像调节设置,默认为无
:return: 无
"""
try:
# 添加贴纸轨道
self.draft.add_track(
track_type=pyJianYingDraft.TrackType.sticker,
track_name=track_name,
)
# 构建贴纸
# 将贴纸保存为我的预设后在C:\Users\admin\AppData\Local\JianyingPro\User Data\Presets\Combination\Presets\我的预设?\preset_draft获取
sticker_segment = pyJianYingDraft.StickerSegment(
resource_id=resource_id,
target_timerange=pyJianYingDraft.trange(
*(
target_timerange
if target_timerange
else (0, self.draft_duration)
)
),
clip_settings=(
pyJianYingDraft.ClipSettings(**clip_settings)
if clip_settings
else None
),
)
# 向指定贴纸轨道添加贴纸片段
self.draft.add_segment(segment=sticker_segment, track_name=track_name)
except Exception as exception:
raise RuntimeError(str(exception)) from exception
def add_subtitles(
self,
text: str,
timbre: str = "女声-晓晓",
rate: str = "+25%",
volume: str = "+0%",
font: Optional[str] = None,
style: Optional[Dict[str, Any]] = {"size": 12.0, "align": "center"},
clip_settings: Optional[Dict[str, Any]] = None,
effect: Optional[Dict[str, Any]] = None,
):
"""
添加字幕
:param text: 文本
:param timbre: 声音音色,默认为女声-晓晓
:param rate: 语速,默认为 +25%
:param volume: 音量,默认为 +0%
:param font: 字体,默认为系统
:param style: 文本样式,默认为字号 12对齐方式 水平居中
:param clip_settings: 图像调节设置,默认为移动至 (0, -0.5)
:param effect: 花字设置,默认为无
:return: 无
"""
# 添加文本轨道
self.draft.add_track(
track_type=pyJianYingDraft.TrackType.text,
track_name=(text_track_name := "subtitles(text)"),
)
# 添加音频轨道
self.draft.add_track(
track_type=pyJianYingDraft.TrackType.audio,
track_name=(audio_track_name := "subtitles(audio)"),
)
# 构造语音文件保存文件夹路径path对象
subtitles_folder_path = self.materials_folder_path / "subtitles"
subtitles_folder_path.mkdir(exist_ok=True)
# 实例化 EdgeTTS
edge_tts = EdgeTTS(folder_path=subtitles_folder_path)
start = 0
for paragraph in text.split(""):
# 根据文本合成语音并将语音文件保存至指定文件夹内
file_path, duration = edge_tts.synthetize(
text=paragraph, timbre=timbre, rate=rate, volume=volume
)
# 向指定文本轨道添加文本片段
self.add_text_segment(
track_name=text_track_name,
add_track=False,
text=paragraph,
timerange=(start, duration),
font=font,
style={
"size": 12.0,
"align": 1,
**(style or {}),
},
clip_settings={
"transform_y": -0.6,
**(clip_settings or {}),
},
effect=effect,
)
# 向指定音频轨道添加音频片段
self.add_audio_segment(
track_name=audio_track_name,
add_track=False,
material_path=file_path,
target_timerange=(start, duration),
volume=1.5,
)
start += duration
# 更新草稿持续时长
self.draft_duration = start
def save(self) -> None:
"""将草稿保存至剪映草稿文件夹内"""
try:
self.draft.save()
except Exception as exception:
raise RuntimeError(str(exception)) from exception