python_字幕文本、音频、视频一键组合
python
import os
import re
import random
import pyJianYingDraft as draft
from pyJianYingDraft import (TrackType, TextStyle, ClipSettings, TextBackground,
KeyframeProperty, trange)
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, video_paths, draft_folder_path,
background_image=None, background_music=None, add_camera_movement=True):
"""
创建剪映草稿,支持视频、音频、字幕同步处理,根据视频横竖屏自适应调整,支持字幕拆分和运镜效果
参数说明:
draft_name: 草稿名称
subtitle_texts: 字幕文本列表
audio_paths: 音频路径列表
video_paths: 视频路径列表
draft_folder_path: 剪映草稿文件夹路径
background_image: 背景图片路径(可选)
background_music: 背景音乐路径(可选)
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 len(subtitle_texts) != len(audio_paths) or len(audio_paths) != len(video_paths):
raise ValueError("字幕文本列表、音频路径列表、视频路径列表长度必须一致")
# 校验视频路径有效性
if not video_paths:
raise ValueError("视频路径列表不能为空")
# 检查所有文件是否存在
missing_files = []
for video_path in video_paths:
if not os.path.exists(video_path):
missing_files.append(f"视频: {video_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}' 已存在,不允许覆盖")
# 获取第一个视频的信息以确定尺寸和横竖屏
first_video = draft.VideoMaterial(video_paths[0])
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]
video_path = video_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, "音频轨道")
# 创建视频片段(根据音频时长调整速度)
video_material = draft.VideoMaterial(video_path)
video_duration = video_material.duration
speed = video_duration / audio_duration # 计算所需速度以匹配音频时长
video_segment = draft.VideoSegment(
video_path,
time_range,
speed=speed
)
# 设置视频居中显示
video_segment.clip_settings = ClipSettings(
transform_x=0,
transform_y=0,
)
# 添加关键帧确保视频保持比例
video_segment.add_keyframe(
KeyframeProperty.uniform_scale,
time_offset=0,
value=1.0
)
# 随机添加运镜效果
if add_camera_movement:
effect = random.choice(camera_effects)
effect(video_segment, audio_duration)
script.add_segment(video_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} 创建成功!")
# 使用示例:
create_jianying_draft(
draft_name="示例草稿19",
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'
],
video_paths=[
'D:\\Desktop\\test_folder\\jianying_materials_2\\videos\\04.mp4',
'D:\\Desktop\\test_folder\\jianying_materials_2\\videos\\05.mp4',
'D:\\Desktop\\test_folder\\jianying_materials_2\\videos\\06.mp4'
],
# background_image="D:\\Desktop\\test_folder\\jianying_materials_2\\影刀logo图片_横版.png",
background_image="D:\\Desktop\\test_folder\\jianying_materials_2\\影刀logo图片_竖版.png",
background_music="D:\\Desktop\\test_folder\\jianying_materials_2\\background_music.mp3",
draft_folder_path="D:\\download_software\\JianyingPro Drafts"
)