<?php
if ( ! defined( 'ABSPATH' ) ) exit;

/**
 * VMO Sora Video Generator
 * Integrates OpenAI Sora 2/Sora 2 Pro for AI video generation
 * 
 * Capabilities:
 * - Video hero backgrounds
 * - Product showcase videos
 * - 360° product spin videos from multiple images
 * - Brand intro videos
 * - Testimonial background videos
 * 
 * @package VMO_AI_Builder
 * @since 9.0.0
 */

if (!defined('ABSPATH')) exit;

class VMO_Sora_Video_Generator {
    
    private $api_key;
    private $api_base = 'https://api.openai.com/v1/videos';
    
    // Token costs for video generation (7x markup on actual API costs - minimum viable for Sora)
    // Sora Standard: ~$0.30/sec actual → 7x markup
    const TOKENS_SORA_2_4SEC = 12600;      // sora-2, 4s ($1.20 actual → 7x = $8.40 → 12,600 tokens)
    const TOKENS_SORA_2_8SEC = 25200;      // sora-2, 8s ($2.40 actual → 7x = $16.80 → 25,200 tokens)
    const TOKENS_SORA_2_12SEC = 37800;     // sora-2, 12s ($3.60 actual → 7x = $25.20 → 37,800 tokens)
    // Sora Pro: ~$0.60/sec actual → 7x markup
    const TOKENS_SORA_2_PRO_4SEC = 25200;  // sora-2-pro, 4s ($2.40 actual → 7x = $16.80 → 25,200 tokens)
    const TOKENS_SORA_2_PRO_8SEC = 50400;  // sora-2-pro, 8s ($4.80 actual → 7x = $33.60 → 50,400 tokens)
    const TOKENS_SORA_2_PRO_12SEC = 75600; // sora-2-pro, 12s ($7.20 actual → 7x = $50.40 → 75,600 tokens)
    
    public function __construct() {
        $this->api_key = get_option('vmo_openai_api_key', '');
        
        // Register AJAX handlers
        add_action('wp_ajax_vmo_generate_video', array($this, 'ajax_generate_video'));
        add_action('wp_ajax_vmo_check_video_status', array($this, 'ajax_check_video_status'));
        add_action('wp_ajax_vmo_download_video', array($this, 'ajax_download_video'));
        add_action('wp_ajax_vmo_generate_360_product_video', array($this, 'ajax_generate_360_product_video'));
        add_action('wp_ajax_vmo_create_spin_video_from_images', array($this, 'ajax_create_spin_video_from_images'));
    }

    protected function prepare_long_running_runtime() {
        if (function_exists('set_time_limit')) {
            @set_time_limit(0);
        }
        if (function_exists('ignore_user_abort')) {
            @ignore_user_abort(true);
        }
        if (function_exists('ini_set')) {
            @ini_set('memory_limit', '512M');
        }
    }
    
    /**
     * Generate a video using Sora
     * 
     * @param string $prompt The video generation prompt
     * @param array $options Optional settings (model, seconds, size, input_reference)
     * @return array Result with video job ID or error
     */
    public function generate_video($prompt, $options = array()) {
        $this->prepare_long_running_runtime();

        if (empty($this->api_key)) {
            return array('error' => 'AI API key not configured');
        }
        
        $defaults = array(
            'model' => 'sora-2',           // sora-2 or sora-2-pro
            'seconds' => '8',               // 4, 8, or 12
            'size' => '1280x720',           // 720x1280, 1280x720, 1024x1792, 1792x1024
            'input_reference' => null,      // Optional starting image
        );
        
        $options = wp_parse_args($options, $defaults);
        
        // Build request body
        $body = array(
            'model' => $options['model'],
            'prompt' => $prompt,
            'seconds' => $options['seconds'],
            'size' => $options['size'],
        );
        
        // Handle multipart if there's an input reference image
        if (!empty($options['input_reference'])) {
            return $this->generate_video_with_image($prompt, $options);
        }
        
        $response = wp_remote_post($this->api_base, array(
            'headers' => array(
                'Authorization' => 'Bearer ' . $this->api_key,
                'Content-Type' => 'application/json',
            ),
            'body' => json_encode($body),
            'timeout' => 60,
        ));
        
        if (is_wp_error($response)) {
            error_log('VMO Sora API Error: ' . $response->get_error_message());
            return array('error' => $response->get_error_message());
        }
        
        $response_code = wp_remote_retrieve_response_code($response);
        $response_body = json_decode(wp_remote_retrieve_body($response), true);
        
        if ($response_code !== 200 && $response_code !== 201) {
            $error_msg = $response_body['error']['message'] ?? 'Unknown error';
            error_log("VMO Sora API Error (HTTP $response_code): $error_msg");
            return array('error' => $error_msg);
        }
        
        error_log('VMO Sora: Video job started - ID: ' . ($response_body['id'] ?? 'unknown'));
        
        return array(
            'success' => true,
            'video_id' => $response_body['id'],
            'status' => $response_body['status'],
            'progress' => $response_body['progress'] ?? 0,
            'model' => $response_body['model'],
            'seconds' => $response_body['seconds'],
            'size' => $response_body['size'],
        );
    }
    
