import os
import json
from PIL import Image, ImageDraw, ImageFont, ImageColor
import cv2
import numpy as np

# Video settings
FPS = 30  # Frames per second
FRAME_WIDTH = 1024
FRAME_HEIGHT = 1024
DEFAULT_FONT_PATH = "/root/mm/vidfinal/opensansvar.ttf"  # Update with your font path
VIDEO_CODEC = "mp4v"  # Codec for MP4 files
OUTPUT_DIR = "output"  # Directory to save the video
VIDEO_OUTPUT = os.path.join(OUTPUT_DIR, "output_video.mp4")
JSON_PATH = "video_data.json"  # Path to your JSON file with scenes

# Define durations in seconds
FADE_IN_DURATION = 1
DISPLAY_DURATION = 5
TEXT_ANIMATION_DURATION = 3
FADE_OUT_DURATION = 1

# Calculate number of frames for each segment
FADE_IN_FRAMES = FADE_IN_DURATION * FPS
DISPLAY_FRAMES = DISPLAY_DURATION * FPS
TEXT_ANIMATION_FRAMES = TEXT_ANIMATION_DURATION * FPS
FADE_OUT_FRAMES = FADE_OUT_DURATION * FPS

# Total frames per scene
TOTAL_SCENE_FRAMES = FADE_IN_FRAMES + DISPLAY_FRAMES + TEXT_ANIMATION_FRAMES + FADE_OUT_FRAMES

def load_scenes(json_path):
    """
    Load scenes from a JSON file.
    """
    try:
        with open(json_path, 'r') as f:
            scenes = json.load(f)
        if not isinstance(scenes, list):
            raise ValueError("JSON data must be a list of scenes.")
        return scenes
    except Exception as e:
        print(f"Error loading JSON: {e}")
        return []

def create_base_image(scene):
    """
    Create the base image for a scene with background color.
    """
    background_color = scene.get("background_color", "black")
    img = Image.new('RGB', (FRAME_WIDTH, FRAME_HEIGHT), color=background_color)
    return img

def load_font(scene):
    """
    Load the specified font or default font.
    """
    try:
        font_path = scene.get("font_path", DEFAULT_FONT_PATH)
        font_size = scene.get("font_size", 50)  # Adjusted default for square frame
        font = ImageFont.truetype(font_path, size=font_size)
    except Exception as e:
        print(f"Error loading font '{scene.get('font_path', DEFAULT_FONT_PATH)}': {e}. Using default font.")
        try:
            font = ImageFont.truetype(DEFAULT_FONT_PATH, size=50)
        except Exception as e:
            print(f"Failed to load default font. {e}")
            font = ImageFont.load_default()
    return font

def add_text(image, text, font, font_color, visible_chars=None):
    """
    Add text to an image. If visible_chars is specified, only that many characters are visible.
    """
    draw = ImageDraw.Draw(image)
    text_width, text_height = draw.textsize(text, font=font)
    position = ((FRAME_WIDTH - text_width) // 2, (FRAME_HEIGHT - text_height) // 2)
    
    if visible_chars is not None:
        text_to_draw = text[:visible_chars]
    else:
        text_to_draw = text
    
    draw.text(position, text_to_draw, font=font, fill=font_color)
    return image

def blend_images(img1, img2, alpha):
    """
    Blend two PIL images with the given alpha for img2.
    """
    return Image.blend(img1, img2, alpha)

def pil_to_cv2(image):
    """
    Convert PIL Image to OpenCV image (BGR).
    """
    return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)

def generate_fade_frames(base_img, fade_type='in'):
    """
    Generate fade in or fade out frames.
    """
    frames = []
    black_img = Image.new('RGB', (FRAME_WIDTH, FRAME_HEIGHT), color="black")
    
    total_frames = FADE_IN_FRAMES if fade_type == 'in' else FADE_OUT_FRAMES
    
    for i in range(total_frames):
        if fade_type == 'in':
            alpha = (i + 1) / total_frames
            blended = blend_images(black_img, base_img, alpha)
        else:
            alpha = 1 - (i + 1) / total_frames
            blended = blend_images(base_img, black_img, alpha)
        cv_frame = pil_to_cv2(blended)
        frames.append(cv_frame)
    
    return frames

def generate_display_frames(base_img):
    """
    Generate static display frames.
    """
    cv_frame = pil_to_cv2(base_img)
    return [cv_frame] * DISPLAY_FRAMES

