import cv2
from moviepy.editor import VideoClip, ImageSequenceClip
from moviepy.video.fx.all import fadein, fadeout
from tqdm import tqdm
from functools import partial

# Paths
image_one_path = '/home/daddy/morph/one.jpeg'
image_two_path = '/home/daddy/morph/two.jpeg'
output_path = '/home/daddy/morph/output'
output_filename = f'tornado_morph{int(time.time())}.mp4'

# Input parameters with default values
duration1 = 3  # Duration of first image display
duration2 = 7  # Duration of tornado formation and movement
duration3 = 5  # Duration of second image display
particle_size = 8  # Size of each particle in pixels
fps = 30  # Frames per second

# Load images and resize to the same dimensions
img1 = Image.open(image_one_path).convert('RGBA').resize((1024, 1024))
img2 = Image.open(image_two_path).convert('RGBA').resize((1024, 1024))

# Convert images to NumPy arrays
img1_array = np.array(img1)
img2_array = np.array(img2)

# Get image dimensions
width, height = img1.size

# Generate particle positions and colors
particles = []

for y in range(0, height, particle_size):
    for x in range(0, width, particle_size):
        particle_color = img1_array[y:y+particle_size, x:x+particle_size].mean(axis=(0, 1))
        particles.append({
            'x': x,
            'y': y,
            'color': particle_color,
            'active': True  # Whether the particle is still visible
        })

# Tornado parameters
tornado_height = height * 0.5  # Half the image height
tornado_width_top = width * 0.2  # Width at the top
tornado_width_bottom = width * 0.05  # Width at the bottom
tornado_start_x = width * 0.2
tornado_start_y = height * 0.25  # Centered vertically

# Tornado path from left to right
tornado_path = {
    'start_x': tornado_start_x,
    'end_x': width - tornado_start_x,
    'y': tornado_start_y
}

def move_particles_to_tornado(particles, t, total_duration):
    """
    Move particles into a spiral towards the tornado center.
    """
    progress = t / total_duration  # Normalize time to [0, 1]
    new_particles = []
    for particle in particles:
        if not particle['active']:
            continue

        # Calculate distance to tornado center
        dx = tornado_start_x - particle['x']
        dy = tornado_start_y - particle['y']
        distance = np.hypot(dx, dy)

        # Spiral motion parameters
        angle = np.arctan2(dy, dx) + progress * 20  # Adjust the multiplier for spiral speed
        radius = distance * (1 - progress)  # Radius decreases over time

        # New position in spiral path
        new_x = tornado_start_x + radius * np.cos(angle)
        new_y = tornado_start_y + radius * np.sin(angle)

        # Update particle position
        particle['x'] = new_x
        particle['y'] = new_y

        # Mark particle as inactive if it reaches the center
        if radius < 5:
            particle['active'] = False

        new_particles.append(particle)
    return new_particles

def move_tornado_across_screen(t, total_duration):
    """
    Calculate tornado position moving from left to right.
    """
    progress = t / total_duration  # Normalize time to [0, 1]
    tornado_x = tornado_path['start_x'] + progress * (tornado_path['end_x'] - tornado_path['start_x'])
    return tornado_x

def make_frame(t):
    """
    Create each frame of the video at time t.
    """
    frame = Image.new('RGBA', (width, height))
    draw = ImageDraw.Draw(frame)

    # First phase: Display first image
    if t < duration1:
        frame.paste(img1, (0, 0))

    # Second phase: Particles move into tornado
    elif t < duration1 + duration2:
        progress = (t - duration1) / duration2
        # Move particles towards tornado
        active_particles = move_particles_to_tornado(particles, t - duration1, duration2)
        # Draw particles
        for particle in active_particles:
            if particle['active']:
                x = int(particle['x'])
                y = int(particle['y'])
                color = tuple(particle['color'].astype(int))
                draw.rectangle([x, y, x + particle_size, y + particle_size], fill=color)

        # Optional: Draw tornado center (for visualization)
        # draw.ellipse([tornado_start_x - 5, tornado_start_y - 5, tornado_start_x + 5, tornado_start_y + 5], fill='white')

    # Third phase: Tornado reveals second image
    else:
        progress = (t - duration1 - duration2) / duration3
        tornado_x = move_tornado_across_screen(t - duration1 - duration2, duration3)

        # Create a mask for the tornado path
        mask = Image.new('L', (width, height), 0)
        mask_draw = ImageDraw.Draw(mask)
        # Define tornado shape (cone) at current position
        top_width = tornado_width_top
        bottom_width = tornado_width_bottom
        tornado_height_pixels = int(tornado_height)
        polygon = [
            (tornado_x - top_width / 2, tornado_start_y),
            (tornado_x + top_width / 2, tornado_start_y),
            (tornado_x + bottom_width / 2, tornado_start_y + tornado_height_pixels),
            (tornado_x - bottom_width / 2, tornado_start_y + tornado_height_pixels)
        ]
        mask_draw.polygon(polygon, fill=255)

        # Composite images using the mask
        frame = Image.composite(img2, img1, mask)

        # Optional: Draw the tornado outline (for visualization)
        # draw = ImageDraw.Draw(frame)
        # draw.polygon(polygon, outline='white')

    # Convert frame to numpy array
    frame_array = np.array(frame.convert('RGB'))
    return frame_array

# Total duration
total_duration = duration1 + duration2 + duration3

# Create the video clip
video_clip = VideoClip(make_frame, duration=total_duration)

# Apply fade-in and fade-out
video_clip = fadein(video_clip, 1.5).fx(fadeout, 1.5)

# Write the video file
output_video_path = os.path.join(output_path, output_filename)
video_clip.write_videofile(output_video_path, fps=fps, codec='libx264')
What's missing?