Source code for dedoc.metadata_extractors.concrete_metadata_extractors.image_metadata_extractor
from typing import Optional, Union
from dedoc.metadata_extractors.abstract_metadata_extractor import AbstractMetadataExtractor
from dedoc.metadata_extractors.concrete_metadata_extractors.base_metadata_extractor import BaseMetadataExtractor
[docs]class ImageMetadataExtractor(AbstractMetadataExtractor):
"""
This class is used to extract metadata from images.
It expands metadata retrieved by :class:`~dedoc.metadata_extractors.BaseMetadataExtractor`.
In addition to them, the following fields can be added to the metadata other fields:
- date time, date time digitized, date time original;
- digital zoom ratio;
- exif image height, image width and version;
- light source;
- make;
- model;
- orientation;
- resolution unit;
- software;
- subject distance range;
- user comment.
"""
def __init__(self, *, config: Optional[dict] = None) -> None:
from dedoc.extensions import recognized_extensions, recognized_mimes
super().__init__(config=config, recognized_extensions=recognized_extensions.image_like_format, recognized_mimes=recognized_mimes.image_like_format)
self.keys = {
"DateTime": ("date_time", self.__parse_date),
"DateTimeDigitized": ("date_time_digitized", self.__parse_date),
"DateTimeOriginal": ("date_time_original", self.__parse_date),
"DigitalZoomRatio": ("digital_zoom_ratio", self.__parse_float),
"ExifImageHeight": ("exif_image_height", self.__parse_int),
"ExifImageWidth": ("exif_image_width", self.__parse_int),
"ExifVersion": ("exif_version", self.__encode_exif),
"LightSource": ("light_source", self.__parse_int),
"Make": ("make", self.__encode_exif),
"Model": ("model", self.__encode_exif),
"Orientation": ("orientation", self.__parse_int),
"ResolutionUnit": ("resolution_unit", self.__parse_int),
"Software": ("software", self.__encode_exif),
"SubjectDistanceRange": ("subject_distance_range", self.__parse_int),
"UserComment": ("user_comment", self.__encode_exif)
}
self.base_extractor = BaseMetadataExtractor(config=config)
[docs] def extract(self,
file_path: str,
converted_filename: Optional[str] = None,
original_filename: Optional[str] = None,
parameters: Optional[dict] = None) -> dict:
"""
Add the predefined list of metadata for images.
Look to the :meth:`~dedoc.metadata_extractors.AbstractMetadataExtractor.extract` documentation to get the information about parameters.
"""
import os
file_dir, file_name, converted_filename, original_filename = self._get_names(file_path, converted_filename, original_filename)
base_fields = self.base_extractor.extract(
file_path=file_path, converted_filename=converted_filename, original_filename=original_filename, parameters=parameters
)
exif_fields = self._get_exif(os.path.join(file_dir, converted_filename))
result = {**base_fields, **exif_fields}
return result
def __encode_exif(self, exif: Union[str, bytes]) -> Optional[str]:
if isinstance(exif, bytes):
try:
return exif.decode()
except UnicodeDecodeError:
return None
return str(exif)
def __parse_int(self, exif: Union[str, bytes]) -> Optional[int]:
try:
exif = self.__encode_exif(exif)
return int(exif)
except Exception:
return None
def __parse_date(self, date_str: Union[str, bytes]) -> Optional[int]:
from dateutil import parser
try:
date_str = self.__encode_exif(date_str)
date = parser.parse(date_str.replace(": ", ":"))
return int(date.timestamp())
except Exception:
return None
def __parse_float(self, exif: Union[str, bytes]) -> Optional[float]:
import math
try:
exif = self.__encode_exif(exif)
result = float(exif)
return None if math.isnan(result) else result
except Exception:
return None
def _get_exif(self, path: str) -> dict:
from PIL import ExifTags, Image
import piexif
try:
image = Image.open(path)
exif_dict = piexif.load(image.info["exif"]).get("Exif", {}) if "exif" in image.info else {}
exif = {ExifTags.TAGS[k]: v for k, v in exif_dict.items() if k in ExifTags.TAGS}
encoded_dict = {key_renamed: encode_function(exif.get(key)) for key, (key_renamed, encode_function) in self.keys.items() if key in exif}
encoded_dict = {k: v for k, v in encoded_dict.items() if k is not None if v is not None}
image.close()
return encoded_dict
except Exception as e:
self.logger.debug(e)
return {"broken_image": True}