diff --git a/ffmpeg/_ffmpeg.py b/ffmpeg/_ffmpeg.py index 007624bb..5f034cf6 100644 --- a/ffmpeg/_ffmpeg.py +++ b/ffmpeg/_ffmpeg.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from past.builtins import basestring -from ._utils import basestring +from ._utils import basestring, fspath from .nodes import ( filter_operator, @@ -23,7 +23,7 @@ def input(filename, **kwargs): Official documentation: `Main options `__ """ - kwargs['filename'] = filename + kwargs['filename'] = fspath(filename) fmt = kwargs.pop('f', None) if fmt: if 'format' in kwargs: @@ -79,9 +79,12 @@ def output(*streams_and_filename, **kwargs): """ streams_and_filename = list(streams_and_filename) if 'filename' not in kwargs: - if not isinstance(streams_and_filename[-1], basestring): + # Raise any errors without destructively modifying streams_and_filenames + try: + fspath(streams_and_filename[-1]) + except TypeError: raise ValueError('A filename must be provided') - kwargs['filename'] = streams_and_filename.pop(-1) + kwargs['filename'] = fspath(streams_and_filename.pop(-1)) streams = streams_and_filename fmt = kwargs.pop('f', None) diff --git a/ffmpeg/_utils.py b/ffmpeg/_utils.py index 9baa2c78..0be35742 100644 --- a/ffmpeg/_utils.py +++ b/ffmpeg/_utils.py @@ -106,3 +106,38 @@ def convert_kwargs_to_cmd_line_args(kwargs): if v is not None: args.append('{}'.format(v)) return args + + +if sys.version_info >= (3, 6): + from os import fspath +else: + # This code is mostly copy-pasted from PEP 519, with (str, bytes) instance + # checks converted to basestring instance checks. + def fspath(path): + """Return the string representation of the path. + + If str or bytes is passed in, it is returned unchanged. If __fspath__() + returns something other than str or bytes then TypeError is raised. If + this function is given something that is not str, bytes, or os.PathLike + then TypeError is raised. + """ + if isinstance(path, basestring): + return path + + # Work from the object's type to match method resolution of other magic + # methods. + path_type = type(path) + try: + path = path_type.__fspath__(path) + except AttributeError: + if hasattr(path_type, '__fspath__'): + raise + else: + if isinstance(path, basestring): + return path + else: + raise TypeError("expected __fspath__() to return str or bytes, " + "not " + type(path).__name__) + + raise TypeError("expected str, bytes or os.PathLike object, not " + + path_type.__name__) diff --git a/ffmpeg/tests/test_ffmpeg.py b/ffmpeg/tests/test_ffmpeg.py index 8dbc271a..4384ab9a 100644 --- a/ffmpeg/tests/test_ffmpeg.py +++ b/ffmpeg/tests/test_ffmpeg.py @@ -79,6 +79,14 @@ def test_fluent_complex_filter(): ).output('dummy2.mp4') +@pytest.mark.skipif(sys.version_info < (3, 6), reason='requires python3.6 or higher') +def test_pathlike_input_output(): + from pathlib import Path + base = ffmpeg.input(Path("dummy.mp4")) + base.output(filename=Path("dummy2.mp4")) + base.output(Path("dummy3.mp4")) + + def test_node_repr(): in_file = ffmpeg.input('dummy.mp4') trim1 = ffmpeg.trim(in_file, start_frame=10, end_frame=20)