| import subprocess |
| import os |
|
|
| def generate_background(images, output_path): |
|
|
| if not images: |
| raise Exception("No images uploaded") |
|
|
| temp_videos = [] |
|
|
| slide_duration = 15 |
| fade_duration = 1 |
|
|
| total_slide_duration = slide_duration + fade_duration |
|
|
| fps = 30 |
| total_frames = int(total_slide_duration * fps) |
|
|
| |
| |
| |
|
|
| for i, img in enumerate(images): |
|
|
| temp_video = f"/tmp/slide_{i}.mp4" |
| temp_videos.append(temp_video) |
|
|
| vf = ( |
| "scale=3840:2160:force_original_aspect_ratio=increase," |
| "crop=3840:2160," |
| f"zoompan=" |
| f"z='1+0.30*on/{total_frames}':" |
| f"x='(iw-iw/zoom)/2':" |
| f"y='(ih-ih/zoom)/2':" |
| f"d={total_frames}:" |
| "s=3840x2160:" |
| f"fps={fps}," |
| "scale=1920:1080:flags=lanczos" |
| ) |
|
|
| cmd = [ |
| "ffmpeg", |
| "-y", |
| "-loop", "1", |
| "-i", img, |
|
|
| "-vf", vf, |
|
|
| "-t", str(total_slide_duration), |
|
|
| "-c:v", "libx264", |
| "-preset", "ultrafast", |
| "-crf", "22", |
| "-pix_fmt", "yuv420p", |
|
|
| temp_video |
| ] |
|
|
| print(f"Generating slide {i+1}/{len(images)}") |
|
|
| subprocess.run(cmd, check=True) |
|
|
| |
| |
| |
|
|
| if len(temp_videos) == 1: |
| os.replace(temp_videos[0], output_path) |
| return |
|
|
| |
| |
| |
|
|
| print("Combining slides...") |
|
|
| cmd_concat = ["ffmpeg", "-y"] |
|
|
| for v in temp_videos: |
| cmd_concat.extend(["-i", v]) |
|
|
| filter_complex = "" |
|
|
| current = "[0:v]" |
| offset = slide_duration |
|
|
| for i in range(1, len(temp_videos)): |
|
|
| next_input = f"[{i}:v]" |
|
|
| if i == len(temp_videos) - 1: |
| out = "" |
| else: |
| out = f"[v{i}]" |
|
|
| filter_complex += ( |
| f"{current}{next_input}" |
| f"xfade=" |
| f"transition=fade:" |
| f"duration={fade_duration}:" |
| f"offset={offset}" |
| f"{out};" |
| ) |
|
|
| current = f"[v{i}]" |
| offset += slide_duration |
|
|
| filter_complex = filter_complex.rstrip(";") |
|
|
| cmd_concat.extend([ |
| "-filter_complex", filter_complex, |
|
|
| "-c:v", "libx264", |
| "-preset", "ultrafast", |
| "-crf", "22", |
| "-pix_fmt", "yuv420p", |
|
|
| output_path |
| ]) |
|
|
| subprocess.run(cmd_concat, check=True) |
|
|
| |
| |
| |
|
|
| probe = subprocess.run( |
| [ |
| "ffprobe", |
| "-v", "error", |
| "-show_entries", |
| "format=duration", |
| "-of", |
| "default=noprint_wrappers=1:nokey=1", |
| output_path |
| ], |
| capture_output=True, |
| text=True |
| ) |
|
|
| print("\nSUCCESS") |
| print(f"Duration: {probe.stdout.strip()} sec") |
|
|
| |
| |
| |
|
|
| for v in temp_videos: |
| if os.path.exists(v): |
| os.remove(v) |