def test_jpeg(self): """Create XMP from scratch to store in a jpeg.""" srcfile = pkg_resources.resource_filename(__name__, "samples/BlueSquare.jpg") with tempfile.NamedTemporaryFile(suffix='.tif', mode='wb') as tfile: # Do some surgery on the file, remove existing xmp. # The APP1 marker segment in question starts at byte 2156, has # length of 4813 with open(srcfile, 'rb') as infptr: # Write the SOI marker tfile.write(infptr.read(2)) # Skip over ALL the APP0, APP1 segments. infptr.seek(21619) tfile.write(infptr.read()) tfile.flush() xmpf = XMPFiles() xmpf.open_file(file_path=tfile.name, open_forupdate=True) xmp = xmpf.get_xmp() xmp.set_property(NS_DC, "Title", u'Stürm und Drang') # Construct the properties that would have been filled in had the # APP0 segment been left in place. xmp.set_property(NS_TIFF, "Orientation", "1") xmp.set_property(NS_TIFF, "XResolution", "720000/10000") xmp.set_property(NS_TIFF, "YResolution", "720000/10000") xmp.set_property(NS_TIFF, "ResolutionUnit", "2") xmpf.put_xmp(xmp) xmpf.close_file() xmpf = XMPFiles() xmpf.open_file(file_path=tfile.name) xmp = xmpf.get_xmp() prop = xmp.get_property(NS_DC, "Title") self.assertEqual(prop, u'Stürm und Drang') prop = xmp.get_property(NS_TIFF, "Orientation") self.assertEqual(prop, "1") prop = xmp.get_property(NS_TIFF, "XResolution") self.assertEqual(prop, "720000/10000") prop = xmp.get_property(NS_TIFF, "YResolution") self.assertEqual(prop, "720000/10000") prop = xmp.get_property(NS_TIFF, "ResolutionUnit") self.assertEqual(prop, "2") xmpf.close_file()
def test_text_property_450_file(self): files = ["fixtures/BlueSquare450.xmp", "fixtures/BlueSquare450.tif"] options = [ 'open_nooption', 'open_read', 'open_forupdate', 'open_onlyxmp', 'open_cachetnail', 'open_strictly', 'open_usesmarthandler', 'open_usepacketscanning', 'open_limitscanning', ] for f in files: for o in options: try: xmpfile = XMPFiles(file_path=f, **{o: True}) xmp_data = xmpfile.get_xmp() headline = xmp_data.get_property(NS_PHOTOSHOP, 'Headline') self.assertEqual(headline[-5:], "=END=") self.assertTrue( len(headline) > 450, "Not all text was extracted.") except ((IOError, XMPError)): pass
def verify_metadatas_are_removed(self, file_path): # helper function that verifies that the provided image doesn't contain any sensitive metadata # empty exif self.assertEqual(piexif.load(file_path)["0th"], {}, msg="sensitive exif data left") self.assertEqual(piexif.load(file_path)["Exif"], {}, msg="sensitive exif data left") self.assertEqual(piexif.load(file_path)["GPS"], {}, msg="sensitive exif data left") self.assertEqual(piexif.load(file_path)["1st"], {}, msg="sensitive exif data left") # Imagine the following scenario: An image contains sensitive information, it gets modified to hide these. # If there is an exif-thumbnail it might represent the previous image and hence could leak those information. self.assertEqual(piexif.load(file_path)["thumbnail"], None, msg="The exif thumbnail has not been removed.") # verify that xmp is also empty. Normally the xmp content is stored in within the rdf tag xmp_file = XMPFiles(file_path=file_path) xmp_content = str(xmp_file.get_xmp()) # this won't match if there are any additional xmp elements left, because they would occur between the opening # of the rdf:Description tag and the closing of the rdf:RDF tag. sensitive_information = re.findall(" <rdf:Description.*\n </rdf:RDF>", xmp_content) self.assertEqual( len(sensitive_information), 1, msg="There are sensitive xmp-tags left:\n\n{}".format(xmp_content)) xmp_file.close_file()
def file_to_meta(fn): xmp_files = XMPFiles() with open(fn, 'r') as fp: header = fp.readline() if "xpacket" in header: # the file is already in a compatible format for the XMP parser. xmp_files.open_file(fn, open_read=True) xmp_obj = xmp_files.get_xmp() else: # need to wrap the file with a header and footer that allows # the XMP parser to parse the file into a dict. # we will only transform the data in a temporary file, leaving # the original file untouched. with NamedTemporaryFile(mode='w', delete=False) as fp,\ open(fn, 'r') as raw_fp: temp_fn = fp.name fp.write(XMP_XPACKET_HEADER + "\n") for line in raw_fp: fp.write("{line}\n".format(line=line)) fp.write(XMP_XPACKET_FOOTER + "\n") xmp_files.open_file(temp_fn, open_read=True, open_onlyxmp=True) xmp_obj = xmp_files.get_xmp() os.remove(temp_fn) return xmp_obj
def UpdateFileMetadata(filename, datetime): exif = GExiv2.Metadata(filename) if exif is not None: fileDate = GetDateFromExif(exif) if fileDate is not None: fileDate = datetime.strptime(fileDate, DATE_FORMAT) # date within acceptable limit. don't update if abs((fileDate - datetime).days) <= FILE_METADATA_DATE_TOLERANCE: return log('Updating exif: %s to date: %s', \ filename, datetime.strftime(DATE_FORMAT)) if DRY_RUN == False: if exif is not None: exif['Exif.Photo.DateTimeOriginal'] = datetime.strftime( DATE_FORMAT) exif['Exif.Photo.DateTimeDigitized'] = datetime.strftime( DATE_FORMAT) exif['Exif.Image.DateTime'] = datetime.strftime(DATE_FORMAT) exif['Exif.Image.DateTimeOriginal'] = datetime.strftime( DATE_FORMAT) exif.save_file() xmpfile = XMPFiles(file_path=filename, open_forupdate=True) xmp = xmpfile.get_xmp() if xmp is not None: for xmpConst in XMP_CONSTANTS: for dateConst in DATE_CONSTANTS: if xmp.does_property_exist(xmpConst, dateConst): xmp.set_property( \ xmpConst, dateConst, datetime.strftime(DATE_FORMAT_XMP)) if (xmpfile.can_put_xmp(xmp)): xmpfile.put_xmp(xmp) xmpfile.close_file()
def test_tiff(self): """Write to a TIFF that does not already have the XMP tag.""" srcfile = pkg_resources.resource_filename(__name__, "fixtures/zeros.tif") with tempfile.NamedTemporaryFile(suffix='.tif') as tfile: shutil.copyfile(srcfile, tfile.name) xmpf = XMPFiles() xmpf.open_file(file_path=tfile.name, open_forupdate=True) # Since it's a TIFF, it already has everything possible from the # TIFF IFD filled in into the TIFF namespace. xmp = xmpf.get_xmp() xmp.set_property(NS_DC, "rights", "no one in particular") xmpf.put_xmp(xmp) xmpf.close_file() xmpf.open_file(file_path=tfile.name) xmp = xmpf.get_xmp() xmpf.close_file() # TODO: explain why this happened. prop = xmp.get_property(NS_DC, "rights") prop2 = xmp.get_localized_text(NS_DC, "rights", None, "x-default") self.assertEqual(prop2, "no one in particular")
def terminal_write(file): # get xmp from file xmpfile = XMPFiles(file_path=file, open_forupdate=True) xmp = xmpfile.get_xmp() if check_xmp_writable(file) == True: #if xmp can be written to file if check_inspo_xmp(file) == False: #if INSPO xmp does not exist # register or update INSPO namespace URI xmp.register_namespace(INSPO_URI, "INSPO") # for each possible article for article in ARTICLES: value = input("What " + article + " are they wearing?: ") # ask if they are wearing a certain article of clothing if value == "": # if the value is empty then pass pass else: # else set that article of clothing xmp.set_property(INSPO_URI, article, value) xmpfile.put_xmp(xmp) xmpfile.close_file() else: xmpfile.close_file() print("INSPO DATA ALREADY EXISTS") return else: xmpfile.close_file() print("XMP NOT WRITEABLE") return
def dictonary_write(file, dict): # get xmp from file xmpfile = XMPFiles(file_path=file, open_forupdate=True) xmp = xmpfile.get_xmp() if check_xmp_writable(file) == True: #if xmp can be written to file if check_inspo_xmp(file) == False: #if INSPO xmp does not exist # register or update INSPO namespace URI xmp.register_namespace(INSPO_URI, "INSPO") # for each possible article for article in ARTICLES: try: value = dict[article] xmp.set_property(INSPO_URI, article, value) except: pass xmpfile.put_xmp(xmp) xmpfile.close_file() return else: xmpfile.close_file() print("INSPO DATA ALREADY EXISTS") return else: xmpfile.close_file() print("XMP NOT WRITEABLE") return
def read_xmp(filename, field=None): """Reads a field, or the entire XMP file, and returns it. If the field cannot be found inside the file and there exists a sidecar file then the sidecar file will also be searched for the field. Args: filename (str): The full path to the base file to read (without .xmp extension) field (str|None) Returns: If ``field`` is None return the entire XMP content, else the value of the field specified or False if no value found. """ xmpfile = XMPFiles(file_path=filename, open_onlyxmp=True) xmp = xmpfile.get_xmp() sidecar = '{0}.xmp'.format(filename) if field is None and xmp: return str(xmp) elif field is None and xmp is None: if os.path.exists(sidecar): return read_xmp_sidecar(sidecar, field=None) else: return False val = _read_xmp_field(xmp, field) if val is not None: return val else: if os.path.exists(sidecar): return read_xmp_sidecar(sidecar, field) else: return False
def test_does_property_exist(self): filename = pkg_resources.resource_filename( __name__, "fixtures/BlueSquare450.tif") xmp = XMPFiles(file_path=filename) xmp_data = xmp.get_xmp() self.assertTrue( xmp_data.does_property_exist("http://ns.adobe.com/photoshop/1.0/", 'Headline'))
def test_exempi_bad_combinations(self): """ Verify bad combinations of formats and open flags. """ # Certain combinations of formats and open flags will raise an XMPError # when you try to open the XMP for flg in open_flags: kwargs = {flg: True} for filename, fmt in zip(self.samplefiles, self.formats): if not self.flg_fmt_combi(flg, fmt): xmpfile = XMPFiles() xmpfile.open_file(filename, **kwargs) xmpfile.get_xmp() else: xmpfile = XMPFiles() with self.assertRaises(XMPError): xmpfile.open_file(filename, **kwargs)
def main(): options = getoptions() logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s") logger = logging.getLogger() vars = { 'UsePanoramaViewer': 'True', 'ProjectionType': 'equirectangular', } for vstr in options.vars: for elem in vstr.split(','): key, value = elem.split('=') if key in namespace: vars[key] = value else: logger.warn("Key {0} is invalid.".format(key)) for filename in options.filenames: logger.info("Filename: {0}".format(filename)) xmpfile = XMPFiles(file_path=filename, open_forupdate=True) xmp = xmpfile.get_xmp() try: assert xmp is not None except AssertionError: logger.warn("Metadata is not available for {0}.".format(filename)) continue xmp.register_namespace(GPano, 'GPano') width, height = imagesize.get(filename) width = "{0}".format(width) height = "{0}".format(height) # width = xmp.get_property(consts.XMP_NS_EXIF, 'PixelXDimension' ) # height = xmp.get_property(consts.XMP_NS_EXIF, 'PixelYDimension' ) # assert width != '', "No width read." # assert height != '', "No height read." for name in namespace: if name in vars: if xmp.does_property_exist(GPano, name): logger.info("Overwrite: {0} {1}->{2}".format( name, xmp.get_property(GPano, name), vars[name])) else: logger.info("Set: {0} {1}".format(name, vars[name])) xmp.set_property(GPano, name, vars[name]) elif name in [ 'FullPanoWidthPixels', 'CroppedAreaImageWidthPixels' ]: if not xmp.does_property_exist(GPano, name): logger.info("Set default: {0} {1}".format(name, width)) xmp.set_property(GPano, name, width) elif name in [ 'FullPanoHeightPixels', 'CroppedAreaImageHeightPixels' ]: if not xmp.does_property_exist(GPano, name): logger.info("Set default: {0} {1}".format(name, height)) xmp.set_property(GPano, name, height) xmpfile.put_xmp(xmp) xmpfile.close_file()
def test_open_open_limitscanning(self): """Verify this library failure.""" # Issue 5 filenames = [ pkg.resource_filename(__name__, "samples/BlueSquare.pdf"), pkg.resource_filename(__name__, "samples/BlueSquare.xmp") ] xmpfile = XMPFiles() for filename in filenames: with self.assertRaises(XMPError): xmpfile.open_file(filename, open_limitscanning=True)
def test_write_in_readonly(self): """If not "open_forupdate = True", should raise exception""" # Note, the file should have been opened with "open_forupdate = True" # so let's check if XMPMeta is raising an Exception. xmpfile = XMPFiles() filename = os.path.join(self.tempdir, 'sig05-002a.tif') xmpfile.open_file(filename) xmp_data = xmpfile.get_xmp() xmp_data.set_property(NS_PHOTOSHOP, 'Headline', "Some text") self.assertRaises(XMPError, xmpfile.put_xmp, xmp_data) self.assertEqual(xmpfile.can_put_xmp(xmp_data), False)
def compressToJP2(fileIn, fileOut, parameters, extractMetadataFlag): """ Compress image to JP2 if extractMetadataFlag is True, metadata is extractede from fileIn, which is subsequently written to an XMP sidecar file and then embedded in an XML box (as per KB specs). However, by default Kakadu already does the metadata extraction natively, but it uses the uuid box! """ # Add command-line arguments args = [kdu_compress] args.append("-i") args.append(fileIn) args.append("-o") args.append(fileOut) for p in parameters: args.append(p) if extractMetadataFlag and sys.platform != "win32": # Metadata extraction and embedding. Skipped if OS is Windows, # because Windows does not support LibXMP! # Extract metadata from fileIn to XMP xmpData = str(XMPFiles(file_path=fileIn).get_xmp()) # Prepend xmpData with string "xml " xmpData = "xml " + xmpData # Write xmpData to temporary file xmpFNameTemp = str(uuid.uuid1()) with io.open(xmpFNameTemp, "w", encoding="utf-8") as fXMP: fXMP.write(xmpData) fXMP.close() args.append("-jp2_box") args.append(xmpFNameTemp) # Command line as string (used for logging purposes only) cmdStr = " ".join(args) status, out, err = launchSubProcess(args) # Main results to dictionary dictOut = {} dictOut["cmdStr"] = cmdStr dictOut["status"] = status dictOut["stdout"] = out dictOut["stderr"] = err if extractMetadataFlag: os.remove(xmpFNameTemp) return dictOut
def inspo_xmp_to_dict(file): out = {} # get xmp from file xmpfile = XMPFiles(file_path=file, open_forupdate=True) xmp = xmpfile.get_xmp() for article in ARTICLES: if xmp.does_property_exist(INSPO_URI, article): out[article] = xmp.get_property(INSPO_URI, article) return out
def get_tags_from_xmp(self, file_path): xmpfile = XMPFiles(file_path=file_path, open_forupdate=True) xmp = xmpfile.get_xmp() property_was_removed = False for index in range(5, 0, -1): property_was_removed |= self.remove_geo_xmp_property( xmp, consts.XMP_NS_DC, 'subject[%s]' % (index)) if property_was_removed: print('Updating %s' % file_path) xmpfile.put_xmp(xmp) xmpfile.close_file() return
def check_inspo_xmp(file): #get xmp from file xmpfile = XMPFiles(file_path=file, open_forupdate=True) xmp = xmpfile.get_xmp() #check to see if xmp.does_property_exist(INSPO_URI, 'id') == True: xmpfile.close_file() return True else: xmpfile.close_file() return False
def test_open_file(self): # Non-existing file. xmpfile = XMPFiles() with self.assertRaises(IOError): xmpfile.open_file('') xmpfile = XMPFiles() xmpfile.open_file(self.samplefiles[0]) self.assertRaises(XMPError, xmpfile.open_file, self.samplefiles[0]) self.assertRaises(XMPError, xmpfile.open_file, self.samplefiles[1]) xmpfile.close_file() xmpfile.open_file(self.samplefiles[1]) self.assertRaises(XMPError, xmpfile.open_file, self.samplefiles[0]) # Open all sample files. for filename in self.samplefiles: xmpfile = XMPFiles() xmpfile.open_file(filename) # Try using init for filename in self.samplefiles: xmpfile = XMPFiles(file_path=filename)
def test_open_use_smarthandler(self): """Verify this library failure.""" # Issue 5 filenames = [ pkg_resources.resource_filename(__name__, "samples/BlueSquare.pdf"), pkg_resources.resource_filename(__name__, "samples/BlueSquare.ai"), pkg_resources.resource_filename(__name__, "samples/BlueSquare.xmp") ] xmpfile = XMPFiles() for filename in filenames: with self.assertRaises(XMPError): xmpfile.open_file(filename, open_usesmarthandler=True)
def _import_original(self, path: str): d = self.components['original'] = {} d['accession_path'] = realpath(expanduser(expandvars(normpath(path)))) d['filename'] = basename(d['accession_path']) fn, ext = splitext(d['filename']) target_path = join(self.path, 'data', d['filename']) shutil.copy2(d['accession_path'], target_path) with ExifTool() as et: meta = et.get_metadata(target_path) pprint(meta) xmp = XMPFiles(file_path=target_path).get_xmp() pprint(xmp) self._update(manifests=True)
def test_descriptive_metadata_from_xmp(self): path = join('tests', 'data', 'src', 'IMG_4107_XMP.png') xmp = XMPFiles(file_path=path).get_xmp() m = DescriptiveMetadata(xmp=xmp) assert_equal(m.titles[0].value, 'Moontown Cotton') assert_equal(m.agents[0].names[0].full_name, 'Tom Elliott') assert_equal( m.descriptions[0].value, 'A picture of cotton alongside a gravel road in Moontown, ' 'Alabama.') assert_equal( [k.value for k in sorted(m.keywords, key=lambda k: k.sort_val)], ['Alabama', 'cotton'])
def test_print_bom(self): """Should be able to print XMP packets despite BOM.""" # The BOM cannot be decoded from utf-8 into ascii, so a 2.7 XMPMeta # object's __repr__ function would error out on it. filename = pkg.resource_filename(__name__, "samples/BlueSquare.jpg") xmpf = XMPFiles() xmpf.open_file(file_path=filename) xmp = xmpf.get_xmp() with patch('sys.stdout', new=StringIO()) as fake_out: print(xmp) repr(xmp) self.assertTrue(True)
def update_xmp(imgpath, keywords): """ updates the xmp data in the image, or creates a sidecar xmp """ # Check if a sidecar file already exists if os.path.isfile(imgpath + '.xmp'): imgpath = imgpath + '.xmp' # NEF requires sidecar embeddedXmpFormats = ['jpg', 'png', 'tif', 'dng'] if not imgpath.lower().endswith(tuple(embeddedXmpFormats)): # create and use sidecar file imgpath = imgpath + '.xmp' with open(imgpath, 'w+') as f: f.write(blank_xmp()) print('wrote in' + imgpath) xmpfiledict = file_to_dict(imgpath) existing_keywords = [] try: dc = [] dc.append(xmpfiledict[consts.XMP_NS_DC]) existing_keywords = [x[1] for x in dc] except: print('nothing') print('existing_keywords') print(existing_keywords) xmpfile = XMPFiles(file_path=imgpath, open_forupdate=True) xmp = xmpfile.get_xmp() print(xmp) keywords_to_add = [x for x in keywords if x not in existing_keywords] print('keywords to add') print(keywords_to_add) def add_keyword(k): """ helper func """ xmp.append_array_item(consts.XMP_NS_DC, u'subject', k) _ = [add_keyword(x) for x in keywords_to_add] if xmpfile.can_put_xmp(xmp): xmpfile.put_xmp(xmp) else: xmpfile.close_file() raise Exception('Cannot write xmp to ' + imgpath) xmpfile.close_file() return 0
def test_sturm_und_drang(self): """Should be able to write a property which includes umlauts.""" srcfile = pkg_resources.resource_filename(__name__, "fixtures/zeros.tif") with tempfile.NamedTemporaryFile(suffix='.tif') as tfile: shutil.copyfile(srcfile, tfile.name) expected_value = u'Stürm und Drang' xmpf = XMPFiles() xmpf.open_file(file_path=tfile.name, open_forupdate=True) xmp = xmpf.get_xmp() xmp.set_property(NS_DC, "Title", expected_value) xmpf.put_xmp(xmp) xmpf.close_file() xmpf = XMPFiles() xmpf.open_file(file_path=tfile.name) xmp = xmpf.get_xmp() actual_value = xmp.get_property(NS_DC, "Title") xmpf.close_file() self.assertEqual(actual_value, expected_value)
def test_can_put_xmp(self): for flg in open_flags: kwargs = {flg: True} for filename, fmt in zip(self.samplefiles, self.formats): # See test_exempi_error() if (((not self.flg_fmt_combi(flg, fmt)) and (not self.exempi_problem(flg, fmt)))): xmpfile = XMPFiles() xmpfile.open_file(filename, **kwargs) xmp = xmpfile.get_xmp() if flg == 'open_forupdate': self.assertTrue(xmpfile.can_put_xmp(xmp)) else: self.assertFalse(xmpfile.can_put_xmp(xmp))
def exempi_error(self): """ Test case that exposes an Exempi bug. Seems like xmp_files_can_put_xmp in exempi is missing a try/catch block. So loading a sidecar file and call can_put_xmp will kill python interpreter since a C++ exception is thrown. """ filename = pkg.resource_filename(__name__, "samples/sig05-002a.xmp") xmpfile = XMPFiles() xmpfile.open_file(filename, open_forupdate=True) xmp = xmpfile.get_xmp() xmpfile.can_put_xmp(xmp)
def check_xmp_writable(file): # get xmp from file xmpfile = XMPFiles(file_path=file, open_forupdate=True) xmp = xmpfile.get_xmp() try: #if you can write new xmp if xmpfile.can_put_xmp(xmp) == True: xmpfile.close_file() return True else: xmpfile.close_file() return False except Exception as e: print("Error: " + str(e))
def recuperar_informacion(self, img_name, tag): xmpfile = XMPFiles(file_path=img_name) xmp = xmpfile.get_xmp() tipo = Image.open(img_name).format # Obtenemos el tipo de la imagen. if tipo == 'JPEG': # Si la imagen es JPEG, obtenemos el contenido almacenado en los campos consts.XMP_NS_JPEG info = xmp.get_property(consts.XMP_NS_JPEG, tag) else: # Si la imagen es PNG, obtenemos el contenido almacenado en los campos consts.XMP_NS_PNG info = xmp.get_property(consts.XMP_NS_PNG, tag) xmpfile.close_file() return info
def test_repr(self): """Test __repr__ and __str__ on XMPFiles objects.""" xmpf = XMPFiles() self.assertEqual(str(xmpf), 'XMPFiles()') self.assertEqual(repr(xmpf), 'XMPFiles()') # If the XMPFiles object has a file associated with it, then use a # regular expression to match the output. filename = pkg.resource_filename(__name__, "samples/BlueSquare.jpg") xmpf.open_file(file_path=filename) actual_value = str(xmpf) regex = re.compile(r"""XMPFiles\(file_path=""", re.VERBOSE) self.assertIsNotNone(regex.match(actual_value)) self.assertTrue(actual_value.endswith("BlueSquare.jpg')"))