Hello community,

here is the log from the commit of package python-imageio-ffmpeg for 
openSUSE:Factory checked in at 2020-04-15 20:06:31
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-imageio-ffmpeg (Old)
 and      /work/SRC/openSUSE:Factory/.python-imageio-ffmpeg.new.2738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-imageio-ffmpeg"

Wed Apr 15 20:06:31 2020 rev:2 rq:792769 version:0.4.1

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-imageio-ffmpeg/python-imageio-ffmpeg.changes  
    2019-05-22 11:08:41.690613817 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-imageio-ffmpeg.new.2738/python-imageio-ffmpeg.changes
    2020-04-15 20:08:04.670169743 +0200
@@ -1,0 +2,11 @@
+Thu Apr  9 14:12:14 UTC 2020 - Marketa Calabkova <mcalabk...@suse.com>
+
+- Update to 0.4.1
+  * The default value for ffmpeg_timeout is now zero, e.g. just wait for 
ffmpeg.
+  * Added bits_per_pixel parameter to read_frames.
+  * Official support for Python 3.8.
+  * Improved handling of interrupts during reading/writing/closing-down.
+  * Allow the ffmpeg_timeout arg in write_frames() to be None.
+  * Don't prevent sigint propagation for ffmpeg call in 
count_frames_and_secs().
+
+-------------------------------------------------------------------

Old:
----
  imageio-ffmpeg-0.3.0.tar.gz

New:
----
  imageio-ffmpeg-0.4.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-imageio-ffmpeg.spec ++++++
--- /var/tmp/diff_new_pack.kLrLPH/_old  2020-04-15 20:08:05.186170095 +0200
+++ /var/tmp/diff_new_pack.kLrLPH/_new  2020-04-15 20:08:05.186170095 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-imageio-ffmpeg
 #
-# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -12,18 +12,19 @@
 # license that conforms to the Open Source Definition (Version 1.9)
 # published by the Open Source Initiative.
 
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
+#
 
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define         skip_python2 1
 Name:           python-imageio-ffmpeg
-Version:        0.3.0
+Version:        0.4.1
 Release:        0
-License:        BSD-2-Clause
 Summary:        FFMPEG wrapper for Python
-Url:            https://github.com/imageio/imageio-ffmpeg
+License:        BSD-2-Clause
 Group:          Development/Languages/Python
+URL:            https://github.com/imageio/imageio-ffmpeg
 Source:         
https://files.pythonhosted.org/packages/source/i/imageio-ffmpeg/imageio-ffmpeg-%{version}.tar.gz
 BuildRequires:  %{python_module pip > 19}
 BuildRequires:  %{python_module setuptools}
@@ -32,7 +33,6 @@
 BuildRequires:  python-rpm-macros
 Requires:       ffmpeg-4
 BuildArch:      noarch
-
 %python_subpackages
 
 %description

++++++ imageio-ffmpeg-0.3.0.tar.gz -> imageio-ffmpeg-0.4.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imageio-ffmpeg-0.3.0/PKG-INFO 
new/imageio-ffmpeg-0.4.1/PKG-INFO
--- old/imageio-ffmpeg-0.3.0/PKG-INFO   2019-04-12 11:08:07.000000000 +0200
+++ new/imageio-ffmpeg-0.4.1/PKG-INFO   2020-02-24 15:11:03.000000000 +0100
@@ -1,11 +1,11 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.2
 Name: imageio-ffmpeg
-Version: 0.3.0
+Version: 0.4.1
 Summary: FFMPEG wrapper for Python
 Home-page: https://github.com/imageio/imageio-ffmpeg
 Author: imageio contributors
 Author-email: almar.kl...@gmail.com
-License: (new) BSD
+License: BSD-2-Clause
 Download-URL: http://pypi.python.org/pypi/imageio-ffmpeg
 Description: FFMPEG wrapper for Python.
         
@@ -33,4 +33,6 @@
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
 Provides: imageio_ffmpeg
