'error','message'=>'invalid job']); exit; } $jobdir = $MVLOG_ROOT . '/in-dir/' . $job; if (!is_dir($jobdir)) { http_response_code(404); echo json_encode(['status'=>'error','message'=>'job not found']); exit; } $describe_prompt_file = $jobdir . '/.mvlog-describe-prompt.txt'; if ($additional_prompt !== '') { @file_put_contents($describe_prompt_file, $additional_prompt . PHP_EOL); @chmod($describe_prompt_file, 0664); @chown($describe_prompt_file, 'www-data'); @chgrp($describe_prompt_file, 'www-data'); } // 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++; } } // If no location in data.txt, attempt to extract GPS from media files and reverse-geocode with Nominatim. if ($location === '') { $ffprobe = file_exists($MVLOG_ROOT . '/ffmpeg/ffprobe') ? $MVLOG_ROOT . '/ffmpeg/ffprobe' : trim((string)shell_exec('command -v ffprobe 2>/dev/null')); if ($ffprobe) { $candidates = array_values(array_filter(scandir($jobdir), function($f) use ($jobdir) { return is_file($jobdir . '/' . $f); })); usort($candidates, 'strnatcasecmp'); $last_country = ''; $exts = ['jpg','jpeg','png','webp','gif','mp4','mov','m4v','webm','mkv']; foreach ($candidates as $f) { $ext = strtolower(pathinfo($f, PATHINFO_EXTENSION)); if (!in_array($ext, $exts, true)) continue; $path = $jobdir . '/' . $f; $cmd = escapeshellcmd($ffprobe) . ' -v error -show_format -show_streams -of json ' . escapeshellarg($path) . ' 2>/dev/null'; $out = @shell_exec($cmd); if (!$out) continue; $json = json_decode($out, true); if (!is_array($json)) continue; $values = []; $collect = function($obj) use (&$collect, &$values) { if (is_array($obj)) { foreach ($obj as $k => $v) { if (is_string($k) && (stripos($k, 'location') !== false || stripos($k, 'gps') !== false)) { if (is_string($v)) $values[] = $v; } $collect($v); } } }; $collect($json); if (empty($values)) continue; foreach ($values as $val) { $lat = $lon = null; if (preg_match('/([+\\-]\d+(?:\\.\\d+)?)([+\\-]\d+(?:\\.\\d+)?)/', $val, $m)) { $lat = floatval($m[1]); $lon = floatval($m[2]); } elseif (preg_match('/([+\\-]?\\d+(?:\\.\\d+)?)[,;\\s]+([+\\-]?\\d+(?:\\.\\d+)?)/', $val, $m)) { $lat = floatval($m[1]); $lon = floatval($m[2]); } else { continue; } for ($zoom = 18; $zoom >= 3; $zoom--) { $query = http_build_query([ 'format' => 'jsonv2', 'lat' => sprintf('%.8f', $lat), 'lon' => sprintf('%.8f', $lon), 'zoom' => $zoom, 'addressdetails' => 1, ]); $opts = ['http' => ['header' => "User-Agent: movmaker/1.0 (reverse geocoding for local movie metadata)\r\n", 'timeout' => 10]]; $ctx = stream_context_create($opts); $url = 'https://nominatim.openstreetmap.org/reverse?' . $query; $resp = @file_get_contents($url, false, $ctx); if (!$resp) continue; $obj = json_decode($resp, true); if (!is_array($obj) || !isset($obj['address'])) continue; $address = $obj['address']; $country = $address['country'] ?? ''; $last_country = $country ?: $last_country; $city = ''; foreach (['city','town','village'] as $k) { if (!empty($address[$k])) { $city = $address[$k]; break; } } if ($city) { $location = trim($city . ($country ? ', ' . $country : '')); break 3; } } } } if ($location === '') $location = $last_country ?: ''; } } $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; } $log = $jobdir . '/generate_data.log'; $bash = '/bin/bash'; if ($additional_prompt !== '') { $cmd = escapeshellcmd($bash) . ' ' . escapeshellarg($script) . ' ' . escapeshellarg($jobdir) . ' ' . escapeshellarg($title) . ' ' . escapeshellarg($location) . ' ' . escapeshellarg((string)$max_frames) . ' ' . escapeshellarg($additional_prompt) . ' >> ' . escapeshellarg($log) . ' 2>&1 & echo $!'; } else { $cmd = escapeshellcmd($bash) . ' ' . escapeshellarg($script) . ' ' . escapeshellarg($jobdir) . ' ' . escapeshellarg($title) . ' ' . escapeshellarg($location) . ' ' . escapeshellarg((string)$max_frames) . ' ' . escapeshellarg('no-append') . ' >> ' . escapeshellarg($log) . ' 2>&1 & echo $!'; } $pid = trim(@shell_exec($cmd)); echo json_encode(['status' => 'ok', 'pid' => $pid, 'log' => $log, 'message' => 'started']); exit;