From 612afca9f70e65de8b6511b7b0c008e563313a39 Mon Sep 17 00:00:00 2001 From: Ken D'Ambrosio Date: Tue, 16 Jun 2026 19:09:54 +0000 Subject: Transcode HEVC/non-H264 MP4 files to H.264 for browser compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- scripts/update.rb | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) (limited to 'scripts/update.rb') 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, -- cgit v1.2.3