mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-01 08:53:37 +00:00
299 lines
10 KiB
Python
299 lines
10 KiB
Python
from hachoir_metadata.metadata import (registerExtractor,
|
|
Metadata, RootMetadata, MultipleMetadata)
|
|
from hachoir_parser.image import (
|
|
BmpFile, IcoFile, PcxFile, GifFile, PngFile, TiffFile,
|
|
XcfFile, TargaFile, WMF_File, PsdFile)
|
|
from hachoir_parser.image.png import getBitsPerPixel as pngBitsPerPixel
|
|
from hachoir_parser.image.xcf import XcfProperty
|
|
from hachoir_core.i18n import _
|
|
from hachoir_metadata.safe import fault_tolerant
|
|
|
|
def computeComprRate(meta, compr_size):
|
|
"""
|
|
Compute image compression rate. Skip size of color palette, focus on
|
|
image pixels. Original size is width x height x bpp. Compressed size
|
|
is an argument (in bits).
|
|
|
|
Set "compr_data" with a string like "1.52x".
|
|
"""
|
|
if not meta.has("width") \
|
|
or not meta.has("height") \
|
|
or not meta.has("bits_per_pixel"):
|
|
return
|
|
if not compr_size:
|
|
return
|
|
orig_size = meta.get('width') * meta.get('height') * meta.get('bits_per_pixel')
|
|
meta.compr_rate = float(orig_size) / compr_size
|
|
|
|
class BmpMetadata(RootMetadata):
|
|
def extract(self, image):
|
|
if "header" not in image:
|
|
return
|
|
hdr = image["header"]
|
|
self.width = hdr["width"].value
|
|
self.height = hdr["height"].value
|
|
bpp = hdr["bpp"].value
|
|
if bpp:
|
|
if bpp <= 8 and "used_colors" in hdr:
|
|
self.nb_colors = hdr["used_colors"].value
|
|
self.bits_per_pixel = bpp
|
|
self.compression = hdr["compression"].display
|
|
self.format_version = u"Microsoft Bitmap version %s" % hdr.getFormatVersion()
|
|
|
|
self.width_dpi = hdr["horizontal_dpi"].value
|
|
self.height_dpi = hdr["vertical_dpi"].value
|
|
|
|
if "pixels" in image:
|
|
computeComprRate(self, image["pixels"].size)
|
|
|
|
class TiffMetadata(RootMetadata):
|
|
key_to_attr = {
|
|
"img_width": "width",
|
|
"img_height": "width",
|
|
|
|
# TODO: Enable that (need link to value)
|
|
# "description": "comment",
|
|
# "doc_name": "title",
|
|
# "orientation": "image_orientation",
|
|
}
|
|
def extract(self, tiff):
|
|
if "ifd" in tiff:
|
|
self.useIFD(tiff["ifd"])
|
|
|
|
def useIFD(self, ifd):
|
|
for field in ifd:
|
|
try:
|
|
attrname = self.key_to_attr[field.name]
|
|
except KeyError:
|
|
continue
|
|
if "value" not in field:
|
|
continue
|
|
value = field["value"].value
|
|
setattr(self, attrname, value)
|
|
|
|
class IcoMetadata(MultipleMetadata):
|
|
color_to_bpp = {
|
|
2: 1,
|
|
16: 4,
|
|
256: 8
|
|
}
|
|
|
|
def extract(self, icon):
|
|
for index, header in enumerate(icon.array("icon_header")):
|
|
image = Metadata(self)
|
|
|
|
# Read size and colors from header
|
|
image.width = header["width"].value
|
|
image.height = header["height"].value
|
|
bpp = header["bpp"].value
|
|
nb_colors = header["nb_color"].value
|
|
if nb_colors != 0:
|
|
image.nb_colors = nb_colors
|
|
if bpp == 0 and nb_colors in self.color_to_bpp:
|
|
bpp = self.color_to_bpp[nb_colors]
|
|
elif bpp == 0:
|
|
bpp = 8
|
|
image.bits_per_pixel = bpp
|
|
image.setHeader(_("Icon #%u (%sx%s)")
|
|
% (1+index, image.get("width", "?"), image.get("height", "?")))
|
|
|
|
# Read compression from data (if available)
|
|
key = "icon_data[%u]/header/codec" % index
|
|
if key in icon:
|
|
image.compression = icon[key].display
|
|
key = "icon_data[%u]/pixels" % index
|
|
if key in icon:
|
|
computeComprRate(image, icon[key].size)
|
|
|
|
# Store new image
|
|
self.addGroup("image[%u]" % index, image)
|
|
|
|
class PcxMetadata(RootMetadata):
|
|
@fault_tolerant
|
|
def extract(self, pcx):
|
|
self.width = 1 + pcx["xmax"].value
|
|
self.height = 1 + pcx["ymax"].value
|
|
self.width_dpi = pcx["horiz_dpi"].value
|
|
self.height_dpi = pcx["vert_dpi"].value
|
|
self.bits_per_pixel = pcx["bpp"].value
|
|
if 1 <= pcx["bpp"].value <= 8:
|
|
self.nb_colors = 2 ** pcx["bpp"].value
|
|
self.compression = _("Run-length encoding (RLE)")
|
|
self.format_version = "PCX: %s" % pcx["version"].display
|
|
if "image_data" in pcx:
|
|
computeComprRate(self, pcx["image_data"].size)
|
|
|
|
class XcfMetadata(RootMetadata):
|
|
# Map image type to bits/pixel
|
|
TYPE_TO_BPP = {0: 24, 1: 8, 2: 8}
|
|
|
|
def extract(self, xcf):
|
|
self.width = xcf["width"].value
|
|
self.height = xcf["height"].value
|
|
try:
|
|
self.bits_per_pixel = self.TYPE_TO_BPP[ xcf["type"].value ]
|
|
except KeyError:
|
|
pass
|
|
self.format_version = xcf["type"].display
|
|
self.readProperties(xcf)
|
|
|
|
@fault_tolerant
|
|
def processProperty(self, prop):
|
|
type = prop["type"].value
|
|
if type == XcfProperty.PROP_PARASITES:
|
|
for field in prop["data"]:
|
|
if "name" not in field or "data" not in field:
|
|
continue
|
|
if field["name"].value == "gimp-comment":
|
|
self.comment = field["data"].value
|
|
elif type == XcfProperty.PROP_COMPRESSION:
|
|
self.compression = prop["data/compression"].display
|
|
elif type == XcfProperty.PROP_RESOLUTION:
|
|
self.width_dpi = int(prop["data/xres"].value)
|
|
self.height_dpi = int(prop["data/yres"].value)
|
|
|
|
def readProperties(self, xcf):
|
|
for prop in xcf.array("property"):
|
|
self.processProperty(prop)
|
|
|
|
class PngMetadata(RootMetadata):
|
|
TEXT_TO_ATTR = {
|
|
"software": "producer",
|
|
}
|
|
|
|
def extract(self, png):
|
|
if "header" in png:
|
|
self.useHeader(png["header"])
|
|
if "time" in png:
|
|
self.useTime(png["time"])
|
|
if "physical" in png:
|
|
self.usePhysical(png["physical"])
|
|
for comment in png.array("text"):
|
|
if "text" not in comment:
|
|
continue
|
|
keyword = comment["keyword"].value
|
|
text = comment["text"].value
|
|
try:
|
|
key = self.TEXT_TO_ATTR[keyword.lower()]
|
|
setattr(self, key, text)
|
|
except KeyError:
|
|
if keyword.lower() != "comment":
|
|
self.comment = "%s=%s" % (keyword, text)
|
|
else:
|
|
self.comment = text
|
|
compr_size = sum( data.size for data in png.array("data") )
|
|
computeComprRate(self, compr_size)
|
|
|
|
@fault_tolerant
|
|
def useTime(self, field):
|
|
self.creation_date = field.value
|
|
|
|
@fault_tolerant
|
|
def usePhysical(self, field):
|
|
self.width_dpi = field["pixel_per_unit_x"].value
|
|
self.height_dpi = field["pixel_per_unit_y"].value
|
|
|
|
@fault_tolerant
|
|
def useHeader(self, header):
|
|
self.width = header["width"].value
|
|
self.height = header["height"].value
|
|
|
|
# Read number of colors and pixel format
|
|
if "/palette/size" in header:
|
|
nb_colors = header["/palette/size"].value // 3
|
|
else:
|
|
nb_colors = None
|
|
if not header["has_palette"].value:
|
|
if header["has_alpha"].value:
|
|
self.pixel_format = _("RGBA")
|
|
else:
|
|
self.pixel_format = _("RGB")
|
|
elif "/transparency" in header:
|
|
self.pixel_format = _("Color index with transparency")
|
|
if nb_colors:
|
|
nb_colors -= 1
|
|
else:
|
|
self.pixel_format = _("Color index")
|
|
self.bits_per_pixel = pngBitsPerPixel(header)
|
|
if nb_colors:
|
|
self.nb_colors = nb_colors
|
|
|
|
# Read compression, timestamp, etc.
|
|
self.compression = header["compression"].display
|
|
|
|
class GifMetadata(RootMetadata):
|
|
def extract(self, gif):
|
|
self.useScreen(gif["/screen"])
|
|
if self.has("bits_per_pixel"):
|
|
self.nb_colors = (1 << self.get('bits_per_pixel'))
|
|
self.compression = _("LZW")
|
|
self.format_version = "GIF version %s" % gif["version"].value
|
|
for comments in gif.array("comments"):
|
|
for comment in gif.array(comments.name + "/comment"):
|
|
self.comment = comment.value
|
|
if "graphic_ctl/has_transp" in gif and gif["graphic_ctl/has_transp"].value:
|
|
self.pixel_format = _("Color index with transparency")
|
|
else:
|
|
self.pixel_format = _("Color index")
|
|
|
|
@fault_tolerant
|
|
def useScreen(self, screen):
|
|
self.width = screen["width"].value
|
|
self.height = screen["height"].value
|
|
self.bits_per_pixel = (1 + screen["size_global_map"].value)
|
|
|
|
class TargaMetadata(RootMetadata):
|
|
def extract(self, tga):
|
|
self.width = tga["width"].value
|
|
self.height = tga["height"].value
|
|
self.bits_per_pixel = tga["bpp"].value
|
|
if tga["nb_color"].value:
|
|
self.nb_colors = tga["nb_color"].value
|
|
self.compression = tga["codec"].display
|
|
if "pixels" in tga:
|
|
computeComprRate(self, tga["pixels"].size)
|
|
|
|
class WmfMetadata(RootMetadata):
|
|
def extract(self, wmf):
|
|
if wmf.isAPM():
|
|
if "amf_header/rect" in wmf:
|
|
rect = wmf["amf_header/rect"]
|
|
self.width = (rect["right"].value - rect["left"].value)
|
|
self.height = (rect["bottom"].value - rect["top"].value)
|
|
self.bits_per_pixel = 24
|
|
elif wmf.isEMF():
|
|
emf = wmf["emf_header"]
|
|
if "description" in emf:
|
|
desc = emf["description"].value
|
|
if "\0" in desc:
|
|
self.producer, self.title = desc.split("\0", 1)
|
|
else:
|
|
self.producer = desc
|
|
if emf["nb_colors"].value:
|
|
self.nb_colors = emf["nb_colors"].value
|
|
self.bits_per_pixel = 8
|
|
else:
|
|
self.bits_per_pixel = 24
|
|
self.width = emf["width_px"].value
|
|
self.height = emf["height_px"].value
|
|
|
|
class PsdMetadata(RootMetadata):
|
|
@fault_tolerant
|
|
def extract(self, psd):
|
|
self.width = psd["width"].value
|
|
self.height = psd["height"].value
|
|
self.bits_per_pixel = psd["depth"].value * psd["nb_channels"].value
|
|
self.pixel_format = psd["color_mode"].display
|
|
self.compression = psd["compression"].display
|
|
|
|
registerExtractor(IcoFile, IcoMetadata)
|
|
registerExtractor(GifFile, GifMetadata)
|
|
registerExtractor(XcfFile, XcfMetadata)
|
|
registerExtractor(TargaFile, TargaMetadata)
|
|
registerExtractor(PcxFile, PcxMetadata)
|
|
registerExtractor(BmpFile, BmpMetadata)
|
|
registerExtractor(PngFile, PngMetadata)
|
|
registerExtractor(TiffFile, TiffMetadata)
|
|
registerExtractor(WMF_File, WmfMetadata)
|
|
registerExtractor(PsdFile, PsdMetadata)
|
|
|