From cc542d2481fee33433ee003dba3fe0b7b815c6f7 Mon Sep 17 00:00:00 2001 From: SylikC Date: Fri, 19 Jul 2019 00:58:56 -0700 Subject: [PATCH] remove print_conversion enhancement (from pull 25), combined the added_args and common_args into one thing, and also an enhancement on linux https://github.com/smarnach/pyexiftool/pull/11 --- CHANGELOG.md | 3 +- exiftool.py | 101 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 77 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63b209b..db0a416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,8 @@ Date (Timezone) | Version | Comment 07/18/2019 04:14:32 AM (PDT) | 0.1.9 | Merge the test cases from the [Pull request #5 "add set_tags_batch, set_tags + constructor takes added options"](https://github.com/smarnach/pyexiftool/pull/5) by [halloleo](https://github.com/halloleo) on Aug 1, 2012 07/18/2019 04:34:46 AM (PDT) | 0.3.0 | changed the setup.py licensing and updated the version numbering as in changelog
changed the version number scheme, as it appears the "official last release" was 0.2.0 tagged. There's going to be a lot of things broken in this current build, and I'll fix it as they come up. I'm going to start playing with the library and the included tests and such.
There's one more pull request #11 which would be pending, but it duplicates the extra arguments option.
I'm also likely to remove the print conversion as it's now covered by the extra args. I'll also rename some variable names with the addedargs patch
**for my changes (sylikc), I can only guarantee they will work on Python 3.7, because that's my environment... and while I'll try to maintain compatibility, there's no guarantees** 07/18/2019 05:06:19 AM (PDT) | 0.3.1 | make some minor tweaks to the naming of the extra args variable. The other pull request 11 names them params, and when I decide how to merge that pull request, I'll probably change the variable names again. -07/19/2019 12:01:22 AM (PTD) | 0.3.2 | fix the select() problem for windows, and fix all tests +07/19/2019 12:01:22 AM (PDT) | 0.3.2 | fix the select() problem for windows, and fix all tests +07/19/2019 12:54:39 AM (PDT) | 0.3.3 | Merge a piece of [Pull request #11 "Robustness enhancements](https://github.com/smarnach/pyexiftool/pull/11) by [Matthias Kiefer (kiefermat)](https://github.com/kiefermat) on Oct 27, 2014
*On linux call prctl in subprocess to be sure that the exiftool child process is killed even if the parent process is killed by itself*
also removed print_conversion
also merged the common_args and added_args into one args list # Changes around the web diff --git a/exiftool.py b/exiftool.py index ebae68d..3be2e3d 100644 --- a/exiftool.py +++ b/exiftool.py @@ -65,6 +65,10 @@ import logging import codecs +# for the pdeathsig +import signal +import ctypes + try: # Py3k compatibility basestring except NameError: @@ -90,6 +94,9 @@ KW_TAGNAME = "IPTC:Keywords" KW_REPLACE, KW_ADD, KW_REMOVE = range(3) +#------------------------------------------------------------------------------------------------ + + # This code has been adapted from Lib/os.py in the Python source tree # (sha1 265e36e277f3) def _fscodec(): @@ -119,6 +126,27 @@ def fsencode(filename): fsencode = _fscodec() del _fscodec +#------------------------------------------------------------------------------------------------ + +def set_pdeathsig(sig=signal.SIGTERM): + """ + Use this method in subprocess.Popen(preexec_fn=set_pdeathsig()) to make sure, + the exiftool childprocess is stopped if this process dies. + However, this only works on linux. + """ + if sys.platform == "linux" or sys.platform == "linux2": + def callable_method(): + # taken from linux/prctl.h + pr_set_pdeathsig = 1 + libc = ctypes.CDLL("libc.so.6") + return libc.prctl(pr_set_pdeathsig, sig) + + return callable_method + else: + return None + + + #string helper def strip_nl (s): @@ -152,6 +180,10 @@ def format_error (result): else: return 'exiftool finished with error: "%s"' % strip_nl(result) + + + +#------------------------------------------------------------------------------------------------ class ExifTool(object): """Run the `exiftool` command-line tool and communicate to it. @@ -161,7 +193,7 @@ class ExifTool(object): name disables the print conversion for this particular tag. You can pass two arguments to the constructor: - - ``added_args`` (list of strings): contains additional paramaters for + - ``common_args`` (list of strings): contains additional paramaters for the stay-open instance of exiftool - ``executable`` (string): file name of the ``exiftool`` executable. The default value ``exiftool`` will only work if the executable @@ -196,57 +228,74 @@ class ExifTool(object): associated with a running subprocess. """ - def __init__(self, executable_=None, added_args=None, win_shell=True, print_conversion=False): + def __init__(self, executable_=None, common_args=None, win_shell=True): self.win_shell = win_shell - self.print_conversion = print_conversion if executable_ is None: self.executable = executable else: self.executable = executable_ self.running = False - - if added_args is None: - self.added_args = [] - elif type(added_args) is list: - self.added_args = added_args + + self._common_args = common_args + # it can't be none, check if it's a list, if not, error + + self._process = None + + if common_args is None: + # default parameters to exiftool + # -n = disable print conversion (speedup) + self.common_args = ["-G", "-n"] + elif type(common_args) is list: + self.common_args = common_args else: - raise TypeError("added_args not a list of strings") + raise TypeError("common_args not a list of strings") + def start(self): """Start an ``exiftool`` process in batch mode for this instance. This method will issue a ``UserWarning`` if the subprocess is - already running. The process is started with the ``-G`` (and, + already running. The process is by default started with the ``-G`` (and, if print conversion was disabled, ``-n``) as common arguments, which are automatically included in every command you run with :py:meth:`execute()`. + + However, you can override these default arguments with the common_args parameter in the constructor. """ if self.running: warnings.warn("ExifTool already running; doing nothing.") return - proc_args = [self.executable, "-stay_open", "True", "-@", "-", "-common_args", "-G"] - # may remove this and just have it added to extra args - if not self.print_conversion: - proc_args.append("-n") + proc_args = [self.executable, "-stay_open", "True", "-@", "-", "-common_args"] + proc_args.extend(self.common_args) # add the common arguments - proc_args.extend(self.added_args) logging.debug(proc_args) + with open(os.devnull, "w") as devnull: - startup_info = subprocess.STARTUPINFO() - if not self.win_shell: - SW_FORCEMINIMIZE = 11 # from win32con - # Adding enum 11 (SW_FORCEMINIMIZE in win32api speak) will - # keep it from throwing up a DOS shell when it launches. - startup_info.dwFlags |= 11 - - self._process = subprocess.Popen( - proc_args, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=devnull, startupinfo=startup_info) + if sys.platform == 'win32': + startup_info = subprocess.STARTUPINFO() + if not self.win_shell: + SW_FORCEMINIMIZE = 11 # from win32con + # Adding enum 11 (SW_FORCEMINIMIZE in win32api speak) will + # keep it from throwing up a DOS shell when it launches. + startup_info.dwFlags |= 11 + + self._process = subprocess.Popen( + proc_args, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=devnull, startupinfo=startup_info) + # TODO check error before saying it's running + else: + # assume it's linux + self._process = subprocess.Popen( + proc_args, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=devnull, preexec_fn=set_pdeathsig(signal.SIGTERM)) + # TODO check error before saying it's running + self.running = True def terminate(self):