Spaces:
Paused
Paused
| // analyze.php β Understand the video, then generate a unique micro-game concept | |
| // 1. Transcribe audio (local whisper or HF Whisper API) | |
| // 2. Gather OCR + metadata + transcript as evidence | |
| // 3. LLM understands what the video is ABOUT and designs a unique game | |
| require_once __DIR__ . '/config.php'; | |
| ensure_dirs(); | |
| $id = $_POST['id'] ?? $_GET['id'] ?? null; | |
| $format = $_POST['format'] ?? $_GET['format'] ?? 'arcade'; | |
| if (!$id || !preg_match('/^[a-f0-9]{16}$/', $id)) { | |
| fail_json('Invalid id'); | |
| } | |
| $receiptPath = RECEIPT_DIR . "/$id/receipt.json"; | |
| $receipt = read_json_file($receiptPath); | |
| if (!$receipt) { | |
| fail_json('Receipt not found', 404); | |
| } | |
| $hfToken = getenv('HF_TOKEN') ?: ''; | |
| // Accept token from POST body as fallback (for local mode) | |
| $postToken = $_POST['hf_token'] ?? ''; | |
| if (!$hfToken && $postToken) { | |
| $hfToken = $postToken; | |
| // Save for future requests | |
| @file_put_contents(APP_ROOT . '/.hf_token', $postToken); | |
| putenv("HF_TOKEN=$postToken"); | |
| } | |
| // Also try loading from file | |
| if (!$hfToken) { | |
| $tokenFile = APP_ROOT . '/.hf_token'; | |
| if (file_exists($tokenFile)) { | |
| $hfToken = trim(file_get_contents($tokenFile)); | |
| if ($hfToken) putenv("HF_TOKEN=$hfToken"); | |
| } | |
| } | |
| $hfModel = getenv('HF_MODEL') ?: 'meta-llama/Llama-3.1-8B-Instruct'; | |
| $whisperModel = getenv('HF_WHISPER_MODEL') ?: 'openai/whisper-large-v3'; | |
| // βββ 1. TRANSCRIPTION ββββββββββββββββββββββββββββββββ | |
| $transcript = $receipt['transcript'] ?? ''; | |
| $transcriptSource = $receipt['transcript_source'] ?? 'none'; | |
| if (!$transcript && $hfToken) { | |
| $audioPath = RECEIPT_DIR . "/$id/audio.wav"; | |
| if (!file_exists($audioPath)) { | |
| $jobPath = RECEIPT_DIR . "/$id.job.json"; | |
| $job = read_json_file($jobPath); | |
| if ($job && !empty($job['upload_path']) && file_exists($job['upload_path'])) { | |
| $cmd = 'ffmpeg -i ' . shell_arg($job['upload_path']) . ' -vn -acodec pcm_s16le -ar 16000 -ac 1 -y ' . shell_arg($audioPath) . ' 2>/dev/null'; | |
| exec($cmd, $out, $code); | |
| } | |
| } | |
| if (file_exists($audioPath) && filesize($audioPath) > 1000) { | |
| $audioData = file_get_contents($audioPath); | |
| $ch = curl_init("https://router.huggingface.co/hf-inference/models/$whisperModel"); | |
| curl_setopt_array($ch, [ | |
| CURLOPT_RETURNTRANSFER => true, | |
| CURLOPT_POST => true, | |
| CURLOPT_HTTPHEADER => [ | |
| 'Authorization: Bearer ' . $hfToken, | |
| 'Content-Type: application/octet-stream' | |
| ], | |
| CURLOPT_POSTFIELDS => $audioData, | |
| CURLOPT_TIMEOUT => 120 | |
| ]); | |
| $raw = curl_exec($ch); | |
| $err = curl_error($ch); | |
| $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
| curl_close($ch); | |
| if ($raw && $status >= 200 && $status < 300) { | |
| $decoded = json_decode($raw, true); | |
| if (isset($decoded['text'])) { | |
| $transcript = $decoded['text']; | |
| $transcriptSource = 'hf_whisper'; | |
| $receipt['transcript'] = $transcript; | |
| $receipt['transcript_source'] = $transcriptSource; | |
| write_json_file($receiptPath, $receipt); | |
| } | |
| } | |
| } | |
| } | |
| // βββ 2. GATHER EVIDENCE ββββββββββββββββββββββββββββββ | |
| $ocrText = ''; | |
| if (!empty($receipt['ocr_samples'])) { | |
| $ocrText = implode("\n", array_map(fn($o) => ($o['text'] ?? ''), array_slice($receipt['ocr_samples'], 0, 20))); | |
| } | |
| $metadata = sprintf( | |
| "Duration: %.1fs, Resolution: %dx%d, Keyframes: %d, OCR blocks: %d, Video: %s", | |
| $receipt['duration'] ?? 0, | |
| $receipt['width'] ?? 0, | |
| $receipt['height'] ?? 0, | |
| $receipt['keyframe_count'] ?? 0, | |
| count($receipt['ocr_samples'] ?? []), | |
| $receipt['videoName'] ?? 'unknown' | |
| ); | |
| // βββ 3. LLM: UNDERSTAND VIDEO + DESIGN GAME ββββββββββ | |
| $prompt = "You are a creative game designer. You analyze video evidence to understand what a video is ABOUT, then design a unique micro-game inspired by that content.\n\n" | |
| . "The game must be:\n" | |
| . "- A real, playable browser game (HTML5 canvas or DOM)\n" | |
| . "- UNIQUE to this specific video β not a template\n" | |
| . "- Inspired by the video's subject matter, not a display of its contents\n" | |
| . "- Playable in under 2 minutes\n" | |
| . "- Fun and replayable\n\n" | |
| . "First, understand what the video is about from the evidence below.\n" | |
| . "Then design a game that captures the ESSENCE of what's happening.\n\n" | |
| . "Return STRICT JSON:\n" | |
| . "{\n" | |
| . " \"understanding\": \"What this video is about (2-3 sentences)\",\n" | |
| . " \"game_title\": \"Catchy game name\",\n" | |
| . " \"game_concept\": \"Core game loop in one sentence\",\n" | |
| . " \"game_mechanic\": \"How the game works β controls, scoring, win/lose\",\n" | |
| . " \"game_theme\": \"Visual theme derived from video content\",\n" | |
| . " \"game_genre\": \"arcade|puzzle|action|strategy|simulation|clicker\",\n" | |
| . " \"game_colors\": {\"bg\": \"#hex\", \"primary\": \"#hex\", \"accent\": \"#hex\", \"text\": \"#hex\"},\n" | |
| . " \"game_entities\": [\"List of game objects/characters derived from video content\"],\n" | |
| . " \"game_scoring\": \"How score works\",\n" | |
| . " \"game_difficulty\": \"How difficulty scales\",\n" | |
| . " \"game_win\": \"Win condition\",\n" | |
| . " \"game_lose\": \"Lose condition\",\n" | |
| . " \"game_powerups\": [\"Optional powerups if any\"],\n" | |
| . " \"inspiration\": \"How the game connects to the video content\"\n" | |
| . "}\n\n" | |
| . "VIDEO EVIDENCE:\n" | |
| . "Metadata: $metadata\n" | |
| . "Transcript: " . substr($transcript, 0, 2000) . "\n" | |
| . "OCR (screen text): " . substr($ocrText, 0, 2000) . "\n"; | |
| $analysis = [ | |
| 'understanding' => '', | |
| 'game_title' => 'MicroGame', | |
| 'game_concept' => '', | |
| 'game_mechanic' => '', | |
| 'game_theme' => '', | |
| 'game_genre' => 'arcade', | |
| 'game_colors' => ['bg' => '#0a0a0f', 'primary' => '#ff8c32', 'accent' => '#3aff7a', 'text' => '#eee8df'], | |
| 'game_entities' => [], | |
| 'game_scoring' => '', | |
| 'game_difficulty' => '', | |
| 'game_win' => '', | |
| 'game_lose' => '', | |
| 'game_powerups' => [], | |
| 'inspiration' => '', | |
| 'transcript' => $transcript, | |
| 'transcript_source' => $transcriptSource | |
| ]; | |
| if ($hfToken) { | |
| $payload = [ | |
| 'model' => $hfModel, | |
| 'messages' => [ | |
| ['role' => 'system', 'content' => 'You are a creative game designer. You analyze video evidence and design unique micro-games. Always respond with valid JSON only, no markdown.'], | |
| ['role' => 'user', 'content' => $prompt] | |
| ], | |
| 'max_tokens' => 1000, | |
| 'temperature' => 0.8, | |
| 'top_p' => 0.92 | |
| ]; | |
| $ch = curl_init("https://router.huggingface.co/v1/chat/completions"); | |
| curl_setopt_array($ch, [ | |
| CURLOPT_RETURNTRANSFER => true, | |
| CURLOPT_POST => true, | |
| CURLOPT_HTTPHEADER => [ | |
| 'Authorization: Bearer ' . $hfToken, | |
| 'Content-Type: application/json' | |
| ], | |
| CURLOPT_POSTFIELDS => json_encode($payload), | |
| CURLOPT_TIMEOUT => 120 | |
| ]); | |
| $raw = curl_exec($ch); | |
| $err = curl_error($ch); | |
| $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
| curl_close($ch); | |
| if ($raw && $status >= 200 && $status < 300) { | |
| $decoded = json_decode($raw, true); | |
| $text = ''; | |
| // OpenAI chat completions format | |
| if (isset($decoded['choices'][0]['message']['content'])) { | |
| $text = $decoded['choices'][0]['message']['content']; | |
| } elseif (isset($decoded['choices'][0]['text'])) { | |
| $text = $decoded['choices'][0]['text']; | |
| } elseif (isset($decoded[0]['generated_text'])) { | |
| $text = $decoded[0]['generated_text']; | |
| } | |
| if ($text) { | |
| $jsonStart = strpos($text, '{'); | |
| $jsonEnd = strrpos($text, '}'); | |
| if ($jsonStart !== false && $jsonEnd !== false && $jsonEnd > $jsonStart) { | |
| $maybe = json_decode(substr($text, $jsonStart, $jsonEnd - $jsonStart + 1), true); | |
| if (is_array($maybe)) { | |
| $analysis = array_merge($analysis, $maybe); | |
| } | |
| } | |
| $analysis['hf_raw'] = $text; | |
| } | |
| } else { | |
| $analysis['hf_warning'] = $err ?: "HF status $status"; | |
| } | |
| } else { | |
| $analysis['hf_warning'] = 'HF_TOKEN not set β using fallback. Set HF_TOKEN for AI-designed unique games.'; | |
| } | |
| $analysisPath = RECEIPT_DIR . "/$id/analysis.json"; | |
| write_json_file($analysisPath, $analysis); | |
| json_response([ | |
| 'ok' => true, | |
| 'id' => $id, | |
| 'analysis' => $analysis | |
| ]); | |