mirror of
https://github.com/kkroening/ffmpeg-python.git
synced 2025-04-05 04:22:51 +08:00
Merge 61e533abd90c88522bfa8a0e1ee452c1e1cb81d4 into df129c7ba30aaa9ffffb81a48f53aa7253b0b4e6
This commit is contained in:
commit
ee4e542957
@ -10,7 +10,7 @@ from .nodes import (
|
||||
MergeOutputsNode,
|
||||
OutputNode,
|
||||
output_operator,
|
||||
)
|
||||
SourceNode)
|
||||
|
||||
|
||||
def input(filename, **kwargs):
|
||||
@ -32,6 +32,27 @@ def input(filename, **kwargs):
|
||||
return InputNode(input.__name__, kwargs=kwargs).stream()
|
||||
|
||||
|
||||
|
||||
def source_multi_output(filter_name, *args, **kwargs):
|
||||
"""Source filter with one or more outputs.
|
||||
|
||||
This is the same as ``source`` except that the filter can produce more than one output.
|
||||
|
||||
To reference an output stream, use either the ``.stream`` operator or bracket shorthand.
|
||||
"""
|
||||
return SourceNode(filter_name, args=args, kwargs=kwargs)
|
||||
|
||||
|
||||
def source(filter_name, *args, **kwargs):
|
||||
"""Source filter.
|
||||
|
||||
It works like `input`, but takes a source filter name instead of a file URL as the first argument.
|
||||
|
||||
Official documentation: `Sources <https://ffmpeg.org/ffmpeg-filters.html#Video-Sources>`__
|
||||
"""
|
||||
return source_multi_output(filter_name, *args, **kwargs).stream()
|
||||
|
||||
|
||||
@output_operator()
|
||||
def global_args(stream, *args):
|
||||
"""Add extra global command-line argument(s), e.g. ``-progress``."""
|
||||
@ -92,4 +113,11 @@ def output(*streams_and_filename, **kwargs):
|
||||
return OutputNode(streams, output.__name__, kwargs=kwargs).stream()
|
||||
|
||||
|
||||
__all__ = ['input', 'merge_outputs', 'output', 'overwrite_output']
|
||||
__all__ = [
|
||||
'input',
|
||||
'source_multi_output',
|
||||
'source',
|
||||
'merge_outputs',
|
||||
'output',
|
||||
'overwrite_output',
|
||||
]
|
||||
|
@ -15,7 +15,7 @@ from .nodes import (
|
||||
InputNode,
|
||||
OutputNode,
|
||||
output_operator,
|
||||
)
|
||||
SourceNode)
|
||||
|
||||
try:
|
||||
from collections.abc import Iterable
|
||||
@ -158,10 +158,10 @@ def get_args(stream_spec, overwrite_output=False):
|
||||
input_nodes = [node for node in sorted_nodes if isinstance(node, InputNode)]
|
||||
output_nodes = [node for node in sorted_nodes if isinstance(node, OutputNode)]
|
||||
global_nodes = [node for node in sorted_nodes if isinstance(node, GlobalNode)]
|
||||
filter_nodes = [node for node in sorted_nodes if isinstance(node, FilterNode)]
|
||||
filter_nodes = [node for node in sorted_nodes if isinstance(node, (FilterNode, SourceNode))]
|
||||
stream_name_map = {(node, None): str(i) for i, node in enumerate(input_nodes)}
|
||||
filter_arg = _get_filter_arg(filter_nodes, outgoing_edge_maps, stream_name_map)
|
||||
args += reduce(operator.add, [_get_input_args(node) for node in input_nodes])
|
||||
args += reduce(operator.add, [_get_input_args(node) for node in input_nodes], [])
|
||||
if filter_arg:
|
||||
args += ['-filter_complex', filter_arg]
|
||||
args += reduce(
|
||||
|
@ -5,6 +5,7 @@ from .dag import KwargReprNode
|
||||
from ._utils import escape_chars, get_hash_int
|
||||
from builtins import object
|
||||
import os
|
||||
import uuid
|
||||
|
||||
|
||||
def _is_of_types(obj, types):
|
||||
@ -237,9 +238,7 @@ class Node(KwargReprNode):
|
||||
|
||||
class FilterableStream(Stream):
|
||||
def __init__(self, upstream_node, upstream_label, upstream_selector=None):
|
||||
super(FilterableStream, self).__init__(
|
||||
upstream_node, upstream_label, {InputNode, FilterNode}, upstream_selector
|
||||
)
|
||||
super(FilterableStream, self).__init__(upstream_node, upstream_label, {InputNode, FilterNode, SourceNode}, upstream_selector)
|
||||
|
||||
|
||||
# noinspection PyMethodOverriding
|
||||
@ -264,20 +263,8 @@ class InputNode(Node):
|
||||
|
||||
|
||||
# noinspection PyMethodOverriding
|
||||
class FilterNode(Node):
|
||||
def __init__(self, stream_spec, name, max_inputs=1, args=[], kwargs={}):
|
||||
super(FilterNode, self).__init__(
|
||||
stream_spec=stream_spec,
|
||||
name=name,
|
||||
incoming_stream_types={FilterableStream},
|
||||
outgoing_stream_type=FilterableStream,
|
||||
min_inputs=1,
|
||||
max_inputs=max_inputs,
|
||||
args=args,
|
||||
kwargs=kwargs,
|
||||
)
|
||||
|
||||
"""FilterNode"""
|
||||
class FilterableNode(Node):
|
||||
"""FilterableNode"""
|
||||
|
||||
def _get_filter(self, outgoing_edges):
|
||||
args = self.args
|
||||
@ -303,6 +290,45 @@ class FilterNode(Node):
|
||||
return escape_chars(params_text, '\\\'[],;')
|
||||
|
||||
|
||||
# noinspection PyMethodOverriding
|
||||
class FilterNode(FilterableNode):
|
||||
"""FilterNode"""
|
||||
def __init__(self, stream_spec, name, max_inputs=1, args=[], kwargs={}):
|
||||
super(FilterNode, self).__init__(
|
||||
stream_spec=stream_spec,
|
||||
name=name,
|
||||
incoming_stream_types={FilterableStream},
|
||||
outgoing_stream_type=FilterableStream,
|
||||
min_inputs=1,
|
||||
max_inputs=max_inputs,
|
||||
args=args,
|
||||
kwargs=kwargs,
|
||||
)
|
||||
|
||||
|
||||
# noinspection PyMethodOverriding
|
||||
class SourceNode(FilterableNode):
|
||||
def __init__(self, name, args=[], kwargs={}):
|
||||
self.source_node_id = uuid.uuid4()
|
||||
super(SourceNode, self).__init__(
|
||||
stream_spec=None,
|
||||
name=name,
|
||||
incoming_stream_types={},
|
||||
outgoing_stream_type=FilterableStream,
|
||||
min_inputs=0,
|
||||
max_inputs=0,
|
||||
args=args,
|
||||
kwargs=kwargs
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
"""Two source nodes with the same options should _not_ be considered
|
||||
the same node. For this reason we create a uuid4 on node instantiation,
|
||||
and use that as our hash.
|
||||
"""
|
||||
return self.source_node_id.int
|
||||
|
||||
|
||||
# noinspection PyMethodOverriding
|
||||
class OutputNode(Node):
|
||||
def __init__(self, stream, name, args=[], kwargs={}):
|
||||
|
@ -684,6 +684,50 @@ def test_mixed_passthrough_selectors():
|
||||
]
|
||||
|
||||
|
||||
def test_sources():
|
||||
out = (ffmpeg
|
||||
.overlay(
|
||||
ffmpeg.source("testsrc"),
|
||||
ffmpeg.source("color", color="red@.3"),
|
||||
)
|
||||
.trim(end=5)
|
||||
.output(TEST_OUTPUT_FILE1)
|
||||
)
|
||||
|
||||
assert out.get_args() == [
|
||||
'-filter_complex',
|
||||
'testsrc[s0];'
|
||||
'color=color=red@.3[s1];'
|
||||
'[s0][s1]overlay=eof_action=repeat[s2];'
|
||||
'[s2]trim=end=5[s3]',
|
||||
'-map',
|
||||
'[s3]',
|
||||
TEST_OUTPUT_FILE1
|
||||
]
|
||||
|
||||
|
||||
|
||||
def test_same_source_multiple_times():
|
||||
out = (ffmpeg
|
||||
.concat(
|
||||
ffmpeg.source("testsrc").trim(end=5),
|
||||
ffmpeg.source("testsrc").trim(start=10, end=14).filter(
|
||||
"setpts", "PTS-STARTPTS"
|
||||
),
|
||||
)
|
||||
.output(TEST_OUTPUT_FILE1)
|
||||
)
|
||||
|
||||
assert out.get_args() == [
|
||||
'-filter_complex',
|
||||
'testsrc[s0];[s0]trim=end=5[s1];testsrc[s2];[s2]trim=end=14:start=10[s3];[s3]setpts=PTS-STARTPTS[s4];[s1][s4]concat=n=2[s5]',
|
||||
'-map',
|
||||
'[s5]',
|
||||
TEST_OUTPUT_FILE1
|
||||
]
|
||||
|
||||
|
||||
|
||||
def test_pipe():
|
||||
width = 32
|
||||
height = 32
|
||||
|
Loading…
x
Reference in New Issue
Block a user