please make the image a bit smaller and the percentage text should also be smaller. and please move the top image more up. the bottom image should stay the same position, but the top image should be moved more up. 


# app.py

import os
import sys
from pathlib import Path

# Import functions from existing scripts
from text_generation import generate_would_you_rather, summarize_text
from image_generation import ComfyUIGenerator
from voice_generation import generate_tts
from moviepy.editor import AudioFileClip

# Import the video generator class
from video_generation1 import WouldYouRatherGenerator  # Ensure the file is named correctly

def debug_print(message):
    print(f"[DEBUG] {message}")

def parse_would_you_rather_text(file_path):
    """
    Parses the 'would_you_rather.txt' file to extract choices and image descriptions.
    Designed to handle minor deviations in formatting.
    
    Expected block structure for each pair:
    1.
    CHOICE: First choice description
    IMAGE: First image description
    VERSUS
    CHOICE: Second choice description
    IMAGE: Second image description
    """
    pairs = []
    with open(file_path, 'r', encoding='utf-8') as f:
        lines = [line.strip() for line in f if line.strip()]  # Remove empty lines

    i = 0
    total_lines = len(lines)
    pair_number = 1
    while i < total_lines:
        line = lines[i]
        if line.endswith('.'):
            debug_print(f"Parsing pair {pair_number} starting at line {i+1}")
            pair = {}
            j = i + 1  # Start parsing after the number line

            # Initialize flags to check if essential components are found
            choice1_found = False
            image1_found = False
            versus_found = False
            choice2_found = False
            image2_found = False

            while j < total_lines and not (lines[j].endswith('.') and lines[j].rstrip('.').isdigit()):
                current_line = lines[j].upper()
                if current_line.startswith('CHOICE:'):
                    if not choice1_found:
                        pair['option1'] = lines[j][len('CHOICE:'):].strip()
                        choice1_found = True
                        debug_print(f"Pair {pair_number} - Option 1: {pair['option1']}")
                    elif not choice2_found:
                        pair['option2'] = lines[j][len('CHOICE:'):].strip()
                        choice2_found = True
                        debug_print(f"Pair {pair_number} - Option 2: {pair['option2']}")
                elif current_line.startswith('IMAGE:'):
                    if choice1_found and not image1_found:
                        pair['image1_desc'] = lines[j][len('IMAGE:'):].strip()
                        image1_found = True
                        debug_print(f"Pair {pair_number} - Image 1: {pair['image1_desc']}")
                    elif choice2_found and not image2_found:
                        pair['image2_desc'] = lines[j][len('IMAGE:'):].strip()
                        image2_found = True
                        debug_print(f"Pair {pair_number} - Image 2: {pair['image2_desc']}")
                elif current_line == 'VERSUS':
                    versus_found = True
                    debug_print(f"Pair {pair_number} - Versus found")
                else:
                    debug_print(f"Pair {pair_number} - Unrecognized line: '{lines[j]}'")
                j += 1

            # Validate if all components are found
            if choice1_found and image1_found and versus_found and choice2_found and image2_found:
                pairs.append(pair)
                debug_print(f"Successfully parsed pair {pair_number}")
                pair_number += 1
            else:
                missing = []
                if not choice1_found:
                    missing.append("Option 1 Choice")
                if not image1_found:
                    missing.append("Option 1 Image")
                if not versus_found:
                    missing.append("'VERSUS'")
                if not choice2_found:
                    missing.append("Option 2 Choice")
                if not image2_found:
                    missing.append("Option 2 Image")
                debug_print(f"Pair {pair_number} incomplete. Missing: {', '.join(missing)}. Skipping this pair.")
            i = j  # Move to the next pair
        else:
            debug_print(f"Unexpected line format at line {i+1}: '{line}'. Skipping.")
            i += 1
    return pairs

