コード例 #1
0
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'
コード例 #2
0
ファイル: filters.py プロジェクト: tumb1er/fffw
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')
コード例 #3
0
class VideoCodec(codecs.VideoCodec):
    force_key_frames: str = param()
    constant_rate_factor: int = param(name='crf')
    preset: str = param()
    max_rate: int = param(name='maxrate')
    buf_size: int = param(name='bufsize')
    profile: str = param(stream_suffix=True)
    gop: int = param(name='g')
    rate: float = param(name='r')
コード例 #4
0
class MediaInfo(BaseWrapper):
    command = 'mediainfo'
    input_file: str = param(name='')

    def handle_stderr(self, line: str) -> str:
        if 'error' in line:
            raise RuntimeError(f"Mediainfo error: {line}")
        return super().handle_stderr(line)
コード例 #5
0
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)]
コード例 #6
0
class FFMPEG(ffmpeg.FFMPEG):
    realtime: bool = param(name='re')
コード例 #7
0
ファイル: extending.py プロジェクト: tumb1er/fffw
class FFMPEG(encoding.FFMPEG):
    no_banner: bool = param(default=False, name='hide_banner')
コード例 #8
0
 class MyFilter(filters.VideoFilter):
     my_flag: bool = param(init=False, default=True)
コード例 #9
0
class StubFilter(AudioFilter):
    filter = 'stub'
    p: int = param()
コード例 #10
0
class AAC(AudioCodec):
    codec = 'aac'
    bitrate: int = param(name='b', stream_suffix=True)

    def transform(self, *metadata: Meta) -> Meta:
        return replace(ensure_audio(*metadata), bitrate=self.bitrate)
コード例 #11
0
ファイル: test_wrapper.py プロジェクト: tumb1er/fffw
class Python(BaseWrapper):
    command = 'python'
    module: str = param(name='m')
コード例 #12
0
class AudioCodec(codecs.AudioCodec):
    rate: float = param(name='ar')
    channels: int = param(name='ac')
