Skip to content

render

Classes:

Functions:

  • clip_async_render

    Iterate over an entire clip and optionally write results to a file.

  • clip_data_gather
  • find_prop

    Find specific frame props in the clip and return a list of frame ranges that meets the conditions.

  • find_prop_rfs

    Conditional replace frames from the original clip with a replacement clip by comparing frame properties.

  • prop_compare_cb

AsyncRenderConf dataclass

AsyncRenderConf(
    n: int = 2, one_pix_frame: bool = False, parallel_input: bool = False
)

Attributes:

n class-attribute instance-attribute

n: int = 2

one_pix_frame class-attribute instance-attribute

one_pix_frame: bool = False

parallel_input class-attribute instance-attribute

parallel_input: bool = False

clip_async_render

clip_async_render(
    clip: VideoNode,
    outfile: BinaryIO | SPathLike | None = None,
    progress: str | Callable[[int, int], None] | None = None,
    callback: None = None,
    prefetch: int = 0,
    backlog: int = -1,
    y4m: bool | None = None,
    async_requests: int | bool | AsyncRenderConf = False,
) -> None
clip_async_render(
    clip: VideoNode,
    outfile: BinaryIO | SPathLike | None = None,
    progress: str | Callable[[int, int], None] | None = None,
    callback: Callable[[int, VideoFrame], T] = ...,
    prefetch: int = 0,
    backlog: int = -1,
    y4m: bool | None = None,
    async_requests: int | bool | AsyncRenderConf = False,
) -> list[T]
clip_async_render(
    clip: VideoNode,
    outfile: BinaryIO | SPathLike | None = None,
    progress: str | Callable[[int, int], None] | None = None,
    callback: Callable[[int, VideoFrame], T] | None = ...,
    prefetch: int = 0,
    backlog: int = -1,
    y4m: bool | None = None,
    async_requests: int | bool | AsyncRenderConf = False,
) -> list[T] | None
clip_async_render(
    clip: VideoNode,
    outfile: BinaryIO | SPathLike | None = None,
    progress: str | Callable[[int, int], None] | None = None,
    callback: Callable[[int, VideoFrame], T] | None = None,
    prefetch: int = 0,
    backlog: int = -1,
    y4m: bool | None = None,
    async_requests: int | bool | AsyncRenderConf = False,
) -> list[T] | None

Iterate over an entire clip and optionally write results to a file.

This is mostly useful for metric gathering that must be performed before any other processing. This could be for example gathering scenechanges, per-frame heuristics, etc.

It's highly recommended to perform as little filtering as possible on the input clip for speed purposes.

Example usage:

.. code-block:: python

# Gather scenechanges.
>>> scenechanges = clip_async_render(clip, None, 'Searching for scenechanges...', lambda n, f: get_prop(f, "_SceneChange", int))

# Gather average planes stats.
>>> avg_planes = clip_async_render(clip, None, 'Calculating average planes...', lambda n, f: get_prop(f, "PlaneStatsAverage", float))

Parameters:

  • clip

    (VideoNode) –

    Clip to render.

  • outfile

    (BinaryIO | SPathLike | None, default: None ) –

    Optional binary output or path to write to.

  • progress

    (str | Callable[[int, int], None] | None, default: None ) –

    A message to display during rendering. This is shown alongside the progress.

  • callback

    (Callable[[int, VideoFrame], T] | None, default: None ) –

    Callback function. Must accept n and f (like a frameeval would) and return some value. This function is used to determine what information gets returned per frame. Default: None.

  • prefetch

    (int, default: 0 ) –

    The amount of frames to prefetch. 0 means automatically determine. Default: 0.

  • backlog

    (int, default: -1 ) –

    How many frames to hold. Useful for if your write of callback is slower than your frame rendering.

  • y4m

    (bool | None, default: None ) –

    Whether to add YUV4MPEG2 headers to the rendered output. If None, automatically determine. Default: None.

  • async_requests

    (int | bool | AsyncRenderConf, default: False ) –

    Whether to render frames non-consecutively. If int, determines the number of requests. Default: False.