def main():
    debug_print("Starting Would You Rather Generator Process")

    # Step 1: Generate 'Would You Rather' text
    debug_print("Generating Would You Rather pairs...")
    generated_text = generate_would_you_rather()
    if not generated_text:
        debug_print("Failed to generate Would You Rather pairs.")
        sys.exit(1)
    
    # Define the project output directories
    project_output_dir = Path('generated_assets')
    images_dir = project_output_dir / 'images'
    audio_dir = project_output_dir / 'audio'
    
    # Ensure directories exist
    images_dir.mkdir(parents=True, exist_ok=True)
    audio_dir.mkdir(parents=True, exist_ok=True)
    
    # Save generated text to file
    wr_file = project_output_dir / 'would_you_rather.txt'
    with open(wr_file, 'w', encoding='utf-8') as f:
        f.write(generated_text)
    debug_print(f"Generated text saved to {wr_file.resolve()}")

    # Step 2: Parse the generated text
    pairs = parse_would_you_rather_text(wr_file)
    debug_print(f"Parsed {len(pairs)} Would You Rather pairs.")

    if not pairs:
        debug_print("No valid pairs found. Exiting.")
        sys.exit(1)

    # Initialize image generator
    image_generator = ComfyUIGenerator()

    # Initialize list to hold all generated assets
    asset_list = []

    # Step 3: Generate images and audio for each pair
    for idx, pair in enumerate(pairs, start=1):
        debug_print(f"Processing pair {idx}: {pair['option1']} vs {pair['option2']}")

        # Generate images for Option 1
        debug_print(f"Generating image for Option 1: {pair['image1_desc']}")
        image1_paths = image_generator.generate_image(
            workflow_path="workflow.json", 
            prompt_text=pair['image1_desc'],
            seed=random.randint(100000000, 999999999),  # Generate a unique seed
            node_id='CLIPTextEncode',
            index=0
        )
        if not image1_paths:
            debug_print(f"Failed to generate image for Option 1: {pair['option1']}")
            continue
        # Assuming one image per prompt
        img1_filename = Path(image1_paths[0]).name
        img1_path = images_dir / img1_filename
        debug_print(f"Image for Option 1 saved as {img1_path}")

        # Generate images for Option 2
        debug_print(f"Generating image for Option 2: {pair['image2_desc']}")
        image2_paths = image_generator.generate_image(
            workflow_path="workflow.json", 
            prompt_text=pair['image2_desc'],
            seed=random.randint(100000000, 999999999),  # Generate a unique seed
            node_id='CLIPTextEncode',
            index=0
        )
        if not image2_paths:
            debug_print(f"Failed to generate image for Option 2: {pair['option2']}")
            continue
        # Assuming one image per prompt
        img2_filename = Path(image2_paths[0]).name
        img2_path = images_dir / img2_filename
        debug_print(f"Image for Option 2 saved as {img2_path}")

        # Generate summaries for both options
        summary1 = summarize_text(pair['option1'])
        summary2 = summarize_text(pair['option2'])
        debug_print(f"Summary for Option 1: {summary1}")
        debug_print(f"Summary for Option 2: {summary2}")

        # Generate TTS audio for Option 1 with summary
        audio1_text = pair['option1']
        debug_print(f"Generating audio for Option 1: {audio1_text}")
        audio1_path = generate_tts(text=audio1_text, output_dir=str(audio_dir))
        if not audio1_path:
            debug_print(f"Failed to generate audio for Option 1: {audio1_text}")
            continue
        debug_print(f"Audio for Option 1 saved as {audio1_path}")

        # Generate TTS audio for Option 2 with summary
        audio2_text = pair['option2']
        debug_print(f"Generating audio for Option 2: {audio2_text}")
        audio2_path = generate_tts(text=audio2_text, output_dir=str(audio_dir))
        if not audio2_path:
            debug_print(f"Failed to generate audio for Option 2: {audio2_text}")
            continue
        debug_print(f"Audio for Option 2 saved as {audio2_path}")

        # Append to asset list
        asset = {
            'option1_text': summary1,   # Use summary for text in video
            'option2_text': summary2,
            'image1_path': str(img1_path.resolve()),
            'image2_path': str(img2_path.resolve()),
            'audio1_path': audio1_path,
            'audio2_path': audio2_path
        }
        asset_list.append(asset)

    if not asset_list:
        debug_print("No assets were generated. Exiting.")
        sys.exit(1)

    # Step 4: Assemble all assets into videos
    debug_print("Assembling videos with generated assets...")

    # Initialize the video generator
    video_generator = WouldYouRatherGenerator()

    for idx, asset in enumerate(asset_list, start=1):
        # Define timings (customize as needed)
        initial_pause = 0  # No initial pause since audio1 starts immediately
        timer_delay = 1     # 1 second after audio1 for starting audio2
        countdown_wait = 1  # 1 second after audio2 before countdown

        # Load audio clips to determine durations
        debug_print(f"Loading audio files for pair {idx}")
        try:
            audio1_clip = AudioFileClip(asset['audio1_path'])
            audio2_clip = AudioFileClip(asset['audio2_path'])
        except Exception as e:
            debug_print(f"Error loading audio files for pair {idx}: {str(e)}")
            continue

        # Calculate durations based on audio lengths
        audio1_duration = audio1_clip.duration
        audio2_duration = audio2_clip.duration

        # Define countdown_seconds based on the actual requirement (fixed to 5 seconds)
        countdown_seconds = 5

        # Define percentages (you can implement logic to set these dynamically)
        percent1 = "50%"  # Placeholder value
        percent2 = "50%"  # Placeholder value

        # Generate unique output filename
        output_filename = project_output_dir / f"would_you_rather_pair_{idx}.mp4"

        debug_print(f"Generating video for pair {idx}: {asset['option1_text']} vs {asset['option2_text']}")
        video_generator.generate_video(
            option1_text=asset['option1_text'],
            option2_text=asset['option2_text'],
            image1_path=asset['image1_path'],
            image2_path=asset['image2_path'],
            audio1_path=asset['audio1_path'],
            audio2_path=asset['audio2_path'],
            percent1=percent1,
            percent2=percent2,
            initial_pause=initial_pause,
            timer_delay=timer_delay,
            countdown_wait=countdown_wait,
            countdown_seconds=countdown_seconds,
            output_filename=str(output_filename)
        )
        debug_print(f"Video for pair {idx} generated successfully: {output_filename}")

    debug_print("All videos have been generated successfully.")

