diff --git a/av/filter/graph.py b/av/filter/graph.py index 5cd3bbf06..507fbf015 100644 --- a/av/filter/graph.py +++ b/av/filter/graph.py @@ -27,6 +27,23 @@ def __dealloc__(self): # This frees the graph, filter contexts, links, etc.. lib.avfilter_graph_free(cython.address(self.ptr)) + @property + def threads(self): + """Maximum number of threads used by filters in this graph. + + Set to 0 for automatic thread count. Must be set before adding any + filters to the graph. + + Wraps :ffmpeg:`AVFilterGraph.nb_threads`. + """ + return self.ptr.nb_threads + + @threads.setter + def threads(self, value: cython.int): + if self.ptr.nb_filters: + raise RuntimeError("Cannot change threads after filters have been added.") + self.ptr.nb_threads = value + @cython.cfunc def _get_unique_name(self, name: str) -> str: count = self._name_counts.get(name, 0) diff --git a/av/filter/graph.pyi b/av/filter/graph.pyi index e170c2ce7..758813ea2 100644 --- a/av/filter/graph.pyi +++ b/av/filter/graph.pyi @@ -14,6 +14,7 @@ from .filter import Filter class Graph: configured: bool + threads: int def __init__(self) -> None: ... def configure(self, auto_buffer: bool = True, force: bool = False) -> None: ... diff --git a/include/avfilter.pxd b/include/avfilter.pxd index 5eb11ecc2..afb953fdd 100644 --- a/include/avfilter.pxd +++ b/include/avfilter.pxd @@ -58,6 +58,7 @@ cdef extern from "libavfilter/avfilter.h" nogil: cdef struct AVFilterGraph: int nb_filters AVFilterContext **filters + int nb_threads cdef struct AVFilterInOut: char *name diff --git a/tests/test_filters.py b/tests/test_filters.py index bd74a633a..51522e7de 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -193,11 +193,10 @@ def test_audio_buffer_volume_filter(self): assert np.allclose(input_data * 0.5, output_data) - def test_video_buffer(self): + def _test_video_buffer(self, graph): input_container = av.open(format="lavfi", file="color=c=pink:duration=1:r=30") input_video_stream = input_container.streams.video[0] - graph = av.filter.Graph() buffer = graph.add_buffer(template=input_video_stream) bwdif = graph.add("bwdif", "send_field:tff:all") buffersink = graph.add("buffersink") @@ -223,6 +222,14 @@ def test_video_buffer(self): assert filtered_frames[1].pts == (frame.pts - 1) * 2 + 1 assert filtered_frames[1].time_base == Fraction(1, 60) + def test_video_buffer(self): + self._test_video_buffer(av.filter.Graph()) + + def test_video_buffer_threading(self): + graph = av.filter.Graph() + graph.threads = 4 + self._test_video_buffer(graph) + def test_EOF(self) -> None: input_container = av.open(format="lavfi", file="color=c=pink:duration=1:r=30") video_stream = input_container.streams.video[0] @@ -246,3 +253,15 @@ def test_EOF(self) -> None: assert isinstance(palette_frame, av.VideoFrame) assert palette_frame.width == 16 assert palette_frame.height == 16 + + def test_graph_threads(self) -> None: + graph = Graph() + assert graph.threads == 0 + + graph.threads = 4 + assert graph.threads == 4 + + graph.add("testsrc") + + with self.assertRaises(RuntimeError): + graph.threads = 2