'error', 'message' => $message]); exit(); } // Ensure the request method is POST if ($_SERVER['REQUEST_METHOD'] !== 'POST') { sendErrorResponse('Method Not Allowed', 405); } // Check for Authorization header $headers = getallheaders(); $authHeader = $headers['Authorization'] ?? ''; if (!preg_match('/Bearer\s+(.+)/', $authHeader, $matches)) { sendErrorResponse('Authentication required', 401); } $accessToken = $matches[1]; // Validate the token (basic validation - check if it looks like a JWT) if (empty($accessToken) || substr_count($accessToken, '.') !== 2) { sendErrorResponse('Invalid authentication token', 401); } // Get the raw POST data $input = file_get_contents('php://input'); $data = json_decode($input, true); // --- Input Validation --- if (json_last_error() !== JSON_ERROR_NONE) { sendErrorResponse('Invalid JSON payload.', 400); } $imageBase64 = $data['image_base64'] ?? null; $prompt = $data['prompt'] ?? null; $apiOptions = $data['api_options'] ?? []; if (empty($imageBase64) || !is_string($imageBase64) || !str_starts_with($imageBase64, 'data:image/')) { sendErrorResponse('Invalid or missing image_base64. Must be a base64 encoded image data URL.', 400); } if (empty($prompt) || !is_string($prompt) || trim($prompt) === '') { sendErrorResponse('Prompt cannot be empty.', 400); } if (!is_array($apiOptions)) { sendErrorResponse('Invalid api_options. Must be an object/array.', 400); } error_log("Received generation request: " . json_encode([ 'prompt' => $prompt, 'image_size' => round(strlen($imageBase64) / 1024) . ' KB', 'api_options' => $apiOptions ])); // --- ACTUAL RUNWAY API INTEGRATION --- try { // --- CONFIGURE THIS SECTION WITH YOUR ACTUAL RUNWAY GEN4 API DETAILS --- // Refer to RunwayML's official Gen4 API documentation for the correct model ID and parameters. // Endpoint for Image to Video as per documentation: $runwayApiEndpoint = 'https://api.dev.runwayml.com/v1/image_to_video'; // Validate image size (base64 data URI should be under 5MB) $imageSizeBytes = strlen($imageBase64); if ($imageSizeBytes > 5 * 1024 * 1024) { throw new Exception('Image too large. Base64 data URI must be under 5MB.'); } // Validate the data URI format if (!preg_match('/^data:image\/(jpeg|jpg|png|webp);base64,/', $imageBase64)) { throw new Exception('Invalid image format. Must be JPEG, PNG, or WebP.'); } $runwayPayload = [ 'promptImage' => $imageBase64, // Send full data URL format 'promptText' => $prompt, 'model' => 'gen4_turbo', 'ratio' => $apiOptions['ratio'] ?? '1280:720' // Required parameter ]; // Add optional parameters if provided if (isset($apiOptions['duration']) && $apiOptions['duration'] !== null) { $runwayPayload['duration'] = (int)$apiOptions['duration']; } if (isset($apiOptions['seed']) && $apiOptions['seed'] !== null) { $runwayPayload['seed'] = (int)$apiOptions['seed']; } // Log payload without the full image data to avoid huge logs $logPayload = $runwayPayload; $logPayload['promptImage'] = substr($imageBase64, 0, 50) . '... (' . strlen($imageBase64) . ' chars)'; error_log("Sending to Runway API: " . json_encode($logPayload)); $ch = curl_init($runwayApiEndpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return response as string curl_setopt($ch, CURLOPT_POST, true); // Set as POST request curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($runwayPayload)); // Encode payload to JSON curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Authorization: Bearer ' . RUNWAY_API_KEY, // Use your RUNWAY_API_KEY from .env 'X-Runway-Version: 2024-11-06' // Required API version header ]); // Optional: for debugging cURL // curl_setopt($ch, CURLOPT_VERBOSE, true); // $verbose = fopen('php://temp', 'rw+'); // curl_setopt($ch, CURLOPT_STDERR, $verbose); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); curl_close($ch); if ($response === false) { throw new Exception("cURL error connecting to Runway API: " . $curlError); } $runwayResult = json_decode($response, true); if ($httpCode >= 400) { // Attempt to extract more specific error message from Runway API response $errorMessage = 'Unknown Runway API error'; if (isset($runwayResult['error']['message'])) { $errorMessage = $runwayResult['error']['message']; } elseif (isset($runwayResult['message'])) { $errorMessage = $runwayResult['message']; } throw new Exception("Runway API returned error ($httpCode): " . $errorMessage . ". Full response: " . json_encode($runwayResult)); } // --- Handle Runway's async response --- $jobId = $runwayResult['id'] ?? null; if (empty($jobId)) { throw new Exception('Runway API did not return a job ID. Full response: ' . json_encode($runwayResult)); } // For async generation, return job ID for polling echo json_encode([ 'status' => 'processing', 'job_id' => $jobId, 'message' => 'Video generation started. Please wait...' ]); } catch (Exception $e) { error_log("Backend processing error: " . $e->getMessage()); sendErrorResponse("Failed to generate video: " . $e->getMessage(), 500); }