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

# Define your paths here
JSON_PATH = 'video_data.json'      # Path to your JSON file
OUTPUT_DIR = 'output_videos'
VIDEO_OUTPUT = os.path.join(OUTPUT_DIR, 'final_video.mp4')
DEFAULT_FONT_PATH = '/root/mm/vidtext/opensansvar.ttf'  # Ensure this path is correct

# Video specifications
FRAME_WIDTH = 1024
FRAME_HEIGHT = 1024
FPS = 24
VIDEO_CODEC = 'mp4v'  # FourCC code for mp4

# Scene timings 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)
        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")
    
    for i in range(FADE_IN_FRAMES):
        alpha = (i + 1) / FADE_IN_FRAMES if fade_type == 'in' else 1 - (i + 1) / FADE_OUT_FRAMES
        if fade_type == 'in':
            blended = blend_images(black_img, base_img, alpha)
        else:
            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):
    """
    Generate frames with text animation (typing effect).
    """
    frames = []
    total_chars = len(text)
    # Determine how many characters to show per frame
    chars_per_frame = max(total_chars // TEXT_ANIMATION_FRAMES, 1)
    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)
    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")
    
    # Fade In
    fade_in_frames = generate_fade_frames(base_img, fade_type='in')
    frames.extend(fade_in_frames)
    
    # Display
    display_frames = generate_display_frames(base_img)
    frames.extend(display_frames)
    
    # Text Animation
    text_animation_frames = generate_text_animation_frames(base_img, text, font, font_color)
    frames.extend(text_animation_frames)
    
    # Fade Out
    fade_out_frames = generate_fade_frames(base_img, fade_type='out')
    frames.extend(fade_out_frames)
    
    return frames

def generate_video(scenes):
    """
    Generate the video from the list of scenes.
    """
    # Ensure output directory exists
    if not os.path.exists(OUTPUT_DIR):
        os.makedirs(OUTPUT_DIR)
    
    # Define the codec and create VideoWriter object
    fourcc = cv2.VideoWriter_fourcc(*VIDEO_CODEC)
    out = cv2.VideoWriter(VIDEO_OUTPUT, fourcc, FPS, (FRAME_WIDTH, FRAME_HEIGHT))
    
    for idx, scene in enumerate(scenes):
        print(f"Processing scene {idx + 1}/{len(scenes)}: '{scene.get('text', '')}'")
        scene_frames = generate_scene_frames(scene)
        for frame in scene_frames:
            out.write(frame)
    
    # Release everything if job is finished
    out.release()
    print(f"Video generated: {VIDEO_OUTPUT}")

def main():
    scenes = load_scenes(JSON_PATH)
    if not scenes:
        print("No scenes to process. Exiting.")
        return
    generate_video(scenes)

if __name__ == "__main__":
    main()
The 'video_data.json' file should be created from setup.py, this is the setup.py script, yes?