diff --git a/new.php b/new.php index 90fb307..9949de2 100644 --- a/new.php +++ b/new.php @@ -70,73 +70,151 @@ function write_data($dir, $post){ } 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))); - $orderFile = $dir . '/order.json'; - if (file_exists($orderFile)) { - $order = json_decode(file_get_contents($orderFile), true); - if (is_array($order)) { - $files_map = array_flip($files); - $sorted_files = []; - foreach ($order as $filename) { - if (isset($files_map[$filename])) { - $sorted_files[] = $filename; - unset($files_map[$filename]); - } - } - return array_merge($sorted_files, array_keys($files_map)); - } - } - 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); - }); - $orderFile = $dir . '/order.json'; - if (!file_exists($orderFile)) { - $allowed = ['jpg','jpeg','png','webp','gif','mp4','mov','m4v','avi','mkv','webm']; - $mediaFiles = array_filter($files, function($f) use ($allowed) { - $ext = strtolower(pathinfo((string)$f, PATHINFO_EXTENSION)); - return in_array($ext, $allowed, true); - }); - file_put_contents($orderFile, json_encode(array_values($mediaFiles), JSON_PRETTY_PRINT)); - chmod($orderFile, 0664); - } - return $files; + +function is_visual_media_file($name){ + $ext = strtolower(pathinfo((string)$name, PATHINFO_EXTENSION)); + return in_array($ext, ['jpg','jpeg','png','webp','gif','mp4','mov','m4v','avi','mkv','webm'], true); } + function parse_date_from_filename($path) { $filename = basename($path); - if (preg_match('/(\d{8})/', $filename, $m)) { - return $m[1]; + if (!preg_match('/(?:^|[^0-9])(\\d{8})[_-]?(\\d{6})(?:[^0-9]|$)/', $filename, $m)) { + return null; } - return null; + $y = (int)substr($m[1], 0, 4); + $mo = (int)substr($m[1], 4, 2); + $d = (int)substr($m[1], 6, 2); + $hh = (int)substr($m[2], 0, 2); + $mi = (int)substr($m[2], 2, 2); + $ss = (int)substr($m[2], 4, 2); + if (!checkdate($mo, $d, $y) || $hh > 23 || $mi > 59 || $ss > 59) { + return null; + } + return $m[1] . $m[2]; } + +function mvlog_local_timezone(){ + static $tz = null; + if ($tz instanceof DateTimeZone) return $tz; + + $name = trim((string)@file_get_contents('/etc/timezone')); + if ($name === '') $name = (string)date_default_timezone_get(); + if ($name === '') $name = 'UTC'; + + try { $tz = new DateTimeZone($name); } + catch (Throwable $e) { $tz = new DateTimeZone('UTC'); } + + return $tz; +} + +function parse_embedded_datetime_to_local($value){ + $value = trim((string)$value); + if ($value === '') return null; + + try { + $dt = new DateTimeImmutable($value); + return $dt->setTimezone(mvlog_local_timezone())->format('YmdHis'); + } catch (Throwable $e) { + return null; + } +} + function media_file_date($path){ $ext = strtolower(pathinfo((string)$path, PATHINFO_EXTENSION)); + + // Priority 2: Exif/embedded metadata 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]"; + $value = (string)($exif[$k] ?? ''); + if ($value !== '' && preg_match('/^(\\d{4}):(\\d{2}):(\\d{2})(?:\\s+(\\d{2}):(\\d{2}):(\\d{2}))?/', $value, $m)) { + $date = $m[1] . $m[2] . $m[3]; + $time = isset($m[4]) ? ($m[4] . $m[5] . $m[6]) : '000000'; + return $date . $time; + } } } + 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'] ?? ''; + if (is_array($data['format']['tags'] ?? null)) $candidates[] = (string)($data['format']['tags']['creation_time'] ?? ''); + foreach (($data['streams'] ?? []) as $stream) if (is_array($stream['tags'] ?? null)) $candidates[] = (string)($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]"; + $normalized = parse_embedded_datetime_to_local($value); + if ($normalized !== null) { + return $normalized; + } + if (preg_match('/^(\\d{4})-(\\d{2})-(\\d{2})/', $value, $m)) { + return $m[1].$m[2].$m[3].'000000'; + } } } } - return parse_date_from_filename($path); + + // Priority 3: filename parsing + $from_filename = parse_date_from_filename($path); + if ($from_filename) { + return $from_filename; + } + + return null; +} + +function media_sort_datetime($path){ + return media_file_date($path) ?? date('YmdHis', filemtime($path)); +} + +function list_files($dir){ + $files = array_values(array_filter(scandir($dir), fn($f)=>editable_file($f) && is_file($dir.'/'.$f))); + $orderFile = $dir . '/order.json'; + + // Priority 1: explicit order file + if (is_file($orderFile)) { + $order = json_decode((string)file_get_contents($orderFile), true); + if (is_array($order)) { + $remaining = array_flip($files); + $sorted = []; + foreach ($order as $name) { + if (isset($remaining[$name])) { + $sorted[] = $name; + unset($remaining[$name]); + } + } + // append anything not listed (no filename sort) + foreach ($files as $name) { + if (isset($remaining[$name])) $sorted[] = $name; + } + return $sorted; + } + } + + // No order.json: sort by datetime only and create it + $media = []; + $other = []; + foreach ($files as $name) { + if (is_visual_media_file($name)) $media[] = $name; + else $other[] = $name; + } + + usort($media, function($a, $b) use ($dir) { + $da = media_sort_datetime($dir . '/' . $a); + $db = media_sort_datetime($dir . '/' . $b); + if ($da === $db) return 0; // never fallback to filename ordering + return strcmp($da, $db); + }); + + file_put_contents($orderFile, json_encode(array_values($media), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + @chmod($orderFile, 0664); + + return array_merge($media, $other); +} + +function media_sort_files($dir, $files){ + return list_files($dir); } function input_dir_date_from_media($dir){ $dates = []; @@ -555,7 +633,7 @@ $runningJobs = active_worker_jobs($config);

Videos without input dir

No orphan videos.


MB
-

Existing input dirs

No input directories yet.

" alt="Map">
No video yet
+

Existing input dirs

No input directories yet.

No video yet
@@ -586,7 +664,7 @@ $runningJobs = active_worker_jobs($config);
RenderingEdit

No videoPreview video

ยท files

Edit input dir

in-dir/

This input directory is . Editing is disabled until the job completes.
-

Edit input dir

in-dir/

Files and captions

" alt="Map">Download
No caption for audio files
+

Edit input dir

in-dir/

Files and captions

No caption for audio files

Add new movie input