python_图片、字幕文本、音频一键组合

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)}")
相关推荐
查拉图斯特拉面条5 小时前
UI自动化断言完全指南:从基础到高级的断言表达式实战
python·自动化
非凡ghost5 小时前
Audacity(免费开源跨平台音频软件)中文绿色版
网络·网络协议·音视频·软件需求
yy我不解释5 小时前
关于comfyui的comfyui-prompt-reader-node节点(import failed)和图片信息问题(metadata)
python·ai作画·prompt
linweidong5 小时前
猫眼ios开发面试题及参考答案(上)
swift·三次握手·ios面试·nsarray·苹果开发·ios内存·nstimer
起风了___5 小时前
Flask生产级模板:统一返回、日志、异常、JSON编解码,开箱即用可扩展
后端·python
BoBoZz195 小时前
SmoothDiscreteMarchingCubes 多边形网格数据的平滑
python·vtk·图形渲染·图形处理
XiaoMu_0015 小时前
多场景头盔佩戴检测
人工智能·python·深度学习
一个无名的炼丹师5 小时前
[硬核实战] 解锁多模态RAG:构建能“看懂”PDF复杂图表的智能问答系统
人工智能·python·pdf·多模态·rag