python_字幕、音频、媒体文件(图片或视频)一键组合

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)}")
相关推荐
shenzhenNBA2 小时前
python用openpyxl操作excel-读取sheet中数据
python·excel·读取sheet数据
daizhe2 小时前
基于JavaCV实现FFmpeg设置视频moov前置以及截取封面图片
ffmpeg·音视频·javacv
网安Ruler3 小时前
崭新出厂,自研CipherForge小工具,攻破 D-Link M30 固件加密
前端·网络·python
艾上编程3 小时前
第二章——数据分析场景之Python数据可视化:用Matplotlib与Seaborn绘制洞察之图
python·信息可视化·数据分析
Cigaretter73 小时前
Day 31 类的装饰器
开发语言·python
MasonYyp3 小时前
简单使用Argos翻译框架
python
XiaoMu_0013 小时前
验证码识别系统
python·深度学习
white-persist3 小时前
网络空间安全核心领域技术架构深度解析
c语言·开发语言·网络·python·安全·网络安全·架构
qq_463944863 小时前
如何修改Anaconda虚拟环境的名字?
开发语言·python·anaconda