diff --git a/bin/generate_data.sh b/bin/generate_data.sh
index 66e718a..1f7cf4d 100755
--- a/bin/generate_data.sh
+++ b/bin/generate_data.sh
@@ -126,6 +126,8 @@ Look at these captured frames from a trip. Synthesize them into one single, cohe
that summarizes the overall experience for motorcycle/travel blog: You are the main character riding your beloved Yamaha FJR.
You love motorcycles of course but also pizzas and the best sport ever invented: ice hockey.
+Before the actual description write 2-3 quoted sentences in Danish that will sound like quoting Victor Borge: entertainer with the elegance of a concert pianist and the mischievous humor of a master storyteller. He is known having an aura of effortless sophistication, and telling clever jokes that nobody sees coming. The atmosphere should combine culture, intelligence, optimism, and gentle satire. Refined, warm, charismatic, and playful. Realistic, cinematic, highly detailed. Those sentences are not told by you, but cites. Always leave one empty row between this quote and description.
+
Title: "$TITLE"
Location: "$LOCATION"
diff --git a/new.php b/new.php
index c6f1d9d..90fb307 100644
--- a/new.php
+++ b/new.php
@@ -6,17 +6,6 @@ foreach (["videos_dir", "thumbs_dir", "uploads_dir"] as $d) if (!is_dir($config[
if (!is_dir(__DIR__ . "/cache")) mkdir(__DIR__ . "/cache", 0775, true);
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, "UTF-8"); }
function ajax_json($data){ header('Content-Type: application/json; charset=UTF-8'); echo json_encode($data, JSON_UNESCAPED_UNICODE); exit; }
-function run_gpsmap($dir) {
- file_put_contents("/var/www/html/mvlog/gps_err.log", "DEBUG: run_gpsmap called for " . $dir . "
-", FILE_APPEND);
- $stateFile = $dir . '/.movmaker-state.json';
- $state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : [];
- $output = basename((string)($state['output'] ?? ''));
- $map_filename = ($output !== '' ? pathinfo($output, PATHINFO_FILENAME) : basename($dir)) . '.png';
- $out_path = '/var/www/html/mvlog/out-dir/' . $map_filename;
- $cmd = "/usr/bin/python3 /var/www/html/mvlog/bin/gpsmap.py " . escapeshellarg($dir) . " " . escapeshellarg($out_path) . " >> /var/www/html/mvlog/gps_err.log 2>&1 &";
- $cmd = "PATH=/usr/bin:/usr/local/bin " . $cmd; shell_exec($cmd);
-}
function slugify($s){ $s = strtolower(trim($s)); $s = preg_replace('/[^a-z0-9]+/', '-', $s); return trim($s, '-') ?: 'movie'; }
function rrmdir($dir){ if(!is_dir($dir)) return; foreach(scandir($dir) as $f){ if($f==='.'||$f==='..') continue; $p="$dir/$f"; is_dir($p)?rrmdir($p):unlink($p);} rmdir($dir); }
function input_dirs($base){ return glob($base.'/*', GLOB_ONLYDIR) ?: []; }
@@ -77,30 +66,77 @@ function write_data($dir, $post){
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 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 parse_date_from_filename($path) {
+ $filename = basename($path);
+ if (preg_match('/(\d{8})/', $filename, $m)) {
+ return $m[1];
+ }
+ return null;
+}
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")) {
+ 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})\s*(\d{2}):(\d{2}):(\d{2})/", (string)$exif[$k], $m)) return $m[1].$m[2].$m[3].$m[4].$m[5].$m[6];
+ 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 (preg_match("/(\d{8})[_-]?(\d{6})?/", basename($path), $m)) {
- return $m[1] . ($m[2] ?? "000000");
+ 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;
+ return parse_date_from_filename($path);
}
function input_dir_date_from_media($dir){
$dates = [];
@@ -153,7 +189,7 @@ for dirpath, dirnames, filenames in os.walk(root):
entries.sort()
print(hashlib.sha256(json.dumps(entries, ensure_ascii=False, separators=(',', ':')).encode('utf-8')).hexdigest())
PY;
- $cmd = '/usr/bin/python3 -c ' . escapeshellarg($script) . ' ' . escapeshellarg($dir);
+ $cmd = 'python3 -c ' . escapeshellarg($script) . ' ' . escapeshellarg($dir);
$output = trim(shell_exec($cmd) ?? '');
return $output !== '' ? $output : '';
}
@@ -186,6 +222,7 @@ function load_input_dir_cache($dirs){
}
}
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){
@@ -243,15 +280,15 @@ function set_input_dir_preview($dir, $preview){
if (!is_array($state)) $state = [];
$state['preview'] = (bool)$preview;
$state['preview_updated_at'] = gmdate('c');
- file_put_contents($stateFile, json_encode($state, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n");
+ file_put_contents($stateFile, json_encode($state, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "
+");
chmod($stateFile, 0664);
}
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
-"); chmod($path, 0664); }
+ else { file_put_contents($path, "hidden\n"); chmod($path, 0664); }
}
function input_dir_state($dir){
$stateFile = $dir . '/.movmaker-state.json';
@@ -353,7 +390,6 @@ try {
if (!rename($tmpDir, $dir)) { rrmdir($tmpDir); throw new RuntimeException('Cannot create input directory.'); }
chmod($dir, 02775);
write_data($dir, $_POST);
- run_gpsmap($dir);
set_input_dir_enabled($dir, false);
set_input_dir_preview($dir, false);
set_input_dir_visible($dir, false);
@@ -423,6 +459,7 @@ try {
include_once __DIR__ . '/lib/send_push.php';
try_send_show_notification(basename($dir), $dir, __DIR__, '/var/log/mvlog_notify.log');
} else {
+ error_log("[mvlog] send_push helper missing, cannot send notification for " . basename($dir) . "\n", 3, '/var/log/mvlog_notify.log');
}
}
@@ -445,7 +482,6 @@ try {
write_data($dir, $_POST);
save_uploads('media', $dir, $allowedMedia);
save_uploads('audio', $dir, $allowedAudio);
- run_gpsmap($dir);
if (!empty($_POST['ajax'])) ajax_json(['ok'=>true, 'message'=>'Updated input directory: in-dir/' . basename($dir), 'dir'=>basename($dir)]);
header('Location: new.php?tab=edit');
exit;
@@ -519,7 +555,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.

" alt="Map">
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.
-
+