diff options
| author | Ken D'Ambrosio <ken@jots.org> | 2026-06-16 19:09:54 +0000 |
|---|---|---|
| committer | Ken D'Ambrosio <ken@jots.org> | 2026-06-16 19:09:54 +0000 |
| commit | 612afca9f70e65de8b6511b7b0c008e563313a39 (patch) | |
| tree | 87313f1f04fe511f54ce0f4ea58148efb8933793 | |
| parent | f2095eeb14db8db6558812225e5a0638d9fc9cd0 (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.rb | 44 |
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, |
