Skip to content

panning

Classes:

Functions:

PanDirection

Bases: CustomIntEnum

Attributes:

INVERTED class-attribute instance-attribute

INVERTED = 1

NORMAL class-attribute instance-attribute

NORMAL = 0

PanFunction

Bases: NamedTuple

Attributes:

direction class-attribute instance-attribute

direction: PanDirection = NORMAL

function_x class-attribute instance-attribute

function_x: EasingT = OnAxis

function_y class-attribute instance-attribute

function_y: EasingT = OnAxis

PanFunctions

Bases: PanFunction, CustomEnum

Attributes:

HORIZONTAL_LTR class-attribute instance-attribute

HORIZONTAL_LTR = PanFunction(function_x=Linear)

HORIZONTAL_RTL class-attribute instance-attribute

HORIZONTAL_RTL = PanFunction(INVERTED, function_x=Linear)

VERTICAL_BTT class-attribute instance-attribute

VERTICAL_BTT = PanFunction(INVERTED, function_y=Linear)

VERTICAL_TTB class-attribute instance-attribute

VERTICAL_TTB = PanFunction(function_y=Linear)

direction class-attribute instance-attribute

direction: PanDirection = NORMAL

function_x class-attribute instance-attribute

function_x: EasingT = OnAxis

function_y class-attribute instance-attribute

function_y: EasingT = OnAxis

panner

panner(
    clip: VideoNode,
    stitched: VideoNode,
    pan_func: PanFunction | PanFunctions = VERTICAL_TTB,
    fps: Fraction = Fraction(24000, 1001),
    kernel: KernelLike = Catrom,
) -> VideoNode
Source code in vstransitions/panning.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def panner(
    clip: vs.VideoNode,
    stitched: vs.VideoNode,
    pan_func: PanFunction | PanFunctions = PanFunctions.VERTICAL_TTB,
    fps: Fraction = Fraction(24000, 1001),
    kernel: KernelLike = Catrom,
) -> vs.VideoNode:
    assert check_variable_format(clip, panner)
    assert check_variable_format(stitched, panner)

    if (stitched.format.subsampling_h, stitched.format.subsampling_w) != (0, 0):
        raise InvalidSubsamplingError(panner, stitched.format, "Stitched can't be subsampled!", reason="{subsampling}")

    kernelo = Kernel.ensure_obj(kernel, panner)
    clip_cfps = change_fps(clip, fps)

    offset_x, offset_y = (stitched.width - clip.width), (stitched.height - clip.height)

    ease_x = pan_func.function_x(0, offset_x, clip_cfps.num_frames).ease
    ease_y = pan_func.function_y(0, offset_y, clip_cfps.num_frames).ease

    clamp_x = partial(lambda x: int(clamp(x, min_val=0, max_val=offset_x)))
    clamp_y = partial(lambda x: int(clamp(x, min_val=0, max_val=offset_y)))

    def _pan(n: int) -> vs.VideoNode:
        x_e, x_v = divmod(clamp_x(ease_x(n)), 1)
        y_e, y_v = divmod(clamp_y(ease_y(n)), 1)

        if n == clip_cfps.num_frames - 1:
            x_e, y_e = clamp_x(offset_x - 1), clamp_y(offset_y - 1)
            x_v, y_v = int(x_e == offset_x - 1), int(y_e == offset_y - 1)

        x_c, y_c = ceil(x_v), ceil(y_v)

        cropped = stitched.std.CropAbs(clip.width + x_c, clip.height + y_c, int(x_e), int(y_e))

        shifted = kernelo.shift(cropped, (y_v, x_v))

        cropped = shifted.std.Crop(bottom=y_c, right=x_c)

        return kernelo.resample(cropped, clip)

    newpan = clip_cfps.std.FrameEval(_pan)

    return newpan[::-1] if pan_func.direction == PanDirection.INVERTED else newpan