require "json" module MiniMagick class Image # @private class Info ASCII_ENCODED_EXIF_KEYS = %w[ExifVersion FlashPixVersion] def initialize(path) @path = path @info = {} end def [](value, *args) case value when "format", "width", "height", "dimensions", "size", "human_size" cheap_info(value) when "colorspace" colorspace when "mime_type" mime_type when "resolution" resolution(*args) when "signature" signature when /^EXIF\:/i raw_exif(value) when "exif" exif when "details" details when "data" data else raw(value) end end def clear @info.clear end def cheap_info(value) @info.fetch(value) do format, width, height, size = parse_warnings(self["%m %w %h %b"]).split(" ") path = @path path = path.match(/\[\d+\]$/).pre_match if path =~ /\[\d+\]$/ @info.update( "format" => format, "width" => Integer(width), "height" => Integer(height), "dimensions" => [Integer(width), Integer(height)], "size" => File.size(path), "human_size" => size, ) @info.fetch(value) end rescue ArgumentError, TypeError raise MiniMagick::Invalid, "image data can't be read" end def parse_warnings(raw_info) return raw_info unless raw_info.split("\n").size > 1 raw_info.split("\n").each do |line| # must match "%m %w %h %b" return line if line.match?(/^[A-Z]+ \d+ \d+ \d+(|\.\d+)([KMGTPEZY]{0,1})B$/) end raise TypeError end def colorspace @info["colorspace"] ||= self["%r"] end def mime_type warn "[MiniMagick] MiniMagick::Image#mime_type has been deprecated, because it wasn't returning correct result for all formats ImageMagick supports. Unfortunately, returning the correct MIME type would be very slow, because it would require ImageMagick to read the whole file. It's better to use Marcel and MimeMagic gems, which are able to determine the MIME type just from the image header." "image/#{self["format"].downcase}" end def resolution(unit = nil) output = identify do |b| b.units unit if unit b.format "%x %y" end output.split(" ").map(&:to_i) end def raw_exif(value) self["%[#{value}]"] end def exif @info["exif"] ||= ( hash = {} output = self["%[EXIF:*]"] output.each_line do |line| line = line.chomp("\n") case MiniMagick.cli when :imagemagick, :imagemagick7 if match = line.match(/^exif:/) key, value = match.post_match.split("=", 2) value = decode_comma_separated_ascii_characters(value) if ASCII_ENCODED_EXIF_KEYS.include?(key) hash[key] = value else hash[hash.keys.last] << "\n#{line}" end when :graphicsmagick next if line == "unknown" key, value = line.split("=", 2) value.gsub!("\\012", "\n") # convert "\012" characters to newlines hash[key] = value end end hash ) end def raw(value) @info["raw:#{value}"] ||= identify { |b| b.format(value) } end def signature @info["signature"] ||= self["%#"] end def details warn "[MiniMagick] MiniMagick::Image#details has been deprecated, as it was causing too many parsing errors. You should use MiniMagick::Image#data instead, which differs in a way that the keys are in camelcase." if MiniMagick.imagemagick? || MiniMagick.imagemagick7? @info["details"] ||= ( details_string = identify(&:verbose) key_stack = [] details_string.lines.to_a[1..-1].each_with_object({}) do |line, details_hash| next if !line.valid_encoding? || line.strip.length.zero? level = line[/^\s*/].length / 2 - 1 if level >= 0 key_stack.pop until key_stack.size <= level else # Some metadata, such as SVG clipping paths, will be saved without # indentation, resulting in a level of -1 last_key = details_hash.keys.last details_hash[last_key] = '' if details_hash[last_key].empty? details_hash[last_key] << line next end key, _, value = line.partition(/:[\s]/).map(&:strip) hash = key_stack.inject(details_hash) { |_hash, _key| _hash.fetch(_key) } if value.empty? hash[key] = {} key_stack.push key else hash[key] = value end end ) end def data raise Error, "MiniMagick::Image#data isn't supported on GraphicsMagick. Use MiniMagick::Image#details instead." if MiniMagick.graphicsmagick? @info["data"] ||= ( json = MiniMagick::Tool::Convert.new do |convert| convert << path convert << "json:" end data = JSON.parse(json) data = data.fetch(0) if data.is_a?(Array) data.fetch("image") ) end def identify MiniMagick::Tool::Identify.new do |builder| yield builder if block_given? builder << path end end private def decode_comma_separated_ascii_characters(encoded_value) return encoded_value unless encoded_value.include?(',') encoded_value.scan(/\d+/).map(&:to_i).map(&:chr).join end def path value = @path value += "[0]" unless value =~ /\[\d+\]$/ value end end end end