python_图片、字幕文本、音频一键组合
python
import os
import re
import random
import pyJianYingDraft as draft
from pyJianYingDraft import TrackType, TextStyle, ClipSettings, TextBackground, KeyframeProperty
from PIL import Image # 用于获取图片尺寸
def split_subtitle(subtitle):
"""将字幕按指定标点符号拆分短句,保留标点符号在句尾"""
# 使用正则表达式拆分,保留分隔符在句尾
separators = r'([,。!,!?;])'
parts = re.split(separators, subtitle)
# 组合拆分后的部分,形成完整短句
sentences = []
for i in range(0, len(parts)-1, 2):
if parts[i] or parts[i+1]: # 避免空字符串
sentences.append(parts[i] + parts[i+1])
# 处理可能剩余的部分(如果字幕不以标点结尾)
if len(parts) % 2 == 1 and parts[-1].strip():
sentences.append(parts[-1].strip())
return sentences
def create_clip_draft(draft_name, image_paths, subtitle_texts, audio_paths, draft_folder_path,
background_image=None, background_music=None, add_camera_movement=True):
# 定义6种运镜效果的关键帧设置函数
def add_zoom_in(segment, duration):
"""从远到近(放大)"""
# 缩放关键帧
segment.add_keyframe(KeyframeProperty.uniform_scale, 0, 1)
segment.add_keyframe(KeyframeProperty.uniform_scale, time_offset=duration, value=1.25)
# 位置关键帧
segment.add_keyframe(KeyframeProperty.position_x, time_offset=0, value=0)
segment.add_keyframe(KeyframeProperty.position_x, time_offset=duration, value=0)
segment.add_keyframe(KeyframeProperty.position_y, time_offset=0, value=0)
segment.add_keyframe(KeyframeProperty.position_y, time_offset=duration, value=0)
def add_zoom_out(segment, duration):
"""从近到远(缩小)"""
# 缩放关键帧
segment.add_keyframe(KeyframeProperty.uniform_scale, time_offset=0, value=1.25)
segment.add_keyframe(KeyframeProperty.uniform_scale, time_offset=duration, value=1)
# 位置关键帧
segment.add_keyframe(KeyframeProperty.position_x, time_offset=0, value=0)
segment.add_keyframe(KeyframeProperty.position_x, time_offset=duration, value=0)
segment.add_keyframe(KeyframeProperty.position_y, time_offset=0, value=0)
segment.add_keyframe(KeyframeProperty.position_y, time_offset=duration, value=0)
def add_move_up(segment, duration):
"""从下到上"""
# 缩放关键帧
segment.add_keyframe(KeyframeProperty.uniform_scale, time_offset=0, value=1.25)
segment.add_keyframe(KeyframeProperty.uniform_scale, time_offset=duration, value=1.25)
# 位置关键帧
segment.add_keyframe(KeyframeProperty.position_x, time_offset=0, value=0)
segment.add_keyframe(KeyframeProperty.position_x, time_offset=duration, value=0)
segment.add_keyframe(KeyframeProperty.position_y, time_offset=0, value=-0.25)
segment.add_keyframe(KeyframeProperty.position_y, time_offset=duration, value=0.25)
def add_move_down(segment, duration):
"""从上到下"""
# 缩放关键帧
segment.add_keyframe(KeyframeProperty.uniform_scale, time_offset=0, value=1.25)
segment.add_keyframe(KeyframeProperty.uniform_scale, time_offset=duration, value=1.25)
# 位置关键帧
segment.add_keyframe(KeyframeProperty.position_x, time_offset=0, value=0)
segment.add_keyframe(KeyframeProperty.position_x, time_offset=duration, value=0)
segment.add_keyframe(KeyframeProperty.position_y, time_offset=0, value=0.25)
segment.add_keyframe(KeyframeProperty.position_y, time_offset=duration, value=-0.25)
def add_move_left(segment, duration):
"""从右到左"""
# 缩放关键帧
segment.add_keyframe(KeyframeProperty.uniform_scale, time_offset=0, value=1.25)
segment.add_keyframe(KeyframeProperty.uniform_scale, time_offset=duration, value=1.25)
# 位置关键帧
segment.add_keyframe(KeyframeProperty.position_x, time_offset=0, value=-0.25)
segment.add_keyframe(KeyframeProperty.position_x, time_offset=duration, value=0.25)
segment.add_keyframe(KeyframeProperty.position_y, time_offset=0, value=0)
segment.add_keyframe(KeyframeProperty.position_y, time_offset=duration, value=0)
def add_move_right(segment, duration):
"""从左到右"""
# 缩放关键帧
segment.add_keyframe(KeyframeProperty.uniform_scale, time_offset=0, value=1.25)
segment.add_keyframe(KeyframeProperty.uniform_scale, time_offset=duration, value=1.25)
# 位置关键帧
segment.add_keyframe(KeyframeProperty.position_x, time_offset=0, value=0.25)
segment.add_keyframe(KeyframeProperty.position_x, time_offset=duration, value=-0.25)
segment.add_keyframe(KeyframeProperty.position_y, time_offset=0, value=0)
segment.add_keyframe(KeyframeProperty.position_y, time_offset=duration, value=0)
# 运镜效果列表
camera_effects = [
add_zoom_in,
add_zoom_out,
add_move_up,
add_move_down,
add_move_left,
add_move_right
]
# 检查列表是否为空
if not image_paths:
raise ValueError("图片列表不能为空")
# 检查列表长度是否一致
if len(image_paths) != len(subtitle_texts) or len(image_paths) != len(audio_paths):
raise ValueError("图片、字幕、音频列表长度不一致,无法创建草稿")
# 检查路径存在性
if not os.path.exists(draft_folder_path):
raise FileNotFoundError(f"剪映草稿文件夹不存在: {draft_folder_path}")
# 检查背景图片和背景音乐是否存在(仅当提供了路径时)
if background_image is not None and not os.path.exists(background_image):
raise FileNotFoundError(f"背景图片不存在: {background_image}")
if background_music is not None and not os.path.exists(background_music):
raise FileNotFoundError(f"背景音乐不存在: {background_music}")
missing_files = []
for img_path in image_paths:
if not os.path.exists(img_path):
missing_files.append(f"图片: {img_path}")
for audio_path in audio_paths:
if not os.path.exists(audio_path):
missing_files.append(f"音频: {audio_path}")
if missing_files:
raise FileNotFoundError(f"以下文件不存在:\n" + "\n".join(missing_files))
# 获取第一张图片的尺寸
try:
with Image.open(image_paths[0]) as img:
img_width, img_height = img.size
except Exception as e:
raise RuntimeError(f"获取第一张图片尺寸失败: {str(e)}")
# 判断横竖屏并设置参数
if img_width > img_height:
# 横屏
draft_width = img_width
draft_height = img_height
font_size = 6.0
subtitle_y = -0.8
else:
# 竖屏
draft_width = img_width
draft_height = img_height
font_size = 13.0
subtitle_y = -0.3
# 初始化草稿文件夹管理器
try:
draft_folder = draft.DraftFolder(draft_folder_path)
except FileNotFoundError as e:
raise FileNotFoundError(f"草稿文件夹处理错误: {str(e)}")
# 检查草稿是否已存在
if draft_folder.has_draft(draft_name):
raise FileExistsError(f"草稿 '{draft_name}' 已存在,不允许覆盖")
# 根据第一张图片尺寸创建草稿
try:
script = draft_folder.create_draft(
draft_name,
width=draft_width,
height=draft_height,
allow_replace=False
)
except Exception as e:
raise RuntimeError(f"创建草稿失败: {str(e)}")
# 添加基础轨道(视频、音频、文本)
track_builder = script.add_track(TrackType.video, "主视频轨道") \
.add_track(TrackType.audio, "音频轨道") \
.add_track(TrackType.text, "字幕轨道")
# 仅在提供了背景图片或背景音乐时添加相应轨道
if background_image is not None:
track_builder.add_track(TrackType.video, "背景图片轨道", relative_index=3) # 最上层轨道
if background_music is not None:
track_builder.add_track(TrackType.audio, "背景音乐轨道")
current_time = 0 # 单位:微秒(1秒 = 1e6微秒)
separators = r'([,。!,!?;])'
# 处理每一组素材
for i in range(len(image_paths)):
img_path = image_paths[i]
subtitle = subtitle_texts[i]
audio_path = audio_paths[i]
# 获取音频时长(微秒)
try:
audio_material = draft.AudioMaterial(audio_path)
audio_duration = audio_material.duration
except Exception as e:
raise RuntimeError(f"处理音频 {audio_path} 时出错: {str(e)}")
# 计算当前片段的时间范围
start_time = current_time
end_time = current_time + audio_duration
time_range = draft.trange(start_time, end_time - start_time)
# 添加音频片段
audio_segment = draft.AudioSegment(
audio_path,
time_range
)
script.add_segment(audio_segment, "音频轨道")
# 添加图片片段
image_segment = draft.VideoSegment(
img_path,
time_range
)
# 设置图片居中显示
image_segment.clip_settings = ClipSettings(
transform_x=0,
transform_y=0,
)
# 添加关键帧确保图片保持比例缩放
image_segment.add_keyframe(
KeyframeProperty.uniform_scale,
time_offset=0,
value=1.0
)
# 如果需要添加运镜效果,随机选择一种效果应用
if add_camera_movement:
effect = random.choice(camera_effects)
effect(image_segment, audio_duration)
script.add_segment(image_segment, "主视频轨道")
# 拆分字幕为短句
sentences = split_subtitle(subtitle)
total_length = len(subtitle)
# 如果没有拆分出短句(无标点符号),使用原字幕
if not sentences:
sentences = [subtitle]
# 计算每个短句的显示时间并添加
current_sub_time = start_time
for sentence in sentences:
# 计算短句长度占比(避免除零错误)
if total_length == 0:
ratio = 1.0 / len(sentences)
else:
ratio = len(sentence) / total_length
# 计算当前短句的显示时长
sentence_duration = int(audio_duration * ratio)
# 确保至少有100ms的显示时间
sentence_duration = max(sentence_duration, 100000) # 100,000微秒 = 0.1秒
# 计算当前短句的时间范围
sentence_end_time = current_sub_time + sentence_duration
sentence_time_range = draft.trange(current_sub_time, sentence_end_time - current_sub_time)
# 添加字幕片段
text_segment = draft.TextSegment(
re.sub(separators, '', sentence),
sentence_time_range,
font=draft.FontType.文轩体,
style=TextStyle(
color=(0.0, 0.0, 0.0), # 黑色字体
size=font_size, # 根据横竖屏设置字体大小
align=1, # 居中对齐
auto_wrapping=True, # 开启自动换行
max_line_width=0.8, # 每行最大宽度为屏幕宽度的80%
),
background=TextBackground(
color="#FFFF00", # 黄色背景
alpha=0.8, # 背景透明度
round_radius=0.1, # 背景圆角
height=0.15, # 背景高度
width=0.8 # 背景宽度
),
clip_settings=ClipSettings(
transform_y=subtitle_y # 根据横竖屏设置字幕Y轴位置
)
)
script.add_segment(text_segment, "字幕轨道")
# 更新当前字幕时间
current_sub_time = sentence_end_time
# 更新当前时间
current_time = end_time
# 添加背景图片(仅当提供了路径时)
if background_image is not None:
background_image_time_range = draft.trange(0, current_time) # 从0开始,持续总时长
background_image_segment = draft.VideoSegment(
background_image,
background_image_time_range
)
# 设置背景图片居中显示
background_image_segment.clip_settings = ClipSettings(
transform_x=0,
transform_y=0,
)
script.add_segment(background_image_segment, "背景图片轨道")
# 添加背景音乐(仅当提供了路径时)
if background_music is not None:
bgm_material = draft.AudioMaterial(background_music)
bgm_duration = bgm_material.duration # 背景音乐时长(微秒)
total_video_duration = current_time # 总视频时长(微秒)
current_bgm_time = 0
while current_bgm_time < total_video_duration:
# 计算当前片段的持续时间
remaining_time = total_video_duration - current_bgm_time
segment_duration = min(bgm_duration, remaining_time)
# 创建当前背景音乐片段
bgm_segment = draft.AudioSegment(
background_music,
draft.trange(current_bgm_time, segment_duration),
volume=0.5,
# 截取音乐的对应部分
source_timerange=draft.trange(0, segment_duration)
)
script.add_segment(bgm_segment, "背景音乐轨道")
current_bgm_time += segment_duration
# 保存草稿
try:
script.save()
except Exception as e:
raise RuntimeError(f"保存草稿失败: {str(e)}")
print(f"剪映草稿 {draft_name} 创建成功!")
# 使用示例(实际使用时请注释或删除)
if __name__ == "__main__":
try:
create_clip_draft(
draft_name="示例草稿07",
image_paths=['D:\\Desktop\\test_folder\\jianying_materials_2\\images\\01.png', 'D:\\Desktop\\test_folder\\jianying_materials_2\\images\\02.png', 'D:\\Desktop\\test_folder\\jianying_materials_2\\images\\03.png'],
subtitle_texts=["在一座古老小镇,每到清明便细雨纷纷。年轻画师林羽,每到此时总会对着一幅未完成的画发呆。","这幅画始于七年前,画中女子荷风微摆衣角,宛如仙子。","七年前,林羽与名叫婉清的姑娘相遇,二人一见钟情。"],
audio_paths=['D:\\Desktop\\test_folder\\jianying_materials_2\\audios\\01.mp3', 'D:\\Desktop\\test_folder\\jianying_materials_2\\audios\\02.mp3', 'D:\\Desktop\\test_folder\\jianying_materials_2\\audios\\03.mp3'],
draft_folder_path=r"D:\download_software\JianyingPro Drafts",
#background_image=r"D:\Desktop\test_folder\jianying_materials_2\影刀logo图片_横版.png", # 背景图片路径
background_image=None,
# background_music=r"D:\Desktop\test_folder\jianying_materials_2\轻音乐缩减版.MP3", # 背景音乐路径
background_music=None,
add_camera_movement=True # 控制是否添加运镜效果
)
except Exception as e:
print(f"错误: {str(e)}")