python_字幕、音频、媒体文件(图片或视频)一键组合
python
import os
import re
import random
import pyJianYingDraft as draft
from pyJianYingDraft import (TrackType, TextStyle, ClipSettings, TextBackground,
KeyframeProperty, trange)
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_jianying_draft(draft_name, subtitle_texts, audio_paths, media_paths, draft_folder_path,
add_image_movement=True, add_video_movement=True,
background_image=None, background_music=None):
"""
创建剪映草稿,支持图片/视频、音频、字幕同步处理,根据媒体类型自适应调整
参数说明:
draft_name: 草稿名称
subtitle_texts: 字幕文本列表
audio_paths: 音频路径列表
media_paths: 图片或视频路径列表
draft_folder_path: 剪映草稿文件夹路径
add_image_movement: 是否为图片添加随机运镜效果
add_video_movement: 是否为视频添加随机运镜效果
background_image: 背景图片路径(可选)
background_music: 背景音乐路径(可选)
"""
# 定义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 len(subtitle_texts) != len(audio_paths) or len(audio_paths) != len(media_paths):
raise ValueError("字幕文本列表、音频路径列表、媒体路径列表长度必须一致")
# 校验媒体路径有效性
if not media_paths:
raise ValueError("媒体路径列表不能为空")
# 检查所有文件是否存在
missing_files = []
for media_path in media_paths:
if not os.path.exists(media_path):
missing_files.append(f"媒体文件: {media_path}")
for audio_path in audio_paths:
if not os.path.exists(audio_path):
missing_files.append(f"音频: {audio_path}")
if background_image and not os.path.exists(background_image):
missing_files.append(f"背景图片: {background_image}")
if background_music and not os.path.exists(background_music):
missing_files.append(f"背景音乐: {background_music}")
if missing_files:
raise FileNotFoundError(f"以下文件不存在:\n" + "\n".join(missing_files))
# 初始化草稿文件夹
draft_folder = draft.DraftFolder(draft_folder_path)
# 检查草稿是否已存在
if draft_folder.has_draft(draft_name):
raise FileExistsError(f"草稿 '{draft_name}' 已存在,不允许覆盖")
# 判断媒体类型(图片/视频)并确定草稿尺寸
all_images = True
first_video_path = None
# 检查是否有视频文件
for path in media_paths:
if path.lower().endswith(('.mp4', '.mov', '.avi', '.mkv')):
all_images = False
first_video_path = path
break
# 获取尺寸信息
if all_images:
# 全是图片,使用第一张图片的尺寸
try:
with Image.open(media_paths[0]) as img:
width, height = img.size
except Exception as e:
raise RuntimeError(f"获取第一张图片尺寸失败: {str(e)}")
else:
# 至少有一个视频,使用第一个视频的尺寸
first_video = draft.VideoMaterial(first_video_path)
width, height = first_video.width, first_video.height
# 根据横竖屏设置字幕参数
if width > height:
# 横屏
font_size = 6.0
subtitle_y = -0.8
else:
# 竖屏
font_size = 13.0
subtitle_y = -0.3
# 创建新草稿
script = draft_folder.create_draft(
draft_name,
width,
height,
allow_replace=False # 不允许覆盖已有草稿
)
# 添加所需轨道
track_builder = script.add_track(TrackType.video, "媒体轨道") \
.add_track(TrackType.audio, "音频轨道") \
.add_track(TrackType.text, "字幕轨道")
if background_image:
# 背景图片放到底层
track_builder.add_track(TrackType.video, "背景图片轨道", relative_index=3)
if background_music:
track_builder.add_track(TrackType.audio, "背景音乐轨道")
current_time = 0 # 当前时间点(微秒)
total_duration = 0 # 总时长(微秒)
separators = r'([,。!,!?;])'
# 处理每个片段(媒体、音频、字幕)
for i in range(len(subtitle_texts)):
audio_path = audio_paths[i]
media_path = media_paths[i]
subtitle_text = subtitle_texts[i]
# 创建音频片段并获取时长
audio_material = draft.AudioMaterial(audio_path)
audio_duration = audio_material.duration
time_range = trange(current_time, audio_duration)
audio_segment = draft.AudioSegment(
audio_path,
time_range
)
script.add_segment(audio_segment, "音频轨道")
# 判断媒体类型并创建相应片段
is_image = media_path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp'))
if is_image:
# 处理图片片段
media_segment = draft.VideoSegment(
media_path,
time_range
)
# 是否添加运镜效果
add_movement = add_image_movement
else:
# 处理视频片段(根据音频时长调整速度)
video_material = draft.VideoMaterial(media_path)
video_duration = video_material.duration
speed = video_duration / audio_duration # 计算所需速度以匹配音频时长
media_segment = draft.VideoSegment(
media_path,
time_range,
speed=speed
)
# 是否添加运镜效果
add_movement = add_video_movement
# 设置媒体居中显示
media_segment.clip_settings = ClipSettings(
transform_x=0,
transform_y=0,
)
# 添加关键帧确保保持比例
media_segment.add_keyframe(
KeyframeProperty.uniform_scale,
time_offset=0,
value=1.0
)
# 随机添加运镜效果
if add_movement:
effect = random.choice(camera_effects)
effect(media_segment, audio_duration)
script.add_segment(media_segment, "媒体轨道")
# 拆分字幕为短句并添加
sentences = split_subtitle(subtitle_text)
total_length = len(subtitle_text)
# 如果没有拆分出短句(无标点符号),使用原字幕
if not sentences:
sentences = [subtitle_text]
# 计算每个短句的显示时间并添加
current_sub_time = current_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_time_range = trange(current_sub_time, sentence_duration)
# 添加字幕片段
text_segment = draft.TextSegment(
re.sub(separators, '', sentence), # 移除标点符号
sentence_time_range,
font=draft.FontType.文轩体,
style=TextStyle(
color=(1.0, 1.0, 1.0), # 白色文字
size=font_size, # 根据横竖屏设置字体大小
align=1, # 居中对齐
auto_wrapping=True, # 开启自动换行
max_line_width=0.8, # 每行最大宽度为屏幕宽度的80%
),
background=TextBackground(
color="#000000", # 黑色背景
alpha=0.5, # 背景透明度
round_radius=0.1, # 背景圆角
height=0.15, # 背景高度
width=0.8 # 背景宽度
),
clip_settings=ClipSettings(transform_y=subtitle_y) # 根据横竖屏设置位置
)
script.add_segment(text_segment, "字幕轨道")
# 更新当前字幕时间
current_sub_time += sentence_duration
# 更新时间
current_time += audio_duration
total_duration = current_time
# 处理背景图片
if background_image:
image_segment = draft.VideoSegment(
background_image,
trange(0, total_duration)
)
script.add_segment(image_segment, "背景图片轨道")
# 处理背景音乐
if background_music:
music_material = draft.AudioMaterial(background_music)
music_duration = music_material.duration
current_music_time = 0
# 循环添加背景音乐直到达到总时长
while current_music_time < total_duration:
remaining_time = total_duration - current_music_time
segment_duration = min(music_duration, remaining_time)
music_segment = draft.AudioSegment(
background_music,
trange(current_music_time, segment_duration),
volume=0.3, # 降低背景音乐音量
source_timerange=trange(0, segment_duration)
)
script.add_segment(music_segment, "背景音乐轨道")
current_music_time += segment_duration
# 保存草稿
script.save()
# 打印成功提示
print(f"剪映草稿 {draft_name} 创建成功!")
# 使用示例:
if __name__ == "__main__":
try:
create_jianying_draft(
draft_name="示例草稿03",
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'
],
media_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'
],
draft_folder_path="D:\\download_software\\JianyingPro Drafts",
add_image_movement=False,
add_video_movement=False,
background_image="D:\\Desktop\\test_folder\\jianying_materials_2\\影刀logo图片_横版.png",
background_music="D:\\Desktop\\test_folder\\jianying_materials_2\\background_music.mp3"
)
except Exception as e:
print(f"错误: {str(e)}")