def _decode_metadata(data): fp = io.BytesIO(data) items_count = read_fmt("I", fp)[0] items = [] for x in range(items_count): sig = fp.read(4) if sig != b"8BIM": raise Error("Invalid signature in metadata item (%r)" % sig) key, copy_on_sheet, data_length = read_fmt("4s ? 3x I", fp) data = fp.read(data_length) if data_length < 4 + 12: # descr_version is 4 bytes, descriptor is at least 12 bytes, # so data can't be a descriptor. descr_ver = None else: # try load data as a descriptor fp2 = io.BytesIO(data) descr_ver = read_fmt("I", fp2)[0] try: data = decode_descriptor(None, fp2) except UnknownOSType as e: # FIXME: can it fail with other exceptions? descr_ver = None warnings.warn("Can't decode metadata item (%s)" % e) items.append(MetadataItem(key, copy_on_sheet, descr_ver, data)) return items
def _read_layers(fp, encoding, depth, length=None): """ Reads info about layers. """ logger.debug('reading layers...') if length is None: length = read_fmt("I", fp)[0] layer_count = read_fmt("h", fp)[0] logger.debug('layer_count=%d, length=%d', layer_count, length) layer_records = [] for idx in range(abs(layer_count)): logger.debug('reading layer record %d, pos=%d', idx, fp.tell()) layer = _read_layer_record(fp, encoding) layer_records.append(layer) channel_image_data = [] for idx, layer in enumerate(layer_records): logger.debug('reading layer channel data %d, pos=%d', idx, fp.tell()) data = _read_channel_image_data(fp, layer, depth) channel_image_data.append(data) return Layers(length, layer_count, layer_records, channel_image_data)
def _decode_bevel_info(data): fp = io.BytesIO(data) version, angle, depth, blur = read_fmt("IiII", fp) highlight_blend_mode = _read_blend_mode(fp) shadow_blend_mode = _read_blend_mode(fp) highlight_color = decode_color(fp) shadow_color = decode_color(fp) bevel_style, highlight_opacity, shadow_opacity = read_fmt("3B", fp) enabled, use_global_angle, direction = read_fmt("3B", fp) real_highlight_color = None real_shadow_color = None if version == 2: real_highlight_color = decode_color(fp) real_shadow_color = decode_color(fp) return BevelInfo( version, bool(enabled), bevel_style, depth, direction, blur, angle, bool(use_global_angle), highlight_blend_mode, highlight_color, highlight_opacity, shadow_blend_mode, shadow_color, shadow_opacity, real_highlight_color, real_shadow_color )
def _decode_section_divider(data): data_length = len(data) blend_mode = None sub_type = None fp = io.BytesIO(data) tp = read_fmt("I", fp)[0] if not SectionDivider.is_known(tp): warnings.warn("Unknown section divider type (%s)" % tp) if data_length >= 12: sig = fp.read(4) if sig != b"8BIM": raise Error("Invalid signature in section divider block (%r)" % sig) blend_mode = fp.read(4) if not BlendMode.is_known(blend_mode): warnings.warn("Unknown section divider blend mode (%s)" % blend_mode) if data_length >= 16: sub_type = read_fmt("I", fp)[0] if not SectionDividerSub.is_known(sub_type): warnings.warn("Unknown section divider sub-type (%s)" % sub_type) return Divider(tp, blend_mode, sub_type)
def _decode_type_tool_object_setting(data): fp = io.BytesIO(data) ver, xx, xy, yx, yy, tx, ty, txt_ver, desc_ver1 = read_fmt("H 6Q H I", fp) # This decoder needs to be updated if we have new formats. if ver != 1 or txt_ver != 50 or desc_ver1 != 16: warnings.warn("Ignoring type setting tagged block due to old versions") return try: text_data = decode_descriptor(None, fp) except UnknownOSType as e: warnings.warn("Ignoring type setting tagged block (%s)" % e) return # XXX: Until Engine Data is parsed properly, the following cannot be parsed. # The end of the engine data dictates where this starts. return TypeToolObjectSetting(ver, xx, xy, yx, yy, tx, ty, txt_ver, desc_ver1, text_data) warp_ver, desc_ver2 = read_fmt("H I", fp) if warp_ver != 1 or desc_ver2 != 16: warnings.warn("Ignoring type setting tagged block due to old versions") return try: warp_data = decode_descriptor(None, fp) except UnknownOSType as e: warnings.warn("Ignoring type setting tagged block (%s)" % e) return left, top, right, bottom = read_fmt("4Q", fp) return TypeToolObjectSetting(ver, xx, xy, yx, yy, tx, ty, txt_ver, desc_ver1, text_data, warp_ver, desc_ver2, warp_data, left, top, right, bottom)
def decode(effects): """ Reads and decodes info about layer effects. """ fp = io.BytesIO(effects) version, effects_count = read_fmt("HH", fp) effects_list = [] for idx in range(effects_count): sig = fp.read(4) if sig != b'8BIM': raise Error("Error parsing layer effect: invalid signature (%r)" % sig) effect_type = fp.read(4) if not EffectOSType.is_known(effect_type): warnings.warn("Unknown effect type (%s)" % effect_type) effect_info_length = read_fmt("I", fp)[0] effect_info = fp.read(effect_info_length) decoder = _effect_info_decoders.get(effect_type, lambda data: data) effects_list.append(LayerEffect(effect_type, decoder(effect_info))) return Effects(version, effects_count, effects_list)
def decode_prop(key, fp): name = read_unicode_string(fp)[:-1] classID_length = read_fmt("I", fp)[0] classID = fp.read(classID_length or 4) keyID_length = read_fmt("I", fp)[0] keyID = fp.read(keyID_length or 4) return Property(name, classID, keyID)
def _decode_type_tool_object_setting(data): fp = io.BytesIO(data) ver, xx, xy, yx, yy, tx, ty, txt_ver, descr1_ver = read_fmt("H 6d H I", fp) # This decoder needs to be updated if we have new formats. if ver != 1 or txt_ver != 50 or descr1_ver != 16: warnings.warn("Ignoring type setting tagged block due to old versions") return data try: text_data = decode_descriptor(None, fp) except UnknownOSType as e: warnings.warn("Ignoring type setting tagged block (%s)" % e) return data warp_ver, descr2_ver = read_fmt("H I", fp) if warp_ver != 1 or descr2_ver != 16: warnings.warn("Ignoring type setting tagged block due to old versions") return data try: warp_data = decode_descriptor(None, fp) except UnknownOSType as e: warnings.warn("Ignoring type setting tagged block (%s)" % e) return data left, top, right, bottom = read_fmt("4i", fp) # wrong info in specs... return TypeToolObjectSetting( ver, xx, xy, yx, yy, tx, ty, txt_ver, descr1_ver, text_data, warp_ver, descr2_ver, warp_data, left, top, right, bottom )
def _read_layer_record(fp, encoding): """ Reads single layer record. """ top, left, bottom, right, num_channels = read_fmt("4i H", fp) logger.debug(' top=%d, left=%d, bottom=%d, right=%d, num_channels=%d', top, left, bottom, right, num_channels) channel_info = [] for channel_num in range(num_channels): info = ChannelInfo(*read_fmt("hI", fp)) channel_info.append(info) sig = fp.read(4) if sig != b'8BIM': raise Error("Error parsing layer: invalid signature (%r)" % sig) blend_mode = fp.read(4) if not BlendMode.is_known(blend_mode): warnings.warn("Unknown blend mode (%s)" % blend_mode) opacity, clipping, flags, extra_length = read_fmt("BBBxI", fp) if not Clipping.is_known(clipping): warnings.warn("Unknown clipping (%s)" % clipping) logger.debug(' extra_length=%s', extra_length) flags = LayerFlags( bool(flags & 1), not bool(flags & 2), # why "not"? bool(flags & 16) if bool(flags & 8) else None ) start_pos = fp.tell() mask_data = _read_layer_mask_data(fp) blending_ranges = _read_layer_blending_ranges(fp) name = read_pascal_string(fp, encoding, 4) remaining_length = extra_length - (fp.tell() - start_pos) logger.debug(' reading layer tagged blocks...') logger.debug(' length=%d, start_pos=%d', remaining_length, fp.tell()) tagged_blocks = _read_layer_tagged_blocks(fp, remaining_length) remaining_length = extra_length - (fp.tell() - start_pos) if remaining_length > 0: fp.seek(remaining_length, 1) # skip the remainder logger.debug(' skipping %s bytes', remaining_length) return LayerRecord( top, left, bottom, right, num_channels, channel_info, blend_mode, opacity, clipping, flags, mask_data, blending_ranges, name, tagged_blocks )
def decode_enum_ref(key, fp): name = read_unicode_string(fp)[:-1] classID_length = read_fmt("I", fp)[0] classID = fp.read(classID_length or 4) typeID_length = read_fmt("I", fp)[0] typeID = fp.read(typeID_length or 4) enum_length = read_fmt("I", fp)[0] enum = fp.read(enum_length or 4) return EnumReference(name, classID, typeID, enum)
def _decode_metadata(data): fp = io.BytesIO(data) items_count = read_fmt("I", fp)[0] items = [] for x in range(items_count): sig, key, copy_on_sheet, data_length = read_fmt("4s 4s ? 3x I", fp) data = fp.read(data_length) items.append(MetadataItem(sig, key, copy_on_sheet, data)) return items
def _decode_version_info(data): fp = io.BytesIO(data) return VersionInfo( read_fmt("I", fp)[0], read_fmt("?", fp)[0], read_unicode_string(fp), read_unicode_string(fp), read_fmt("I", fp)[0], )
def decode_descriptor(data): fp = io.BytesIO(data) name = read_unicode_string(fp) classID_length = read_fmt("I", fp)[0] classID = fp.read(classID_length or 4) item_count = read_fmt("I", fp)[0] items = fp.read() # TODO: detailed parsing return Descriptor(name, classID, item_count, items)
def decode_color(fp): color_space_id = read_fmt("H", fp)[0] if not ColorSpaceID.is_known(color_space_id): warnings.warn("Unknown color space (%s)" % color_space_id) if color_space_id == ColorSpaceID.LAB: color_data = read_fmt("4h", fp) else: color_data = read_fmt("4H", fp) return Color(color_space_id, color_data)
def decode_list(key, fp): items_count = read_fmt("I", fp)[0] items = [] for _ in range(items_count): ostype = read_fmt("I", fp) decode_ostype = get_ostype(ostype) if decode_ostype: value = decode_ostype(fp) if value is not None: items.append(value) return List(items)
def decode_unit_floats(key, fp): unit_key = fp.read(4) if not UnitFloatType.is_known(unit_key): warnings.warn("Unknown UnitFloatType: %r" % unit_key) floats_count = read_fmt("I", fp)[0] floats = [] for n in range(floats_count): value = read_fmt("d", fp)[0] floats.append(UnitFloat(UnitFloatType.name_of(unit_key), value)) return floats
def decode_object_array(key, fp): items_per_object_count = read_fmt("I", fp)[0] classObj = decode_class(None, fp) items_count = read_fmt("I", fp)[0] items = [] for n in range(items_count): object_array_item = decode_object_array_item(None, fp) if object_array_item is not None: items.append(object_array_item) return ObjectArray(classObj, items)
def decode_unit_float(key, fp): unit_key = read_fmt("I", fp) unit = { b'#Ang': 'angle', b'#Rsl': 'density', b'#Rlt': 'distance', b'#Nne': 'none', b'#Prc': 'percent', b'#Pxl': 'pixels', }.get(unit_key, None) if unit: value = read_fmt("d", fp) return UnitFloat(unit, value)
def decode(data): """ Reads and decodes info about linked layers. These are embedded files (embedded smart objects). But Adobe calls them "linked layers", so we'll follow that nomenclature. Note that non-embedded smart objects are not included here. """ fp = io.BytesIO(data) layers = [] while True: start = fp.tell() length_buf = fp.read(8) if not length_buf: break # end of file length = struct.unpack(str('>Q'), length_buf)[0] liFD, version = read_fmt('4s I', fp) if liFD != b'liFD': warnings.warn('unknown layer type') break unique_id = read_pascal_string(fp, 'ascii') filename = read_unicode_string(fp) filetype, creator, filelength, have_file_open_descriptor = read_fmt('4s 4s Q B', fp) filetype = str(filetype) if have_file_open_descriptor: # Does not seem to contain any useful information undocumented_integer = read_fmt("I", fp) file_open_descriptor = decode_descriptor(None, fp) else: file_open_descriptor = None decoded = fp.read(filelength) # Undocumented extra field if version == 5: uuid = read_unicode_string(fp) else: uuid = None layers.append( LinkedLayer(version, unique_id, filename, filetype, file_open_descriptor, creator, decoded, uuid) ) # Gobble up anything that we don't know how to decode expected_position = start + 8 + length # first 8 bytes contained the length if expected_position != fp.tell(): warnings.warn('skipping over undocumented additional fields') fp.read(expected_position - fp.tell()) # Each layer is padded to start and end at 4-byte boundary pad = -fp.tell() % 4 fp.read(pad) return LinkedLayerCollection(layers)
def _decode_solid_fill_info(data): fp = io.BytesIO(data) version = read_fmt("I", fp)[0] blend_mode = _read_blend_mode(fp) color = decode_color(fp) opacity, enabled = read_fmt("2B", fp) native_color = decode_color(fp) return SolidFillInfo( version, bool(enabled), blend_mode, color, opacity, native_color )
def decode_color(fp): color_space_id = read_fmt("H", fp)[0] if not ColorSpaceID.is_known(color_space_id): warnings.warn("Unknown color space (%s)" % color_space_id) if color_space_id in (ColorSpaceID.RGB, ColorSpaceID.HSB): color_data = read_fmt("3H 2x", fp) elif color_space_id == ColorSpaceID.LAB: color_data = read_fmt("Hhh 2x", fp) elif color_space_id == ColorSpaceID.GRAYSCALE: color_data = read_fmt("H 6x", fp) else: color_data = read_fmt("4H", fp) return Color(color_space_id, color_data)
def _decode_old_display_info(data): fp = io.BytesIO(data) return DisplayInfo( decode_color(fp), *(read_fmt("HB", fp)) )
def _read_global_mask_info(fp): """ Reads global layer mask info. """ # XXX: Does it really work properly? What is it for? start_pos = fp.tell() length = read_fmt("H", fp)[0] if length: overlay_color_space, c1, c2, c3, c4, opacity, kind = read_fmt("H 4H HB", fp) filler_length = length - (fp.tell()-start_pos) if filler_length > 0: fp.seek(filler_length, 1) return GlobalMaskInfo(overlay_color_space, (c1, c2, c3, c4), opacity, kind) else: return None
def decode_unit_float(key, fp): unit_key = fp.read(4) if not UnitFloatType.is_known(unit_key): warnings.warn("Unknown UnitFloatType: %r" % unit_key) value = read_fmt("d", fp)[0] return UnitFloat(UnitFloatType.name_of(unit_key), value)
def read(fp, encoding, depth): """ Reads layers and masks information. """ logger.debug('reading layers and masks information...') length = read_fmt("I", fp)[0] start_pos = fp.tell() logger.debug('length=%d, start_pos=%d', length, start_pos) layers = _read_layers(fp, encoding, depth) global_mask_info = None tagged_blocks = [] remaining_length = length - (fp.tell() - start_pos) if remaining_length > 0: logger.debug('reading global mask info...') global_mask_info = _read_global_mask_info(fp) synchronize(fp) # hack hack hack remaining_length = length - (fp.tell() - start_pos) if remaining_length > 0: tagged_blocks = _read_layer_tagged_blocks(fp, remaining_length, 4) # remaining_length = length - (fp.tell() - start_pos) # if remaining_length > 0: # fp.seek(remaining_length, 1) # logger.debug('skipping %s bytes', remaining_length) return LayerAndMaskData(layers, global_mask_info, tagged_blocks)
def _read_block(fp, encoding): """ Reads single image resource block. Such blocks contain non-pixel data for the images (e.g. pen tool paths). """ sig = fp.read(4) if sig != b"8BIM": raise Error("Invalid resource signature (%r)" % sig) resource_id = read_fmt("H", fp)[0] name = read_pascal_string(fp, encoding, 2) data_size = pad(read_fmt("I", fp)[0], 2) data = fp.read(data_size) return ImageResource(resource_id, name, data)
def _decode_soco(data): fp = io.BytesIO(data) version = read_fmt("I", fp) try: data = decode_descriptor(None, fp) return SolidColorSettings(version, data) except UnknownOSType as e: warnings.warn("Ignoring solid color tagged block (%s)" % e)
def _read_layer_record(fp, encoding): """ Reads single layer record. """ top, left, bottom, right, num_channels = read_fmt("4i H", fp) channel_info = [] for channel_num in range(num_channels): info = ChannelInfo(*read_fmt("hI", fp)) channel_info.append(info) sig = fp.read(4) if sig != b'8BIM': raise Error("Error parsing layer: invalid signature (%r)" % sig) blend_mode = fp.read(4).decode('ascii') if not BlendMode.is_known(blend_mode): warnings.warn("Unknown blend mode (%s)" % blend_mode) opacity, clipping, flags, extra_length = read_fmt("BBBxI", fp) flags = LayerFlags(bool(flags & 1), not bool(flags & 2)) # why not? if not Clipping.is_known(clipping): warnings.warn("Unknown clipping: %s" % clipping) start = fp.tell() mask_data = _read_layer_mask_data(fp) blending_ranges = _read_layer_blending_ranges(fp) name = read_pascal_string(fp, encoding, 4) remaining_length = extra_length - (fp.tell()-start) tagged_blocks = _read_layer_tagged_blocks(fp, remaining_length) remaining_length = extra_length - (fp.tell()-start) fp.seek(remaining_length, 1) # skip the reminder return LayerRecord( top, left, bottom, right, num_channels, channel_info, blend_mode, opacity, clipping, flags, mask_data, blending_ranges, name, tagged_blocks )
def _read_layer_mask_data(fp): """ Reads layer mask or adjustment layer data. """ length = read_fmt("I", fp)[0] start_pos = fp.tell() logger.debug(' reading layer mask data...') logger.debug(' length=%d, start_pos=%d', length, start_pos) if not length: return None top, left, bottom, right, background_color, flags = read_fmt("4i 2B", fp) flags = MaskFlags( bool(flags & 1), bool(flags & 2), bool(flags & 4), bool(flags & 8), bool(flags & 16) ) # Order is based on tests. The specification is messed up here... if length < 36: real_flags, real_background_color = None, None real_top, real_left, real_bottom, real_right = None, None, None, None else: real_flags, real_background_color = read_fmt("2B", fp) real_flags = MaskFlags( bool(real_flags & 1), bool(real_flags & 2), bool(real_flags & 4), bool(real_flags & 8), bool(real_flags & 16) ) real_top, real_left, real_bottom, real_right = read_fmt("4i", fp) if flags.parameters_applied: parameters = read_fmt("B", fp)[0] parameters = MaskParameters( read_fmt("B", fp)[0] if bool(parameters & 1) else None, read_fmt("d", fp)[0] if bool(parameters & 2) else None, read_fmt("B", fp)[0] if bool(parameters & 4) else None, read_fmt("d", fp)[0] if bool(parameters & 8) else None ) else: parameters = None padding_size = length - (fp.tell() - start_pos) if padding_size > 0: fp.seek(padding_size, 1) return MaskData( top, left, bottom, right, background_color, flags, parameters, real_flags, real_background_color, real_top, real_left, real_bottom, real_right )
def _read_global_mask_info(fp): """ Reads global layer mask info. """ length = read_fmt("I", fp)[0] if not length: return None start_pos = fp.tell() overlay_color = fp.read(10) opacity, kind = read_fmt("HB", fp) filler_length = length - (fp.tell() - start_pos) if filler_length > 0: fp.seek(filler_length, 1) return GlobalMaskInfo(overlay_color, opacity, kind)
def read(cls, fp, **kwargs): count = read_fmt('I', fp)[0] items = [] for _ in range(count): items.append(MetadataSetting.read(fp)) return cls(items)
def read(cls, fp): unit, count = read_fmt('4sI', fp) values = list(read_fmt('%dd' % count, fp)) return cls(unit, values)
def read(cls, fp): return cls(*decode_fixed_point(read_fmt('5i4x', fp)))
def read(cls, fp): items_count = read_fmt('I', fp)[0] return cls(items_count=items_count, **cls._read_body(fp))
def read(cls, fp): number, id = read_fmt('2I', fp) name = read_unicode_string(fp) return cls(number, id, name)
def read(cls, fp): unit, value = read_fmt('4sd', fp) return cls(unit=UnitFloatType(unit), value=value)
def read(cls, fp, **kwargs): version, has_composite = read_fmt('I?', fp) writer = read_unicode_string(fp) reader = read_unicode_string(fp) file_version = read_fmt('I', fp)[0] return cls(version, has_composite, writer, reader, file_version)
def read(cls, fp): return cls(*read_fmt(cls._FORMAT, fp))
def _read_channel_image_data(fp, layer, depth): """ Reads image data for all channels in a layer. """ channel_data = [] bytes_per_pixel = depth // 8 for idx, channel in enumerate(layer.channels): logger.debug(" reading %s", channel) if channel.id == ChannelID.USER_LAYER_MASK: w, h = layer.mask_data.width(), layer.mask_data.height() elif channel.id == ChannelID.REAL_USER_LAYER_MASK: w, h = layer.mask_data.real_width(), layer.mask_data.real_height() else: w, h = layer.width(), layer.height() start_pos = fp.tell() compress_type = read_fmt("H", fp)[0] logger.debug(" start_pos=%s, compress_type=%s", start_pos, Compression.name_of(compress_type)) data = None # read data size if compress_type == Compression.RAW: data_size = w * h * bytes_per_pixel logger.debug(' data size = %sx%sx%s=%s bytes', w, h, bytes_per_pixel, data_size) elif compress_type == Compression.PACK_BITS: byte_counts = read_be_array("H", h, fp) data_size = sum(byte_counts) logger.debug(' data size = %s bytes', data_size) elif compress_type in (Compression.ZIP, Compression.ZIP_WITH_PREDICTION): data_size = channel.length - 2 logger.debug(' data size = %s-2=%s bytes', channel.length, data_size) else: warnings.warn("Bad compression type %s" % compress_type) return [] # read the data itself if data_size > channel.length: warnings.warn("Incorrect data size: %s > %s" % (data_size, channel.length)) else: raw_data = fp.read(data_size) if compress_type in (Compression.RAW, Compression.PACK_BITS): data = raw_data elif compress_type == Compression.ZIP: data = zlib.decompress(raw_data) elif compress_type == Compression.ZIP_WITH_PREDICTION: decompressed = zlib.decompress(raw_data) data = compression.decode_prediction(decompressed, w, h, bytes_per_pixel) if data is None: return [] channel_data.append( ChannelData(compress_type, data, layer.mask_data)) remaining_length = channel.length - (fp.tell() - start_pos) if remaining_length > 0: fp.seek(remaining_length, 1) logger.debug(' skipping %s bytes', remaining_length) return channel_data
def read(cls, fp, **kwargs): return cls(SheetColorType(*read_fmt('H6x', fp)))
def read(cls, fp, **kwargs): return cls(list(read_fmt('2d', fp)))
def read_channel_range(): src_start, src_end, dest_start, dest_end = read_fmt("4H", fp) return (src_start, src_end), (dest_start, dest_end)
def read(cls, fp, **kwargs): kind, version = read_fmt('4sI', fp) data = DescriptorBlock.read(fp) return cls(kind, version, data)
def read(cls, fp, **kwargs): color = Color.read(fp) opacity, flag = read_fmt('HBx', fp) return cls(color, opacity, flag)
def read(cls, fp): read_fmt('24x', fp) return cls()
def _read_body(cls, fp): overlay_color = list(read_fmt('5H', fp)) opacity, kind = read_fmt('HB', fp) return cls(overlay_color, opacity, kind)
def read(cls, fp): preceding = decode_fixed_point(read_fmt('2i', fp)) anchor = decode_fixed_point(read_fmt('2i', fp)) leaving = decode_fixed_point(read_fmt('2i', fp)) return cls(preceding, anchor, leaving)
def read(cls, fp): name = read_unicode_string(fp) classID = read_length_and_key(fp) offset = read_fmt('I', fp)[0] return cls(name, classID, offset)
def read(cls, fp, **kwargs): color = Color.read(fp) opacity = read_fmt('H', fp)[0] return cls(color, opacity)
def read(cls, fp): return cls(*read_fmt('d', fp))
def read(cls, fp, **kwargs): items = [] while is_readable(fp, 4): items.append(read_fmt('I', fp)[0]) return cls(items)
def read(cls, fp): return cls(*read_fmt('H22x', fp))
def read(cls, fp, **kwargs): version, flags = read_fmt('2I', fp) assert version == 3, 'Unknown vector mask version %d' % version path = Path.read(fp) return cls(version, flags, path)
def read(cls, fp, **kwargs): key, version = read_fmt('4sI', fp) return cls(key=key, version=version, **cls._read_body(fp))
def read(cls, fp): return cls(read_fmt('i', fp)[0])
def read_channel_range(f): values = read_fmt("4H", f) return [values[0:2], values[2:4]]
def read(cls, fp, **kwargs): curve = read_fmt('13H', fp) override = read_fmt('H', fp)[0] return cls(curve, override)
def read(cls, fp, **kwargs): version, data_version = read_fmt('2I', fp) return cls(version=version, data_version=data_version, **cls._read_body(fp))
def read(cls, fp, **kwargs): version = read_fmt('I', fp)[0] return cls(version=version, **cls._read_body(fp))
def read(cls, fp, **kwargs): count = read_fmt('I', fp)[0] items = [] for _ in range(count): items.append(URLItem.read(fp)) return cls(items)