diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..cf65206
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,44 @@
+name: CI
+on:
+ - push
+ - pull_request
+jobs:
+ test:
+ runs-on: ubuntu-20.04
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version:
+ - "2.7"
+ - "3.5"
+ - "3.6"
+ - "3.7"
+ - "3.8"
+ - "3.9"
+ - "3.10"
+ steps:
+ - uses: actions/checkout@v1
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install ffmpeg
+ run: |
+ sudo apt update
+ sudo apt install ffmpeg
+ - name: Setup pip + tox
+ run: |
+ python -m pip install --upgrade \
+ "pip==20.3.4; python_version < '3.6'" \
+ "pip==21.3.1; python_version >= '3.6'"
+ python -m pip install tox==3.24.5 tox-gh-actions==2.9.1
+ - name: Test with tox
+ run: tox
+ black:
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v2
+ - uses: psf/black@21.12b0 # TODO: upgrade after dropping Python 2 support.
+ with:
+ src: ffmpeg # TODO: also format `examples`.
+ version: 21.12b0
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index e4d7d75..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-language: python
-before_install:
- - >
- [ -f ffmpeg-release/ffmpeg ] || (
- curl -O https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz &&
- mkdir -p ffmpeg-release &&
- tar Jxf ffmpeg-release-amd64-static.tar.xz --strip-components=1 -C ffmpeg-release
- )
-matrix:
- include:
- - python: 2.7
- env: TOX_ENV=py27
- - python: 3.4
- env: TOX_ENV=py34
- - python: 3.5
- env: TOX_ENV=py35
- - python: 3.6
- env: TOX_ENV=py36
- - python: 3.7
- dist: xenial # required for Python >= 3.7
- env: TOX_ENV=py37
- - python: pypy
- env: TOX_ENV=pypy
-install:
- - pip install tox
-script:
- - export PATH=$(readlink -f ffmpeg-release):$PATH
- - tox -e $TOX_ENV
-cache:
- directories:
- - .tox
- - ffmpeg-release
diff --git a/README.md b/README.md
index 8cfe89a..b8ee922 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,9 @@
# ffmpeg-python: Python bindings for FFmpeg
-[](https://travis-ci.org/kkroening/ffmpeg-python)
+[![CI][ci-badge]][ci]
+
+[ci-badge]: https://github.com/kkroening/ffmpeg-python/actions/workflows/ci.yml/badge.svg
+[ci]: https://github.com/kkroening/ffmpeg-python/actions/workflows/ci.yml
@@ -78,9 +81,11 @@ Real-world signal graphs can get a heck of a lot more complex, but `ffmpeg-pytho
## Installation
+### Installing `ffmpeg-python`
+
The latest version of `ffmpeg-python` can be acquired via a typical pip install:
-```
+```bash
pip install ffmpeg-python
```
@@ -90,6 +95,24 @@ git clone git@github.com:kkroening/ffmpeg-python.git
pip install -e ./ffmpeg-python
```
+> **Note**: `ffmpeg-python` makes no attempt to download/install FFmpeg, as `ffmpeg-python` is merely a pure-Python wrapper - whereas FFmpeg installation is platform-dependent/environment-specific, and is thus the responsibility of the user, as described below.
+
+### Installing FFmpeg
+
+Before using `ffmpeg-python`, FFmpeg must be installed and accessible via the `$PATH` environment variable.
+
+There are a variety of ways to install FFmpeg, such as the [official download links](https://ffmpeg.org/download.html), or using your package manager of choice (e.g. `sudo apt install ffmpeg` on Debian/Ubuntu, `brew install ffmpeg` on OS X, etc.).
+
+Regardless of how FFmpeg is installed, you can check if your environment path is set correctly by running the `ffmpeg` command from the terminal, in which case the version information should appear, as in the following example (truncated for brevity):
+
+```
+$ ffmpeg
+ffmpeg version 4.2.4-1ubuntu0.1 Copyright (c) 2000-2020 the FFmpeg developers
+ built with gcc 9 (Ubuntu 9.3.0-10ubuntu2)
+```
+
+> **Note**: The actual version information displayed here may vary from one system to another; but if a message such as `ffmpeg: command not found` appears instead of the version information, FFmpeg is not properly installed.
+
## [Examples](https://github.com/kkroening/ffmpeg-python/tree/master/examples)
When in doubt, take a look at the [examples](https://github.com/kkroening/ffmpeg-python/tree/master/examples) to see if there's something that's close to whatever you're trying to do.
@@ -194,7 +217,7 @@ When in doubt, refer to the [existing filters](https://github.com/kkroening/ffmp
**Why do I get an import/attribute/etc. error from `import ffmpeg`?**
-Make sure you ran `pip install ffmpeg-python` and not `pip install ffmpeg` or `pip install python-ffmpeg`.
+Make sure you ran `pip install ffmpeg-python` and _**not**_ `pip install ffmpeg` (wrong) or `pip install python-ffmpeg` (also wrong).
**Why did my audio stream get dropped?**
diff --git a/examples/split_silence.py b/examples/split_silence.py
index a889db1..90b46d9 100755
--- a/examples/split_silence.py
+++ b/examples/split_silence.py
@@ -27,10 +27,10 @@ parser.add_argument('--start-time', type=float, help='Start time (seconds)')
parser.add_argument('--end-time', type=float, help='End time (seconds)')
parser.add_argument('-v', dest='verbose', action='store_true', help='Verbose mode')
-silence_start_re = re.compile(' silence_start: (?P[0-9]+(\.?[0-9]*))$')
-silence_end_re = re.compile(' silence_end: (?P[0-9]+(\.?[0-9]*)) ')
+silence_start_re = re.compile(r' silence_start: (?P[0-9]+(\.?[0-9]*))$')
+silence_end_re = re.compile(r' silence_end: (?P[0-9]+(\.?[0-9]*)) ')
total_duration_re = re.compile(
- 'size=[^ ]+ time=(?P[0-9]{2}):(?P[0-9]{2}):(?P[0-9\.]{5}) bitrate=')
+ r'size=[^ ]+ time=(?P[0-9]{2}):(?P[0-9]{2}):(?P[0-9\.]{5}) bitrate=')
def _logged_popen(cmd_line, *args, **kwargs):
diff --git a/ffmpeg/_ffmpeg.py b/ffmpeg/_ffmpeg.py
index 31e2b90..007624b 100644
--- a/ffmpeg/_ffmpeg.py
+++ b/ffmpeg/_ffmpeg.py
@@ -34,8 +34,7 @@ def input(filename, **kwargs):
@output_operator()
def global_args(stream, *args):
- """Add extra global command-line argument(s), e.g. ``-progress``.
- """
+ """Add extra global command-line argument(s), e.g. ``-progress``."""
return GlobalNode(stream, global_args.__name__, args).stream()
@@ -50,8 +49,7 @@ def overwrite_output(stream):
@output_operator()
def merge_outputs(*streams):
- """Include all given outputs in one ffmpeg command line
- """
+ """Include all given outputs in one ffmpeg command line"""
return MergeOutputsNode(streams, merge_outputs.__name__).stream()
diff --git a/ffmpeg/_filters.py b/ffmpeg/_filters.py
index c5a6ae9..5bca23d 100644
--- a/ffmpeg/_filters.py
+++ b/ffmpeg/_filters.py
@@ -8,9 +8,11 @@ from ._utils import escape_chars
def filter_multi_output(stream_spec, filter_name, *args, **kwargs):
"""Apply custom filter with one or more outputs.
- This is the same as ``filter`` except that the filter can produce more than one output.
+ This is the same as ``filter`` except that the filter can produce more than one
+ output.
- To reference an output stream, use either the ``.stream`` operator or bracket shorthand:
+ To reference an output stream, use either the ``.stream`` operator or bracket
+ shorthand:
Example:
@@ -30,9 +32,10 @@ def filter_multi_output(stream_spec, filter_name, *args, **kwargs):
def filter(stream_spec, filter_name, *args, **kwargs):
"""Apply custom filter.
- ``filter_`` is normally used by higher-level filter functions such as ``hflip``, but if a filter implementation
- is missing from ``ffmpeg-python``, you can call ``filter_`` directly to have ``ffmpeg-python`` pass the filter name
- and arguments to ffmpeg verbatim.
+ ``filter_`` is normally used by higher-level filter functions such as ``hflip``,
+ but if a filter implementation is missing from ``ffmpeg-python``, you can call
+ ``filter_`` directly to have ``ffmpeg-python`` pass the filter name and arguments
+ to ffmpeg verbatim.
Args:
stream_spec: a Stream, list of Streams, or label-to-Stream dictionary mapping
@@ -40,7 +43,8 @@ def filter(stream_spec, filter_name, *args, **kwargs):
*args: list of args to pass to ffmpeg verbatim
**kwargs: list of keyword-args to pass to ffmpeg verbatim
- The function name is suffixed with ``_`` in order avoid confusion with the standard python ``filter`` function.
+ The function name is suffixed with ``_`` in order avoid confusion with the standard
+ python ``filter`` function.
Example:
@@ -72,7 +76,8 @@ def setpts(stream, expr):
"""Change the PTS (presentation timestamp) of the input frames.
Args:
- expr: The expression which is evaluated for each frame to construct its timestamp.
+ expr: The expression which is evaluated for each frame to construct its
+ timestamp.
Official documentation: `setpts, asetpts `__
"""
@@ -84,14 +89,15 @@ def trim(stream, **kwargs):
"""Trim the input so that the output contains one continuous subpart of the input.
Args:
- start: Specify the time of the start of the kept section, i.e. the frame with the timestamp start will be the
- first frame in the output.
- end: Specify the time of the first frame that will be dropped, i.e. the frame immediately preceding the one
- with the timestamp end will be the last frame in the output.
- start_pts: This is the same as start, except this option sets the start timestamp in timebase units instead of
- seconds.
- end_pts: This is the same as end, except this option sets the end timestamp in timebase units instead of
- seconds.
+ start: Specify the time of the start of the kept section, i.e. the frame with
+ the timestamp start will be the first frame in the output.
+ end: Specify the time of the first frame that will be dropped, i.e. the frame
+ immediately preceding the one with the timestamp end will be the last frame
+ in the output.
+ start_pts: This is the same as start, except this option sets the start
+ timestamp in timebase units instead of seconds.
+ end_pts: This is the same as end, except this option sets the end timestamp in
+ timebase units instead of seconds.
duration: The maximum duration of the output in seconds.
start_frame: The number of the first frame that should be passed to the output.
end_frame: The number of the first frame that should be dropped.
@@ -106,14 +112,16 @@ def overlay(main_parent_node, overlay_parent_node, eof_action='repeat', **kwargs
"""Overlay one video on top of another.
Args:
- x: Set the expression for the x coordinates of the overlaid video on the main video. Default value is 0. In
- case the expression is invalid, it is set to a huge value (meaning that the overlay will not be displayed
- within the output visible area).
- y: Set the expression for the y coordinates of the overlaid video on the main video. Default value is 0. In
- case the expression is invalid, it is set to a huge value (meaning that the overlay will not be displayed
- within the output visible area).
- eof_action: The action to take when EOF is encountered on the secondary input; it accepts one of the following
- values:
+ x: Set the expression for the x coordinates of the overlaid video on the main
+ video. Default value is 0. In case the expression is invalid, it is set to
+ a huge value (meaning that the overlay will not be displayed within the
+ output visible area).
+ y: Set the expression for the y coordinates of the overlaid video on the main
+ video. Default value is 0. In case the expression is invalid, it is set to
+ a huge value (meaning that the overlay will not be displayed within the
+ output visible area).
+ eof_action: The action to take when EOF is encountered on the secondary input;
+ it accepts one of the following values:
* ``repeat``: Repeat the last frame (the default).
* ``endall``: End both streams.
@@ -122,12 +130,13 @@ def overlay(main_parent_node, overlay_parent_node, eof_action='repeat', **kwargs
eval: Set when the expressions for x, and y are evaluated.
It accepts the following values:
- * ``init``: only evaluate expressions once during the filter initialization or when a command is
- processed
+ * ``init``: only evaluate expressions once during the filter initialization
+ or when a command is processed
* ``frame``: evaluate expressions for each incoming frame
Default value is ``frame``.
- shortest: If set to 1, force the output to terminate when the shortest input terminates. Default value is 0.
+ shortest: If set to 1, force the output to terminate when the shortest input
+ terminates. Default value is 0.
format: Set the format for the output video.
It accepts the following values:
@@ -138,10 +147,12 @@ def overlay(main_parent_node, overlay_parent_node, eof_action='repeat', **kwargs
* ``gbrp``: force planar RGB output
Default value is ``yuv420``.
- rgb (deprecated): If set to 1, force the filter to accept inputs in the RGB color space. Default value is 0.
- This option is deprecated, use format instead.
- repeatlast: If set to 1, force the filter to draw the last overlay frame over the main input until the end of
- the stream. A value of 0 disables this behavior. Default value is 1.
+ rgb (deprecated): If set to 1, force the filter to accept inputs in the RGB
+ color space. Default value is 0. This option is deprecated, use format
+ instead.
+ repeatlast: If set to 1, force the filter to draw the last overlay frame over
+ the main input until the end of the stream. A value of 0 disables this
+ behavior. Default value is 1.
Official documentation: `overlay `__
"""
@@ -196,14 +207,20 @@ def drawbox(stream, x, y, width, height, color, thickness=None, **kwargs):
"""Draw a colored box on the input image.
Args:
- x: The expression which specifies the top left corner x coordinate of the box. It defaults to 0.
- y: The expression which specifies the top left corner y coordinate of the box. It defaults to 0.
- width: Specify the width of the box; if 0 interpreted as the input width. It defaults to 0.
- height: Specify the height of the box; if 0 interpreted as the input height. It defaults to 0.
- color: Specify the color of the box to write. For the general syntax of this option, check the "Color" section
- in the ffmpeg-utils manual. If the special value invert is used, the box edge color is the same as the
- video with inverted luma.
- thickness: The expression which sets the thickness of the box edge. Default value is 3.
+ x: The expression which specifies the top left corner x coordinate of the box.
+ It defaults to 0.
+ y: The expression which specifies the top left corner y coordinate of the box.
+ It defaults to 0.
+ width: Specify the width of the box; if 0 interpreted as the input width. It
+ defaults to 0.
+ height: Specify the height of the box; if 0 interpreted as the input height. It
+ defaults to 0.
+ color: Specify the color of the box to write. For the general syntax of this
+ option, check the "Color" section in the ffmpeg-utils manual. If the
+ special value invert is used, the box edge color is the same as the video
+ with inverted luma.
+ thickness: The expression which sets the thickness of the box edge. Default
+ value is 3.
w: Alias for ``width``.
h: Alias for ``height``.
c: Alias for ``color``.
@@ -220,46 +237,57 @@ def drawbox(stream, x, y, width, height, color, thickness=None, **kwargs):
@filter_operator()
def drawtext(stream, text=None, x=0, y=0, escape_text=True, **kwargs):
- """Draw a text string or text from a specified file on top of a video, using the libfreetype library.
+ """Draw a text string or text from a specified file on top of a video, using the
+ libfreetype library.
- To enable compilation of this filter, you need to configure FFmpeg with ``--enable-libfreetype``. To enable default
- font fallback and the font option you need to configure FFmpeg with ``--enable-libfontconfig``. To enable the
+ To enable compilation of this filter, you need to configure FFmpeg with
+ ``--enable-libfreetype``. To enable default font fallback and the font option you
+ need to configure FFmpeg with ``--enable-libfontconfig``. To enable the
text_shaping option, you need to configure FFmpeg with ``--enable-libfribidi``.
Args:
- box: Used to draw a box around text using the background color. The value must be either 1 (enable) or 0
- (disable). The default value of box is 0.
- boxborderw: Set the width of the border to be drawn around the box using boxcolor. The default value of
- boxborderw is 0.
- boxcolor: The color to be used for drawing box around text. For the syntax of this option, check the "Color"
- section in the ffmpeg-utils manual. The default value of boxcolor is "white".
- line_spacing: Set the line spacing in pixels of the border to be drawn around the box using box. The default
- value of line_spacing is 0.
- borderw: Set the width of the border to be drawn around the text using bordercolor. The default value of
- borderw is 0.
- bordercolor: Set the color to be used for drawing border around text. For the syntax of this option, check the
- "Color" section in the ffmpeg-utils manual. The default value of bordercolor is "black".
- expansion: Select how the text is expanded. Can be either none, strftime (deprecated) or normal (default). See
- the Text expansion section below for details.
- basetime: Set a start time for the count. Value is in microseconds. Only applied in the deprecated strftime
- expansion mode. To emulate in normal expansion mode use the pts function, supplying the start time (in
- seconds) as the second argument.
+ box: Used to draw a box around text using the background color. The value must
+ be either 1 (enable) or 0 (disable). The default value of box is 0.
+ boxborderw: Set the width of the border to be drawn around the box using
+ boxcolor. The default value of boxborderw is 0.
+ boxcolor: The color to be used for drawing box around text. For the syntax of
+ this option, check the "Color" section in the ffmpeg-utils manual. The
+ default value of boxcolor is "white".
+ line_spacing: Set the line spacing in pixels of the border to be drawn around
+ the box using box. The default value of line_spacing is 0.
+ borderw: Set the width of the border to be drawn around the text using
+ bordercolor. The default value of borderw is 0.
+ bordercolor: Set the color to be used for drawing border around text. For the
+ syntax of this option, check the "Color" section in the ffmpeg-utils
+ manual. The default value of bordercolor is "black".
+ expansion: Select how the text is expanded. Can be either none, strftime
+ (deprecated) or normal (default). See the Text expansion section below for
+ details.
+ basetime: Set a start time for the count. Value is in microseconds. Only
+ applied in the deprecated strftime expansion mode. To emulate in normal
+ expansion mode use the pts function, supplying the start time (in seconds)
+ as the second argument.
fix_bounds: If true, check and fix text coords to avoid clipping.
- fontcolor: The color to be used for drawing fonts. For the syntax of this option, check the "Color" section in
- the ffmpeg-utils manual. The default value of fontcolor is "black".
- fontcolor_expr: String which is expanded the same way as text to obtain dynamic fontcolor value. By default
- this option has empty value and is not processed. When this option is set, it overrides fontcolor option.
+ fontcolor: The color to be used for drawing fonts. For the syntax of this
+ option, check the "Color" section in the ffmpeg-utils manual. The default
+ value of fontcolor is "black".
+ fontcolor_expr: String which is expanded the same way as text to obtain dynamic
+ fontcolor value. By default this option has empty value and is not
+ processed. When this option is set, it overrides fontcolor option.
font: The font family to be used for drawing text. By default Sans.
- fontfile: The font file to be used for drawing text. The path must be included. This parameter is mandatory if
- the fontconfig support is disabled.
- alpha: Draw the text applying alpha blending. The value can be a number between 0.0 and 1.0. The expression
- accepts the same variables x, y as well. The default value is 1. Please see fontcolor_expr.
- fontsize: The font size to be used for drawing text. The default value of fontsize is 16.
- text_shaping: If set to 1, attempt to shape the text (for example, reverse the order of right-to-left text and
- join Arabic characters) before drawing it. Otherwise, just draw the text exactly as given. By default 1 (if
- supported).
- ft_load_flags: The flags to be used for loading the fonts. The flags map the corresponding flags supported by
- libfreetype, and are a combination of the following values:
+ fontfile: The font file to be used for drawing text. The path must be included.
+ This parameter is mandatory if the fontconfig support is disabled.
+ alpha: Draw the text applying alpha blending. The value can be a number between
+ 0.0 and 1.0. The expression accepts the same variables x, y as well. The
+ default value is 1. Please see fontcolor_expr.
+ fontsize: The font size to be used for drawing text. The default value of
+ fontsize is 16.
+ text_shaping: If set to 1, attempt to shape the text (for example, reverse the
+ order of right-to-left text and join Arabic characters) before drawing it.
+ Otherwise, just draw the text exactly as given. By default 1 (if supported).
+ ft_load_flags: The flags to be used for loading the fonts. The flags map the
+ corresponding flags supported by libfreetype, and are a combination of the
+ following values:
* ``default``
* ``no_scale``
@@ -277,75 +305,89 @@ def drawtext(stream, text=None, x=0, y=0, escape_text=True, **kwargs):
* ``linear_design``
* ``no_autohint``
- Default value is "default". For more information consult the documentation for the FT_LOAD_* libfreetype
- flags.
- shadowcolor: The color to be used for drawing a shadow behind the drawn text. For the syntax of this option,
- check the "Color" section in the ffmpeg-utils manual. The default value of shadowcolor is "black".
- shadowx: The x offset for the text shadow position with respect to the position of the text. It can be either
- positive or negative values. The default value is "0".
- shadowy: The y offset for the text shadow position with respect to the position of the text. It can be either
- positive or negative values. The default value is "0".
- start_number: The starting frame number for the n/frame_num variable. The default value is "0".
- tabsize: The size in number of spaces to use for rendering the tab. Default value is 4.
- timecode: Set the initial timecode representation in "hh:mm:ss[:;.]ff" format. It can be used with or without
- text parameter. timecode_rate option must be specified.
+ Default value is "default". For more information consult the documentation
+ for the FT_LOAD_* libfreetype flags.
+ shadowcolor: The color to be used for drawing a shadow behind the drawn text.
+ For the syntax of this option, check the "Color" section in the ffmpeg-utils
+ manual. The default value of shadowcolor is "black".
+ shadowx: The x offset for the text shadow position with respect to the position
+ of the text. It can be either positive or negative values. The default value
+ is "0".
+ shadowy: The y offset for the text shadow position with respect to the position
+ of the text. It can be either positive or negative values. The default value
+ is "0".
+ start_number: The starting frame number for the n/frame_num variable. The
+ default value is "0".
+ tabsize: The size in number of spaces to use for rendering the tab. Default
+ value is 4.
+ timecode: Set the initial timecode representation in "hh:mm:ss[:;.]ff" format.
+ It can be used with or without text parameter. timecode_rate option must be
+ specified.
rate: Set the timecode frame rate (timecode only).
timecode_rate: Alias for ``rate``.
r: Alias for ``rate``.
- tc24hmax: If set to 1, the output of the timecode option will wrap around at 24 hours. Default is 0 (disabled).
- text: The text string to be drawn. The text must be a sequence of UTF-8 encoded characters. This parameter is
- mandatory if no file is specified with the parameter textfile.
- textfile: A text file containing text to be drawn. The text must be a sequence of UTF-8 encoded characters.
- This parameter is mandatory if no text string is specified with the parameter text. If both text and
- textfile are specified, an error is thrown.
- reload: If set to 1, the textfile will be reloaded before each frame. Be sure to update it atomically, or it
- may be read partially, or even fail.
- x: The expression which specifies the offset where text will be drawn within the video frame. It is relative to
- the left border of the output image. The default value is "0".
- y: The expression which specifies the offset where text will be drawn within the video frame. It is relative to
- the top border of the output image. The default value is "0". See below for the list of accepted constants
- and functions.
+ tc24hmax: If set to 1, the output of the timecode option will wrap around at 24
+ hours. Default is 0 (disabled).
+ text: The text string to be drawn. The text must be a sequence of UTF-8 encoded
+ characters. This parameter is mandatory if no file is specified with the
+ parameter textfile.
+ textfile: A text file containing text to be drawn. The text must be a sequence
+ of UTF-8 encoded characters. This parameter is mandatory if no text string
+ is specified with the parameter text. If both text and textfile are
+ specified, an error is thrown.
+ reload: If set to 1, the textfile will be reloaded before each frame. Be sure
+ to update it atomically, or it may be read partially, or even fail.
+ x: The expression which specifies the offset where text will be drawn within
+ the video frame. It is relative to the left border of the output image. The
+ default value is "0".
+ y: The expression which specifies the offset where text will be drawn within
+ the video frame. It is relative to the top border of the output image. The
+ default value is "0". See below for the list of accepted constants and
+ functions.
Expression constants:
- The parameters for x and y are expressions containing the following constants and functions:
- - dar: input display aspect ratio, it is the same as ``(w / h) * sar``
- - hsub: horizontal chroma subsample values. For example for the pixel format "yuv422p" hsub is 2 and vsub
- is 1.
- - vsub: vertical chroma subsample values. For example for the pixel format "yuv422p" hsub is 2 and vsub
- is 1.
- - line_h: the height of each text line
- - lh: Alias for ``line_h``.
- - main_h: the input height
- - h: Alias for ``main_h``.
- - H: Alias for ``main_h``.
- - main_w: the input width
- - w: Alias for ``main_w``.
- - W: Alias for ``main_w``.
- - ascent: the maximum distance from the baseline to the highest/upper grid coordinate used to place a glyph
- outline point, for all the rendered glyphs. It is a positive value, due to the grid's orientation with the Y
- axis upwards.
- - max_glyph_a: Alias for ``ascent``.
- - descent: the maximum distance from the baseline to the lowest grid coordinate used to place a glyph outline
- point, for all the rendered glyphs. This is a negative value, due to the grid's orientation, with the Y axis
- upwards.
- - max_glyph_d: Alias for ``descent``.
- - max_glyph_h: maximum glyph height, that is the maximum height for all the glyphs contained in the rendered
- text, it is equivalent to ascent - descent.
- - max_glyph_w: maximum glyph width, that is the maximum width for all the glyphs contained in the rendered
- text.
- - n: the number of input frame, starting from 0
- - rand(min, max): return a random number included between min and max
- - sar: The input sample aspect ratio.
- - t: timestamp expressed in seconds, NAN if the input timestamp is unknown
- - text_h: the height of the rendered text
- - th: Alias for ``text_h``.
- - text_w: the width of the rendered text
- - tw: Alias for ``text_w``.
- - x: the x offset coordinates where the text is drawn.
- - y: the y offset coordinates where the text is drawn.
+ The parameters for x and y are expressions containing the following constants
+ and functions:
+ - dar: input display aspect ratio, it is the same as ``(w / h) * sar``
+ - hsub: horizontal chroma subsample values. For example for the pixel format
+ "yuv422p" hsub is 2 and vsub is 1.
+ - vsub: vertical chroma subsample values. For example for the pixel format
+ "yuv422p" hsub is 2 and vsub is 1.
+ - line_h: the height of each text line
+ - lh: Alias for ``line_h``.
+ - main_h: the input height
+ - h: Alias for ``main_h``.
+ - H: Alias for ``main_h``.
+ - main_w: the input width
+ - w: Alias for ``main_w``.
+ - W: Alias for ``main_w``.
+ - ascent: the maximum distance from the baseline to the highest/upper grid
+ coordinate used to place a glyph outline point, for all the rendered glyphs.
+ It is a positive value, due to the grid's orientation with the Y axis
+ upwards.
+ - max_glyph_a: Alias for ``ascent``.
+ - descent: the maximum distance from the baseline to the lowest grid
+ coordinate used to place a glyph outline
+ point, for all the rendered glyphs. This is a negative value, due to the
+ grid's orientation, with the Y axis upwards.
+ - max_glyph_d: Alias for ``descent``.
+ - max_glyph_h: maximum glyph height, that is the maximum height for all the
+ glyphs contained in the rendered text, it is equivalent to ascent - descent.
+ - max_glyph_w: maximum glyph width, that is the maximum width for all the
+ glyphs contained in the rendered text.
+ - n: the number of input frame, starting from 0
+ - rand(min, max): return a random number included between min and max
+ - sar: The input sample aspect ratio.
+ - t: timestamp expressed in seconds, NAN if the input timestamp is unknown
+ - text_h: the height of the rendered text
+ - th: Alias for ``text_h``.
+ - text_w: the width of the rendered text
+ - tw: Alias for ``text_w``.
+ - x: the x offset coordinates where the text is drawn.
+ - y: the y offset coordinates where the text is drawn.
- These parameters allow the x and y expressions to refer each other, so you can for example specify
- ``y=x/dar``.
+ These parameters allow the x and y expressions to refer each other, so you can
+ for example specify ``y=x/dar``.
Official documentation: `drawtext `__
"""
@@ -364,25 +406,28 @@ def drawtext(stream, text=None, x=0, y=0, escape_text=True, **kwargs):
def concat(*streams, **kwargs):
"""Concatenate audio and video streams, joining them together one after the other.
- The filter works on segments of synchronized video and audio streams. All segments must have the same number of
- streams of each type, and that will also be the number of streams at output.
+ The filter works on segments of synchronized video and audio streams. All segments
+ must have the same number of streams of each type, and that will also be the number
+ of streams at output.
Args:
unsafe: Activate unsafe mode: do not fail if segments have a different format.
- Related streams do not always have exactly the same duration, for various reasons including codec frame size or
- sloppy authoring. For that reason, related synchronized streams (e.g. a video and its audio track) should be
- concatenated at once. The concat filter will use the duration of the longest stream in each segment (except the
- last one), and if necessary pad shorter audio streams with silence.
+ Related streams do not always have exactly the same duration, for various reasons
+ including codec frame size or sloppy authoring. For that reason, related
+ synchronized streams (e.g. a video and its audio track) should be concatenated at
+ once. The concat filter will use the duration of the longest stream in each segment
+ (except the last one), and if necessary pad shorter audio streams with silence.
For this filter to work correctly, all segments must start at timestamp 0.
- All corresponding streams must have the same parameters in all segments; the filtering system will automatically
- select a common pixel format for video streams, and a common sample format, sample rate and channel layout for
- audio streams, but other settings, such as resolution, must be converted explicitly by the user.
+ All corresponding streams must have the same parameters in all segments; the
+ filtering system will automatically select a common pixel format for video streams,
+ and a common sample format, sample rate and channel layout for audio streams, but
+ other settings, such as resolution, must be converted explicitly by the user.
- Different frame rates are acceptable but will result in variable frame rate at output; be sure to configure the
- output file to handle it.
+ Different frame rates are acceptable but will result in variable frame rate at
+ output; be sure to configure the output file to handle it.
Official documentation: `concat `__
"""
@@ -407,8 +452,8 @@ def zoompan(stream, **kwargs):
zoom: Set the zoom expression. Default is 1.
x: Set the x expression. Default is 0.
y: Set the y expression. Default is 0.
- d: Set the duration expression in number of frames. This sets for how many number of frames effect will last
- for single input image.
+ d: Set the duration expression in number of frames. This sets for how many
+ number of frames effect will last for single input image.
s: Set the output image size, default is ``hd720``.
fps: Set the output frame rate, default is 25.
z: Alias for ``zoom``.
@@ -423,10 +468,14 @@ def hue(stream, **kwargs):
"""Modify the hue and/or the saturation of the input.
Args:
- h: Specify the hue angle as a number of degrees. It accepts an expression, and defaults to "0".
- s: Specify the saturation in the [-10,10] range. It accepts an expression and defaults to "1".
- H: Specify the hue angle as a number of radians. It accepts an expression, and defaults to "0".
- b: Specify the brightness in the [-10,10] range. It accepts an expression and defaults to "0".
+ h: Specify the hue angle as a number of degrees. It accepts an expression, and
+ defaults to "0".
+ s: Specify the saturation in the [-10,10] range. It accepts an expression and
+ defaults to "1".
+ H: Specify the hue angle as a number of radians. It accepts an expression, and
+ defaults to "0".
+ b: Specify the brightness in the [-10,10] range. It accepts an expression and
+ defaults to "0".
Official documentation: `hue `__
"""
diff --git a/ffmpeg/_run.py b/ffmpeg/_run.py
index eadb90a..f42d1d7 100644
--- a/ffmpeg/_run.py
+++ b/ffmpeg/_run.py
@@ -3,7 +3,6 @@ from .dag import get_outgoing_edges, topo_sort
from ._utils import basestring, convert_kwargs_to_cmd_line_args
from builtins import str
from functools import reduce
-import collections
import copy
import operator
import subprocess
@@ -18,6 +17,11 @@ from .nodes import (
output_operator,
)
+try:
+ from collections.abc import Iterable
+except ImportError:
+ from collections import Iterable
+
class Error(Exception):
def __init__(self, cmd, stdout, stderr):
@@ -88,8 +92,8 @@ def _allocate_filter_stream_names(filter_nodes, outgoing_edge_maps, stream_name_
if len(downstreams) > 1:
# TODO: automatically insert `splits` ahead of time via graph transformation.
raise ValueError(
- 'Encountered {} with multiple outgoing edges with same upstream label {!r}; a '
- '`split` filter is probably required'.format(
+ 'Encountered {} with multiple outgoing edges with same upstream '
+ 'label {!r}; a `split` filter is probably required'.format(
upstream_node, upstream_label
)
)
@@ -136,9 +140,7 @@ def _get_output_args(node, stream_name_map):
args += ['-b:a', str(kwargs.pop('audio_bitrate'))]
if 'video_size' in kwargs:
video_size = kwargs.pop('video_size')
- if not isinstance(video_size, basestring) and isinstance(
- video_size, collections.Iterable
- ):
+ if not isinstance(video_size, basestring) and isinstance(video_size, Iterable):
video_size = '{}x{}'.format(video_size[0], video_size[1])
args += ['-video_size', video_size]
args += convert_kwargs_to_cmd_line_args(kwargs)
@@ -199,7 +201,7 @@ def run_async(
pipe_stderr=False,
quiet=False,
overwrite_output=False,
- cwd=None
+ cwd=None,
):
"""Asynchronously invoke ffmpeg for the supplied node graph.
@@ -286,8 +288,11 @@ def run_async(
stderr_stream = subprocess.STDOUT
stdout_stream = subprocess.DEVNULL
return subprocess.Popen(
- args, stdin=stdin_stream, stdout=stdout_stream, stderr=stderr_stream,
- cwd=cwd
+ args,
+ stdin=stdin_stream,
+ stdout=stdout_stream,
+ stderr=stderr_stream,
+ cwd=cwd,
)
@@ -300,7 +305,7 @@ def run(
input=None,
quiet=False,
overwrite_output=False,
- cwd=None
+ cwd=None,
):
"""Invoke ffmpeg for the supplied node graph.
@@ -324,7 +329,7 @@ def run(
pipe_stderr=capture_stderr,
quiet=quiet,
overwrite_output=overwrite_output,
- cwd=cwd
+ cwd=cwd,
)
out, err = process.communicate(input)
retcode = process.poll()
@@ -333,4 +338,10 @@ def run(
return out, err
-__all__ = ['compile', 'Error', 'get_args', 'run', 'run_async']
+__all__ = [
+ 'compile',
+ 'Error',
+ 'get_args',
+ 'run',
+ 'run_async',
+]
diff --git a/ffmpeg/_utils.py b/ffmpeg/_utils.py
index 92d7611..9baa2c7 100644
--- a/ffmpeg/_utils.py
+++ b/ffmpeg/_utils.py
@@ -3,13 +3,17 @@ from builtins import str
from past.builtins import basestring
import hashlib
import sys
-import collections
if sys.version_info.major == 2:
# noinspection PyUnresolvedReferences,PyShadowingBuiltins
str = str
+try:
+ from collections.abc import Iterable
+except ImportError:
+ from collections import Iterable
+
# `past.builtins.basestring` module can't be imported on Python3 in some environments (Ubuntu).
# This code is copy-pasted from it to avoid crashes.
@@ -40,7 +44,6 @@ if sys.version_info.major >= 3:
class basestring(with_metaclass(BaseBaseString)):
pass
-
else:
# noinspection PyUnresolvedReferences,PyCompatibility
from builtins import basestring
@@ -49,8 +52,8 @@ else:
def _recursive_repr(item):
"""Hack around python `repr` to deterministically represent dictionaries.
- This is able to represent more things than json.dumps, since it does not require things to be JSON serializable
- (e.g. datetimes).
+ This is able to represent more things than json.dumps, since it does not require
+ things to be JSON serializable (e.g. datetimes).
"""
if isinstance(item, basestring):
result = str(item)
@@ -93,7 +96,7 @@ def convert_kwargs_to_cmd_line_args(kwargs):
args = []
for k in sorted(kwargs.keys()):
v = kwargs[k]
- if isinstance(v, collections.Iterable) and not isinstance(v, str):
+ if isinstance(v, Iterable) and not isinstance(v, str):
for value in v:
args.append('-{}'.format(k))
if value is not None:
diff --git a/ffmpeg/_view.py b/ffmpeg/_view.py
index fb129fa..31955af 100644
--- a/ffmpeg/_view.py
+++ b/ffmpeg/_view.py
@@ -35,8 +35,8 @@ def view(stream_spec, detail=False, filename=None, pipe=False, **kwargs):
import graphviz
except ImportError:
raise ImportError(
- 'failed to import graphviz; please make sure graphviz is installed (e.g. `pip install '
- 'graphviz`)'
+ 'failed to import graphviz; please make sure graphviz is installed (e.g. '
+ '`pip install graphviz`)'
)
show_labels = kwargs.pop('show_labels', True)
diff --git a/ffmpeg/dag.py b/ffmpeg/dag.py
index 9564d7f..3508dd4 100644
--- a/ffmpeg/dag.py
+++ b/ffmpeg/dag.py
@@ -9,38 +9,45 @@ class DagNode(object):
"""Node in a directed-acyclic graph (DAG).
Edges:
- DagNodes are connected by edges. An edge connects two nodes with a label for each side:
+ DagNodes are connected by edges. An edge connects two nodes with a label for
+ each side:
- ``upstream_node``: upstream/parent node
- ``upstream_label``: label on the outgoing side of the upstream node
- ``downstream_node``: downstream/child node
- ``downstream_label``: label on the incoming side of the downstream node
- For example, DagNode A may be connected to DagNode B with an edge labelled "foo" on A's side, and "bar" on B's
- side:
+ For example, DagNode A may be connected to DagNode B with an edge labelled
+ "foo" on A's side, and "bar" on B's side:
_____ _____
| | | |
| A >[foo]---[bar]> B |
|_____| |_____|
- Edge labels may be integers or strings, and nodes cannot have more than one incoming edge with the same label.
+ Edge labels may be integers or strings, and nodes cannot have more than one
+ incoming edge with the same label.
- DagNodes may have any number of incoming edges and any number of outgoing edges. DagNodes keep track only of
- their incoming edges, but the entire graph structure can be inferred by looking at the furthest downstream
- nodes and working backwards.
+ DagNodes may have any number of incoming edges and any number of outgoing
+ edges. DagNodes keep track only of their incoming edges, but the entire graph
+ structure can be inferred by looking at the furthest downstream nodes and
+ working backwards.
Hashing:
- DagNodes must be hashable, and two nodes are considered to be equivalent if they have the same hash value.
+ DagNodes must be hashable, and two nodes are considered to be equivalent if
+ they have the same hash value.
- Nodes are immutable, and the hash should remain constant as a result. If a node with new contents is required,
- create a new node and throw the old one away.
+ Nodes are immutable, and the hash should remain constant as a result. If a
+ node with new contents is required, create a new node and throw the old one
+ away.
String representation:
- In order for graph visualization tools to show useful information, nodes must be representable as strings. The
- ``repr`` operator should provide a more or less "full" representation of the node, and the ``short_repr``
- property should be a shortened, concise representation.
+ In order for graph visualization tools to show useful information, nodes must
+ be representable as strings. The ``repr`` operator should provide a more or
+ less "full" representation of the node, and the ``short_repr`` property should
+ be a shortened, concise representation.
- Again, because nodes are immutable, the string representations should remain constant.
+ Again, because nodes are immutable, the string representations should remain
+ constant.
"""
def __hash__(self):
@@ -48,7 +55,9 @@ class DagNode(object):
raise NotImplementedError()
def __eq__(self, other):
- """Compare two nodes; implementations should return True if (and only if) hashes match."""
+ """Compare two nodes; implementations should return True if (and only if)
+ hashes match.
+ """
raise NotImplementedError()
def __repr__(self, other):
@@ -64,8 +73,9 @@ class DagNode(object):
def incoming_edge_map(self):
"""Provides information about all incoming edges that connect to this node.
- The edge map is a dictionary that maps an ``incoming_label`` to ``(outgoing_node, outgoing_label)``. Note that
- implicity, ``incoming_node`` is ``self``. See "Edges" section above.
+ The edge map is a dictionary that maps an ``incoming_label`` to
+ ``(outgoing_node, outgoing_label)``. Note that implicity, ``incoming_node`` is
+ ``self``. See "Edges" section above.
"""
raise NotImplementedError()
@@ -116,8 +126,7 @@ def get_outgoing_edges(upstream_node, outgoing_edge_map):
class KwargReprNode(DagNode):
- """A DagNode that can be represented as a set of args+kwargs.
- """
+ """A DagNode that can be represented as a set of args+kwargs."""
@property
def __upstream_hashes(self):
diff --git a/ffmpeg/nodes.py b/ffmpeg/nodes.py
index cacab8e..e8b2838 100644
--- a/ffmpeg/nodes.py
+++ b/ffmpeg/nodes.py
@@ -21,7 +21,9 @@ def _get_types_str(types):
class Stream(object):
- """Represents the outgoing edge of an upstream node; may be used to create more downstream nodes."""
+ """Represents the outgoing edge of an upstream node; may be used to create more
+ downstream nodes.
+ """
def __init__(
self, upstream_node, upstream_label, node_types, upstream_selector=None
@@ -214,9 +216,10 @@ class Node(KwargReprNode):
return self.__outgoing_stream_type(self, label, upstream_selector=selector)
def __getitem__(self, item):
- """Create an outgoing stream originating from this node; syntactic sugar for ``self.stream(label)``.
- It can also be used to apply a selector: e.g. ``node[0:'a']`` returns a stream with label 0 and
- selector ``'a'``, which is the same as ``node.stream(label=0, selector='a')``.
+ """Create an outgoing stream originating from this node; syntactic sugar for
+ ``self.stream(label)``. It can also be used to apply a selector: e.g.
+ ``node[0:'a']`` returns a stream with label 0 and selector ``'a'``, which is
+ the same as ``node.stream(label=0, selector='a')``.
Example:
Process the audio and video portions of a stream independently::
diff --git a/ffmpeg/tests/test_ffmpeg.py b/ffmpeg/tests/test_ffmpeg.py
index 4a8183c..8dbc271 100644
--- a/ffmpeg/tests/test_ffmpeg.py
+++ b/ffmpeg/tests/test_ffmpeg.py
@@ -30,7 +30,7 @@ subprocess.check_call(['ffmpeg', '-version'])
def test_escape_chars():
- assert ffmpeg._utils.escape_chars('a:b', ':') == 'a\:b'
+ assert ffmpeg._utils.escape_chars('a:b', ':') == r'a\:b'
assert ffmpeg._utils.escape_chars('a\\:b', ':\\') == 'a\\\\\\:b'
assert (
ffmpeg._utils.escape_chars('a:b,c[d]e%{}f\'g\'h\\i', '\\\':,[]%')
@@ -116,9 +116,20 @@ def test_stream_repr():
dummy_out.label, dummy_out.node.short_hash
)
+
def test_repeated_args():
- out_file = ffmpeg.input('dummy.mp4').output('dummy2.mp4', streamid=['0:0x101', '1:0x102'])
- assert out_file.get_args() == ['-i', 'dummy.mp4', '-streamid', '0:0x101', '-streamid', '1:0x102', 'dummy2.mp4']
+ out_file = ffmpeg.input('dummy.mp4').output(
+ 'dummy2.mp4', streamid=['0:0x101', '1:0x102']
+ )
+ assert out_file.get_args() == [
+ '-i',
+ 'dummy.mp4',
+ '-streamid',
+ '0:0x101',
+ '-streamid',
+ '1:0x102',
+ 'dummy2.mp4',
+ ]
def test__get_args__simple():
@@ -332,8 +343,13 @@ def test_filter_asplit():
'-i',
TEST_INPUT_FILE1,
'-filter_complex',
- '[0]vflip[s0];[s0]asplit=2[s1][s2];[s1]atrim=end=20:start=10[s3];[s2]atrim=end=40:start=30[s4];[s3]'
- '[s4]concat=n=2[s5]',
+ (
+ '[0]vflip[s0];'
+ '[s0]asplit=2[s1][s2];'
+ '[s1]atrim=end=20:start=10[s3];'
+ '[s2]atrim=end=40:start=30[s4];'
+ '[s3][s4]concat=n=2[s5]'
+ ),
'-map',
'[s5]',
TEST_OUTPUT_FILE1,
@@ -357,10 +373,14 @@ def test__output__video_size(video_size):
def test_filter_normal_arg_escape():
- """Test string escaping of normal filter args (e.g. ``font`` param of ``drawtext`` filter)."""
+ """Test string escaping of normal filter args (e.g. ``font`` param of ``drawtext``
+ filter).
+ """
def _get_drawtext_font_repr(font):
- """Build a command-line arg using drawtext ``font`` param and extract the ``-filter_complex`` arg."""
+ """Build a command-line arg using drawtext ``font`` param and extract the
+ ``-filter_complex`` arg.
+ """
args = (
ffmpeg.input('in')
.drawtext('test', font='a{}b'.format(font))
@@ -370,7 +390,9 @@ def test_filter_normal_arg_escape():
assert args[:3] == ['-i', 'in', '-filter_complex']
assert args[4:] == ['-map', '[s0]', 'out']
match = re.match(
- r'\[0\]drawtext=font=a((.|\n)*)b:text=test\[s0\]', args[3], re.MULTILINE
+ r'\[0\]drawtext=font=a((.|\n)*)b:text=test\[s0\]',
+ args[3],
+ re.MULTILINE,
)
assert match is not None, 'Invalid -filter_complex arg: {!r}'.format(args[3])
return match.group(1)
@@ -394,10 +416,14 @@ def test_filter_normal_arg_escape():
def test_filter_text_arg_str_escape():
- """Test string escaping of normal filter args (e.g. ``text`` param of ``drawtext`` filter)."""
+ """Test string escaping of normal filter args (e.g. ``text`` param of ``drawtext``
+ filter).
+ """
def _get_drawtext_text_repr(text):
- """Build a command-line arg using drawtext ``text`` param and extract the ``-filter_complex`` arg."""
+ """Build a command-line arg using drawtext ``text`` param and extract the
+ ``-filter_complex`` arg.
+ """
args = ffmpeg.input('in').drawtext('a{}b'.format(text)).output('out').get_args()
assert args[:3] == ['-i', 'in', '-filter_complex']
assert args[4:] == ['-map', '[s0]', 'out']
@@ -447,8 +473,11 @@ def test__run_async(mocker, pipe_stdin, pipe_stdout, pipe_stderr, cwd):
popen__mock = mocker.patch.object(subprocess, 'Popen', return_value=process__mock)
stream = _get_simple_example()
process = ffmpeg.run_async(
- stream, pipe_stdin=pipe_stdin, pipe_stdout=pipe_stdout,
- pipe_stderr=pipe_stderr, cwd=cwd
+ stream,
+ pipe_stdin=pipe_stdin,
+ pipe_stdout=pipe_stdout,
+ pipe_stderr=pipe_stderr,
+ cwd=cwd,
)
assert process is process__mock
@@ -458,8 +487,10 @@ def test__run_async(mocker, pipe_stdin, pipe_stdout, pipe_stderr, cwd):
(args,), kwargs = popen__mock.call_args
assert args == ffmpeg.compile(stream)
assert kwargs == dict(
- stdin=expected_stdin, stdout=expected_stdout, stderr=expected_stderr,
- cwd=cwd
+ stdin=expected_stdin,
+ stdout=expected_stdout,
+ stderr=expected_stderr,
+ cwd=cwd,
)
@@ -695,7 +726,10 @@ def test_pipe():
cmd = ['ffmpeg'] + args
p = subprocess.Popen(
- cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ cmd,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
)
in_data = bytes(
@@ -715,10 +749,10 @@ def test__probe():
assert data['format']['duration'] == '7.036000'
-@pytest.mark.skipif(sys.version_info < (3, 3), reason="requires python3.3 or higher")
+@pytest.mark.skipif(sys.version_info < (3, 3), reason='requires python3.3 or higher')
def test__probe_timeout():
with pytest.raises(subprocess.TimeoutExpired) as excinfo:
- data = ffmpeg.probe(TEST_INPUT_FILE1, timeout=0)
+ ffmpeg.probe(TEST_INPUT_FILE1, timeout=0)
assert 'timed out after 0 seconds' in str(excinfo.value)
@@ -751,24 +785,24 @@ def get_filter_complex_outputs(flt, name):
def test__get_filter_complex_input():
- assert get_filter_complex_input("", "scale") is None
- assert get_filter_complex_input("scale", "scale") is None
- assert get_filter_complex_input("scale[s3][s4];etc", "scale") is None
- assert get_filter_complex_input("[s2]scale", "scale") == "s2"
- assert get_filter_complex_input("[s2]scale;etc", "scale") == "s2"
- assert get_filter_complex_input("[s2]scale[s3][s4];etc", "scale") == "s2"
+ assert get_filter_complex_input('', 'scale') is None
+ assert get_filter_complex_input('scale', 'scale') is None
+ assert get_filter_complex_input('scale[s3][s4];etc', 'scale') is None
+ assert get_filter_complex_input('[s2]scale', 'scale') == 's2'
+ assert get_filter_complex_input('[s2]scale;etc', 'scale') == 's2'
+ assert get_filter_complex_input('[s2]scale[s3][s4];etc', 'scale') == 's2'
def test__get_filter_complex_outputs():
- assert get_filter_complex_outputs("", "scale") is None
- assert get_filter_complex_outputs("scale", "scale") is None
- assert get_filter_complex_outputs("scalex[s0][s1]", "scale") is None
- assert get_filter_complex_outputs("scale[s0][s1]", "scale") == ['s0', 's1']
- assert get_filter_complex_outputs("[s5]scale[s0][s1]", "scale") == ['s0', 's1']
- assert get_filter_complex_outputs("[s5]scale[s1][s0]", "scale") == ['s1', 's0']
- assert get_filter_complex_outputs("[s5]scale[s1]", "scale") == ['s1']
- assert get_filter_complex_outputs("[s5]scale[s1];x", "scale") == ['s1']
- assert get_filter_complex_outputs("y;[s5]scale[s1];x", "scale") == ['s1']
+ assert get_filter_complex_outputs('', 'scale') is None
+ assert get_filter_complex_outputs('scale', 'scale') is None
+ assert get_filter_complex_outputs('scalex[s0][s1]', 'scale') is None
+ assert get_filter_complex_outputs('scale[s0][s1]', 'scale') == ['s0', 's1']
+ assert get_filter_complex_outputs('[s5]scale[s0][s1]', 'scale') == ['s0', 's1']
+ assert get_filter_complex_outputs('[s5]scale[s1][s0]', 'scale') == ['s1', 's0']
+ assert get_filter_complex_outputs('[s5]scale[s1]', 'scale') == ['s1']
+ assert get_filter_complex_outputs('[s5]scale[s1];x', 'scale') == ['s1']
+ assert get_filter_complex_outputs('y;[s5]scale[s1];x', 'scale') == ['s1']
def test__multi_output_edge_label_order():
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..de71e58
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,15 @@
+[tool.black]
+skip-string-normalization = true
+target_version = ['py27'] # TODO: drop Python 2 support (... "Soon").
+include = '\.pyi?$'
+exclude = '''
+(
+ /(
+ \.eggs
+ | \.git
+ | \.tox
+ | \venv
+ | dist
+ )/
+)
+'''
diff --git a/setup.py b/setup.py
index 0282c67..72f381c 100644
--- a/setup.py
+++ b/setup.py
@@ -60,8 +60,6 @@ keywords = misc_keywords + file_formats
setup(
name='ffmpeg-python',
packages=['ffmpeg'],
- setup_requires=['pytest-runner'],
- tests_require=['pytest', 'pytest-mock'],
version=version,
description='Python bindings for FFmpeg - with complex filtering support',
author='Karl Kroening',
@@ -94,5 +92,9 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.10',
],
)
diff --git a/tox.ini b/tox.ini
index 1e3ba53..9881407 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,7 +4,17 @@
# and then run "tox" from this directory.
[tox]
-envlist = py27, py34, py35, py36, py37, pypy
+envlist = py27, py35, py36, py37, py38, py39, py310
+
+[gh-actions]
+python =
+ 2.7: py27
+ 3.5: py35
+ 3.6: py36
+ 3.7: py37
+ 3.8: py38
+ 3.9: py39
+ 3.10: py310
[testenv]
commands = py.test -vv