def generate_text_animation_frames(base_img, text, font, font_color, animation_type, scene):
    """
    Generate frames with text animation effects.
    Supported animations: Typing Effect, Fade In, Slide In, Zoom In
    """
    frames = []
    total_chars = len(text)
    chars_per_frame = max(total_chars // TEXT_ANIMATION_FRAMES, 1)

    if animation_type == "Typing Effect":
        for i in range(TEXT_ANIMATION_FRAMES):
            visible_chars = min((i + 1) * chars_per_frame, total_chars)
            animated_img = base_img.copy()
            add_text(animated_img, text, font, font_color, visible_chars=visible_chars)
            cv_frame = pil_to_cv2(animated_img)
            frames.append(cv_frame)
        # Ensure the entire text is visible in the last frame
        while len(frames) < TEXT_ANIMATION_FRAMES:
            animated_img = base_img.copy()
            add_text(animated_img, text, font, font_color)
            cv_frame = pil_to_cv2(animated_img)
            frames.append(cv_frame)

    elif animation_type == "Fade In":
        for i in range(TEXT_ANIMATION_FRAMES):
            animated_img = base_img.copy()
            alpha = (i + 1) / TEXT_ANIMATION_FRAMES
            overlay = Image.new('RGBA', animated_img.size, (0, 0, 0, 0))
            draw = ImageDraw.Draw(overlay)
            text_width, text_height = draw.textsize(text, font=font)
            position = ((FRAME_WIDTH - text_width) // 2, (FRAME_HEIGHT - text_height) // 2)
            draw.text(position, text, font=font, fill=(*ImageColor.getrgb(font_color), int(255 * alpha)))
            animated_img = Image.alpha_composite(animated_img.convert('RGBA'), overlay)
            cv_frame = pil_to_cv2(animated_img.convert('RGB'))
            frames.append(cv_frame)

    elif animation_type == "Slide In":
        for i in range(TEXT_ANIMATION_FRAMES):
            animated_img = base_img.copy()
            draw = ImageDraw.Draw(animated_img)
            text_width, text_height = draw.textsize(text, font=font)
            total_slide = FRAME_WIDTH
            current_slide = int(-text_width + (text_width + (FRAME_WIDTH // 2)) * (i + 1) / TEXT_ANIMATION_FRAMES)
            position = (current_slide, (FRAME_HEIGHT - text_height) // 2)
            draw.text(position, text, font=font, fill=font_color)
            cv_frame = pil_to_cv2(animated_img)
            frames.append(cv_frame)

    elif animation_type == "Zoom In":
        initial_size = max(int(font.size * 0.5), 10)
        final_size = font.size
        for i in range(TEXT_ANIMATION_FRAMES):
            temp_font_size = initial_size + int((final_size - initial_size) * (i + 1) / TEXT_ANIMATION_FRAMES)
            try:
                temp_font = ImageFont.truetype(scene.get("font_path", DEFAULT_FONT_PATH), size=temp_font_size)
            except Exception as e:
                print(f"Error loading font for Zoom In: {e}. Using default font.")
                temp_font = ImageFont.load_default()
            animated_img = base_img.copy()
            add_text(animated_img, text, temp_font, font_color)
            cv_frame = pil_to_cv2(animated_img)
            frames.append(cv_frame)

    else:
        for i in range(TEXT_ANIMATION_FRAMES):
            visible_chars = min((i + 1) * chars_per_frame, total_chars)
            animated_img = base_img.copy()
            add_text(animated_img, text, font, font_color, visible_chars=visible_chars)
            cv_frame = pil_to_cv2(animated_img)
            frames.append(cv_frame)

    return frames

def generate_scene_frames(scene):
    """
    Generate all frames for a single scene including transitions and animations.
    """
    frames = []
    base_img = create_base_image(scene)
    font = load_font(scene)
    text = scene.get("text", "")
    font_color = scene.get("font_color", "white")
    text_animation = scene.get("text_animation", "Typing Effect")
    
    # Fade In
    fade_in_frames = generate_fade_frames(base_img, fade_type='in')
    frames.extend(fade_in_frames)
    
    # Display with Text Animation
    text_animation_frames = generate_text_animation_frames(base_img, text, font, font_color, text_animation, scene)
    display_frames = generate_display_frames(base_img)
    
    # Combine display frames with text animation frames
    for i in range(DISPLAY_FRAMES):
        if i < len(text_animation_frames):
            animated_frame = text_animation_frames[i]
        else:
            animated_frame = pil_to_cv2(base_img)
        frames.append(animated_frame)
    
    frames.extend(display_frames)
    
    # Fade Out
    fade_out_frames = generate_fade_frames(base_img, fade_type='out')
    frames.extend(fade_out_frames)
    
    return frames

def create_video(frames):
    """
    Create a video from frames using OpenCV.
    """
    if not os.path.exists(OUTPUT_DIR):
        os.makedirs(OUTPUT_DIR)

    fourcc = cv2.VideoWriter_fourcc(*VIDEO_CODEC)
    out = cv2.VideoWriter(VIDEO_OUTPUT, fourcc, FPS, (FRAME_WIDTH, FRAME_HEIGHT))
    
    for frame in frames:
        out.write(frame)
    
    out.release()
    print(f"Video created: {VIDEO_OUTPUT}")

def main():
    """
    Main function to generate video from scenes.
    """
    scenes = load_scenes(JSON_PATH)
    all_frames = []

    for scene in scenes:
        scene_frames = generate_scene_frames(scene)
        all_frames.extend(scene_frames)

    create_video(all_frames)

if __name__ == "__main__":
    main()
(venv) ➜  vidfinal python vg.py
Traceback (most recent call last):
  File "/root/mm/vidfinal/vg.py", line 265, in <module>
    main()
  File "/root/mm/vidfinal/vg.py", line 259, in main
    scene_frames = generate_scene_frames(scene)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/mm/vidfinal/vg.py", line 216, in generate_scene_frames
    text_animation_frames = generate_text_animation_frames(base_img, text, font, font_color, text_animation, scene)
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/mm/vidfinal/vg.py", line 167, in generate_text_animation_frames
    text_width, text_height = draw.textsize(text, font=font)
                              ^^^^^^^^^^^^^
AttributeError: 'ImageDraw' object has no attribute 'textsize'