make a configureable amount of time before the first image even appears (on top) and make a configureable amount of time before the timer starts the countdown

Code:
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from moviepy.editor import (
    TextClip,
    ColorClip,
    CompositeVideoClip,
    ImageClip,
)
from moviepy.config import change_settings
import numpy as np
from PIL import Image, ImageDraw
import os

# Configure ImageMagick (Ensure this path is correct for your system)
change_settings(
    {"IMAGEMAGICK_BINARY": r"C:\Program Files\ImageMagick-7.1.1-Q16-HDRI\magick.exe"}
)


class WouldYouRatherGenerator:
    def __init__(self):
        self.window = tk.Tk()
        self.window.title("Would You Rather Generator")
        self.window.geometry("800x600")  # Adjusted window size

        # Video dimensions (9:16 ratio)
        self.width = 1080
        self.height = 1920
        self.middle_y = self.height // 2

        # Store image paths
        self.image1_path = None  # Top image (Black socks)
        self.image2_path = None  # Bottom image (White socks)

        self.setup_ui()

    def setup_ui(self):
        main_frame = ttk.Frame(self.window, padding="20")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        # Configure grid columns for consistent alignment
        for i in range(4):
            main_frame.columnconfigure(i, weight=1, pad=5)

        # Option 1 Section (Top Half - Black Socks)
        option1_frame = ttk.LabelFrame(main_frame, text="Option 1 (Black Socks)", padding="10")
        option1_frame.grid(row=0, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=10)

        ttk.Label(option1_frame, text="Text:").grid(row=0, column=0, sticky=tk.W, pady=5)
        self.option1_text = ttk.Entry(option1_frame, width=50)
        self.option1_text.grid(row=0, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=5)
        self.option1_text.insert(0, "SCHWARZE SOCKEN")  # Default text

        ttk.Label(option1_frame, text="Image:").grid(row=1, column=0, sticky=tk.W, pady=5)
        self.image1_label = ttk.Label(option1_frame, text="No image selected")
        self.image1_label.grid(row=1, column=1, sticky=tk.W, pady=5)
        ttk.Button(
            option1_frame, text="Browse...", command=lambda: self.select_image(1)
        ).grid(row=1, column=2, padx=5, pady=5)

        ttk.Label(option1_frame, text="Percentage:").grid(row=2, column=0, sticky=tk.W, pady=5)
        self.percent1 = ttk.Entry(option1_frame, width=10)
        self.percent1.grid(row=2, column=1, sticky=tk.W, pady=5)
        self.percent1.insert(0, "1,02%")  # Default percentage

        # Option 2 Section (Bottom Half - White Socks)
        option2_frame = ttk.LabelFrame(main_frame, text="Option 2 (White Socks)", padding="10")
        option2_frame.grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=10)

        ttk.Label(option2_frame, text="Text:").grid(row=0, column=0, sticky=tk.W, pady=5)
        self.option2_text = ttk.Entry(option2_frame, width=50)
        self.option2_text.grid(row=0, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=5)
        self.option2_text.insert(0, "WEIßE SOCKEN")  # Default text

        ttk.Label(option2_frame, text="Image:").grid(row=1, column=0, sticky=tk.W, pady=5)
        self.image2_label = ttk.Label(option2_frame, text="No image selected")
        self.image2_label.grid(row=1, column=1, sticky=tk.W, pady=5)
        ttk.Button(
            option2_frame, text="Browse...", command=lambda: self.select_image(2)
        ).grid(row=1, column=2, padx=5, pady=5)

        ttk.Label(option2_frame, text="Percentage:").grid(row=2, column=0, sticky=tk.W, pady=5)
        self.percent2 = ttk.Entry(option2_frame, width=10)
        self.percent2.grid(row=2, column=1, sticky=tk.W, pady=5)
        self.percent2.insert(0, "999,72%")  # Default percentage

        # Timing Controls Section
        timing_frame = ttk.LabelFrame(main_frame, text="Timing Controls", padding="10")
        timing_frame.grid(row=2, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=10)

        ttk.Label(timing_frame, text="Initial Pause (seconds):").grid(
            row=0, column=0, sticky=tk.W, pady=5
        )
        self.initial_pause = ttk.Entry(timing_frame, width=10)
        self.initial_pause.grid(row=0, column=1, sticky=tk.W, pady=5)
        self.initial_pause.insert(0, "2")  # Default 2 seconds

        ttk.Label(timing_frame, text="Timer Delay (seconds):").grid(
            row=1, column=0, sticky=tk.W, pady=5
        )
        self.timer_delay = ttk.Entry(timing_frame, width=10)
        self.timer_delay.grid(row=1, column=1, sticky=tk.W, pady=5)
        self.timer_delay.insert(0, "1")  # Default 1 second

        ttk.Label(timing_frame, text="Countdown (seconds):").grid(
            row=2, column=0, sticky=tk.W, pady=5
        )
        self.countdown_duration = ttk.Entry(timing_frame, width=10)
        self.countdown_duration.grid(row=2, column=1, sticky=tk.W, pady=5)
        self.countdown_duration.insert(0, "5")  # Default 5 seconds

        # Generate Button
        generate_button = ttk.Button(
            main_frame, text="Generate Video", command=self.generate_video
        )
        generate_button.grid(row=3, column=0, columnspan=4, pady=20)

    def select_image(self, option_num):
        """
        Open a file dialog to select an image and update the corresponding label.
        """
        file_path = filedialog.askopenfilename(
            filetypes=[("Image files", "*.png *.jpg *.jpeg *.gif *.bmp")]
        )
        if file_path:
            if option_num == 1:
                self.image1_path = file_path
                self.image1_label.config(text=os.path.basename(file_path))
            elif option_num == 2:
                self.image2_path = file_path
                self.image2_label.config(text=os.path.basename(file_path))

    def process_image(self, image_path, size=(500, 500), start_time=0, duration=0):
        """
        Process the image by converting mode and resizing proportionally without warping.

        :param image_path: Path to the image file.
        :param size: Maximum width and height as a tuple. Increased to (500, 500).
        :param start_time: Time at which the image should appear in the video.
        :param duration: Duration of the image clip.
        :return: An ImageClip object.
        """
        img = Image.open(image_path)

        if img.mode != "RGBA":
            img = img.convert("RGBA")

        # Resize image proportionally if size is provided
        if size:
            img.thumbnail(size, Image.LANCZOS)  # In-place resizing

        img_array = np.array(img)

        clip_duration = duration if duration > 0 else self.total_duration

        clip = ImageClip(img_array).set_duration(clip_duration)

        if start_time > 0:
            clip = clip.set_start(start_time)

        return clip

    def create_styled_text(
        self, text, y_position, duration, start_time=0, fontsize=70, color="yellow",
        stroke_color="black", stroke_width=2
    ):
        """
        Create a styled TextClip.

        :param text: Text content.
        :param y_position: Vertical position of the text.
        :param duration: Duration for which the text is visible.
        :param start_time: Time at which the text appears.
        :param fontsize: Font size of the text.
        :param color: Text color.
        :param stroke_color: Stroke (outline) color.
        :param stroke_width: Width of the stroke.
        :return: A TextClip object.
        """
        text_clip = TextClip(
            text,
            font="Arial-Bold",
            color=color,
            fontsize=fontsize,
            method="label",  # Changed to 'label' for better positioning
            stroke_color=stroke_color,
            stroke_width=stroke_width,
        ).set_duration(duration)

        if start_time > 0:
            text_clip = text_clip.set_start(start_time)

        return text_clip.set_position(("center", y_position))

    def create_timer_clips(self, start_time, countdown_seconds, total_duration):
        """
        Create countdown timer clips from countdown_seconds to 0.

        :param start_time: The time at which the countdown starts.
        :param countdown_seconds: Number of seconds to count down from.
        :param total_duration: Total duration of the video.
        :return: A list of TextClip objects for the countdown.
        """
        timer_clips = []
        for i in range(countdown_seconds, -1, -1):  # Includes 0
            current_time = countdown_seconds - i
            timer_text = str(i)
            timer_duration = 1  # Each number shows for 1 second
            timer_clip = self.create_styled_text(
                timer_text,
                self.middle_y - 100,  # Positioned slightly above the center
                timer_duration,
                start_time + current_time,
                fontsize=150,
                color="white",
                stroke_color="black",
                stroke_width=3
            )
            timer_clips.append(timer_clip)
        return timer_clips

    def generate_video(self):
        """
        Generate the 'Would You Rather' video based on user inputs.
        """
        # Validate inputs
        if not self.image1_path or not self.image2_path:
            messagebox.showerror("Input Error", "Please select both images (Option 1 and Option 2).")
            return

        option1_text = self.option1_text.get().strip()
        option2_text = self.option2_text.get().strip()

        if not option1_text or not option2_text:
            messagebox.showerror(
                "Input Error", "Please enter text for both options."
            )
            return

        # Validate timing inputs
        try:
            initial_pause = float(self.initial_pause.get())
            timer_delay = float(self.timer_delay.get())
            countdown_seconds = int(self.countdown_duration.get())
        except ValueError:
            messagebox.showerror(
                "Input Error", "Please enter valid numeric values for timings."
            )
            return

        # Validate percentage inputs
        try:
            # Replace comma with dot for decimal conversion if used
            percent1_val = self.percent1.get().strip().replace(',', '.').replace('%', '')
            percent2_val = self.percent2.get().strip().replace(',', '.').replace('%', '')
            percent1 = float(percent1_val)
            percent2 = float(percent2_val)
        except ValueError:
            messagebox.showerror(
                "Input Error", "Please enter valid numeric values for percentages (e.g., 1.02%)."
            )
            return

        # Define percentage display duration and desired extra time
        percentage_duration = 3  # seconds (increased from 2)
        desired_extra_time = 0    # seconds (no extra buffer after percentages)

        # Calculate buffer_time as the sum of percentage_duration and desired_extra_time
        buffer_time = percentage_duration + desired_extra_time  # 3 + 0 = 3 seconds

        # Calculate total_duration
        self.total_duration = initial_pause + timer_delay + countdown_seconds + buffer_time  # e.g., 2 + 1 + 5 + 3 = 11 seconds

        # Create background clips
        red_clip = ColorClip((self.width, self.middle_y), color=(255, 0, 0)).set_duration(
            self.total_duration
        )
        blue_clip = ColorClip((self.width, self.middle_y), color=(0, 0, 255)).set_duration(
            self.total_duration
        )

        clips = [
            red_clip.set_position((0, 0)),
            blue_clip.set_position((0, self.middle_y)),
        ]

        # Fixed Positions

        # Top Half
        top_image_y = 250    # Moved further down from 200 to 250
        top_text_y = 650     # Moved up by 50 pixels from 700 to 650
        top_percentage_y = 750  # Moved up by 50 pixels from 800 to 750

        # Bottom Half
        bottom_image_y = self.middle_y + 350  # Moved further down from self.middle_y + 300 to +350
        bottom_text_y = 1200  # Fixed position for bottom text
        bottom_percentage_y = 1100  # Changed from 1300 to 1100 to place above the bottom text

        # Add top image (Black Socks)
        img1_clip = self.process_image(
            self.image1_path,
            size=(500, 500),       # Increased size to 500x500
            start_time=0,          # Starts at the beginning
            duration=self.total_duration
        )
        if img1_clip:
            clips.append(img1_clip.set_position(("center", top_image_y)))

        # Add top text
        option1_txt = self.create_styled_text(
            option1_text.upper(),
            y_position=top_text_y,
            duration=self.total_duration,
            start_time=0,          # Starts at the beginning
            fontsize=80,
            color="white",
            stroke_color="black",
            stroke_width=3
        )
        clips.append(option1_txt)

        # Add bottom image (White Socks) after initial_pause + timer_delay
        img2_clip = self.process_image(
            self.image2_path,
            size=(500, 500),       # Increased size to 500x500
            start_time=initial_pause + timer_delay,
            duration=self.total_duration - (initial_pause + timer_delay)
        )
        if img2_clip:
            clips.append(img2_clip.set_position(("center", bottom_image_y)).set_start(initial_pause + timer_delay))

        # Add bottom text below the bottom image after initial_pause + timer_delay
        option2_txt = self.create_styled_text(
            option2_text.upper(),
            y_position=bottom_text_y,
            duration=self.total_duration - (initial_pause + timer_delay),  # Duration until the end
            start_time=initial_pause + timer_delay,
            fontsize=80,
            color="white",
            stroke_color="black",
            stroke_width=3
        )
        clips.append(option2_txt)

        # Add dividing line
        line_thickness = 8
        black_line = ColorClip((self.width, line_thickness), color=(0, 0, 0)).set_duration(self.total_duration)
        black_line = black_line.set_position((0, self.middle_y - line_thickness // 2))
        clips.append(black_line)

        # Add small black circle in the middle of the dividing line
        circle_diameter = 50  # Adjust size as needed
        circle_size = (circle_diameter, circle_diameter)

        # Create a transparent image with a black circle
        circle_img = Image.new("RGBA", circle_size, (0, 0, 0, 0))
        draw = ImageDraw.Draw(circle_img)
        draw.ellipse((0, 0, circle_diameter, circle_diameter), fill=(0, 0, 0, 255))
        circle_array = np.array(circle_img)

        # Create ImageClip for the circle
        circle_clip = ImageClip(circle_array).set_duration(self.total_duration)
        # Position the circle in the middle of the dividing line
        circle_position = (self.width // 2 - circle_diameter // 2, self.middle_y - circle_diameter // 2)
        clips.append(circle_clip.set_position(circle_position))

        # Add countdown timer after the circle (to ensure it's above the circle)
        countdown_start_time = initial_pause + timer_delay
        timer_clips = self.create_timer_clips(countdown_start_time, countdown_seconds, self.total_duration)
        clips.extend(timer_clips)

        # Add Percentage 1 Text ("1.02%") in Yellow after countdown
        percent1_txt = self.create_styled_text(
            f"{percent1:.2f}%",
            y_position=top_percentage_y,
            duration=3,  # Display duration increased to 3 seconds
            start_time=initial_pause + timer_delay + countdown_seconds,
            fontsize=100,  # Increased font size
            color="yellow",
            stroke_color="black",
            stroke_width=2
        )
        clips.append(percent1_txt)

        # Add Percentage 2 Text ("999.72%") in Yellow above the bottom text after countdown
        percent2_txt = self.create_styled_text(
            f"{percent2:.2f}%",
            y_position=bottom_percentage_y,
            duration=3,  # Display duration increased to 3 seconds
            start_time=initial_pause + timer_delay + countdown_seconds,
            fontsize=100,  # Increased font size
            color="yellow",
            stroke_color="black",
            stroke_width=2
        )
        clips.append(percent2_txt)

        # No additional buffer_time; video will end after percentages are hidden

        # Create final video
        final_clip = CompositeVideoClip(clips, size=(self.width, self.height))

        # Render the video
        final_clip.write_videofile(
            "would_you_rather.mp4",
            fps=30,
            codec="libx264",
            audio=False,
            threads=4,
            preset="medium",
        )
        messagebox.showinfo("Success", "Video generated successfully!")

    def run(self):
        self.window.mainloop()


if __name__ == "__main__":
    app = WouldYouRatherGenerator()
    app.run()