このコミットが含まれているのは:
2026-06-25 07:44:11 +09:00
コミット fa6c547cc9
11個のファイルの変更865行の追加323行の削除
+86
ファイルの表示
@@ -0,0 +1,86 @@
# frozen_string_literal: true
require 'mini_magick'
require 'open3'
require 'tempfile'
class MaterialThumbnailGenerator
SIZE = '180x180'
class << self
def generate! material
new(material).generate!
rescue ActiveStorage::FileNotFoundError, ArgumentError, MiniMagick::Error => e
Rails.logger.warn("Material thumbnail generation skipped: #{ e.class }: #{ e.message }")
nil
end
end
def initialize material
@material = material
end
def generate!
@material.thumbnail.purge if @material.thumbnail.attached?
return unless @material.file.attached?
return unless image? || video?
@material.file.blob.open do |file|
thumbnail = image? ? image_thumbnail(file.path) : video_thumbnail(file.path)
attach_thumbnail(thumbnail) if thumbnail
end
end
private
def image? = content_type.start_with?('image/')
def video? = content_type.start_with?('video/')
def content_type = @material.file.blob.content_type.to_s
def image_thumbnail path
image = MiniMagick::Image.open(path)
image.resize(SIZE)
image.format('jpg')
image
end
def video_thumbnail path
[1, 0].each do |seconds|
tempfile = Tempfile.new(['material-thumbnail', '.jpg'])
tempfile.close
ok = extract_video_frame(path, tempfile.path, seconds)
next unless ok && File.size?(tempfile.path)
return image_thumbnail(tempfile.path)
ensure
tempfile&.unlink
end
nil
end
def extract_video_frame input_path, output_path, seconds
_stdout, stderr, status =
Open3.capture3('ffmpeg',
'-y',
'-ss', seconds.to_s,
'-i', input_path,
'-frames:v', '1',
'-f', 'image2',
output_path)
Rails.logger.warn("ffmpeg thumbnail failed: #{ stderr }") unless status.success?
status.success?
rescue Errno::ENOENT => e
Rails.logger.warn("ffmpeg unavailable for material thumbnail: #{ e.message }")
false
end
def attach_thumbnail image
@material.thumbnail.attach(io: File.open(image.path),
filename: 'material-thumbnail.jpg',
content_type: 'image/jpeg')
end
end