    /**
     * Generate video with an input reference image
     */
    private function generate_video_with_image($prompt, $options) {
        $this->prepare_long_running_runtime();

        $image_path = $options['input_reference'];
        
        // If it's a URL, download it first
        if (filter_var($image_path, FILTER_VALIDATE_URL)) {
            $temp_file = download_url($image_path);
            if (is_wp_error($temp_file)) {
                return array('error' => 'Failed to download reference image');
            }
            $image_path = $temp_file;
        }
        
        if (!file_exists($image_path)) {
            return array('error' => 'Reference image not found');
        }
        
        $mime_type = mime_content_type($image_path);
        $file_name = basename($image_path);
        
        // Build multipart form body for wp_remote_post
        $boundary = wp_generate_password(24, false);
        $body = '';

        $fields = array(
            'prompt'  => $prompt,
            'model'   => $options['model'],
            'seconds' => $options['seconds'],
            'size'    => $options['size'],
        );

        foreach ($fields as $name => $value) {
            $body .= "--{$boundary}\r\n";
            $body .= "Content-Disposition: form-data; name=\"{$name}\"\r\n\r\n";
            $body .= "{$value}\r\n";
        }

        // Add file field
        $file_content = vmo_get_filesystem()->get_contents($image_path);
        $body .= "--{$boundary}\r\n";
        $body .= "Content-Disposition: form-data; name=\"input_reference\"; filename=\"{$file_name}\"\r\n";
        $body .= "Content-Type: {$mime_type}\r\n\r\n";
        $body .= $file_content . "\r\n";
        $body .= "--{$boundary}--\r\n";

        $wp_response = wp_remote_post($this->api_base, array(
            'timeout' => 120,
            'headers' => array(
                'Authorization' => 'Bearer ' . $this->api_key,
                'Content-Type'  => 'multipart/form-data; boundary=' . $boundary,
            ),
            'body' => $body,
        ));

        if (is_wp_error($wp_response)) {
            $response  = '';
            $http_code = 0;
            $error     = $wp_response->get_error_message();
        } else {
            $response  = wp_remote_retrieve_body($wp_response);
            $http_code = wp_remote_retrieve_response_code($wp_response);
            $error     = '';
        }

        // Clean up temp file if we downloaded it
        if (isset($temp_file) && file_exists($temp_file)) {
            @unlink($temp_file);
        }

        if ($error) {
            return array('error' => 'API request error: ' . $error);
        }
        
        $response_body = json_decode($response, true);
        
        if ($http_code !== 200 && $http_code !== 201) {
            $error_msg = $response_body['error']['message'] ?? 'Unknown error';
            return array('error' => $error_msg);
        }
        
        return array(
            'success' => true,
            'video_id' => $response_body['id'],
            'status' => $response_body['status'],
            'progress' => $response_body['progress'] ?? 0,
        );
    }
    
    /**
     * Check video generation status
     */
    public function get_video_status($video_id) {
        $this->prepare_long_running_runtime();

        if (empty($this->api_key)) {
            return array('error' => 'AI API key not configured');
        }
        
        $response = wp_remote_get($this->api_base . '/' . $video_id, array(
            'headers' => array(
                'Authorization' => 'Bearer ' . $this->api_key,
            ),
            'timeout' => 30,
        ));
        
        if (is_wp_error($response)) {
            return array('error' => $response->get_error_message());
        }
        
        $body = json_decode(wp_remote_retrieve_body($response), true);
        
        return array(
            'success' => true,
            'video_id' => $body['id'],
            'status' => $body['status'],
            'progress' => $body['progress'] ?? 0,
            'model' => $body['model'] ?? '',
            'seconds' => $body['seconds'] ?? '',
            'size' => $body['size'] ?? '',
            'completed_at' => $body['completed_at'] ?? null,
            'expires_at' => $body['expires_at'] ?? null,
            'error' => $body['error'] ?? null,
        );
    }
    
