'ae','Æ'=>'ae','ø'=>'o','Ø'=>'o','å'=>'aa','Å'=>'aa', 'ä'=>'ae','Ä'=>'ae','ö'=>'oe','Ö'=>'oe','ü'=>'ue','Ü'=>'ue','ß'=>'ss','ẞ'=>'ss', 'č'=>'c','Č'=>'c','ć'=>'c','Ć'=>'c','ž'=>'z','Ž'=>'z','š'=>'s','Š'=>'s','đ'=>'d','Đ'=>'d', ]); if (function_exists('iconv')) { $x = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); if ($x !== false) $s = $x; } $s = lower_text($s); return preg_replace('/[^a-z0-9]+/', ' ', $s) ?? ''; } function ci_contains($haystack, $needle){ $haystack = (string)$haystack; $needle = (string)$needle; if ($needle === '') return true; if (function_exists('mb_stripos') && mb_stripos($haystack, $needle, 0, 'UTF-8') !== false) return true; if (strpos(lower_text($haystack), lower_text($needle)) !== false) return true; $foldHay = fold_text($haystack); $foldNeedle = trim(fold_text($needle)); if ($foldNeedle === '') return false; return strpos($foldHay, $foldNeedle) !== false; } 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) { if (!is_file($dir . '/.mvlog-hidden')) continue; $state = is_file($dir . '/.movmaker-state.json') ? json_decode((string)file_get_contents($dir . '/.movmaker-state.json'), true) : []; if (is_array($state) && !empty($state['output'])) $hidden[basename((string)$state['output'])] = true; } return $hidden; } function map_hidden_outputs($config){ $hidden = []; foreach (glob($config['uploads_dir'].'/*', GLOB_ONLYDIR) ?: [] as $dir) { if (!is_file($dir . '/.mvlog-hide-map')) continue; $state = is_file($dir . '/.movmaker-state.json') ? json_decode((string)file_get_contents($dir . '/.movmaker-state.json'), true) : []; if (is_array($state) && !empty($state['output'])) $hidden[basename((string)$state['output'])] = true; } return $hidden; } function video_thumb_public_url($config, $videoName){ $videoName = basename((string)$videoName); if ($videoName === '') return ''; $base = pathinfo($videoName, PATHINFO_FILENAME); foreach (['jpg','jpeg','png','webp','gif'] as $ext) { $file = $config['thumbs_dir'] . '/' . $base . '.' . $ext; if (is_file($file)) return $config['public_thumbs'] . '/' . rawurlencode($base . '.' . $ext) . '?v=' . filemtime($file); } return ''; } function video_metadata($path){ $base = pathinfo($path, PATHINFO_FILENAME); $meta = [ 'title' => nice_title($base), 'teaser' => '', 'date' => date('Y-m-d', filemtime($path)), 'sort_date' => date('Y-m-d', filemtime($path)), 'location' => '', 'quote_da' => '', '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','teaser','date','location','place','description','quote_da'], true)) { $target = ($k === 'place') ? 'location' : $k; $meta[$target] = $target === 'quote_da' ? trim((string)$v, "\"“”") : $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((string)$k)] = $v; if (!empty($lower['title'])) $meta['title'] = $lower['title']; if (!empty($lower['teaser'])) $meta['teaser'] = $lower['teaser']; if (!empty($lower['date'])) $meta['date'] = $lower['date']; if (!empty($lower['creation_time'])) { $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) { if (!empty($lower[$k])) { $meta['location'] = $lower[$k]; break; } } foreach (['description','synopsis'] as $k) { if (!empty($lower[$k])) { $meta['description'] = $lower[$k]; break; } } if (!empty($lower['quote_da'])) { $meta['quote_da'] = trim((string)$lower['quote_da'], "\"“”"); } } return $meta; } function load_video_cache($videos){ $cacheFile = __DIR__ . '/cache/videos.json'; $cache = is_file($cacheFile) ? json_decode((string)file_get_contents($cacheFile), true) : []; if (!is_array($cache) || ($cache['version'] ?? 0) !== 4) $cache = []; $items = $cache['items'] ?? []; $newItems = []; $changed = false; foreach ($videos as $path) { $key = basename($path); $mtime = filemtime($path); $size = filesize($path); $cached = $items[$key] ?? null; if (!is_array($cached) || ($cached['mtime'] ?? null) !== $mtime || ($cached['size'] ?? null) !== $size) { $cached = ['mtime' => $mtime, 'size' => $size, 'metadata' => video_metadata($path)]; $changed = true; } $newItems[$key] = $cached; } if (array_diff(array_keys($items), array_keys($newItems))) $changed = true; if ($changed) { file_put_contents($cacheFile, json_encode([ 'version' => 4, 'generated_at' => date('c'), 'items' => $newItems ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX); } return $newItems; } $hiddenOutputs = hidden_video_outputs($config); $hiddenMapOutputs = map_hidden_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)) )); $videoCache = load_video_cache($videos); usort($videos, function($a, $b) use ($videoCache) { $am = $videoCache[basename($a)] ?? []; $bm = $videoCache[basename($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; }); $rawKeyword = trim((string)($_GET['q'] ?? '')); // Backward compatibility with older filter/query formats. if ($rawKeyword === '') { $legacyTitle = trim((string)($_GET['f_title'] ?? '')); $legacyDate = trim((string)($_GET['f_date'] ?? '')); $legacyLocation = trim((string)($_GET['f_location'] ?? '')); $legacyJob = trim((string)($_GET['job'] ?? '')); if ($legacyTitle !== '') $rawKeyword = $legacyTitle; elseif ($legacyDate !== '') $rawKeyword = 'date=' . $legacyDate; elseif ($legacyLocation !== '') $rawKeyword = 'location=' . $legacyLocation; elseif ($legacyJob !== '') { $legacy = preg_replace('/^\d{8}[_-]/', '', $legacyJob); $rawKeyword = nice_title($legacy ?: $legacyJob); } } $keywordType = 'general'; $keywordValue = $rawKeyword; if ($rawKeyword !== '' && preg_match('/^\s*(date|location|place)\s*=\s*(.+)\s*$/i', $rawKeyword, $m)) { $keywordType = lower_text((string)$m[1]); if ($keywordType === 'place') $keywordType = 'location'; $keywordValue = trim((string)$m[2]); } $videos = array_values(array_filter($videos, function($v) use ($videoCache, $keywordType, $keywordValue) { if ($keywordValue === '') return true; $name = basename($v); $m = $videoCache[$name]['metadata'] ?? video_metadata($v); $title = (string)($m['title'] ?? ''); $teaser = (string)($m['teaser'] ?? ''); $description = (string)($m['description'] ?? ''); $quoteDa = (string)($m['quote_da'] ?? ''); $date = (string)($m['date'] ?? ''); $sortDate = (string)($m['sort_date'] ?? ''); $location = (string)($m['location'] ?? ''); if ($keywordType === 'date') { return ci_contains($date, $keywordValue) || ci_contains($sortDate, $keywordValue); } if ($keywordType === 'location') { return ci_contains($location, $keywordValue); } return ci_contains($title, $keywordValue) || ci_contains($teaser, $keywordValue) || ci_contains($quoteDa, $keywordValue) || ci_contains($description, $keywordValue); })); $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 = ($keywordValue !== '') ? 1 : 0; $baseParams = ($rawKeyword !== '') ? ['q' => $rawKeyword] : []; $headerTitles = [ 'Moto Vlog', 'Multiple Voices Log', 'MultiVerse Log', 'Miles & Views Log', 'Moving Vistas Log', 'Motorcycle Voyage Log', 'Mapped Vagabond Log', 'Mountain Valley Log', 'Miles Versus Logic', 'Motorcycle Voyage Log', 'Mapped Vagabond Log', 'Multiple Viewpoints Log', 'Multiple Versions of Life', 'Motion, Velocity & Luck', 'Miles, Views & Life', 'Many Voices of Life', 'Many Views of Life', 'Miles Beyond Reality', 'Motion, Velocity & Lunacy', 'Maps, Villages & Legends', 'Multi-Vision Log', 'Mutable View Log', 'Mistakes, Victories & Lessons', 'Mostly Verified Legends', 'Mostly Vague Logistics', 'Marginally Viable Leadership', 'Mileage Versus Luck', 'Mechanical Violence Log', 'Moderately Violent Leisure', 'Mistakes Validated Later', 'Maybe Very Lost', 'Miles, Views & Lunch', 'Maps, Villages & Lager', 'Missing Various Lefts', 'Mistakes, Views & Luck', 'Many Very Longroads', 'Mileage Varies Largely', 'Motorcycling Via Luck', 'Motorcycle Victim Lifestyle', 'Mildly Violating Limits', 'My Vices Last', ]; $siteHeaderTitle = $headerTitles[array_rand($headerTitles)]; ?> MVL
×

Showing article(s) 0): ?> with active filter(s)

No videos found for current filter.

'date=' . (string)($m['date'] ?? '')]); $locFilterUrl = query_url(['q' => 'location=' . (string)($m['location'] ?? '')]); ?>
1) $linkParams['page'] = $i; ?>