Remove submodule, just put Dependencies in ./libs

This commit is contained in:
Ruud
2011-02-28 15:25:42 +01:00
parent 2fb578d4de
commit 6888798ef6
541 changed files with 173394 additions and 4 deletions
+12
View File
@@ -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
+195
View File
@@ -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
+49
View File
@@ -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)
+361
View File
@@ -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
+227
View File
@@ -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
+139
View File
@@ -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
+113
View File
@@ -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)
+368
View File
@@ -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
+73
View File
@@ -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)
+268
View File
@@ -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
+85
View File
@@ -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)
+85
View File
@@ -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)
+211
View File
@@ -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
+611
View File
@@ -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
+331
View File
@@ -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)