Class: FormatParser::EXIFParser::EXIFStack

Inherits:
Object
  • Object
show all
Defined in:
lib/parsers/exif_parser.rb

Overview

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:

  • When merging data for JSON conversion, it will merge it top-down. It will overwrite any specified properties. An exception is made for orientation (see below)

  • When retrieving a property, it will look “from the end to the beginning” of the EXIF dataframe stack, looking for the first dataframe which has this property with a non-nil value

  • When retrieving orientation, it will pick the first orientation value which is not nil but also not 0 (“unknown orientation”). Even files in our test suite contain these.

Instance Method Summary collapse

Constructor Details

#initialize(multiple_exif_results) ⇒ EXIFStack

Returns a new instance of EXIFStack.



105
106
107
# File 'lib/parsers/exif_parser.rb', line 105

def initialize(multiple_exif_results)
  @multiple_exif_results = Array(multiple_exif_results)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(*a) ⇒ Object (private)



156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/parsers/exif_parser.rb', line 156

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

Instance Method Details

#orientationObject



121
122
123
124
125
126
127
128
129
130
131
# File 'lib/parsers/exif_parser.rb', line 121

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
    return orientation_value if !orientation_value.nil? && orientation_value != 0
  end
  0 # If none were found - the orientation is unknown
end

#orientation_symObject



113
114
115
# File 'lib/parsers/exif_parser.rb', line 113

def orientation_sym
  ORIENTATIONS.fetch(orientation)
end

#rotated?Boolean

Returns:

  • (Boolean)


117
118
119
# File 'lib/parsers/exif_parser.rb', line 117

def rotated?
  orientation > 4
end

#to_hashObject

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



135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/parsers/exif_parser.rb', line 135

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) ⇒ Object



109
110
111
# File 'lib/parsers/exif_parser.rb', line 109

def to_json(*maybe_coder)
  to_hash.to_json(*maybe_coder)
end