コード例 #13
0
class FFMPEG(BaseWrapper):
    """
    ffmpeg command line basic wrapper.

    >>> from fffw.encoding.codecs import VideoCodec, AudioCodec
    >>> from fffw.encoding.filters import Scale
    >>> from fffw.encoding.outputs import output_file
    >>> ff = FFMPEG('/tmp/input.mp4', overwrite=True)
    >>> c = VideoCodec('libx264', bitrate=4_000_000)
    >>> ff.video | Scale(1280, 720) > c
    VideoCodec(codec='libx264', bitrate=4000000)
    >>> ff.overwrite = True
    >>> ff > output_file('/tmp/output.mp4', c,
    ...                  AudioCodec('libfdk_aac', bitrate=192_000))
    >>> ff.get_cmd()
    'ffmpeg -y -i /tmp/input.mp4\
 -filter_complex "[0:v]scale=w=1280:h=720[vout0]"\
 -map "[vout0]" -c:v libx264 -b:v 4000000 -map 0:a -c:a libfdk_aac -b:a 192000\
 /tmp/output.mp4'
    >>>
    """
    command = 'ffmpeg'
    stderr_markers = ['[error]', '[fatal]']
    input: Union[str, Input] = param(skip=True)
    output: Union[str, Output] = param(skip=True)

    loglevel: str = param()
    """ Loglevel: i.e. `level+info`."""
    overwrite: bool = param(name='y')
    """ Overwrite output files without manual confirmation."""
    init_hardware: str = param(name='init_hw_device')
    """ Initializes hardware acceleration device."""
    filter_hardware: str = param(name='filter_hw_device')
    """ Sets a device for filter graph by it's name set with `init_hardware`."""
    def __post_init__(self) -> None:
        """
        Fills internal shared structures for input and output files, and
        initializes filter graph.
        """

        self.__inputs = InputList()
        if self.input:
            if not isinstance(self.input, Input):
                self.__inputs.append(Input(input_file=self.input))
            else:
                self.__inputs.append(self.input)

        self.__outputs = OutputList()
        if self.output:
            if not isinstance(self.output, Output):
                self.__outputs.append(Output(output_file=self.output))
            else:
                self.__outputs.append(self.output)

        self.__filter_complex = FilterComplex(self.__inputs, self.__outputs)

        # calling super() to freeze params.
        super().__post_init__()

    def __lt__(self, other: Input) -> Input:
        """ Adds new source file.

        >>> ff = FFMPEG()
        >>> src = ff < Input(input_file='/tmp/input.mp4')
        >>>
        """
        if not isinstance(other, Input):
            return NotImplemented
        return self.add_input(other)

    def __gt__(self, other: Output) -> Output:
        """ Adds new output file.

        >>> from fffw.encoding.inputs import *
        >>> from fffw.encoding.outputs import *
        >>> ff = FFMPEG(input=input_file('input.mp4'))
        >>> dest = ff > output_file('/tmp/output.mp4')
        >>>
        """
        if not isinstance(other, Output):
            return NotImplemented
        return self.add_output(other)

    @property
    def inputs(self) -> Tuple[Input, ...]:
        """
        :return: a copy of ffmpeg input list.
        """
        return tuple(self.__inputs)

    @property
    def outputs(self) -> Tuple[Output, ...]:
        """
        :return: a copy of ffmpeg output list.
        """
        return tuple(self.__outputs)

    @property
    def filter_device(self) -> meta.Device:
        """ Returns filter hardware device metadata."""
        hardware, init = self.init_hardware.split("=")
        name = init.split(':', 1)[0]
        if self.filter_hardware != name:
            raise ValueError(self.filter_hardware)
        return meta.Device(hardware, name)

    @property
    def video(self) -> Stream:
        """
        :returns: first video stream not yet connected to filter graph or codec.

        >>> from fffw.encoding.filters import Scale
        >>> ff = FFMPEG('/tmp/input.mp4')
        >>> ff.video | Scale(1280, 720)
        Scale(width=1280, height=720)
        >>>
        """
        return self._get_free_source(VIDEO)

    @property
    def audio(self) -> Stream:
        """

        :returns: first audio stream not yet connected to filter graph or codec.

        >>> from fffw.encoding.codecs import AudioCodec
        >>> ff = FFMPEG('/tmp/input.mp4')
        >>> ac = AudioCodec('aac')
        >>> ff.audio > ac
        AudioCodec(codec='aac', bitrate=0)
        >>>
        """
        return self._get_free_source(AUDIO)

    def _get_free_source(self, kind: StreamType) -> Stream:
        """
        :param kind: stream type
        :return: first stream of this kind not connected to destination
        """
        for stream in self.__inputs.streams:
            if stream.kind != kind or stream.connected:
                continue
            return stream
        else:
            raise RuntimeError("no free streams")

    def _add_codec(self, c: Codec) -> Optional[Codec]:
        """ Connect codec to filter graph output or input stream.

        :param c: codec to connect to free source
        :returns: None of codec already connected to filter graph or codec
            itself if it was connected successfully to input stream.
        """
        if c.connected:
            return None
        node = self._get_free_source(c.kind)
        node.connect_dest(c)
        return c

    def get_args(self) -> List[bytes]:
        """
        :returns: command line arguments for ffmpeg.

        This includes:
        - ffmpeg executable name
        - ffmpeg parameters
        - input list args
        - filter_graph definition
        - output list args
        """

        with base.Namer():
            fc = str(self.__filter_complex)
            fc_args = ['-filter_complex', fc] if fc else []

            # Namer context is used to generate unique output stream names
            return (super().get_args() + self.__inputs.get_args() +
                    ensure_binary(fc_args) + self.__outputs.get_args())

    def add_input(self, input_file: Input) -> Input:
        """ Adds new source to ffmpeg.

        >>> ff = FFMPEG()
        >>> ff.add_input(Input(input_file="/tmp/input.mp4"))
        >>>
        """
        if not isinstance(input_file, Input):
            raise ValueError('Illegal input file type')
        self.__inputs.append(input_file)
        return input_file

    def add_output(self, output: Output) -> Output:
        """
        Adds output file to ffmpeg and connect it's codecs to free sources.

        >>> ff = FFMPEG()
        >>> ff.add_output(Output(output_file='/tmp/output.mp4'))
        >>>
        """
        self.__outputs.append(output)
        for codec in output.codecs:
            self._add_codec(codec)
        return output

    def handle_stderr(self, line: str) -> str:
        """
        Handle ffmpeg output.

        Capture only lines containing one of `stderr_markers`.

        :param line: ffmpeg output line
        :returns: line to be appended to whole ffmpeg output.
        """
        if not self.stderr_markers:
            # if no markers are defined, handle each line
            return super().handle_stderr(line)
        # capture only lines containing markers
        for marker in self.stderr_markers:
            if marker in line:
                return super().handle_stderr(line)
        return ''

    def check_buffering(self) -> None:
        """
        Checks that ffmpeg command will not cause frame buffering and
        out of memory errors.

        Each input file must be read simultaneously be all codecs in outputs,
        or some streams will be buffered until requested by output codecs.
        """
        chains = []
        for output in self.__outputs:
            for codec in output.codecs:
                streams = codec.check_buffering()
                if streams is None:
                    # Streams can't be computed because of missing metadata.
                    raise ValueError(streams)
                chains.append(streams)
        for chunk in zip(*chains):
            # Check that every codec reads same input stream
            if len(set(chunk)) > 1:
                # some codec read different file at this step, so one of input
                # stream will be buffered until this file is read by another
                # codec.
                raise BufferError(chunk)
