I have a issue where it doesnt add the clock sounds to the video and I get this error:
[DEBUG] Creating final composite...
[DEBUG] Adding audio tracks...
[DEBUG] Error adding clock sound effect: 'module' object is not callable
[DEBUG] Added bling sound effect at 14.32s
[DEBUG] Rendering final video to generated_assets\videos\would_you_rather_pair_4.mp4...



here is the code for app.py:
# app.py

import os
import sys
import random
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,
    concatenate_videoclips,
    VideoFileClip  # Ensure VideoFileClip is imported
)

# Import the video generator class
from video_generation4 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'
    videos_dir = project_output_dir / 'videos'
    final_video_dir = project_output_dir / 'final_video'
    sounds_dir = project_output_dir / 'sounds'  # Directory for sound effects
    
    # Ensure directories exist
    images_dir.mkdir(parents=True, exist_ok=True)
    audio_dir.mkdir(parents=True, exist_ok=True)
    videos_dir.mkdir(parents=True, exist_ok=True)
    final_video_dir.mkdir(parents=True, exist_ok=True)
    sounds_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()}")

    # Paths to sound effects
    clock_sound_path = sounds_dir / 'clock.mp3'
    bling_sound_path = sounds_dir / 'bling.mp3'
    
    # Verify that sound effects exist
    if not clock_sound_path.exists():
        debug_print(f"Error: '{clock_sound_path}' does not exist. Please ensure the file is present.")
        sys.exit(1)
    if not bling_sound_path.exists():
        debug_print(f"Error: '{bling_sound_path}' does not exist. Please ensure the file is present.")
        sys.exit(1)

    # 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 = "or " + 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 individual videos
    debug_print("Assembling individual 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 timings based on audio lengths
        audio1_duration = audio1_clip.duration
        audio2_duration = audio2_clip.duration

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

        # Generate random percentages
        percent1_num = random.randint(0, 100)
        percent2_num = 100 - percent1_num

        # Turn the percentage int into a string and add % sign
        percent1 = f"{percent1_num}%"
        percent2 = f"{percent2_num}%"

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

        # Paths to sound effects (already verified existence in app.py)
        clock_sound_path = Path('generated_assets/sounds/clock.mp3').resolve()
        bling_sound_path = Path('generated_assets/sounds/bling.mp3').resolve()

        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),
            clock_sound_path=str(clock_sound_path),  # Pass clock sound path
            bling_sound_path=str(bling_sound_path)   # Pass bling sound path
        )
        debug_print(f"Video for pair {idx} generated successfully: {output_filename}")

        # Close audio clips to release resources
        audio1_clip.close()
        audio2_clip.close()

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

    # Step 5: Combine all individual videos into a final video
    debug_print("Combining all individual videos into a final video...")

    try:
        video_files = sorted(videos_dir.glob("would_you_rather_pair_*.mp4"))
        if not video_files:
            debug_print("No video files found to combine.")
            sys.exit(1)
        
        video_clips = [VideoFileClip(str(video)) for video in video_files]
        final_video = concatenate_videoclips(video_clips, method="compose")
        
        # Define the final output path
        final_output_path = final_video_dir / "would_you_rather_final.mp4"
        
        # Write the final video file
        final_video.write_videofile(
            str(final_output_path),
            fps=30,
            codec="libx264",
            audio_codec="aac",
            threads=4,
            preset="medium",
            remove_temp=True,
            logger=None  # Suppress MoviePy logs if desired
        )
        debug_print(f"Final combined video saved as: {final_output_path}")

        # Close video clips to release resources
        for clip in video_clips:
            clip.close()
        final_video.close()

    except Exception as e:
        debug_print(f"Failed to combine videos: {str(e)}")
        sys.exit(1)
    
    debug_print("All videos have been combined into the final video successfully.")

if __name__ == "__main__":
    main()
	
	
	
	
	



and here is the code for the video_generation4.py:
# video_generation.py

