class FormatParser::EXIFParser::EXIFStack

With some formats, multiple EXIF tag frames can be included in a single file. For example, JPEGs might have multiple APP1 markers which each contain EXIF data. The EXIF data in them, however, is not necessarily “complete” - it seems most applications assume that these blocks “overwrite” each other with the properties they specify. Probably this is done for more efficient saving - instead of overwriting the EXIF data with a modified version - which would also potentially disturb any digital signing that this data might include - the applications are supposed to follow the order in which these tags appear in the file:

Take a resized image for example:

APP1 {author: 'John', pixel_width: 1024}
APP1 {pixel_width: 10}

That image would get a combined EXIF of:

APP1 {author: 'John', pixel_width: 10}

since the frame that comes later in the file overwrites a property. From what I see exiftools do this is the way it works.

This class acts as a wrapper for this “layering” of chunks of EXIF properties, and will follow the following conventions:

Public Class Methods

new(multiple_exif_results) click to toggle source
# File lib/parsers/exif_parser.rb, line 98
def initialize(multiple_exif_results)
  @multiple_exif_results = Array(multiple_exif_results)
end

Public Instance Methods

orientation() click to toggle source
# File lib/parsers/exif_parser.rb, line 114
def orientation
  # Retrieving an orientation "through" the sequence of EXIF tags
  # is trickier than the method_missing case, because the value
  # of the orientation can be 0, meaning "unknown". We need to skip through
  # those and return the _last_ non-0 orientation, or 0 otherwise
  @multiple_exif_results.reverse_each do |exif_tag_frame|
    orientation_value = exif_tag_frame.orientation
    if !orientation_value.nil? && orientation_value != 0
      return orientation_value
    end
  end
  0 # If none were found - the orientation is unknown
end
orientation_sym() click to toggle source
# File lib/parsers/exif_parser.rb, line 106
def orientation_sym
  ORIENTATIONS.fetch(orientation)
end
rotated?() click to toggle source
# File lib/parsers/exif_parser.rb, line 110
def rotated?
  orientation > 4
end
to_hash() click to toggle source

ActiveSupport will attempt to call to_hash first, and to_hash is a decent default implementation to have

# File lib/parsers/exif_parser.rb, line 130
def to_hash
  # Let EXIF tags that come later overwrite the properties from the tags
  # that come earlier
  overlay = @multiple_exif_results.each_with_object({}) do |one_exif_frame, h|
    h.merge!(one_exif_frame.to_hash)
  end
  # Overwrite the orientation with our custom method implementation, because
  # it does reject 0-values.
  overlay[:orientation] = orientation

  FormatParser::AttributesJSON._sanitize_json_value(overlay)
end
to_json(*maybe_coder) click to toggle source
# File lib/parsers/exif_parser.rb, line 102
def to_json(*maybe_coder)
  to_hash.to_json(*maybe_coder)
end

Private Instance Methods

method_missing(*a) click to toggle source
Calls superclass method
# File lib/parsers/exif_parser.rb, line 151
def method_missing(*a)
  return super unless @multiple_exif_results.any?

  # The EXIF tags get appended to the file, so the ones coming _later_
  # are more specific and potentially overwrite the earlier ones. Walk
  # through the frames in reverse (starting with one that comes last)
  # and if it contans the requisite EXIF property, return the value
  # from that tag.
  @multiple_exif_results.reverse_each do |exif_tag_frame|
    value_of = exif_tag_frame.public_send(*a)
    return value_of if value_of
  end
  nil
end
respond_to_missing?(method_name, include_private_methods) click to toggle source

respond_to_missing? accepts 2 arguments: the method name symbol and whether the method being looked up can be private or not

# File lib/parsers/exif_parser.rb, line 147
def respond_to_missing?(method_name, include_private_methods)
  @multiple_exif_results.last.respond_to?(method_name, include_private_methods)
end