def parse(cls, stream: IO) -> 'TiffHeader': tiff_header_offset = stream.tell() # read byte order byte_order = tools.read_bytes_strict(stream, 2) if byte_order[0] == byte_order[1] == 0x49: byte_order = ByteOrder.LITTLE_ENDIAN elif byte_order[0] == byte_order[1] == 0x4D: byte_order = ByteOrder.BIG_ENDIAN else: raise RuntimeError('invalid tiff header format') # check tiff header signature tiff_id = tools.read_bytes_strict(stream, 2) tiff_id = endianess.convert(tiff_id, byte_order=byte_order) if tiff_id != TiffHeader.ID: raise RuntimeError('invalid tiff header format') # read IFD0 offset ifd0_offset = tools.read_bytes_strict(stream, 4) ifd0_offset = endianess.convert(ifd0_offset, byte_order=byte_order) return TiffHeader(offset=tiff_header_offset, byte_order=byte_order, ifd0_offset=ifd0_offset)
def load(self): if self.is_loaded: return self._stream.seek(self.value_offset) data = tools.read_bytes_strict(self._stream, self.count*self.field_type.byte_count) self._value = parse_value(data=data, count=self.count, field_type=self.field_type, byte_order=self._byte_order) self._is_loaded = True
def scan_jpeg_structure(stream: IO, include_eoi: bool) -> List[JpegSegment]: offset = stream.tell() check_jpeg_signature(stream) segment = JpegSegment.create(marker=SOI, stream=stream, offset=offset, size=JpegMarker.MARKER_SIZE) segment.log() structure = [segment] offset += JpegMarker.MARKER_SIZE # scan segments segment_marker = stream.read(JpegMarker.MARKER_SIZE) while len(segment_marker) == JpegMarker.MARKER_SIZE: segment_marker = endianess.convert_big_endian(segment_marker) segment_marker = JpegMarker.detect(segment_marker) if segment_marker == EOI: raise RuntimeError('unexpected EOI marker before SOS marker') segment_size = tools.read_bytes_strict(stream, JpegMarker.LENGTH_SIZE) segment_size = endianess.convert_big_endian( segment_size) + JpegMarker.MARKER_SIZE segment = JpegSegment.create(marker=segment_marker, stream=stream, offset=offset, size=segment_size) segment.log() structure.append(segment) offset += segment_size stream.seek( segment_size - JpegMarker.MARKER_SIZE - JpegMarker.LENGTH_SIZE, SEEK_CUR) if segment_marker == SOS: break segment_marker = stream.read(JpegMarker.MARKER_SIZE) if include_eoi: eoi_offset = scan_for_eoi(stream) if eoi_offset == 0: raise RuntimeError('EOI is not found') segment = JpegSegment.create(marker=EOI, stream=stream, offset=offset + eoi_offset, size=JpegMarker.MARKER_SIZE) segment.log() structure.append(segment) return structure
def parse_app_name(stream: IO) -> str: name = '' while True: byte = tools.read_bytes_strict(stream, 1) if byte[0] == 0x00: break name = name + byte.decode('ascii') return name
def load(self): if self.is_loaded: return self._stream.seek(self.offset) tools.logger.debug(f'[{self.marker.name}] segment loading...') self._stream.seek(self.offset + JpegMarker.MARKER_SIZE + JpegMarker.LENGTH_SIZE) self._name = parse_app_name(self._stream) tools.logger.debug(f'-> name: {self._name}') if self._name.upper() == 'JFIF': self._is_loaded = True return # Exif or custom APP[2-15] byte = tools.read_bytes_strict(self._stream, 1) # skip one more 0x00 byte ('Exif\0x00\0x00') if byte[0] != 0x00: raise RuntimeError('unexpected format of APP segment') # parse tiff header self._tiff_header = TiffHeader.parse(self._stream) tools.logger.debug(f'-> {self._tiff_header}') # parse IFD next_ifd_offset = self._tiff_header.offset + self._tiff_header.ifd0_offset next_offset_filed_used = False ifd = [] while True: self._stream.seek(next_ifd_offset) tools.logger.debug(f'-> IDF #{len(ifd)}, offset=0x{next_ifd_offset:08X}') ifd_i = ImageFileDirectory.parse(self._stream, tiff_header=self._tiff_header) ifd.append(ifd_i) if next_offset_filed_used and ifd_i.next_ifd_offset == 0: break # the last ifd is reached if ifd_i.next_ifd_offset > 0: next_ifd_offset = self._tiff_header.offset + ifd_i.next_ifd_offset next_offset_filed_used = True else: assert ifd_i.offset + ifd_i.size <= self.offset + self.size, 'smth wrong with sizes' # some camera manufactures put IFDs one by one without next_ifd_offset if ifd_i.offset + ifd_i.size == self.offset + self.size: break # the end of the segment is reached next_ifd_offset = ifd_i.offset + ifd_i.size self._ifd = tuple(ifd)
def parse(cls, stream: IO, tiff_header: TiffHeader) -> 'IfdField': field_offset = stream.tell() tag_id = tools.read_bytes_strict(stream, 2) tag_id = endianess.convert(tag_id, byte_order=tiff_header.byte_order) type_id = tools.read_bytes_strict(stream, 2) type_id = endianess.convert(type_id, byte_order=tiff_header.byte_order) if FieldType.is_unknown(type_id): type_id = FieldType.Unknown else: type_id = FieldType(type_id) count = tools.read_bytes_strict(stream, 4) count = endianess.convert(count, byte_order=tiff_header.byte_order) field_size = count * type_id.byte_count if field_size <= 4: value_offset = field_offset + 8 stream.seek(4, io.SEEK_CUR) # skip value data field_size = IfdField.FIXED_SIZE # no extra data outsize the field structure else: value_offset = tools.read_bytes_strict(stream, 4) value_offset = endianess.convert(value_offset, byte_order=tiff_header.byte_order) value_offset += tiff_header.offset field_size = tools.align4(field_size) + IfdField.FIXED_SIZE return IfdField(tag_id=tag_id, count=count, field_type=type_id, stream=stream, byte_order=tiff_header.byte_order, value_offset=value_offset, size=field_size, offset=field_offset)
def check_jpeg_signature(stream: IO): marker = tools.read_bytes_strict(stream, JpegMarker.MARKER_SIZE) marker = endianess.convert_big_endian(marker) if marker != SOI.signature: raise RuntimeError('file is not JPEG')