class Exif::Scanner

Attributes

result[R]

Public Class Methods

new(fin) click to toggle source
# File lib/exifparser/scan.rb, line 18
def initialize(fin)
  @fin = fin.binmode
  @result = {}
  @tiffHeader0 = nil  # origin at which TIFF header begins
  @byteOrder_module = nil
end

Public Instance Methods

finish() click to toggle source
# File lib/exifparser/scan.rb, line 26
def finish
  @fin.close
end
marshal_dump() click to toggle source
# File lib/exifparser/scan.rb, line 213
def marshal_dump
  {
    result: Marshal.dump(@result),
    tiffHeader0: Marshal.dump(@tiffHeader0),
    byteOrder_module: Marshal.dump(@byteOrder_module)
  }
end
marshal_load(marshaled) click to toggle source
# File lib/exifparser/scan.rb, line 221
def marshal_load marshaled
  @result = Marshal.load marshaled[:result]
  @tiffHeader0 = Marshal.load marshaled[:tiffHeader0]
  @byteOrder_module = Marshal.load marshaled[:byteOrder_module]
end
scan() click to toggle source
# File lib/exifparser/scan.rb, line 30
def scan
  tic = Time.now if $DEBUG
  #
  # check soi (start of image)
  #
  @fin.pos = 0
  unless get_soi == 0xFFD8
    raise RuntimeError, 'not JPEG format'
  end

  #
  # seek app1 (EXIF signature)
  #
  begin
    marker = get_marker
    break if (marker == 0xFFE1)
    size = get_marker_datasize
    @fin.seek(size - 2, IO::SEEK_CUR)
  end while (!@fin.eof?)

  if marker != 0xFFE1
    raise RuntimeError, 'not EXIF format'
  end

  #
  # get app1 Data size
  #
  @result[:app1DataSize] = get_marker_datasize()
  curpos = @fin.pos
  @result[:app1Data] = fin_read_n(@result[:app1DataSize])
  @fin.pos = curpos

  #
  # EXIF header must be exactly "Exif\000\000", but some model
  # does not provide correct one. So we relax the condition.
  #
  if (h = exif_identifier()) !~ /\AExif\000/
    raise RuntimeError, "Invalid EXIF header: #{h}"
  end

  #
  # examine TIFF header
  #
  @tiffHeader0, tiff_header = get_tiff_header()

  #
  # get byte order
  #
  case tiff_header[0,2]
  when "MM"
    @byteOrder_module = Utils::Decode::Motorola
  when "II"
    @byteOrder_module = Utils::Decode::Intel
  else
    raise RuntimeError, "Unknown byte order"
  end
  self.extend @byteOrder_module
  @result[:offset_IFD0] = decode_ulong(tiff_header[4..-1])

  #
  # IFD0
  #
  @fin.pos = @tiffHeader0 + @result[:offset_IFD0]
  @result[:IFD0] = []
  scan_IFD(Tag::IFD0Table, Tag::IFD0Table.name) do |tag|
    @result[:IFD0].push tag
  end

  #
  # IFD1
  #
  @result[:IFD1] = []
  next_ifd = decode_ulong(fin_read_n(4))
  if next_ifd > 0
    @fin.pos = @tiffHeader0 + next_ifd
    scan_IFD(Tag::IFD1Table, Tag::IFD1Table.name) do |tag|
      @result[:IFD1].push tag
    end
  end

  #
  # GPS IFD
  #
  @result[:GPS] = []
  found = @result[:IFD0].find{ |e|
    e.class == Tag::GPSIFDPointer
  }
  if found
    @result[:offset_GPS] = found.processData
    @fin.pos = @tiffHeader0 + @result[:offset_GPS]
    scan_IFD(Tag::GPSIFDTable, Tag::GPSIFDTable.name) do |tag|
      @result[:GPS].push tag
    end
  end

  #
  # Exif IFD
  #
  @result[:Exif] = []
  found = @result[:IFD0].find{ |e|
    e.class == Tag::ExifIFDPointer
  }
  if found
    @result[:offset_Exif] = found.processData
    @fin.pos = @tiffHeader0 + @result[:offset_Exif]
    scan_IFD(Tag::ExifIFDTable, Tag::ExifIFDTable.name) do |tag|
      @result[:Exif].push tag
    end
  end

  #
  # Interoperability subIFD
  #
  @result[:Interoperability] = []
  found = @result[:Exif].find {|e|
    e.class == Tag::InteroperabilityIFDPointer
  }
  if found
    @result[:offset_InteroperabilityIFD] = found.processData
    @fin.pos = @tiffHeader0 + @result[:offset_InteroperabilityIFD]
    scan_IFD(Tag::InteroperabilityIFDTable, Tag::InteroperabilityIFDTable.name) do |tag|
      @result[:Interoperability].push tag
    end
  end

  #
  # MakerNote subIFD
  #
  @result[:MakerNote]=[]
  found = @result[:Exif].find {|e| e.class == Tag::Exif::MakerNote }
  if (found)
    begin
      # Because some vendors do not put any identifier in the header,
      # we try to find which model is by seeing Tag::TIFF::Make, Tag::TIFF::Model.
      make = @result[:IFD0].find {|e| e.class == Tag::TIFF::Make}
      model = @result[:IFD0].find {|e| e.class == Tag::TIFF::Model}
      # prove the maker
      makernote_class = Exif::MakerNote.prove(found.data, make, model)
      # set file pointer to the position where the tag was found.
      @fin.pos = found.pos
      makernote = makernote_class.new(@fin, @tiffHeader0, found.dataPos, @byteOrder_module)
      makernote.scan_IFD do |tag|
        @result[:MakerNote].push tag
      end
    rescue MakerNote::NotSupportedError
    rescue Exception # what to do?
      if $DEBUG
        raise $!
      end
    end
  end

  #
  # get thumbnail
  #
  if !@result[:IFD1].empty?
    format = @result[:IFD1].find do |e|
      e.class == Tag::TIFF::Compression
    end.value
    unless format == 6
      raise NotImplementedError, "Sorry, thumbnail of other than JPEG format is not supported."
    end
    thumbStart = @result[:IFD1].find do |e|
      e.class == Exif::Tag::TIFF::JpegInterchangeFormat
    end.value
    thumbLen = @result[:IFD1].find do |e|
      e.class == Exif::Tag::TIFF::JpegInterchangeFormatLength
    end.value
    @fin.pos = @tiffHeader0 + thumbStart
    # check JPEG soi maker
    if get_soi != 0xFFD8
      raise RuntimeError, 'not JPEG format'
    end
    @fin.pos = @fin.pos - 2
    # now read thumbnail image
    @result[:Thumbnail] = @fin.read(thumbLen)
  end

  # turn on if $DEBUG
  toc = Time.now if $DEBUG
  puts(sprintf("scan time: %1.4f sec.", toc-tic)) if $DEBUG
