summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKen D'Ambrosio <ken@jots.org>2026-05-14 22:53:09 +0000
committerKen D'Ambrosio <ken@jots.org>2026-05-14 22:53:09 +0000
commit6acd47c1ca27d705afe88b292a55a5170c038d2e (patch)
tree83a1795e8d3971644e29d2f4ed2d772beaa305e0
parent9cebd2e909793e12f7b9e5125d4fba671b5b660d (diff)
Auto-transcode non-browser-playable videos to MP4 in update.rb
On each run, any .avi, .mkv, or .mov file without a same-named .mp4 sibling is transcoded with ffmpeg (H.264/AAC, CRF 23, faststart). The original is kept on disk and hidden in album.json so only the playable MP4 appears in the UI; admins can un-hide the original if needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--scripts/update.rb43
1 files changed, 39 insertions, 4 deletions
diff --git a/scripts/update.rb b/scripts/update.rb
index 0b052ba..a4969d3 100644
--- a/scripts/update.rb
+++ b/scripts/update.rb
@@ -22,10 +22,11 @@ MEDIA_ROOT = (ENV['MEDIA_ROOT'] || '/var/albumen').freeze
CACHE_ROOT = (ENV['CACHE_ROOT'] || '/opt/albumen/cache/thumbs').freeze
THUMB_SIZE = 300
-IMAGE_EXTS = %w[jpg jpeg png gif webp heic heif tiff bmp].freeze
-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
+IMAGE_EXTS = %w[jpg jpeg png gif webp heic heif tiff bmp].freeze
+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
# ── Directory processing ───────────────────────────────────────────────────────
@@ -37,6 +38,7 @@ def process_dir(dir)
json_path = File.join(dir, 'album.json')
data = load_json(json_path)
data['files'] ||= {}
+ data['cover'] = '__random__' unless data.key?('cover')
data['visible'] = true unless data.key?('visible')
# Enumerate current media files
@@ -57,6 +59,26 @@ def process_dir(dir)
end
end
+ # Transcode non-web-friendly videos to MP4; hide the original
+ current.select { |n| TRANSCODE_EXTS.include?(File.extname(n).downcase.delete_prefix('.')) }
+ .each do |name|
+ base = File.basename(name, '.*')
+ target = "#{base}.mp4"
+ next if current.include?(target) # already transcoded on a previous run
+ full = File.join(dir, name)
+ dest = File.join(dir, target)
+ puts " Transcoding: #{name} → #{target}"
+ transcode_to_mp4(full, dest)
+ if File.exist?(dest)
+ data['files'][name] ||= {}
+ data['files'][name]['visible'] ||= false # hide original; admin can override
+ current << target # include in processing pass below
+ puts " → done (original hidden)"
+ else
+ warn " Transcode failed: #{name}"
+ end
+ end
+
# Process each file
current.each do |name|
full = File.join(dir, name)
@@ -161,6 +183,19 @@ rescue StandardError => e
warn " Thumb error (image): #{e.message}"
end
+def transcode_to_mp4(source, dest)
+ system(
+ 'ffmpeg', '-y', '-i', source,
+ '-c:v', 'libx264', '-crf', '23', '-preset', 'medium',
+ '-c:a', 'aac',
+ '-movflags', '+faststart',
+ dest,
+ %i[out err] => '/dev/null'
+ )
+rescue StandardError => e
+ warn " Transcode error: #{e.message}"
+end
+
def generate_video_thumb(source, dest)
system(
'ffmpeg', '-y', '-ss', '2', '-i', source,