    /**
     * Download completed video
     */
    public function download_video($video_id, $variant = 'video') {
        $this->prepare_long_running_runtime();

        if (empty($this->api_key)) {
            return array('error' => 'AI API key not configured');
        }
        
        $url = $this->api_base . '/' . $video_id . '/content';
        if ($variant !== 'video') {
            $url .= '?variant=' . $variant;
        }
        
        $response = wp_remote_get($url, array(
            'headers' => array(
                'Authorization' => 'Bearer ' . $this->api_key,
            ),
            'timeout' => 240, // Videos can be large
        ));
        
        if (is_wp_error($response)) {
            return array('error' => $response->get_error_message());
        }
        
        $body = wp_remote_retrieve_body($response);
        $content_type = wp_remote_retrieve_header($response, 'content-type');
        
        return array(
            'success' => true,
            'content' => $body,
            'content_type' => $content_type,
        );
    }
    
    /**
     * Download video and save to WordPress Media Library
     */
    public function download_and_save_video($video_id, $user_id, $title = '', $context = array()) {
        $this->prepare_long_running_runtime();

        $download = $this->download_video($video_id);
        
        if (isset($download['error'])) {
            return $download;
        }
        
        // Create temp file
        $temp_file = wp_tempnam('vmo_video_');
        vmo_get_filesystem()->put_contents($temp_file, $download['content'], FS_CHMOD_FILE);
        
        // Determine file extension
        $ext = 'mp4';
        if (strpos($download['content_type'], 'webm') !== false) {
            $ext = 'webm';
        }
        
        $file_array = array(
            'name' => 'vmo-sora-video-' . time() . '.' . $ext,
            'tmp_name' => $temp_file,
        );
        
        // Ensure media functions are loaded
        if (!function_exists('media_handle_sideload')) {
            require_once ABSPATH . 'wp-admin/includes/image.php';
            require_once ABSPATH . 'wp-admin/includes/file.php';
            require_once ABSPATH . 'wp-admin/includes/media.php';
        }
        
        $attachment_id = media_handle_sideload($file_array, 0);
        
        @unlink($temp_file);
        
        if (is_wp_error($attachment_id)) {
            return array('error' => 'Failed to save video: ' . $attachment_id->get_error_message());
        }
        
        // Set author and metadata
        wp_update_post(array(
            'ID' => $attachment_id,
            'post_author' => $user_id,
            'post_title' => $title ?: 'VMO AI Video',
        ));
        
        // Save context as meta
        update_post_meta($attachment_id, '_vmo_video_source', 'sora');
        update_post_meta($attachment_id, '_vmo_video_id', $video_id);
        update_post_meta($attachment_id, '_vmo_video_context', $context);
        
        // Also download thumbnail
        $thumbnail = $this->download_video($video_id, 'thumbnail');
        if (!isset($thumbnail['error'])) {
            $thumb_temp = wp_tempnam('vmo_thumb_');
            vmo_get_filesystem()->put_contents($thumb_temp, $thumbnail['content'], FS_CHMOD_FILE);
            
            $thumb_array = array(
                'name' => 'vmo-sora-thumb-' . time() . '.webp',
                'tmp_name' => $thumb_temp,
            );
            
            $thumb_id = media_handle_sideload($thumb_array, 0);
            @unlink($thumb_temp);
            
            if (!is_wp_error($thumb_id)) {
                update_post_meta($attachment_id, '_vmo_video_thumbnail_id', $thumb_id);
            }
        }
        
        $video_url = wp_get_attachment_url($attachment_id);
        
        return array(
            'success' => true,
            'attachment_id' => $attachment_id,
            'url' => $video_url,
        );
    }
    
