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

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"
)
相关推荐
我的xiaodoujiao2 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 32--开源电商商城系统项目实战--如何区分登录状态
python·学习·测试工具·pytest
ZAz_2 小时前
DAY 38 模型可视化与推理
python
YANshangqian2 小时前
音频录制和编辑软件
音视频
艾上编程2 小时前
第二章——数据分析场景之用Python进行CSV/Excel数据清洗:为数据分析筑牢根基
python·数据分析·excel
闲人编程2 小时前
FastAPI性能优化技巧
后端·python·性能优化·fastapi·性能·codecapsule
Amazon数据采集2 小时前
# 🚀 亚马逊URL参数拼接实战:数据采集效率提升指南
python
岁月宁静2 小时前
FastAPI 入门指南
人工智能·后端·python
AI小云2 小时前
【数据操作与可视化】Serborn绘图-类别散点图和热力图
python·数据可视化
码界奇点2 小时前
基于Python与Django的白泽自动化运维系统设计与实现
运维·python·django·毕业设计·源代码管理