end

Private Instance Methods

eoi() click to toggle source
# File lib/exifparser/scan.rb, line 285
def eoi
  @fin.seek(-2, IO::SEEK_END)
  @fin.read(2)
end
exif_identifier() click to toggle source
# File lib/exifparser/scan.rb, line 276
def exif_identifier
  @fin.read(6)
end
fin_read_n(n) click to toggle source
# File lib/exifparser/scan.rb, line 229
def fin_read_n(n)
  @fin.read(n)
end
get_marker() click to toggle source
# File lib/exifparser/scan.rb, line 268
def get_marker
  (@fin.read(1).unpack("C*")[0]) << 8 | (@fin.read(1).unpack("C*")[0])
end
get_marker_datasize() click to toggle source
# File lib/exifparser/scan.rb, line 272
def get_marker_datasize
  (@fin.read(1).unpack("C*")[0]) << 8 | (@fin.read(1).unpack("C*")[0])
end
get_soi() click to toggle source
# File lib/exifparser/scan.rb, line 264
def get_soi
  (@fin.read(1).unpack("C*")[0]) << 8 | (@fin.read(1).unpack("C*")[0])
end
get_tiff_header() click to toggle source
# File lib/exifparser/scan.rb, line 280
def get_tiff_header
  pos = @fin.pos
  [pos, fin_read_n(8)]
end
scan_IFD(tagTable, ifdname) { |obj| ... } click to toggle source
# File lib/exifparser/scan.rb, line 233
def scan_IFD(tagTable, ifdname)
  num_dirs = decode_ushort(fin_read_n(2))
  1.upto(num_dirs) {
    curpos_tag = @fin.pos
    tag = parseTagID(fin_read_n(2))
    tagclass = Tag.find(tag.hex, tagTable)
    unit, formatter = Tag::Format::Unit[decode_ushort(fin_read_n(2))]
    count = decode_ulong(fin_read_n(4))
    tagdata = fin_read_n(4)
    next if formatter == nil
    obj = tagclass.new(tag, ifdname, count)
    obj.extend formatter, @byteOrder_module
    obj.pos = curpos_tag
    if unit * count > 4
      curpos = @fin.pos
      begin
        @fin.pos = @tiffHeader0 + decode_ulong(tagdata)
        obj.dataPos = @fin.pos
        obj.data = fin_read_n(unit*count)
      ensure
        @fin.pos = curpos
      end
    else
      obj.dataPos = @fin.pos - 4
      obj.data = tagdata
    end
    obj.processData
    yield obj
  }
end