    /**
     * Generate a hero background video
     */
    public function generate_hero_video($business_context, $options = array()) {
        $business_name = $business_context['business_name'] ?? 'the business';
        $industry = $business_context['industry'] ?? 'professional services';
        $brand_personality = $business_context['brand_personality'] ?? 'modern and professional';
        
        $prompt = "Cinematic hero background video for a {$industry} business. "
                . "Brand style: {$brand_personality}. "
                . "Smooth, subtle motion perfect for a website hero section. "
                . "No text, no people's faces, no logos. "
                . "Elegant lighting, shallow depth of field, professional color grading. "
                . "Ambient movement that doesn't distract from overlaid text.";
        
        $defaults = array(
            'model' => 'sora-2-pro', // Higher quality for hero
            'seconds' => '8',
            'size' => '1280x720', // Landscape for hero
        );
        
        return $this->generate_video($prompt, wp_parse_args($options, $defaults));
    }
    
    /**
     * Generate a product showcase video
     */
    public function generate_product_video($product_name, $product_description, $options = array()) {
        $prompt = "Professional product showcase video for: {$product_name}. "
                . "Description: {$product_description}. "
                . "Elegant presentation on a clean, minimalist background. "
                . "Smooth camera movement orbiting around the product. "
                . "Soft studio lighting, subtle shadows, premium feel. "
                . "No text, no watermarks. "
                . "E-commerce quality, high-end product photography style in motion.";
        
        $defaults = array(
            'model' => 'sora-2-pro',
            'seconds' => '8',
            'size' => '1280x720',
        );
        
        return $this->generate_video($prompt, wp_parse_args($options, $defaults));
    }
    
    /**
     * Generate 360° spin video from a single product image
     * Uses Sora's image reference feature to animate the product
     */
    public function generate_360_spin_from_image($image_url, $product_name, $options = array()) {
        $prompt = "360-degree rotating product view of {$product_name}. "
                . "Smooth, continuous rotation showing all angles of the product. "
                . "Clean white or neutral gradient background. "
                . "Studio lighting that highlights product details. "
                . "Professional e-commerce style, perfect loop for web display.";
        
        $defaults = array(
            'model' => 'sora-2-pro',
            'seconds' => '8',
            'size' => '1280x720',
            'input_reference' => $image_url,
        );
        
        return $this->generate_video($prompt, wp_parse_args($options, $defaults));
    }
    
    /**
     * Create a "fake" 360° spin video by sequencing multiple product images
     * This is client-side - returns the configuration for the JS player
     */
    public function create_360_spin_from_images($images) {
        if (count($images) < 3) {
            return array('error' => 'Need at least 3 images for 360° spin viewer');
        }
        
        // Sort images by filename/order if needed
        $sorted_images = array_values($images);
        
        return array(
            'success' => true,
            'type' => '360_spin',
            'frames' => $sorted_images,
            'frame_count' => count($sorted_images),
            'config' => array(
                'autoplay' => false,
                'loop' => true,
                'speed' => 1,
                'drag_sensitivity' => 5,
                'reverse_on_drag' => true,
                'inertia' => 0.9,
            ),
        );
    }
    
    /**
     * Calculate token cost for video generation
     */
    public static function get_token_cost($model, $seconds) {
        $costs = array(
            'sora-2' => array(
                '4' => self::TOKENS_SORA_2_4SEC,
                '8' => self::TOKENS_SORA_2_8SEC,
                '12' => self::TOKENS_SORA_2_12SEC,
            ),
            'sora-2-pro' => array(
                '4' => self::TOKENS_SORA_2_PRO_4SEC,
                '8' => self::TOKENS_SORA_2_PRO_8SEC,
                '12' => self::TOKENS_SORA_2_PRO_12SEC,
            ),
        );
        
        return $costs[$model][$seconds] ?? self::TOKENS_SORA_2_8SEC;
    }
    
