import os
import argparse
import subprocess
import re
from PIL import Image, ImageOps, ExifTags
import cv2
import numpy as np

def natural_sort_key(s):
    return [int(c) if c.isdigit() else c.lower() for c in re.split(r'(\d+)', s)]

def list_images(input_folder):
    """List all images in the folder sorted naturally."""
    images = [f for f in os.listdir(input_folder) if f.lower().endswith(('.jpg', '.jpeg', '.png', '.gif'))]
    images.sort(key=natural_sort_key)
    return [os.path.join(input_folder, f) for f in images]

def group_related_images(images):
    """Group related images (original, restored, color) together."""
    groups = {}
    single_images = []
    for img in images:
        base_name = re.split(r'_(?:original|restored|color)', os.path.basename(img))[0]
        if base_name not in groups:
            groups[base_name] = []
        groups[base_name].append(img)
    
    valid_groups = []
    for group in groups.values():
        if len(group) == 3:
            valid_groups.append(group)
        else:
            single_images.extend(group)
    
    print(f"Found {len(valid_groups)} valid image groups and {len(single_images)} single images.")
    return valid_groups, single_images

def pad_image_to_resolution(image, target_resolution=(1280, 720)):
    """Pad the image with black borders to achieve target resolution while preserving aspect ratio."""
    width, height = image.size
    target_width, target_height = target_resolution
    
    # Calculate scaling factor
    scale = min(target_width / width, target_height / height)
    
    # Calculate new dimensions
    new_width = int(width * scale)
    new_height = int(height * scale)
    
    # Resize image
    resized_image = image.resize((new_width, new_height), Image.LANCZOS)
    
    # Create new image with black background
    new_image = Image.new('RGB', target_resolution, (0, 0, 0))
    
    # Calculate position to paste resized image
    left = (target_width - new_width) // 2
    top = (target_height - new_height) // 2
    
    # Paste resized image onto black background
    new_image.paste(resized_image, (left, top))
    
    return new_image

def pil_to_opencv(pil_image):
    """Convert PIL Image to OpenCV format."""
    return cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR)

def blend_images(image1, image2, alpha):
    """Blend two images (crossfade transition)."""
    return cv2.addWeighted(image1, 1 - alpha, image2, alpha, 0)

def create_transition_video(image_group, output_path, fps=30, duration=5):
    """Create a transition video for a group of images."""
    print(f"Creating transition video for images: {image_group}")
    output_resolution = (1280, 720)
    transition_duration = 1  # seconds

    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    video_writer = cv2.VideoWriter(output_path, fourcc, fps, output_resolution)

    images = []
    for img_path in image_group:
        try:
            img = Image.open(img_path)
            images.append(img)
            print(f"Successfully opened image: {img_path}")
        except Exception as e:
            print(f"Error opening image {img_path}: {str(e)}")
            return False

    padded_images = [pad_image_to_resolution(img, output_resolution) for img in images]
    opencv_images = [pil_to_opencv(img) for img in padded_images]

    total_frames = fps * duration
    if len(opencv_images) == 1:
        # If there's only one image, show it for the entire duration
        for _ in range(total_frames):
            video_writer.write(opencv_images[0])
    else:
        transition_frames = fps * transition_duration
        static_frames = (total_frames - (len(opencv_images) - 1) * transition_frames) // len(opencv_images)

        for i, img in enumerate(opencv_images):
            # Show static image
            for _ in range(static_frames):
                video_writer.write(img)

            # Transition to next image
            if i < len(opencv_images) - 1:
                next_img = opencv_images[i + 1]
                for t in range(transition_frames):
                    alpha = t / transition_frames
                    frame = blend_images(img, next_img, alpha)
                    video_writer.write(frame)

    video_writer.release()
    print(f"Transition video created: {output_path}")
    return True