コード例 #14
0
ファイル: outputs.py プロジェクト: tumb1er/fffw
class Output(BaseWrapper):
    # noinspection PyUnresolvedReferences
    """
    Base class for ffmpeg output.

    :arg codecs: list of codecs used in output.
    :arg format: output file format.
    :arg output_file: output file name.
    """
    codecs: List[Codec] = param(default=list, skip=True)
    no_video: Optional[bool] = param(name='vn')
    no_audio: Optional[bool] = param(name='an')
    format: str = param(name="f")
    output_file: str = param(name="", skip=True)

    def __lt__(self, other: base.InputType) -> "Output":
        """
        Connects a source or a filter to a first free codec.

        If there is no free codecs, new codec stub is created.
        """
        codec = self.get_free_codec(other.kind)
        other.connect_dest(codec)
        return self

    @property
    def video(self) -> Codec:
        """
        :returns: first video codec not connected to source.

        If no free codecs left, new one codec stub is appended to output.
        """
        return self.get_free_codec(VIDEO)

    @property
    def audio(self) -> Codec:
        """
        :returns: first audio codec not connected to source.

        If no free codecs left, new one codec stub is appended to output.
        """
        return self.get_free_codec(AUDIO)

    def get_free_codec(self, kind: StreamType, create: bool = True) -> Codec:
        """
        Finds first codec not connected to filter graph or to an input, or
        creates a new unnamed codec stub if no free codecs left.

        :param kind: desired codec stream type
        :param create: create new codec stub
        :return: first free codec or a new codec stub.
        """
        try:
            codec = next(
                filter(lambda c: not c.connected and c.kind == kind,
                       self.codecs))
        except StopIteration:
            if not create:
                raise KeyError(kind)
            codec = Codec()
            codec.kind = kind
            self.codecs.append(codec)
        return codec

    def get_args(self) -> List[bytes]:
        """
        :returns: codec args and output file parameters for ffmpeg
        """
        args = []
        # Check if we need to disable audio or video for output file because no
        # corresponding codecs are found.
        # Skipping `-an` / `-vn` parameters is still supported by  manually
        # setting `no_audio` / `no_video` parameters to `False`.
        for codec in self.codecs:
            if codec.kind == VIDEO:
                self.no_video = False
            if codec.kind == AUDIO:
                self.no_audio = False
            args.extend(codec.get_args())
        if self.no_video is None:
            self.no_video = True
        if self.no_audio is None:
            self.no_audio = True
        args.extend(super().get_args())
        args.append(ensure_binary(self.output_file))
        return args
コード例 #15
0
ファイル: outputs.py プロジェクト: tumb1er/fffw
class Codec(mixins.StreamValidationMixin, base.Dest, BaseWrapper):
    # noinspection PyUnresolvedReferences
    """
    Base class for output codecs.

    :arg codec: ffmpeg codec name.
    :arg bitrate: output bitrate in bps.
    """

    index = cast(int, base.Once('index'))
    """ Index of current codec in ffmpeg output streams."""

    codec: str = param(name='c', stream_suffix=True)
    bitrate: int = param(default=0, name='b', stream_suffix=True)

    def __post_init__(self) -> None:
        if self.codec is None:
            self.codec = self.__class__.codec
        super().__post_init__()

    @property
    def map(self) -> Optional[str]:
        """
        :returns: `-map` argument value depending of a node or a source
        connected to codec.
        """
        if self.edge is None:
            raise RuntimeError("Codec not connected to source")
        source = self.edge.input
        # Source input has name generated from input file index, stream
        # specifier and stream index. Node has no attribute index, so we use
        # Dest name ("[vout0]") as map argument. See also `Node.get_filter_args`
        return getattr(source, 'name', self.name)

    @property
    def connected(self) -> bool:
        """
        :return: True if codec is already connected to a node or a source.
        """
        return bool(self.edge)

    def get_args(self) -> List[bytes]:
        args = ['-map', self.map]
        return ensure_binary(args) + super().get_args()

    def clone(self, count: int = 1) -> List["Codec"]:
        """
        Creates multiple copies of self to reuse it as output node for multiple
        sources.

        Any connected input node is being split and connected to a corresponding
        copy of current filter.
        """
        if count == 1:
            return [self]
        raise RuntimeError("Trying to clone codec, is this intended?")

    def check_buffering(self) -> Optional[List[str]]:
        """
        Check that scenes read from input stream are ordered with ascending
        timestamps.

        :returns: A list of streams needed for this codec or None if metadata
            for codec can't be computed.
        """
        meta = self.get_meta_data()
        if not meta:
            return None
        prev = meta.scenes[0]
        for scene in meta.scenes[1:]:
            if prev.stream == scene.stream and prev.end > scene.start:
                # Previous scene in same stream is located after current, so
                # current decoded scene will be buffered until previous scene is
                # decoded.
                raise BufferError(prev, scene)
            prev = scene
        return meta.streams
コード例 #16
0
class FdkAAC(codecs.AudioCodec):
    codec = 'libfdk_aac'
    bitrate: int = param(name='b', stream_suffix=True)

    def transform(self, metadata: Meta) -> Meta:
        return replace(metadata, bitrate=self.bitrate)