    /**
     * Determine if product needs 360° viewer based on business type
     */
    public static function product_needs_360_view($business_type, $product_type = '') {
        // Products that benefit from 360° view
        $physical_products = array(
            'shoe', 'shoes', 'footwear', 'sneakers',
            'cosmetic', 'cosmetics', 'makeup', 'beauty',
            'jewelry', 'jewellery', 'watch', 'watches',
            'bag', 'bags', 'handbag', 'purse',
            'furniture', 'chair', 'table', 'sofa',
            'electronics', 'phone', 'laptop', 'gadget',
            'clothing', 'apparel', 'fashion', 'dress',
            'car', 'vehicle', 'automobile',
            'art', 'sculpture', 'figurine', 'collectible',
            'toy', 'toys', 'game',
            'appliance', 'kitchen', 'cookware',
            'sports', 'equipment', 'gear',
            'ecommerce', 'e-commerce', 'shop', 'store',
            'dropshipping', 'retail',
        );
        
        // Services that DON'T need 360° product view
        $service_businesses = array(
            'videographer', 'videography', 'video production',
            'photographer', 'photography',
            'consultant', 'consulting', 'consulting firm',
            'lawyer', 'legal', 'law firm', 'attorney',
            'accountant', 'accounting', 'bookkeeper',
            'doctor', 'medical', 'healthcare', 'clinic',
            'dentist', 'dental',
            'therapist', 'therapy', 'counseling',
            'coach', 'coaching', 'mentor',
            'agency', 'marketing agency', 'seo', 'content',
            'va agency', 'virtual assistant',
            'cleaning', 'lawn care', 'handyman', 'plumber',
            'restaurant', 'cafe', 'food', 'catering',
            'gym', 'fitness', 'yoga', 'wellness',
            'real estate', 'property',
            'insurance', 'financial',
            'education', 'tutoring', 'school',
            'non-profit', 'nonprofit', 'charity',
            'church', 'religious',
            'event', 'wedding', 'party planning',
        );
        
        $check_text = strtolower($business_type . ' ' . $product_type);
        
        // Check if it's a service business (no 360° needed)
        foreach ($service_businesses as $service) {
            if (strpos($check_text, $service) !== false) {
                return false;
            }
        }
        
        // Check if it's a physical product business (360° beneficial)
        foreach ($physical_products as $product) {
            if (strpos($check_text, $product) !== false) {
                return true;
            }
        }
        
        // Default: if unsure, suggest 360° for "products" but not "services"
        if (strpos($check_text, 'product') !== false || strpos($check_text, 'sell') !== false) {
            return true;
        }
        
        return false;
    }
    
    /**
     * AJAX: Start video generation
     */
    public function ajax_generate_video() {
        $this->prepare_long_running_runtime();

        check_ajax_referer('vmo_builder_nonce', 'security');
        
        if (!is_user_logged_in()) {
            wp_send_json_error(array('message' => 'Not logged in'));
        }
        
        $user_id = get_current_user_id();
        $prompt = sanitize_textarea_field($_POST['prompt'] ?? '');
        $model = sanitize_text_field($_POST['model'] ?? 'sora-2');
        $seconds = sanitize_text_field($_POST['seconds'] ?? '8');
        $size = sanitize_text_field($_POST['size'] ?? '1280x720');
        $video_type = sanitize_text_field($_POST['video_type'] ?? 'custom');
        
        if (empty($prompt)) {
            wp_send_json_error(array('message' => 'Prompt is required'));
        }
        
        // Token deduction
        $token_cost = self::get_token_cost($model, $seconds);
        if (function_exists('vmo_deduct_tokens')) {
            if (!vmo_deduct_tokens($user_id, $token_cost, 'sora_video', "AI video generation ({$model}, {$seconds}s)")) {
                wp_send_json_error(array(
                    'message' => 'Insufficient tokens for video generation. Cost: ' . number_format($token_cost) . ' tokens.',
                    'token_cost' => $token_cost,
                ));
            }
        }
        
        $result = $this->generate_video($prompt, array(
            'model' => $model,
            'seconds' => $seconds,
            'size' => $size,
        ));
        
        if (isset($result['error'])) {
            // Refund tokens on failure
            if (function_exists('vmo_add_tokens')) {
                vmo_add_tokens($user_id, $token_cost, 'sora_refund', 'Video generation failed - refund');
            }
            wp_send_json_error(array('message' => $result['error']));
        }
        
        // Store job info for tracking
        $video_jobs = get_user_meta($user_id, 'vmo_sora_video_jobs', true) ?: array();
        $video_jobs[$result['video_id']] = array(
            'video_id' => $result['video_id'],
            'prompt' => $prompt,
            'model' => $model,
            'seconds' => $seconds,
            'size' => $size,
            'video_type' => $video_type,
            'created_at' => current_time('mysql'),
            'status' => 'queued',
        );
        update_user_meta($user_id, 'vmo_sora_video_jobs', $video_jobs);
        
        wp_send_json_success($result);
    }
    
