Skip to main content

vapoursynth-letterbox

A VapourSynth plugin to find and clean letterbox.

Usage

You should use functions in the Python package to use this plugin. These functions perform some very important pre and postprocessing. Only with these protection is this letterbox masking safe to use.

The functions in the Python package depends on vs-jetpack. If you can't use vs-jetpack, it's highly suggested to copy the code out and implement it into your environment.

For video with no permanent letterbox:

from vsletterbox import clean_letterbox

clip = clean_letterbox(clip)

For video with 129-pixel permanent letterbox:

clip = clean_letterbox(clip, permanent=[129, 129]) # [Top, Bottom]

In addition to cleaning the noise, we can also perform border deringing based on the dynamic letterbox detected.

clip = clean_letterbox(
    clip,
    permanent=[129, 129],
    border_y=lambda clip: clip.bore.SinglePlane(top=4, bottom=4),
    border_u=lambda clip: clip.bore.SinglePlane(top=2, bottom=2),
    border_v=lambda clip: clip.bore.SinglePlane(top=2, bottom=2)
)

Alternatively, we also provide letterbox_mask function with which you can apply your own operations.

from vsletterbox import letterbox_mask

mask = letterbox_mask(clip, permanent=[129, 129])

Method

We detect letterbox based on three details:

  1. We iterate row by row from the edge pixel in, calculating the mean brightness of the pixels in each row.
    We detect if starting from a certain row the mean brightness rapidly increases, through a statistical moving predicter.

  2. We apply a sensitive general edgemask to the image, and we require the row detected in method 1. to have high edgemask coverage.
    This is to prevent cases such as title screens with only white text on black background to be detected as letterbox.

  3. If the detected row is within 1 row from the user provided permanent row, we snap the detected row to the permanent row.
    This is to combat the case where there is not a clean cutoff at the border and random pixels protrude to the otherside.
    In addition, method 1. currently requires the border row to be at least 6.375 at 8-bit higher than the mean noise brightness. In very dark scenes with dirty border, there might not be enough change to trigger the detection on the very first border row. This is also to combat this potential issue.

  4. When there is no letterbox detected such as in a pure black frame, or when the border is rejected by method 2. user provided permanent row applies.

Once letterbox border is identified:

  1. Any pixel of the letterbox whose brightness and colour is below a set threshold is cleaned to pure black.
    This threshold is to protect cases where there are intentional items in the border such as the opening of 173295 / 57810.

    This is protected by a Morpho.minimum() to make the cleaning stay further away from intentional items.
    If the source contains very heavy noise or chroma noise, the threshold for this needs to be increased.

  2. We don't want to eliminate the noise in a pure black screen for multiple reasons.
    First, the video is still going and it shouldn't just be completely blank.
    Second, there are situations such as fading. When the image is fading to black, there is noise during the fading. But when the fading ends, the letterbox detection triggers, and suddenly all the noise goes away within the time of a single frame. That'll be really odd.

    Instead a protection is applied and the letterbox cleaning strength is reduced as the area of the letterbox increases until it reaches a full black screen where no cleaning is applied.

    As an exception, the cleaning will still apply to the user provided permanent letterbox in a pure black screen.

  3. border_y, border_u, border_v will be applied to the image clip with letterbox cropped away.

Reference

clean_letterbox(
    clip:            vs.VideoNode,

    # Threshold in method 5.
    # Full cleaning is applied when transformed pixel value <= thr - transition
    # No cleaning is applied when transformed pixel value > thr
    thr:             float     = 0.030
    transition:      float     = 0.015

    # Permanent letterbox used in method 3. and 4., as well as in method 6.
    permanent:       list[int] = [0, 0], # [Top, Bottom]

    # Enables the detection, without which only method 5. and 7. will apply  
    dynamic:         bool      = True,
    # Method 2.
    dynamic_ref:     Callable[[vs.VideoNode], vs.VideoNode]
                               = ExKirsch().edgemask,
    # Method 2.
    dynamic_ref_thr: float     = 2/3,

    # Method 6.
    # The cleaning strength starts reducing when the area of the image is smaller than this
    # threshold.
    fullblack_thr:   float     = 1/5,

    # Method 7.
    # Example function:
    # lambda clip: clip.bore.SinglePlane(top=2, bottom=2)
    border_y:        Callable[[vs.VideoNode], vs.VideoNode] | None
                               = None,
    border_u:        Callable[[vs.VideoNode], vs.VideoNode] | None
                               = None,
    border_v:        Callable[[vs.VideoNode], vs.VideoNode] | None
                               = None,
)
letterbox_mask(
    clip:            vs.VideoNode,

    # Method 1., 2., 3., 4., and 6. applies

    # Permanent letterbox used in method 3. and 4., as well as in method 6.
    permanent:       list[int] = [0, 0], # [Top, Bottom]

    # Method 2.
    dynamic_ref:     Callable[[vs.VideoNode], vs.VideoNode]
                               = ExKirsch().edgemask,
    # Method 2.
    dynamic_ref_thr: float     = 2/3,

    # Method 6.
    # The cleaning strength starts reducing when the area of the image is smaller than this
    # threshold.
    fullblack_thr:   float     = 1/5,
)
find_letterbox(
    clip:            vs.VideoNode,

    # Method 1., 2., 3., 4. applies
    # Outputs `VSLETTERBOX_TOP_ROW` and `VSLETTERBOX_BOTTOM_ROW` frame properties marking the first
    # and last row of the image, both inclusive (of the image)

    # Permanent letterbox used in method 3. and 4., as well as in method 6.
    permanent:       list[int] = [0, 0], # [Top, Bottom]

    # Method 2.
    dynamic_ref:     Callable[[vs.VideoNode], vs.VideoNode]
                               = ExKirsch().edgemask,
    # Method 2.
    dynamic_ref_thr: float     = 2/3,
)

