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:
-
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. -
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. -
If the detected row is within 1 row from the user provided
permanentrow, we snap the detected row to thepermanentrow.
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 least6.375at 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. -
When there is no letterbox detected such as in a pure black frame, or when the border is rejected by method 2. user provided
permanentrow applies.
Once letterbox border is identified:
-
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 aMorpho.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. -
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. -
border_y,border_u,border_vwill 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.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vapoursynth_letterbox-1.0.1.tar.gz | 12.1 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| vapoursynth_letterbox-1.0.1-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| vapoursynth_letterbox-1.0.1-py3-none-manylinux_2_24_x86_64.whl | Python 3 | none | Linux glibc 2.24+ x86-64 | Details |
| vapoursynth_letterbox-1.0.1-py3-none-macosx_26_0_x86_64.whl | Python 3 | none | macOS 26.0+ x86-64 | Details |
| vapoursynth_letterbox-1.0.1-py3-none-macosx_26_0_arm64.whl | Python 3 | none | macOS 26.0+ ARM64 | Details |
Total release size: 74.4 kB
Release files / vapoursynth_letterbox-1.0.1.tar.gz
| Download URL | vapoursynth_letterbox-1.0.1.tar.gz |
|---|---|
| Size | 12.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
483fa711cebd2ce663cfea50f9274da727e80cf32427dd796b7bda2e4ed3d66d
|
|
BLAKE2b-256 checksum How to use checksums |
45c924fe8cca2b40c99a1ceb130b322c3803800dbf9f62dcb1401094ea8f42b8
|
| 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 logRelease files / vapoursynth_letterbox-1.0.1-py3-none-win_amd64.whl
| Download URL | vapoursynth_letterbox-1.0.1-py3-none-win_amd64.whl |
|---|---|
| Size | 18.3 kB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
6d0b64b74defaef55f3d8fe6db5309280b89f3b67ecc85fc5349700a3ae83263
|
|
BLAKE2b-256 checksum How to use checksums |
f2af1db84d3e68dc5b53833a1a153453b343ba98fcfb5e518be2b496f5d7729b
|
| 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 logRelease files / vapoursynth_letterbox-1.0.1-py3-none-manylinux_2_24_x86_64.whl
| Download URL | vapoursynth_letterbox-1.0.1-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 |
02e17ab82d609d2fea2e77687c9a2e965ef7118d17691d1c75498aff073a01aa
|
|
BLAKE2b-256 checksum How to use checksums |
064e7b7b93aa998065dad61d8ebfbff06d97b9f036b7f4114b809d0e360244c5
|
| 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 logRelease files / vapoursynth_letterbox-1.0.1-py3-none-macosx_26_0_x86_64.whl
| Download URL | vapoursynth_letterbox-1.0.1-py3-none-macosx_26_0_x86_64.whl |
|---|---|
| Size | 14.2 kB |
| Tags | Python 3 macOS 26.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
c0f53e7a1081855475090a8401ad2e6b73221f02135ca3406060c0aef85c9538
|
|
BLAKE2b-256 checksum How to use checksums |
c9d43ca57a29ac32c08cd990d8c77ed49d2eefaf8304294cb6711ccf81525e53
|
| 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 logRelease files / vapoursynth_letterbox-1.0.1-py3-none-macosx_26_0_arm64.whl
| Download URL | vapoursynth_letterbox-1.0.1-py3-none-macosx_26_0_arm64.whl |
|---|---|
| Size | 14.3 kB |
| Tags | Python 3 macOS 26.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
1f9f4aaf9ec314f8592e5753e292a4af4eecccde94ca83259597287d37e046b4
|
|
BLAKE2b-256 checksum How to use checksums |
3f3c000721f7ad5c9e8e6acfbf8462ec45013e19973a8126c3b402414ada2bfd
|
| 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