$tmpName) { $originalName = $_FILES['files']['name'][$i]; $ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION)); if ($ext !== 'png') continue; if ($_FILES['files']['error'][$i] !== UPLOAD_ERR_OK) continue; $safeName = preg_replace('/[^a-zA-Z0-9._-]/', '_', $originalName); $destPath = $batchDir . $safeName; move_uploaded_file($tmpName, $destPath); $uploaded[] = [ 'name' => $originalName, 'file' => $safeName, 'batchId' => $batchId, 'preview' => 'uploads/' . $batchId . '/' . $safeName, ]; } } echo json_encode(['success' => true, 'files' => $uploaded, 'batchId' => $batchId ?? null]); exit; } if ($_POST['action'] === 'generate') { $groups = json_decode($_POST['groups'], true); $quality = intval($_POST['quality'] ?? 256); $batchId = $_POST['batchId'] ?? ''; if (!$groups || !$batchId) { echo json_encode(['success' => false, 'error' => 'Missing groups or batchId']); exit; } $results = processGroups($groups, $batchId, $quality); echo json_encode(['success' => true, 'results' => $results]); exit; } } // ────────────────────────────────────────────── // JSON API (Figma plugin / scripts) // ────────────────────────────────────────────── // POST /api.php with JSON body or multipart with json field if ($_SERVER['REQUEST_METHOD'] === 'POST' && !isset($_POST['action'])) { $json = null; // Check for JSON body $rawBody = file_get_contents('php://input'); if ($rawBody) { $json = json_decode($rawBody, true); } // Check for multipart with 'json' field + file uploads if (!$json && isset($_POST['json'])) { $json = json_decode($_POST['json'], true); } if ($json && isset($json['groups'])) { $batchId = uniqid('api_'); $batchDir = $uploadDir . $batchId . '/'; mkdir($batchDir, 0755, true); $quality = intval($json['quality'] ?? 256); $groups = $json['groups'] ?? []; // Handle file uploads (multipart) — files[] keyed by original name if (isset($_FILES['files'])) { foreach ($_FILES['files']['tmp_name'] as $i => $tmpName) { $originalName = $_FILES['files']['name'][$i]; $ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION)); if ($ext !== 'png') continue; if ($_FILES['files']['error'][$i] !== UPLOAD_ERR_OK) continue; $safeName = preg_replace('/[^a-zA-Z0-9._-]/', '_', $originalName); move_uploaded_file($tmpName, $batchDir . $safeName); } } // Handle base64 encoded files from JSON body if (isset($json['files'])) { foreach ($json['files'] as $fileName => $base64Data) { $safeName = preg_replace('/[^a-zA-Z0-9._-]/', '_', $fileName); $data = base64_decode($base64Data); if ($data !== false) { file_put_contents($batchDir . $safeName, $data); } } } $results = processGroups($groups, $batchId, $quality); // For API responses, include base64 GIF data so caller doesn't need a second request $baseUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['SCRIPT_NAME']); foreach ($results as &$r) { if ($r['success'] && isset($r['url'])) { $filePath = __DIR__ . '/' . $r['url']; if (file_exists($filePath)) { $r['base64'] = base64_encode(file_get_contents($filePath)); $r['download_url'] = rtrim($baseUrl, '/') . '/' . $r['url']; } } } echo json_encode(['success' => true, 'batchId' => $batchId, 'results' => $results]); exit; } } // ────────────────────────────────────────────── // Download // ────────────────────────────────────────────── if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['download'])) { $file = $outputDir . basename(dirname($_GET['download'])) . '/' . basename($_GET['download']); if (file_exists($file) && pathinfo($file, PATHINFO_EXTENSION) === 'gif') { header('Content-Type: image/gif'); header('Content-Disposition: attachment; filename="' . basename($file) . '"'); readfile($file); exit; } http_response_code(404); echo json_encode(['error' => 'File not found']); exit; } http_response_code(400); echo json_encode(['error' => 'Invalid request']); // ────────────────────────────────────────────── // Shared encoder logic // ────────────────────────────────────────────── function processGroups($groups, $batchId, $quality) { global $uploadDir, $outputDir, $pythonBin, $encoderScript; $batchDir = $uploadDir . basename($batchId) . '/'; if (!is_dir($batchDir)) return [['success' => false, 'error' => 'Batch not found']]; $outputBatchDir = $outputDir . basename($batchId) . '/'; if (!is_dir($outputBatchDir)) mkdir($outputBatchDir, 0755, true); $results = []; foreach ($groups as $groupName => $groupData) { $files = $groupData['files'] ?? []; $loop = $groupData['loop'] ?? true; if (empty($files)) continue; // Per-frame delays: array or single value $delays = $groupData['delays'] ?? []; $defaultDelay = intval($groupData['delay'] ?? 500); $inputPaths = []; foreach ($files as $file) { $path = $batchDir . basename($file); if (file_exists($path)) { $inputPaths[] = escapeshellarg($path); } } if (empty($inputPaths)) { $results[] = ['group' => $groupName, 'success' => false, 'error' => 'No valid files']; continue; } $safeGroupName = preg_replace('/[^a-zA-Z0-9._-]/', '_', $groupName); $outputFile = $outputBatchDir . $safeGroupName . '.gif'; $cmd = escapeshellarg($pythonBin) . ' ' . escapeshellarg($encoderScript) . ' --input ' . implode(' ', $inputPaths) . ' --output ' . escapeshellarg($outputFile) . ' --quality ' . intval($quality); // Use per-frame delays if provided, otherwise uniform if (!empty($delays) && is_array($delays)) { $delayStr = implode(',', array_map('intval', $delays)); $cmd .= ' --delays ' . escapeshellarg($delayStr); } else { $cmd .= ' --delay ' . intval($defaultDelay); } if (!$loop) { $cmd .= ' --no-loop'; } $cmd .= ' 2>&1'; $output = shell_exec($cmd); $result = json_decode($output, true); if ($result && $result['success']) { $results[] = [ 'group' => $groupName, 'success' => true, 'url' => 'output/' . basename($batchId) . '/' . $safeGroupName . '.gif', 'frames' => $result['frames'], ]; } else { $results[] = [ 'group' => $groupName, 'success' => false, 'error' => $result['error'] ?? $output ?? 'Unknown error', ]; } } return $results; }