Release files for vapoursynth-letterbox 1.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for vapoursynth-letterbox 1.0.2
File Size Uploaded
vapoursynth_letterbox-1.0.2.tar.gz 12.1 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for vapoursynth-letterbox 1.0.2
File
vapoursynth_letterbox-1.0.2-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
vapoursynth_letterbox-1.0.2-py3-none-manylinux_2_24_x86_64.whl Python 3 none Linux glibc 2.24+ x86-64 Details
vapoursynth_letterbox-1.0.2-py3-none-macosx_12_0_x86_64.whl Python 3 none macOS 12.0+ x86-64 Details
vapoursynth_letterbox-1.0.2-py3-none-macosx_12_0_arm64.whl Python 3 none macOS 12.0+ ARM64 Details

Total release size: 74.4 kB

Release files / vapoursynth_letterbox-1.0.2.tar.gz

Download URL vapoursynth_letterbox-1.0.2.tar.gz
Size 12.1 kB
Tags Source
SHA-256 checksum
How to use checksums
e03c97b7dd755da1e78c9dd771c13cd8f574f1d69405d102aa92b9089e478c7c
BLAKE2b-256 checksum
How to use checksums
8d1ba7fc4dee9602d123e88c210d8fa1b3e756a4b5f63a43fb6c0ea94ecceaa9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 13, 2026.

Transparency log

Release files / vapoursynth_letterbox-1.0.2-py3-none-win_amd64.whl

Download URL vapoursynth_letterbox-1.0.2-py3-none-win_amd64.whl
Size 18.3 kB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
159ac9a84da6249df6ec0ca5abebedf17bcee94e1390b77fefbbce40707d027e
BLAKE2b-256 checksum
How to use checksums
4e27d726547d78df7def2563ddefa4299843dd8d673abcb32d6711b23356b1e6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 13, 2026.

Transparency log

Release files / vapoursynth_letterbox-1.0.2-py3-none-manylinux_2_24_x86_64.whl

Download URL vapoursynth_letterbox-1.0.2-py3-none-manylinux_2_24_x86_64.whl
Size 15.5 kB
Tags Linux glibc 2.24+ x86-64 Python 3
SHA-256 checksum
How to use checksums
5a5ed536a639bab42a1dd3375d3977f22aa1821f119c6092068d059c2d56f1e9
BLAKE2b-256 checksum
How to use checksums
f4390daea3d9c2e7fcc907c08ead9e830f44548bf27800fa3be1222ed3d015b3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 13, 2026.

Transparency log

Release files / vapoursynth_letterbox-1.0.2-py3-none-macosx_12_0_x86_64.whl

Download URL vapoursynth_letterbox-1.0.2-py3-none-macosx_12_0_x86_64.whl
Size 14.2 kB
Tags Python 3 macOS 12.0+ x86-64
SHA-256 checksum
How to use checksums
e919327d01e9de09a8b3401977129fb93444d6eec318f1ab41afdc5609495441
BLAKE2b-256 checksum
How to use checksums
edb2a56a251091f1b68b546b48e9d3c51cdea95f20c1461121122acf5f1fad2c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 13, 2026.

Transparency log

Release files / vapoursynth_letterbox-1.0.2-py3-none-macosx_12_0_arm64.whl

Download URL vapoursynth_letterbox-1.0.2-py3-none-macosx_12_0_arm64.whl
Size 14.3 kB
Tags Python 3 macOS 12.0+ ARM64
SHA-256 checksum
How to use checksums
1468a52e8a7e0271bcd960b8ea127ddc8b1534f73a4449a5c56d8c7db0063b97
BLAKE2b-256 checksum
How to use checksums
24505bad60b4c767516dcc37cb83a1056bae8199f1412702d8746e72e760aa5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 13, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.2 This release

5 release files

1.0.1

5 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page