def setFilename(self, filename, append=True): """ Use a file to store all messages. The UTF-8 encoding will be used. Write an informative message if the file can't be created. @param filename: C{L{string}} """ # Look if file already exists or not filename = os.path.expanduser(filename) filename = os.path.realpath(filename) append = os.access(filename, os.F_OK) # Create log file (or open it in append mode, if it already exists) try: import codecs if append: self.__file = codecs.open(filename, "a", "utf-8") else: self.__file = codecs.open(filename, "w", "utf-8") self._writeIntoFile(_("Starting Hachoir")) except IOError, err: if err.errno == 2: self.__file = None self.info( _("[Log] setFilename(%s) fails: no such file") % filename) else: raise
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
def getHachoirOptions(parser): """ Create an option group (type optparse.OptionGroup) of Hachoir library options. """ def setLogFilename(*args): log.setFilename(args[2]) common = OptionGroup(parser, _("Hachoir library"), \ "Configure Hachoir library") common.add_option("--verbose", help=_("Verbose mode"), default=False, action="store_true") common.add_option("--log", help=_("Write log in a file"), type="string", action="callback", callback=setLogFilename) common.add_option("--quiet", help=_("Quiet mode (don't display warning)"), default=False, action="store_true") common.add_option("--debug", help=_("Debug mode"), default=False, action="store_true") return common
def setFilename(self, filename, append=True): """ Use a file to store all messages. The UTF-8 encoding will be used. Write an informative message if the file can't be created. @param filename: C{L{string}} """ # Look if file already exists or not filename = os.path.expanduser(filename) filename = os.path.realpath(filename) append = os.access(filename, os.F_OK) # Create log file (or open it in append mode, if it already exists) try: import codecs if append: self.__file = codecs.open(filename, "a", "utf-8") else: self.__file = codecs.open(filename, "w", "utf-8") self._writeIntoFile(_("Starting Hachoir")) except IOError, err: if err.errno == 2: self.__file = None self.info(_("[Log] setFilename(%s) fails: no such file") % filename) else: raise
def __init__(self, size, address, got=None): self.size = size self.address = address self.got = got if self.got is not None: msg = _("Can't read %u bits at address %u (got %u bits)") % (self.size, self.address, self.got) else: msg = _("Can't read %u bits at address %u") % (self.size, self.address) InputStreamError.__init__(self, msg)
def __init__(self, size, address, got=None): self.size = size self.address = address self.got = got if self.got is not None: msg = _("Can't read %u bits at address %u (got %u bits)") % ( self.size, self.address, self.got) else: msg = _("Can't read %u bits at address %u") % (self.size, self.address) InputStreamError.__init__(self, msg)
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")
def extract(self, tar): max_nb = maxNbFile(self) for index, field in enumerate(tar.array("file")): if max_nb is not None and max_nb <= index: self.warning("TAR archive contains many files, but only first %s files are processed" % max_nb) break meta = Metadata(self) self.extractFile(field, meta) if meta.has("filename"): title = _('File "%s"') % meta.getText('filename') else: title = _("File") self.addGroup(field.name, meta, title)
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")
def useFile(self, field): meta = Metadata(self) meta.filename = field["filename"].value meta.file_size = field["filesize"].value meta.creation_date = field["timestamp"].value attr = field["attributes"].value if attr != "(none)": meta.file_attr = attr if meta.has("filename"): title = _("File \"%s\"") % meta.getText('filename') else: title = _("File") self.addGroup(field.name, meta, title)
def __init__(self, stream, **args): validate = args.pop("validate", False) self._mime_type = None while validate: nbits = self.getParserTags()["min_size"] if stream.sizeGe(nbits): res = self.validate() if res is True: break res = makeUnicode(res) else: res = _("stream is smaller than %s.%s bytes" % divmod(nbits, 8)) raise ValidateError(res or _("no reason given")) self._autofix = True
def startOfFrame(self, sof): # Set compression method key = sof["../type"].value self.compression = "JPEG (%s)" % JpegChunk.START_OF_FRAME[key] # Read image size and bits/pixel self.width = sof["width"].value self.height = sof["height"].value nb_components = sof["nr_components"].value self.bits_per_pixel = 8 * nb_components if nb_components == 3: self.pixel_format = _("YCbCr") elif nb_components == 1: self.pixel_format = _("Grayscale") self.nb_colors = 256
def extractAVI(self, headers): audio_index = 1 for stream in headers.array("stream"): if "stream_hdr/stream_type" not in stream: continue stream_type = stream["stream_hdr/stream_type"].value if stream_type == "vids": if "stream_hdr" in stream: meta = Metadata(self) self.extractAVIVideo(stream["stream_hdr"], meta) self.addGroup("video", meta, "Video stream") elif stream_type == "auds": if "stream_fmt" in stream: meta = Metadata(self) self.extractAVIAudio(stream["stream_fmt"], meta) self.addGroup("audio[%u]" % audio_index, meta, "Audio stream") audio_index += 1 if "avi_hdr" in headers: self.useAviHeader(headers["avi_hdr"]) # Compute global bit rate if self.has("duration") and "/movie/size" in headers: self.bit_rate = float( headers["/movie/size"].value) * 8 / timedelta2seconds( self.get('duration')) # Video has index? if "/index" in headers: self.comment = _("Has audio/video index (%s)") \ % humanFilesize(headers["/index"].size/8)
def computeBitrate(self, frame): bit_rate = frame.getBitRate() # may returns None on error if not bit_rate: return self.bit_rate = (bit_rate, _("%s (constant)") % humanBitRate(bit_rate)) self.duration = timedelta(seconds=float(frame["/frames"].size) / bit_rate)
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)
def __delitem__(self, index): """ Delete item at position index. May raise IndexError. >>> d=Dict( ((6, 'six'), (9, 'neuf'), (4, 'quatre')) ) >>> del d[1] >>> d {6: 'six', 4: 'quatre'} """ if index < 0: index += len(self._value_list) if not (0 <= index < len(self._value_list)): raise IndexError(_("list assignment index out of range (%s/%s)") % (index, len(self._value_list))) del self._value_list[index] del self._key_list[index] # First loop which may alter self._index for key, item_index in self._index.iteritems(): if item_index == index: del self._index[key] break # Second loop update indexes for key, item_index in self._index.iteritems(): if index < item_index: self._index[key] -= 1
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)
def extractAVI(self, headers): audio_index = 1 for stream in headers.array("stream"): if "stream_hdr/stream_type" not in stream: continue stream_type = stream["stream_hdr/stream_type"].value if stream_type == "vids": if "stream_hdr" in stream: meta = Metadata(self) self.extractAVIVideo(stream["stream_hdr"], meta) self.addGroup("video", meta, "Video stream") elif stream_type == "auds": if "stream_fmt" in stream: meta = Metadata(self) self.extractAVIAudio(stream["stream_fmt"], meta) self.addGroup("audio[%u]" % audio_index, meta, "Audio stream") audio_index += 1 if "avi_hdr" in headers: self.useAviHeader(headers["avi_hdr"]) # Compute global bit rate if self.has("duration") and "/movie/size" in headers: self.bit_rate = float(headers["/movie/size"].value) * 8 / timedelta2seconds(self.get("duration")) # Video has index? if "/index" in headers: self.comment = _("Has audio/video index (%s)") % humanFilesize(headers["/index"].size / 8)
def computeVariableBitrate(self, mp3): if self.quality <= QUALITY_FAST: return count = 0 if QUALITY_BEST <= self.quality: self.warning( "Process all MPEG audio frames to compute exact duration") max_count = None else: max_count = 500 * self.quality total_bit_rate = 0.0 for index, frame in enumerate(mp3.array("frames/frame")): if index < 3: continue bit_rate = frame.getBitRate() if bit_rate: total_bit_rate += float(bit_rate) count += 1 if max_count and max_count <= count: break if not count: return bit_rate = total_bit_rate / count self.bit_rate = (bit_rate, _("%s (Variable bit rate)") % humanBitRate(bit_rate)) duration = timedelta(seconds=float(mp3["frames"].size) / bit_rate) self.duration = duration
def displayStat(self, stat): """ Display statistics to stdout: - best time (minimum) - average time (arithmetic average) - worst time (maximum) - total time (sum) Use arithmetic avertage instead of geometric average because geometric fails if any value is zero (returns zero) and also because floating point multiplication lose precision with many values. """ average = stat.getSum() / len(stat) values = (stat.getMin(), average, stat.getMax(), stat.getSum()) values = tuple(self.formatTime(value) for value in values) print _("Benchmark: best=%s average=%s worst=%s total=%s") \ % values
def append(self, key, value): """ Append new value """ if key in self._index: raise UniqKeyError(_("Key '%s' already exists") % key) self._index[key] = len(self._value_list) self._key_list.append(key) self._value_list.append(value)
def __setattr__(self, key, value): """ Add a new value to data with name 'key'. Skip duplicates. """ # Invalid key? if key not in self.__data: raise KeyError(_("%s has no metadata '%s'") % (self.__class__.__name__, key)) # Skip duplicates self.__data[key].add(value)
def __setattr__(self, key, value): """ Add a new value to data with name 'key'. Skip duplicates. """ # Invalid key? if key not in self.__data: raise KeyError( _("%s has no metadata '%s'") % (self.__class__.__name__, key)) # Skip duplicates self.__data[key].add(value)
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)
def insert(self, index, key, value): """ Insert an item at specified position index. >>> d=Dict( ((6, 'six'), (9, 'neuf'), (4, 'quatre')) ) >>> d.insert(1, '40', 'quarante') >>> d {6: 'six', '40': 'quarante', 9: 'neuf', 4: 'quatre'} """ if key in self: raise UniqKeyError(_("Insert error: key '%s' ready exists") % key) _index = index if index < 0: index += len(self._value_list) if not(0 <= index <= len(self._value_list)): raise IndexError(_("Insert error: index '%s' is invalid") % _index) for item_key, item_index in self._index.iteritems(): if item_index >= index: self._index[item_key] += 1 self._index[key] = index self._key_list.insert(index, key) self._value_list.insert(index, value)
def humanFilesize(size): """ Convert a file size in byte to human natural representation. It uses the values: 1 KB is 1024 bytes, 1 MB is 1024 KB, etc. The result is an unicode string. >>> humanFilesize(1) u'1 byte' >>> humanFilesize(790) u'790 bytes' >>> humanFilesize(256960) u'250.9 KB' """ if size < 10000: return ngettext("%u byte", "%u bytes", size) % size units = [_("KB"), _("MB"), _("GB"), _("TB")] size = float(size) divisor = 1024 for unit in units: size = size / divisor if size < divisor: return "%.1f %s" % (size, unit) return "%u %s" % (size, unit)
def timestampMac32(value): """ Convert an Mac (32-bit) timestamp to string. The format is the number of seconds since the 1st January 1904 (to 2040). Returns unicode string. >>> timestampMac32(0) datetime.datetime(1904, 1, 1, 0, 0) >>> timestampMac32(2843043290) datetime.datetime(1994, 2, 2, 14, 14, 50) """ if not isinstance(value, (float, int, long)): raise TypeError("an integer or float is required") if not(0 <= value <= 4294967295): return _("invalid Mac timestamp (%s)") % value return MAC_TIMESTAMP_T0 + timedelta(seconds=value)
def timestampMac32(value): """ Convert an Mac (32-bit) timestamp to string. The format is the number of seconds since the 1st January 1904 (to 2040). Returns unicode string. >>> timestampMac32(0) datetime.datetime(1904, 1, 1, 0, 0) >>> timestampMac32(2843043290) datetime.datetime(1994, 2, 2, 14, 14, 50) """ if not isinstance(value, (float, int, long)): raise TypeError("an integer or float is required") if not (0 <= value <= 4294967295): return _("invalid Mac timestamp (%s)") % value return MAC_TIMESTAMP_T0 + timedelta(seconds=value)
def timestampWin64(value): """ Convert Windows 64-bit timestamp to string. The timestamp format is a 64-bit number which represents number of 100ns since the 1st January 1601 at 00:00. Result is an unicode string. See also durationWin64(). Maximum date is 28 may 60056. >>> timestampWin64(0) datetime.datetime(1601, 1, 1, 0, 0) >>> timestampWin64(127840491566710000) datetime.datetime(2006, 2, 10, 12, 45, 56, 671000) """ try: return WIN64_TIMESTAMP_T0 + durationWin64(value) except OverflowError: raise ValueError(_("date newer than year %s (value=%s)") % (MAXYEAR, value))
def timestampWin64(value): """ Convert Windows 64-bit timestamp to string. The timestamp format is a 64-bit number which represents number of 100ns since the 1st January 1601 at 00:00. Result is an unicode string. See also durationWin64(). Maximum date is 28 may 60056. >>> timestampWin64(0) datetime.datetime(1601, 1, 1, 0, 0) >>> timestampWin64(127840491566710000) datetime.datetime(2006, 2, 10, 12, 45, 56, 671000) """ try: return WIN64_TIMESTAMP_T0 + durationWin64(value) except OverflowError: raise ValueError( _("date newer than year %s (value=%s)") % (MAXYEAR, value))
def FileInputStream(filename, real_filename=None, **args): """ Create an input stream of a file. filename must be unicode. real_filename is an optional argument used to specify the real filename, its type can be 'str' or 'unicode'. Use real_filename when you are not able to convert filename to real unicode string (ie. you have to use unicode(name, 'replace') or unicode(name, 'ignore')). """ assert isinstance(filename, unicode) if not real_filename: real_filename = filename try: inputio = open(real_filename, "rb") except IOError, err: charset = getTerminalCharset() errmsg = unicode(str(err), charset) raise InputStreamError(_("Unable to open file %s: %s") % (filename, errmsg))
def _get(self, index): if index >= len(self.buffers): return '' buf = self.buffers[index] if buf is None: raise InputStreamError(_("Error: Buffers too small. Can't seek backward.")) if self.last != index: next = buf[1] prev = buf[2] self.buffers[next][2] = prev self.buffers[prev][1] = next first = self.buffers[self.last][1] buf[1] = first buf[2] = self.last self.buffers[first][2] = index self.buffers[self.last][1] = index self.last = index return buf[0]
def FileInputStream(filename, real_filename=None, **args): """ Create an input stream of a file. filename must be unicode. real_filename is an optional argument used to specify the real filename, its type can be 'str' or 'unicode'. Use real_filename when you are not able to convert filename to real unicode string (ie. you have to use unicode(name, 'replace') or unicode(name, 'ignore')). """ assert isinstance(filename, unicode) if not real_filename: real_filename = filename try: inputio = open(real_filename, 'rb') except IOError, err: charset = getTerminalCharset() errmsg = unicode(str(err), charset) raise InputStreamError(_("Unable to open file %s: %s") % (filename, errmsg))
def __init__(self, input, size=None, **args): if not hasattr(input, "seek"): if size is None: input = InputPipe(input, self._setSize) else: input = InputPipe(input) elif size is None: try: input.seek(0, 2) size = input.tell() * 8 except IOError, err: if err.errno == ESPIPE: input = InputPipe(input, self._setSize) else: charset = getTerminalCharset() errmsg = unicode(str(err), charset) source = args.get("source", "<inputio:%r>" % input) raise InputStreamError(_("Unable to get size of %s: %s") % (source, errmsg))
def computeQuality(self, jpeg): # This function is an adaption to Python of ImageMagick code # to compute JPEG quality using quantization tables # Read quantization tables qtlist = [] for dqt in jpeg.array("quantization"): for qt in dqt.array("content/qt"): # TODO: Take care of qt["index"].value? qtlist.append(qt) if not qtlist: return # Compute sum of all coefficients sumcoeff = 0 for qt in qtlist: coeff = qt.array("coeff") for index in xrange(64): sumcoeff += coeff[index].value # Choose the right quality table and compute hash value try: hashval = qtlist[0]["coeff[2]"].value + qtlist[0]["coeff[53]"].value if 2 <= len(qtlist): hashval += qtlist[1]["coeff[0]"].value + qtlist[1][ "coeff[63]"].value hashtable = QUALITY_HASH_COLOR sumtable = QUALITY_SUM_COLOR else: hashtable = QUALITY_HASH_GRAY sumtable = QUALITY_SUM_GRAY except (MissingField, IndexError): # A coefficient is missing, so don't compute JPEG quality return # Find the JPEG quality for index in xrange(100): if (hashval >= hashtable[index]) or (sumcoeff >= sumtable[index]): quality = "%s%%" % (index + 1) if (hashval > hashtable[index]) or (sumcoeff > sumtable[index]): quality += " " + _("(approximate)") self.comment = "JPEG quality: %s" % quality return
def createDisplay(self, human=True): if not human: if self._raw_value is None: self._raw_value = GenericString.createValue(self, False) value = makePrintable(self._raw_value, "ASCII", to_unicode=True) elif self._charset: value = makePrintable(self.value, "ISO-8859-1", to_unicode=True) else: value = self.value if config.max_string_length < len(value): # Truncate string if needed value = "%s(...)" % value[:config.max_string_length] if not self._charset or not human: return makePrintable(value, "ASCII", quote='"', to_unicode=True) else: if value: return '"%s"' % value.replace('"', '\\"') else: return _("(empty)")
def timestampUUID60(value): """ Convert UUID 60-bit timestamp to string. The timestamp format is a 60-bit number which represents number of 100ns since the the 15 October 1582 at 00:00. Result is an unicode string. >>> timestampUUID60(0) datetime.datetime(1582, 10, 15, 0, 0) >>> timestampUUID60(130435676263032368) datetime.datetime(1996, 2, 14, 5, 13, 46, 303236) """ if not isinstance(value, (float, int, long)): raise TypeError("an integer or float is required") if value < 0: raise ValueError("value have to be a positive or nul integer") try: return UUID60_TIMESTAMP_T0 + timedelta(microseconds=value/10) except OverflowError: raise ValueError(_("timestampUUID60() overflow (value=%s)") % value)
def __init__(self, input, size=None, **args): if not hasattr(input, "seek"): if size is None: input = InputPipe(input, self._setSize) else: input = InputPipe(input) elif size is None: try: input.seek(0, 2) size = input.tell() * 8 except IOError, err: if err.errno == ESPIPE: input = InputPipe(input, self._setSize) else: charset = getTerminalCharset() errmsg = unicode(str(err), charset) source = args.get("source", "<inputio:%r>" % input) raise InputStreamError( _("Unable to get size of %s: %s") % (source, errmsg))
def _get(self, index): if index >= len(self.buffers): return '' buf = self.buffers[index] if buf is None: raise InputStreamError( _("Error: Buffers too small. Can't seek backward.")) if self.last != index: next = buf[1] prev = buf[2] self.buffers[next][2] = prev self.buffers[prev][1] = next first = self.buffers[self.last][1] buf[1] = first buf[2] = self.last self.buffers[first][2] = index self.buffers[self.last][1] = index self.last = index return buf[0]
def timestampUUID60(value): """ Convert UUID 60-bit timestamp to string. The timestamp format is a 60-bit number which represents number of 100ns since the the 15 October 1582 at 00:00. Result is an unicode string. >>> timestampUUID60(0) datetime.datetime(1582, 10, 15, 0, 0) >>> timestampUUID60(130435676263032368) datetime.datetime(1996, 2, 14, 5, 13, 46, 303236) """ if not isinstance(value, (float, int, long)): raise TypeError("an integer or float is required") if value < 0: raise ValueError("value have to be a positive or nul integer") try: return UUID60_TIMESTAMP_T0 + timedelta(microseconds=value / 10) except OverflowError: raise ValueError(_("timestampUUID60() overflow (value=%s)") % value)
def computeQuality(self, jpeg): # This function is an adaption to Python of ImageMagick code # to compute JPEG quality using quantization tables # Read quantization tables qtlist = [] for dqt in jpeg.array("quantization"): for qt in dqt.array("content/qt"): # TODO: Take care of qt["index"].value? qtlist.append(qt) if not qtlist: return # Compute sum of all coefficients sumcoeff = 0 for qt in qtlist: coeff = qt.array("coeff") for index in xrange(64): sumcoeff += coeff[index].value # Choose the right quality table and compute hash value try: hashval= qtlist[0]["coeff[2]"].value + qtlist[0]["coeff[53]"].value if 2 <= len(qtlist): hashval += qtlist[1]["coeff[0]"].value + qtlist[1]["coeff[63]"].value hashtable = QUALITY_HASH_COLOR sumtable = QUALITY_SUM_COLOR else: hashtable = QUALITY_HASH_GRAY sumtable = QUALITY_SUM_GRAY except (MissingField, IndexError): # A coefficient is missing, so don't compute JPEG quality return # Find the JPEG quality for index in xrange(100): if (hashval >= hashtable[index]) or (sumcoeff >= sumtable[index]): quality = "%s%%" % (index + 1) if (hashval > hashtable[index]) or (sumcoeff > sumtable[index]): quality += " " + _("(approximate)") self.comment = "JPEG quality: %s" % quality return
def _getValue(self): try: value = self.createValue() except HACHOIR_ERRORS, err: self.error(_("Unable to create value: %s") % unicode(err)) value = None
class JpegMetadata(RootMetadata): EXIF_KEY = { # Exif metadatas ExifEntry.TAG_CAMERA_MANUFACTURER: "camera_manufacturer", ExifEntry.TAG_CAMERA_MODEL: "camera_model", ExifEntry.TAG_ORIENTATION: "image_orientation", ExifEntry.TAG_EXPOSURE: "camera_exposure", ExifEntry.TAG_FOCAL: "camera_focal", ExifEntry.TAG_BRIGHTNESS: "camera_brightness", ExifEntry.TAG_APERTURE: "camera_aperture", # Generic metadatas ExifEntry.TAG_IMG_TITLE: "title", ExifEntry.TAG_SOFTWARE: "producer", ExifEntry.TAG_FILE_TIMESTAMP: "creation_date", ExifEntry.TAG_WIDTH: "width", ExifEntry.TAG_HEIGHT: "height", ExifEntry.TAG_USER_COMMENT: "comment", } IPTC_KEY = { 80: "author", 90: "city", 101: "country", 116: "copyright", 120: "title", 231: "comment", } orientation_name = { 1: _('Horizontal (normal)'), 2: _('Mirrored horizontal'), 3: _('Rotated 180'), 4: _('Mirrored vertical'), 5: _('Mirrored horizontal then rotated 90 counter-clock-wise'), 6: _('Rotated 90 clock-wise'), 7: _('Mirrored horizontal then rotated 90 clock-wise'), 8: _('Rotated 90 counter clock-wise'), } def extract(self, jpeg): if "start_frame/content" in jpeg: self.startOfFrame(jpeg["start_frame/content"]) elif "start_scan/content/nr_components" in jpeg: self.bits_per_pixel = 8 * jpeg["start_scan/content/nr_components"].value if "app0/content" in jpeg: self.extractAPP0(jpeg["app0/content"]) if "exif/content" in jpeg: for ifd in jpeg.array("exif/content/ifd"): for entry in ifd.array("entry"): self.processIfdEntry(ifd, entry) self.readGPS(ifd) if "photoshop/content" in jpeg: psd = jpeg["photoshop/content"] if "version/content/reader_name" in psd: self.producer = psd["version/content/reader_name"].value if "iptc/content" in psd: self.parseIPTC(psd["iptc/content"]) for field in jpeg.array("comment"): if "content/comment" in field: self.comment = field["content/comment"].value self.computeQuality(jpeg) if "data" in jpeg: computeComprRate(self, jpeg["data"].size) if not self.has("producer") and "photoshop" in jpeg: self.producer = u"Adobe Photoshop" if self.has("compression"): self.compression = "JPEG" @fault_tolerant def startOfFrame(self, sof): # Set compression method key = sof["../type"].value self.compression = "JPEG (%s)" % JpegChunk.START_OF_FRAME[key] # Read image size and bits/pixel self.width = sof["width"].value self.height = sof["height"].value nb_components = sof["nr_components"].value self.bits_per_pixel = 8 * nb_components if nb_components == 3: self.pixel_format = _("YCbCr") elif nb_components == 1: self.pixel_format = _("Grayscale") self.nb_colors = 256 @fault_tolerant def computeQuality(self, jpeg): # This function is an adaption to Python of ImageMagick code # to compute JPEG quality using quantization tables # Read quantization tables qtlist = [] for dqt in jpeg.array("quantization"): for qt in dqt.array("content/qt"): # TODO: Take care of qt["index"].value? qtlist.append(qt) if not qtlist: return # Compute sum of all coefficients sumcoeff = 0 for qt in qtlist: coeff = qt.array("coeff") for index in xrange(64): sumcoeff += coeff[index].value # Choose the right quality table and compute hash value try: hashval= qtlist[0]["coeff[2]"].value + qtlist[0]["coeff[53]"].value if 2 <= len(qtlist): hashval += qtlist[1]["coeff[0]"].value + qtlist[1]["coeff[63]"].value hashtable = QUALITY_HASH_COLOR sumtable = QUALITY_SUM_COLOR else: hashtable = QUALITY_HASH_GRAY sumtable = QUALITY_SUM_GRAY except (MissingField, IndexError): # A coefficient is missing, so don't compute JPEG quality return # Find the JPEG quality for index in xrange(100): if (hashval >= hashtable[index]) or (sumcoeff >= sumtable[index]): quality = "%s%%" % (index + 1) if (hashval > hashtable[index]) or (sumcoeff > sumtable[index]): quality += " " + _("(approximate)") self.comment = "JPEG quality: %s" % quality return @fault_tolerant def extractAPP0(self, app0): self.format_version = u"JFIF %u.%02u" \ % (app0["ver_maj"].value, app0["ver_min"].value) if "y_density" in app0: self.width_dpi = app0["x_density"].value self.height_dpi = app0["y_density"].value @fault_tolerant def processIfdEntry(self, ifd, entry): # Skip unknown tags tag = entry["tag"].value if tag not in self.EXIF_KEY: return key = self.EXIF_KEY[tag] if key in ("width", "height") and self.has(key): # EXIF "valid size" are sometimes not updated when the image is scaled # so we just ignore it return # Read value if "value" in entry: value = entry["value"].value else: value = ifd["value_%s" % entry.name].value # Convert value to string if tag == ExifEntry.TAG_ORIENTATION: value = self.orientation_name.get(value, value) elif tag == ExifEntry.TAG_EXPOSURE: if not value: return if isinstance(value, float): value = (value, u"1/%g" % (1/value)) elif entry["type"].value in (ExifEntry.TYPE_RATIONAL, ExifEntry.TYPE_SIGNED_RATIONAL): value = (value, u"%.3g" % value) # Store information setattr(self, key, value) @fault_tolerant def readGPS(self, ifd): # Read latitude and longitude latitude_ref = None longitude_ref = None latitude = None longitude = None altitude_ref = 1 altitude = None timestamp = None datestamp = None for entry in ifd.array("entry"): tag = entry["tag"].value if tag == ExifEntry.TAG_GPS_LATITUDE_REF: if entry["value"].value == "N": latitude_ref = 1 else: latitude_ref = -1 elif tag == ExifEntry.TAG_GPS_LONGITUDE_REF: if entry["value"].value == "E": longitude_ref = 1 else: longitude_ref = -1 elif tag == ExifEntry.TAG_GPS_ALTITUDE_REF: if entry["value"].value == 1: altitude_ref = -1 else: altitude_ref = 1 elif tag == ExifEntry.TAG_GPS_LATITUDE: latitude = [ifd["value_%s[%u]" % (entry.name, index)].value for index in xrange(3)] elif tag == ExifEntry.TAG_GPS_LONGITUDE: longitude = [ifd["value_%s[%u]" % (entry.name, index)].value for index in xrange(3)] elif tag == ExifEntry.TAG_GPS_ALTITUDE: altitude = ifd["value_%s" % entry.name].value elif tag == ExifEntry.TAG_GPS_DATESTAMP: datestamp = ifd["value_%s" % entry.name].value elif tag == ExifEntry.TAG_GPS_TIMESTAMP: items = [ifd["value_%s[%u]" % (entry.name, index)].value for index in xrange(3)] items = map(int, items) items = map(str, items) timestamp = ":".join(items) if latitude_ref and latitude: value = deg2float(*latitude) if latitude_ref < 0: value = -value self.latitude = value if longitude and longitude_ref: value = deg2float(*longitude) if longitude_ref < 0: value = -value self.longitude = value if altitude: value = altitude if altitude_ref < 0: value = -value self.altitude = value if datestamp: if timestamp: datestamp += " " + timestamp self.creation_date = datestamp def parseIPTC(self, iptc): datestr = hourstr = None for field in iptc: # Skip incomplete field if "tag" not in field or "content" not in field: continue # Get value value = field["content"].value if isinstance(value, (str, unicode)): value = value.replace("\r", " ") value = value.replace("\n", " ") # Skip unknown tag tag = field["tag"].value if tag == 55: datestr = value continue if tag == 60: hourstr = value continue if tag not in self.IPTC_KEY: if tag != 0: self.warning("Skip IPTC key %s: %s" % ( field["tag"].display, makeUnicode(value))) continue setattr(self, self.IPTC_KEY[tag], value) if datestr and hourstr: try: year = int(datestr[0:4]) month = int(datestr[4:6]) day = int(datestr[6:8]) hour = int(hourstr[0:2]) min = int(hourstr[2:4]) sec = int(hourstr[4:6]) self.creation_date = datetime(year, month, day, hour, min, sec) except ValueError: pass
for key, value in self.parser_args.iteritems(): setattr(parser_obj, key, value) return parser_obj except ValidateError, err: res = unicode(err) if fallback and self.fallback: fb = parser except HACHOIR_ERRORS, err: res = unicode(err) if warn: if parser == self.other: warn = info warn(_("Skip parser '%s': %s") % (parser.__name__, res)) fallback = False if self.use_fallback and fb: warning(_("Force use of parser '%s'") % fb.__name__) return fb(stream) def guessParser(stream): return QueryParser(stream.tags).parse(stream) def createParser(filename, real_filename=None, tags=None): """ Create a parser from a file or returns None on error. Options: - filename (unicode): Input file name ; - real_filename (str|unicode): Real file name. """
from lib.hachoir_core.i18n import _, ngettext NB_CHANNEL_NAME = {1: _("mono"), 2: _("stereo")} def humanAudioChannel(value): return NB_CHANNEL_NAME.get(value, unicode(value)) def humanFrameRate(value): if isinstance(value, (int, long, float)): return _("%.1f fps") % value else: return value def humanComprRate(rate): return u"%.1fx" % rate def humanAltitude(value): return ngettext("%.1f meter", "%.1f meters", value) % value def humanPixelSize(value): return ngettext("%s pixel", "%s pixels", value) % value def humanDPI(value): return u"%s DPI" % value
def humanFrameRate(value): if isinstance(value, (int, long, float)): return _("%.1f fps") % value else: return value
def processMovieHeader(self, hdr): self.creation_date = hdr["creation_date"].value self.last_modification = hdr["lastmod_date"].value self.duration = timedelta(seconds=float(hdr["duration"].value) / hdr["time_scale"].value) self.comment = _("Play speed: %.1f%%") % (hdr["play_speed"].value*100) self.comment = _("User volume: %.1f%%") % (float(hdr["volume"].value)*100//255)