diff --git a/assets/fonts/BebasNeue-Regular.ttf b/assets/fonts/BebasNeue-Regular.ttf new file mode 100644 index 0000000..c328c6e Binary files /dev/null and b/assets/fonts/BebasNeue-Regular.ttf differ diff --git a/assets/fonts/IBMPlexSans-SemiBold.ttf b/assets/fonts/IBMPlexSans-SemiBold.ttf new file mode 100644 index 0000000..0dd7347 Binary files /dev/null and b/assets/fonts/IBMPlexSans-SemiBold.ttf differ diff --git a/assets/fonts/NotoSans-Bold.ttf b/assets/fonts/NotoSans-Bold.ttf new file mode 100644 index 0000000..4d923cb Binary files /dev/null and b/assets/fonts/NotoSans-Bold.ttf differ diff --git a/index.php b/index.php index 6af65bb..d67d1e8 100644 --- a/index.php +++ b/index.php @@ -2,14 +2,53 @@ $config = require __DIR__ . "/config.php"; foreach (["videos_dir", "thumbs_dir", "uploads_dir"] as $d) if (!is_dir($config[$d])) mkdir($config[$d], 0775, true); function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, "UTF-8"); } +function nice_title($s){ return trim(ucwords(str_replace(['_','-'], ' ', $s))); } +function video_metadata($path){ + $base = pathinfo($path, PATHINFO_FILENAME); + $meta = ['title' => nice_title($base), '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['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; + } + } + + $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; + if (!empty($lower['title'])) $meta['title'] = $lower['title']; + if (!empty($lower['date'])) $meta['date'] = $lower['date']; + if (!empty($lower['creation_time'])) $meta['date'] = substr($lower['creation_time'], 0, 10); + foreach (['location','place','location-eng','com.apple.quicktime.location.iso6709'] as $k) { + if (!empty($lower[$k])) { $meta['location'] = $lower[$k]; break; } + } + foreach (['description','comment','synopsis'] as $k) { + if (!empty($lower[$k])) { $meta['description'] = $lower[$k]; break; } + } + } + return $meta; +} $videos = glob($config['videos_dir'].'/*.{mp4,webm,mov,m4v}', GLOB_BRACE) ?: []; usort($videos, fn($a,$b)=>filemtime($b)<=>filemtime($a)); $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); ?> MVLog

MVLog

-

Videos

No videos yet.

- - +

No videos yet.

+ +

MB

diff --git a/style.css b/style.css index bfc632c..249ed60 100644 --- a/style.css +++ b/style.css @@ -1 +1 @@ -:root{font-family:system-ui,sans-serif;color:#17202a;background:#f6f7fb}body{margin:0}header{background:#101827;color:white;padding:1.2rem 2rem;text-align:center}a{color:#2563eb}.button,button{background:#2563eb;color:white;border:0;border-radius:.5rem;padding:.7rem 1rem;text-decoration:none;cursor:pointer}nav a{margin-left:1rem;color:white}main{max-width:1100px;margin:2rem auto;padding:0 1rem}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:1rem}.card,form{background:white;border-radius:.8rem;padding:1rem;box-shadow:0 2px 10px #0001}.card video{width:100%;aspect-ratio:16/9;background:#111;border-radius:.5rem}label{display:block;margin:.8rem 0;font-weight:600}input,textarea{display:block;width:100%;box-sizing:border-box;margin-top:.3rem;padding:.65rem;border:1px solid #cbd5e1;border-radius:.45rem}textarea{min-height:6rem}.ok,.err{padding:1rem;border-radius:.5rem;margin-bottom:1rem}.ok{background:#dcfce7}.err{background:#fee2e2}.pages a{display:inline-block;margin:.2rem;padding:.45rem .7rem;background:white;border-radius:.4rem;text-decoration:none}.pages .active{background:#2563eb;color:white}footer{text-align:center;color:#667085;padding:2rem;font-size:.85rem} +@font-face{font-family:BebasNeue;src:url('assets/fonts/BebasNeue-Regular.ttf')}@font-face{font-family:IBMPlexCaption;src:url('assets/fonts/IBMPlexSans-SemiBold.ttf')}@font-face{font-family:NotoStamp;src:url('assets/fonts/NotoSans-Bold.ttf')}:root{font-family:IBMPlexCaption,system-ui,sans-serif;color:#17202a;background:#f6f7fb}body{margin:0}header{background:#101827;color:white;padding:1.2rem 2rem;text-align:center}header h1{font-family:BebasNeue,system-ui,sans-serif;font-size:3rem;letter-spacing:.06em;margin:0}a{color:#2563eb}.button,button{background:#2563eb;color:white;border:0;border-radius:.5rem;padding:.7rem 1rem;text-decoration:none;cursor:pointer}nav a{margin-left:1rem;color:white}main{max-width:1100px;margin:2rem auto;padding:0 1rem}.video-list{display:flex;flex-direction:column;gap:1rem}.video-row,form{background:white;border-radius:.8rem;padding:1rem;box-shadow:0 2px 10px #0001}.video-row{display:grid;grid-template-columns:minmax(280px,42%) 1fr;gap:1rem;align-items:start}.video-row video{width:100%;aspect-ratio:16/9;background:#111;border-radius:.5rem}.video-info h2{font-size:1.6rem;margin:.1rem 0 .5rem}.meta{display:flex;gap:.5rem;flex-wrap:wrap;color:#475569;margin:.25rem 0}.meta span{background:#eef2ff;border-radius:999px;padding:.25rem .55rem}.description{font-size:1rem;line-height:1.45;color:#334155;margin:.9rem 0}.details{color:#667085;font-size:.9rem}label{display:block;margin:.8rem 0;font-weight:600}input,textarea{display:block;width:100%;box-sizing:border-box;margin-top:.3rem;padding:.65rem;border:1px solid #cbd5e1;border-radius:.45rem}textarea{min-height:6rem}.ok,.err{padding:1rem;border-radius:.5rem;margin-bottom:1rem}.ok{background:#dcfce7}.err{background:#fee2e2}.pages a{display:inline-block;margin:.2rem;padding:.45rem .7rem;background:white;border-radius:.4rem;text-decoration:none}.pages .active{background:#2563eb;color:white}footer{font-family:NotoStamp,system-ui,sans-serif;text-align:center;color:#667085;padding:2rem;font-size:.8rem}@media(max-width:760px){.video-row{grid-template-columns:1fr}header h1{font-size:2.4rem}}