diff --git a/build/lib/pyexif/__init__.py b/build/lib/pyexif/__init__.py new file mode 100644 index 0000000..eaf61c7 --- /dev/null +++ b/build/lib/pyexif/__init__.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import datetime +import json +import os +import re +import six +import subprocess +import sys + + +def _install_exiftool_info(): + print(""" +Cannot find 'exiftool'. + +The ExifEditor class requires that the 'exiftool' command-line +utility is installed in order to work. Information on obtaining +this excellent utility can be found at: + +http://www.sno.phy.queensu.ca/~phil/exiftool/ +""") + + +def _runproc(cmd, fpath=None): + # if not _EXIFTOOL_INSTALLED: + # _install_exiftool_info() + # msg = "Running this class requires that exiftool is installed" + # raise RuntimeError(msg) + pipe = subprocess.PIPE + proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, + stderr=pipe, close_fds=True) + proc.wait() + err = proc.stderr.read() + if err: + # See if it's a damaged EXIF directory. If so, fix it and re-try + if (err.startswith(b"Warning: Bad ExifIFD directory") + and fpath is not None): + fixcmd = ('exiftool -overwrite_original_in_place -all= ' + '-tagsfromfile @ -all:all -unsafe "{fpath}"'.format( + **locals())) + try: + _runproc(fixcmd) + except RuntimeError: + # It will always raise a warning, so ignore it + pass + # Retry + return _runproc(cmd, fpath) + raise RuntimeError(err) + else: + return proc.stdout.read() + + +# Test that the exiftool is installed +_EXIFTOOL_INSTALLED = True +try: + out = _runproc("exiftool -ver") +except RuntimeError as e: + # If the tool is installed, the error should be 'File not found'. + # Otherwise, assume it isn't installed. + err = "{0}".format(e).strip() + if "File not found" not in err: + _EXIFTOOL_INSTALLED = False + _install_exiftool_info() + + + +class ExifEditor(object): + def __init__(self, photo=None, save_backup=False, extra_opts=None): + self.save_backup = save_backup + extra_opts = extra_opts or [] + if not save_backup: + extra_opts.append("-overwrite_original_in_place") + self._optExpr = " ".join(extra_opts) + if not isinstance(photo, six.string_types): + photo = photo.decode("utf-8") + self.photo = photo + # Tuples of (degrees, mirrored) + self._rotations = { + 0: (0, 0), + 1: (0, 0), + 2: (0, 1), + 3: (180, 0), + 4: (180, 1), + 5: (90, 1), + 6: (90, 0), + 7: (270, 1), + 8: (270, 0)} + self._invertedRotations = dict([[v, k] for k, v in self._rotations.items()]) + # DateTime patterns + self._datePattern = re.compile(r"\d{4}:[01]\d:[0-3]\d$") + self._dateTimePattern = re.compile(r"\d{4}:[01]\d:[0-3]\d [0-2]\d:[0-5]\d:[0-5]\d$") + self._badTagPat = re.compile(r"Warning: Tag '[^']+' does not exist") + + super(ExifEditor, self).__init__() + + + def rotateCCW(self, num=1, calc_only=False): + """Rotate left in 90 degree increments""" + return self._rotate(-90 * num, calc_only) + + + def rotateCW(self, num=1, calc_only=False): + """Rotate right in 90 degree increments""" + return self._rotate(90 * num, calc_only) + + + def getOrientation(self): + """Returns the current Orientation tag number.""" + return self.getTag("Orientation#", 1) + + + def _rotate(self, deg, calc_only=False): + currOrient = self.getOrientation() + currRot, currMirror = self._rotations[currOrient] + dummy, newRot = divmod(currRot + deg, 360) + currOrient = self.getOrientation() + currRot, currMirror = self._rotations[currOrient] + dummy, newRot = divmod(currRot + deg, 360) + newOrient = self._invertedRotations[(newRot, currMirror)] + if calc_only: + return newOrient + self.setOrientation(newOrient) + + + def mirrorVertically(self): + """Flips the image top to bottom.""" + # First, rotate 180 + currOrient = self.rotateCW(2, calc_only=True) + currRot, currMirror = self._rotations[currOrient] + newMirror = currMirror ^ 1 + newOrient = self._invertedRotations[(currRot, newMirror)] + self.setOrientation(newOrient) + + + def mirrorHorizontally(self): + """Flips the image left to right.""" + currOrient = self.getOrientation() + currRot, currMirror = self._rotations[currOrient] + newMirror = currMirror ^ 1 + newOrient = self._invertedRotations[(currRot, newMirror)] + self.setOrientation(newOrient) + + + def setOrientation(self, val): + """Orientation codes: + Rot Img + 1: 0 Normal + 2: 0 Mirrored + 3: 180 Normal + 4: 180 Mirrored + 5: +90 Mirrored + 6: +90 Normal + 7: -90 Mirrored + 8: -90 Normal + """ + cmd = """exiftool {self._optExpr} -Orientation#='{val}' "{self.photo}" """.format(**locals()) + _runproc(cmd, self.photo) + + + def addKeyword(self, kw): + """Add the passed string to the image's keyword tag, preserving existing keywords.""" + self.addKeywords([kw]) + + + def addKeywords(self, kws): + """Add the passed list of strings to the image's keyword tag, preserving + existing keywords. + """ + kws = ["-iptc:keywords+={0}".format(kw.replace(" ", r"\ ")) for kw in kws] + kwopt = " ".join(kws) + cmd = """exiftool {self._optExpr} {kwopt} "{self.photo}" """.format(**locals()) + _runproc(cmd, self.photo) + + + def getKeywords(self): + """Returns the current keywords for the image as a list.""" + ret = self.getTag("Keywords") + if not ret: + return [] + if isinstance(ret, six.string_types): + return [ret] + return sorted(ret) + + + def setKeywords(self, kws): + """Sets the image's keyword list to the passed list of strings. Any + existing keywords are overwritten. + """ + self.clearKeywords() + self.addKeywords(kws) + + + def clearKeywords(self): + """Removes all keywords from the image.""" + self.setTag("Keywords", "") + + + def clearKeyword(self, kw): + """Removes a single keyword from the image. If the keyword does not + exist, this call is a no-op. + """ + kws = self.getKeywords() + try: + kws.remove(kw) + except ValueError: + pass + self.setKeywords(kws) + + + def getTag(self, tag, default=None): + """Returns the value of the specified tag, or the default value + if the tag does not exist. + """ + cmd = """exiftool -j -d "%Y:%m:%d %H:%M:%S" -{tag} "{self.photo}" """.format(**locals()) + out = _runproc(cmd, self.photo) + if not isinstance(out, six.string_types): + out = out.decode("utf-8") + info = json.loads(out)[0] + ret = info.get(tag, default) + return ret + + + def getTags(self, just_names=False, include_empty=True): + """Returns a list of all the tags for the current image.""" + cmd = """exiftool -j -d "%Y:%m:%d %H:%M:%S" "{self.photo}" """.format(**locals()) + out = _runproc(cmd, self.photo) + if not isinstance(out, six.string_types): + out = out.decode("utf-8") + info = json.loads(out)[0] + if include_empty: + if just_names: + ret = list(info.keys()) + else: + ret = list(info.items()) + else: + # Exclude those tags with empty values + if just_names: + ret = [tag for tag in info.keys() if info.get(tag)] + else: + ret = [(tag, val) for tag, val in info.items() if val] + return sorted(ret) + + + def getDictTags(self, include_empty=True): + """Returns a dict of all the tags for the current image, with the tag + name as the key and the tag value as the value. + """ + tags = self.getTags(include_empty=include_empty) + return {k:v for k, v in tags} + + + def setTag(self, tag, val): + """Sets the specified tag to the passed value. You can set multiple values + for the same tag by passing those values in as a list. + """ + if not isinstance(val, (list, tuple)): + val = [val] + vallist = ['-{0}="{1}"'.format(tag, + v.replace('"', '\\"') if isinstance(v, six.string_types) else v) for v in val] + valstr = " ".join(vallist) + cmd = """exiftool {self._optExpr} {valstr} "{self.photo}" """.format(**locals()) + try: + out = _runproc(cmd, self.photo) + except RuntimeError as e: + err = "{0}".format(e).strip() + if self._badTagPat.match(err): + print("Tag '{tag}' is invalid.".format(**locals())) + else: + raise + + + def setTags(self, tags_dict): + """Sets the specified tags_dict ({tag: val, tag_n: val_n}) tag value combinations. + Used to set more than one tag, val value in a single call. + """ + if not isinstance(tags_dict, dict): + raise TypeError('tags_dict is not instance of dict') + vallist = [] + for tag in tags_dict: + val = tags_dict[tag] + # escape double quotes in case of string type + if isinstance(val, six.string_types): + val = val.replace('"', '\\"') + vallist.append('-{0}="{1}"'.format(tag, val)) + valstr = " ".join(vallist) + cmd = """exiftool {self._optExpr} {valstr} "{self.photo}" """.format(**locals()) + try: + out = _runproc(cmd, self.photo) + except RuntimeError as e: + err = "{0}".format(e).strip() + if self._badTagPat.match(err): + print("Tag '{tag}' is invalid.".format(**locals())) + else: + raise + + + def getOriginalDateTime(self): + """Get the image's original date/time value (i.e., when the picture + was 'taken'). + """ + return self._getDateTimeField("DateTimeOriginal") + + + def setOriginalDateTime(self, dttm=None): + """Set the image's original date/time (i.e., when the picture + was 'taken') to the passed value. If no value is passed, set + it to the current datetime. + """ + self._setDateTimeField("DateTimeOriginal", dttm) + + + def getModificationDateTime(self): + """Get the image's modification date/time value.""" + return self._getDateTimeField("FileModifyDate") + + + def setModificationDateTime(self, dttm=None): + """Set the image's modification date/time to the passed value. + If no value is passed, set it to the current datetime (i.e., + like 'touch'. + """ + self._setDateTimeField("FileModifyDate", dttm) + + + def _getDateTimeField(self, fld): + """Generic getter for datetime values.""" + # Convert to string format if needed +# if isinstance(dttm, (datetime.datetime, datetime.date)): +# dtstring = dttm.strftime("%Y:%m:%d %H:%M:%S") +# else: +# dtstring = self._formatDateTime(dttm) + ret = self.getTag(fld) + if ret is not None: + # It will be a string in exif std datetime format + ret = datetime.datetime.strptime(ret, "%Y:%m:%d %H:%M:%S") + return ret + + + def _setDateTimeField(self, fld, dttm): + """Generic setter for datetime values.""" + if dttm is None: + dttm = datetime.datetime.now() + # Convert to string format if needed + if isinstance(dttm, (datetime.datetime, datetime.date)): + dtstring = dttm.strftime("%Y:%m:%d %H:%M:%S") + else: + dtstring = self._formatDateTime(dttm) + cmd = """exiftool {self._optExpr} -{fld}='{dtstring}' "{self.photo}" """.format(**locals()) + _runproc(cmd, self.photo) + + + def _formatDateTime(self, dt): + """Accepts a string representation of a date or datetime, + and returns a string correctly formatted for EXIF datetimes. + """ + if self._datePattern.match(dt): + # Add the time portion + return "{0} 00:00:00".format(dt) + elif self._dateTimePattern.match(dt): + # Leave as-is + return dt + else: + raise ValueError("Incorrect datetime value '{0}' received".format(dt)) + + +def usage(): + print(""" +To use this module, create an instance of the ExifEditor class, passing +in a path to the image to be handled. You may also pass in whether you +want the program to automatically keep a backup of your original photo +(default=False). If a backup is created, it will be in the same location +as the original, with "_ORIGINAL" appended to the file name. + +Once you have an editor instance, you call its methods to get information +about the image, or to modify the image's metadata. +""") + + +if __name__ == "__main__": + usage() diff --git a/dist/pyexif-0.5.0-py3.6.egg b/dist/pyexif-0.5.0-py3.6.egg new file mode 100644 index 0000000..70a6967 Binary files /dev/null and b/dist/pyexif-0.5.0-py3.6.egg differ diff --git a/dist/pyexif-0.5.0-py3.7.egg b/dist/pyexif-0.5.0-py3.7.egg new file mode 100644 index 0000000..3b53c91 Binary files /dev/null and b/dist/pyexif-0.5.0-py3.7.egg differ diff --git a/pyexif.egg-info/PKG-INFO b/pyexif.egg-info/PKG-INFO new file mode 100644 index 0000000..010e039 --- /dev/null +++ b/pyexif.egg-info/PKG-INFO @@ -0,0 +1,25 @@ +Metadata-Version: 1.1 +Name: pyexif +Version: 0.5.0 +Summary: Python module to read/write EXIF image data +Home-page: https://github.com/EdLeafe/pyexif +Author: Ed Leafe +Author-email: ed@leafe.com +License: Python Software Foundation License +Description: Python module for working with EXIF image data. + + It does its work mainly though the command-line "exiftool", which is required + for this module to work. Information on obtaining and installing exiftool are + in the README file. + +Keywords: exif image metadata photo +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Topic :: Utilities +Classifier: Operating System :: OS Independent +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: Natural Language :: English +Classifier: Topic :: Multimedia :: Graphics diff --git a/pyexif.egg-info/SOURCES.txt b/pyexif.egg-info/SOURCES.txt new file mode 100644 index 0000000..71838ce --- /dev/null +++ b/pyexif.egg-info/SOURCES.txt @@ -0,0 +1,7 @@ +README +setup.py +pyexif/__init__.py +pyexif.egg-info/PKG-INFO +pyexif.egg-info/SOURCES.txt +pyexif.egg-info/dependency_links.txt +pyexif.egg-info/top_level.txt \ No newline at end of file diff --git a/pyexif.egg-info/dependency_links.txt b/pyexif.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/pyexif.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/pyexif.egg-info/top_level.txt b/pyexif.egg-info/top_level.txt new file mode 100644 index 0000000..e3ce0d2 --- /dev/null +++ b/pyexif.egg-info/top_level.txt @@ -0,0 +1 @@ +pyexif diff --git a/pyexif/.ipynb_checkpoints/__init__-checkpoint.py b/pyexif/.ipynb_checkpoints/__init__-checkpoint.py new file mode 100644 index 0000000..51a866d --- /dev/null +++ b/pyexif/.ipynb_checkpoints/__init__-checkpoint.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import datetime +import json +import os +import re +import six +import subprocess +import sys + + +def _install_exiftool_info(): + print(""" +Cannot find 'exiftool'. + +The ExifEditor class requires that the 'exiftool' command-line +utility is installed in order to work. Information on obtaining +this excellent utility can be found at: + +http://www.sno.phy.queensu.ca/~phil/exiftool/ +""") + + +def _runproc(cmd, fpath=None): + # if not _EXIFTOOL_INSTALLED: + # _install_exiftool_info() + # msg = "Running this class requires that exiftool is installed" + # raise RuntimeError(msg) + pipe = subprocess.PIPE + proc = subprocess.Popen(cmd, shell=True, stdin=pipe, stdout=pipe, + stderr=pipe, close_fds=True) + proc.wait() + err = proc.stderr.read() + if err: + # See if it's a damaged EXIF directory. If so, fix it and re-try + if (err.startswith(b"Warning: Bad ExifIFD directory") + and fpath is not None): + fixcmd = ('exiftool -overwrite_original_in_place -all= ' + '-tagsfromfile @ -all:all -unsafe "{fpath}"'.format( + **locals())) + try: + _runproc(fixcmd) + except RuntimeError: + # It will always raise a warning, so ignore it + pass + # Retry + return _runproc(cmd, fpath) + raise RuntimeError(err) + else: + return proc.stdout.read() + + +# Test that the exiftool is installed +_EXIFTOOL_INSTALLED = True +try: + out = _runproc("exiftool -ver") +except RuntimeError as e: + # If the tool is installed, the error should be 'File not found'. + # Otherwise, assume it isn't installed. + err = "{0}".format(e).strip() + if "File not found" not in err: + _EXIFTOOL_INSTALLED = False + _install_exiftool_info() + + + +class ExifEditor(object): + def __init__(self, photo=None, save_backup=False, extra_opts=None): + self.save_backup = save_backup + extra_opts = extra_opts or [] + if not save_backup: + extra_opts.append("-overwrite_original_in_place") + self._optExpr = " ".join(extra_opts) + if not isinstance(photo, six.string_types): + photo = photo.decode("utf-8") + self.photo = photo + # Tuples of (degrees, mirrored) + self._rotations = { + 0: (0, 0), + 1: (0, 0), + 2: (0, 1), + 3: (180, 0), + 4: (180, 1), + 5: (90, 1), + 6: (90, 0), + 7: (270, 1), + 8: (270, 0)} + self._invertedRotations = dict([[v, k] for k, v in self._rotations.items()]) + # DateTime patterns + self._datePattern = re.compile(r"\d{4}:[01]\d:[0-3]\d$") + self._dateTimePattern = re.compile(r"\d{4}:[01]\d:[0-3]\d [0-2]\d:[0-5]\d:[0-5]\d$") + self._badTagPat = re.compile(r"Warning: Tag '[^']+' does not exist") + + super(ExifEditor, self).__init__() + + + def rotateCCW(self, num=1, calc_only=False): + """Rotate left in 90 degree increments""" + return self._rotate(-90 * num, calc_only) + + + def rotateCW(self, num=1, calc_only=False): + """Rotate right in 90 degree increments""" + return self._rotate(90 * num, calc_only) + + + def getOrientation(self): + """Returns the current Orientation tag number.""" + return self.getTag("Orientation#", 1) + + + def _rotate(self, deg, calc_only=False): + currOrient = self.getOrientation() + currRot, currMirror = self._rotations[currOrient] + dummy, newRot = divmod(currRot + deg, 360) + currOrient = self.getOrientation() + currRot, currMirror = self._rotations[currOrient] + dummy, newRot = divmod(currRot + deg, 360) + newOrient = self._invertedRotations[(newRot, currMirror)] + if calc_only: + return newOrient + self.setOrientation(newOrient) + + + def mirrorVertically(self): + """Flips the image top to bottom.""" + # First, rotate 180 + currOrient = self.rotateCW(2, calc_only=True) + currRot, currMirror = self._rotations[currOrient] + newMirror = currMirror ^ 1 + newOrient = self._invertedRotations[(currRot, newMirror)] + self.setOrientation(newOrient) + + + def mirrorHorizontally(self): + """Flips the image left to right.""" + currOrient = self.getOrientation() + currRot, currMirror = self._rotations[currOrient] + newMirror = currMirror ^ 1 + newOrient = self._invertedRotations[(currRot, newMirror)] + self.setOrientation(newOrient) + + + def setOrientation(self, val): + """Orientation codes: + Rot Img + 1: 0 Normal + 2: 0 Mirrored + 3: 180 Normal + 4: 180 Mirrored + 5: +90 Mirrored + 6: +90 Normal + 7: -90 Mirrored + 8: -90 Normal + """ + cmd = """exiftool {self._optExpr} -Orientation#='{val}' "{self.photo}" """.format(**locals()) + _runproc(cmd, self.photo) + + + def addKeyword(self, kw): + """Add the passed string to the image's keyword tag, preserving existing keywords.""" + self.addKeywords([kw]) + + + def addKeywords(self, kws): + """Add the passed list of strings to the image's keyword tag, preserving + existing keywords. + """ + kws = ["-iptc:keywords+={0}".format(kw.replace(" ", r"\ ")) for kw in kws] + kwopt = " ".join(kws) + cmd = """exiftool {self._optExpr} {kwopt} "{self.photo}" """.format(**locals()) + _runproc(cmd, self.photo) + + + def getKeywords(self): + """Returns the current keywords for the image as a list.""" + ret = self.getTag("Keywords") + if not ret: + return [] + if isinstance(ret, six.string_types): + return [ret] + return sorted(ret) + + + def setKeywords(self, kws): + """Sets the image's keyword list to the passed list of strings. Any + existing keywords are overwritten. + """ + self.clearKeywords() + self.addKeywords(kws) + + + def clearKeywords(self): + """Removes all keywords from the image.""" + self.setTag("Keywords", "") + + + def clearKeyword(self, kw): + """Removes a single keyword from the image. If the keyword does not + exist, this call is a no-op. + """ + kws = self.getKeywords() + try: + kws.remove(kw) + except ValueError: + pass + self.setKeywords(kws) + + + def getTag(self, tag, default=None): + """Returns the value of the specified tag, or the default value + if the tag does not exist. + """ + cmd = """exiftool -j -d "%Y:%m:%d %H:%M:%S" -{tag} "{self.photo}" """.format(**locals()) + out = _runproc(cmd, self.photo) + if not isinstance(out, six.string_types): + out = out.decode("utf-8") + info = json.loads(out)[0] + ret = info.get(tag, default) + return ret + + + def getTags(self, just_names=False, include_empty=True): + """Returns a list of all the tags for the current image.""" + cmd = """exiftool -j -d "%Y:%m:%d %H:%M:%S" "{self.photo}" """.format(**locals()) + out = _runproc(cmd, self.photo) + if not isinstance(out, six.string_types): + out = out.decode("utf-8") + info = json.loads(out)[0] + if include_empty: + if just_names: + ret = list(info.keys()) + else: + ret = list(info.items()) + else: + # Exclude those tags with empty values + if just_names: + ret = [tag for tag in info.keys() if info.get(tag)] + else: + ret = [(tag, val) for tag, val in info.items() if val] + return sorted(ret) + + + def getDictTags(self, include_empty=True): + """Returns a dict of all the tags for the current image, with the tag + name as the key and the tag value as the value. + """ + tags = self.getTags(include_empty=include_empty) + return {k:v for k, v in tags} + + + def setTag(self, tag, val): + """Sets the specified tag to the passed value. You can set multiple values + for the same tag by passing those values in as a list. + """ + if not isinstance(val, (list, tuple)): + val = [val] + vallist = ['-{0}="{1}"'.format(tag, + v.replace('"', '\\"') if isinstance(v, six.string_types) else v) for v in val] + valstr = " ".join(vallist) + cmd = """exiftool {self._optExpr} {valstr} "{self.photo}" """.format(**locals()) + try: + out = _runproc(cmd, self.photo) + except RuntimeError as e: + err = "{0}".format(e).strip() + if self._badTagPat.match(err): + print("Tag '{tag}' is invalid.".format(**locals())) + else: + raise + + + def setTags(self, tags_dict): + """Sets the specified tags_dict ({tag: val, tag_n: val_n}) tag value combinations. + Used to set more than one tag, val value in a single call. + """ + if not isinstance(tags_dict, dict): + raise TypeError('tags_dict is not instance of dict') + vallist = [] + for tag in tags_dict: + val = tags_dict[tag] + # escape double quotes in case of string type + if isinstance(val, six.string_types): + val = val.replace('"', '\\"') + vallist.append('-{0}="{1}"'.format(tag, val)) + valstr = " ".join(vallist) + cmd = """exiftool {self._optExpr} {valstr} "{self.photo}" """.format(**locals()) + try: + out = _runproc(cmd, self.photo) + except RuntimeError as e: + err = "{0}".format(e).strip() + if self._badTagPat.match(err): + print("Tag '{tag}' is invalid.".format(**locals())) + else: + raise + + + def getOriginalDateTime(self): + """Get the image's original date/time value (i.e., when the picture + was 'taken'). + """ + return self._getDateTimeField("DateTimeOriginal") + + + def setOriginalDateTime(self, dttm=None): + """Set the image's original date/time (i.e., when the picture + was 'taken') to the passed value. If no value is passed, set + it to the current datetime. + """ + self._setDateTimeField("DateTimeOriginal", dttm) + + + def getModificationDateTime(self): + """Get the image's modification date/time value.""" + return self._getDateTimeField("FileModifyDate") + + + def setModificationDateTime(self, dttm=None): + """Set the image's modification date/time to the passed value. + If no value is passed, set it to the current datetime (i.e., + like 'touch'. + """ + self._setDateTimeField("FileModifyDate", dttm) + + + def _getDateTimeField(self, fld): + """Generic getter for datetime values.""" + # Convert to string format if needed +# if isinstance(dttm, (datetime.datetime, datetime.date)): +# dtstring = dttm.strftime("%Y:%m:%d %H:%M:%S") +# else: +# dtstring = self._formatDateTime(dttm) + ret = self.getTag(fld) + if ret is not None: + # It will be a string in exif std datetime format + ret = datetime.datetime.strptime(ret, "%Y:%m:%d %H:%M:%S") + return ret + + + def _setDateTimeField(self, fld, dttm): + """Generic setter for datetime values.""" + if dttm is None: + dttm = datetime.datetime.now() + # Convert to string format if needed + if isinstance(dttm, (datetime.datetime, datetime.date)): + dtstring = dttm.strftime("%Y:%m:%d %H:%M:%S") + else: + dtstring = self._formatDateTime(dttm) + cmd = """exiftool {self._optExpr} -{fld}='{dtstring}' "{self.photo}" """.format(**locals()) + _runproc(cmd, self.photo) + + + def _formatDateTime(self, dt): + """Accepts a string representation of a date or datetime, + and returns a string correctly formatted for EXIF datetimes. + """ + if self._datePattern.match(dt): + # Add the time portion + return "{0} 00:00:00".format(dt) + elif self._dateTimePattern.match(dt): + # Leave as-is + return dt + else: + raise ValueError("Incorrect datetime value '{0}' received".format(dt)) + + +def usage(): + print(""" +To use this module, create an instance of the ExifEditor class, passing +in a path to the image to be handled. You may also pass in whether you +want the program to automatically keep a backup of your original photo +(default=False). If a backup is created, it will be in the same location +as the original, with "_ORIGINAL" appended to the file name. + +Once you have an editor instance, you call its methods to get information +about the image, or to modify the image's metadata. +""") + + +if __name__ == "__main__": + usage() diff --git a/pyexif/__init__.py b/pyexif/__init__.py index 85945bc..51a866d 100644 --- a/pyexif/__init__.py +++ b/pyexif/__init__.py @@ -23,12 +23,12 @@ def _install_exiftool_info(): def _runproc(cmd, fpath=None): - if not _EXIFTOOL_INSTALLED: - _install_exiftool_info() - msg = "Running this class requires that exiftool is installed" - raise RuntimeError(msg) + # if not _EXIFTOOL_INSTALLED: + # _install_exiftool_info() + # msg = "Running this class requires that exiftool is installed" + # raise RuntimeError(msg) pipe = subprocess.PIPE - proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, + proc = subprocess.Popen(cmd, shell=True, stdin=pipe, stdout=pipe, stderr=pipe, close_fds=True) proc.wait() err = proc.stderr.read()