Skip to content

masks

Functions:

  • range_mask
  • stabilize_mask

    Generate a stabilization mask highlighting unstable regions between frames using temporal median and blur filtering.

  • strength_zones_mask

    Creates a mask based on a threshold strength, with optional adjustments using defined zones.

range_mask

range_mask(
    clip: VideoNode, rad: int = 2, radc: int = 0
) -> ConstantFormatVideoNode
Source code in vsmasktools/masks.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@limiter(mask=True)
def range_mask(clip: vs.VideoNode, rad: int = 2, radc: int = 0) -> ConstantFormatVideoNode:
    assert check_variable(clip, range_mask)

    def _minmax(clip: vs.VideoNode, iters: int, maxx: bool) -> vs.VideoNode:
        func = Morpho.maximum if maxx else Morpho.minimum

        for i in range(1, iters + 1):
            clip = func(clip, coordinates=Coordinates.from_iter(i))

        return clip

    return join(
        [
            ExprOp.SUB.combine(_minmax(plane, r, True), _minmax(plane, r, False))
            for plane, r in zip(split(clip), normalize_seq((radc and [rad, radc]) or rad, clip.format.num_planes))
        ]
    )

stabilize_mask

stabilize_mask(
    clip: VideoNode,
    radius: int = 3,
    ranges: FrameRangeN | FrameRangesN = None,
    scenechanges: Iterable[int] | None = None,
    kernel: BlurMatrix = MEAN_NO_CENTER,
    brz: int = 0,
    planes: Planes = None,
    func: FuncExcept | None = None,
) -> VideoNode

Generate a stabilization mask highlighting unstable regions between frames using temporal median and blur filtering.

Useful for stabilizing credit masks.

Parameters:

  • clip

    (VideoNode) –

    Input mask.

  • radius

    (int, default: 3 ) –

    Temporal radius for filtering. Higher values smooth more. Defaults to 3.

  • ranges

    (FrameRangeN | FrameRangesN, default: None ) –

    Frame ranges treated as scene changes.

  • scenechanges

    (Iterable[int] | None, default: None ) –

    Explicit list of scenechange frames.

  • kernel

    (BlurMatrix, default: MEAN_NO_CENTER ) –

    Blur kernel applied after the median step.

  • brz

    (int, default: 0 ) –

    Threshold bias for binarization. Higher = stricter. Defaults to 0.

  • planes

    (Planes, default: None ) –

    Which planes to process. Defaults to all.

  • func

    (FuncExcept | None, default: None ) –

    Function returned for custom error handling. This should only be set by VS package developers.

Returns:

  • VideoNode

    A mask clip where white marks unstable areas and black marks stable regions.

Source code in vsmasktools/masks.py
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
def stabilize_mask(
    clip: vs.VideoNode,
    radius: int = 3,
    ranges: FrameRangeN | FrameRangesN = None,
    scenechanges: Iterable[int] | None = None,
    kernel: BlurMatrix = BlurMatrix.MEAN_NO_CENTER,
    brz: int = 0,
    planes: Planes = None,
    func: FuncExcept | None = None,
) -> vs.VideoNode:
    """
    Generate a stabilization mask highlighting unstable regions between frames using temporal median and blur filtering.

    Useful for stabilizing credit masks.

    Args:
        clip: Input mask.
        radius: Temporal radius for filtering. Higher values smooth more. Defaults to 3.
        ranges: Frame ranges treated as scene changes.
        scenechanges: Explicit list of scenechange frames.
        kernel: Blur kernel applied after the median step.
        brz: Threshold bias for binarization. Higher = stricter. Defaults to 0.
        planes: Which planes to process. Defaults to all.
        func: Function returned for custom error handling. This should only be set by VS package developers.

    Returns:
        A mask clip where white marks unstable areas and black marks stable regions.
    """
    func = func or stabilize_mask

    frames = list[int]()
    scprev, scnext = set[int](), set[int]()

    if ranges:
        frames.extend(x for s, e in normalize_ranges(clip, ranges) for x in (s, e + int(not replace_ranges.exclusive)))

    if scenechanges:
        frames.extend(scenechanges)

    for f in frames:
        scprev.add(f)
        scnext.add(f - 1)

    def set_scenechanges(n: int, f: vs.VideoFrame) -> vs.VideoFrame:
        f = f.copy()
        f.props.update(
            _SceneChangePrev=f.props.get("_SceneChangePrev", False),
            _SceneChangeNext=f.props.get("_SceneChangeNext", False),
        )

        if n in scprev:
            f.props["_SceneChangePrev"] = True

        if n in scnext:
            f.props["_SceneChangeNext"] = True

        return f

    if scprev or scnext:
        clip = core.std.ModifyFrame(clip, clip, set_scenechanges)

    median = median_blur(clip, radius, ConvMode.TEMPORAL, planes, scenechange=True, func=func)

    radius_blur = radius * 2 + 1
    blurred = kernel(radius_blur, mode=ConvMode.TEMPORAL)(median, planes, scenechange=True, func=func)

    binarized = blurred.std.Binarize(
        scale_mask(1.0 / (radius_blur - clamp(brz, 0, radius_blur)), 32, clip), planes=planes
    )

    return binarized