if __name__ == "__main__":
    import random  # Ensure random is imported
    main()
	
	



# video_generation.py

import os
from moviepy.editor import (
    TextClip,
    ColorClip,
    CompositeVideoClip,
    ImageClip,
    AudioFileClip,
    CompositeAudioClip
)
from moviepy.config import change_settings
import numpy as np
from PIL import Image, ImageDraw

# 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):
        # Initialize video parameters
        self.width = 1080
        self.height = 1920
        self.middle_y = self.height // 2

    def process_image(self, image_path, size=(1000, 1000), start_time=0, duration=0):
        """
        Process the image by converting mode and resizing proportionally without warping.
        """
        debug_print = lambda msg: print(f"[DEBUG] {msg}")
        debug_print(f"Processing image: {image_path}")

        if not os.path.exists(image_path):
            debug_print(f"Image file does not exist: {image_path}")
            return None

        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)

        debug_print(f"Image processed: {image_path}")
        return clip

    def create_styled_text(
        self, text, y_position, duration, start_time=0, fontsize=60, color="yellow",
        stroke_color="black", stroke_width=2, max_width=1000
    ):
        """
        Create a styled TextClip with automatic text wrapping into multiple lines.
        """
        debug_print = lambda msg: print(f"[DEBUG] {msg}")

        debug_print(f"Creating styled text: {text} at position {y_position}")

        # Use 'caption' method for automatic text wrapping
        text_clip = TextClip(
            text,
            font="Arial-Bold",
            color=color,
            fontsize=fontsize,
            method="caption",        # Changed to 'caption' for automatic wrapping
            stroke_color=stroke_color,
            stroke_width=stroke_width,
            size=(max_width, None)   # Set maximum width; height is automatic
        ).set_duration(duration)

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

        debug_print(f"Styled text created: {text}")
        return text_clip.set_position(("center", y_position))

    def create_countdown_text(
        self, text, y_position, duration, start_time=0, fontsize=150, color="yellow",
        stroke_color="black", stroke_width=3, max_width=300
    ):
        """
        Create a styled TextClip specifically for countdown and percentages.
        """
        debug_print = lambda msg: print(f"[DEBUG] {msg}")

        debug_print(f"Creating countdown/percentage text: {text} at position {y_position}")

        # Use 'label' method for single-line text
        text_clip = TextClip(
            text,
            font="Arial-Bold",
            color=color,
            fontsize=fontsize,
            method="label",          # 'label' to prevent multi-line
            stroke_color=stroke_color,
            stroke_width=stroke_width,
            align='center'
        ).set_duration(duration)

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

        debug_print(f"Countdown/percentage text created: {text}")
        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 1 (excluding 0).
        """
        timer_clips = []
        for i in range(countdown_seconds, 0, -1):  # Excludes 0
            current_time = countdown_seconds - i
            timer_text = str(i)
            timer_duration = 1  # Each number shows for 1 second
            timer_clip = self.create_countdown_text(
                timer_text,
                self.middle_y - 150,  # Positioned slightly above the center
                timer_duration,
                start_time + current_time,
                fontsize=150,          # Larger fontsize for countdown
                color="white",
                stroke_color="black",
                stroke_width=3,
                max_width=300
            )
            timer_clips.append(timer_clip)
        return timer_clips

    def generate_video(
        self,
        option1_text,
        option2_text,
        image1_path,
        image2_path,
        audio1_path,
        audio2_path,
        percent1="50%",
        percent2="50%",
        initial_pause=0,      # Set to 0 since audio1 starts immediately
        timer_delay=1,        # 1 second after audio1 for starting audio2
        countdown_wait=1,     # 1 second after audio2 before countdown
        countdown_seconds=5,  # 5-second countdown
        output_filename="would_you_rather.mp4"
    ):
        """
        Generate the 'Would You Rather' video based on provided parameters.
        """
        debug_print = lambda msg: print(f"[DEBUG] {msg}")

        debug_print(f"Starting video generation: {output_filename}")

        # Load audio clips
        try:
            audio1_clip = AudioFileClip(audio1_path)
            audio2_clip = AudioFileClip(audio2_path)
            debug_print(f"Loaded audio1: {audio1_path} (Duration: {audio1_clip.duration:.2f}s)")
            debug_print(f"Loaded audio2: {audio2_path} (Duration: {audio2_clip.duration:.2f}s)")
        except Exception as e:
            debug_print(f"Error loading audio files: {str(e)}")
            return

        # Calculate start times
        audio1_start = initial_pause  # 0
        audio2_start = audio1_start + audio1_clip.duration + timer_delay
        countdown_start_time = audio2_start + audio2_clip.duration + countdown_wait

        # Total video duration: countdown_start_time + countdown_seconds + 1s buffer
        total_duration = countdown_start_time + countdown_seconds + 1
        debug_print(f"Total video duration set to: {total_duration:.2f}s")

        # Update total_duration in the class for use in clip durations
        self.total_duration = total_duration

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

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

        # Fixed Positions

        # Top Half
        top_image_y = 100    # Positioned higher up (previously 150)
        top_text_y = 500     # Positioned below the image
        top_percentage_y = 600  # Positioned below the text

        # Bottom Half
        bottom_image_y = self.middle_y + 450  # Increased from 350 to 450
        bottom_text_y = 1300  # Positioned below the image
        bottom_percentage_y = 1200  # Positioned above the bottom text

        # Add top image (Option 1) with audio1_start
        img1_clip = self.process_image(
            image1_path,
            size=(1000, 1000),       # Increased size to 1000x1000
            start_time=audio1_start,          # Starts at 0
            duration=self.total_duration - audio1_start  # Adjust duration accordingly
        )
        if img1_clip:
            clips.append(img1_clip.set_position(("center", top_image_y)))
        else:
            debug_print("Failed to add Option 1 image clip.")

        # Add top text with audio1_start
        option1_txt = self.create_styled_text(
            option1_text,
            y_position=top_text_y,
            duration=self.total_duration - audio1_start,  # Duration until end
            start_time=audio1_start,          # Starts at 0
            fontsize=50,  # Increased font size from 60 to 50
            color="white",
            stroke_color="black",
            stroke_width=2,
            max_width=1000  # Adjust max_width as needed
        )
        clips.append(option1_txt)

        # Add bottom image (Option 2) with audio2_start
        img2_clip = self.process_image(
            image2_path,
            size=(1000, 1000),       # Increased size to 1000x1000
            start_time=audio2_start,
            duration=self.total_duration - audio2_start  # Adjust duration accordingly
        )
        if img2_clip:
            clips.append(img2_clip.set_position(("center", bottom_image_y)).set_start(audio2_start))
        else:
            debug_print("Failed to add Option 2 image clip.")

        # Add bottom text with audio2_start
        option2_txt = self.create_styled_text(
            option2_text,
            y_position=bottom_text_y,
            duration=self.total_duration - audio2_start,  # Duration until end
            start_time=audio2_start,
            fontsize=50,  # Increased font size from 60 to 50
            color="white",
            stroke_color="black",
            stroke_width=2,
            max_width=1000  # Adjust max_width as needed
        )
        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 countdown_start_time
        timer_clips = self.create_timer_clips(countdown_start_time, countdown_seconds, self.total_duration)
        clips.extend(timer_clips)

        # Add Percentage 1 Text ("50%") in Yellow after countdown
        percent1_txt = self.create_countdown_text(
            f"{percent1}",
            y_position=top_percentage_y,
            duration=3,  # seconds
            start_time=countdown_start_time + countdown_seconds,
            fontsize=150,  # Increased font size from 100 to 150
            color="yellow",
            stroke_color="black",
            stroke_width=2,
            max_width=300  # Adjust max_width as needed
        )
        clips.append(percent1_txt)

        # Add Percentage 2 Text ("50%") in Yellow above the bottom text after countdown
        percent2_txt = self.create_countdown_text(
            f"{percent2}",
            y_position=bottom_percentage_y,
            duration=3,  # seconds
            start_time=countdown_start_time + countdown_seconds,
            fontsize=150,  # Increased font size from 100 to 150
            color="yellow",
            stroke_color="black",
            stroke_width=2,
            max_width=300  # Adjust max_width as needed
        )
        clips.append(percent2_txt)

        # Combine all visual clips
        debug_print("Creating CompositeVideoClip...")
        final_clip = CompositeVideoClip(clips, size=(self.width, self.height))
        debug_print("CompositeVideoClip created.")

        # Combine the audio clips sequentially
        debug_print("Combining audio tracks...")
        try:
            # Position audio1 at audio1_start (0)
            audio1 = audio1_clip.set_start(audio1_start)

            # Position audio2 at audio2_start
            audio2 = audio2_clip.set_start(audio2_start)

            # Create CompositeAudioClip
            composite_audio = CompositeAudioClip([audio1, audio2])

            # Assign the combined audio to the final video
            final_clip = final_clip.set_audio(composite_audio)
            debug_print("Audio tracks combined successfully.")
        except Exception as e:
            debug_print(f"Error combining audio tracks: {str(e)}")

        # Render the video
        debug_print(f"Rendering video to {output_filename}...")
        final_clip.write_videofile(
            output_filename,
            fps=30,
            codec="libx264",
            audio_codec="aac",
            threads=4,
            preset="medium",
        )
        debug_print(f"Video generated and saved as {output_filename}")