Source code
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
def clip_async_render(
    clip: vs.VideoNode, outfile: BinaryIO | SPathLike | None = None, progress: str | Callable[[int, int], None] | None = None,
    callback: Callable[[int, vs.VideoFrame], T] | None = None,
    prefetch: int = 0, backlog: int = -1, y4m: bool | None = None,
    async_requests: int | bool | AsyncRenderConf = False
) -> list[T] | None:
    """
    Iterate over an entire clip and optionally write results to a file.

    This is mostly useful for metric gathering that must be performed before any other processing.
    This could be for example gathering scenechanges, per-frame heuristics, etc.

    It's highly recommended to perform as little filtering as possible on the input clip for speed purposes.

    Example usage:

    .. code-block:: python

        # Gather scenechanges.
        >>> scenechanges = clip_async_render(clip, None, 'Searching for scenechanges...', lambda n, f: get_prop(f, "_SceneChange", int))

        # Gather average planes stats.
        >>> avg_planes = clip_async_render(clip, None, 'Calculating average planes...', lambda n, f: get_prop(f, "PlaneStatsAverage", float))

    :param clip:            Clip to render.
    :param outfile:         Optional binary output or path to write to.
    :param progress:        A message to display during rendering. This is shown alongside the progress.
    :param callback:        Callback function. Must accept `n` and `f` (like a frameeval would) and return some value.
                            This function is used to determine what information gets returned per frame.
                            Default: None.
    :param prefetch:        The amount of frames to prefetch. 0 means automatically determine.
                            Default: 0.
    :param backlog:         How many frames to hold. Useful for if your write of callback is slower
                            than your frame rendering.
    :param y4m:             Whether to add YUV4MPEG2 headers to the rendered output. If None, automatically determine.
                            Default: None.
    :param async_requests:  Whether to render frames non-consecutively.
                            If int, determines the number of requests.
                            Default: False.
    """

    from .funcs import fallback

    if isinstance(outfile, (str, PathLike, Path, SPath)) and outfile is not None:
        with open(outfile, 'wb') as f:
            return clip_async_render(clip, f, progress, callback, prefetch, backlog, y4m, async_requests)

    result = dict[int, T]()
    async_conf: AsyncRenderConf | Literal[False]

    if async_requests is True:
        async_conf = AsyncRenderConf(1)
    elif isinstance(async_requests, int):
        if isinstance(async_requests, int) and async_requests <= 1:
            async_conf = False
        else:
            async_conf = AsyncRenderConf(async_requests)
    else:
        async_conf = False if async_requests.n <= 1 else async_requests

    if async_conf and async_conf.one_pix_frame and y4m:
        raise CustomValueError('You cannot have y4m=True and one_pix_frame in AsyncRenderConf!')

    num_frames = len(clip)

    pr_update: Callable[[], None]
    pr_update_custom: Callable[[int, int], None]

    if callback:
        def get_callback(shift: int = 0) -> Callable[[int, vs.VideoFrame], vs.VideoFrame]:
            if shift:
                if outfile is None and progress is not None:
                    if isinstance(progress, str):
                        def _cb(n: int, f: vs.VideoFrame) -> vs.VideoFrame:
                            n += shift
                            result[n] = callback(n, f)
                            pr_update()
                            return f
                    else:
                        def _cb(n: int, f: vs.VideoFrame) -> vs.VideoFrame:
                            n += shift
                            result[n] = callback(n, f)
                            pr_update_custom(n, num_frames)
                            return f
                else:
                    def _cb(n: int, f: vs.VideoFrame) -> vs.VideoFrame:
                        n += shift
                        result[n] = callback(n, f)
                        return f
            else:
                if outfile is None and progress is not None:
                    if isinstance(progress, str):
                        def _cb(n: int, f: vs.VideoFrame) -> vs.VideoFrame:
                            result[n] = callback(n, f)
                            pr_update()
                            return f
                    else:
                        def _cb(n: int, f: vs.VideoFrame) -> vs.VideoFrame:
                            result[n] = callback(n, f)
                            pr_update_custom(n, num_frames)
                            return f
                else:
                    def _cb(n: int, f: vs.VideoFrame) -> vs.VideoFrame:
                        result[n] = callback(n, f)
                        return f

            return _cb

        if async_conf and async_conf.one_pix_frame and (clip.width != clip.height != 1):
            clip = clip.std.CropAbs(1, 1)

        if not async_conf or async_conf.n == 1:
            blankclip = clip.std.BlankClip(keep=True)

            _cb = get_callback()

            if async_conf:
                rend_clip = blankclip.std.FrameEval(lambda n: blankclip.std.ModifyFrame(clip, _cb))
            else:
                rend_clip = blankclip.std.ModifyFrame(clip, _cb)
        else:
            if outfile:
                raise CustomValueError('You cannot have and output file with multi async request!', clip_async_render)

            chunk = floor(clip.num_frames / async_conf.n)
            cl = chunk * async_conf.n

            blankclip = clip.std.BlankClip(length=chunk, keep=True)

            stack = async_conf.parallel_input and not async_conf.one_pix_frame

            if stack:
                rend_clip = vs.core.std.StackHorizontal([
                    blankclip.std.ModifyFrame(clip[chunk * i:chunk * (i + 1)], get_callback(chunk * i))
                    for i in range(async_conf.n)
                ])
            else:
                _cb = get_callback()

                clip_indices = list(range(cl))
                range_indices = list(range(async_conf.n))

                indices = [clip_indices[i::async_conf.n] for i in range_indices]

                def _var(n: int, f: list[vs.VideoFrame]) -> vs.VideoFrame:
                    for i, fi in zip(range_indices, f):
                        _cb(indices[i][n], fi)

                    return f[0]

                rend_clip = blankclip.std.ModifyFrame([clip[i::async_conf.n] for i in range_indices], _var)

            if cl != clip.num_frames:
                rend_rest = blankclip[:clip.num_frames - cl].std.ModifyFrame(clip[cl:], get_callback(cl))
                rend_clip = vs.core.std.Splice([rend_clip, rend_rest], stack)
    else:
        rend_clip = clip

    if outfile is None:
        if y4m:
            raise CustomValueError('You cannot have y4m=False without any output file!', clip_async_render)

        clip_it = rend_clip.frames(prefetch, backlog, True)

        if progress is None:
            deque(clip_it, 0)
        elif isinstance(progress, str):
            with get_render_progress(progress, clip.num_frames) as pr:
                if callback:
                    pr_update = pr.update
                    deque(clip_it, 0)
                else:
                    for _ in clip_it:
                        pr.update()
        else:
            if callback:
                pr_update_custom = progress
                deque(clip_it, 0)
            else:
                for i, _ in enumerate(clip_it):
                    progress(i, num_frames)

    else:
        y4m = fallback(y4m, bool(rend_clip.format and (rend_clip.format.color_family is vs.YUV)))

        if y4m:
            if rend_clip.format is None:
                raise CustomValueError(
                    'You cannot have y4m=True when rendering a variable resolution clip!', clip_async_render
                )
            else:
                InvalidColorFamilyError.check(
                    rend_clip, (vs.YUV, vs.GRAY), clip_async_render,
                    message='Can only render to y4m clips with {correct} color family, not {wrong}!'
                )

        if progress is None:
            rend_clip.output(outfile, y4m, None, prefetch, backlog)
        elif isinstance(progress, str):
            with get_render_progress(progress, clip.num_frames) as pr:
                rend_clip.output(outfile, y4m, pr.update, prefetch, backlog)
        else:
            rend_clip.output(outfile, y4m, progress, prefetch, backlog)

    if callback:
        try:
            return [result[i] for i in range(clip.num_frames)]
        except KeyError:
            raise CustomRuntimeError(
                'There was an error with the rendering and one frame request was rejected!',
                clip_async_render
            )

    return None

