87 行
2.2 KiB
Ruby
87 行
2.2 KiB
Ruby
# 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
|