summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKen D'Ambrosio <ken@jots.org>2026-06-16 19:09:54 +0000
committerKen D'Ambrosio <ken@jots.org>2026-06-16 19:09:54 +0000
commit612afca9f70e65de8b6511b7b0c008e563313a39 (patch)
tree87313f1f04fe511f54ce0f4ea58148efb8933793
parentf2095eeb14db8db6558812225e5a0638d9fc9cd0 (diff)
Transcode HEVC/non-H264 MP4 files to H.264 for browser compatibility
Firefox has no HEVC support. Pixel 9a (and newer phones) record video in HEVC by default. update.rb now probes the codec of every .mp4/.m4v file via ffprobe and transcodes non-web-friendly codecs (anything not h264/vp8/vp9/av1) to H.264 MP4 with a .h264.mp4 suffix, same as the existing MOV→MP4 flow. The original is kept and hidden from non-admins. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--scripts/update.rb44
1 files changed, 35 insertions, 9 deletions
diff --git a/scripts/update.rb b/scripts/update.rb
index f909510..996ecbd 100644
--- a/scripts/update.rb
+++ b/scripts/update.rb
@@ -35,7 +35,8 @@ 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
+TRANSCODE_EXTS = %w[avi mkv mov].freeze
+WEB_VIDEO_CODECS = %w[h264 vp8 vp9 av1].freeze
SENTINEL_FILE = '.albumen_scanned'.freeze
# Explicit directory argument implies force — you asked for it, it should run.
@@ -91,19 +92,33 @@ def process_dir(dir, idx, total)
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|
+ # Transcode non-web-friendly videos to H.264 MP4; hide the original
+ current.each do |name|
+ ext = File.extname(name).downcase.delete_prefix('.')
+ next unless VIDEO_EXTS.include?(ext)
+ full = File.join(dir, name)
+
+ needs_transcode = if TRANSCODE_EXTS.include?(ext)
+ true
+ elsif ext == 'mp4' || ext == 'm4v'
+ codec = video_codec(full)
+ codec && !WEB_VIDEO_CODECS.include?(codec)
+ else
+ false
+ end
+ next unless needs_transcode
+
base = File.basename(name, '.*')
- target = "#{base}.mp4"
- if current.include?(target)
+ target = TRANSCODE_EXTS.include?(ext) ? "#{base}.mp4" : "#{base}.h264.mp4"
+ if current.include?(target) || data['files'][name]&.fetch('transcoded_to', nil)
data['files'][name] ||= {}
- data['files'][name]['transcoded_to'] = target
+ data['files'][name]['transcoded_to'] ||= target
next
end
- full = File.join(dir, name)
+
dest = File.join(dir, target)
- log " Transcoding: #{name} → #{target}"
+ codec_name = TRANSCODE_EXTS.include?(ext) ? ext.upcase : video_codec(full)&.upcase
+ log " Transcoding #{codec_name}: #{name} → #{target}"
transcode_to_mp4(full, dest)
if File.exist?(dest)
data['files'][name] ||= {}
@@ -225,6 +240,17 @@ rescue StandardError => e
warn " Thumb error (image): #{e.message}"
end
+def video_codec(path)
+ out = IO.popen(
+ ['ffprobe', '-v', 'quiet', '-select_streams', 'v:0',
+ '-show_entries', 'stream=codec_name', '-of', 'default=noprint_wrappers=1:nokey=1', path],
+ err: '/dev/null', &:read
+ ).strip
+ out.empty? ? nil : out
+rescue StandardError
+ nil
+end
+
def transcode_to_mp4(source, dest)
system(
'ffmpeg', '-y', '-i', source,