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 2691220..cb1438e 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. - heigth: 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. + heigth: 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..5f25a34 100644 --- a/ffmpeg/_run.py +++ b/ffmpeg/_run.py @@ -88,8 +88,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 ) ) @@ -199,7 +199,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 +286,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 +303,7 @@ def run( input=None, quiet=False, overwrite_output=False, - cwd=None + cwd=None, ): """Invoke ffmpeg for the supplied node graph. @@ -324,7 +327,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 +336,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..55682a2 100644 --- a/ffmpeg/_utils.py +++ b/ffmpeg/_utils.py @@ -49,8 +49,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) 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..ba1fa36 100644 --- a/ffmpeg/tests/test_ffmpeg.py +++ b/ffmpeg/tests/test_ffmpeg.py @@ -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 + )/ +) +'''