create a job for all image treatments
This commit is contained in:
parent
fce2c03015
commit
b4a7b4bfbd
8 changed files with 91 additions and 60 deletions
59
app/jobs/image_processor_job.rb
Normal file
59
app/jobs/image_processor_job.rb
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
class ImageProcessorJob < ApplicationJob
|
||||||
|
class FileNotScannedYetError < StandardError
|
||||||
|
end
|
||||||
|
|
||||||
|
# If by the time the job runs the blob has been deleted, ignore the error
|
||||||
|
discard_on ActiveRecord::RecordNotFound
|
||||||
|
# If the file is deleted during the scan, ignore the error
|
||||||
|
discard_on ActiveStorage::FileNotFoundError
|
||||||
|
# If the file is not analyzed or scanned for viruses yet, retry later
|
||||||
|
# (to avoid modifying the file while it is being scanned).
|
||||||
|
retry_on FileNotScannedYetError, wait: :exponentially_longer, attempts: 10
|
||||||
|
|
||||||
|
def perform(blob)
|
||||||
|
return if blob.nil?
|
||||||
|
raise FileNotScannedYetError if blob.virus_scanner.pending?
|
||||||
|
return if ActiveStorage::Attachment.find_by(blob_id: blob.id).record_type == "ActiveStorage::VariantRecord"
|
||||||
|
|
||||||
|
auto_rotate(blob) if ["image/jpeg", "image/jpg"].include?(blob.content_type)
|
||||||
|
create_variants(blob) if blob.variant_required?
|
||||||
|
add_watermark(blob) if blob.watermark_pending?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def auto_rotate(blob)
|
||||||
|
blob.open do |file|
|
||||||
|
Tempfile.create(["rotated", File.extname(file)]) do |output|
|
||||||
|
processed = AutoRotateService.new.process(file, output)
|
||||||
|
return if processed.blank?
|
||||||
|
|
||||||
|
blob.upload(processed) # also update checksum & byte_size accordingly
|
||||||
|
blob.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_variants(blob)
|
||||||
|
blob.attachments.each do |attachment|
|
||||||
|
next unless attachment&.representable?
|
||||||
|
attachment.representation(resize_to_limit: [300, 300]).processed
|
||||||
|
attachment.representation(resize_to_limit: [400, 400]).processed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_watermark(blob)
|
||||||
|
return if blob.watermark_done?
|
||||||
|
|
||||||
|
blob.open do |file|
|
||||||
|
Tempfile.create(["watermarked", File.extname(file)]) do |output|
|
||||||
|
processed = WatermarkService.new.process(file, output)
|
||||||
|
return if processed.blank?
|
||||||
|
|
||||||
|
blob.upload(processed) # also update checksum & byte_size accordingly
|
||||||
|
blob.watermarked_at = Time.current
|
||||||
|
blob.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,28 +0,0 @@
|
||||||
class TitreIdentiteWatermarkJob < ApplicationJob
|
|
||||||
class FileNotScannedYetError < StandardError
|
|
||||||
end
|
|
||||||
|
|
||||||
# If by the time the job runs the blob has been deleted, ignore the error
|
|
||||||
discard_on ActiveRecord::RecordNotFound
|
|
||||||
# If the file is deleted during the scan, ignore the error
|
|
||||||
discard_on ActiveStorage::FileNotFoundError
|
|
||||||
# If the file is not analyzed or scanned for viruses yet, retry later
|
|
||||||
# (to avoid modifying the file while it is being scanned).
|
|
||||||
retry_on FileNotScannedYetError, wait: :exponentially_longer, attempts: 10
|
|
||||||
|
|
||||||
def perform(blob)
|
|
||||||
return if blob.watermark_done?
|
|
||||||
raise FileNotScannedYetError if blob.virus_scanner.pending?
|
|
||||||
|
|
||||||
blob.open do |file|
|
|
||||||
Tempfile.create(["watermarked", File.extname(file)]) do |output|
|
|
||||||
processed = WatermarkService.new.process(file, output)
|
|
||||||
return if processed.blank?
|
|
||||||
|
|
||||||
blob.upload(processed) # also update checksum & byte_size accordingly
|
|
||||||
blob.watermarked_at = Time.current
|
|
||||||
blob.save!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,22 +0,0 @@
|
||||||
module AttachmentAutoRotateConcern
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
after_create_commit :auto_rotate
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def auto_rotate
|
|
||||||
return if blob.nil?
|
|
||||||
return if ["image/jpeg", "image/jpg"].exclude?(blob.content_type)
|
|
||||||
|
|
||||||
blob.open do |file|
|
|
||||||
Tempfile.create(["rotated", File.extname(file)]) do |output|
|
|
||||||
processed = AutoRotateService.new.process(file, output)
|
|
||||||
blob.upload(processed) # also update checksum & byte_size accordingly
|
|
||||||
blob.save!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
21
app/models/concerns/attachment_image_processor_concern.rb
Normal file
21
app/models/concerns/attachment_image_processor_concern.rb
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Run a virus scan on all attachments after they are analyzed.
|
||||||
|
#
|
||||||
|
# We're using a class extension to ensure that all attachments get scanned,
|
||||||
|
# regardless on how they were created. This could be an ActiveStorage::Analyzer,
|
||||||
|
# but as of Rails 6.1 only the first matching analyzer is ever run on
|
||||||
|
# a blob (and we may want to analyze the dimension of a picture as well
|
||||||
|
# as scanning it).
|
||||||
|
module AttachmentImageProcessorConcern
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
after_create_commit :process_image
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def process_image
|
||||||
|
return if blob.nil?
|
||||||
|
ImageProcessorJob.perform_later(blob)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,4 +1,4 @@
|
||||||
module BlobTitreIdentiteWatermarkConcern
|
module BlobImageProcessorConcern
|
||||||
def watermark_pending?
|
def watermark_pending?
|
||||||
watermark_required? && !watermark_done?
|
watermark_required? && !watermark_done?
|
||||||
end
|
end
|
||||||
|
@ -7,10 +7,8 @@ module BlobTitreIdentiteWatermarkConcern
|
||||||
watermarked_at.present?
|
watermarked_at.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def watermark_later
|
def variant_required?
|
||||||
if watermark_pending?
|
attachments.any? { _1.record.class == Champs::TitreIdentiteChamp || _1.record.class == Champs::PieceJustificativeChamp }
|
||||||
TitreIdentiteWatermarkJob.perform_later(self)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
|
@ -1,7 +1,6 @@
|
||||||
class AutoRotateService
|
class AutoRotateService
|
||||||
def process(file, output)
|
def process(file, output)
|
||||||
auto_rotate_image(file, output)
|
auto_rotate_image(file, output)
|
||||||
output
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -9,6 +8,8 @@ class AutoRotateService
|
||||||
def auto_rotate_image(file, output)
|
def auto_rotate_image(file, output)
|
||||||
image = MiniMagick::Image.new(file.to_path)
|
image = MiniMagick::Image.new(file.to_path)
|
||||||
|
|
||||||
|
return nil if !image.valid?
|
||||||
|
|
||||||
case image["%[orientation]"]
|
case image["%[orientation]"]
|
||||||
when 'LeftBottom'
|
when 'LeftBottom'
|
||||||
rotate_image(file, output, 90)
|
rotate_image(file, output, 90)
|
||||||
|
@ -16,6 +17,8 @@ class AutoRotateService
|
||||||
rotate_image(file, output, 180)
|
rotate_image(file, output, 180)
|
||||||
when 'RightTop'
|
when 'RightTop'
|
||||||
rotate_image(file, output, 270)
|
rotate_image(file, output, 270)
|
||||||
|
else
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -26,5 +29,6 @@ class AutoRotateService
|
||||||
convert.auto_orient
|
convert.auto_orient
|
||||||
convert << output.to_path
|
convert << output.to_path
|
||||||
end
|
end
|
||||||
|
output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer
|
||||||
Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer::VideoAnalyzer
|
Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer::VideoAnalyzer
|
||||||
|
|
||||||
ActiveSupport.on_load(:active_storage_blob) do
|
ActiveSupport.on_load(:active_storage_blob) do
|
||||||
include BlobTitreIdentiteWatermarkConcern
|
include BlobImageProcessorConcern
|
||||||
include BlobVirusScannerConcern
|
include BlobVirusScannerConcern
|
||||||
include BlobSignedIdConcern
|
include BlobSignedIdConcern
|
||||||
|
|
||||||
|
@ -15,9 +15,8 @@ ActiveSupport.on_load(:active_storage_blob) do
|
||||||
end
|
end
|
||||||
|
|
||||||
ActiveSupport.on_load(:active_storage_attachment) do
|
ActiveSupport.on_load(:active_storage_attachment) do
|
||||||
include AttachmentTitreIdentiteWatermarkConcern
|
include AttachmentImageProcessorConcern
|
||||||
include AttachmentVirusScannerConcern
|
include AttachmentVirusScannerConcern
|
||||||
include AttachmentAutoRotateConcern
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Rails.application.reloader.to_prepare do
|
Rails.application.reloader.to_prepare do
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
describe TitreIdentiteWatermarkJob, type: :job do
|
describe ImageProcessorJob, type: :job do
|
||||||
let(:blob) do
|
let(:blob) do
|
||||||
ActiveStorage::Blob.create_and_upload!(io: StringIO.new("toto"), filename: "toto.png")
|
ActiveStorage::Blob.create_and_upload!(io: StringIO.new("toto"), filename: "toto.png")
|
||||||
end
|
end
|
Loading…
Reference in a new issue