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);
=h($err)?>
Videos without input dir
No orphan videos.
=page_links('videos',$videoPage,$videoPages)?>
-Existing input dirs
No input directories yet.

" alt="Map">
No video yet
+
Existing input dirs
No input directories yet.
No video yet=h($meta['title'] ?: ($info['title'] ?? basename($d)))?>
=h($meta['date'])?>=h($meta['location'])?>No videoPreview video
=nl2br(h($meta['description']), false)?>
=h(basename($d))?> ยท =h($info['file_count'] ?? count(list_files($d)))?> files
=page_links('edit',$editPage,$editPages)?>
Edit input dir
in-dir/=h(basename($editDir))?>
This input directory is =h($editStatus)?>. Editing is disabled until the job completes.
-
+