=h($m['title'])?>
=nl2br(h($m['description']), false)?>
diff --git a/index.php b/index.php index 99e60de..283824b 100644 --- a/index.php +++ b/index.php @@ -3,8 +3,16 @@ date_default_timezone_set('Europe/Copenhagen'); $config = require __DIR__ . "/config.php"; foreach (["videos_dir", "thumbs_dir", "uploads_dir"] as $d) if (!is_dir($config[$d])) mkdir($config[$d], 0775, true); if (!is_dir(__DIR__ . "/cache")) mkdir(__DIR__ . "/cache", 0775, true); + function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, "UTF-8"); } -function nice_title($s){ return trim(ucwords(str_replace(['_','-'], ' ', $s))); } +function nice_title($s){ return trim(ucwords(str_replace(['_','-'], ' ', (string)$s))); } +function lower_text($s){ return function_exists('mb_strtolower') ? mb_strtolower((string)$s, 'UTF-8') : strtolower((string)$s); } +function keep_non_empty(array $params): array { return array_filter($params, fn($v)=>$v !== '' && $v !== null); } +function query_url(array $params): string { + $params = keep_non_empty($params); + return $params ? ('?' . http_build_query($params)) : 'index.php'; +} + function hidden_video_outputs($config){ $hidden = []; foreach (glob($config['uploads_dir'].'/*', GLOB_ONLYDIR) ?: [] as $dir) { @@ -14,34 +22,47 @@ function hidden_video_outputs($config){ } return $hidden; } + function video_metadata($path){ $base = pathinfo($path, PATHINFO_FILENAME); - $meta = ['title' => nice_title($base), 'date' => date('Y-m-d', filemtime($path)), 'sort_date' => date('Y-m-d', filemtime($path)), 'location' => '', 'description' => '']; + $meta = [ + 'title' => nice_title($base), + 'date' => date('Y-m-d', filemtime($path)), + 'sort_date' => date('Y-m-d', filemtime($path)), + 'location' => '', + 'description' => '' + ]; + if (preg_match('/^(\d{4})(\d{2})(\d{2})[_-](.+)$/', $base, $m)) { $meta['date'] = "$m[1]-$m[2]-$m[3]"; $meta['sort_date'] = "$m[1]-$m[2]-$m[3]"; $meta['title'] = nice_title($m[4]); } + foreach ([dirname($path).'/'.$base.'.txt', dirname($path).'/'.$base.'.data.txt'] as $sidecar) { if (!is_file($sidecar)) continue; foreach (file($sidecar, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) { if (!str_contains($line, ':')) continue; [$k, $v] = array_map('trim', explode(':', $line, 2)); $k = strtolower($k); - if (in_array($k, ['title','date','location','place','description'], true)) $meta[$k === 'place' ? 'location' : $k] = $v; + if (in_array($k, ['title','date','location','place','description'], true)) { + $meta[$k === 'place' ? 'location' : $k] = $v; + } } } + $ffprobe = trim((string)shell_exec('command -v ffprobe 2>/dev/null')); if ($ffprobe !== '') { $json = shell_exec(escapeshellarg($ffprobe).' -v quiet -print_format json -show_entries format_tags '.escapeshellarg($path).' 2>/dev/null'); $data = json_decode((string)$json, true); $tags = $data['format']['tags'] ?? []; $lower = []; - foreach ($tags as $k => $v) $lower[strtolower($k)] = $v; + foreach ($tags as $k => $v) $lower[strtolower((string)$k)] = $v; + if (!empty($lower['title'])) $meta['title'] = $lower['title']; if (!empty($lower['date'])) $meta['date'] = $lower['date']; if (!empty($lower['creation_time'])) { - $meta['sort_date'] = substr($lower['creation_time'], 0, 10); + $meta['sort_date'] = substr((string)$lower['creation_time'], 0, 10); if (empty($lower['date'])) $meta['date'] = $meta['sort_date']; } foreach (['location','place','location-eng','com.apple.quicktime.location.name','com.apple.quicktime.location.iso6709'] as $k) { @@ -51,8 +72,10 @@ function video_metadata($path){ if (!empty($lower[$k])) { $meta['description'] = $lower[$k]; break; } } } + return $meta; } + function load_video_cache($videos){ $cacheFile = __DIR__ . '/cache/videos.json'; $cache = is_file($cacheFile) ? json_decode((string)file_get_contents($cacheFile), true) : []; @@ -60,6 +83,7 @@ function load_video_cache($videos){ $items = $cache['items'] ?? []; $newItems = []; $changed = false; + foreach ($videos as $path) { $key = basename($path); $mtime = filemtime($path); @@ -71,41 +95,174 @@ function load_video_cache($videos){ } $newItems[$key] = $cached; } + if (array_diff(array_keys($items), array_keys($newItems))) $changed = true; + if ($changed) { - file_put_contents($cacheFile, json_encode(['version' => 2, 'generated_at' => date('c'), 'items' => $newItems], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX); + file_put_contents($cacheFile, json_encode([ + 'version' => 2, + 'generated_at' => date('c'), + 'items' => $newItems + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX); } + return $newItems; } + $hiddenOutputs = hidden_video_outputs($config); -$videos = array_values(array_filter(glob($config['videos_dir'].'/*.{mp4,webm,mov,m4v}', GLOB_BRACE) ?: [], fn($v)=>empty($hiddenOutputs[basename($v)]) && !preg_match('/_preview\.(mp4|webm|mov|m4v)$/i', basename($v)))); +$videos = array_values(array_filter( + glob($config['videos_dir'].'/*.{mp4,webm,mov,m4v}', GLOB_BRACE) ?: [], + fn($v)=>empty($hiddenOutputs[basename($v)]) && !preg_match('/_preview\.(mp4|webm|mov|m4v)$/i', basename($v)) +)); + $videoCache = load_video_cache($videos); + usort($videos, function($a, $b) use ($videoCache) { $am = $videoCache[basename($a)] ?? []; $bm = $videoCache[basename($b)] ?? []; - $ad = strtotime($am['metadata']['sort_date'] ?? $am['metadata']['date'] ?? '') ?: filemtime($a); - $bd = strtotime($bm['metadata']['sort_date'] ?? $bm['metadata']['date'] ?? '') ?: filemtime($b); + $ad = strtotime((string)($am['metadata']['sort_date'] ?? $am['metadata']['date'] ?? '')) ?: filemtime($a); + $bd = strtotime((string)($bm['metadata']['sort_date'] ?? $bm['metadata']['date'] ?? '')) ?: filemtime($b); return $bd <=> $ad; }); -$page=max(1,(int)($_GET['page']??1)); $per=$config['items_per_page']; $total=count($videos); $pages=max(1,(int)ceil($total/$per)); $page=min($page,$pages); $slice=array_slice($videos,($page-1)*$per,$per); + +$filters = [ + 'f_title' => trim((string)($_GET['f_title'] ?? '')), + 'f_date' => trim((string)($_GET['f_date'] ?? '')), + 'f_location' => trim((string)($_GET['f_location'] ?? '')), +]; +$titleExact = !empty($_GET['f_exact']) && (string)$_GET['f_exact'] === '1'; + +// Backward compatibility with old push URL format (?job=...) +$legacyJob = trim((string)($_GET['job'] ?? '')); +if ($filters['f_title'] === '' && $legacyJob !== '') { + $legacy = preg_replace('/^\d{8}[_-]/', '', $legacyJob); + $filters['f_title'] = nice_title($legacy ?: $legacyJob); +} + +$videos = array_values(array_filter($videos, function($v) use ($videoCache, $filters) { + $name = basename($v); + $m = $videoCache[$name]['metadata'] ?? video_metadata($v); + + $title = (string)($m['title'] ?? ''); + $date = (string)($m['date'] ?? ''); + $sortDate = (string)($m['sort_date'] ?? ''); + $location = (string)($m['location'] ?? ''); + + if ($filters['f_title'] !== '') { + if ($titleExact) { + if (lower_text(trim($title)) !== lower_text(trim($filters['f_title']))) return false; + } else { + if (stripos($title, $filters['f_title']) === false) return false; + } + } + if ($filters['f_date'] !== '' && stripos($date, $filters['f_date']) === false && stripos($sortDate, $filters['f_date']) === false) return false; + if ($filters['f_location'] !== '' && stripos($location, $filters['f_location']) === false) return false; + + return true; +})); + +$page = max(1, (int)($_GET['page'] ?? 1)); +$per = $config['items_per_page']; +$total = count($videos); +$pages = max(1, (int)ceil($total / $per)); +$page = min($page, $pages); +$slice = array_slice($videos, ($page - 1) * $per, $per); + +$activeFilterCount = count(keep_non_empty($filters)); +$baseParams = keep_non_empty($filters); +if ($titleExact && ($filters['f_title'] ?? '') !== '') $baseParams['f_exact'] = '1'; ?> -
No videos yet.
=nl2br(h($m['description']), false)?>