clip_data_gather

clip_data_gather(
    clip: VideoNode,
    progress: str | Callable[[int, int], None] | None,
    callback: Callable[[int, VideoFrame], SentinelT | T],
    async_requests: int | bool | AsyncRenderConf = False,
    prefetch: int = 0,
    backlog: int = -1,
) -> list[T]
Source code
289
290
291
292
293
294
295
296
def clip_data_gather(
    clip: vs.VideoNode, progress: str | Callable[[int, int], None] | None,
    callback: Callable[[int, vs.VideoFrame], SentinelT | T],
    async_requests: int | bool | AsyncRenderConf = False, prefetch: int = 0, backlog: int = -1
) -> list[T]:
    frames = clip_async_render(clip, None, progress, callback, prefetch, backlog, False, async_requests)

    return list(Sentinel.filter(frames))

find_prop

find_prop(
    src: VideoNode,
    prop: str,
    op: str | Callable[[float, float], bool] | None,
    ref: float | bool,
    range_length: Literal[0] = ...,
    async_requests: int = 1,
) -> list[int]
find_prop(
    src: VideoNode,
    prop: str,
    op: str | Callable[[float, float], bool] | None,
    ref: float | bool,
    range_length: int = ...,
    async_requests: int = 1,
) -> list[tuple[int, int]]
find_prop(
    src: VideoNode,
    prop: str,
    op: str | Callable[[float, float], bool] | None,
    ref: float | bool,
    range_length: int = 0,
    async_requests: int = 1,
) -> list[int] | list[tuple[int, int]]

