create a job for all image treatments

This commit is contained in:
Lisa Durand 2024-04-29 16:53:21 +02:00 committed by Eric Leroy-Terquem
parent fce2c03015
commit b4a7b4bfbd
No known key found for this signature in database
GPG key ID: ECE60B4C1FA2ABB3
8 changed files with 91 additions and 60 deletions

View 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

View file

@ -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

View file

@ -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

View 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

View file

@ -1,4 +1,4 @@
module BlobTitreIdentiteWatermarkConcern
module BlobImageProcessorConcern
def watermark_pending?
watermark_required? && !watermark_done?
end
@ -7,10 +7,8 @@ module BlobTitreIdentiteWatermarkConcern
watermarked_at.present?
end
def watermark_later
if watermark_pending?
TitreIdentiteWatermarkJob.perform_later(self)
end
def variant_required?
attachments.any? { _1.record.class == Champs::TitreIdentiteChamp || _1.record.class == Champs::PieceJustificativeChamp }
end
private

View file

@ -1,7 +1,6 @@
class AutoRotateService
def process(file, output)
auto_rotate_image(file, output)
output
end
private
@ -9,6 +8,8 @@ class AutoRotateService
def auto_rotate_image(file, output)
image = MiniMagick::Image.new(file.to_path)
return nil if !image.valid?
case image["%[orientation]"]
when 'LeftBottom'
rotate_image(file, output, 90)
@ -16,6 +17,8 @@ class AutoRotateService
rotate_image(file, output, 180)
when 'RightTop'
rotate_image(file, output, 270)
else
nil
end
end
@ -26,5 +29,6 @@ class AutoRotateService
convert.auto_orient
convert << output.to_path
end
output
end
end

View file

@ -4,7 +4,7 @@ Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer
Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer::VideoAnalyzer
ActiveSupport.on_load(:active_storage_blob) do
include BlobTitreIdentiteWatermarkConcern
include BlobImageProcessorConcern
include BlobVirusScannerConcern
include BlobSignedIdConcern
@ -15,9 +15,8 @@ ActiveSupport.on_load(:active_storage_blob) do
end
ActiveSupport.on_load(:active_storage_attachment) do
include AttachmentTitreIdentiteWatermarkConcern
include AttachmentImageProcessorConcern
include AttachmentVirusScannerConcern
include AttachmentAutoRotateConcern
end
Rails.application.reloader.to_prepare do

View file

@ -1,4 +1,4 @@
describe TitreIdentiteWatermarkJob, type: :job do
describe ImageProcessorJob, type: :job do
let(:blob) do
ActiveStorage::Blob.create_and_upload!(io: StringIO.new("toto"), filename: "toto.png")
end