news 2026/3/14 3:11:22

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
python_字幕、音频、媒体文件(图片或视频)一键组合

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

importosimportreimportrandomimportpyJianYingDraftasdraftfrompyJianYingDraftimport(TrackType,TextStyle,ClipSettings,TextBackground,KeyframeProperty,trange)fromPILimportImage# 用于获取图片尺寸defsplit_subtitle(subtitle):"""将字幕按指定标点符号拆分短句,保留标点符号在句尾"""separators=r'([,。!,!?;])'parts=re.split(separators,subtitle)sentences=[]foriinrange(0,len(parts)-1,2):ifparts[i]orparts[i+1]:# 避免空字符串sentences.append(parts[i]+parts[i+1])# 处理可能剩余的部分(如果字幕不以标点结尾)iflen(parts)%2==1andparts[-1].strip():sentences.append(parts[-1].strip())returnsentencesdefcreate_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种运镜效果的关键帧设置函数defadd_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)defadd_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)defadd_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)defadd_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)defadd_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)defadd_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]# 校验输入参数长度一致性iflen(subtitle_texts)!=len(audio_paths)orlen(audio_paths)!=len(media_paths):raiseValueError("字幕文本列表、音频路径列表、媒体路径列表长度必须一致")# 校验媒体路径有效性ifnotmedia_paths:raiseValueError("媒体路径列表不能为空")# 检查所有文件是否存在missing_files=[]formedia_pathinmedia_paths:ifnotos.path.exists(media_path):missing_files.append(f"媒体文件:{media_path}")foraudio_pathinaudio_paths:ifnotos.path.exists(audio_path):missing_files.append(f"音频:{audio_path}")ifbackground_imageandnotos.path.exists(background_image):missing_files.append(f"背景图片:{background_image}")ifbackground_musicandnotos.path.exists(background_music):missing_files.append(f"背景音乐:{background_music}")ifmissing_files:raiseFileNotFoundError(f"以下文件不存在:\n"+"\n".join(missing_files))# 初始化草稿文件夹draft_folder=draft.DraftFolder(draft_folder_path)# 检查草稿是否已存在ifdraft_folder.has_draft(draft_name):raiseFileExistsError(f"草稿 '{draft_name}' 已存在,不允许覆盖")# 判断媒体类型(图片/视频)并确定草稿尺寸all_images=Truefirst_video_path=None# 检查是否有视频文件forpathinmedia_paths:ifpath.lower().endswith(('.mp4','.mov','.avi','.mkv')):all_images=Falsefirst_video_path=pathbreak# 获取尺寸信息ifall_images:# 全是图片,使用第一张图片的尺寸try:withImage.open(media_paths[0])asimg:width,height=img.sizeexceptExceptionase:raiseRuntimeError(f"获取第一张图片尺寸失败:{str(e)}")else:# 至少有一个视频,使用第一个视频的尺寸first_video=draft.VideoMaterial(first_video_path)width,height=first_video.width,first_video.height# 根据横竖屏设置字幕参数ifwidth>height:# 横屏font_size=6.0subtitle_y=-0.8else:# 竖屏font_size=13.0subtitle_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,"字幕轨道")ifbackground_image:# 背景图片放到底层track_builder.add_track(TrackType.video,"背景图片轨道",relative_index=3)ifbackground_music:track_builder.add_track(TrackType.audio,"背景音乐轨道")current_time=0# 当前时间点(微秒)total_duration=0# 总时长(微秒)separators=r'([,。!,!?;])'# 处理每个片段(媒体、音频、字幕)foriinrange(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'))ifis_image:# 处理图片片段media_segment=draft.VideoSegment(media_path,time_range)# 是否添加运镜效果add_movement=add_image_movementelse:# 处理视频片段(根据音频时长调整速度)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)# 随机添加运镜效果ifadd_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)# 如果没有拆分出短句(无标点符号),使用原字幕ifnotsentences:sentences=[subtitle_text]# 计算每个短句的显示时间并添加current_sub_time=current_timeforsentenceinsentences:# 计算短句长度占比(避免除零错误)iftotal_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# 处理背景图片ifbackground_image:image_segment=draft.VideoSegment(background_image,trange(0,total_duration))script.add_segment(image_segment,"背景图片轨道")# 处理背景音乐ifbackground_music:music_material=draft.AudioMaterial(background_music)music_duration=music_material.duration current_music_time=0# 循环添加背景音乐直到达到总时长whilecurrent_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")exceptExceptionase:print(f"错误:{str(e)}")
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/13 1:00:35

YashanDB数据库的核心优势与应用场景分析

YashanDB是一种新兴的数据库解决方案&#xff0c;具有多种核心优势和应用场景。以下是对其核心优势及应用场景的分析&#xff1a;核心优势1. 高性能- YashanDB采用高效的数据存储和检索机制&#xff0c;能够在处理大规模数据时保持优异的性能&#xff0c;适合对实时性要求较高的…

作者头像 李华
网站建设 2026/3/12 21:00:54

YashanDB数据库的缓存机制及性能提升策略探究

YashanDB数据库的缓存机制及性能提升策略是一个重要的话题&#xff0c;尤其是在面对现代应用对高性能和低延迟的需求时。以下是关于YashanDB的一些缓存机制及其性能提升策略的探讨。一、缓存机制1. 内存缓存&#xff1a;- YashanDB可能使用内存作为主要的数据缓存层&#xff0c…

作者头像 李华
网站建设 2026/3/12 23:18:35

20、Swerve详细设计解析

Swerve详细设计解析 1. 连接与I/O操作 在进行网络连接操作时,连接对象可能会持续一段时间,并且可能会有进一步向连接写入数据的尝试。因此,所有的I/O函数在执行之前都会检查套接字是否仍然打开,以及是否没有出现中止条件。 当向套接字发送数据时,存在部分写入的风险。为…

作者头像 李华
网站建设 2026/3/13 0:20:27

21、节点系统的详细设计与实现

节点系统的详细设计与实现 在节点系统的设计中,存在诸多关键的技术点和实现细节,下面将详细介绍节点系统的设计与实现,包括通用节点和目录节点处理程序等方面。 1. 节点创建的依赖处理 在节点创建过程中,为了避免模块之间的循环依赖问题,采用了将工厂的创建函数传递给目…

作者头像 李华
网站建设 2026/3/12 23:18:33

22、服务器模块详细设计解析

服务器模块详细设计解析 1. 目录操作与 HTML 构建 目录列表的获取需要从文件描述符读取,这意味着它必须经过开放文件管理器,并且可能会因超时被中止。而 HTML 的构建则是使用 TextFrag 模块进行的复杂文本格式化操作。代码假设服务器中有一个 /icons 的 URL 路径用于获…

作者头像 李华
网站建设 2026/3/13 0:36:49

34、深入探索文件操作与系统IPC调试工具

深入探索文件操作与系统IPC调试工具 在日常的系统管理和开发工作中,我们经常需要处理各种文件和进程,以及调试系统的进程间通信(IPC)。本文将详细介绍一系列实用的命令行工具,帮助你更好地完成这些任务。 1. 处理打开文件的工具 1.1 lsof命令 lsof(list open files)…

作者头像 李华