Find specific frame props in the clip and return a list of frame ranges that meets the conditions.

Example usage:

.. code-block:: python

# Return a list of all frames that were marked as combed.
>>> find_prop(clip, "_Combed", None, True, 0)

Parameters:

  • src

    (VideoNode) –

    Input clip.

  • prop

    (str) –

    Frame prop to perform checks on.

  • op

    (str | Callable[[float, float], bool] | None) –

    Conditional operator to apply between prop and ref ("<", "<=", "==", "!=", ">" or ">="). If None, check whether a prop is truthy.

  • ref

    (float | bool) –

    Value to be compared with prop.

  • range_length

    (int, default: 0 ) –

    Amount of frames to finish a sequence, to avoid false negatives. This will create ranges with a sequence of start-end tuples.

  • async_requests

    (int, default: 1 ) –

    Whether to render frames non-consecutively. If int, determines the number of requests. Default: 1.

Returns:

Source code
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
def find_prop(
    src: vs.VideoNode, prop: str, op: str | Callable[[float, float], bool] | None, ref: float | bool,
    range_length: int = 0, async_requests: int = 1
) -> list[int] | list[tuple[int, int]]:
    """
    Find specific frame props in the clip and return a list of frame ranges that meets the conditions.

    Example usage:

    .. code-block:: python

        # Return a list of all frames that were marked as combed.
        >>> find_prop(clip, "_Combed", None, True, 0)

    :param src:                 Input clip.
    :param prop:                Frame prop to perform checks on.
    :param op:                  Conditional operator to apply between prop and ref ("<", "<=", "==", "!=", ">" or ">=").
                                If None, check whether a prop is truthy.
    :param ref:                 Value to be compared with prop.
    :param range_length:        Amount of frames to finish a sequence, to avoid false negatives.
                                This will create ranges with a sequence of start-end tuples.
    :param async_requests:      Whether to render frames non-consecutively.
                                If int, determines the number of requests.
                                Default: 1.

    :return:                    Frame ranges at the specified conditions.
    """

    prop_src, callback = prop_compare_cb(src, prop, op, ref, True)

    aconf = AsyncRenderConf(async_requests, (prop_src.width, prop_src.height) == (1, 1), False)

    frames = clip_data_gather(prop_src, f'Searching {prop} {op} {ref}...', callback, aconf)

    if range_length > 0:
        return normalize_list_to_ranges(frames, range_length)

    return frames

find_prop_rfs

find_prop_rfs(
    clip_a: VideoNode,
    clip_b: VideoNode,
    prop: str,
    op: str | Callable[[float, float], bool] | None,
    prop_ref: float | bool,
    ref: VideoNode | None = None,
    mismatch: bool = False,
) -> VideoNode

Conditional replace frames from the original clip with a replacement clip by comparing frame properties.

Example usage:

.. code-block:: python

# Replace a rescaled clip with the original clip for frames where the error
# (defined on another clip) is equal to or greater than 0.025.
>>> find_prop_rfs(scaled, src, 'PlaneStatsAverage', '>=', 0.025, err_clip)

Parameters:

  • clip_a

    (VideoNode) –

    Original clip.

  • clip_b

    (VideoNode) –

    Replacement clip.

  • prop

    (str) –

    Frame prop to perform checks on.

  • op

    (str | Callable[[float, float], bool] | None) –

    Conditional operator to apply between prop and ref ("<", "<=", "==", "!=", ">" or ">="). If None, check whether a prop is truthy. Default: None.

  • prop_ref

    (float | bool) –

    Value to be compared with prop.

  • ref

    (VideoNode | None, default: None ) –

    Optional reference clip to read frame properties from. Default: None.

  • mismatch

    (bool, default: False ) –

    Accept format or resolution mismatch between clips. Default: False.

Returns:

  • VideoNode

    Clip where frames that meet the specified criteria were replaced with a different clip.