    /**
     * AJAX: Check video status
     */
    public function ajax_check_video_status() {
        $this->prepare_long_running_runtime();

        check_ajax_referer('vmo_builder_nonce', 'security');
        
        $video_id = sanitize_text_field($_POST['video_id'] ?? '');
        
        if (empty($video_id)) {
            wp_send_json_error(array('message' => 'Video ID required'));
        }
        
        $result = $this->get_video_status($video_id);
        
        if (isset($result['error'])) {
            wp_send_json_error(array('message' => $result['error']));
        }
        
        wp_send_json_success($result);
    }
    
    /**
     * AJAX: Download completed video
     */
    public function ajax_download_video() {
        $this->prepare_long_running_runtime();

        check_ajax_referer('vmo_builder_nonce', 'security');
        
        if (!is_user_logged_in()) {
            wp_send_json_error(array('message' => 'Not logged in'));
        }
        
        $user_id = get_current_user_id();
        $video_id = sanitize_text_field($_POST['video_id'] ?? '');
        $title = sanitize_text_field($_POST['title'] ?? 'VMO Video');
        
        if (empty($video_id)) {
            wp_send_json_error(array('message' => 'Video ID required'));
        }
        
        $context = array(
            'video_type' => sanitize_text_field($_POST['video_type'] ?? 'custom'),
            'prompt' => sanitize_textarea_field($_POST['prompt'] ?? ''),
        );
        
        $result = $this->download_and_save_video($video_id, $user_id, $title, $context);
        
        if (isset($result['error'])) {
            wp_send_json_error(array('message' => $result['error']));
        }
        
        wp_send_json_success($result);
    }
    
    /**
     * AJAX: Generate 360° product video from single image
     */
    public function ajax_generate_360_product_video() {
        $this->prepare_long_running_runtime();

        check_ajax_referer('vmo_builder_nonce', 'security');
        
        if (!is_user_logged_in()) {
            wp_send_json_error(array('message' => 'Not logged in'));
        }
        
        $user_id = get_current_user_id();
        $image_url = esc_url_raw($_POST['image_url'] ?? '');
        $product_name = sanitize_text_field($_POST['product_name'] ?? 'Product');
        $model = sanitize_text_field($_POST['model'] ?? 'sora-2-pro');
        
        if (empty($image_url)) {
            wp_send_json_error(array('message' => 'Product image URL required'));
        }
        
        // Token deduction
        $token_cost = self::get_token_cost($model, '8');
        if (function_exists('vmo_deduct_tokens')) {
            if (!vmo_deduct_tokens($user_id, $token_cost, 'sora_360_video', "360° product video ({$model})")) {
                wp_send_json_error(array(
                    'message' => 'Insufficient tokens for 360° video generation.',
                    'token_cost' => $token_cost,
                ));
            }
        }
        
        $result = $this->generate_360_spin_from_image($image_url, $product_name, array(
            'model' => $model,
        ));
        
        if (isset($result['error'])) {
            if (function_exists('vmo_add_tokens')) {
                vmo_add_tokens($user_id, $token_cost, 'sora_refund', '360° video generation failed - refund');
            }
            wp_send_json_error(array('message' => $result['error']));
        }
        
        wp_send_json_success($result);
    }
    
    /**
     * AJAX: Create spin viewer config from multiple images (no AI needed)
     */
    public function ajax_create_spin_video_from_images() {
        $this->prepare_long_running_runtime();

        check_ajax_referer('vmo_builder_nonce', 'security');
        
        $images = array_map('esc_url_raw', wp_unslash($_POST['images'] ?? array()));
        
        if (!is_array($images) || count($images) < 3) {
            wp_send_json_error(array('message' => 'At least 3 images required for 360° spin viewer'));
        }
        
        // Sanitize image URLs
        $sanitized_images = array_map('esc_url_raw', $images);
        
        $result = $this->create_360_spin_from_images($sanitized_images);
        
        if (isset($result['error'])) {
            wp_send_json_error(array('message' => $result['error']));
        }
        
        wp_send_json_success($result);
    }
}

// Initialize the class
if (class_exists('VMO_Sora_Video_Generator')) {
    $GLOBALS['vmo_sora_video_generator'] = new VMO_Sora_Video_Generator();
}
