No video yet=h($meta['title'] ?: ($info['title'] ?? basename($d)))?>
=nl2br(h($meta['description']), false)?>
=h(basename($d))?> ยท =h($info['file_count'] ?? count(list_files($d)))?> files
'','date'=>'','place'=>'','description'=>'','captions'=>[],'video_audio'=>[]]; $file = $dir . '/data.txt'; if (!is_file($file)) return $data; $lines = file($file, FILE_IGNORE_NEW_LINES); for ($i = 0; $i < count($lines); $i++) { $line = trim($lines[$i]); if ($line === '' || str_starts_with($line, '#') || !str_contains($line, ':')) continue; [$key, $value] = array_map('trim', explode(':', $line, 2)); $low = strtolower($key); if ($value === '|' && in_array($low, ['description','desc','synopsis'], true)) { $block = []; while ($i + 1 < count($lines)) { $next = $lines[$i + 1]; if (trim($next) !== '' && $next[0] !== ' ' && $next[0] !== "\t") break; $i++; if (str_starts_with($next, ' ')) $next = substr($next, 2); elseif (str_starts_with($next, ' ') || str_starts_with($next, "\t")) $next = substr($next, 1); $block[] = rtrim($next); } $data['description'] = trim(implode("\n", $block)); } elseif (in_array($low, ['title','name'], true)) $data['title'] = $value; elseif (in_array($low, ['date','dates','when'], true)) $data['date'] = $value; elseif (in_array($low, ['place','location','where'], true)) $data['place'] = $value; elseif (in_array($low, ['description','desc','synopsis'], true)) $data['description'] = $value; elseif (str_ends_with($low, ' audio')) { $fileKey = trim(substr($key, 0, -6)); if ($fileKey !== '' && in_array(strtolower($value), ['1','yes','true','on'], true)) $data['video_audio'][$fileKey] = true; } elseif ($key !== '') $data['captions'][$key] = $value; } return $data; } function write_data($dir, $post){ $title = trim($post['title'] ?? ''); $place = trim($post['place'] ?? ''); $date = trim($post['date'] ?? ''); $description = trim($post['description'] ?? ''); if (function_exists('mb_substr')) $description = mb_substr($description, 0, 5000, 'UTF-8'); else $description = substr($description, 0, 5000); $lines = []; if ($title !== '') $lines[] = 'Title: ' . $title; if ($place !== '') $lines[] = 'Location: ' . $place; if ($date !== '') $lines[] = 'Date: ' . $date; if ($description !== '') { $lines[] = 'Description: |'; $description = str_replace(["\r\n", "\r"], "\n", $description); foreach (explode("\n", $description) as $descLine) $lines[] = ' ' . rtrim($descLine); } $useAudioFiles = $post['use_audio_files'] ?? []; $useAudio = is_array($useAudioFiles) ? array_flip(array_map('basename', array_map('strval', $useAudioFiles))) : []; foreach (array_keys($useAudio) as $fileName) if ($fileName !== '') $lines[] = $fileName . ' audio: yes'; $captionFiles = $post['caption_files'] ?? []; $captions = $post['captions'] ?? []; if (is_array($captionFiles) && is_array($captions)) { foreach ($captionFiles as $i => $fileName) { $fileName = basename((string)$fileName); $caption = trim((string)($captions[$i] ?? '')); if ($fileName !== '' && $caption !== '') $lines[] = $fileName . ': ' . $caption; } } file_put_contents($dir . '/data.txt', implode("\n", $lines) . "\n"); } function editable_file($f){ $ext = strtolower(pathinfo((string)$f, PATHINFO_EXTENSION)); return $f !== '' && $f[0] !== '.' && $f !== 'data.txt' && in_array($ext, ['jpg','jpeg','png','webp','gif','mp4','mov','m4v','avi','mkv','webm','mp3','wav','m4a','aac','ogg','flac'], true); } function natural_file_compare($a, $b){ return strnatcasecmp((string)$a, (string)$b); } function list_files($dir){ $files = array_values(array_filter(scandir($dir), fn($f)=>editable_file($f) && is_file($dir.'/'.$f))); usort($files, 'natural_file_compare'); return $files; } function media_sort_files($dir, $files){ usort($files, function($a, $b) use ($dir) { $da = media_file_date($dir . '/' . $a) ?? date('Ymd', filemtime($dir . '/' . $a)); $db = media_file_date($dir . '/' . $b) ?? date('Ymd', filemtime($dir . '/' . $b)); return $da === $db ? natural_file_compare($a, $b) : strcmp($da, $db); }); return $files; } function media_file_date($path){ $ext = strtolower(pathinfo((string)$path, PATHINFO_EXTENSION)); if (in_array($ext, ['jpg','jpeg','tif','tiff'], true) && function_exists('exif_read_data')) { $exif = @exif_read_data($path); foreach (['DateTimeOriginal','DateTimeDigitized','DateTime'] as $k) { if (!empty($exif[$k]) && preg_match('/^(\d{4}):(\d{2}):(\d{2})/', (string)$exif[$k], $m)) return "$m[1]$m[2]$m[3]"; } } if (in_array($ext, ['mp4','mov','m4v','avi','mkv','webm'], true)) { $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=creation_time:stream_tags=creation_time '.escapeshellarg($path).' 2>/dev/null'); $data = json_decode((string)$json, true); $candidates = []; if (is_array($data['format']['tags'] ?? null)) $candidates[] = $data['format']['tags']['creation_time'] ?? ''; foreach (($data['streams'] ?? []) as $stream) if (is_array($stream['tags'] ?? null)) $candidates[] = $stream['tags']['creation_time'] ?? ''; foreach ($candidates as $value) { if (preg_match('/^(\d{4})-(\d{2})-(\d{2})/', (string)$value, $m)) return "$m[1]$m[2]$m[3]"; } } } return null; } function input_dir_date_from_media($dir){ $dates = []; foreach (list_files($dir) as $file) { $date = media_file_date($dir . '/' . $file); if ($date !== null) $dates[] = $date; } if ($dates) { sort($dates, SORT_STRING); return $dates[0]; } return date('Ymd'); } function input_dir_date_from_text($text){ $text = trim((string)$text); if ($text === '') return null; if (preg_match('/^(\d{4})[.-]?(\d{2})[.-]?(\d{2})$/', $text, $m) && checkdate((int)$m[2], (int)$m[3], (int)$m[1])) return "$m[1]$m[2]$m[3]"; if (preg_match('/^(\d{1,2})[\/. -](\d{1,2})[\/. -](\d{4})$/', $text, $m) && checkdate((int)$m[2], (int)$m[1], (int)$m[3])) return sprintf('%04d%02d%02d', $m[3], $m[2], $m[1]); $ts = strtotime($text); if ($ts !== false) return date('Ymd', $ts); if (preg_match('/^(jan|january|feb|february|mar|march|apr|april|may|jun|june|jul|july|aug|august|sep|sept|september|oct|october|nov|november|dec|december)\s+(\d{4})$/i', $text, $m)) { $months = ['jan'=>1,'january'=>1,'feb'=>2,'february'=>2,'mar'=>3,'march'=>3,'apr'=>4,'april'=>4,'may'=>5,'jun'=>6,'june'=>6,'jul'=>7,'july'=>7,'aug'=>8,'august'=>8,'sep'=>9,'sept'=>9,'september'=>9,'oct'=>10,'october'=>10,'nov'=>11,'november'=>11,'dec'=>12,'december'=>12]; return sprintf('%04d%02d01', (int)$m[2], $months[strtolower($m[1])]); } return null; } function unique_input_dir($base, $slug){ $dir = $base . '/' . $slug; if (!file_exists($dir)) return $dir; for ($i = 2; ; $i++) { $candidate = $base . '/' . $slug . '-' . $i; if (!file_exists($candidate)) return $candidate; } } function input_dir_signature($dir){ $entries = []; foreach (scandir($dir) ?: [] as $file) { if ($file === '.' || $file === '..' || in_array($file, ['.movmaker-state.json','.movmaker-enabled','.movmaker-preview','.mvlog-hidden'], true)) continue; if ($file === '.movmaker-lock') continue; $path = $dir . '/' . $file; if (is_file($path)) $entries[] = [$file, filesize($path), filemtime($path)]; } sort($entries); return hash('sha256', json_encode($entries, JSON_UNESCAPED_UNICODE)); } function input_dir_info($dir){ $data = read_data($dir); $files = list_files($dir); return [ 'name' => basename($dir), 'title' => $data['title'] ?: basename($dir), 'sort_date' => input_dir_date_from_text($data['date'] ?? '') ?? input_dir_date_from_media($dir), 'file_count' => count($files), 'signature' => input_dir_signature($dir), ]; } function load_input_dir_cache($dirs){ $cacheFile = __DIR__ . '/cache/input-dirs.json'; $cache = is_file($cacheFile) ? json_decode((string)file_get_contents($cacheFile), true) : []; if (!is_array($cache) || ($cache['version'] ?? 0) !== 1) $cache = ['version'=>1,'items'=>[]]; $oldItems = is_array($cache['items'] ?? null) ? $cache['items'] : []; $items = []; $changed = false; foreach ($dirs as $dir) { $name = basename($dir); $sig = input_dir_signature($dir); $cached = $oldItems[$name] ?? null; if (is_array($cached) && ($cached['signature'] ?? '') === $sig) { $items[$name] = $cached; } else { $items[$name] = input_dir_info($dir); $changed = true; } } if (array_diff(array_keys($oldItems), array_keys($items))) $changed = true; if ($changed) file_put_contents($cacheFile, json_encode(['version'=>1,'generated_at'=>date('c'),'items'=>$items], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX); return $items; } function sort_input_dirs_by_metadata_date(&$dirs, $dirInfo){ usort($dirs, function($a, $b) use ($dirInfo) { $ad = $dirInfo[basename($a)]['sort_date'] ?? '00000000'; $bd = $dirInfo[basename($b)]['sort_date'] ?? '00000000'; $cmp = strcmp($bd, $ad); return $cmp !== 0 ? $cmp : strnatcasecmp(basename($b), basename($a)); }); } function orphan_videos($config){ $referenced = []; foreach (input_dirs($config['uploads_dir']) as $dir) { $stateFile = $dir . '/.movmaker-state.json'; $state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : []; if (is_array($state) && !empty($state['output'])) $referenced[basename((string)$state['output'])] = true; } $videos = glob($config['videos_dir'].'/*.{mp4,webm,mov,m4v}', GLOB_BRACE) ?: []; return array_values(array_filter($videos, fn($v)=>empty($referenced[basename($v)]))); } function input_dir_status($dir){ $stateFile = $dir . '/.movmaker-state.json'; $lockDir = $dir . '/.movmaker-lock'; $state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : []; $status = is_array($state) ? (string)($state['status'] ?? '') : ''; if (is_dir($lockDir)) return $status !== '' ? $status : 'processing'; return in_array($status, ['processing', 'stale', 'error'], true) ? $status : ''; } function input_dir_running($dir){ $stateFile = $dir . '/.movmaker-state.json'; $lockDir = $dir . '/.movmaker-lock'; $state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : []; $status = is_array($state) ? (string)($state['status'] ?? '') : ''; return is_dir($lockDir) || $status === 'processing'; } function input_dir_enabled($dir){ return is_file($dir . '/.movmaker-enabled'); } function set_input_dir_enabled($dir, $enabled){ $path = $dir . '/.movmaker-enabled'; if ($enabled) { file_put_contents($path, "enabled\n"); chmod($path, 0664); } elseif (is_file($path)) unlink($path); } function input_dir_preview($dir){ return is_file($dir . '/.movmaker-preview'); } function set_input_dir_preview($dir, $preview){ $path = $dir . '/.movmaker-preview'; if ($preview) { file_put_contents($path, "preview\n"); chmod($path, 0664); } elseif (is_file($path)) unlink($path); } function input_dir_visible($dir){ return !is_file($dir . '/.mvlog-hidden'); } function set_input_dir_visible($dir, $visible){ $path = $dir . '/.mvlog-hidden'; if ($visible) { if (is_file($path)) unlink($path); } else { file_put_contents($path, "hidden\n"); chmod($path, 0664); } } function input_dir_state($dir){ $stateFile = $dir . '/.movmaker-state.json'; $state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : []; return is_array($state) ? $state : []; } function input_dir_output($dir){ $state = input_dir_state($dir); return basename((string)($state['output'] ?? '')); } function cached_video_metadata($output, $fallback){ $meta = ['title'=>$fallback['title'] ?? '', 'date'=>$fallback['date'] ?? '', 'location'=>$fallback['place'] ?? '', 'description'=>$fallback['description'] ?? '']; $cacheFile = __DIR__ . '/cache/videos.json'; $cache = is_file($cacheFile) ? json_decode((string)file_get_contents($cacheFile), true) : []; $cached = $cache['items'][$output]['metadata'] ?? null; if (is_array($cached)) { foreach (['title','date','location','description'] as $k) if (!empty($cached[$k])) $meta[$k] = $cached[$k]; } return $meta; } function paginate($items, $page, $perPage = 8){ $total = count($items); $pages = max(1, (int)ceil($total / $perPage)); $page = max(1, min((int)$page, $pages)); return [$page, $pages, array_slice($items, ($page - 1) * $perPage, $perPage), $total]; } function page_links($tab, $page, $pages){ if ($pages <= 1) return ''; $html = '
No orphan videos.
=page_links('videos',$videoPage,$videoPages)?>No input directories yet.
No video yet=nl2br(h($meta['description']), false)?>
=h(basename($d))?> ยท =h($info['file_count'] ?? count(list_files($d)))?> files
in-dir/=h(basename($editDir))?>
in-dir/=h(basename($editDir))?>