Source code
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
def find_prop_rfs(
    clip_a: vs.VideoNode, clip_b: vs.VideoNode,
    prop: str, op: str | Callable[[float, float], bool] | None, prop_ref: float | bool,
    ref: vs.VideoNode | None = None, mismatch: bool = False
) -> vs.VideoNode:
    """
    Conditional replace frames from the original clip with a replacement clip by comparing frame properties.

    Example usage:

    .. code-block:: python

        # Replace a rescaled clip with the original clip for frames where the error
        # (defined on another clip) is equal to or greater than 0.025.
        >>> find_prop_rfs(scaled, src, 'PlaneStatsAverage', '>=', 0.025, err_clip)

    :param clip_a:          Original clip.
    :param clip_b:          Replacement clip.
    :param prop:            Frame prop to perform checks on.
    :param op:              Conditional operator to apply between prop and ref ("<", "<=", "==", "!=", ">" or ">=").
                            If None, check whether a prop is truthy. Default: None.
    :param prop_ref:        Value to be compared with prop.
    :param ref:             Optional reference clip to read frame properties from. Default: None.
    :param mismatch:        Accept format or resolution mismatch between clips. Default: False.

    :return:                Clip where frames that meet the specified criteria were replaced with a different clip.
    """

    from ..utils import replace_ranges

    prop_src, callback = prop_compare_cb(ref or clip_a, prop, op, prop_ref, False)

    return replace_ranges(clip_a, clip_b, callback, False, mismatch, prop_src=prop_src)

prop_compare_cb

prop_compare_cb(
    src: VideoNodeT,
    prop: str,
    op: str | Callable[[float, float], bool] | None,
    ref: float | bool,
    return_frame_n: Literal[False] = ...,
) -> tuple[VideoNodeT, Callable[[int, VideoFrame], bool]]
prop_compare_cb(
    src: VideoNodeT,
    prop: str,
    op: str | Callable[[float, float], bool] | None,
    ref: float | bool,
    return_frame_n: Literal[True] = ...,
) -> tuple[VideoNodeT, Callable[[int, VideoFrame], int | SentinelT]]
prop_compare_cb(
    src: VideoNodeT,
    prop: str,
    op: str | Callable[[float, float], bool] | None,
    ref: float | bool,
    return_frame_n: bool = False,
) -> Union[
    tuple[VideoNodeT, Callable[[int, VideoFrame], bool]],
    tuple[VideoNodeT, Callable[[int, VideoFrame], int | SentinelT]],
]
Source code
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
def prop_compare_cb(
    src: VideoNodeT, prop: str, op: str | Callable[[float, float], bool] | None, ref: float | bool,
    return_frame_n: bool = False
) -> Union[
     tuple[VideoNodeT, Callable[[int, vs.VideoFrame], bool]],
     tuple[VideoNodeT, Callable[[int, vs.VideoFrame], int | SentinelT]]
]:
    bool_check = isinstance(ref, bool)
    one_pix = hasattr(vs.core, 'akarin') and not (callable(op) or ' ' in prop)
    assert (op is None) if bool_check else (op is not None)

    if isinstance(op, str):
        assert op in _operators

    callback: Callable[[int, vs.VideoFrame], SentinelT | int]
    if one_pix:
        clip = vs.core.std.BlankClip(
            None, 1, 1, vs.GRAY8 if bool_check else vs.GRAYS, length=src.num_frames
        ).std.CopyFrameProps(src).akarin.Expr(
            f'x.{prop}' if bool_check else f'x.{prop} {ref} {_operators[op][1]}'  # type: ignore[index]
        )
        src = clip  # type: ignore[assignment]

        def _cb_one_px_return_frame_n(n: int, f: vs.VideoFrame) -> int | SentinelT:
            return Sentinel.check(n, not not f[0][0, 0])

        def _cb_one_px_not_return_frame_n(n: int, f: vs.VideoFrame) -> bool:
            return not not f[0][0, 0]

        if return_frame_n:
            callback = _cb_one_px_return_frame_n
        else:
            callback = _cb_one_px_not_return_frame_n
    else:
        from ..utils import get_prop

        _op = _operators[op][0] if isinstance(op, str) else op

        def _cb_return_frame_n(n: int, f: vs.VideoFrame) -> int | SentinelT:
            assert _op
            return Sentinel.check(n, _op(get_prop(f, prop, (float, bool)), ref))

        def _cb_not_return_frame_n(n: int, f: vs.VideoFrame) -> bool:
            assert _op
            return _op(get_prop(f, prop, (float, bool)), ref)

        if return_frame_n:
            callback = _cb_return_frame_n
        else:
            callback = _cb_not_return_frame_n

    return src, callback