'error','message'=>'job not found']); exit; } // Read existing data.txt for Title and Location (case-insensitive aliases) $data_file = $jobdir . '/data.txt'; $title = ''; $location = ''; if (is_file($data_file)) { $lines = file($data_file, FILE_IGNORE_NEW_LINES); $i = 0; while ($i < count($lines)) { $raw = $lines[$i]; $line = trim($raw); if ($line === '' || str_starts_with($line, '#')) { $i++; continue; } if (!str_contains($line, ':')) { $i++; continue; } [$key, $value] = array_map('trim', explode(':', $line, 2)); $low = strtolower($key); if ($value === '|' && in_array($low, ['description','desc','synopsis'], true)) { // Skip indented block $i++; while ($i < count($lines)) { $next = $lines[$i]; if (trim($next) !== '' && $next[0] !== ' ' && $next[0] !== "\t") break; $i++; } continue; } if (in_array($low, ['title','name'], true) && $title === '') $title = $value; if (in_array($low, ['place','location','where'], true) && $location === '') $location = $value; $i++; } } // Run generator synchronously but in no-append mode so it doesn't modify data.txt directly. $script = $MVLOG_ROOT . '/bin/generate_data.sh'; if (!is_file($script)) { http_response_code(500); echo json_encode(['status' => 'error', 'message' => 'generate_data.sh not found']); exit; } // Build command and execute. Use no-append flag as last argument. $cmd = escapeshellcmd($script) . ' ' . escapeshellarg($jobdir) . ' ' . escapeshellarg($title) . ' ' . escapeshellarg($location) . ' ' . escapeshellarg((string)$max_frames) . ' ' . escapeshellarg('no-append') . ' 2>&1'; // Allow long-running @set_time_limit(0); $gfile = dirname($jobdir) . '/' . basename($jobdir) . '.json'; $gfileExistedBefore = is_file($gfile); $gfileMtimeBefore = $gfileExistedBefore ? @filemtime($gfile) : null; exec($cmd, $out_lines, $rc); $log = implode("\n", $out_lines); clearstatcache(true, $gfile); if ($rc !== 0) { http_response_code(500); echo json_encode([ 'status' => 'error', 'message' => 'generator failed', 'log' => $log, 'rc' => $rc, 'output_file' => basename($gfile), 'stale_output_present' => is_file($gfile), ], JSON_UNESCAPED_UNICODE); exit; } if (!is_file($gfile)) { http_response_code(500); echo json_encode([ 'status' => 'error', 'message' => 'no gemini output', 'log' => $log, 'rc' => $rc, ], JSON_UNESCAPED_UNICODE); exit; } // If gemini_generated.json exists, parse and return if (is_file($gfile)) { $raw = @file_get_contents($gfile); $json_data = @json_decode($raw, true); // Handle case where Gemini returns an array of candidates or a single object. // We will always take the first valid description object we can find. $json = null; if (is_array($json_data) && isset($json_data[0]) && is_array($json_data[0])) { $json = $json_data[0]; // It's an array of descriptions, take the first. } elseif (is_array($json_data)) { $json = $json_data; // It's already a single description object. } $description = ''; $teaser = ''; $tags = []; if (is_array($json)) { $description = isset($json['description']) ? (string)$json['description'] : ''; $teaser = isset($json['teaser']) ? (string)$json['teaser'] : ''; $tags = isset($json['tags']) && is_array($json['tags']) ? $json['tags'] : []; } echo json_encode([ 'status' => 'ok', 'description' => $description, 'teaser' => $teaser, 'tags' => $tags, 'raw_json' => $json_data, // Send original raw data back for reference 'log' => $log, 'rc' => $rc, ], JSON_UNESCAPED_UNICODE); exit; } // No gemini file — return error and include log http_response_code(500); echo json_encode(['status'=>'error','message'=>'no gemini output','log'=>$log,'rc'=>$rc], JSON_UNESCAPED_UNICODE); exit;