def create_videos(image_groups, single_images, output_folder):
    """Create videos from image groups and single images."""
    successful_videos = []
    for i, group in enumerate(image_groups, start=1):
        output_path = os.path.join(output_folder, f'group_video_{i}.mp4')
        if create_transition_video(group, output_path):
            successful_videos.append(output_path)
    
    for i, img in enumerate(single_images, start=1):
        output_path = os.path.join(output_folder, f'single_video_{i}.mp4')
        if create_transition_video([img], output_path):
            successful_videos.append(output_path)
    
    return successful_videos

def concatenate_all_videos(video_paths, output_path, fade_duration=1):
    """Concatenate all videos with fade transition using FFmpeg."""
    if not video_paths:
        print("No videos to concatenate.")
        return False

    if len(video_paths) == 1:
        os.rename(video_paths[0], output_path)
        print(f"Single video renamed as final output: {output_path}")
        return True

    # Create a temporary file to store the list of input videos
    with open('video_list.txt', 'w') as f:
        for video in video_paths:
            f.write(f"file '{video}'\n")

    # Construct the FFmpeg command
    filter_complex = []
    for i in range(len(video_paths)):
        filter_complex.append(f"[{i}:v]format=yuva444p,fade=d={fade_duration}:t=in:alpha=1,setpts=PTS-STARTPTS[v{i}];")

    for i in range(len(video_paths) - 1):
        filter_complex.append(f"[v{i}][v{i+1}]overlay=shortest=1:format=yuv420[v{i+1}out];")

    filter_complex = "".join(filter_complex)
    filter_complex = filter_complex.rstrip(';')  # Remove the last semicolon

    command = [
        'ffmpeg',
        '-f', 'concat',
        '-safe', '0',
        '-i', 'video_list.txt',
        '-filter_complex', filter_complex,
        '-map', f'[v{len(video_paths)-1}out]',
        '-c:v', 'libx264',
        '-preset', 'medium',
        '-crf', '23',
        output_path
    ]

    print("Concatenating all videos...")
    try:
        subprocess.run(command, check=True)
        print(f"Final concatenated video created: {output_path}")
        os.remove('video_list.txt')  # Clean up the temporary file
        return True
    except subprocess.CalledProcessError as e:
        print(f"Error occurred during video concatenation: {e}")
        return False
    
def main():
    parser = argparse.ArgumentParser(description="Create and concatenate transition videos from image groups and single images.")
    parser.add_argument("input_folder", help="Folder containing the input images")
    parser.add_argument("output_folder", help="Folder where the output videos will be saved")
    
    args = parser.parse_args()

    print(f"Input folder: {args.input_folder}")
    print(f"Output folder: {args.output_folder}")

    # Ensure output folder exists
    os.makedirs(args.output_folder, exist_ok=True)
    print(f"Ensured output folder exists: {args.output_folder}")

    # List and group images
    images = list_images(args.input_folder)
    print(f"Found {len(images)} images in the input folder.")

    image_groups, single_images = group_related_images(images)
    print(f"Grouped images into {len(image_groups)} sets of related images and {len(single_images)} single images.")

    # Create individual transition videos
    video_paths = create_videos(image_groups, single_images, args.output_folder)
    print(f"Created {len(video_paths)} individual transition videos.")

    # Concatenate all videos
    final_output = os.path.join(args.output_folder, 'final_video.mp4')
    if concatenate_all_videos(video_paths, final_output):
        print("Video processing completed successfully.")
        print(f"Final video created: {final_output}")
    else:
        print("Error occurred during video concatenation.")

if __name__ == "__main__":
    main() take this code a modify the concatenation part while keeping all other functionality -  INSTRUCTIONS: you need to modify concatenation video part, the videos shall be concatenated in a way that first rendered video is concatenated into second one, this creates longer video made of two videos, to this video made of two videos concatenate next third videos, this will create even longer video, to that concatenate fourth video, and so on, make sure the total final video is the merge of all temp rendered videos