lib/image_processing/pipeline.rb in image_processing-0.11.1 vs lib/image_processing/pipeline.rb in image_processing-0.11.2

- old
+ new

@@ -1,59 +1,92 @@ require "tempfile" module ImageProcessing class Pipeline - include Chainable + DEFAULT_FORMAT = "jpg" + attr_reader :source, :loader, :saver, :format, :operations, :processor_class, :destination + def initialize(options) - @default_options = options + options.each do |name, value| + value = normalize_source(value, options) if name == :source + instance_variable_set(:"@#{name}", value) + end end - def call!(save: true, destination: nil) - fail Error, "source file is not provided" unless default_options[:source] + def call(save: true) + processor = processor_class.new(self) + image = processor.load_image(source, **loader) - image_class = default_options[:processor]::IMAGE_CLASS + operations.each do |name, args| + image = processor.apply_operation(name, image, *args) + end - if default_options[:source].is_a?(image_class) - source = default_options[:source] - elsif default_options[:source].is_a?(String) - source = default_options[:source] - elsif default_options[:source].respond_to?(:path) - source = default_options[:source].path - elsif default_options[:source].respond_to?(:to_path) - source = default_options[:source].to_path + if save == false + image + elsif destination + handle_destination do + processor.save_image(image, destination, **saver) + end else - fail Error, "source file needs to respond to #path, or be a String, a Pathname, or a #{image_class} object" + create_tempfile do |tempfile| + processor.save_image(image, tempfile.path, **saver) + end end + end - processor = default_options[:processor].new - image = processor.load_image(source, default_options[:loader]) + def source_path + source if source.is_a?(String) + end - default_options[:operations].each do |name, args| - if name == :custom - image = args.first.call(image) || image - else - image = processor.apply_operation(name, image, *args) - end - end + def destination_format + format = File.extname(destination)[1..-1] if destination + format ||= self.format + format ||= File.extname(source_path)[1..-1] if source_path - return image unless save + format || DEFAULT_FORMAT + end - return processor.save_image(image, destination, default_options[:saver]) if destination + private - source_path = source if source.is_a?(String) - format = default_options[:format] || File.extname(source_path.to_s)[1..-1] || "jpg" + def create_tempfile + tempfile = Tempfile.new(["image_processing", ".#{destination_format}"], binmode: true) - result = Tempfile.new(["image_processing", ".#{format}"], binmode: true) + yield tempfile - begin - processor.save_image(image, result.path, default_options[:saver]) - rescue - result.close! - raise - end + tempfile.open + tempfile + rescue + tempfile.close! if tempfile + raise + end - result.open - result + # In case of processing errors, both libvips and imagemagick will leave the + # empty destination file they created, so this method makes sure it is + # deleted in case an exception is raised on saving the image. + def handle_destination + destination_existed = File.exist?(destination) + yield + rescue + File.delete(destination) if File.exist?(destination) && !destination_existed + raise + end + + def normalize_source(source, options) + fail Error, "source file is not provided" unless source + + image_class = options[:processor_class]::IMAGE_CLASS + + if source.is_a?(image_class) + source + elsif source.is_a?(String) + source + elsif source.respond_to?(:path) + source.path + elsif source.respond_to?(:to_path) + source.to_path + else + fail Error, "source file needs to respond to #path, or be a String, a Pathname, or a #{image_class} object" + end end end end