123 lines
5.2 KiB
Python
123 lines
5.2 KiB
Python
import argparse
|
|
from PIL import Image
|
|
import numpy as np
|
|
from moviepy.editor import ImageClip, concatenate_videoclips
|
|
|
|
# Setup command line arguments
|
|
parser = argparse.ArgumentParser(
|
|
description='Create a video comparison of two images, scaling to a specified height while maintaining aspect ratio.')
|
|
parser.add_argument('image1_path', type=str, help='Path to the first image')
|
|
parser.add_argument('image2_path', type=str, help='Path to the second image')
|
|
parser.add_argument('output_video_path', type=str, help='Path where the output video will be saved')
|
|
parser.add_argument('--target_height', type=int, default=1080,
|
|
help='Target height of the output video in pixels (e.g., 640, 720, 1080). Aspect ratio is preserved.')
|
|
|
|
# The target_resolution variable will be derived from the target_height argument
|
|
args = parser.parse_args()
|
|
|
|
def resize_and_center_image(image1, image2, background_color=(255, 255, 255)):
|
|
"""
|
|
Resize image1 to fit within the resolution of image2 while maintaining its aspect ratio,
|
|
and paste it centered onto a canvas of the same resolution as image2 with a specified background color.
|
|
|
|
Args:
|
|
- image1 (PIL.Image): The first input image.
|
|
- image2 (PIL.Image): The second input image, which determines the canvas size.
|
|
- background_color (tuple): The background color for the canvas in RGB.
|
|
|
|
Returns:
|
|
- PIL.Image: The resulting combined image.
|
|
"""
|
|
# Create a canvas with the resolution of image2 and the specified background color
|
|
canvas = Image.new("RGB", image2.size, color=background_color)
|
|
|
|
# Calculate the new size for image1, maintaining its aspect ratio
|
|
img1_aspect = image1.width / image1.height
|
|
img2_aspect = image2.width / image2.height
|
|
|
|
if img1_aspect > img2_aspect:
|
|
# Image1 is wider than image2
|
|
new_width = image2.width
|
|
new_height = int(new_width / img1_aspect)
|
|
else:
|
|
# Image1 is taller than image2
|
|
new_height = image2.height
|
|
new_width = int(new_height * img1_aspect)
|
|
|
|
# Resize image1 to fit within the canvas while maintaining its aspect ratio
|
|
image1_resized = image1.resize((new_width, new_height), Image.LANCZOS)
|
|
|
|
# Calculate the position to paste the resized image1 on the canvas, centered
|
|
paste_position = ((image2.width - new_width) // 2, (image2.height - new_height) // 2)
|
|
|
|
# Paste the resized image1 onto the canvas
|
|
canvas.paste(image1_resized, paste_position)
|
|
|
|
return canvas
|
|
|
|
|
|
def resize_image_to_height(image, target_height):
|
|
# Calculate new dimensions
|
|
original_width, original_height = image.size
|
|
aspect_ratio = original_width / original_height
|
|
new_height = target_height
|
|
new_width = int(new_height * aspect_ratio)
|
|
return image.resize((new_width, new_height))
|
|
|
|
|
|
# Load and resize images
|
|
image1 = Image.open(args.image1_path)
|
|
image2 = Image.open(args.image2_path)
|
|
|
|
image1 = resize_and_center_image(image1, image2)
|
|
|
|
image1_resized = resize_image_to_height(image1, args.target_height)
|
|
image2_resized = resize_image_to_height(image2, args.target_height)
|
|
|
|
# Convert images to numpy arrays
|
|
image1_array = np.array(image1_resized)
|
|
image2_array = np.array(image2_resized)
|
|
|
|
# Parameters for animation
|
|
frame_rate = 30
|
|
video_duration = 30 # seconds, adjust if you want slower or faster animation
|
|
num_frames = frame_rate * video_duration
|
|
width = image1_resized.width
|
|
|
|
|
|
def make_frame(t):
|
|
cycle_duration = video_duration / 4 # Four phases: reveal 2nd, reveal 1st, reveal 2nd, reveal 1st
|
|
cycle_progress = (t % cycle_duration) / cycle_duration
|
|
cycle_number = int(t // cycle_duration)
|
|
|
|
if cycle_number == 1: # Move line from right to left to reveal the first image
|
|
divider_position = width - int(cycle_progress * width)
|
|
frame = np.copy(image2_array)
|
|
frame[:, divider_position:] = image1_array[:, divider_position:]
|
|
elif cycle_number == 2: # Start with the first image fully visible, then reveal the second
|
|
divider_position = int(cycle_progress * width)
|
|
frame = np.copy(image1_array)
|
|
frame[:, :divider_position] = image2_array[:, :divider_position]
|
|
elif cycle_number == 3: # Move line from right to left to reveal the first image again
|
|
divider_position = width - int(cycle_progress * width)
|
|
frame = np.copy(image2_array)
|
|
frame[:, divider_position:] = image1_array[:, divider_position:]
|
|
else: # Initial phase and default action: reveal the second image by moving right
|
|
divider_position = int(cycle_progress * width)
|
|
frame = np.copy(image1_array)
|
|
frame[:, :divider_position] = image2_array[:, :divider_position]
|
|
|
|
# Add a vertical line at the divider position. Adjust the color and width as needed.
|
|
line_color = [255, 0, 0] # Red line, change the RGB values as desired
|
|
line_width = 5 # Adjust the width of the line as needed
|
|
if 0 <= divider_position < width: # To ensure the line is only drawn within bounds
|
|
frame[:, divider_position:divider_position + line_width] = line_color
|
|
|
|
return frame
|
|
|
|
|
|
# Generate video
|
|
video_clips = [ImageClip(make_frame(t / frame_rate)).set_duration(1 / frame_rate) for t in range(num_frames)]
|
|
final_clip = concatenate_videoclips(video_clips, method="compose")
|
|
final_clip.write_videofile(args.output_video_path, fps=frame_rate, bitrate="3000k", codec="libx264")
|