strength_zones_mask

strength_zones_mask(
    base: SupportsFloat | VideoNode | None = None,
    zones: (
        Sequence[
            tuple[FrameRangeN | FrameRangesN, SupportsFloat | VideoNode | None]
        ]
        | None
    ) = None,
    format: int | VideoFormatLike | HoldsVideoFormat = GRAYS,
    length: int | None = None,
) -> ConstantFormatVideoNode

Creates a mask based on a threshold strength, with optional adjustments using defined zones.

Parameters:

  • base

    (SupportsFloat | VideoNode | None, default: None ) –

    The base clip used to generate the strength mask. If set to None, a blank mask (all zeros) will be created using the specified format.

  • zones

    (Sequence[tuple[FrameRangeN | FrameRangesN, SupportsFloat | VideoNode | None]] | None, default: None ) –

    Optional list of zones to define varying strength regions. Defaults to None.

  • format

    (int | VideoFormatLike | HoldsVideoFormat, default: GRAYS ) –

    Pixel format for the mask. Defaults to vs.GRAYS.

  • length

    (int | None, default: None ) –

    Total number of frames for the mask. If None, uses the length of the base clip.

Returns:

  • ConstantFormatVideoNode

    A mask clip representing the defined strength zones.

Source code in vsmasktools/masks.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 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
def strength_zones_mask(
    base: SupportsFloat | vs.VideoNode | None = None,
    zones: Sequence[tuple[FrameRangeN | FrameRangesN, SupportsFloat | vs.VideoNode | None]] | None = None,
    format: int | VideoFormatLike | HoldsVideoFormat = vs.GRAYS,
    length: int | None = None,
) -> ConstantFormatVideoNode:
    """
    Creates a mask based on a threshold strength, with optional adjustments using defined zones.

    Args:
        base: The base clip used to generate the strength mask. If set to None, a blank mask (all zeros) will be created
            using the specified format.
        zones: Optional list of zones to define varying strength regions. Defaults to None.
        format: Pixel format for the mask. Defaults to vs.GRAYS.
        length: Total number of frames for the mask. If None, uses the length of the base clip.

    Returns:
        A mask clip representing the defined strength zones.
    """

    if isinstance(base, vs.VideoNode):
        base_clip = depth(base, format, dither_type=DitherType.NONE)

        if length:
            if base_clip.num_frames > length:
                base_clip = base_clip[:length]

            if base_clip.num_frames < length:
                base_clip = base_clip + base_clip[-1] * (length - base_clip.num_frames)

    elif base is None:
        base_clip = vs.core.std.BlankClip(format=get_video_format(format).id, length=length, color=base, keep=True)
    else:
        base_clip = vs.core.std.BlankClip(
            format=get_video_format(format).id, length=length, color=float(base), keep=True
        )

    if not zones:
        return base_clip

    cache_strength_clips = dict[float, ConstantFormatVideoNode]()
    strength_clips = [base_clip]
    indices = [(0, n) for n in range(base_clip.num_frames)]

    for i, z in enumerate(zones, 1):
        rng, strength = z
        rng = normalize_ranges(base_clip, rng)

        for s, e in rng:
            e += bool(not replace_ranges.exclusive)
            indices[s:e] = [(i, n) for n in range(s, e)]

        if isinstance(strength, vs.VideoNode):
            if TYPE_CHECKING:
                assert check_variable_format(strength, strength_zones_mask)

            check_ref_clip(base_clip, strength, strength_zones_mask)

            strength_clips.append(strength)
            continue

        strength = 0 if strength is None else float(strength)

        if strength not in cache_strength_clips:
            cache_strength_clips[strength] = vs.core.std.BlankClip(base_clip, color=strength, keep=True)

        strength_clips.append(cache_strength_clips[strength])

    cache_strength_clips.clear()

    return vs.core.std.FrameEval(base_clip, lambda n: strength_clips[indices[n][0]][indices[n][1]], clip_src=base_clip)