summaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/update.rb36
1 files changed, 31 insertions, 5 deletions
diff --git a/scripts/update.rb b/scripts/update.rb
index 9953505..822405f 100644
--- a/scripts/update.rb
+++ b/scripts/update.rb
@@ -1,9 +1,12 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
-# Usage: ruby update.rb [relative/path]
-# Without argument: process entire MEDIA_ROOT tree.
-# With argument: process only that subdirectory (and its children).
+# Usage: ruby update.rb [--force] [relative/path]
+# Without argument: process entire MEDIA_ROOT tree, skipping directories
+# whose mtime hasn't changed since the last scan.
+# With argument: process only that subdirectory (and its children),
+# always scanning regardless of mtime (explicit request).
+# --force: scan entire tree ignoring all mtime sentinels.
#
# Resilience guarantees:
# - album.json is written atomically (temp-file + rename), so a crash
@@ -11,6 +14,8 @@
# - Thumbnails are checked before generation; already-done work is skipped.
# - EXIF and dimension extraction are skipped if already recorded.
# - Safe to re-run at any time; all operations are idempotent.
+# - Unchanged directories are skipped via a .albumen_scanned sentinel file;
+# pass --force to bypass.
require 'json'
require 'yaml'
@@ -27,12 +32,25 @@ VIDEO_EXTS = %w[mp4 mov avi mkv webm m4v ogv].freeze
AUDIO_EXTS = %w[mp3 flac ogg wav m4a aac].freeze
MEDIA_EXTS = (IMAGE_EXTS + VIDEO_EXTS + AUDIO_EXTS).freeze
TRANSCODE_EXTS = %w[avi mkv mov].freeze # not universally browser-playable; convert to MP4
+SENTINEL_FILE = '.albumen_scanned'.freeze
+
+# Explicit directory argument implies force — you asked for it, it should run.
+FORCE_UPDATE = !!(ARGV.delete('--force') || ARGV[0])
# ── Directory processing ───────────────────────────────────────────────────────
def process_dir(dir)
rel = dir.delete_prefix(MEDIA_ROOT).delete_prefix('/')
label = rel.empty? ? '(root)' : rel
+
+ unless FORCE_UPDATE
+ sentinel = File.join(dir, SENTINEL_FILE)
+ if File.exist?(sentinel) && File.mtime(sentinel) >= File.mtime(dir)
+ puts "Skipping #{label} (unchanged)"
+ return
+ end
+ end
+
puts "Scanning #{label}"
json_path = File.join(dir, 'album.json')
@@ -102,13 +120,15 @@ def process_dir(dir)
end
atomic_write_json(json_path, data)
+ FileUtils.touch(File.join(dir, SENTINEL_FILE))
end
# ── Metadata enrichment ────────────────────────────────────────────────────────
def enrich_image(full, name, meta)
- needs_exif = meta['taken_at'].nil? || meta['camera'].nil? ||
- meta['aperture'].nil? || meta['shutter'].nil? || meta['iso'].nil?
+ needs_exif = !meta['exif_absent'] &&
+ (meta['taken_at'].nil? || meta['camera'].nil? ||
+ meta['aperture'].nil? || meta['shutter'].nil? || meta['iso'].nil?)
if needs_exif
begin
exif = MiniExiftool.new(full, numerical: false)
@@ -133,6 +153,12 @@ def enrich_image(full, name, meta)
rescue StandardError => e
warn " #{name}: EXIF error — #{e.message}"
end
+
+ # If exiftool found nothing at all, record that so we don't retry on every re-scan.
+ if meta['taken_at'].nil? && meta['camera'].nil? &&
+ meta['aperture'].nil? && meta['shutter'].nil? && meta['iso'].nil?
+ meta['exif_absent'] = true
+ end
end
# Dimensions (skip if already recorded)