import os
from moviepy.editor import (
    TextClip,
    ColorClip,
    CompositeVideoClip,
    ImageClip,
    AudioFileClip,
    CompositeAudioClip
)
from moviepy.audio.fx import audio_loop  # Correctly import the audio_loop function
from moviepy.config import change_settings
import numpy as np
from PIL import Image, ImageDraw

# Configure ImageMagick
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=(600, 600), 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:
            img.thumbnail(size, Image.LANCZOS)

        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="white",
        stroke_color="black", stroke_width=3, max_width=800
    ):
        """
        Create a styled TextClip with automatic text wrapping based on maximum width.
        
        Parameters:
        - max_width: The maximum width of the text box before wrapping.
        """
        debug_print = lambda msg: print(f"[DEBUG] {msg}")
        debug_print(f"Creating styled text: {text} at position {y_position}")

        text_clip = TextClip(
            text.upper(),
            font="Arial-Bold",
            color=color,
            fontsize=fontsize,
            method="caption",  # Use 'caption' for automatic wrapping
            stroke_color=stroke_color,
            stroke_width=stroke_width,
            size=(max_width, None),  # Set maximum width; height is automatic
            align='center'
        ).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_timer_clips(self, start_time, countdown_seconds):
        """
        Create countdown timer clips from countdown_seconds to 1.
        """
        timer_clips = []
        for i in range(countdown_seconds, 0, -1):
            current_time = countdown_seconds - i
            timer_text = str(i)
            timer_duration = 1
            timer_clip = self.create_styled_text(
                timer_text,
                self.middle_y - 100,  # Positioned slightly above center
                timer_duration,
                start_time + current_time,
                fontsize=150,
                color="white",
                stroke_color="black",
                stroke_width=3,
                max_width=300  # Adjust if necessary
            )
            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="1.02%",
        percent2="999.72%",
        initial_pause=2,
        timer_delay=1,
        countdown_wait=1,
        countdown_seconds=3,
        output_filename="would_you_rather.mp4",
        clock_sound_path=None,    # New parameter for clock sound
        bling_sound_path=None    # New parameter for bling sound
    ):
        """
        Generate the 'Would You Rather' video with optimized positioning and timing,
        including sound effects for the countdown timer and percentage reveal.
        """
        debug_print = lambda msg: print(f"[DEBUG] {msg}")
        debug_print(f"Starting video generation: {output_filename}")

        # Load main 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 main audio files: {str(e)}")
            return

        # Calculate timings
        audio1_start = initial_pause
        audio2_start = audio1_start + audio1_clip.duration + timer_delay
        countdown_start_time = audio2_start + audio2_clip.duration + countdown_wait
        percentage_duration = 3
        buffer_time = percentage_duration
        self.total_duration = countdown_start_time + countdown_seconds + buffer_time

        debug_print(f"Calculated total_duration: {self.total_duration:.2f}s")

        # Create background clips
        clips = [
            ColorClip((self.width, self.middle_y), color=(255, 0, 0))
                .set_duration(self.total_duration)
                .set_position((0, 0)),
            ColorClip((self.width, self.middle_y), color=(0, 0, 255))
                .set_duration(self.total_duration)
                .set_position((0, self.middle_y))
        ]

        # Fixed positions
        top_image_y = 15
        top_text_y = 650
        top_percentage_y = 750
        bottom_image_y = self.middle_y + 350
        bottom_text_y = 1200
        bottom_percentage_y = 1100

        # 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 center black circle
        circle_diameter = 50
        circle_size = (circle_diameter, circle_diameter)
        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_clip = ImageClip(np.array(circle_img)).set_duration(self.total_duration)
        circle_position = (self.width // 2 - circle_diameter // 2, self.middle_y - circle_diameter // 2)
        clips.append(circle_clip.set_position(circle_position))

        # Add top image (Option 1)
        img1_clip = self.process_image(
            image1_path,
            start_time=audio1_start,
            duration=self.total_duration - audio1_start
        )
        if img1_clip:
            clips.append(img1_clip.set_position(("center", top_image_y)))

        # Add top text
        clips.append(self.create_styled_text(
            option1_text,
            top_text_y,
            self.total_duration - audio1_start,
            audio1_start,
            max_width=800  # Ensure max_width aligns with no looping
        ))

        # Add bottom image (Option 2)
        img2_clip = self.process_image(
            image2_path,
            start_time=audio2_start,
            duration=self.total_duration - audio2_start
        )
        if img2_clip:
            clips.append(img2_clip.set_position(("center", bottom_image_y)))

        # Add bottom text
        clips.append(self.create_styled_text(
            option2_text,
            bottom_text_y,
            self.total_duration - audio2_start,
            audio2_start,
            max_width=800  # Ensure max_width aligns with no looping
        ))

        # Add countdown timer
        countdown_start_time_sec = countdown_start_time
        countdown_clips = self.create_timer_clips(countdown_start_time_sec, countdown_seconds)
        clips.extend(countdown_clips)

        # Add percentage indicators
        clips.append(self.create_styled_text(
            percent1,
            top_percentage_y,
            percentage_duration,
            countdown_start_time_sec + countdown_seconds,
            fontsize=100,
            color="yellow",
            max_width=400  # Adjust based on your requirements
        ))

        clips.append(self.create_styled_text(
            percent2,
            bottom_percentage_y,
            percentage_duration,
            countdown_start_time_sec + countdown_seconds,
            fontsize=100,
            color="yellow",
            max_width=400  # Adjust based on your requirements
        ))

        # Create final composite
        debug_print("Creating final composite...")
        final_clip = CompositeVideoClip(clips, size=(self.width, self.height))

        # Set video duration to the maximum duration to prevent unintended extensions
        final_clip = final_clip.set_duration(self.total_duration)

        # Add audio tracks
        debug_print("Adding audio tracks...")

        # Initialize list for all audio clips
        audio_clips = [
            audio1_clip.set_start(audio1_start),
            audio2_clip.set_start(audio2_start)
        ]

        # Add clock sound effect at countdown_start_time
        if clock_sound_path and os.path.exists(clock_sound_path):
            try:
                clock_sound_clip = AudioFileClip(clock_sound_path)
                # Loop the clock sound to match countdown duration using the correctly imported audio_loop
                clock_sound_clip = audio_loop(clock_sound_clip, duration=countdown_seconds)
                clock_sound_clip = clock_sound_clip.set_start(countdown_start_time_sec)
                audio_clips.append(clock_sound_clip)
                debug_print(f"Added clock sound effect at {countdown_start_time_sec}s")
            except Exception as e:
                debug_print(f"Error adding clock sound effect: {str(e)}")
        else:
            debug_print("Clock sound effect not provided or file does not exist.")

        # Add bling sound effect at the end of countdown (percentages revealed)
        if bling_sound_path and os.path.exists(bling_sound_path):
            try:
                bling_sound_clip = AudioFileClip(bling_sound_path)
                bling_sound_clip = bling_sound_clip.set_start(countdown_start_time_sec + countdown_seconds)
                audio_clips.append(bling_sound_clip)
                debug_print(f"Added bling sound effect at {countdown_start_time_sec + countdown_seconds}s")
            except Exception as e:
                debug_print(f"Error adding bling sound effect: {str(e)}")
        else:
            debug_print("Bling sound effect not provided or file does not exist.")

        # Create composite audio
        try:
            composite_audio = CompositeAudioClip(audio_clips).set_duration(self.total_duration)
            final_clip = final_clip.set_audio(composite_audio)
        except Exception as e:
            debug_print(f"Error combining audio tracks: {str(e)}")

        # Render video
        debug_print(f"Rendering final video to {output_filename}...")
        final_clip.write_videofile(
            output_filename,
            fps=30,
            codec="libx264",
            audio_codec="aac",
            threads=4,
            preset="medium",
            remove_temp=True,
            logger=None  # Suppress MoviePy logs if desired
        )
        debug_print("Video generation complete.")

        # Close clips to free up resources
        final_clip.close()
        audio1_clip.close()
        audio2_clip.close()
        if 'clock_sound_clip' in locals():
            clock_sound_clip.close()
        if 'bling_sound_clip' in locals():
            bling_sound_clip.close()