Class: FormatParser::NEFParser

Inherits:
Object
  • Object
show all
Includes:
EXIFParser, IOUtils
Defined in:
lib/parsers/nef_parser.rb

Constant Summary collapse

MAGIC_LE =
[0x49, 0x49, 0x2A, 0x0].pack('C4')
MAGIC_BE =
[0x4D, 0x4D, 0x0, 0x2A].pack('C4')
HEADER_BYTES =
[MAGIC_LE, MAGIC_BE]
NEF_MIME_TYPE =
'image/x-nikon-nef'
SUBFILE_TYPE_FULL_RESOLUTION =
0
SUBFILE_TYPE_REDUCED_RESOLUTION =
1
SHOULD_PARSE_SUB_IFDS =
true

Constants included from EXIFParser

EXIFParser::ORIENTATIONS

Constants included from IOUtils

IOUtils::INTEGER_DIRECTIVES

Instance Method Summary collapse

Methods included from EXIFParser

#exif_from_tiff_io

Methods included from IOUtils

#read_bytes, #read_fixed_point, #read_int, #safe_read, #safe_skip, #skip_bytes

Instance Method Details

#call(io) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/parsers/nef_parser.rb', line 19

def call(io)
  io = FormatParser::IOConstraint.new(io)

  return unless HEADER_BYTES.include?(safe_read(io, 4))

  # Because of how NEF files organize their IFDs and subIFDs, we need to dive into the subIFDs
  # to get the actual image dimensions instead of the preview's
  exif_data = exif_from_tiff_io(io, SHOULD_PARSE_SUB_IFDS)

  return unless valid?(exif_data)

  full_resolution_data = get_full_resolution_ifd(exif_data)

  w = full_resolution_data.image_width || exif_data.width || exif_data.pixel_x_dimension
  h = full_resolution_data.image_length || exif_data.height || exif_data.pixel_y_dimension

  FormatParser::Image.new(
    format: :nef,
    width_px: w,
    height_px: h,
    display_width_px: exif_data.rotated? ? h : w,
    display_height_px: exif_data.rotated? ? w : h,
    orientation: exif_data.orientation_sym,
    intrinsics: { exif: exif_data },
    content_type: NEF_MIME_TYPE,
  )
rescue EXIFR::MalformedTIFF
  nil
end

#get_full_resolution_ifd(exif_data) ⇒ Object

Investigates data from all subIFDs and find the one holding the full-resolution image



56
57
58
59
60
61
62
63
64
65
66
# File 'lib/parsers/nef_parser.rb', line 56

def get_full_resolution_ifd(exif_data)
  # Most of the time, NEF files have 2 subIFDs:
  # First one: Thumbnail (Reduced resolution)
  # Second one: Full resolution
  # While this is true in most situations, there are exceptions,
  # so we can't rely in this order alone.

  exif_data.sub_ifds_data.each do |_ifd_offset, ifd_data|
    return ifd_data if ifd_data.new_subfile_type == SUBFILE_TYPE_FULL_RESOLUTION
  end
end

#likely_match?(filename) ⇒ Boolean

Returns:

  • (Boolean)


15
16
17
# File 'lib/parsers/nef_parser.rb', line 15

def likely_match?(filename)
  filename =~ /\.nef$/i
end

#valid?(exif_data) ⇒ Boolean

Returns:

  • (Boolean)


49
50
51
52
53
# File 'lib/parsers/nef_parser.rb', line 49

def valid?(exif_data)
  # NEF files should hold subIFDs and have "NIKON" or "NIKON CORPORATION" as maker
  has_sub_ifds_data = !exif_data&.sub_ifds_data.keys.empty?
  has_sub_ifds_data && exif_data.make&.start_with?('NIKON')
end