runway-video/backend/api.php
2025-09-04 05:53:16 +05:30

158 lines
5.8 KiB
PHP

<?php
// backend/api.php
require_once __DIR__ . '/config.php'; // Load environment variables and set CORS headers
/**
* Handles error responses for the API.
* @param string $message The error message.
* @param int $statusCode The HTTP status code (default 500).
*/
function sendErrorResponse($message, $statusCode = 500) {
http_response_code($statusCode);
echo json_encode(['status' => '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);
}