videoapp-compiler / api /analyze.php
josephrw's picture
Upload api/analyze.php with huggingface_hub
c076088 verified
Raw
History Blame Contribute Delete
8.74 kB
<?php
// 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
]);