+Requires-Python: >=3.4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imageio-ffmpeg-0.3.0/README.md 
new/imageio-ffmpeg-0.4.1/README.md
--- old/imageio-ffmpeg-0.3.0/README.md  2019-04-12 11:07:51.000000000 +0200
+++ new/imageio-ffmpeg-0.4.1/README.md  2020-02-24 14:13:52.000000000 +0100
@@ -1,6 +1,7 @@
 # imageio-ffmpeg
 
 [![Build 
Status](https://travis-ci.org/imageio/imageio-ffmpeg.svg?branch=master)](https://travis-ci.org/imageio/imageio-ffmpeg)
+[![PyPI 
Version](https://img.shields.io/pypi/v/imageio-ffmpeg.svg)](https://pypi.python.org/pypi/imageio-ffmpeg/)
 
 FFMPEG wrapper for Python
 
@@ -45,7 +46,7 @@
 `IMAGEIO_FFMPEG_EXE` environment variable if needed.
 
 
-## Usage
+## Example usage
 
 The `imageio_ffmpeg` library provides low level functionality to read
 and write video data, using Python generators:
@@ -67,6 +68,8 @@
 writer.close()  # don't forget this
 ```
 
+(Also see the API section further down.)
+
 
 ## How it works
 
@@ -77,20 +80,45 @@
 the code itself too. In contrast, [PyAV](https://github.com/mikeboers/PyAV)
 wraps ffmpeg at the C level.
 
+Note that because of how `imageio-ffmpeg` works, `read_frames()` and
+`write_frames()` only accept file names, and not file (like) objects.
+
+
+
+## imageio-ffmpeg for enterprise
+
+Available as part of the Tidelift Subscription
+
+The maintainers of imageio-ffmpeg and thousands of other packages are working 
with Tidelift to deliver commercial support and maintenance for the open source 
dependencies you use to build your applications. Save time, reduce risk, and 
improve code health, while paying the maintainers of the exact dependencies you 
use. [Learn 
more.](https://tidelift.com/subscription/pkg/pypi-imageio-ffmpeg?utm_source=pypi-imageio-ffmpeg&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
+
+
+## Security contact information
+
+To report a security vulnerability, please use the
+[Tidelift security contact](https://tidelift.com/security).
+Tidelift will coordinate the fix and disclosure.
+
 
 ## API
 
 ```py
-def read_frames(path, pix_fmt="rgb24", bpp=3, input_params=None, 
output_params=None):
+def read_frames(
+    path,
+    pix_fmt="rgb24",
+    bpp=None,
+    input_params=None,
+    output_params=None,
+    bits_per_pixel=None,
+):
     """
     Create a generator to iterate over the frames in a video file.
     
     It first yields a small metadata dictionary that contains:
     
-    * ffmpeg_version: the ffmpeg version is use (as a string).
-    * codec: a hint about the codec used to encode the video, e.g. "h264"
-    * source_size: the width and height of the encoded video frames
-    * size: the width and height of the frames that will be produced
+    * ffmpeg_version: the ffmpeg version in use (as a string).
+    * codec: a hint about the codec used to encode the video, e.g. "h264".
+    * source_size: the width and height of the encoded video frames.
+    * size: the width and height of the frames that will be produced.
     * fps: the frames per second. Can be zero if it could not be detected.
     * duration: duration in seconds. Can be zero if it could not be detected.
     
@@ -113,13 +141,16 @@
             print(len(frame))
     
     Parameters:
-        path (str): the file to write to.
+        path (str): the filename of the file to read from.
         pix_fmt (str): the pixel format of the frames to be read.
             The default is "rgb24" (frames are uint8 RGB images).
-        bpp (int): The number of bytes per pixel in the output frames.
-            This depends on the given pix_fmt. Default is 3 (RGB).
         input_params (list): Additional ffmpeg input command line parameters.
         output_params (list): Additional ffmpeg output command line parameters.
+        bits_per_pixel (int): The number of bits per pixel in the output 
frames.
+            This depends on the given pix_fmt. Default is 24 (RGB)
+        bpp (int): DEPRECATED, USE bits_per_pixel INSTEAD. The number of bytes 
per pixel in the output frames.
+            This depends on the given pix_fmt. Some pixel formats like yuv420p 
have 12 bits per pixel
+            and cannot be set in bytes as integer. For this reason the bpp 
argument is deprecated.
     """
 ```
 
@@ -135,6 +166,7 @@
     codec=None,
     macro_block_size=16,
     ffmpeg_log_level="warning",
+    ffmpeg_timeout=0,
     input_params=None,
     output_params=None,
 ):
@@ -154,7 +186,7 @@
         gen.close()  # don't forget this
     
     Parameters:
-        path (str): the file to write to.
+        path (str): the filename to write to.
         size (tuple): the width and height of the frames.
         pix_fmt_in (str): the pixel format of incoming frames.
             E.g. "gray", "gray8a", "rgb24", or "rgba". Default "rgb24".
@@ -167,7 +199,10 @@
         macro_block_size (int): You probably want to align the size of frames
             to this value to avoid image resizing. Default 16. Can be set
             to 1 to avoid block alignment, though this is not recommended.
-        ffmpeg_log_level (str): The ffmpeg logging level.
+        ffmpeg_log_level (str): The ffmpeg logging level. Default "warning".
+        ffmpeg_timeout (float): Timeout in seconds to wait for ffmpeg process
+            to finish. Value of 0 will wait forever (default). The time that
+            ffmpeg needs depends on CPU speed, compression, and frame size.
         input_params (list): Additional ffmpeg input command line parameters.
         output_params (list): Additional ffmpeg output command line parameters.
     """
@@ -188,7 +223,7 @@
 ```py
 def get_ffmpeg_exe():
     """
-    Get the ffmpeg executable file. This can be the binary defined by 
+    Get the ffmpeg executable file. This can be the binary defined by
     the IMAGEIO_FFMPEG_EXE environment variable, the binary distributed
     with imageio-ffmpeg, an ffmpeg binary installed with conda, or the
     system ffmpeg (in that order). A RuntimeError is raised if no valid
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imageio-ffmpeg-0.3.0/imageio_ffmpeg/_definitions.py 
new/imageio-ffmpeg-0.4.1/imageio_ffmpeg/_definitions.py
--- old/imageio-ffmpeg-0.3.0/imageio_ffmpeg/_definitions.py     2019-04-12 
11:07:51.000000000 +0200
+++ new/imageio-ffmpeg-0.4.1/imageio_ffmpeg/_definitions.py     2020-02-24 
15:10:39.000000000 +0100
@@ -1,7 +1,7 @@
 import sys
 import struct
 
-__version__ = "0.3.0"
+__version__ = "0.4.1"
 
 
 def get_platform():
@@ -10,6 +10,8 @@
         return "linux{}".format(bits)
     elif sys.platform.startswith("win"):
         return "win{}".format(bits)
+    elif sys.platform.startswith("cygwin"):
+        return "win{}".format(bits)
     elif sys.platform.startswith("darwin"):
         return "osx{}".format(bits)
     else:  # pragma: no cover
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imageio-ffmpeg-0.3.0/imageio_ffmpeg/_io.py 
new/imageio-ffmpeg-0.4.1/imageio_ffmpeg/_io.py
--- old/imageio-ffmpeg-0.3.0/imageio_ffmpeg/_io.py      2019-04-12 
11:07:51.000000000 +0200
+++ new/imageio-ffmpeg-0.4.1/imageio_ffmpeg/_io.py      2020-02-24 
15:10:39.000000000 +0100
@@ -1,9 +1,9 @@
 import sys
 import time
-import signal
+import pathlib
 import subprocess
 
-from ._utils import get_ffmpeg_exe, logger
+from ._utils import get_ffmpeg_exe, _popen_kwargs, logger
 from ._parsing import LogCatcher, parse_ffmpeg_header, cvsecs
 
 
@@ -31,11 +31,14 @@
     """
     # https://stackoverflow.com/questions/2017843/fetch-frame-count-with-ffmpeg
 
-    assert isinstance(path, str), "Video path must be a string"
+    if isinstance(path, pathlib.PurePath):
+        path = str(path)
+    if not isinstance(path, str):
+        raise TypeError("Video path must be a string or pathlib.Path.")
 
     cmd = [_get_exe(), "-i", path, "-map", "0:v:0", "-c", "copy", "-f", 
"null", "-"]
     try:
-        out = subprocess.check_output(cmd, stderr=subprocess.STDOUT, 
shell=ISWIN)
+        out = subprocess.check_output(cmd, stderr=subprocess.STDOUT, 
**_popen_kwargs())
     except subprocess.CalledProcessError as err:
         out = err.output.decode(errors="ignore")
         raise RuntimeError("FFMEG call failed with 
{}:\n{}".format(err.returncode, out))
@@ -60,16 +63,23 @@
     raise RuntimeError("Could not get number of frames")  # pragma: no cover
 
 
-def read_frames(path, pix_fmt="rgb24", bpp=3, input_params=None, 
output_params=None):
+def read_frames(
+    path,
+    pix_fmt="rgb24",
+    bpp=None,
+    input_params=None,
+    output_params=None,
+    bits_per_pixel=None,
+):
     """
     Create a generator to iterate over the frames in a video file.
     
     It first yields a small metadata dictionary that contains:
     
-    * ffmpeg_version: the ffmpeg version is use (as a string).
-    * codec: a hint about the codec used to encode the video, e.g. "h264"
-    * source_size: the width and height of the encoded video frames
-    * size: the width and height of the frames that will be produced
+    * ffmpeg_version: the ffmpeg version in use (as a string).
+    * codec: a hint about the codec used to encode the video, e.g. "h264".
+    * source_size: the width and height of the encoded video frames.
+    * size: the width and height of the frames that will be produced.
     * fps: the frames per second. Can be zero if it could not be detected.
     * duration: duration in seconds. Can be zero if it could not be detected.
     
@@ -92,27 +102,34 @@
             print(len(frame))
     
     Parameters:
-        path (str): the file to write to.
+        path (str): the filename of the file to read from.
         pix_fmt (str): the pixel format of the frames to be read.
             The default is "rgb24" (frames are uint8 RGB images).
-        bpp (int): The number of bytes per pixel in the output frames.
-            This depends on the given pix_fmt. Default is 3 (RGB).
         input_params (list): Additional ffmpeg input command line parameters.
         output_params (list): Additional ffmpeg output command line parameters.
+        bits_per_pixel (int): The number of bits per pixel in the output 
frames.
+            This depends on the given pix_fmt. Default is 24 (RGB)
+        bpp (int): DEPRECATED, USE bits_per_pixel INSTEAD. The number of bytes 
per pixel in the output frames.
+            This depends on the given pix_fmt. Some pixel formats like yuv420p 
have 12 bits per pixel
+            and cannot be set in bytes as integer. For this reason the bpp 
argument is deprecated.
     """
 
     # ----- Input args
 
-    assert isinstance(path, str), "Video path must be a string"
+    if isinstance(path, pathlib.PurePath):
+        path = str(path)
+    if not isinstance(path, str):
+        raise TypeError("Video path must be a string or pathlib.Path.")
     # Note: Dont check whether it exists. The source could be e.g. a camera.
 
     pix_fmt = pix_fmt or "rgb24"
     bpp = bpp or 3
+    bits_per_pixel = bits_per_pixel or bpp * 8
     input_params = input_params or []
     output_params = output_params or []
 
     assert isinstance(pix_fmt, str), "pix_fmt must be a string"
-    assert isinstance(bpp, int), "bpp must be an int"
+    assert isinstance(bits_per_pixel, int), "bpp and bits_per_pixel must be an 
int"
     assert isinstance(input_params, list), "input_params must be a list"
     assert isinstance(output_params, list), "output_params must be a list"
 
@@ -129,18 +146,25 @@
         stdin=subprocess.PIPE,
         stdout=subprocess.PIPE,
         stderr=subprocess.PIPE,
-        shell=ISWIN,
+        **_popen_kwargs(prevent_sigint=True)
     )
 
     log_catcher = LogCatcher(p.stderr)
 
+    # Init policy by which to terminate ffmpeg. May be set to "kill" later.
+    stop_policy = "timeout"  # not wait; ffmpeg should be able to quit quickly
+
+    # Enter try block directly after opening the process.
+    # We terminate ffmpeg in the finally clause.
+    # Generators are automatically closed when they get deleted,
+    # so the finally block is guaranteed to run.
     try:
 
         # ----- Load meta data
 
         # Wait for the log catcher to get the meta information
         etime = time.time() + 10.0
-        while (not log_catcher.header) and time.time() < etime:
+        while log_catcher.is_alive() and not log_catcher.header and 
time.time() < etime:
             time.sleep(0.01)
 
         # Check whether we have the information
@@ -157,15 +181,20 @@
         # ----- Read frames
 
         w, h = meta["size"]
-        framesize = w * h * bpp
+        framesize_bits = w * h * bits_per_pixel
+        framesize_bytes = framesize_bits / 8
+        assert (
+            framesize_bytes.is_integer()
+        ), "incorrect bits_per_pixel, framesize in bytes must be an int"
+        framesize_bytes = int(framesize_bytes)
         framenr = 0
 
         while True:
             framenr += 1
             try:
                 bb = bytes()
-                while len(bb) < framesize:
-                    extra_bytes = p.stdout.read(framesize - len(bb))
+                while len(bb) < framesize_bytes:
+                    extra_bytes = p.stdout.read(framesize_bytes - len(bb))
                     if not extra_bytes:
                         if len(bb) == 0:
                             return
@@ -181,31 +210,55 @@
                 fmt = "Could not read frame {}:\n{}\n=== stderr ===\n{}"
                 raise RuntimeError(fmt.format(framenr, err1, err2))
 
+    except GeneratorExit:
+        # Note that GeneratorExit does not inherit from Exception but 
BaseException
+        pass
+
+    except Exception:
+        # Normal exceptions fall through
+        raise
+
+    except BaseException:
+        # Detect KeyboardInterrupt / SystemExit: don't wait for ffmpeg to quit
+        stop_policy = "kill"
+        raise
+
     finally:
-        # Generators are automatically closed when they get deleted,
-        # so this code is almost guaranteed to run.
 
+        # Make sure that ffmpeg is terminated.
         if p.poll() is None:
 
             # Ask ffmpeg to quit
             try:
-                if True:
-                    p.communicate(b"q")
-                else:  # pragma: no cover
-                    # I read somewhere that modern ffmpeg on Linux prefers a
-                    # "ctrl-c", but tests so far suggests sending q is better.
-                    p.send_signal(signal.SIGINT)
+                # I read somewhere that modern ffmpeg on Linux prefers a
+                # "ctrl-c", but tests so far suggests sending q is more robust.
+                # > p.send_signal(signal.SIGINT)
+                # Sending q via communicate works, but can hang (see #17)
+                # > p.communicate(b"q")
+                # So let's do similar to what communicate does, but without
+                # reading stdout (which may block). It looks like only closing
+                # stdout is enough (tried Windows+Linux), but let's play safe.
+                # Found that writing to stdin can cause "Invalid argument" on
+                # Windows # and "Broken Pipe" on Unix.
+                # p.stdin.write(b"q")  # commented out in v0.4.1
+                p.stdout.close()
+                p.stdin.close()
             except Exception as err:  # pragma: no cover
-                logger.warning("Error while attempting stop ffmpeg: " + 
str(err))
+                logger.warning("Error while attempting stop ffmpeg (r): " + 
str(err))
 
-            # Wait for it to stop
-            etime = time.time() + 1.5
-            while time.time() < etime and p.poll() is None:
-                time.sleep(0.01)
-
-            # Grr, we have to kill it
-            if p.poll() is None:  # pragma: no cover
-                logger.warning("We had to kill ffmpeg to stop it.")
+            if stop_policy == "timeout":
+                # Wait until timeout, produce a warning and kill if it still 
exists
+                try:
+                    etime = time.time() + 1.5
+                    while time.time() < etime and p.poll() is None:
+                        time.sleep(0.01)
+                finally:
+                    if p.poll() is None:  # pragma: no cover
+                        logger.warning("We had to kill ffmpeg to stop it.")
+                        p.kill()
+
+            else:  # stop_policy == "kill"
+                # Just kill it
                 p.kill()
 
 
@@ -220,7 +273,7 @@
     codec=None,
     macro_block_size=16,
     ffmpeg_log_level="warning",
-    ffmpeg_timeout=20.0,
+    ffmpeg_timeout=None,
     input_params=None,
     output_params=None,
 ):
@@ -240,7 +293,7 @@
         gen.close()  # don't forget this
     
     Parameters:
-        path (str): the file to write to.
+        path (str): the filename to write to.
         size (tuple): the width and height of the frames.
         pix_fmt_in (str): the pixel format of incoming frames.
             E.g. "gray", "gray8a", "rgb24", or "rgba". Default "rgb24".
@@ -255,15 +308,18 @@
             to 1 to avoid block alignment, though this is not recommended.
         ffmpeg_log_level (str): The ffmpeg logging level. Default "warning".
         ffmpeg_timeout (float): Timeout in seconds to wait for ffmpeg process
-            to finish. Value of 0 will wait forever. The time that ffmpeg needs
-            depends on CPU speed, compression, and frame size. Default 20.0.
+            to finish. Value of 0 or None will wait forever (default). The 
time that
+            ffmpeg needs depends on CPU speed, compression, and frame size.
         input_params (list): Additional ffmpeg input command line parameters.
         output_params (list): Additional ffmpeg output command line parameters.
     """
 
     # ----- Input args
 
-    assert isinstance(path, str), "Video path must be a string"
+    if isinstance(path, pathlib.PurePath):
+        path = str(path)
+    if not isinstance(path, str):
+        raise TypeError("Video path must be a string or pathlib.Path.")
 
     # The pix_fmt_out yuv420p is the best for the outpur to work in
     # QuickTime and most other players. These players only support
@@ -281,6 +337,7 @@
     ffmpeg_log_level = ffmpeg_log_level or "warning"
     input_params = input_params or []
     output_params = output_params or []
+    ffmpeg_timeout = ffmpeg_timeout or 0
 
     floatish = float, int
     if isinstance(size, (tuple, list)):
@@ -376,20 +433,31 @@
 
     # Launch process
     p = subprocess.Popen(
-        cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None, 
shell=ISWIN
+        cmd,
+        stdin=subprocess.PIPE,
+        stdout=subprocess.PIPE,
+        stderr=None,
+        **_popen_kwargs(prevent_sigint=True)
     )
 
-    # For Windows, set `shell=True` in sp.Popen to prevent popup
-    # of a command line window in frozen applications.
     # Note that directing stderr to a pipe on windows will cause ffmpeg
     # to hang if the buffer is not periodically cleared using
     # StreamCatcher or other means.
     # Setting bufsize to 0 or a small value does not seem to have much effect
-    # (at least on Windows). I suspect that ffmpeg buffers # multiple frames
-    # (before encoding in a batch).
+    # (tried on Windows and Linux). I suspect that ffmpeg buffers
+    # multiple frames (before encoding in a batch).
+
+    # Init policy by which to terminate ffmpeg. May be set to "kill" later.
+    stop_policy = "timeout"
+    if not ffmpeg_timeout:
+        stop_policy = "wait"
 
     # ----- Write frames
 
+    # Enter try block directly after opening the process.
+    # We terminate ffmpeg in the finally clause.
+    # Generators are automatically closed when they get deleted,
+    # so the finally block is guaranteed to run.
     try:
 
         # Just keep going until the generator.close() is called (raises 
GeneratorExit).
@@ -398,7 +466,7 @@
         while True:
 
             # Get frame
-            bb = (yield)
+            bb = yield
 
             # framesize = size[0] * size[1] * depth * bpp
             # assert isinstance(bb, bytes), "Frame must be send as bytes"
@@ -420,29 +488,55 @@
             nframes += 1
 
     except GeneratorExit:
+        # Note that GeneratorExit does not inherit from Exception but 
BaseException
+        # Detect premature closing
         if nframes == 0:
             logger.warning("No frames have been written; the written video is 
invalid.")
+
+    except Exception:
+        # Normal exceptions fall through
+        raise
+
+    except BaseException:
+        # Detect KeyboardInterrupt / SystemExit: don't wait for ffmpeg to quit
+        stop_policy = "kill"
+        raise
+
     finally:
 
+        # Make sure that ffmpeg is terminated.
         if p.poll() is None:
 
-            # Ask ffmpeg to quit - and wait for it to finish writing the file.
-            # Depending on the frame size and encoding this can take a few
-            # seconds (sometimes 10-20). Since a user may get bored and hit
-            # Ctrl-C, we wrap this in a try-except.
-            waited = False
+            # Tell ffmpeg that we're done
             try:
+                p.stdin.close()
+            except Exception as err:  # pragma: no cover
+                logger.warning("Error while attempting stop ffmpeg (w): " + 
str(err))
+
+            if stop_policy == "timeout":
+                # Wait until timeout, produce a warning and kill if it still 
exists
+                try:
+                    etime = time.time() + ffmpeg_timeout
+                    while (time.time() < etime) and p.poll() is None:
+                        time.sleep(0.01)
+                finally:
+                    if p.poll() is None:  # pragma: no cover
+                        logger.warning(
+                            "We had to kill ffmpeg to stop it. "
+                            + "Consider increasing ffmpeg_timeout, "
+                            + "or setting it to zero (no timeout)."
+                        )
+                        p.kill()
+
+            elif stop_policy == "wait":
+                # Wait forever, kill if it if we're interrupted
                 try:
-                    p.stdin.close()
-                except Exception:  # pragma: no cover
-                    pass
-                etime = time.time() + ffmpeg_timeout
-                while (not ffmpeg_timeout or time.time() < etime) and p.poll() 
is None:
-                    time.sleep(0.01)
-                waited = True
-            finally:
-                # Grr, we have to kill it
-                if p.poll() is None:  # pragma: no cover
-                    more = " Consider increasing ffmpeg_timeout." if waited 
else ""
-                    logger.warning("We had to kill ffmpeg to stop it." + more)
-                    p.kill()
+                    while p.poll() is None:
+                        time.sleep(0.01)
+                finally:  # the above can raise e.g. by ctrl-c or systemexit
+                    if p.poll() is None:  # pragma: no cover
+                        p.kill()
+
+            else:  #  stop_policy == "kill":
+                # Just kill it
+                p.kill()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imageio-ffmpeg-0.3.0/imageio_ffmpeg/_utils.py 
new/imageio-ffmpeg-0.4.1/imageio_ffmpeg/_utils.py
--- old/imageio-ffmpeg-0.3.0/imageio_ffmpeg/_utils.py   2019-04-12 
11:07:51.000000000 +0200
+++ new/imageio-ffmpeg-0.4.1/imageio_ffmpeg/_utils.py   2020-02-24 
15:10:39.000000000 +0100
@@ -1,8 +1,8 @@
 import os
-from pkg_resources import resource_filename
 import sys
-import subprocess
 import logging
+import subprocess
+from pkg_resources import resource_filename
 
 from ._definitions import get_platform, FNAME_PER_PLATFORM
 
@@ -11,7 +11,7 @@
 
 def get_ffmpeg_exe():
     """
-    Get the ffmpeg executable file. This can be the binary defined by 
+    Get the ffmpeg executable file. This can be the binary defined by
     the IMAGEIO_FFMPEG_EXE environment variable, the binary distributed
     with imageio-ffmpeg, an ffmpeg binary installed with conda, or the
     system ffmpeg (in that order). A RuntimeError is raised if no valid
@@ -52,11 +52,35 @@
     )
 
 
+def _popen_kwargs(prevent_sigint=False):
+    startupinfo = None
+    preexec_fn = None
+    creationflags = 0
+    if sys.platform.startswith("win"):
+        # Stops executable from flashing on Windows (see #22)
+        startupinfo = subprocess.STARTUPINFO()
+        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+    if prevent_sigint:
+        # Prevent propagation of sigint (see #4)
+        # https://stackoverflow.com/questions/5045771
+        if sys.platform.startswith("win"):
+            creationflags = 0x00000200
+        else:
+            preexec_fn = os.setpgrp  # the _pre_exec does not seem to work
+    return {
+        "startupinfo": startupinfo,
+        "creationflags": creationflags,
+        "preexec_fn": preexec_fn,
+    }
+
+
 def _is_valid_exe(exe):
     cmd = [exe, "-version"]
     try:
         with open(os.devnull, "w") as null:
-            subprocess.check_call(cmd, stdout=null, stderr=subprocess.STDOUT)
+            subprocess.check_call(
+                cmd, stdout=null, stderr=subprocess.STDOUT, **_popen_kwargs()
+            )
         return True
     except (OSError, ValueError, subprocess.CalledProcessError):
         return False
@@ -67,7 +91,9 @@
     Get the version of the used ffmpeg executable (as a string).
     """
     exe = get_ffmpeg_exe()
-    line = subprocess.check_output([exe, "-version"]).split(b"\n", 1)[0]
+    line = subprocess.check_output([exe, "-version"], **_popen_kwargs()).split(
+        b"\n", 1
+    )[0]
     line = line.decode(errors="ignore").strip()
     version = line.split("version", 1)[-1].lstrip().split(" ", 1)[0].strip()
     return version
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/imageio-ffmpeg-0.3.0/imageio_ffmpeg.egg-info/PKG-INFO 
new/imageio-ffmpeg-0.4.1/imageio_ffmpeg.egg-info/PKG-INFO
--- old/imageio-ffmpeg-0.3.0/imageio_ffmpeg.egg-info/PKG-INFO   2019-04-12 
11:08:07.000000000 +0200
+++ new/imageio-ffmpeg-0.4.1/imageio_ffmpeg.egg-info/PKG-INFO   2020-02-24 
15:11:03.000000000 +0100
@@ -1,11 +1,11 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.2
 Name: imageio-ffmpeg
-Version: 0.3.0
+Version: 0.4.1
 Summary: FFMPEG wrapper for Python
 Home-page: https://github.com/imageio/imageio-ffmpeg
 Author: imageio contributors
 Author-email: almar.kl...@gmail.com
-License: (new) BSD
+License: BSD-2-Clause
 Download-URL: http://pypi.python.org/pypi/imageio-ffmpeg
 Description: FFMPEG wrapper for Python.
         
@@ -33,4 +33,6 @@
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
 Provides: imageio_ffmpeg
+Requires-Python: >=3.4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imageio-ffmpeg-0.3.0/setup.cfg 
new/imageio-ffmpeg-0.4.1/setup.cfg
--- old/imageio-ffmpeg-0.3.0/setup.cfg  2019-04-12 11:08:07.000000000 +0200
+++ new/imageio-ffmpeg-0.4.1/setup.cfg  2020-02-24 15:11:03.000000000 +0100
@@ -1,5 +1,4 @@
 [egg_info]
-tag_date = 0
 tag_build = 
-tag_svn_revision = 0
+tag_date = 0
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/imageio-ffmpeg-0.3.0/setup.py 
new/imageio-ffmpeg-0.4.1/setup.py
--- old/imageio-ffmpeg-0.3.0/setup.py   2019-04-12 11:07:51.000000000 +0200
+++ new/imageio-ffmpeg-0.4.1/setup.py   2020-02-24 14:13:52.000000000 +0100
@@ -48,7 +48,7 @@
     version=__version__,
     author="imageio contributors",
     author_email="almar.kl...@gmail.com",
-    license="(new) BSD",
+    license="BSD-2-Clause",
     url="https://github.com/imageio/imageio-ffmpeg";,
     download_url="http://pypi.python.org/pypi/imageio-ffmpeg";,
     keywords="video ffmpeg",
@@ -58,7 +58,7 @@
     provides=["imageio_ffmpeg"],
     python_requires=">=3.4",
     setup_requires=["pip>19"],
-    install_requires=[],  # todo: maybe numpy
+    install_requires=[],
     packages=["imageio_ffmpeg"],
     package_dir={"imageio_ffmpeg": "imageio_ffmpeg"},
     package_data={"imageio_ffmpeg": ["binaries/*.*"]},
@@ -79,5 +79,6 @@
         "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
     ],
 )


Reply via email to