def getMeta(self, files): et = ExifTool() et.start() a = et.get_metadata_batch(files) et.terminate() meta = self._removeErrors(a) return meta
class FlirExtractor: """Extracts thermal data from FLIR images using ExifTool. Attributes: exiftoolpath: The path to the ExifTool executable. Example: with FlirExtractor(exiftoolpath="/usr/bin/exiftool") as extractor: # get a single thermal data point thermal_data = extractor.get_thermal("./path/to/FLIR.jpg") # get multiple thermal data thermal_d_list = extractor.get_thermal_batch( ["./FLIR1.jpg", "./FLIR2.jpg"] ) """ exiftoolpath: Optional[Path] _exiftool: Optional[ExifTool] def __init__(self, exiftoolpath: Path = exiftool_default_exe): self.exiftoolpath = exiftoolpath self._exiftool = None @property def exiftool(self) -> ExifTool: _exiftool = self._exiftool if _exiftool is None: raise AttributeError( "ExifTool was not initialized. " "Use FlirExtractor in a context manager, e.g. \n" "with FlirExtractor() as e:\n" " e.do_magic()") return _exiftool def open(self): """Creates an Exiftool process. Not recommended, use `with:` context manager instead. """ if self._exiftool is not None: raise Exception("ExifTool was already initialized.") self._exiftool = ExifTool(executable_=str(self.exiftoolpath)) self._exiftool.start() def close(self): """Closes the Exiftool process. Not recommended, use `with:` context manager instead. """ if self._exiftool is None: return # already closed, do nothing self._exiftool.terminate() self._exiftool = None def __enter__(self): self.open() return self def __exit__(self, exception_type, exception_value, traceback): self.close() def get_thermal(self, filepath: Path) -> "np.ndarray": """Gets a thermal image from a FLIR file. Parameters: filepath: The path to the FLIR file. Returns: The thermal data in Celcius as a 2-D numpy array. """ return get_thermal(self.exiftool, filepath) def get_thermal_batch(self, filepaths: Iterable[Path]) -> Iterable["np.ndarray"]: """Gets thermal images from a list of FLIR files. Parameters: filepaths: The paths to the FLIR files. Returns: A list of the thermal data in Celcius as 2-D numpy arrays. """ return get_thermal_batch(self.exiftool, filepaths)
class TestExifManager(OPennTestCase): staging_dir = os.path.join(os.path.dirname(__file__), 'staging') source_dir = os.path.join(os.path.dirname(__file__), 'data/mscodex1223') test_image_names = [ 'mscodex1223_wk1_body0001.tif', 'mscodex1223_wk1_body0002.tif', 'mscodex1223_wk1_body0003.tif' ] test_images = [ os.path.join(source_dir, img) for img in test_image_names ] staged_images = [ os.path.join(staging_dir, img) for img in test_image_names ] img_desc = { 'ImageDescription': 'This is the image description' } img_desc_nl = { 'ImageDescription': """This is the two-line description""" } xmp_marked = { 'xmpRights:Marked': 'true' } # the following has a unicode (c) symbol dc_rights = { 'dc:Rights': 'The dc rights ©2014' } tag_dict = { 'Marked': True, 'rights': 'The dc rights ©2014' } def setUp(self): if not os.path.exists(TestExifManager.staging_dir): os.mkdir(TestExifManager.staging_dir) self._exiftool = ExifTool() def tearDown(self): if os.path.exists(TestExifManager.staging_dir): shutil.rmtree(TestExifManager.staging_dir) self._exiftool.terminate() def get_metadata(self,img): with ExifTool() as et: md = et.get_metadata(img) return md def stage_images(self): for f in TestExifManager.test_images: shutil.copy(f, TestExifManager.staging_dir) def stage_image(self, index=0): shutil.copy(TestExifManager.test_images[index], TestExifManager.staged_images[index]) return TestExifManager.staged_images[index] def test_init(self): self.assertIsInstance(ExifManager(), ExifManager) def test_add_tag(self): img = self.stage_image(0) xman = ExifManager() xman.add_metadata([img], TestExifManager.img_desc) md = self.get_metadata(img) self.assertIn('EXIF:ImageDescription', md) self.assertIn('the image description', md['EXIF:ImageDescription']) def test_add_nl_tag(self): img = self.stage_image(0) xman = ExifManager() xman.add_metadata([img], TestExifManager.img_desc_nl) md = self.get_metadata(img) self.assertIn('EXIF:ImageDescription', md) self.assertIn(TestExifManager.img_desc_nl.values()[0], md['EXIF:ImageDescription']) def test_add_xmp_metadata(self): self.stage_images() xman = ExifManager() xman.add_metadata(TestExifManager.staged_images, TestExifManager.xmp_marked) for img in TestExifManager.staged_images: md = self.get_metadata(img) self.assertIn('XMP:Marked', md) self.assertTrue(os.path.exists(img + "_original")) def test_add_json_metadata(self): self.stage_images() xman = ExifManager() xman.add_json_metadata(TestExifManager.staged_images, TestExifManager.tag_dict) for img in TestExifManager.staged_images: md = self.get_metadata(img) self.assertIn('XMP:Marked', md) self.assertIn('XMP:Rights', md) self.assertTrue(os.path.exists(img + "_original")) def test_add_xmp_metadata_overwrite(self): self.stage_images() xman = ExifManager() xman.add_metadata(TestExifManager.staged_images, TestExifManager.xmp_marked, overwrite_original=True) for img in TestExifManager.staged_images: md = self.get_metadata(img) self.assertIn('XMP:Marked', md) self.assertFalse(os.path.exists(img + "_original")) def test_add_json_metadata_overwrite(self): self.stage_images() xman = ExifManager() xman.add_json_metadata(TestExifManager.staged_images, TestExifManager.tag_dict, overwrite_original=True) for img in TestExifManager.staged_images: md = self.get_metadata(img) self.assertIn('XMP:Marked', md) self.assertIn('XMP:Rights', md) self.assertFalse(os.path.exists(img + "_original"))
class ExifManager(object): xmp_re = re.compile(':') newline_re = re.compile('\n') logger = logging.getLogger(__name__) def __init__(self): self._exiftool = ExifTool() self._tempfiles = set() def __del__(self): self.stop() def serialize_xmp(self, file_list, **kwargs): args = {'keep_open': True} args.update(kwargs) counter = CountLogger(self.logger, file_list) counter.count(msg='Serialize XMP', inc=False) for path in file_list: self.serialize_xmp_one_file(path, **args) counter.count(msg="XMP for %s" % (os.path.basename(path), )) self.stop() def serialize_xmp_one_file(self, path, **kwargs): keep_open = kwargs.get('keep_open', False) kwargs.pop('keep_open', None) if not self._exiftool.running: self.start() xmp = "%s.xmp" % (path, ) tags = ['-tagsFromFile', path, xmp] self._exiftool.execute(*tags) if not keep_open: self.stop() def get_tag(self, tag, file): self.start() value = self._exiftool.get_tag(tag, file) self.stop() return value def add_metadata(self, file_list, prop_dict, overwrite_original=False): self.start() for file in file_list: self._add_md_to_file(file, prop_dict, overwrite_original) self.stop() def add_json_metadata(self, file_list, prop_dict, overwrite_original=False): self.start() for file in file_list: self._add_json_md_to_file(file, prop_dict, overwrite_original) self.stop() def add_json_one_file(self, filename, prop_dict, **kwargs): """ kwargs can be: overwrite_original : True|[False] keep_open : True|[False] (keep exiftool instance running) """ keep_open = kwargs.get('keep_open', False) kwargs.pop('keep_open', None) if not self._exiftool.running: self.start() self._add_json_md_to_file(filename, prop_dict, **kwargs) if not keep_open: self.stop() def add_md_one_file(self, filename, prop_dict, **kwargs): """ kwargs can be: overwrite_original : True|[False] keep_open : True|[False] (keep exiftool instance running) """ keep_open = kwargs.get('keep_open', False) kwargs.pop('keep_open', None) if not self._exiftool.running: self.start() self._add_md_to_file(filename, prop_dict, **kwargs) if not keep_open: self.stop() def _add_json_md_to_file(self, file, prop_dict, overwrite_original=False): dct = {'SourceFile': file} dct.update(**prop_dict) path = self._to_json_file(dct) tags = ['-json=%s' % path, file] if overwrite_original: tags.insert(0, '-overwrite_original') return self._exiftool.execute(*tags) def _add_md_to_file(self, file, prop_dict, overwrite_original=False): tags = self._build_tags(prop_dict) if overwrite_original: tags.insert(0, '-overwrite_original') tags.append(file) self._exiftool.execute(*tags) def start(self): self._exiftool.start() def stop(self): self._exiftool.terminate() self._cleanup() def _build_tags(self, prop_dict): tags = [] for key in prop_dict: value = prop_dict.get(key) if isinstance(value, list) or isinstance(value, tuple): for v in value: tags.append(self._build_tag(key, v)) else: tags.append(self._build_tag(key, value)) return tags def _build_tag(self, name, value): if ExifManager.newline_re.search(value): path = self._value_to_file(value) return "%s<=%s" % (self._tag(name), path) else: return "%s=%s" % (self._tag(name), value.encode('utf-8')) def _tag(self, name): if ExifManager.xmp_re.search(name): return "-xmp-%s" % name else: return "-%s" % name def _to_json_file(self, prop_dict): return self._value_to_file(json.dumps(prop_dict)) def _value_to_file(self, value): f = tempfile.NamedTemporaryFile(delete=False) f.write(value.encode('utf-8')) f.flush() f.seek(0) name = f.name self._tempfiles.add(name) return name def _cleanup(self): for f in self._tempfiles: try: os.remove(f) except OSError: pass self._tempfiles.clear()