Extending fffw

The aim of fffw library is to provide mechanics for preparing ffmpeg command line arguments, not to cover all possible ffmpeg parameters. It provides all necessary base classes to extend input file handling, filtering and output configuration.

Before writing code it’s useful to read section ffmpeg command line structure.

Extending FFMPEG

If you need to add more flags to common section of ffmpeg, just extend main wrapper:

from dataclasses import dataclass
from fffw import encoding
from fffw.wrapper import param


@dataclass
class FFMPEG(encoding.FFMPEG):
    no_banner: bool = param(default=False, name='hide_banner')

Alternate inputs

Files are not the only sources for ffmpeg. Audio/video streams could be read from network or hardware devices. Below there is a sample implementation of screen grabbing input.

from dataclasses import dataclass
from typing import List, Tuple, Optional

from fffw import encoding
from fffw.encoding import *
from fffw.wrapper import param


@dataclass
class X11Grab(encoding.Input):
    """
    X-server grabbing input.
    """
    # `skip=True` removes parameter from argument list
    # (it is added manually in `as_pairs`).
    # This field overwrites `default` from `encoding.Input`.
    input_file: str = param(name='i', default=':0.0', skip=True)

    # `init=False` excludes parameter from `__init__`.
    # Field is initialized with value passed in `default`
    # parameter. Exactly as in dataclasses.
    format: str = param(name='f', default='x11grab', init=False)

    size: str = param(name='video_size')
    fps: float = param(name='framerate')

    def as_pairs(self) -> List[Tuple[Optional[str], Optional[str]]]:
        return super().as_pairs() + [('i', self.input_file)]


ff = FFMPEG()

ff < X11Grab(fps=25, size='cif', input_file=':0.0+10,20')

# As Output is not initialized with any video codec,
# force excluding `-vn` flag.
ff > output_file('out.mpg', no_video=False)

print(ff.get_cmd())

Implement filters

There are many useful filters in ffmpeg. I.e. you may use scale2ref to scale one video to fit another one.

from dataclasses import dataclass

from fffw.encoding import *
from fffw.graph import VIDEO
from fffw.wrapper import param


@dataclass
class Scale2Ref(VideoFilter):
    """
    Filter that scales one stream to fit another one.
    """
    # define filter name
    filter = 'scale2ref'
    # configure input and output edges count
    input_count = 2
    output_count = 2

    # add some parameters that compute dimensions
    # based on input stream characteristics
    width: str = param(name='w')
    height: str = param(name='h')


ff = FFMPEG()

ff < input_file('preroll.mp4')
ff < input_file('input.mp4')

# don't know what that formulas mean, it's from ffmpeg docs
scale2ref = ff.video | Scale2Ref('oh*mdar', 'ih/10')
# pass second file to same filter as second input
ff.video | scale2ref

output = output_file('output.mp4')
# concatenate scaled preroll and main video
concat = scale2ref | Concat(VIDEO)
scale2ref | concat > output

ff > output

print(ff.get_cmd())

Configure muxer

There are lot’s of formats supported by ffmpeg. Internet video streaming often uses HLS protocol to provide better experience. HLS muxer has a bunch of options to tune.

from dataclasses import dataclass

from fffw.encoding import *
from fffw.wrapper import param


@dataclass
class HLS(Output):
    """
    m3u8 muxer
    """
    format: str = param(name='f', init=False, default='hls')
    # Add empty `param()` call to prevent
    # "Non-default argument(s) follows default argument(s)"
    # dataclass error.
    hls_init_time: int = param()
    hls_base_url: str = param()
    hls_segment_filename: str = 'file%03d.ts'


ff = FFMPEG(input='input.mp4')

base_url = 'https://my.video.streaming.server.ru/my-playlist/'
ff > HLS(hls_base_url=base_url,
         output_file='out.m3u8',
         codecs=[VideoCodec('libx264'), AudioCodec('aac')])

print(ff.get_cmd())