# complete_script.pyimport osfrom moviepy import VideoFileClip, AudioFileClip, ImageClipdef mp4_to_mp3_to_mp4(mp4_path, image_path, final_video_path="final_output.mp4"): """ 一键处理:MP4 -> 提取音频 -> 与图片合成 -> 新MP4 """ print("="*30) print("开始处理...") print("="*30) # 1. 提取音频 mp3_path = os.path.splitext(mp4_path)[0] + ".mp3" try: print("[1/3] 提取音频...") with VideoFileClip(mp4_path) as video: audio = video.audio audio.write_audiofile(mp3_path, codec='mp3', bitrate='320k') print(f"✅ 音频提取完成: {mp3_path}") except Exception as e: print(f"❌ 音频提取失败: {e}") return # 2. 合成视频 try: print("\n[2/3] 合成视频...") with AudioFileClip(mp3_path) as audio_clip: duration = audio_clip.duration image_clip = ImageClip(image_path).with_duration(duration) final_clip = image_clip.with_audio(audio_clip) final_clip.write_videofile(final_video_path, fps=24, codec='libx264', audio_codec='aac') print(f"✅ 视频合成完成: {final_video_path}") except Exception as e: print(f"❌ 视频合成失败: {e}") return finally: # 3. 清理临时文件(可选) # 如果你不想保留中间的 MP3 文件,可以取消下一行的注释 # os.remove(mp3_path) print("\n[3/3] 清理完成") print("="*30) print("🎉 所有操作成功完成!") print("="*30)# 使用示例if __name__ == '__main__': # 请将下面的路径替换为你自己的文件路径 video = "我的视频.mp4" image = "我的图片.jpg" mp4_to_mp3_to_mp4(video, image)
# slideshow_transition_final.pyimport osfrom moviepy import VideoFileClip, AudioFileClip, ImageClip, concatenate_videoclips, vfx, CompositeVideoClip, ColorClipdef extract_audio_from_mp4(mp4_path, mp3_path=None): if mp3_path is None: mp3_path = os.path.splitext(mp4_path)[0] + ".mp3" print(f"[1/3] 正在从视频提取音频: {mp4_path}") try: with VideoFileClip(mp4_path) as video: if video.audio is None: print("❌ 视频中没有音频轨道") return None video.audio.write_audiofile(mp3_path, codec='mp3', bitrate='320k') print(f"✅ 音频提取成功: {mp3_path}") return mp3_path except Exception as e: print(f"❌ 音频提取失败: {e}") return Nonedef resize_image_keep_aspect(img_clip, target_size, bg_color=(0, 0, 0)): w, h = img_clip.size target_w, target_h = target_size scale = min(target_w / w, target_h / h) new_w = int(w * scale) new_h = int(h * scale) img_resized = img_clip.resized(new_size=(new_w, new_h)) bg_clip = ColorClip(size=target_size, color=bg_color, duration=img_clip.duration) # 新版 moviepy 正确居中写法 x_pos = (target_w - new_w) // 2 y_pos = (target_h - new_h) // 2 final_clip = CompositeVideoClip( [bg_clip, img_resized.with_position((x_pos, y_pos))] ) return final_clipdef create_slideshow_with_transitions(image_paths, output_video_path, audio_path, duration_per_image=3, transition_duration=0.5, target_size=(1920, 1080), bg_color=(0, 0, 0)): print(f"\n[2/3] 开始创建带转场效果的幻灯片,共 {len(image_paths)} 张图片") print(f" 每张图片展示 {duration_per_image} 秒,转场时长 {transition_duration} 秒") print(f" 目标分辨率: {target_size},背景色: {bg_color}") try: with AudioFileClip(audio_path) as audio_clip: total_audio_duration = audio_clip.duration clips = [] for i, img_path in enumerate(image_paths): if not os.path.exists(img_path): print(f"⚠️ 警告: 图片不存在,跳过 {img_path}") continue img_clip = ImageClip(img_path).with_duration(duration_per_image) img_clip = resize_image_keep_aspect(img_clip, target_size, bg_color) fade_in_duration = min(transition_duration, duration_per_image / 2) fade_out_duration = min(transition_duration, duration_per_image / 2) img_clip = img_clip.with_effects([ vfx.FadeIn(fade_in_duration), vfx.FadeOut(fade_out_duration) ]) clips.append(img_clip) print(f" 已加载图片 {i+1}: {os.path.basename(img_path)}") if not clips: print("❌ 没有有效图片") return None video_clip = concatenate_videoclips(clips, method="compose") if video_clip.duration > total_audio_duration: video_clip = video_clip.subclipped(0, total_audio_duration) elif video_clip.duration < total_audio_duration: loops_needed = int(total_audio_duration / video_clip.duration) + 1 looped_clips = [video_clip] * loops_needed video_clip = concatenate_videoclips(looped_clips, method="compose") video_clip = video_clip.subclipped(0, total_audio_duration) video_clip = video_clip.with_audio(audio_clip) print(f"\n[3/3] 正在渲染最终视频...") video_clip.write_videofile( output_video_path, codec='libx264', audio_codec='aac', fps=24, threads=4 ) print(f"✅ 视频生成成功: {output_video_path}") return output_video_path except Exception as e: print(f"❌ 视频合成失败: {e}") return None# --- 使用示例 ---if __name__ == '__main__': input_mp4 = "input.mp4" output_video = "output_slideshow_final.mp4" image_folder = "images" audio_file = extract_audio_from_mp4(input_mp4) if audio_file is None: print("程序终止,音频提取失败") exit() image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff') image_files = [] if os.path.isdir(image_folder): for file in sorted(os.listdir(image_folder)): if file.lower().endswith(image_extensions): image_files.append(os.path.join(image_folder, file)) create_slideshow_with_transitions( image_paths=image_files, output_video_path=output_video, audio_path=audio_file, duration_per_image=3, transition_duration=0.5, target_size=(1920, 1080), bg_color=(0, 0, 0) )