Remove submodule, just put Dependencies in ./libs
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
from hachoir_parser.image.bmp import BmpFile
|
||||
from hachoir_parser.image.gif import GifFile
|
||||
from hachoir_parser.image.ico import IcoFile
|
||||
from hachoir_parser.image.jpeg import JpegFile
|
||||
from hachoir_parser.image.pcx import PcxFile
|
||||
from hachoir_parser.image.psd import PsdFile
|
||||
from hachoir_parser.image.png import PngFile
|
||||
from hachoir_parser.image.tga import TargaFile
|
||||
from hachoir_parser.image.tiff import TiffFile
|
||||
from hachoir_parser.image.wmf import WMF_File
|
||||
from hachoir_parser.image.xcf import XcfFile
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
"""
|
||||
Microsoft Bitmap picture parser.
|
||||
- file extension: ".bmp"
|
||||
|
||||
Author: Victor Stinner
|
||||
Creation: 16 december 2005
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet,
|
||||
UInt8, UInt16, UInt32, Bits,
|
||||
String, RawBytes, Enum,
|
||||
PaddingBytes, NullBytes, createPaddingField)
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal
|
||||
from hachoir_parser.image.common import RGB, PaletteRGBA
|
||||
from hachoir_core.tools import alignValue
|
||||
|
||||
class Pixel4bit(Bits):
|
||||
static_size = 4
|
||||
def __init__(self, parent, name):
|
||||
Bits.__init__(self, parent, name, 4)
|
||||
|
||||
class ImageLine(FieldSet):
|
||||
def __init__(self, parent, name, width, pixel_class):
|
||||
FieldSet.__init__(self, parent, name)
|
||||
self._pixel = pixel_class
|
||||
self._width = width
|
||||
self._size = alignValue(self._width * self._pixel.static_size, 32)
|
||||
|
||||
def createFields(self):
|
||||
for x in xrange(self._width):
|
||||
yield self._pixel(self, "pixel[]")
|
||||
size = self.size - self.current_size
|
||||
if size:
|
||||
yield createPaddingField(self, size)
|
||||
|
||||
class ImagePixels(FieldSet):
|
||||
def __init__(self, parent, name, width, height, pixel_class, size=None):
|
||||
FieldSet.__init__(self, parent, name, size=size)
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._pixel = pixel_class
|
||||
|
||||
def createFields(self):
|
||||
for y in xrange(self._height-1, -1, -1):
|
||||
yield ImageLine(self, "line[%u]" % y, self._width, self._pixel)
|
||||
size = (self.size - self.current_size) // 8
|
||||
if size:
|
||||
yield NullBytes(self, "padding", size)
|
||||
|
||||
class CIEXYZ(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "x")
|
||||
yield UInt32(self, "y")
|
||||
yield UInt32(self, "z")
|
||||
|
||||
class BmpHeader(FieldSet):
|
||||
color_space_name = {
|
||||
1: "Business (Saturation)",
|
||||
2: "Graphics (Relative)",
|
||||
4: "Images (Perceptual)",
|
||||
8: "Absolute colormetric (Absolute)",
|
||||
}
|
||||
|
||||
def getFormatVersion(self):
|
||||
if "gamma_blue" in self:
|
||||
return 4
|
||||
if "important_color" in self:
|
||||
return 3
|
||||
return 2
|
||||
|
||||
def createFields(self):
|
||||
# Version 2 (12 bytes)
|
||||
yield UInt32(self, "header_size", "Header size")
|
||||
yield UInt32(self, "width", "Width (pixels)")
|
||||
yield UInt32(self, "height", "Height (pixels)")
|
||||
yield UInt16(self, "nb_plan", "Number of plan (=1)")
|
||||
yield UInt16(self, "bpp", "Bits per pixel") # may be zero for PNG/JPEG picture
|
||||
|
||||
# Version 3 (40 bytes)
|
||||
if self["header_size"].value < 40:
|
||||
return
|
||||
yield Enum(UInt32(self, "compression", "Compression method"), BmpFile.COMPRESSION_NAME)
|
||||
yield UInt32(self, "image_size", "Image size (bytes)")
|
||||
yield UInt32(self, "horizontal_dpi", "Horizontal DPI")
|
||||
yield UInt32(self, "vertical_dpi", "Vertical DPI")
|
||||
yield UInt32(self, "used_colors", "Number of color used")
|
||||
yield UInt32(self, "important_color", "Number of import colors")
|
||||
|
||||
# Version 4 (108 bytes)
|
||||
if self["header_size"].value < 108:
|
||||
return
|
||||
yield textHandler(UInt32(self, "red_mask"), hexadecimal)
|
||||
yield textHandler(UInt32(self, "green_mask"), hexadecimal)
|
||||
yield textHandler(UInt32(self, "blue_mask"), hexadecimal)
|
||||
yield textHandler(UInt32(self, "alpha_mask"), hexadecimal)
|
||||
yield Enum(UInt32(self, "color_space"), self.color_space_name)
|
||||
yield CIEXYZ(self, "red_primary")
|
||||
yield CIEXYZ(self, "green_primary")
|
||||
yield CIEXYZ(self, "blue_primary")
|
||||
yield UInt32(self, "gamma_red")
|
||||
yield UInt32(self, "gamma_green")
|
||||
yield UInt32(self, "gamma_blue")
|
||||
|
||||
def parseImageData(parent, name, size, header):
|
||||
if ("compression" not in header) or (header["compression"].value in (0, 3)):
|
||||
width = header["width"].value
|
||||
height = header["height"].value
|
||||
bpp = header["bpp"].value
|
||||
if bpp == 32:
|
||||
cls = UInt32
|
||||
elif bpp == 24:
|
||||
cls = RGB
|
||||
elif bpp == 8:
|
||||
cls = UInt8
|
||||
elif bpp == 4:
|
||||
cls = Pixel4bit
|
||||
else:
|
||||
cls = None
|
||||
if cls:
|
||||
return ImagePixels(parent, name, width, height, cls, size=size*8)
|
||||
return RawBytes(parent, name, size)
|
||||
|
||||
class BmpFile(Parser):
|
||||
PARSER_TAGS = {
|
||||
"id": "bmp",
|
||||
"category": "image",
|
||||
"file_ext": ("bmp",),
|
||||
"mime": (u"image/x-ms-bmp", u"image/x-bmp"),
|
||||
"min_size": 30*8,
|
||||
# "magic": (("BM", 0),),
|
||||
"magic_regex": ((
|
||||
# "BM", <filesize>, <reserved>, header_size=(12|40|108)
|
||||
"BM.{4}.{8}[\x0C\x28\x6C]\0{3}",
|
||||
0),),
|
||||
"description": "Microsoft bitmap (BMP) picture"
|
||||
}
|
||||
endian = LITTLE_ENDIAN
|
||||
|
||||
COMPRESSION_NAME = {
|
||||
0: u"Uncompressed",
|
||||
1: u"RLE 8-bit",
|
||||
2: u"RLE 4-bit",
|
||||
3: u"Bitfields",
|
||||
4: u"JPEG",
|
||||
5: u"PNG",
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if self.stream.readBytes(0, 2) != 'BM':
|
||||
return "Wrong file signature"
|
||||
if self["header/header_size"].value not in (12, 40, 108):
|
||||
return "Unknown header size (%s)" % self["header_size"].value
|
||||
if self["header/nb_plan"].value != 1:
|
||||
return "Invalid number of planes"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield String(self, "signature", 2, "Header (\"BM\")", charset="ASCII")
|
||||
yield UInt32(self, "file_size", "File size (bytes)")
|
||||
yield PaddingBytes(self, "reserved", 4, "Reserved")
|
||||
yield UInt32(self, "data_start", "Data start position")
|
||||
yield BmpHeader(self, "header")
|
||||
|
||||
# Compute number of color
|
||||
header = self["header"]
|
||||
bpp = header["bpp"].value
|
||||
if 0 < bpp <= 8:
|
||||
if "used_colors" in header and header["used_colors"].value:
|
||||
nb_color = header["used_colors"].value
|
||||
else:
|
||||
nb_color = (1 << bpp)
|
||||
else:
|
||||
nb_color = 0
|
||||
|
||||
# Color palette (if any)
|
||||
if nb_color:
|
||||
yield PaletteRGBA(self, "palette", nb_color)
|
||||
|
||||
# Seek to data start
|
||||
field = self.seekByte(self["data_start"].value)
|
||||
if field:
|
||||
yield field
|
||||
|
||||
# Image pixels
|
||||
size = min(self["file_size"].value-self["data_start"].value, (self.size - self.current_size)//8)
|
||||
yield parseImageData(self, "pixels", size, header)
|
||||
|
||||
def createDescription(self):
|
||||
return u"Microsoft Bitmap version %s" % self["header"].getFormatVersion()
|
||||
|
||||
def createContentSize(self):
|
||||
return self["file_size"].value * 8
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
from hachoir_core.field import FieldSet, UserVector, UInt8
|
||||
|
||||
class RGB(FieldSet):
|
||||
color_name = {
|
||||
( 0, 0, 0): "Black",
|
||||
(255, 0, 0): "Red",
|
||||
( 0, 255, 0): "Green",
|
||||
( 0, 0, 255): "Blue",
|
||||
(255, 255, 255): "White",
|
||||
}
|
||||
static_size = 24
|
||||
|
||||
def createFields(self):
|
||||
yield UInt8(self, "red", "Red")
|
||||
yield UInt8(self, "green", "Green")
|
||||
yield UInt8(self, "blue", "Blue")
|
||||
|
||||
def createDescription(self):
|
||||
rgb = self["red"].value, self["green"].value, self["blue"].value
|
||||
name = self.color_name.get(rgb)
|
||||
if not name:
|
||||
name = "#%02X%02X%02X" % rgb
|
||||
return "RGB color: " + name
|
||||
|
||||
class RGBA(RGB):
|
||||
static_size = 32
|
||||
|
||||
def createFields(self):
|
||||
yield UInt8(self, "red", "Red")
|
||||
yield UInt8(self, "green", "Green")
|
||||
yield UInt8(self, "blue", "Blue")
|
||||
yield UInt8(self, "alpha", "Alpha")
|
||||
|
||||
def createDescription(self):
|
||||
description = RGB.createDescription(self)
|
||||
opacity = self["alpha"].value*100/255
|
||||
return "%s (opacity: %s%%)" % (description, opacity)
|
||||
|
||||
class PaletteRGB(UserVector):
|
||||
item_class = RGB
|
||||
item_name = "color"
|
||||
def createDescription(self):
|
||||
return "Palette of %u RGB colors" % len(self)
|
||||
|
||||
class PaletteRGBA(PaletteRGB):
|
||||
item_class = RGBA
|
||||
def createDescription(self):
|
||||
return "Palette of %u RGBA colors" % len(self)
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
"""
|
||||
EXIF metadata parser (can be found in a JPEG picture for example)
|
||||
|
||||
Author: Victor Stinner
|
||||
"""
|
||||
|
||||
from hachoir_core.field import (FieldSet, ParserError,
|
||||
UInt8, UInt16, UInt32,
|
||||
Int32, Enum, String,
|
||||
Bytes, SubFile,
|
||||
NullBytes, createPaddingField)
|
||||
from hachoir_core.endian import LITTLE_ENDIAN, BIG_ENDIAN, NETWORK_ENDIAN
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal
|
||||
from hachoir_core.tools import createDict
|
||||
|
||||
MAX_COUNT = 1000
|
||||
|
||||
def rationalFactory(class_name, size, field_class):
|
||||
class Rational(FieldSet):
|
||||
static_size = size
|
||||
|
||||
def createFields(self):
|
||||
yield field_class(self, "numerator")
|
||||
yield field_class(self, "denominator")
|
||||
|
||||
def createValue(self):
|
||||
return float(self["numerator"].value) / self["denominator"].value
|
||||
cls = Rational
|
||||
cls.__name__ = class_name
|
||||
return cls
|
||||
|
||||
RationalInt32 = rationalFactory("RationalInt32", 64, Int32)
|
||||
RationalUInt32 = rationalFactory("RationalUInt32", 64, UInt32)
|
||||
|
||||
class BasicIFDEntry(FieldSet):
|
||||
TYPE_BYTE = 0
|
||||
TYPE_UNDEFINED = 7
|
||||
TYPE_RATIONAL = 5
|
||||
TYPE_SIGNED_RATIONAL = 10
|
||||
TYPE_INFO = {
|
||||
1: (UInt8, "BYTE (8 bits)"),
|
||||
2: (String, "ASCII (8 bits)"),
|
||||
3: (UInt16, "SHORT (16 bits)"),
|
||||
4: (UInt32, "LONG (32 bits)"),
|
||||
5: (RationalUInt32, "RATIONAL (2x LONG, 64 bits)"),
|
||||
7: (Bytes, "UNDEFINED (8 bits)"),
|
||||
9: (Int32, "SIGNED LONG (32 bits)"),
|
||||
10: (RationalInt32, "SRATIONAL (2x SIGNED LONGs, 64 bits)"),
|
||||
}
|
||||
ENTRY_FORMAT = createDict(TYPE_INFO, 0)
|
||||
TYPE_NAME = createDict(TYPE_INFO, 1)
|
||||
|
||||
def createFields(self):
|
||||
yield Enum(textHandler(UInt16(self, "tag", "Tag"), hexadecimal), self.TAG_NAME)
|
||||
yield Enum(textHandler(UInt16(self, "type", "Type"), hexadecimal), self.TYPE_NAME)
|
||||
yield UInt32(self, "count", "Count")
|
||||
if self["type"].value not in (self.TYPE_BYTE, self.TYPE_UNDEFINED) \
|
||||
and MAX_COUNT < self["count"].value:
|
||||
raise ParserError("EXIF: Invalid count value (%s)" % self["count"].value)
|
||||
value_size, array_size = self.getSizes()
|
||||
|
||||
# Get offset/value
|
||||
if not value_size:
|
||||
yield NullBytes(self, "padding", 4)
|
||||
elif value_size <= 32:
|
||||
if 1 < array_size:
|
||||
name = "value[]"
|
||||
else:
|
||||
name = "value"
|
||||
kw = {}
|
||||
cls = self.value_cls
|
||||
if cls is String:
|
||||
args = (self, name, value_size/8, "Value")
|
||||
kw["strip"] = " \0"
|
||||
kw["charset"] = "ISO-8859-1"
|
||||
elif cls is Bytes:
|
||||
args = (self, name, value_size/8, "Value")
|
||||
else:
|
||||
args = (self, name, "Value")
|
||||
for index in xrange(array_size):
|
||||
yield cls(*args, **kw)
|
||||
|
||||
size = array_size * value_size
|
||||
if size < 32:
|
||||
yield NullBytes(self, "padding", (32-size)//8)
|
||||
else:
|
||||
yield UInt32(self, "offset", "Value offset")
|
||||
|
||||
def getSizes(self):
|
||||
"""
|
||||
Returns (value_size, array_size): value_size in bits and
|
||||
array_size in number of items.
|
||||
"""
|
||||
# Create format
|
||||
self.value_cls = self.ENTRY_FORMAT.get(self["type"].value, Bytes)
|
||||
|
||||
# Set size
|
||||
count = self["count"].value
|
||||
if self.value_cls in (String, Bytes):
|
||||
return 8 * count, 1
|
||||
else:
|
||||
return self.value_cls.static_size * count, count
|
||||
|
||||
class ExifEntry(BasicIFDEntry):
|
||||
OFFSET_JPEG_SOI = 0x0201
|
||||
EXIF_IFD_POINTER = 0x8769
|
||||
|
||||
TAG_WIDTH = 0xA002
|
||||
TAG_HEIGHT = 0xA003
|
||||
|
||||
TAG_GPS_LATITUDE_REF = 0x0001
|
||||
TAG_GPS_LATITUDE = 0x0002
|
||||
TAG_GPS_LONGITUDE_REF = 0x0003
|
||||
TAG_GPS_LONGITUDE = 0x0004
|
||||
TAG_GPS_ALTITUDE_REF = 0x0005
|
||||
TAG_GPS_ALTITUDE = 0x0006
|
||||
TAG_GPS_TIMESTAMP = 0x0007
|
||||
TAG_GPS_DATESTAMP = 0x001d
|
||||
|
||||
TAG_IMG_TITLE = 0x010e
|
||||
TAG_FILE_TIMESTAMP = 0x0132
|
||||
TAG_SOFTWARE = 0x0131
|
||||
TAG_CAMERA_MODEL = 0x0110
|
||||
TAG_CAMERA_MANUFACTURER = 0x010f
|
||||
TAG_ORIENTATION = 0x0112
|
||||
TAG_EXPOSURE = 0x829A
|
||||
TAG_FOCAL = 0x829D
|
||||
TAG_BRIGHTNESS = 0x9203
|
||||
TAG_APERTURE = 0x9205
|
||||
TAG_USER_COMMENT = 0x9286
|
||||
|
||||
TAG_NAME = {
|
||||
# GPS
|
||||
0x0000: "GPS version ID",
|
||||
0x0001: "GPS latitude ref",
|
||||
0x0002: "GPS latitude",
|
||||
0x0003: "GPS longitude ref",
|
||||
0x0004: "GPS longitude",
|
||||
0x0005: "GPS altitude ref",
|
||||
0x0006: "GPS altitude",
|
||||
0x0007: "GPS timestamp",
|
||||
0x0008: "GPS satellites",
|
||||
0x0009: "GPS status",
|
||||
0x000a: "GPS measure mode",
|
||||
0x000b: "GPS DOP",
|
||||
0x000c: "GPS speed ref",
|
||||
0x000d: "GPS speed",
|
||||
0x000e: "GPS track ref",
|
||||
0x000f: "GPS track",
|
||||
0x0010: "GPS img direction ref",
|
||||
0x0011: "GPS img direction",
|
||||
0x0012: "GPS map datum",
|
||||
0x0013: "GPS dest latitude ref",
|
||||
0x0014: "GPS dest latitude",
|
||||
0x0015: "GPS dest longitude ref",
|
||||
0x0016: "GPS dest longitude",
|
||||
0x0017: "GPS dest bearing ref",
|
||||
0x0018: "GPS dest bearing",
|
||||
0x0019: "GPS dest distance ref",
|
||||
0x001a: "GPS dest distance",
|
||||
0x001b: "GPS processing method",
|
||||
0x001c: "GPS area information",
|
||||
0x001d: "GPS datestamp",
|
||||
0x001e: "GPS differential",
|
||||
|
||||
0x0100: "Image width",
|
||||
0x0101: "Image height",
|
||||
0x0102: "Number of bits per component",
|
||||
0x0103: "Compression scheme",
|
||||
0x0106: "Pixel composition",
|
||||
TAG_ORIENTATION: "Orientation of image",
|
||||
0x0115: "Number of components",
|
||||
0x011C: "Image data arrangement",
|
||||
0x0212: "Subsampling ratio Y to C",
|
||||
0x0213: "Y and C positioning",
|
||||
0x011A: "Image resolution width direction",
|
||||
0x011B: "Image resolution in height direction",
|
||||
0x0128: "Unit of X and Y resolution",
|
||||
|
||||
0x0111: "Image data location",
|
||||
0x0116: "Number of rows per strip",
|
||||
0x0117: "Bytes per compressed strip",
|
||||
0x0201: "Offset to JPEG SOI",
|
||||
0x0202: "Bytes of JPEG data",
|
||||
|
||||
0x012D: "Transfer function",
|
||||
0x013E: "White point chromaticity",
|
||||
0x013F: "Chromaticities of primaries",
|
||||
0x0211: "Color space transformation matrix coefficients",
|
||||
0x0214: "Pair of blank and white reference values",
|
||||
|
||||
TAG_FILE_TIMESTAMP: "File change date and time",
|
||||
TAG_IMG_TITLE: "Image title",
|
||||
TAG_CAMERA_MANUFACTURER: "Camera (Image input equipment) manufacturer",
|
||||
TAG_CAMERA_MODEL: "Camera (Input input equipment) model",
|
||||
TAG_SOFTWARE: "Software",
|
||||
0x013B: "File change date and time",
|
||||
0x8298: "Copyright holder",
|
||||
0x8769: "Exif IFD Pointer",
|
||||
|
||||
TAG_EXPOSURE: "Exposure time",
|
||||
TAG_FOCAL: "F number",
|
||||
0x8822: "Exposure program",
|
||||
0x8824: "Spectral sensitivity",
|
||||
0x8827: "ISO speed rating",
|
||||
0x8828: "Optoelectric conversion factor OECF",
|
||||
0x9201: "Shutter speed",
|
||||
0x9202: "Aperture",
|
||||
TAG_BRIGHTNESS: "Brightness",
|
||||
0x9204: "Exposure bias",
|
||||
TAG_APERTURE: "Maximum lens aperture",
|
||||
0x9206: "Subject distance",
|
||||
0x9207: "Metering mode",
|
||||
0x9208: "Light source",
|
||||
0x9209: "Flash",
|
||||
0x920A: "Lens focal length",
|
||||
0x9214: "Subject area",
|
||||
0xA20B: "Flash energy",
|
||||
0xA20C: "Spatial frequency response",
|
||||
0xA20E: "Focal plane X resolution",
|
||||
0xA20F: "Focal plane Y resolution",
|
||||
0xA210: "Focal plane resolution unit",
|
||||
0xA214: "Subject location",
|
||||
0xA215: "Exposure index",
|
||||
0xA217: "Sensing method",
|
||||
0xA300: "File source",
|
||||
0xA301: "Scene type",
|
||||
0xA302: "CFA pattern",
|
||||
0xA401: "Custom image processing",
|
||||
0xA402: "Exposure mode",
|
||||
0xA403: "White balance",
|
||||
0xA404: "Digital zoom ratio",
|
||||
0xA405: "Focal length in 35 mm film",
|
||||
0xA406: "Scene capture type",
|
||||
0xA407: "Gain control",
|
||||
0xA408: "Contrast",
|
||||
|
||||
0x9000: "Exif version",
|
||||
0xA000: "Supported Flashpix version",
|
||||
0xA001: "Color space information",
|
||||
0x9101: "Meaning of each component",
|
||||
0x9102: "Image compression mode",
|
||||
TAG_WIDTH: "Valid image width",
|
||||
TAG_HEIGHT: "Valid image height",
|
||||
0x927C: "Manufacturer notes",
|
||||
TAG_USER_COMMENT: "User comments",
|
||||
0xA004: "Related audio file",
|
||||
0x9003: "Date and time of original data generation",
|
||||
0x9004: "Date and time of digital data generation",
|
||||
0x9290: "DateTime subseconds",
|
||||
0x9291: "DateTimeOriginal subseconds",
|
||||
0x9292: "DateTimeDigitized subseconds",
|
||||
0xA420: "Unique image ID",
|
||||
0xA005: "Interoperability IFD Pointer"
|
||||
}
|
||||
|
||||
def createDescription(self):
|
||||
return "Entry: %s" % self["tag"].display
|
||||
|
||||
def sortExifEntry(a,b):
|
||||
return int( a["offset"].value - b["offset"].value )
|
||||
|
||||
class ExifIFD(FieldSet):
|
||||
def seek(self, offset):
|
||||
"""
|
||||
Seek to byte address relative to parent address.
|
||||
"""
|
||||
padding = offset - (self.address + self.current_size)/8
|
||||
if 0 < padding:
|
||||
return createPaddingField(self, padding*8)
|
||||
else:
|
||||
return None
|
||||
|
||||
def createFields(self):
|
||||
offset_diff = 6
|
||||
yield UInt16(self, "count", "Number of entries")
|
||||
entries = []
|
||||
next_chunk_offset = None
|
||||
count = self["count"].value
|
||||
if not count:
|
||||
return
|
||||
while count:
|
||||
addr = self.absolute_address + self.current_size
|
||||
next = self.stream.readBits(addr, 32, NETWORK_ENDIAN)
|
||||
if next in (0, 0xF0000000):
|
||||
break
|
||||
entry = ExifEntry(self, "entry[]")
|
||||
yield entry
|
||||
if entry["tag"].value in (ExifEntry.EXIF_IFD_POINTER, ExifEntry.OFFSET_JPEG_SOI):
|
||||
next_chunk_offset = entry["value"].value + offset_diff
|
||||
if 32 < entry.getSizes()[0]:
|
||||
entries.append(entry)
|
||||
count -= 1
|
||||
yield UInt32(self, "next", "Next IFD offset")
|
||||
try:
|
||||
entries.sort( sortExifEntry )
|
||||
except TypeError:
|
||||
raise ParserError("Unable to sort entries!")
|
||||
value_index = 0
|
||||
for entry in entries:
|
||||
padding = self.seek(entry["offset"].value + offset_diff)
|
||||
if padding is not None:
|
||||
yield padding
|
||||
|
||||
value_size, array_size = entry.getSizes()
|
||||
if not array_size:
|
||||
continue
|
||||
cls = entry.value_cls
|
||||
if 1 < array_size:
|
||||
name = "value_%s[]" % entry.name
|
||||
else:
|
||||
name = "value_%s" % entry.name
|
||||
desc = "Value of \"%s\"" % entry["tag"].display
|
||||
if cls is String:
|
||||
for index in xrange(array_size):
|
||||
yield cls(self, name, value_size/8, desc, strip=" \0", charset="ISO-8859-1")
|
||||
elif cls is Bytes:
|
||||
for index in xrange(array_size):
|
||||
yield cls(self, name, value_size/8, desc)
|
||||
else:
|
||||
for index in xrange(array_size):
|
||||
yield cls(self, name, desc)
|
||||
value_index += 1
|
||||
if next_chunk_offset is not None:
|
||||
padding = self.seek(next_chunk_offset)
|
||||
if padding is not None:
|
||||
yield padding
|
||||
|
||||
def createDescription(self):
|
||||
return "Exif IFD (id %s)" % self["id"].value
|
||||
|
||||
class Exif(FieldSet):
|
||||
def createFields(self):
|
||||
# Headers
|
||||
yield String(self, "header", 6, "Header (Exif\\0\\0)", charset="ASCII")
|
||||
if self["header"].value != "Exif\0\0":
|
||||
raise ParserError("Invalid EXIF signature!")
|
||||
yield String(self, "byte_order", 2, "Byte order", charset="ASCII")
|
||||
if self["byte_order"].value not in ("II", "MM"):
|
||||
raise ParserError("Invalid endian!")
|
||||
if self["byte_order"].value == "II":
|
||||
self.endian = LITTLE_ENDIAN
|
||||
else:
|
||||
self.endian = BIG_ENDIAN
|
||||
yield UInt16(self, "version", "TIFF version number")
|
||||
yield UInt32(self, "img_dir_ofs", "Next image directory offset")
|
||||
while not self.eof:
|
||||
addr = self.absolute_address + self.current_size
|
||||
tag = self.stream.readBits(addr, 16, NETWORK_ENDIAN)
|
||||
if tag == 0xFFD8:
|
||||
size = (self._size - self.current_size) // 8
|
||||
yield SubFile(self, "thumbnail", size, "Thumbnail (JPEG file)", mime_type="image/jpeg")
|
||||
break
|
||||
elif tag == 0xFFFF:
|
||||
break
|
||||
yield ExifIFD(self, "ifd[]", "IFD")
|
||||
padding = self.seekBit(self._size)
|
||||
if padding is not None:
|
||||
yield padding
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
"""
|
||||
GIF picture parser.
|
||||
|
||||
Author: Victor Stinner
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet, ParserError,
|
||||
Enum, UInt8, UInt16,
|
||||
Bit, Bits, NullBytes,
|
||||
String, PascalString8, Character,
|
||||
NullBits, RawBytes)
|
||||
from hachoir_parser.image.common import PaletteRGB
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_core.tools import humanDuration
|
||||
from hachoir_core.text_handler import textHandler, displayHandler, hexadecimal
|
||||
|
||||
# Maximum image dimension (in pixel)
|
||||
MAX_WIDTH = 6000
|
||||
MAX_HEIGHT = MAX_WIDTH
|
||||
MAX_FILE_SIZE = 100 * 1024 * 1024
|
||||
|
||||
class Image(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt16(self, "left", "Left")
|
||||
yield UInt16(self, "top", "Top")
|
||||
yield UInt16(self, "width", "Width")
|
||||
yield UInt16(self, "height", "Height")
|
||||
|
||||
yield Bits(self, "bpp", 3, "Bits / pixel minus one")
|
||||
yield NullBits(self, "nul", 2)
|
||||
yield Bit(self, "sorted", "Sorted??")
|
||||
yield Bit(self, "interlaced", "Interlaced?")
|
||||
yield Bit(self, "has_local_map", "Use local color map?")
|
||||
|
||||
if self["has_local_map"].value:
|
||||
nb_color = 1 << (1 + self["bpp"].value)
|
||||
yield PaletteRGB(self, "local_map", nb_color, "Local color map")
|
||||
|
||||
yield UInt8(self, "code_size", "LZW Minimum Code Size")
|
||||
while True:
|
||||
blen = UInt8(self, "block_len[]", "Block Length")
|
||||
yield blen
|
||||
if blen.value != 0:
|
||||
yield RawBytes(self, "data[]", blen.value, "Image Data")
|
||||
else:
|
||||
break
|
||||
|
||||
def createDescription(self):
|
||||
return "Image: %ux%u pixels at (%u,%u)" % (
|
||||
self["width"].value, self["height"].value,
|
||||
self["left"].value, self["top"].value)
|
||||
|
||||
DISPOSAL_METHOD = {
|
||||
0: "No disposal specified",
|
||||
1: "Do not dispose",
|
||||
2: "Restore to background color",
|
||||
3: "Restore to previous",
|
||||
}
|
||||
|
||||
NETSCAPE_CODE = {
|
||||
1: "Loop count",
|
||||
}
|
||||
|
||||
def parseApplicationExtension(parent):
|
||||
yield PascalString8(parent, "app_name", "Application name")
|
||||
yield UInt8(parent, "size")
|
||||
size = parent["size"].value
|
||||
if parent["app_name"].value == "NETSCAPE2.0" and size == 3:
|
||||
yield Enum(UInt8(parent, "netscape_code"), NETSCAPE_CODE)
|
||||
if parent["netscape_code"].value == 1:
|
||||
yield UInt16(parent, "loop_count")
|
||||
else:
|
||||
yield RawBytes(parent, "raw", 2)
|
||||
else:
|
||||
yield RawBytes(parent, "raw", size)
|
||||
yield NullBytes(parent, "terminator", 1, "Terminator (0)")
|
||||
|
||||
def parseGraphicControl(parent):
|
||||
yield UInt8(parent, "size", "Block size (4)")
|
||||
|
||||
yield Bit(parent, "has_transp", "Has transparency")
|
||||
yield Bit(parent, "user_input", "User input")
|
||||
yield Enum(Bits(parent, "disposal_method", 3), DISPOSAL_METHOD)
|
||||
yield NullBits(parent, "reserved[]", 3)
|
||||
|
||||
if parent["size"].value != 4:
|
||||
raise ParserError("Invalid graphic control size")
|
||||
yield displayHandler(UInt16(parent, "delay", "Delay time in millisecond"), humanDuration)
|
||||
yield UInt8(parent, "transp", "Transparent color index")
|
||||
yield NullBytes(parent, "terminator", 1, "Terminator (0)")
|
||||
|
||||
def parseComments(parent):
|
||||
while True:
|
||||
field = PascalString8(parent, "comment[]", strip=" \0\r\n\t")
|
||||
yield field
|
||||
if field.length == 0:
|
||||
break
|
||||
|
||||
def parseTextExtension(parent):
|
||||
yield UInt8(parent, "block_size", "Block Size")
|
||||
yield UInt16(parent, "left", "Text Grid Left")
|
||||
yield UInt16(parent, "top", "Text Grid Top")
|
||||
yield UInt16(parent, "width", "Text Grid Width")
|
||||
yield UInt16(parent, "height", "Text Grid Height")
|
||||
yield UInt8(parent, "cell_width", "Character Cell Width")
|
||||
yield UInt8(parent, "cell_height", "Character Cell Height")
|
||||
yield UInt8(parent, "fg_color", "Foreground Color Index")
|
||||
yield UInt8(parent, "bg_color", "Background Color Index")
|
||||
while True:
|
||||
field = PascalString8(parent, "comment[]", strip=" \0\r\n\t")
|
||||
yield field
|
||||
if field.length == 0:
|
||||
break
|
||||
|
||||
def defaultExtensionParser(parent):
|
||||
while True:
|
||||
size = UInt8(parent, "size[]", "Size (in bytes)")
|
||||
yield size
|
||||
if 0 < size.value:
|
||||
yield RawBytes(parent, "content[]", size.value)
|
||||
else:
|
||||
break
|
||||
|
||||
class Extension(FieldSet):
|
||||
ext_code = {
|
||||
0xf9: ("graphic_ctl[]", parseGraphicControl, "Graphic control"),
|
||||
0xfe: ("comments[]", parseComments, "Comments"),
|
||||
0xff: ("app_ext[]", parseApplicationExtension, "Application extension"),
|
||||
0x01: ("text_ext[]", parseTextExtension, "Plain text extension")
|
||||
}
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
code = self["code"].value
|
||||
if code in self.ext_code:
|
||||
self._name, self.parser, self._description = self.ext_code[code]
|
||||
else:
|
||||
self.parser = defaultExtensionParser
|
||||
|
||||
def createFields(self):
|
||||
yield textHandler(UInt8(self, "code", "Extension code"), hexadecimal)
|
||||
for field in self.parser(self):
|
||||
yield field
|
||||
|
||||
def createDescription(self):
|
||||
return "Extension: function %s" % self["func"].display
|
||||
|
||||
class ScreenDescriptor(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt16(self, "width", "Width")
|
||||
yield UInt16(self, "height", "Height")
|
||||
yield Bits(self, "bpp", 3, "Bits per pixel minus one")
|
||||
yield Bit(self, "reserved", "(reserved)")
|
||||
yield Bits(self, "color_res", 3, "Color resolution minus one")
|
||||
yield Bit(self, "global_map", "Has global map?")
|
||||
yield UInt8(self, "background", "Background color")
|
||||
yield UInt8(self, "pixel_aspect_ratio", "Pixel Aspect Ratio")
|
||||
|
||||
def createDescription(self):
|
||||
colors = 1 << (self["bpp"].value+1)
|
||||
return "Screen descriptor: %ux%u pixels %u colors" \
|
||||
% (self["width"].value, self["height"].value, colors)
|
||||
|
||||
class GifFile(Parser):
|
||||
endian = LITTLE_ENDIAN
|
||||
separator_name = {
|
||||
"!": "Extension",
|
||||
",": "Image",
|
||||
";": "Terminator"
|
||||
}
|
||||
PARSER_TAGS = {
|
||||
"id": "gif",
|
||||
"category": "image",
|
||||
"file_ext": ("gif",),
|
||||
"mime": (u"image/gif",),
|
||||
"min_size": (6 + 7 + 1 + 9)*8, # signature + screen + separator + image
|
||||
"magic": (("GIF87a", 0), ("GIF89a", 0)),
|
||||
"description": "GIF picture"
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if self.stream.readBytes(0, 6) not in ("GIF87a", "GIF89a"):
|
||||
return "Wrong header"
|
||||
if self["screen/width"].value == 0 or self["screen/height"].value == 0:
|
||||
return "Invalid image size"
|
||||
if MAX_WIDTH < self["screen/width"].value:
|
||||
return "Image width too big (%u)" % self["screen/width"].value
|
||||
if MAX_HEIGHT < self["screen/height"].value:
|
||||
return "Image height too big (%u)" % self["screen/height"].value
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
# Header
|
||||
yield String(self, "magic", 3, "File magic code", charset="ASCII")
|
||||
yield String(self, "version", 3, "GIF version", charset="ASCII")
|
||||
|
||||
yield ScreenDescriptor(self, "screen")
|
||||
if self["screen/global_map"].value:
|
||||
bpp = (self["screen/bpp"].value+1)
|
||||
yield PaletteRGB(self, "color_map", 1 << bpp, "Color map")
|
||||
self.color_map = self["color_map"]
|
||||
else:
|
||||
self.color_map = None
|
||||
|
||||
self.images = []
|
||||
while True:
|
||||
code = Enum(Character(self, "separator[]", "Separator code"), self.separator_name)
|
||||
yield code
|
||||
code = code.value
|
||||
if code == "!":
|
||||
yield Extension(self, "extensions[]")
|
||||
elif code == ",":
|
||||
yield Image(self, "image[]")
|
||||
elif code == ";":
|
||||
# GIF Terminator
|
||||
break
|
||||
else:
|
||||
raise ParserError("Wrong GIF image separator: 0x%02X" % ord(code))
|
||||
|
||||
def createContentSize(self):
|
||||
field = self["image[0]"]
|
||||
start = field.absolute_address + field.size
|
||||
end = start + MAX_FILE_SIZE*8
|
||||
pos = self.stream.searchBytes("\0;", start, end)
|
||||
if pos:
|
||||
return pos + 16
|
||||
return None
|
||||
@@ -0,0 +1,139 @@
|
||||
"""
|
||||
Microsoft Windows icon and cursor file format parser.
|
||||
|
||||
Author: Victor Stinner
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet, ParserError,
|
||||
UInt8, UInt16, UInt32, Enum, RawBytes)
|
||||
from hachoir_parser.image.common import PaletteRGBA
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_parser.common.win32 import BitmapInfoHeader
|
||||
|
||||
class IconHeader(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt8(self, "width", "Width")
|
||||
yield UInt8(self, "height", "Height")
|
||||
yield UInt8(self, "nb_color", "Number of colors")
|
||||
yield UInt8(self, "reserved", "(reserved)")
|
||||
yield UInt16(self, "planes", "Color planes (=1)")
|
||||
yield UInt16(self, "bpp", "Bits per pixel")
|
||||
yield UInt32(self, "size", "Content size in bytes")
|
||||
yield UInt32(self, "offset", "Data offset")
|
||||
|
||||
def createDescription(self):
|
||||
return "Icon: %ux%u pixels, %u bits/pixel" % \
|
||||
(self["width"].value, self["height"].value, self["bpp"].value)
|
||||
|
||||
def isValid(self):
|
||||
if self["nb_color"].value == 0:
|
||||
if self["bpp"].value in (8, 24, 32) and self["planes"].value == 1:
|
||||
return True
|
||||
if self["planes"].value == 4 and self["bpp"].value == 0:
|
||||
return True
|
||||
elif self["nb_color"].value == 16:
|
||||
if self["bpp"].value in (4, 16) and self["planes"].value == 1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
if self["bpp"].value == 0 and self["planes"].value == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
class IconData(FieldSet):
|
||||
def __init__(self, parent, name, header):
|
||||
FieldSet.__init__(self, parent, name, "Icon data")
|
||||
self.header = header
|
||||
|
||||
def createFields(self):
|
||||
yield BitmapInfoHeader(self, "header")
|
||||
|
||||
# Read palette if needed
|
||||
nb_color = self.header["nb_color"].value
|
||||
if self.header["bpp"].value == 8:
|
||||
nb_color = 256
|
||||
if nb_color != 0:
|
||||
yield PaletteRGBA(self, "palette", nb_color)
|
||||
|
||||
# Read pixels
|
||||
size = self.header["size"].value - self.current_size/8
|
||||
yield RawBytes(self, "pixels", size, "Image pixels")
|
||||
|
||||
class IcoFile(Parser):
|
||||
endian = LITTLE_ENDIAN
|
||||
PARSER_TAGS = {
|
||||
"id": "ico",
|
||||
"category": "image",
|
||||
"file_ext": ("ico", "cur"),
|
||||
"mime": (u"image/x-ico",),
|
||||
"min_size": (22 + 40)*8,
|
||||
# "magic": (
|
||||
# ("\0\0\1\0", 0), # Icon
|
||||
# ("\0\0\2\0", 0), # Cursor
|
||||
# ),
|
||||
"magic_regex": ((
|
||||
# signature=0, type=(1|2), count in 1..20,
|
||||
"\0\0[\1\2]\0[\x01-\x14]."
|
||||
# size=(16x16|32x32|48x48|64x64),
|
||||
"(\x10\x10|\x20\x20|\x30\x30|\x40\x40)"
|
||||
# nb_color=0 or 16; nb_plane=(0|1|4), bpp=(0|8|24|32)
|
||||
"[\x00\x10]\0[\0\1\4][\0\x08\x18\x20]\0",
|
||||
0),),
|
||||
"description": "Microsoft Windows icon or cursor",
|
||||
}
|
||||
TYPE_NAME = {
|
||||
1: "icon",
|
||||
2: "cursor"
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
# Check signature and type
|
||||
if self["signature"].value != 0:
|
||||
return "Wrong file signature"
|
||||
if self["type"].value not in self.TYPE_NAME:
|
||||
return "Unknown picture type"
|
||||
|
||||
# Check all icon headers
|
||||
index = -1
|
||||
for field in self:
|
||||
if field.name.startswith("icon_header"):
|
||||
index += 1
|
||||
if not field.isValid():
|
||||
return "Invalid header #%u" % index
|
||||
elif 0 <= index:
|
||||
break
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield UInt16(self, "signature", "Signature (0x0000)")
|
||||
yield Enum(UInt16(self, "type", "Resource type"), self.TYPE_NAME)
|
||||
yield UInt16(self, "nb_items", "Number of items")
|
||||
items = []
|
||||
for index in xrange(self["nb_items"].value):
|
||||
item = IconHeader(self, "icon_header[]")
|
||||
yield item
|
||||
items.append(item)
|
||||
for header in items:
|
||||
if header["offset"].value*8 != self.current_size:
|
||||
raise ParserError("Icon: Problem with icon data offset.")
|
||||
yield IconData(self, "icon_data[]", header)
|
||||
|
||||
def createDescription(self):
|
||||
desc = "Microsoft Windows %s" % self["type"].display
|
||||
size = []
|
||||
for header in self.array("icon_header"):
|
||||
size.append("%ux%ux%u" % (header["width"].value,
|
||||
header["height"].value, header["bpp"].value))
|
||||
if size:
|
||||
return "%s: %s" % (desc, ", ".join(size))
|
||||
else:
|
||||
return desc
|
||||
|
||||
def createContentSize(self):
|
||||
count = self["nb_items"].value
|
||||
if not count:
|
||||
return None
|
||||
field = self["icon_data[%u]" % (count-1)]
|
||||
return field.absolute_address + field.size
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
IPTC metadata parser (can be found in a JPEG picture for example)
|
||||
|
||||
Sources:
|
||||
- Image-MetaData Perl module:
|
||||
http://www.annocpan.org/~BETTELLI/Image-MetaData-JPEG-0.15/...
|
||||
...lib/Image/MetaData/JPEG/TagLists.pod
|
||||
- IPTC tag name and description:
|
||||
http://peccatte.karefil.com/software/IPTCTableau.pdf
|
||||
|
||||
Author: Victor Stinner
|
||||
"""
|
||||
|
||||
from hachoir_core.field import (FieldSet, ParserError,
|
||||
UInt8, UInt16, String, RawBytes, NullBytes)
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal
|
||||
|
||||
def IPTC_String(parent, name, desc=None):
|
||||
# Charset may be utf-8, ISO-8859-1, or ...
|
||||
return String(parent, name, parent["size"].value, desc,
|
||||
strip=" ")
|
||||
|
||||
dataset1 = {
|
||||
}
|
||||
dataset2 = {
|
||||
0: ("record_version", "Record version (2 for JPEG)", UInt16),
|
||||
5: ("obj_name", "Object name", None),
|
||||
7: ("edit_stat", "Edit status", None),
|
||||
10: ("urgency", "Urgency", UInt8),
|
||||
15: ("category[]", "Category", None),
|
||||
22: ("fixture", "Fixture identifier", IPTC_String),
|
||||
25: ("keyword[]", "Keywords", IPTC_String),
|
||||
30: ("release_date", "Release date", IPTC_String),
|
||||
35: ("release_time", "Release time", IPTC_String),
|
||||
40: ("instruction", "Special instructions", IPTC_String),
|
||||
55: ("date_created", "Date created", IPTC_String),
|
||||
60: ("time_created", "Time created (ISO 8601)", IPTC_String),
|
||||
65: ("originating_prog", "Originating program", IPTC_String),
|
||||
70: ("prog_ver", "Program version", IPTC_String),
|
||||
80: ("author", "By-line (Author)", IPTC_String),
|
||||
85: ("author_job", "By-line (Author precision)", IPTC_String),
|
||||
90: ("city", "City", IPTC_String),
|
||||
95: ("state", "Province / State", IPTC_String),
|
||||
100: ("country_code", "Country / Primary location code", IPTC_String),
|
||||
101: ("country_name", "Country / Primary location name", IPTC_String),
|
||||
103: ("trans_ref", "Original transmission reference", IPTC_String),
|
||||
105: ("headline", "Headline", IPTC_String),
|
||||
110: ("credit", "Credit", IPTC_String),
|
||||
115: ("source", "Source", IPTC_String),
|
||||
116: ("copyright", "Copyright notice", IPTC_String),
|
||||
120: ("caption", "Caption/Abstract", IPTC_String),
|
||||
122: ("writer", "Writer/editor", IPTC_String),
|
||||
231: ("history[]", "Document history (timestamp)", IPTC_String)
|
||||
}
|
||||
datasets = {1: dataset1, 2: dataset2}
|
||||
|
||||
class IPTC_Size(FieldSet):
|
||||
def __init__(self, *args, **kw):
|
||||
FieldSet.__init__(self, *args, **kw)
|
||||
value = 0
|
||||
for field in self:
|
||||
value <<= 15
|
||||
value += (field.value & 0x7fff)
|
||||
self.createValue = lambda: value
|
||||
|
||||
def createFields(self):
|
||||
while True:
|
||||
field = UInt16(self, "value[]")
|
||||
yield field
|
||||
if field.value < 0x8000:
|
||||
break
|
||||
|
||||
class IPTC_Chunk(FieldSet):
|
||||
def __init__(self, *args, **kw):
|
||||
FieldSet.__init__(self, *args, **kw)
|
||||
number = self["dataset_nb"].value
|
||||
self.dataset_info = None
|
||||
if number in datasets:
|
||||
tag = self["tag"].value
|
||||
if tag in datasets[number]:
|
||||
self.dataset_info = datasets[number][tag]
|
||||
self._name = self.dataset_info[0]
|
||||
self._description = self.dataset_info[1]
|
||||
size_chunk = self["size"]
|
||||
self._size = 3*8 + size_chunk.size + size_chunk.value*8
|
||||
|
||||
def createFields(self):
|
||||
yield textHandler(UInt8(self, "signature", "IPTC signature (0x1c)"), hexadecimal)
|
||||
if self["signature"].value != 0x1C:
|
||||
raise ParserError("Wrong IPTC signature")
|
||||
yield textHandler(UInt8(self, "dataset_nb", "Dataset number"), hexadecimal)
|
||||
yield UInt8(self, "tag", "Tag")
|
||||
yield IPTC_Size(self, "size", "Content size")
|
||||
|
||||
size = self["size"].value
|
||||
if 0 < size:
|
||||
if self.dataset_info:
|
||||
cls = self.dataset_info[2]
|
||||
else:
|
||||
cls = None
|
||||
if cls:
|
||||
yield cls(self, "content")
|
||||
else:
|
||||
yield RawBytes(self, "content", size)
|
||||
|
||||
class IPTC(FieldSet):
|
||||
def createFields(self):
|
||||
while 5 <= (self._size - self.current_size)/8:
|
||||
yield IPTC_Chunk(self, "chunk[]")
|
||||
size = (self._size - self.current_size) / 8
|
||||
if 0 < size:
|
||||
yield NullBytes(self, "padding", size)
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
"""
|
||||
JPEG picture parser.
|
||||
|
||||
Information:
|
||||
|
||||
- APP14 documents
|
||||
http://partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf
|
||||
http://java.sun.com/j2se/1.5.0/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#color
|
||||
- APP12:
|
||||
http://search.cpan.org/~exiftool/Image-ExifTool/lib/Image/ExifTool/TagNames.pod
|
||||
|
||||
Author: Victor Stinner
|
||||
"""
|
||||
|
||||
from hachoir_core.error import HachoirError
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet, ParserError,
|
||||
UInt8, UInt16, Enum,
|
||||
Bit, Bits, NullBits, NullBytes,
|
||||
String, RawBytes)
|
||||
from hachoir_parser.image.common import PaletteRGB
|
||||
from hachoir_core.endian import BIG_ENDIAN
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal
|
||||
from hachoir_parser.image.exif import Exif
|
||||
from hachoir_parser.image.photoshop_metadata import PhotoshopMetadata
|
||||
|
||||
MAX_FILESIZE = 100 * 1024 * 1024
|
||||
|
||||
# The four tables (hash/sum for color/grayscale JPEG) comes
|
||||
# from ImageMagick project
|
||||
QUALITY_HASH_COLOR = (
|
||||
1020, 1015, 932, 848, 780, 735, 702, 679, 660, 645,
|
||||
632, 623, 613, 607, 600, 594, 589, 585, 581, 571,
|
||||
555, 542, 529, 514, 494, 474, 457, 439, 424, 410,
|
||||
397, 386, 373, 364, 351, 341, 334, 324, 317, 309,
|
||||
299, 294, 287, 279, 274, 267, 262, 257, 251, 247,
|
||||
243, 237, 232, 227, 222, 217, 213, 207, 202, 198,
|
||||
192, 188, 183, 177, 173, 168, 163, 157, 153, 148,
|
||||
143, 139, 132, 128, 125, 119, 115, 108, 104, 99,
|
||||
94, 90, 84, 79, 74, 70, 64, 59, 55, 49,
|
||||
45, 40, 34, 30, 25, 20, 15, 11, 6, 4,
|
||||
0)
|
||||
|
||||
QUALITY_SUM_COLOR = (
|
||||
32640,32635,32266,31495,30665,29804,29146,28599,28104,27670,
|
||||
27225,26725,26210,25716,25240,24789,24373,23946,23572,22846,
|
||||
21801,20842,19949,19121,18386,17651,16998,16349,15800,15247,
|
||||
14783,14321,13859,13535,13081,12702,12423,12056,11779,11513,
|
||||
11135,10955,10676,10392,10208, 9928, 9747, 9564, 9369, 9193,
|
||||
9017, 8822, 8639, 8458, 8270, 8084, 7896, 7710, 7527, 7347,
|
||||
7156, 6977, 6788, 6607, 6422, 6236, 6054, 5867, 5684, 5495,
|
||||
5305, 5128, 4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698,
|
||||
3509, 3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846,
|
||||
1666, 1483, 1297, 1109, 927, 735, 554, 375, 201, 128,
|
||||
0)
|
||||
|
||||
QUALITY_HASH_GRAY = (
|
||||
510, 505, 422, 380, 355, 338, 326, 318, 311, 305,
|
||||
300, 297, 293, 291, 288, 286, 284, 283, 281, 280,
|
||||
279, 278, 277, 273, 262, 251, 243, 233, 225, 218,
|
||||
211, 205, 198, 193, 186, 181, 177, 172, 168, 164,
|
||||
158, 156, 152, 148, 145, 142, 139, 136, 133, 131,
|
||||
129, 126, 123, 120, 118, 115, 113, 110, 107, 105,
|
||||
102, 100, 97, 94, 92, 89, 87, 83, 81, 79,
|
||||
76, 74, 70, 68, 66, 63, 61, 57, 55, 52,
|
||||
50, 48, 44, 42, 39, 37, 34, 31, 29, 26,
|
||||
24, 21, 18, 16, 13, 11, 8, 6, 3, 2,
|
||||
0)
|
||||
|
||||
QUALITY_SUM_GRAY = (
|
||||
16320,16315,15946,15277,14655,14073,13623,13230,12859,12560,
|
||||
12240,11861,11456,11081,10714,10360,10027, 9679, 9368, 9056,
|
||||
8680, 8331, 7995, 7668, 7376, 7084, 6823, 6562, 6345, 6125,
|
||||
5939, 5756, 5571, 5421, 5240, 5086, 4976, 4829, 4719, 4616,
|
||||
4463, 4393, 4280, 4166, 4092, 3980, 3909, 3835, 3755, 3688,
|
||||
3621, 3541, 3467, 3396, 3323, 3247, 3170, 3096, 3021, 2952,
|
||||
2874, 2804, 2727, 2657, 2583, 2509, 2437, 2362, 2290, 2211,
|
||||
2136, 2068, 1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477,
|
||||
1398, 1326, 1251, 1179, 1109, 1031, 961, 884, 814, 736,
|
||||
667, 592, 518, 441, 369, 292, 221, 151, 86, 64,
|
||||
0)
|
||||
|
||||
JPEG_NATURAL_ORDER = (
|
||||
0, 1, 8, 16, 9, 2, 3, 10,
|
||||
17, 24, 32, 25, 18, 11, 4, 5,
|
||||
12, 19, 26, 33, 40, 48, 41, 34,
|
||||
27, 20, 13, 6, 7, 14, 21, 28,
|
||||
35, 42, 49, 56, 57, 50, 43, 36,
|
||||
29, 22, 15, 23, 30, 37, 44, 51,
|
||||
58, 59, 52, 45, 38, 31, 39, 46,
|
||||
53, 60, 61, 54, 47, 55, 62, 63)
|
||||
|
||||
class JpegChunkApp0(FieldSet):
|
||||
UNIT_NAME = {
|
||||
0: "pixels",
|
||||
1: "dots per inch",
|
||||
2: "dots per cm",
|
||||
}
|
||||
|
||||
def createFields(self):
|
||||
yield String(self, "jfif", 5, "JFIF string", charset="ASCII")
|
||||
if self["jfif"].value != "JFIF\0":
|
||||
raise ParserError(
|
||||
"Stream doesn't look like JPEG chunk (wrong JFIF signature)")
|
||||
yield UInt8(self, "ver_maj", "Major version")
|
||||
yield UInt8(self, "ver_min", "Minor version")
|
||||
yield Enum(UInt8(self, "units", "Units"), self.UNIT_NAME)
|
||||
if self["units"].value == 0:
|
||||
yield UInt16(self, "aspect_x", "Aspect ratio (X)")
|
||||
yield UInt16(self, "aspect_y", "Aspect ratio (Y)")
|
||||
else:
|
||||
yield UInt16(self, "x_density", "X density")
|
||||
yield UInt16(self, "y_density", "Y density")
|
||||
yield UInt8(self, "thumb_w", "Thumbnail width")
|
||||
yield UInt8(self, "thumb_h", "Thumbnail height")
|
||||
thumb_size = self["thumb_w"].value * self["thumb_h"].value
|
||||
if thumb_size != 0:
|
||||
yield PaletteRGB(self, "thumb_palette", 256)
|
||||
yield RawBytes(self, "thumb_data", thumb_size, "Thumbnail data")
|
||||
|
||||
class Ducky(FieldSet):
|
||||
BLOCK_TYPE = {
|
||||
0: "end",
|
||||
1: "Quality",
|
||||
2: "Comment",
|
||||
3: "Copyright",
|
||||
}
|
||||
def createFields(self):
|
||||
yield Enum(UInt16(self, "type"), self.BLOCK_TYPE)
|
||||
if self["type"].value == 0:
|
||||
return
|
||||
yield UInt16(self, "size")
|
||||
size = self["size"].value
|
||||
if size:
|
||||
yield RawBytes(self, "data", size)
|
||||
|
||||
class APP12(FieldSet):
|
||||
"""
|
||||
The JPEG APP12 "Picture Info" segment was used by some older cameras, and
|
||||
contains ASCII-based meta information.
|
||||
"""
|
||||
def createFields(self):
|
||||
yield String(self, "ducky", 5, '"Ducky" string', charset="ASCII")
|
||||
while not self.eof:
|
||||
yield Ducky(self, "item[]")
|
||||
|
||||
class StartOfFrame(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt8(self, "precision")
|
||||
|
||||
yield UInt16(self, "height")
|
||||
yield UInt16(self, "width")
|
||||
yield UInt8(self, "nr_components")
|
||||
|
||||
for index in range(self["nr_components"].value):
|
||||
yield UInt8(self, "component_id[]")
|
||||
yield UInt8(self, "high[]")
|
||||
yield UInt8(self, "low[]")
|
||||
|
||||
class Comment(FieldSet):
|
||||
def createFields(self):
|
||||
yield String(self, "comment", self.size//8, strip="\0")
|
||||
|
||||
class AdobeChunk(FieldSet):
|
||||
COLORSPACE_TRANSFORMATION = {
|
||||
1: "YCbCr (converted from RGB)",
|
||||
2: "YCCK (converted from CMYK)",
|
||||
}
|
||||
def createFields(self):
|
||||
if self.stream.readBytes(self.absolute_address, 5) != "Adobe":
|
||||
yield RawBytes(self, "raw", self.size//8, "Raw data")
|
||||
return
|
||||
yield String(self, "adobe", 5, "\"Adobe\" string", charset="ASCII")
|
||||
yield UInt16(self, "version", "DCT encoder version")
|
||||
yield Enum(Bit(self, "flag00"),
|
||||
{False: "Chop down or subsampling", True: "Blend"})
|
||||
yield NullBits(self, "flags0_reserved", 15)
|
||||
yield NullBytes(self, "flags1", 2)
|
||||
yield Enum(UInt8(self, "color_transform", "Colorspace transformation code"), self.COLORSPACE_TRANSFORMATION)
|
||||
|
||||
class StartOfScan(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt8(self, "nr_components")
|
||||
|
||||
for index in range(self["nr_components"].value):
|
||||
comp_id = UInt8(self, "component_id[]")
|
||||
yield comp_id
|
||||
if not(1 <= comp_id.value <= self["nr_components"].value):
|
||||
raise ParserError("JPEG error: Invalid component-id")
|
||||
yield UInt8(self, "value[]")
|
||||
yield RawBytes(self, "raw", 3) # TODO: What's this???
|
||||
|
||||
class RestartInterval(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt16(self, "interval", "Restart interval")
|
||||
|
||||
class QuantizationTable(FieldSet):
|
||||
def createFields(self):
|
||||
# Code based on function get_dqt() (jdmarker.c from libjpeg62)
|
||||
yield Bits(self, "is_16bit", 4)
|
||||
yield Bits(self, "index", 4)
|
||||
if self["index"].value >= 4:
|
||||
raise ParserError("Invalid quantification index (%s)" % self["index"].value)
|
||||
if self["is_16bit"].value:
|
||||
coeff_type = UInt16
|
||||
else:
|
||||
coeff_type = UInt8
|
||||
for index in xrange(64):
|
||||
natural = JPEG_NATURAL_ORDER[index]
|
||||
yield coeff_type(self, "coeff[%u]" % natural)
|
||||
|
||||
def createDescription(self):
|
||||
return "Quantification table #%u" % self["index"].value
|
||||
|
||||
class DefineQuantizationTable(FieldSet):
|
||||
def createFields(self):
|
||||
while self.current_size < self.size:
|
||||
yield QuantizationTable(self, "qt[]")
|
||||
|
||||
class JpegChunk(FieldSet):
|
||||
TAG_SOI = 0xD8
|
||||
TAG_EOI = 0xD9
|
||||
TAG_SOS = 0xDA
|
||||
TAG_DQT = 0xDB
|
||||
TAG_DRI = 0xDD
|
||||
TAG_INFO = {
|
||||
0xC4: ("huffman[]", "Define Huffman Table (DHT)", None),
|
||||
0xD8: ("start_image", "Start of image (SOI)", None),
|
||||
0xD9: ("end_image", "End of image (EOI)", None),
|
||||
0xDA: ("start_scan", "Start Of Scan (SOS)", StartOfScan),
|
||||
0xDB: ("quantization[]", "Define Quantization Table (DQT)", DefineQuantizationTable),
|
||||
0xDC: ("nb_line", "Define number of Lines (DNL)", None),
|
||||
0xDD: ("restart_interval", "Define Restart Interval (DRI)", RestartInterval),
|
||||
0xE0: ("app0", "APP0", JpegChunkApp0),
|
||||
0xE1: ("exif", "Exif metadata", Exif),
|
||||
0xE2: ("icc", "ICC profile", None),
|
||||
0xEC: ("app12", "APP12", APP12),
|
||||
0xED: ("photoshop", "Photoshop", PhotoshopMetadata),
|
||||
0xEE: ("adobe", "Image encoding information for DCT filters (Adobe)", AdobeChunk),
|
||||
0xFE: ("comment[]", "Comment", Comment),
|
||||
}
|
||||
START_OF_FRAME = {
|
||||
0xC0: u"Baseline",
|
||||
0xC1: u"Extended sequential",
|
||||
0xC2: u"Progressive",
|
||||
0xC3: u"Lossless",
|
||||
0xC5: u"Differential sequential",
|
||||
0xC6: u"Differential progressive",
|
||||
0xC7: u"Differential lossless",
|
||||
0xC9: u"Extended sequential, arithmetic coding",
|
||||
0xCA: u"Progressive, arithmetic coding",
|
||||
0xCB: u"Lossless, arithmetic coding",
|
||||
0xCD: u"Differential sequential, arithmetic coding",
|
||||
0xCE: u"Differential progressive, arithmetic coding",
|
||||
0xCF: u"Differential lossless, arithmetic coding",
|
||||
}
|
||||
for key, text in START_OF_FRAME.iteritems():
|
||||
TAG_INFO[key] = ("start_frame", "Start of frame (%s)" % text.lower(), StartOfFrame)
|
||||
|
||||
def __init__(self, parent, name, description=None):
|
||||
FieldSet.__init__(self, parent, name, description)
|
||||
tag = self["type"].value
|
||||
if tag == 0xE1:
|
||||
# Hack for Adobe extension: XAP metadata (as XML)
|
||||
bytes = self.stream.readBytes(self.absolute_address + 32, 6)
|
||||
if bytes == "Exif\0\0":
|
||||
self._name = "exif"
|
||||
self._description = "EXIF"
|
||||
self._parser = Exif
|
||||
else:
|
||||
self._parser = None
|
||||
elif tag in self.TAG_INFO:
|
||||
self._name, self._description, self._parser = self.TAG_INFO[tag]
|
||||
else:
|
||||
self._parser = None
|
||||
|
||||
def createFields(self):
|
||||
yield textHandler(UInt8(self, "header", "Header"), hexadecimal)
|
||||
if self["header"].value != 0xFF:
|
||||
raise ParserError("JPEG: Invalid chunk header!")
|
||||
yield textHandler(UInt8(self, "type", "Type"), hexadecimal)
|
||||
tag = self["type"].value
|
||||
if tag in (self.TAG_SOI, self.TAG_EOI):
|
||||
return
|
||||
yield UInt16(self, "size", "Size")
|
||||
size = (self["size"].value - 2)
|
||||
if 0 < size:
|
||||
if self._parser:
|
||||
yield self._parser(self, "content", "Chunk content", size=size*8)
|
||||
else:
|
||||
yield RawBytes(self, "data", size, "Data")
|
||||
|
||||
def createDescription(self):
|
||||
return "Chunk: %s" % self["type"].display
|
||||
|
||||
class JpegFile(Parser):
|
||||
endian = BIG_ENDIAN
|
||||
PARSER_TAGS = {
|
||||
"id": "jpeg",
|
||||
"category": "image",
|
||||
"file_ext": ("jpg", "jpeg"),
|
||||
"mime": (u"image/jpeg",),
|
||||
"magic": (
|
||||
("\xFF\xD8\xFF\xE0", 0), # (Start Of Image, APP0)
|
||||
("\xFF\xD8\xFF\xE1", 0), # (Start Of Image, EXIF)
|
||||
("\xFF\xD8\xFF\xEE", 0), # (Start Of Image, Adobe)
|
||||
),
|
||||
"min_size": 22*8,
|
||||
"description": "JPEG picture",
|
||||
"subfile": "skip",
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if self.stream.readBytes(0, 2) != "\xFF\xD8":
|
||||
return "Invalid file signature"
|
||||
try:
|
||||
for index, field in enumerate(self):
|
||||
chunk_type = field["type"].value
|
||||
if chunk_type not in JpegChunk.TAG_INFO:
|
||||
return "Unknown chunk type: 0x%02X (chunk #%s)" % (chunk_type, index)
|
||||
if index == 2:
|
||||
# Only check 3 fields
|
||||
break
|
||||
except HachoirError:
|
||||
return "Unable to parse at least three chunks"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
while not self.eof:
|
||||
chunk = JpegChunk(self, "chunk[]")
|
||||
yield chunk
|
||||
if chunk["type"].value == JpegChunk.TAG_SOS:
|
||||
# TODO: Read JPEG image data...
|
||||
break
|
||||
|
||||
# TODO: is it possible to handle piped input?
|
||||
if self._size is None:
|
||||
raise NotImplementedError
|
||||
|
||||
has_end = False
|
||||
size = (self._size - self.current_size) // 8
|
||||
if size:
|
||||
if 2 < size \
|
||||
and self.stream.readBytes(self._size - 16, 2) == "\xff\xd9":
|
||||
has_end = True
|
||||
size -= 2
|
||||
yield RawBytes(self, "data", size, "JPEG data")
|
||||
if has_end:
|
||||
yield JpegChunk(self, "chunk[]")
|
||||
|
||||
def createDescription(self):
|
||||
desc = "JPEG picture"
|
||||
if "sof/content" in self:
|
||||
header = self["sof/content"]
|
||||
desc += ": %ux%u pixels" % (header["width"].value, header["height"].value)
|
||||
return desc
|
||||
|
||||
def createContentSize(self):
|
||||
if "end" in self:
|
||||
return self["end"].absolute_address + self["end"].size
|
||||
if "data" not in self:
|
||||
return None
|
||||
start = self["data"].absolute_address
|
||||
end = self.stream.searchBytes("\xff\xd9", start, MAX_FILESIZE*8)
|
||||
if end is not None:
|
||||
return end + 16
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
PCX picture filter.
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (
|
||||
UInt8, UInt16,
|
||||
PaddingBytes, RawBytes,
|
||||
Enum)
|
||||
from hachoir_parser.image.common import PaletteRGB
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
|
||||
class PcxFile(Parser):
|
||||
endian = LITTLE_ENDIAN
|
||||
PARSER_TAGS = {
|
||||
"id": "pcx",
|
||||
"category": "image",
|
||||
"file_ext": ("pcx",),
|
||||
"mime": (u"image/x-pcx",),
|
||||
"min_size": 128*8,
|
||||
"description": "PC Paintbrush (PCX) picture"
|
||||
}
|
||||
compression_name = { 1: "Run-length encoding (RLE)" }
|
||||
version_name = {
|
||||
0: u"Version 2.5 of PC Paintbrush",
|
||||
2: u"Version 2.8 with palette information",
|
||||
3: u"Version 2.8 without palette information",
|
||||
4: u"PC Paintbrush for Windows",
|
||||
5: u"Version 3.0 (or greater) of PC Paintbrush"
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if self["id"].value != 10:
|
||||
return "Wrong signature"
|
||||
if self["version"].value not in self.version_name:
|
||||
return "Unknown format version"
|
||||
if self["bpp"].value not in (1, 2, 4, 8, 24, 32):
|
||||
return "Unknown bits/pixel"
|
||||
if self["reserved[0]"].value != "\0":
|
||||
return "Invalid reserved value"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield UInt8(self, "id", "PCX identifier (10)")
|
||||
yield Enum(UInt8(self, "version", "PCX version"), self.version_name)
|
||||
yield Enum(UInt8(self, "compression", "Compression method"), self.compression_name)
|
||||
yield UInt8(self, "bpp", "Bits / pixel")
|
||||
yield UInt16(self, "xmin", "Minimum X")
|
||||
yield UInt16(self, "ymin", "Minimum Y")
|
||||
yield UInt16(self, "xmax", "Width minus one") # value + 1
|
||||
yield UInt16(self, "ymax", "Height minus one") # value + 1
|
||||
yield UInt16(self, "horiz_dpi", "Horizontal DPI")
|
||||
yield UInt16(self, "vert_dpi", "Vertical DPI")
|
||||
yield PaletteRGB(self, "palette_4bits", 16, "Palette (4 bits)")
|
||||
yield PaddingBytes(self, "reserved[]", 1)
|
||||
yield UInt8(self, "nb_color_plan", "Number of color plans")
|
||||
yield UInt16(self, "bytes_per_line", "Bytes per line")
|
||||
yield UInt16(self, "color_mode", "Color mode")
|
||||
yield PaddingBytes(self, "reserved[]", 58)
|
||||
|
||||
if self._size is None: # TODO: is it possible to handle piped input?
|
||||
raise NotImplementedError
|
||||
|
||||
nb_colors = 256
|
||||
size = (self._size - self.current_size)/8
|
||||
has_palette = self["bpp"].value == 8
|
||||
if has_palette:
|
||||
size -= nb_colors*3
|
||||
yield RawBytes(self, "image_data", size, "Image data")
|
||||
|
||||
if has_palette:
|
||||
yield PaletteRGB(self, "palette_8bits", nb_colors, "Palette (8 bit)")
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
""" Photoshop metadata parser.
|
||||
|
||||
References:
|
||||
- http://www.scribd.com/doc/32900475/Photoshop-File-Formats
|
||||
"""
|
||||
|
||||
from hachoir_core.field import (FieldSet, ParserError,
|
||||
UInt8, UInt16, UInt32, Float32, Enum,
|
||||
SubFile, String, CString, PascalString8,
|
||||
NullBytes, RawBytes)
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal
|
||||
from hachoir_core.tools import alignValue, createDict
|
||||
from hachoir_parser.image.iptc import IPTC
|
||||
from hachoir_parser.common.win32 import PascalStringWin32
|
||||
|
||||
BOOL = {0: False, 1: True}
|
||||
|
||||
class Version(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "version")
|
||||
yield UInt8(self, "has_realm")
|
||||
yield PascalStringWin32(self, "writer_name", charset="UTF-16-BE")
|
||||
yield PascalStringWin32(self, "reader_name", charset="UTF-16-BE")
|
||||
yield UInt32(self, "file_version")
|
||||
size = (self.size - self.current_size) // 8
|
||||
if size:
|
||||
yield NullBytes(self, "padding", size)
|
||||
|
||||
class FixedFloat32(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt16(self, "int_part")
|
||||
yield UInt16(self, "float_part")
|
||||
|
||||
def createValue(self):
|
||||
return self["int_part"].value + float(self["float_part"].value) / (1<<16)
|
||||
|
||||
class ResolutionInfo(FieldSet):
|
||||
def createFields(self):
|
||||
yield FixedFloat32(self, "horiz_res")
|
||||
yield Enum(UInt16(self, "horiz_res_unit"), {1:'px/in', 2:'px/cm'})
|
||||
yield Enum(UInt16(self, "width_unit"), {1:'inches', 2:'cm', 3:'points', 4:'picas', 5:'columns'})
|
||||
yield FixedFloat32(self, "vert_res")
|
||||
yield Enum(UInt16(self, "vert_res_unit"), {1:'px/in', 2:'px/cm'})
|
||||
yield Enum(UInt16(self, "height_unit"), {1:'inches', 2:'cm', 3:'points', 4:'picas', 5:'columns'})
|
||||
|
||||
class PrintScale(FieldSet):
|
||||
def createFields(self):
|
||||
yield Enum(UInt16(self, "style"), {0:'centered', 1:'size to fit', 2:'user defined'})
|
||||
yield Float32(self, "x_location")
|
||||
yield Float32(self, "y_location")
|
||||
yield Float32(self, "scale")
|
||||
|
||||
class PrintFlags(FieldSet):
|
||||
def createFields(self):
|
||||
yield Enum(UInt8(self, "labels"), BOOL)
|
||||
yield Enum(UInt8(self, "crop_marks"), BOOL)
|
||||
yield Enum(UInt8(self, "color_bars"), BOOL)
|
||||
yield Enum(UInt8(self, "reg_marks"), BOOL)
|
||||
yield Enum(UInt8(self, "negative"), BOOL)
|
||||
yield Enum(UInt8(self, "flip"), BOOL)
|
||||
yield Enum(UInt8(self, "interpolate"), BOOL)
|
||||
yield Enum(UInt8(self, "caption"), BOOL)
|
||||
yield Enum(UInt8(self, "print_flags"), BOOL)
|
||||
yield Enum(UInt8(self, "unknown"), BOOL)
|
||||
|
||||
def createValue(self):
|
||||
return [field.name for field in self if field.value]
|
||||
|
||||
def createDisplay(self):
|
||||
return ', '.join(self.value)
|
||||
|
||||
class PrintFlags2(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt16(self, "version")
|
||||
yield UInt8(self, "center_crop_marks")
|
||||
yield UInt8(self, "reserved")
|
||||
yield UInt32(self, "bleed_width")
|
||||
yield UInt16(self, "bleed_width_scale")
|
||||
|
||||
class GridGuides(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "version")
|
||||
yield UInt32(self, "horiz_cycle", "Horizontal grid spacing, in quarter inches")
|
||||
yield UInt32(self, "vert_cycle", "Vertical grid spacing, in quarter inches")
|
||||
yield UInt32(self, "guide_count", "Number of guide resource blocks (can be 0)")
|
||||
|
||||
class Thumbnail(FieldSet):
|
||||
def createFields(self):
|
||||
yield Enum(UInt32(self, "format"), {0:'Raw RGB', 1:'JPEG RGB'})
|
||||
yield UInt32(self, "width", "Width of thumbnail in pixels")
|
||||
yield UInt32(self, "height", "Height of thumbnail in pixels")
|
||||
yield UInt32(self, "widthbytes", "Padded row bytes = (width * bits per pixel + 31) / 32 * 4")
|
||||
yield UInt32(self, "uncompressed_size", "Total size = widthbytes * height * planes")
|
||||
yield UInt32(self, "compressed_size", "Size after compression. Used for consistency check")
|
||||
yield UInt16(self, "bits_per_pixel")
|
||||
yield UInt16(self, "num_planes")
|
||||
yield SubFile(self, "thumbnail", self['compressed_size'].value, "Thumbnail (JPEG file)", mime_type="image/jpeg")
|
||||
|
||||
class Photoshop8BIM(FieldSet):
|
||||
TAG_INFO = {
|
||||
0x03ed: ("res_info", ResolutionInfo, "Resolution information"),
|
||||
0x03f3: ("print_flag", PrintFlags, "Print flags: labels, crop marks, colour bars, etc."),
|
||||
0x03f5: ("col_half_info", None, "Colour half-toning information"),
|
||||
0x03f8: ("color_trans_func", None, "Colour transfer function"),
|
||||
0x0404: ("iptc", IPTC, "IPTC/NAA"),
|
||||
0x0406: ("jpeg_qual", None, "JPEG quality"),
|
||||
0x0408: ("grid_guide", GridGuides, "Grid guides informations"),
|
||||
0x0409: ("thumb_res", Thumbnail, "Thumbnail resource (PS 4.0)"),
|
||||
0x0410: ("watermark", UInt8, "Watermark"),
|
||||
0x040a: ("copyright_flag", UInt8, "Copyright flag"),
|
||||
0x040b: ("url", None, "URL"),
|
||||
0x040c: ("thumb_res2", Thumbnail, "Thumbnail resource (PS 5.0)"),
|
||||
0x040d: ("glob_angle", UInt32, "Global lighting angle for effects"),
|
||||
0x0411: ("icc_tagged", None, "ICC untagged (1 means intentionally untagged)"),
|
||||
0x0414: ("base_layer_id", UInt32, "Base value for new layers ID's"),
|
||||
0x0416: ("indexed_colors", UInt16, "Number of colors in table that are actually defined"),
|
||||
0x0417: ("transparency_index", UInt16, "Index of transparent color"),
|
||||
0x0419: ("glob_altitude", UInt32, "Global altitude"),
|
||||
0x041a: ("slices", None, "Slices"),
|
||||
0x041e: ("url_list", None, "Unicode URLs"),
|
||||
0x0421: ("version", Version, "Version information"),
|
||||
0x0425: ("caption_digest", None, "16-byte MD5 caption digest"),
|
||||
0x0426: ("printscale", PrintScale, "Printer scaling"),
|
||||
0x2710: ("print_flag2", PrintFlags2, "Print flags (2)"),
|
||||
}
|
||||
TAG_NAME = createDict(TAG_INFO, 0)
|
||||
CONTENT_HANDLER = createDict(TAG_INFO, 1)
|
||||
TAG_DESC = createDict(TAG_INFO, 2)
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
FieldSet.__init__(self, *args, **kw)
|
||||
try:
|
||||
self._name, self.handler, self._description = self.TAG_INFO[self["tag"].value]
|
||||
except KeyError:
|
||||
self.handler = None
|
||||
size = self["size"]
|
||||
self._size = size.address + size.size + alignValue(size.value, 2) * 8
|
||||
|
||||
def createFields(self):
|
||||
yield String(self, "signature", 4, "8BIM signature", charset="ASCII")
|
||||
if self["signature"].value != "8BIM":
|
||||
raise ParserError("Stream doesn't look like 8BIM item (wrong signature)!")
|
||||
yield textHandler(UInt16(self, "tag"), hexadecimal)
|
||||
if self.stream.readBytes(self.absolute_address + self.current_size, 4) != "\0\0\0\0":
|
||||
yield PascalString8(self, "name")
|
||||
size = 2 + (self["name"].size // 8) % 2
|
||||
yield NullBytes(self, "name_padding", size)
|
||||
else:
|
||||
yield String(self, "name", 4, strip="\0")
|
||||
yield UInt16(self, "size")
|
||||
size = alignValue(self["size"].value, 2)
|
||||
if not size:
|
||||
return
|
||||
if self.handler:
|
||||
if issubclass(self.handler, FieldSet):
|
||||
yield self.handler(self, "content", size=size*8)
|
||||
else:
|
||||
yield self.handler(self, "content")
|
||||
else:
|
||||
yield RawBytes(self, "content", size)
|
||||
|
||||
class PhotoshopMetadata(FieldSet):
|
||||
def createFields(self):
|
||||
yield CString(self, "signature", "Photoshop version")
|
||||
if self["signature"].value == "Photoshop 3.0":
|
||||
while not self.eof:
|
||||
yield Photoshop8BIM(self, "item[]")
|
||||
else:
|
||||
size = (self._size - self.current_size) / 8
|
||||
yield RawBytes(self, "rawdata", size)
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
"""
|
||||
PNG picture file parser.
|
||||
|
||||
Documents:
|
||||
- RFC 2083
|
||||
http://www.faqs.org/rfcs/rfc2083.html
|
||||
|
||||
Author: Victor Stinner
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet, Fragment,
|
||||
ParserError, MissingField,
|
||||
UInt8, UInt16, UInt32,
|
||||
String, CString,
|
||||
Bytes, RawBytes,
|
||||
Bit, NullBits,
|
||||
Enum, CompressedField)
|
||||
from hachoir_parser.image.common import RGB
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal
|
||||
from hachoir_core.endian import NETWORK_ENDIAN
|
||||
from hachoir_core.tools import humanFilesize
|
||||
from datetime import datetime
|
||||
|
||||
MAX_FILESIZE = 500 * 1024 * 1024 # 500 MB
|
||||
|
||||
try:
|
||||
from zlib import decompressobj
|
||||
|
||||
class Gunzip:
|
||||
def __init__(self, stream):
|
||||
self.gzip = decompressobj()
|
||||
|
||||
def __call__(self, size, data=None):
|
||||
if data is None:
|
||||
data = self.gzip.unconsumed_tail
|
||||
return self.gzip.decompress(data, size)
|
||||
|
||||
has_deflate = True
|
||||
except ImportError:
|
||||
has_deflate = False
|
||||
|
||||
UNIT_NAME = {1: "Meter"}
|
||||
COMPRESSION_NAME = {
|
||||
0: u"deflate" # with 32K sliding window
|
||||
}
|
||||
MAX_CHUNK_SIZE = 5 * 1024 * 1024 # Maximum chunk size (5 MB)
|
||||
|
||||
def headerParse(parent):
|
||||
yield UInt32(parent, "width", "Width (pixels)")
|
||||
yield UInt32(parent, "height", "Height (pixels)")
|
||||
yield UInt8(parent, "bit_depth", "Bit depth")
|
||||
yield NullBits(parent, "reserved", 5)
|
||||
yield Bit(parent, "has_alpha", "Has alpha channel?")
|
||||
yield Bit(parent, "color", "Color used?")
|
||||
yield Bit(parent, "has_palette", "Has a color palette?")
|
||||
yield Enum(UInt8(parent, "compression", "Compression method"), COMPRESSION_NAME)
|
||||
yield UInt8(parent, "filter", "Filter method")
|
||||
yield UInt8(parent, "interlace", "Interlace method")
|
||||
|
||||
def headerDescription(parent):
|
||||
return "Header: %ux%u pixels and %u bits/pixel" % \
|
||||
(parent["width"].value, parent["height"].value, getBitsPerPixel(parent))
|
||||
|
||||
def paletteParse(parent):
|
||||
size = parent["size"].value
|
||||
if (size % 3) != 0:
|
||||
raise ParserError("Palette have invalid size (%s), should be 3*n!" % size)
|
||||
nb_colors = size // 3
|
||||
for index in xrange(nb_colors):
|
||||
yield RGB(parent, "color[]")
|
||||
|
||||
def paletteDescription(parent):
|
||||
return "Palette: %u colors" % (parent["size"].value // 3)
|
||||
|
||||
def gammaParse(parent):
|
||||
yield UInt32(parent, "gamma", "Gamma (x100,000)")
|
||||
def gammaValue(parent):
|
||||
return float(parent["gamma"].value) / 100000
|
||||
def gammaDescription(parent):
|
||||
return "Gamma: %.3f" % parent.value
|
||||
|
||||
def textParse(parent):
|
||||
yield CString(parent, "keyword", "Keyword", charset="ISO-8859-1")
|
||||
length = parent["size"].value - parent["keyword"].size/8
|
||||
if length:
|
||||
yield String(parent, "text", length, "Text", charset="ISO-8859-1")
|
||||
|
||||
def textDescription(parent):
|
||||
if "text" in parent:
|
||||
return u'Text: %s' % parent["text"].display
|
||||
else:
|
||||
return u'Text'
|
||||
|
||||
def timestampParse(parent):
|
||||
yield UInt16(parent, "year", "Year")
|
||||
yield UInt8(parent, "month", "Month")
|
||||
yield UInt8(parent, "day", "Day")
|
||||
yield UInt8(parent, "hour", "Hour")
|
||||
yield UInt8(parent, "minute", "Minute")
|
||||
yield UInt8(parent, "second", "Second")
|
||||
|
||||
def timestampValue(parent):
|
||||
value = datetime(
|
||||
parent["year"].value, parent["month"].value, parent["day"].value,
|
||||
parent["hour"].value, parent["minute"].value, parent["second"].value)
|
||||
return value
|
||||
|
||||
def physicalParse(parent):
|
||||
yield UInt32(parent, "pixel_per_unit_x", "Pixel per unit, X axis")
|
||||
yield UInt32(parent, "pixel_per_unit_y", "Pixel per unit, Y axis")
|
||||
yield Enum(UInt8(parent, "unit", "Unit type"), UNIT_NAME)
|
||||
|
||||
def physicalDescription(parent):
|
||||
x = parent["pixel_per_unit_x"].value
|
||||
y = parent["pixel_per_unit_y"].value
|
||||
desc = "Physical: %ux%u pixels" % (x,y)
|
||||
if parent["unit"].value == 1:
|
||||
desc += " per meter"
|
||||
return desc
|
||||
|
||||
def parseBackgroundColor(parent):
|
||||
yield UInt16(parent, "red")
|
||||
yield UInt16(parent, "green")
|
||||
yield UInt16(parent, "blue")
|
||||
|
||||
def backgroundColorDesc(parent):
|
||||
rgb = parent["red"].value, parent["green"].value, parent["blue"].value
|
||||
name = RGB.color_name.get(rgb)
|
||||
if not name:
|
||||
name = "#%02X%02X%02X" % rgb
|
||||
return "Background color: %s" % name
|
||||
|
||||
|
||||
class ImageData(Fragment):
|
||||
def __init__(self, parent, name="compressed_data"):
|
||||
Fragment.__init__(self, parent, name, None, 8*parent["size"].value)
|
||||
data = parent.name.split('[')
|
||||
data, next = "../%s[%%u]" % data[0], int(data[1][:-1]) + 1
|
||||
first = parent.getField(data % 0)
|
||||
if first is parent:
|
||||
first = None
|
||||
if has_deflate:
|
||||
CompressedField(self, Gunzip)
|
||||
else:
|
||||
first = first[name]
|
||||
try:
|
||||
_next = parent[data % next]
|
||||
next = lambda: _next[name]
|
||||
except MissingField:
|
||||
next = None
|
||||
self.setLinks(first, next)
|
||||
|
||||
def parseTransparency(parent):
|
||||
for i in range(parent["size"].value):
|
||||
yield UInt8(parent, "alpha_value[]", "Alpha value for palette entry %i"%i)
|
||||
|
||||
def getBitsPerPixel(header):
|
||||
nr_component = 1
|
||||
if header["has_alpha"].value:
|
||||
nr_component += 1
|
||||
if header["color"].value and not header["has_palette"].value:
|
||||
nr_component += 2
|
||||
return nr_component * header["bit_depth"].value
|
||||
|
||||
class Chunk(FieldSet):
|
||||
TAG_INFO = {
|
||||
"tIME": ("time", timestampParse, "Timestamp", timestampValue),
|
||||
"pHYs": ("physical", physicalParse, physicalDescription, None),
|
||||
"IHDR": ("header", headerParse, headerDescription, None),
|
||||
"PLTE": ("palette", paletteParse, paletteDescription, None),
|
||||
"gAMA": ("gamma", gammaParse, gammaDescription, gammaValue),
|
||||
"tEXt": ("text[]", textParse, textDescription, None),
|
||||
"tRNS": ("transparency", parseTransparency, "Transparency Info", None),
|
||||
|
||||
"bKGD": ("background", parseBackgroundColor, backgroundColorDesc, None),
|
||||
"IDAT": ("data[]", lambda parent: (ImageData(parent),), "Image data", None),
|
||||
"iTXt": ("utf8_text[]", None, "International text (encoded in UTF-8)", None),
|
||||
"zTXt": ("comp_text[]", None, "Compressed text", None),
|
||||
"IEND": ("end", None, "End", None)
|
||||
}
|
||||
|
||||
def createValueFunc(self):
|
||||
return self.value_func(self)
|
||||
|
||||
def __init__(self, parent, name, description=None):
|
||||
FieldSet.__init__(self, parent, name, description)
|
||||
self._size = (self["size"].value + 3*4) * 8
|
||||
if MAX_CHUNK_SIZE < (self._size//8):
|
||||
raise ParserError("PNG: Chunk is too big (%s)"
|
||||
% humanFilesize(self._size//8))
|
||||
tag = self["tag"].value
|
||||
self.desc_func = None
|
||||
self.value_func = None
|
||||
if tag in self.TAG_INFO:
|
||||
self._name, self.parse_func, desc, value_func = self.TAG_INFO[tag]
|
||||
if value_func:
|
||||
self.value_func = value_func
|
||||
self.createValue = self.createValueFunc
|
||||
if desc:
|
||||
if isinstance(desc, str):
|
||||
self._description = desc
|
||||
else:
|
||||
self.desc_func = desc
|
||||
else:
|
||||
self._description = ""
|
||||
self.parse_func = None
|
||||
|
||||
def createFields(self):
|
||||
yield UInt32(self, "size", "Size")
|
||||
yield String(self, "tag", 4, "Tag", charset="ASCII")
|
||||
|
||||
size = self["size"].value
|
||||
if size != 0:
|
||||
if self.parse_func:
|
||||
for field in self.parse_func(self):
|
||||
yield field
|
||||
else:
|
||||
yield RawBytes(self, "content", size, "Data")
|
||||
yield textHandler(UInt32(self, "crc32", "CRC32"), hexadecimal)
|
||||
|
||||
def createDescription(self):
|
||||
if self.desc_func:
|
||||
return self.desc_func(self)
|
||||
else:
|
||||
return "Chunk: %s" % self["tag"].display
|
||||
|
||||
class PngFile(Parser):
|
||||
PARSER_TAGS = {
|
||||
"id": "png",
|
||||
"category": "image",
|
||||
"file_ext": ("png",),
|
||||
"mime": (u"image/png", u"image/x-png"),
|
||||
"min_size": 8*8, # just the identifier
|
||||
"magic": [('\x89PNG\r\n\x1A\n', 0)],
|
||||
"description": "Portable Network Graphics (PNG) picture"
|
||||
}
|
||||
endian = NETWORK_ENDIAN
|
||||
|
||||
def validate(self):
|
||||
if self["id"].value != '\x89PNG\r\n\x1A\n':
|
||||
return "Invalid signature"
|
||||
if self[1].name != "header":
|
||||
return "First chunk is not header"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield Bytes(self, "id", 8, r"PNG identifier ('\x89PNG\r\n\x1A\n')")
|
||||
while not self.eof:
|
||||
yield Chunk(self, "chunk[]")
|
||||
|
||||
def createDescription(self):
|
||||
header = self["header"]
|
||||
desc = "PNG picture: %ux%ux%u" % (
|
||||
header["width"].value, header["height"].value, getBitsPerPixel(header))
|
||||
if header["has_alpha"].value:
|
||||
desc += " (alpha layer)"
|
||||
return desc
|
||||
|
||||
def createContentSize(self):
|
||||
field = self["header"]
|
||||
start = field.absolute_address + field.size
|
||||
end = MAX_FILESIZE * 8
|
||||
pos = self.stream.searchBytes("\0\0\0\0IEND\xae\x42\x60\x82", start, end)
|
||||
if pos is not None:
|
||||
return pos + 12*8
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
Photoshop parser (.psd file).
|
||||
|
||||
Creation date: 8 january 2006
|
||||
Author: Victor Stinner
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet,
|
||||
UInt16, UInt32, String, NullBytes, Enum, RawBytes)
|
||||
from hachoir_core.endian import BIG_ENDIAN
|
||||
from hachoir_parser.image.photoshop_metadata import Photoshop8BIM
|
||||
|
||||
class Config(FieldSet):
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
self._size = (4 + self["size"].value) * 8
|
||||
|
||||
def createFields(self):
|
||||
yield UInt32(self, "size")
|
||||
while not self.eof:
|
||||
yield Photoshop8BIM(self, "item[]")
|
||||
|
||||
class PsdFile(Parser):
|
||||
endian = BIG_ENDIAN
|
||||
PARSER_TAGS = {
|
||||
"id": "psd",
|
||||
"category": "image",
|
||||
"file_ext": ("psd",),
|
||||
"mime": (u"image/psd", u"image/photoshop", u"image/x-photoshop"),
|
||||
"min_size": 4*8,
|
||||
"magic": (("8BPS\0\1",0),),
|
||||
"description": "Photoshop (PSD) picture",
|
||||
}
|
||||
COLOR_MODE = {
|
||||
0: u"Bitmap",
|
||||
1: u"Grayscale",
|
||||
2: u"Indexed",
|
||||
3: u"RGB color",
|
||||
4: u"CMYK color",
|
||||
7: u"Multichannel",
|
||||
8: u"Duotone",
|
||||
9: u"Lab Color",
|
||||
}
|
||||
COMPRESSION_NAME = {
|
||||
0: "Raw data",
|
||||
1: "RLE",
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if self.stream.readBytes(0, 4) != "8BPS":
|
||||
return "Invalid signature"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield String(self, "signature", 4, "PSD signature (8BPS)", charset="ASCII")
|
||||
yield UInt16(self, "version")
|
||||
yield NullBytes(self, "reserved[]", 6)
|
||||
yield UInt16(self, "nb_channels")
|
||||
yield UInt32(self, "width")
|
||||
yield UInt32(self, "height")
|
||||
yield UInt16(self, "depth")
|
||||
yield Enum(UInt16(self, "color_mode"), self.COLOR_MODE)
|
||||
|
||||
# Mode data
|
||||
yield UInt32(self, "mode_data_size")
|
||||
size = self["mode_data_size"].value
|
||||
if size:
|
||||
yield RawBytes(self, "mode_data", size)
|
||||
|
||||
# Resources
|
||||
yield Config(self, "config")
|
||||
|
||||
# Reserved
|
||||
yield UInt32(self, "reserved_data_size")
|
||||
size = self["reserved_data_size"].value
|
||||
if size:
|
||||
yield RawBytes(self, "reserved_data", size)
|
||||
|
||||
yield Enum(UInt16(self, "compression"), self.COMPRESSION_NAME)
|
||||
|
||||
size = (self.size - self.current_size) // 8
|
||||
if size:
|
||||
yield RawBytes(self, "end", size)
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
Truevision Targa Graphic (TGA) picture parser.
|
||||
|
||||
Author: Victor Stinner
|
||||
Creation: 18 december 2006
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import FieldSet, UInt8, UInt16, Enum, RawBytes
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_parser.image.common import PaletteRGB
|
||||
|
||||
class Line(FieldSet):
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
self._size = self["/width"].value * self["/bpp"].value
|
||||
|
||||
def createFields(self):
|
||||
for x in xrange(self["/width"].value):
|
||||
yield UInt8(self, "pixel[]")
|
||||
|
||||
class Pixels(FieldSet):
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
self._size = self["/width"].value * self["/height"].value * self["/bpp"].value
|
||||
|
||||
def createFields(self):
|
||||
if self["/options"].value == 0:
|
||||
RANGE = xrange(self["/height"].value-1,-1,-1)
|
||||
else:
|
||||
RANGE = xrange(self["/height"].value)
|
||||
for y in RANGE:
|
||||
yield Line(self, "line[%u]" % y)
|
||||
|
||||
class TargaFile(Parser):
|
||||
PARSER_TAGS = {
|
||||
"id": "targa",
|
||||
"category": "image",
|
||||
"file_ext": ("tga",),
|
||||
"mime": (u"image/targa", u"image/tga", u"image/x-tga"),
|
||||
"min_size": 18*8,
|
||||
"description": u"Truevision Targa Graphic (TGA)"
|
||||
}
|
||||
CODEC_NAME = {
|
||||
1: u"8-bit uncompressed",
|
||||
2: u"24-bit uncompressed",
|
||||
9: u"8-bit RLE",
|
||||
10: u"24-bit RLE",
|
||||
}
|
||||
endian = LITTLE_ENDIAN
|
||||
|
||||
def validate(self):
|
||||
if self["version"].value != 1:
|
||||
return "Unknown version"
|
||||
if self["codec"].value not in self.CODEC_NAME:
|
||||
return "Unknown codec"
|
||||
if self["x_min"].value != 0 or self["y_min"].value != 0:
|
||||
return "(x_min, y_min) is not (0,0)"
|
||||
if self["bpp"].value not in (8, 24):
|
||||
return "Unknown bits/pixel value"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield UInt8(self, "hdr_size", "Header size in bytes")
|
||||
yield UInt8(self, "version", "Targa version (always one)")
|
||||
yield Enum(UInt8(self, "codec", "Pixels encoding"), self.CODEC_NAME)
|
||||
yield UInt16(self, "palette_ofs", "Palette absolute file offset")
|
||||
yield UInt16(self, "nb_color", "Number of color")
|
||||
yield UInt8(self, "color_map_size", "Color map entry size")
|
||||
yield UInt16(self, "x_min")
|
||||
yield UInt16(self, "y_min")
|
||||
yield UInt16(self, "width")
|
||||
yield UInt16(self, "height")
|
||||
yield UInt8(self, "bpp", "Bits per pixel")
|
||||
yield UInt8(self, "options", "Options (0: vertical mirror)")
|
||||
if self["bpp"].value == 8:
|
||||
yield PaletteRGB(self, "palette", 256)
|
||||
if self["codec"].value == 1:
|
||||
yield Pixels(self, "pixels")
|
||||
else:
|
||||
size = (self.size - self.current_size) // 8
|
||||
if size:
|
||||
yield RawBytes(self, "raw_pixels", size)
|
||||
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
TIFF image parser.
|
||||
|
||||
Authors: Victor Stinner and Sebastien Ponce
|
||||
Creation date: 30 september 2006
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet, SeekableFieldSet, ParserError, RootSeekableFieldSet,
|
||||
UInt16, UInt32, Bytes, String)
|
||||
from hachoir_core.endian import LITTLE_ENDIAN, BIG_ENDIAN
|
||||
from hachoir_parser.image.exif import BasicIFDEntry
|
||||
from hachoir_core.tools import createDict
|
||||
|
||||
MAX_COUNT = 250
|
||||
|
||||
class IFDEntry(BasicIFDEntry):
|
||||
static_size = 12*8
|
||||
|
||||
TAG_INFO = {
|
||||
254: ("new_subfile_type", "New subfile type"),
|
||||
255: ("subfile_type", "Subfile type"),
|
||||
256: ("img_width", "Image width in pixels"),
|
||||
257: ("img_height", "Image height in pixels"),
|
||||
258: ("bits_per_sample", "Bits per sample"),
|
||||
259: ("compression", "Compression method"),
|
||||
262: ("photo_interpret", "Photometric interpretation"),
|
||||
263: ("thres", "Thresholding"),
|
||||
264: ("cell_width", "Cellule width"),
|
||||
265: ("cell_height", "Cellule height"),
|
||||
266: ("fill_order", "Fill order"),
|
||||
269: ("doc_name", "Document name"),
|
||||
270: ("description", "Image description"),
|
||||
271: ("make", "Make"),
|
||||
272: ("model", "Model"),
|
||||
273: ("strip_ofs", "Strip offsets"),
|
||||
274: ("orientation", "Orientation"),
|
||||
277: ("sample_pixel", "Samples per pixel"),
|
||||
278: ("row_per_strip", "Rows per strip"),
|
||||
279: ("strip_byte", "Strip byte counts"),
|
||||
280: ("min_sample_value", "Min sample value"),
|
||||
281: ("max_sample_value", "Max sample value"),
|
||||
282: ("xres", "X resolution"),
|
||||
283: ("yres", "Y resolution"),
|
||||
284: ("planar_conf", "Planar configuration"),
|
||||
285: ("page_name", "Page name"),
|
||||
286: ("xpos", "X position"),
|
||||
287: ("ypos", "Y position"),
|
||||
288: ("free_ofs", "Free offsets"),
|
||||
289: ("free_byte", "Free byte counts"),
|
||||
290: ("gray_resp_unit", "Gray response unit"),
|
||||
291: ("gray_resp_curve", "Gray response curve"),
|
||||
292: ("group3_opt", "Group 3 options"),
|
||||
293: ("group4_opt", "Group 4 options"),
|
||||
296: ("res_unit", "Resolution unit"),
|
||||
297: ("page_nb", "Page number"),
|
||||
301: ("color_respt_curve", "Color response curves"),
|
||||
305: ("software", "Software"),
|
||||
306: ("date_time", "Date time"),
|
||||
315: ("artist", "Artist"),
|
||||
316: ("host_computer", "Host computer"),
|
||||
317: ("predicator", "Predicator"),
|
||||
318: ("white_pt", "White point"),
|
||||
319: ("prim_chomat", "Primary chromaticities"),
|
||||
320: ("color_map", "Color map"),
|
||||
321: ("half_tone_hints", "Halftone Hints"),
|
||||
322: ("tile_width", "TileWidth"),
|
||||
323: ("tile_length", "TileLength"),
|
||||
324: ("tile_offsets", "TileOffsets"),
|
||||
325: ("tile_byte_counts", "TileByteCounts"),
|
||||
332: ("ink_set", "InkSet"),
|
||||
333: ("ink_names", "InkNames"),
|
||||
334: ("number_of_inks", "NumberOfInks"),
|
||||
336: ("dot_range", "DotRange"),
|
||||
337: ("target_printer", "TargetPrinter"),
|
||||
338: ("extra_samples", "ExtraSamples"),
|
||||
339: ("sample_format", "SampleFormat"),
|
||||
340: ("smin_sample_value", "SMinSampleValue"),
|
||||
341: ("smax_sample_value", "SMaxSampleValue"),
|
||||
342: ("transfer_range", "TransferRange"),
|
||||
512: ("jpeg_proc", "JPEGProc"),
|
||||
513: ("jpeg_interchange_format", "JPEGInterchangeFormat"),
|
||||
514: ("jpeg_interchange_format_length", "JPEGInterchangeFormatLength"),
|
||||
515: ("jpeg_restart_interval", "JPEGRestartInterval"),
|
||||
517: ("jpeg_lossless_predictors", "JPEGLosslessPredictors"),
|
||||
518: ("jpeg_point_transforms", "JPEGPointTransforms"),
|
||||
519: ("jpeg_qtables", "JPEGQTables"),
|
||||
520: ("jpeg_dctables", "JPEGDCTables"),
|
||||
521: ("jpeg_actables", "JPEGACTables"),
|
||||
529: ("ycbcr_coefficients", "YCbCrCoefficients"),
|
||||
530: ("ycbcr_subsampling", "YCbCrSubSampling"),
|
||||
531: ("ycbcr_positioning", "YCbCrPositioning"),
|
||||
532: ("reference_blackwhite", "ReferenceBlackWhite"),
|
||||
33432: ("copyright", "Copyright"),
|
||||
0x8769: ("ifd_pointer", "Pointer to next IFD entry"),
|
||||
}
|
||||
TAG_NAME = createDict(TAG_INFO, 0)
|
||||
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
tag = self["tag"].value
|
||||
if tag in self.TAG_INFO:
|
||||
self._name, self._description = self.TAG_INFO[tag]
|
||||
else:
|
||||
self._parser = None
|
||||
|
||||
class IFD(FieldSet):
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
self._size = 16 + self["count"].value * IFDEntry.static_size
|
||||
self._has_offset = False
|
||||
|
||||
def createFields(self):
|
||||
yield UInt16(self, "count")
|
||||
if MAX_COUNT < self["count"].value:
|
||||
raise ParserError("TIFF IFD: Invalid count (%s)"
|
||||
% self["count"].value)
|
||||
for index in xrange(self["count"].value):
|
||||
yield IFDEntry(self, "entry[]")
|
||||
|
||||
class ImageFile(SeekableFieldSet):
|
||||
def __init__(self, parent, name, description, ifd):
|
||||
SeekableFieldSet.__init__(self, parent, name, description, None)
|
||||
self._has_offset = False
|
||||
self._ifd = ifd
|
||||
|
||||
def createFields(self):
|
||||
datas = {}
|
||||
for entry in self._ifd:
|
||||
if type(entry) != IFDEntry:
|
||||
continue
|
||||
for c in entry:
|
||||
if c.name != "offset":
|
||||
continue
|
||||
self.seekByte(c.value, False)
|
||||
desc = "data of ifd entry " + entry.name,
|
||||
entryType = BasicIFDEntry.ENTRY_FORMAT[entry["type"].value]
|
||||
count = entry["count"].value
|
||||
if entryType == String:
|
||||
yield String(self, entry.name, count, desc, "\0", "ISO-8859-1")
|
||||
else:
|
||||
d = Data(self, entry.name, desc, entryType, count)
|
||||
datas[d.name] = d
|
||||
yield d
|
||||
break
|
||||
# image data
|
||||
if "strip_ofs" in datas and "strip_byte" in datas:
|
||||
for i in xrange(datas["strip_byte"]._count):
|
||||
self.seekByte(datas["strip_ofs"]["value["+str(i)+"]"].value, False)
|
||||
yield Bytes(self, "strip[]", datas["strip_byte"]["value["+str(i)+"]"].value)
|
||||
|
||||
class Data(FieldSet):
|
||||
|
||||
def __init__(self, parent, name, desc, type, count):
|
||||
size = type.static_size * count
|
||||
FieldSet.__init__(self, parent, name, desc, size)
|
||||
self._count = count
|
||||
self._type = type
|
||||
|
||||
def createFields(self):
|
||||
for i in xrange(self._count):
|
||||
yield self._type(self, "value[]")
|
||||
|
||||
class TiffFile(RootSeekableFieldSet, Parser):
|
||||
PARSER_TAGS = {
|
||||
"id": "tiff",
|
||||
"category": "image",
|
||||
"file_ext": ("tif", "tiff"),
|
||||
"mime": (u"image/tiff",),
|
||||
"min_size": 8*8,
|
||||
# TODO: Re-enable magic
|
||||
"magic": (("II\x2A\0", 0), ("MM\0\x2A", 0)),
|
||||
"description": "TIFF picture"
|
||||
}
|
||||
|
||||
# Correct endian is set in constructor
|
||||
endian = LITTLE_ENDIAN
|
||||
|
||||
def __init__(self, stream, **args):
|
||||
RootSeekableFieldSet.__init__(self, None, "root", stream, None, stream.askSize(self))
|
||||
if self.stream.readBytes(0, 2) == "MM":
|
||||
self.endian = BIG_ENDIAN
|
||||
Parser.__init__(self, stream, **args)
|
||||
|
||||
def validate(self):
|
||||
endian = self.stream.readBytes(0, 2)
|
||||
if endian not in ("MM", "II"):
|
||||
return "Invalid endian (%r)" % endian
|
||||
if self["version"].value != 42:
|
||||
return "Unknown TIFF version"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
yield String(self, "endian", 2, 'Endian ("II" or "MM")', charset="ASCII")
|
||||
yield UInt16(self, "version", "TIFF version number")
|
||||
offset = UInt32(self, "img_dir_ofs[]", "Next image directory offset (in bytes from the beginning)")
|
||||
yield offset
|
||||
ifds = []
|
||||
while True:
|
||||
if offset.value == 0:
|
||||
break
|
||||
|
||||
self.seekByte(offset.value, relative=False)
|
||||
ifd = IFD(self, "ifd[]", "Image File Directory", None)
|
||||
ifds.append(ifd)
|
||||
yield ifd
|
||||
offset = UInt32(self, "img_dir_ofs[]", "Next image directory offset (in bytes from the beginning)")
|
||||
yield offset
|
||||
for ifd in ifds:
|
||||
image = ImageFile(self, "image[]", "Image File", ifd)
|
||||
yield image
|
||||
@@ -0,0 +1,611 @@
|
||||
"""
|
||||
Hachoir parser of Microsoft Windows Metafile (WMF) file format.
|
||||
|
||||
Documentation:
|
||||
- Microsoft Windows Metafile; also known as: WMF,
|
||||
Enhanced Metafile, EMF, APM
|
||||
http://wvware.sourceforge.net/caolan/ora-wmf.html
|
||||
- libwmf source code:
|
||||
- include/libwmf/defs.h: enums
|
||||
- src/player/meta.h: arguments parsers
|
||||
- libemf source code
|
||||
|
||||
Author: Victor Stinner
|
||||
Creation date: 26 december 2006
|
||||
"""
|
||||
|
||||
MAX_FILESIZE = 50 * 1024 * 1024
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (FieldSet, StaticFieldSet, Enum,
|
||||
MissingField, ParserError,
|
||||
UInt32, Int32, UInt16, Int16, UInt8, NullBytes, RawBytes, String)
|
||||
from hachoir_core.endian import LITTLE_ENDIAN
|
||||
from hachoir_core.text_handler import textHandler, hexadecimal
|
||||
from hachoir_core.tools import createDict
|
||||
from hachoir_parser.image.common import RGBA
|
||||
|
||||
POLYFILL_MODE = {1: "Alternate", 2: "Winding"}
|
||||
|
||||
BRUSH_STYLE = {
|
||||
0: u"Solid",
|
||||
1: u"Null",
|
||||
2: u"Hollow",
|
||||
3: u"Pattern",
|
||||
4: u"Indexed",
|
||||
5: u"DIB pattern",
|
||||
6: u"DIB pattern point",
|
||||
7: u"Pattern 8x8",
|
||||
8: u"DIB pattern 8x8",
|
||||
}
|
||||
|
||||
HATCH_STYLE = {
|
||||
0: u"Horizontal", # -----
|
||||
1: u"Vertical", # |||||
|
||||
2: u"FDIAGONAL", # \\\\\
|
||||
3: u"BDIAGONAL", # /////
|
||||
4: u"Cross", # +++++
|
||||
5: u"Diagonal cross", # xxxxx
|
||||
}
|
||||
|
||||
PEN_STYLE = {
|
||||
0: u"Solid",
|
||||
1: u"Dash", # -------
|
||||
2: u"Dot", # .......
|
||||
3: u"Dash dot", # _._._._
|
||||
4: u"Dash dot dot", # _.._.._
|
||||
5: u"Null",
|
||||
6: u"Inside frame",
|
||||
7: u"User style",
|
||||
8: u"Alternate",
|
||||
}
|
||||
|
||||
# Binary raster operations
|
||||
ROP2_DESC = {
|
||||
1: u"Black (0)",
|
||||
2: u"Not merge pen (DPon)",
|
||||
3: u"Mask not pen (DPna)",
|
||||
4: u"Not copy pen (PN)",
|
||||
5: u"Mask pen not (PDna)",
|
||||
6: u"Not (Dn)",
|
||||
7: u"Xor pen (DPx)",
|
||||
8: u"Not mask pen (DPan)",
|
||||
9: u"Mask pen (DPa)",
|
||||
10: u"Not xor pen (DPxn)",
|
||||
11: u"No operation (D)",
|
||||
12: u"Merge not pen (DPno)",
|
||||
13: u"Copy pen (P)",
|
||||
14: u"Merge pen not (PDno)",
|
||||
15: u"Merge pen (DPo)",
|
||||
16: u"White (1)",
|
||||
}
|
||||
|
||||
def parseXY(parser):
|
||||
yield Int16(parser, "x")
|
||||
yield Int16(parser, "y")
|
||||
|
||||
def parseCreateBrushIndirect(parser):
|
||||
yield Enum(UInt16(parser, "brush_style"), BRUSH_STYLE)
|
||||
yield RGBA(parser, "color")
|
||||
yield Enum(UInt16(parser, "brush_hatch"), HATCH_STYLE)
|
||||
|
||||
def parsePenIndirect(parser):
|
||||
yield Enum(UInt16(parser, "pen_style"), PEN_STYLE)
|
||||
yield UInt16(parser, "pen_width")
|
||||
yield UInt16(parser, "pen_height")
|
||||
yield RGBA(parser, "color")
|
||||
|
||||
def parsePolyFillMode(parser):
|
||||
yield Enum(UInt16(parser, "operation"), POLYFILL_MODE)
|
||||
|
||||
def parseROP2(parser):
|
||||
yield Enum(UInt16(parser, "operation"), ROP2_DESC)
|
||||
|
||||
def parseObjectID(parser):
|
||||
yield UInt16(parser, "object_id")
|
||||
|
||||
class Point(FieldSet):
|
||||
static_size = 32
|
||||
def createFields(self):
|
||||
yield Int16(self, "x")
|
||||
yield Int16(self, "y")
|
||||
def createDescription(self):
|
||||
return "Point (%s, %s)" % (self["x"].value, self["y"].value)
|
||||
|
||||
def parsePolygon(parser):
|
||||
yield UInt16(parser, "count")
|
||||
for index in xrange(parser["count"].value):
|
||||
yield Point(parser, "point[]")
|
||||
|
||||
META = {
|
||||
0x0000: ("EOF", u"End of file", None),
|
||||
0x001E: ("SAVEDC", u"Save device context", None),
|
||||
0x0035: ("REALIZEPALETTE", u"Realize palette", None),
|
||||
0x0037: ("SETPALENTRIES", u"Set palette entries", None),
|
||||
0x00f7: ("CREATEPALETTE", u"Create palette", None),
|
||||
0x0102: ("SETBKMODE", u"Set background mode", None),
|
||||
0x0103: ("SETMAPMODE", u"Set mapping mode", None),
|
||||
0x0104: ("SETROP2", u"Set foreground mix mode", parseROP2),
|
||||
0x0106: ("SETPOLYFILLMODE", u"Set polygon fill mode", parsePolyFillMode),
|
||||
0x0107: ("SETSTRETCHBLTMODE", u"Set bitmap streching mode", None),
|
||||
0x0108: ("SETTEXTCHAREXTRA", u"Set text character extra", None),
|
||||
0x0127: ("RESTOREDC", u"Restore device context", None),
|
||||
0x012A: ("INVERTREGION", u"Invert region", None),
|
||||
0x012B: ("PAINTREGION", u"Paint region", None),
|
||||
0x012C: ("SELECTCLIPREGION", u"Select clipping region", None),
|
||||
0x012D: ("SELECTOBJECT", u"Select object", parseObjectID),
|
||||
0x012E: ("SETTEXTALIGN", u"Set text alignment", None),
|
||||
0x0142: ("CREATEDIBPATTERNBRUSH", u"Create DIB brush with specified pattern", None),
|
||||
0x01f0: ("DELETEOBJECT", u"Delete object", parseObjectID),
|
||||
0x0201: ("SETBKCOLOR", u"Set background color", None),
|
||||
0x0209: ("SETTEXTCOLOR", u"Set text color", None),
|
||||
0x020A: ("SETTEXTJUSTIFICATION", u"Set text justification", None),
|
||||
0x020B: ("SETWINDOWORG", u"Set window origin", parseXY),
|
||||
0x020C: ("SETWINDOWEXT", u"Set window extends", parseXY),
|
||||
0x020D: ("SETVIEWPORTORG", u"Set view port origin", None),
|
||||
0x020E: ("SETVIEWPORTEXT", u"Set view port extends", None),
|
||||
0x020F: ("OFFSETWINDOWORG", u"Offset window origin", None),
|
||||
0x0211: ("OFFSETVIEWPORTORG", u"Offset view port origin", None),
|
||||
0x0213: ("LINETO", u"Draw a line to", None),
|
||||
0x0214: ("MOVETO", u"Move to", None),
|
||||
0x0220: ("OFFSETCLIPRGN", u"Offset clipping rectangle", None),
|
||||
0x0228: ("FILLREGION", u"Fill region", None),
|
||||
0x0231: ("SETMAPPERFLAGS", u"Set mapper flags", None),
|
||||
0x0234: ("SELECTPALETTE", u"Select palette", None),
|
||||
0x02FB: ("CREATEFONTINDIRECT", u"Create font indirect", None),
|
||||
0x02FA: ("CREATEPENINDIRECT", u"Create pen indirect", parsePenIndirect),
|
||||
0x02FC: ("CREATEBRUSHINDIRECT", u"Create brush indirect", parseCreateBrushIndirect),
|
||||
0x0324: ("POLYGON", u"Draw a polygon", parsePolygon),
|
||||
0x0325: ("POLYLINE", u"Draw a polyline", None),
|
||||
0x0410: ("SCALEWINDOWEXT", u"Scale window extends", None),
|
||||
0x0412: ("SCALEVIEWPORTEXT", u"Scale view port extends", None),
|
||||
0x0415: ("EXCLUDECLIPRECT", u"Exclude clipping rectangle", None),
|
||||
0x0416: ("INTERSECTCLIPRECT", u"Intersect clipping rectangle", None),
|
||||
0x0418: ("ELLIPSE", u"Draw an ellipse", None),
|
||||
0x0419: ("FLOODFILL", u"Flood fill", None),
|
||||
0x041B: ("RECTANGLE", u"Draw a rectangle", None),
|
||||
0x041F: ("SETPIXEL", u"Set pixel", None),
|
||||
0x0429: ("FRAMEREGION", u"Fram region", None),
|
||||
0x0521: ("TEXTOUT", u"Draw text", None),
|
||||
0x0538: ("POLYPOLYGON", u"Draw multiple polygons", None),
|
||||
0x0548: ("EXTFLOODFILL", u"Extend flood fill", None),
|
||||
0x061C: ("ROUNDRECT", u"Draw a rounded rectangle", None),
|
||||
0x061D: ("PATBLT", u"Pattern blitting", None),
|
||||
0x0626: ("ESCAPE", u"Escape", None),
|
||||
0x06FF: ("CREATEREGION", u"Create region", None),
|
||||
0x0817: ("ARC", u"Draw an arc", None),
|
||||
0x081A: ("PIE", u"Draw a pie", None),
|
||||
0x0830: ("CHORD", u"Draw a chord", None),
|
||||
0x0940: ("DIBBITBLT", u"DIB bit blitting", None),
|
||||
0x0a32: ("EXTTEXTOUT", u"Draw text (extra)", None),
|
||||
0x0b41: ("DIBSTRETCHBLT", u"DIB stretch blitting", None),
|
||||
0x0d33: ("SETDIBTODEV", u"Set DIB to device", None),
|
||||
0x0f43: ("STRETCHDIB", u"Stretch DIB", None),
|
||||
}
|
||||
META_NAME = createDict(META, 0)
|
||||
META_DESC = createDict(META, 1)
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# EMF constants
|
||||
|
||||
# EMF mapping modes
|
||||
EMF_MAPPING_MODE = {
|
||||
1: "TEXT",
|
||||
2: "LOMETRIC",
|
||||
3: "HIMETRIC",
|
||||
4: "LOENGLISH",
|
||||
5: "HIENGLISH",
|
||||
6: "TWIPS",
|
||||
7: "ISOTROPIC",
|
||||
8: "ANISOTROPIC",
|
||||
}
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# EMF parser
|
||||
|
||||
def parseEmfMappingMode(parser):
|
||||
yield Enum(Int32(parser, "mapping_mode"), EMF_MAPPING_MODE)
|
||||
|
||||
def parseXY32(parser):
|
||||
yield Int32(parser, "x")
|
||||
yield Int32(parser, "y")
|
||||
|
||||
def parseObjectID32(parser):
|
||||
yield textHandler(UInt32(parser, "object_id"), hexadecimal)
|
||||
|
||||
def parseBrushIndirect(parser):
|
||||
yield UInt32(parser, "ihBrush")
|
||||
yield UInt32(parser, "style")
|
||||
yield RGBA(parser, "color")
|
||||
yield Int32(parser, "hatch")
|
||||
|
||||
class Point16(FieldSet):
|
||||
static_size = 32
|
||||
def createFields(self):
|
||||
yield Int16(self, "x")
|
||||
yield Int16(self, "y")
|
||||
def createDescription(self):
|
||||
return "Point16: (%i,%i)" % (self["x"].value, self["y"].value)
|
||||
|
||||
def parsePoint16array(parser):
|
||||
yield RECT32(parser, "bounds")
|
||||
yield UInt32(parser, "count")
|
||||
for index in xrange(parser["count"].value):
|
||||
yield Point16(parser, "point[]")
|
||||
|
||||
def parseGDIComment(parser):
|
||||
yield UInt32(parser, "data_size")
|
||||
size = parser["data_size"].value
|
||||
if size:
|
||||
yield RawBytes(parser, "data", size)
|
||||
|
||||
def parseICMMode(parser):
|
||||
yield UInt32(parser, "icm_mode")
|
||||
|
||||
def parseExtCreatePen(parser):
|
||||
yield UInt32(parser, "ihPen")
|
||||
yield UInt32(parser, "offBmi")
|
||||
yield UInt32(parser, "cbBmi")
|
||||
yield UInt32(parser, "offBits")
|
||||
yield UInt32(parser, "cbBits")
|
||||
yield UInt32(parser, "pen_style")
|
||||
yield UInt32(parser, "width")
|
||||
yield UInt32(parser, "brush_style")
|
||||
yield RGBA(parser, "color")
|
||||
yield UInt32(parser, "hatch")
|
||||
yield UInt32(parser, "nb_style")
|
||||
for index in xrange(parser["nb_style"].value):
|
||||
yield UInt32(parser, "style")
|
||||
|
||||
EMF_META = {
|
||||
1: ("HEADER", u"Header", None),
|
||||
2: ("POLYBEZIER", u"Draw poly bezier", None),
|
||||
3: ("POLYGON", u"Draw polygon", None),
|
||||
4: ("POLYLINE", u"Draw polyline", None),
|
||||
5: ("POLYBEZIERTO", u"Draw poly bezier to", None),
|
||||
6: ("POLYLINETO", u"Draw poly line to", None),
|
||||
7: ("POLYPOLYLINE", u"Draw poly polyline", None),
|
||||
8: ("POLYPOLYGON", u"Draw poly polygon", None),
|
||||
9: ("SETWINDOWEXTEX", u"Set window extend EX", parseXY32),
|
||||
10: ("SETWINDOWORGEX", u"Set window origin EX", parseXY32),
|
||||
11: ("SETVIEWPORTEXTEX", u"Set viewport extend EX", parseXY32),
|
||||
12: ("SETVIEWPORTORGEX", u"Set viewport origin EX", parseXY32),
|
||||
13: ("SETBRUSHORGEX", u"Set brush org EX", None),
|
||||
14: ("EOF", u"End of file", None),
|
||||
15: ("SETPIXELV", u"Set pixel V", None),
|
||||
16: ("SETMAPPERFLAGS", u"Set mapper flags", None),
|
||||
17: ("SETMAPMODE", u"Set mapping mode", parseEmfMappingMode),
|
||||
18: ("SETBKMODE", u"Set background mode", None),
|
||||
19: ("SETPOLYFILLMODE", u"Set polyfill mode", None),
|
||||
20: ("SETROP2", u"Set ROP2", None),
|
||||
21: ("SETSTRETCHBLTMODE", u"Set stretching blitting mode", None),
|
||||
22: ("SETTEXTALIGN", u"Set text align", None),
|
||||
23: ("SETCOLORADJUSTMENT", u"Set color adjustment", None),
|
||||
24: ("SETTEXTCOLOR", u"Set text color", None),
|
||||
25: ("SETBKCOLOR", u"Set background color", None),
|
||||
26: ("OFFSETCLIPRGN", u"Offset clipping region", None),
|
||||
27: ("MOVETOEX", u"Move to EX", parseXY32),
|
||||
28: ("SETMETARGN", u"Set meta region", None),
|
||||
29: ("EXCLUDECLIPRECT", u"Exclude clipping rectangle", None),
|
||||
30: ("INTERSECTCLIPRECT", u"Intersect clipping rectangle", None),
|
||||
31: ("SCALEVIEWPORTEXTEX", u"Scale viewport extend EX", None),
|
||||
32: ("SCALEWINDOWEXTEX", u"Scale window extend EX", None),
|
||||
33: ("SAVEDC", u"Save device context", None),
|
||||
34: ("RESTOREDC", u"Restore device context", None),
|
||||
35: ("SETWORLDTRANSFORM", u"Set world transform", None),
|
||||
36: ("MODIFYWORLDTRANSFORM", u"Modify world transform", None),
|
||||
37: ("SELECTOBJECT", u"Select object", parseObjectID32),
|
||||
38: ("CREATEPEN", u"Create pen", None),
|
||||
39: ("CREATEBRUSHINDIRECT", u"Create brush indirect", parseBrushIndirect),
|
||||
40: ("DELETEOBJECT", u"Delete object", parseObjectID32),
|
||||
41: ("ANGLEARC", u"Draw angle arc", None),
|
||||
42: ("ELLIPSE", u"Draw ellipse", None),
|
||||
43: ("RECTANGLE", u"Draw rectangle", None),
|
||||
44: ("ROUNDRECT", u"Draw rounded rectangle", None),
|
||||
45: ("ARC", u"Draw arc", None),
|
||||
46: ("CHORD", u"Draw chord", None),
|
||||
47: ("PIE", u"Draw pie", None),
|
||||
48: ("SELECTPALETTE", u"Select palette", None),
|
||||
49: ("CREATEPALETTE", u"Create palette", None),
|
||||
50: ("SETPALETTEENTRIES", u"Set palette entries", None),
|
||||
51: ("RESIZEPALETTE", u"Resize palette", None),
|
||||
52: ("REALIZEPALETTE", u"Realize palette", None),
|
||||
53: ("EXTFLOODFILL", u"EXT flood fill", None),
|
||||
54: ("LINETO", u"Draw line to", parseXY32),
|
||||
55: ("ARCTO", u"Draw arc to", None),
|
||||
56: ("POLYDRAW", u"Draw poly draw", None),
|
||||
57: ("SETARCDIRECTION", u"Set arc direction", None),
|
||||
58: ("SETMITERLIMIT", u"Set miter limit", None),
|
||||
59: ("BEGINPATH", u"Begin path", None),
|
||||
60: ("ENDPATH", u"End path", None),
|
||||
61: ("CLOSEFIGURE", u"Close figure", None),
|
||||
62: ("FILLPATH", u"Fill path", None),
|
||||
63: ("STROKEANDFILLPATH", u"Stroke and fill path", None),
|
||||
64: ("STROKEPATH", u"Stroke path", None),
|
||||
65: ("FLATTENPATH", u"Flatten path", None),
|
||||
66: ("WIDENPATH", u"Widen path", None),
|
||||
67: ("SELECTCLIPPATH", u"Select clipping path", None),
|
||||
68: ("ABORTPATH", u"Arbort path", None),
|
||||
70: ("GDICOMMENT", u"GDI comment", parseGDIComment),
|
||||
71: ("FILLRGN", u"Fill region", None),
|
||||
72: ("FRAMERGN", u"Frame region", None),
|
||||
73: ("INVERTRGN", u"Invert region", None),
|
||||
74: ("PAINTRGN", u"Paint region", None),
|
||||
75: ("EXTSELECTCLIPRGN", u"EXT select clipping region", None),
|
||||
76: ("BITBLT", u"Bit blitting", None),
|
||||
77: ("STRETCHBLT", u"Stretch blitting", None),
|
||||
78: ("MASKBLT", u"Mask blitting", None),
|
||||
79: ("PLGBLT", u"PLG blitting", None),
|
||||
80: ("SETDIBITSTODEVICE", u"Set DIB bits to device", None),
|
||||
81: ("STRETCHDIBITS", u"Stretch DIB bits", None),
|
||||
82: ("EXTCREATEFONTINDIRECTW", u"EXT create font indirect W", None),
|
||||
83: ("EXTTEXTOUTA", u"EXT text out A", None),
|
||||
84: ("EXTTEXTOUTW", u"EXT text out W", None),
|
||||
85: ("POLYBEZIER16", u"Draw poly bezier (16-bit)", None),
|
||||
86: ("POLYGON16", u"Draw polygon (16-bit)", parsePoint16array),
|
||||
87: ("POLYLINE16", u"Draw polyline (16-bit)", parsePoint16array),
|
||||
88: ("POLYBEZIERTO16", u"Draw poly bezier to (16-bit)", parsePoint16array),
|
||||
89: ("POLYLINETO16", u"Draw polyline to (16-bit)", parsePoint16array),
|
||||
90: ("POLYPOLYLINE16", u"Draw poly polyline (16-bit)", None),
|
||||
91: ("POLYPOLYGON16", u"Draw poly polygon (16-bit)", parsePoint16array),
|
||||
92: ("POLYDRAW16", u"Draw poly draw (16-bit)", None),
|
||||
93: ("CREATEMONOBRUSH", u"Create monobrush", None),
|
||||
94: ("CREATEDIBPATTERNBRUSHPT", u"Create DIB pattern brush PT", None),
|
||||
95: ("EXTCREATEPEN", u"EXT create pen", parseExtCreatePen),
|
||||
96: ("POLYTEXTOUTA", u"Poly text out A", None),
|
||||
97: ("POLYTEXTOUTW", u"Poly text out W", None),
|
||||
98: ("SETICMMODE", u"Set ICM mode", parseICMMode),
|
||||
99: ("CREATECOLORSPACE", u"Create color space", None),
|
||||
100: ("SETCOLORSPACE", u"Set color space", None),
|
||||
101: ("DELETECOLORSPACE", u"Delete color space", None),
|
||||
102: ("GLSRECORD", u"GLS record", None),
|
||||
103: ("GLSBOUNDEDRECORD", u"GLS bound ED record", None),
|
||||
104: ("PIXELFORMAT", u"Pixel format", None),
|
||||
}
|
||||
EMF_META_NAME = createDict(EMF_META, 0)
|
||||
EMF_META_DESC = createDict(EMF_META, 1)
|
||||
|
||||
class Function(FieldSet):
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
if self.root.isEMF():
|
||||
self._size = self["size"].value * 8
|
||||
else:
|
||||
self._size = self["size"].value * 16
|
||||
|
||||
def createFields(self):
|
||||
if self.root.isEMF():
|
||||
yield Enum(UInt32(self, "function"), EMF_META_NAME)
|
||||
yield UInt32(self, "size")
|
||||
try:
|
||||
parser = EMF_META[self["function"].value][2]
|
||||
except KeyError:
|
||||
parser = None
|
||||
else:
|
||||
yield UInt32(self, "size")
|
||||
yield Enum(UInt16(self, "function"), META_NAME)
|
||||
try:
|
||||
parser = META[self["function"].value][2]
|
||||
except KeyError:
|
||||
parser = None
|
||||
if parser:
|
||||
for field in parser(self):
|
||||
yield field
|
||||
else:
|
||||
size = (self.size - self.current_size) // 8
|
||||
if size:
|
||||
yield RawBytes(self, "data", size)
|
||||
|
||||
def isValid(self):
|
||||
func = self["function"]
|
||||
return func.value in func.getEnum()
|
||||
|
||||
def createDescription(self):
|
||||
if self.root.isEMF():
|
||||
return EMF_META_DESC[self["function"].value]
|
||||
try:
|
||||
return META_DESC[self["function"].value]
|
||||
except KeyError:
|
||||
return "Function %s" % self["function"].display
|
||||
|
||||
class RECT16(StaticFieldSet):
|
||||
format = (
|
||||
(Int16, "left"),
|
||||
(Int16, "top"),
|
||||
(Int16, "right"),
|
||||
(Int16, "bottom"),
|
||||
)
|
||||
def createDescription(self):
|
||||
return "%s: %ux%u at (%u,%u)" % (
|
||||
self.__class__.__name__,
|
||||
self["right"].value-self["left"].value,
|
||||
self["bottom"].value-self["top"].value,
|
||||
self["left"].value,
|
||||
self["top"].value)
|
||||
|
||||
class RECT32(RECT16):
|
||||
format = (
|
||||
(Int32, "left"),
|
||||
(Int32, "top"),
|
||||
(Int32, "right"),
|
||||
(Int32, "bottom"),
|
||||
)
|
||||
|
||||
class PlaceableHeader(FieldSet):
|
||||
"""
|
||||
Header of Placeable Metafile (file extension .APM),
|
||||
created by Aldus Corporation
|
||||
"""
|
||||
MAGIC = "\xD7\xCD\xC6\x9A\0\0" # (magic, handle=0x0000)
|
||||
|
||||
def createFields(self):
|
||||
yield textHandler(UInt32(self, "signature", "Placeable Metafiles signature (0x9AC6CDD7)"), hexadecimal)
|
||||
yield UInt16(self, "handle")
|
||||
yield RECT16(self, "rect")
|
||||
yield UInt16(self, "inch")
|
||||
yield NullBytes(self, "reserved", 4)
|
||||
yield textHandler(UInt16(self, "checksum"), hexadecimal)
|
||||
|
||||
class EMF_Header(FieldSet):
|
||||
MAGIC = "\x20\x45\x4D\x46\0\0" # (magic, min_ver=0x0000)
|
||||
def __init__(self, *args):
|
||||
FieldSet.__init__(self, *args)
|
||||
self._size = self["size"].value * 8
|
||||
|
||||
def createFields(self):
|
||||
LONG = Int32
|
||||
yield UInt32(self, "type", "Record type (always 1)")
|
||||
yield UInt32(self, "size", "Size of the header in bytes")
|
||||
yield RECT32(self, "Bounds", "Inclusive bounds")
|
||||
yield RECT32(self, "Frame", "Inclusive picture frame")
|
||||
yield textHandler(UInt32(self, "signature", "Signature ID (always 0x464D4520)"), hexadecimal)
|
||||
yield UInt16(self, "min_ver", "Minor version")
|
||||
yield UInt16(self, "maj_ver", "Major version")
|
||||
yield UInt32(self, "file_size", "Size of the file in bytes")
|
||||
yield UInt32(self, "NumOfRecords", "Number of records in the metafile")
|
||||
yield UInt16(self, "NumOfHandles", "Number of handles in the handle table")
|
||||
yield NullBytes(self, "reserved", 2)
|
||||
yield UInt32(self, "desc_size", "Size of description in 16-bit words")
|
||||
yield UInt32(self, "desc_ofst", "Offset of description string in metafile")
|
||||
yield UInt32(self, "nb_colors", "Number of color palette entries")
|
||||
yield LONG(self, "width_px", "Width of reference device in pixels")
|
||||
yield LONG(self, "height_px", "Height of reference device in pixels")
|
||||
yield LONG(self, "width_mm", "Width of reference device in millimeters")
|
||||
yield LONG(self, "height_mm", "Height of reference device in millimeters")
|
||||
|
||||
# Read description (if any)
|
||||
offset = self["desc_ofst"].value
|
||||
current = (self.absolute_address + self.current_size) // 8
|
||||
size = self["desc_size"].value * 2
|
||||
if offset == current and size:
|
||||
yield String(self, "description", size, charset="UTF-16-LE", strip="\0 ")
|
||||
|
||||
# Read padding (if any)
|
||||
size = self["size"].value - self.current_size//8
|
||||
if size:
|
||||
yield RawBytes(self, "padding", size)
|
||||
|
||||
class WMF_File(Parser):
|
||||
PARSER_TAGS = {
|
||||
"id": "wmf",
|
||||
"category": "image",
|
||||
"file_ext": ("wmf", "apm", "emf"),
|
||||
"mime": (
|
||||
u"image/wmf", u"image/x-wmf", u"image/x-win-metafile",
|
||||
u"application/x-msmetafile", u"application/wmf", u"application/x-wmf",
|
||||
u"image/x-emf"),
|
||||
"magic": (
|
||||
(PlaceableHeader.MAGIC, 0),
|
||||
(EMF_Header.MAGIC, 40*8),
|
||||
# WMF: file_type=memory, header size=9, version=3.0
|
||||
("\0\0\x09\0\0\3", 0),
|
||||
# WMF: file_type=disk, header size=9, version=3.0
|
||||
("\1\0\x09\0\0\3", 0),
|
||||
),
|
||||
"min_size": 40*8,
|
||||
"description": u"Microsoft Windows Metafile (WMF)",
|
||||
}
|
||||
endian = LITTLE_ENDIAN
|
||||
FILE_TYPE = {0: "memory", 1: "disk"}
|
||||
|
||||
def validate(self):
|
||||
if self.isEMF():
|
||||
# Check EMF header
|
||||
emf = self["emf_header"]
|
||||
if emf["signature"].value != 0x464D4520:
|
||||
return "Invalid signature"
|
||||
if emf["type"].value != 1:
|
||||
return "Invalid record type"
|
||||
if emf["reserved"].value != "\0\0":
|
||||
return "Invalid reserved"
|
||||
else:
|
||||
# Check AMF header
|
||||
if self.isAPM():
|
||||
amf = self["amf_header"]
|
||||
if amf["handle"].value != 0:
|
||||
return "Invalid handle"
|
||||
if amf["reserved"].value != "\0\0\0\0":
|
||||
return "Invalid reserved"
|
||||
|
||||
# Check common header
|
||||
if self["file_type"].value not in (0, 1):
|
||||
return "Invalid file type"
|
||||
if self["header_size"].value != 9:
|
||||
return "Invalid header size"
|
||||
if self["nb_params"].value != 0:
|
||||
return "Invalid number of parameters"
|
||||
|
||||
# Check first functions
|
||||
for index in xrange(5):
|
||||
try:
|
||||
func = self["func[%u]" % index]
|
||||
except MissingField:
|
||||
if self.done:
|
||||
return True
|
||||
return "Unable to get function #%u" % index
|
||||
except ParserError:
|
||||
return "Unable to create function #%u" % index
|
||||
|
||||
# Check first frame values
|
||||
if not func.isValid():
|
||||
return "Function #%u is invalid" % index
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
if self.isEMF():
|
||||
yield EMF_Header(self, "emf_header")
|
||||
else:
|
||||
if self.isAPM():
|
||||
yield PlaceableHeader(self, "amf_header")
|
||||
yield Enum(UInt16(self, "file_type"), self.FILE_TYPE)
|
||||
yield UInt16(self, "header_size", "Size of header in 16-bit words (always 9)")
|
||||
yield UInt8(self, "win_ver_min", "Minor version of Microsoft Windows")
|
||||
yield UInt8(self, "win_ver_maj", "Major version of Microsoft Windows")
|
||||
yield UInt32(self, "file_size", "Total size of the metafile in 16-bit words")
|
||||
yield UInt16(self, "nb_obj", "Number of objects in the file")
|
||||
yield UInt32(self, "max_record_size", "The size of largest record in 16-bit words")
|
||||
yield UInt16(self, "nb_params", "Not Used (always 0)")
|
||||
|
||||
while not(self.eof):
|
||||
yield Function(self, "func[]")
|
||||
|
||||
def isEMF(self):
|
||||
"""File is in EMF format?"""
|
||||
if 1 <= self.current_length:
|
||||
return self[0].name == "emf_header"
|
||||
if self.size < 44*8:
|
||||
return False
|
||||
magic = EMF_Header.MAGIC
|
||||
return self.stream.readBytes(40*8, len(magic)) == magic
|
||||
|
||||
def isAPM(self):
|
||||
"""File is in Aldus Placeable Metafiles format?"""
|
||||
if 1 <= self.current_length:
|
||||
return self[0].name == "amf_header"
|
||||
else:
|
||||
magic = PlaceableHeader.MAGIC
|
||||
return (self.stream.readBytes(0, len(magic)) == magic)
|
||||
|
||||
def createDescription(self):
|
||||
if self.isEMF():
|
||||
return u"Microsoft Enhanced Metafile (EMF) picture"
|
||||
elif self.isAPM():
|
||||
return u"Aldus Placeable Metafile (APM) picture"
|
||||
else:
|
||||
return u"Microsoft Windows Metafile (WMF) picture"
|
||||
|
||||
def createMimeType(self):
|
||||
if self.isEMF():
|
||||
return u"image/x-emf"
|
||||
else:
|
||||
return u"image/wmf"
|
||||
|
||||
def createContentSize(self):
|
||||
if self.isEMF():
|
||||
return None
|
||||
start = self["func[0]"].absolute_address
|
||||
end = self.stream.searchBytes("\3\0\0\0\0\0", start, MAX_FILESIZE * 8)
|
||||
if end is not None:
|
||||
return end + 6*8
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
"""
|
||||
Gimp image parser (XCF file, ".xcf" extension).
|
||||
|
||||
You can find informations about XCF file in Gimp source code. URL to read
|
||||
CVS online:
|
||||
http://cvs.gnome.org/viewcvs/gimp/app/xcf/
|
||||
\--> files xcf-read.c and xcf-load.c
|
||||
|
||||
Author: Victor Stinner
|
||||
"""
|
||||
|
||||
from hachoir_parser import Parser
|
||||
from hachoir_core.field import (StaticFieldSet, FieldSet, ParserError,
|
||||
UInt8, UInt32, Enum, Float32, String, PascalString32, RawBytes)
|
||||
from hachoir_parser.image.common import RGBA
|
||||
from hachoir_core.endian import NETWORK_ENDIAN
|
||||
|
||||
class XcfCompression(FieldSet):
|
||||
static_size = 8
|
||||
COMPRESSION_NAME = {
|
||||
0: u"None",
|
||||
1: u"RLE",
|
||||
2: u"Zlib",
|
||||
3: u"Fractal"
|
||||
}
|
||||
|
||||
def createFields(self):
|
||||
yield Enum(UInt8(self, "compression", "Compression method"), self.COMPRESSION_NAME)
|
||||
|
||||
class XcfResolution(StaticFieldSet):
|
||||
format = (
|
||||
(Float32, "xres", "X resolution in DPI"),
|
||||
(Float32, "yres", "Y resolution in DPI")
|
||||
)
|
||||
|
||||
class XcfTattoo(StaticFieldSet):
|
||||
format = ((UInt32, "tattoo", "Tattoo"),)
|
||||
|
||||
class LayerOffsets(StaticFieldSet):
|
||||
format = (
|
||||
(UInt32, "ofst_x", "Offset X"),
|
||||
(UInt32, "ofst_y", "Offset Y")
|
||||
)
|
||||
|
||||
class LayerMode(FieldSet):
|
||||
static_size = 32
|
||||
MODE_NAME = {
|
||||
0: u"Normal",
|
||||
1: u"Dissolve",
|
||||
2: u"Behind",
|
||||
3: u"Multiply",
|
||||
4: u"Screen",
|
||||
5: u"Overlay",
|
||||
6: u"Difference",
|
||||
7: u"Addition",
|
||||
8: u"Subtract",
|
||||
9: u"Darken only",
|
||||
10: u"Lighten only",
|
||||
11: u"Hue",
|
||||
12: u"Saturation",
|
||||
13: u"Color",
|
||||
14: u"Value",
|
||||
15: u"Divide",
|
||||
16: u"Dodge",
|
||||
17: u"Burn",
|
||||
18: u"Hard light",
|
||||
19: u"Soft light",
|
||||
20: u"Grain extract",
|
||||
21: u"Grain merge",
|
||||
22: u"Color erase"
|
||||
}
|
||||
|
||||
def createFields(self):
|
||||
yield Enum(UInt32(self, "mode", "Layer mode"), self.MODE_NAME)
|
||||
|
||||
class GimpBoolean(UInt32):
|
||||
def __init__(self, parent, name):
|
||||
UInt32.__init__(self, parent, name)
|
||||
|
||||
def createValue(self):
|
||||
return 1 == UInt32.createValue(self)
|
||||
|
||||
class XcfUnit(StaticFieldSet):
|
||||
format = ((UInt32, "unit", "Unit"),)
|
||||
|
||||
class XcfParasiteEntry(FieldSet):
|
||||
def createFields(self):
|
||||
yield PascalString32(self, "name", "Name", strip="\0", charset="UTF-8")
|
||||
yield UInt32(self, "flags", "Flags")
|
||||
yield PascalString32(self, "data", "Data", strip=" \0", charset="UTF-8")
|
||||
|
||||
class XcfLevel(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "width", "Width in pixel")
|
||||
yield UInt32(self, "height", "Height in pixel")
|
||||
yield UInt32(self, "offset", "Offset")
|
||||
offset = self["offset"].value
|
||||
if offset == 0:
|
||||
return
|
||||
data_offsets = []
|
||||
while (self.absolute_address + self.current_size)/8 < offset:
|
||||
chunk = UInt32(self, "data_offset[]", "Data offset")
|
||||
yield chunk
|
||||
if chunk.value == 0:
|
||||
break
|
||||
data_offsets.append(chunk)
|
||||
if (self.absolute_address + self.current_size)/8 != offset:
|
||||
raise ParserError("Problem with level offset.")
|
||||
previous = offset
|
||||
for chunk in data_offsets:
|
||||
data_offset = chunk.value
|
||||
size = data_offset - previous
|
||||
yield RawBytes(self, "data[]", size, "Data content of %s" % chunk.name)
|
||||
previous = data_offset
|
||||
|
||||
class XcfHierarchy(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "width", "Width")
|
||||
yield UInt32(self, "height", "Height")
|
||||
yield UInt32(self, "bpp", "Bits/pixel")
|
||||
|
||||
offsets = []
|
||||
while True:
|
||||
chunk = UInt32(self, "offset[]", "Level offset")
|
||||
yield chunk
|
||||
if chunk.value == 0:
|
||||
break
|
||||
offsets.append(chunk.value)
|
||||
for offset in offsets:
|
||||
padding = self.seekByte(offset, relative=False)
|
||||
if padding is not None:
|
||||
yield padding
|
||||
yield XcfLevel(self, "level[]", "Level")
|
||||
# yield XcfChannel(self, "channel[]", "Channel"))
|
||||
|
||||
class XcfChannel(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "width", "Channel width")
|
||||
yield UInt32(self, "height", "Channel height")
|
||||
yield PascalString32(self, "name", "Channel name", strip="\0", charset="UTF-8")
|
||||
for field in readProperties(self):
|
||||
yield field
|
||||
yield UInt32(self, "hierarchy_ofs", "Hierarchy offset")
|
||||
yield XcfHierarchy(self, "hierarchy", "Hierarchy")
|
||||
|
||||
def createDescription(self):
|
||||
return 'Channel "%s"' % self["name"].value
|
||||
|
||||
class XcfLayer(FieldSet):
|
||||
def createFields(self):
|
||||
yield UInt32(self, "width", "Layer width in pixels")
|
||||
yield UInt32(self, "height", "Layer height in pixels")
|
||||
yield Enum(UInt32(self, "type", "Layer type"), XcfFile.IMAGE_TYPE_NAME)
|
||||
yield PascalString32(self, "name", "Layer name", strip="\0", charset="UTF-8")
|
||||
for prop in readProperties(self):
|
||||
yield prop
|
||||
|
||||
# --
|
||||
# TODO: Hack for Gimp 1.2 files
|
||||
# --
|
||||
|
||||
yield UInt32(self, "hierarchy_ofs", "Hierarchy offset")
|
||||
yield UInt32(self, "mask_ofs", "Layer mask offset")
|
||||
padding = self.seekByte(self["hierarchy_ofs"].value, relative=False)
|
||||
if padding is not None:
|
||||
yield padding
|
||||
yield XcfHierarchy(self, "hierarchy", "Hierarchy")
|
||||
# TODO: Read layer mask if needed: self["mask_ofs"].value != 0
|
||||
|
||||
def createDescription(self):
|
||||
return 'Layer "%s"' % self["name"].value
|
||||
|
||||
class XcfParasites(FieldSet):
|
||||
def createFields(self):
|
||||
size = self["../size"].value * 8
|
||||
while self.current_size < size:
|
||||
yield XcfParasiteEntry(self, "parasite[]", "Parasite")
|
||||
|
||||
class XcfProperty(FieldSet):
|
||||
PROP_COMPRESSION = 17
|
||||
PROP_RESOLUTION = 19
|
||||
PROP_PARASITES = 21
|
||||
TYPE_NAME = {
|
||||
0: u"End",
|
||||
1: u"Colormap",
|
||||
2: u"Active layer",
|
||||
3: u"Active channel",
|
||||
4: u"Selection",
|
||||
5: u"Floating selection",
|
||||
6: u"Opacity",
|
||||
7: u"Mode",
|
||||
8: u"Visible",
|
||||
9: u"Linked",
|
||||
10: u"Lock alpha",
|
||||
11: u"Apply mask",
|
||||
12: u"Edit mask",
|
||||
13: u"Show mask",
|
||||
14: u"Show masked",
|
||||
15: u"Offsets",
|
||||
16: u"Color",
|
||||
17: u"Compression",
|
||||
18: u"Guides",
|
||||
19: u"Resolution",
|
||||
20: u"Tattoo",
|
||||
21: u"Parasites",
|
||||
22: u"Unit",
|
||||
23: u"Paths",
|
||||
24: u"User unit",
|
||||
25: u"Vectors",
|
||||
26: u"Text layer flags",
|
||||
}
|
||||
|
||||
handler = {
|
||||
6: RGBA,
|
||||
7: LayerMode,
|
||||
8: GimpBoolean,
|
||||
9: GimpBoolean,
|
||||
10: GimpBoolean,
|
||||
11: GimpBoolean,
|
||||
12: GimpBoolean,
|
||||
13: GimpBoolean,
|
||||
15: LayerOffsets,
|
||||
17: XcfCompression,
|
||||
19: XcfResolution,
|
||||
20: XcfTattoo,
|
||||
21: XcfParasites,
|
||||
22: XcfUnit
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
FieldSet.__init__(self, *args, **kw)
|
||||
self._size = (8 + self["size"].value) * 8
|
||||
|
||||
def createFields(self):
|
||||
yield Enum(UInt32(self, "type", "Property type"), self.TYPE_NAME)
|
||||
yield UInt32(self, "size", "Property size")
|
||||
|
||||
size = self["size"].value
|
||||
if 0 < size:
|
||||
cls = self.handler.get(self["type"].value, None)
|
||||
if cls:
|
||||
yield cls(self, "data", size=size*8)
|
||||
else:
|
||||
yield RawBytes(self, "data", size, "Data")
|
||||
|
||||
def createDescription(self):
|
||||
return "Property: %s" % self["type"].display
|
||||
|
||||
def readProperties(parser):
|
||||
while True:
|
||||
prop = XcfProperty(parser, "property[]")
|
||||
yield prop
|
||||
if prop["type"].value == 0:
|
||||
return
|
||||
|
||||
class XcfFile(Parser):
|
||||
PARSER_TAGS = {
|
||||
"id": "xcf",
|
||||
"category": "image",
|
||||
"file_ext": ("xcf",),
|
||||
"mime": (u"image/x-xcf", u"application/x-gimp-image"),
|
||||
"min_size": (26 + 8 + 4 + 4)*8, # header+empty property+layer offset+channel offset
|
||||
"magic": (
|
||||
('gimp xcf file\0', 0),
|
||||
('gimp xcf v002\0', 0),
|
||||
),
|
||||
"description": "Gimp (XCF) picture"
|
||||
}
|
||||
endian = NETWORK_ENDIAN
|
||||
IMAGE_TYPE_NAME = {
|
||||
0: u"RGB",
|
||||
1: u"Gray",
|
||||
2: u"Indexed"
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
if self.stream.readBytes(0, 14) not in ('gimp xcf file\0', 'gimp xcf v002\0'):
|
||||
return "Wrong signature"
|
||||
return True
|
||||
|
||||
def createFields(self):
|
||||
# Read signature
|
||||
yield String(self, "signature", 14, "Gimp picture signature (ends with nul byte)", charset="ASCII")
|
||||
|
||||
# Read image general informations (width, height, type)
|
||||
yield UInt32(self, "width", "Image width")
|
||||
yield UInt32(self, "height", "Image height")
|
||||
yield Enum(UInt32(self, "type", "Image type"), self.IMAGE_TYPE_NAME)
|
||||
for prop in readProperties(self):
|
||||
yield prop
|
||||
|
||||
# Read layer offsets
|
||||
layer_offsets = []
|
||||
while True:
|
||||
chunk = UInt32(self, "layer_offset[]", "Layer offset")
|
||||
yield chunk
|
||||
if chunk.value == 0:
|
||||
break
|
||||
layer_offsets.append(chunk.value)
|
||||
|
||||
# Read channel offsets
|
||||
channel_offsets = []
|
||||
while True:
|
||||
chunk = UInt32(self, "channel_offset[]", "Channel offset")
|
||||
yield chunk
|
||||
if chunk.value == 0:
|
||||
break
|
||||
channel_offsets.append(chunk.value)
|
||||
|
||||
# Read layers
|
||||
for index, offset in enumerate(layer_offsets):
|
||||
if index+1 < len(layer_offsets):
|
||||
size = (layer_offsets[index+1] - offset) * 8
|
||||
else:
|
||||
size = None
|
||||
padding = self.seekByte(offset, relative=False)
|
||||
if padding:
|
||||
yield padding
|
||||
yield XcfLayer(self, "layer[]", size=size)
|
||||
|
||||
# Read channels
|
||||
for index, offset in enumerate(channel_offsets):
|
||||
if index+1 < len(channel_offsets):
|
||||
size = (channel_offsets[index+1] - offset) * 8
|
||||
else:
|
||||
size = None
|
||||
padding = self.seekByte(offset, relative=False)
|
||||
if padding is not None:
|
||||
yield padding
|
||||
yield XcfChannel(self, "channel[]", "Channel", size=size)
|
||||
|
||||
Reference in New Issue
Block a user