Usage

Installation

vsfieldkit is hosted on PyPI so it’s as simple as using your favorite installer:

python -m pip install vsfieldkit
poetry add vsfieldkit

To add to a specific scripts directory:

python -m pip install --target=./my_scripts_dir vsfieldkit

The package uses semantic versioning to indicate backwards compatible changes to the API.

As the developer does not have Windows, vsrepo is not officially supported. That said, it seems to be able to install vsfieldkit.

Dependencies

For most functions, just VapourSynth. The fill_analog_frame_ends() function requires the FillBorders and either the ContinuityFixer or EdgeFixer plugins.

Functions

Reinterpreting

vsfieldkit.assume_bff(clip) VideoNode

Returns a new clip where every frame is marked as interlaced in bottom-field-first order. Only changes metadata, does not adjust the clip content or re-arrange chroma samples.

Parameters

clip (vapoursynth.VideoNode) –

Return type

vapoursynth.VideoNode

vsfieldkit.assume_progressive(clip) VideoNode

Returns a new clip where every frame is marked as progressive. Only changes metadata, does not adjust the clip content or re-arrange chroma samples.

For progressive content that has been encoded as interlaced with vertical chroma subsampling, use vsfieldkit.resample_as_progressive() or vsfieldkit.upsample_as_progressive() instead.

Parameters

clip (vapoursynth.VideoNode) –

Return type

vapoursynth.VideoNode

vsfieldkit.assume_tff(clip) VideoNode

Returns a new clip where every frame is marked as interlaced in top-field-first order. Only changes metadata, does not adjust the clip content or re-arrange chroma samples.

Parameters

clip (vapoursynth.VideoNode) –

Return type

vapoursynth.VideoNode

Deinterlacing

vsfieldkit.bob(clip, shift=True, tff=None, keep_field_property=True, kernel=core.resize.Spline36, dither_type='random')

A simple bob deinterlacer. Returns a clip of progressive frames, each consisting of a field from the original interlaced clip in order of its original capture. As interlaced fields have half the resolution of a given moment, the new frames are stretched up to the original clip’s height.

If shifting for playback comfort, VapourSynth R58 and above provides a built-in resize.Bob() that should be used instead as it provides near-identical functionality.

Parameters
  • clip (VideoNode) – Video with interlaced frames to bob into the resulting clip.

  • shift (bool) – Whether to shift the lines during scaling to account for the field’s position in a full frame. Recommended if the output is intended for playback.

  • tff (bool) – Specifies the field order to assume when scanning progressive footage or clips without field order marking. True assumes top-field-first. False for bottom-field-first.

  • kernel (Callable) – Resizing/resampling function from vapoursynth.core.resize to use to stretch the fields to the target frame height. Defaults to resize.Spline36().

  • dither_type (str) – If video is processed at a higher bit depth internally before being returned to an original depth of less than 16 bits per plane, this dithering method will be used to avoid banding and other unnatural artifacts caused by rounding at low bit rate.

vsfieldkit.resample_as_progressive(clip, kernel=core.resize.Spline36, dither_type='random') VideoNode

This should be used instead of vsfieldkit.assume_progressive() when progressive content has been encoded interlaced with vertical chroma subsampling.

The primary use-case for this is removing 2:2 pulldown on 25p content that’s been hard-telecined to 50i in DV, DVB, or DVD formats with 4:2:0 chroma subsampling. It can also be used to resample chroma on frames created with manual field matching that pulled up other pulldown patterns.

When progressive content is encoded as interlaced pictures with 4:2:0 chroma subsampling, the chroma samples span alternating instead of adjacent lines. Simply marking/assuming such clips as progressive could result in color samples being attributed to the wrong lines (bleeding), and in those cases this function can be used instead. It will prevent bleeding, though as this comes up with new samples for the progressive content, it can result in some loss of original color precision.

If you wish to perform additional processing before the final chroma subsampling is restored, use vsfieldkit.upsample_as_progressive() instead.

Parameters
  • clip (VideoNode) – Video with progressive frames encoded as interlaced with vertical subsampling.

  • kernel (Callable) – Resizing/resampling function from vapoursynth.core.resize to use to stretch the fields to the target frame height. Defaults to resize.Spline36().

  • dither_type (str) – If video is processed at a higher bit depth internally before being returned to an original depth of less than 16 bits per plane, this dithering method will be used to avoid banding and other unnatural artifacts caused by rounding at low bit rate.

vsfieldkit.scan_interlaced(clip, warmup_clip=None, tff=None, chroma_subsample_scanning=ChromaSubsampleScanning.SCAN_LATEST, decay_factor=None, decay_base=None, dither_type='random', post_processing=(), post_processing_blend_kernel=core.resize.Spline36) VideoNode

Returns a new clip where interlaced fields from the original clip are painted onto each frame in their correct position moment-by-moment like an interlaced scan display would. This is sometimes referred to as display interlacing, phosphor deinterlacing, or simply interlaced scan. Like bob deinterlacing, it doubles the amount of frames used to portray the moments represented in the interlaced footage.

Interlaced content is typically stored or transmitted with two moments interlaced into one frame and each moment only appearing in that one frame. This balances the destructive compression of time and image resolution. The frames generated by scan_interlaced will repeat a field from the previous moment, losing that compression advantage. Additionally, they can’t be treated as interlaced by downstream filters or playback systems that expect a field’s picture to only appear once. Because of this, they are marked as progressive by the function. It might be better to call this function a “display interlacer” rather than a deinterlacer.

This was inspired by Juha Jeronen’s wonderful Phosphor deinterlacer for VideoLAN’s VLC media player. This code was not derived from it, but it tries to at least keep the subsampling nomenclature the same.

More background and some examples can be found in the scan_interlaced Deep Dive.

Parameters
  • clip (VideoNode) – Video with interlaced frames to scan to the resulting clip.

  • warmup_clip (VideoNode) – The first field from the main clip will be painted alongside the last field of the warmup clip if supplied. This can allow seamless splicing of scan_interlace output with other clips. If no warmup clip is supplied, black scanlines are used to warm up that field.

  • tff (bool) – Specifies the field order to assume when scanning progressive footage or clips without field order marking. True assumes top-field-first. False for bottom-field-first. Applies to the main clip and/or the warmup clip if either have not-explicitly-interlaced frames.

  • chroma_subsample_scanning (ChromaSubsampleScanning) –

    When Chroma is sub-sampled vertically, such as in Y’CbCr 4:2:0 clips, a decision must be made on how to present the color of the newly-laced scan lines in the final frames because those frames will be marked as progressive. Progressive frames don’t have chroma samples for alternating scan lines. Without a chroma scanning decision, the first line’s color would bleed into the second line, which was scanned from a different moment, third into the fourth… resulting in thicker visual comb lines and lines having color untrue to their source material.

    Enumerations are available on the vsfieldkit top level module and the ChromaSubsampleScanning enum.

  • decay_factor (Factor) – Amount by which to dim the lines scanned in the previous moment, exposing the decay_base clip. Usually expressed as a float, Decimal or Fraction where 1 means the previously-laced scan lines are completely replaced by lines from the decay_base clip, 0.5 means the clip is dimmed half and 0 means there is no dimming at all. This simulates the decay of cathode ray tube phosphors in the moments after they’ve been scanned onto. decay_base can be used to dim to a background other than solid black.

  • decay_base (VideoNode) – A background clip that previously-scanned scan lines should be dimmed to instead of black. Ignored if decay_factor is not set. Should be one frame long. The frame will be re-used.

  • dither_type (str) – If video is processed at a higher bit depth internally before being returned to an original depth of less than 16 bits per plane, this dithering method will be used to avoid banding and other unnatural artifacts caused by rounding colors to the nearest integer.

  • post_processing (Sequence[InterlacedScanPostProcessor]) –

    Post-processing steps to run on the frames resulting from interlaced scanning. At the moment, only BLEND_VERTICALLY is available.

    Enumerations are available on the vsfieldkit top level module and the InterlacedScanPostProcessor enum.

vsfieldkit.upsample_as_progressive(clip) VideoNode

Returns a clip now marked as progressive and with any vertical chroma subsampling removed so that previously-alternating chroma lines will be laid out in the correct one-line-after-another order for progressive content.

This should be used instead of vsfieldkit.assume_progressive() when the progressive frames have been encoded interlaced and additional processing is desired before restoring the target chroma sub-sampling.

Example
# Interpret as progressive, removing vertical chroma subsampling
upsampled = vsfieldkit.upsample_as_progressive(clip)

# Additional processing:
fixed_edges = awsmfunc.bbmod(upsampled, left=2, right=3)

# Restore original subsampling with favorite kernel then output:
resampled = fixed_edges.resize.Spline36(format=clip.format)
resampled.set_output()

Repair

vsfieldkit.fill_analog_frame_ends(clip, top_blank_width=None, bottom_blank_width=None, continuity_radius=(5,), luma_splash_radius=1, original_format=None, restore_blank_detail=False) VideoNode

Fills the beginning and end half-lines from frames digitized from or for PAL/NTSC/SECAM signal. These lines are often half-blanked so that a CRT monitor’s electron beam won’t light up phosphors as it zig-zags from the bottom of screen to the top to start painting the next frame.

It aims to interpolate only the missing data, leaving clean pixels in-tact. Interpolation is performed by repetition and averaging of adjacent line data using the FillBorders plugin followed by least-squares regression using the ContinuityFixer or EdgeFixer plugin.

If the bottom black bar coincides with head-switching noise from a camera or VCR, the bottom bar repair will not be useful.

Parameters
  • clip (VideoNode) – Video from or for analog source. Can be in its original interlaced form or de-interlaced.

  • top_blank_width (int) – Width in pixels of the top-left black bar at its longest, including any horizontal fade. If not supplied, assumed to be 65% of the top line. Set to 0 to not attempt top line repair.

  • bottom_blank_width (int) – Width in pixels of the bottom-right black bar at its longest, including any horizontal fade. If not supplied, assumed to be 65% of the bottom line. Set to 0 to not attempt bottom line repair.

  • continuity_radius (int or Sequence[int]) – Number of rows next to the black bar to use as input for interpolating the new pixels to generate inside the bar.

  • luma_splash_radius (int) –

    Repair this many extra rows of luma data above or below the half line. Adjacent picture data is often damaged by the black bar if the video’s fields are resized from their original signal height (e.g. from 486i to 480i for NTSC to fit a DVD or DV stream) or if the studio applied artificial sharpening.

    If the adjacent rows have correct brightness even if they’re gray, this can be set to 0 to persist the clean luma data. The function’s adjustments for chroma sub-sampling should address adjacent gray area.

  • original_format (PresetFormat, VideoFormat, VideoNode or int) – If the clip to repair has been up-sampled for editing (e.g. from YUV420P8 to YUV422P16), pass in the original clip’s format here so that correct assumptions are made for damage repair decisions.

  • restore_blank_detail (bool) – In rare cases where the black bars contain salvageable image data, this can be used to merge some of that original data on top of the filled-and-continued repair of the bar. Otherwise, this introduces noise.

Utility

vsfieldkit.double(clip) VideoNode

Returns a clip where each original frame is repeated once and plays at twice the speed so the played image matches the original in time.

Not specific to interlacing or deinterlacing, but useful for comparing original interlaced pictures with frame-doubled content such as that from a bob or phosphor deinterlacer.

Parameters

clip (vapoursynth.VideoNode) –

Return type

vapoursynth.VideoNode

vsfieldkit.group_by_combed(clip) Iterator[Tuple[Union[bool, None], VideoNode]]

Assuming the passed-in clip was processed by a filter that performs comb detection, this splits the clip into segments based on whether they are combed or not. The values it generates are True, False, or None if it was marked combed, not combed, or not marked as well as the segment of the clip. This does not have any built-in comb detection.

This function requests rendered frames and blocks until it gets them. If not needing to remove frames, splice additional frames, or analyze frames, consider using std.FrameEval() or std.ModifyFrame() instead for simple comb-based frame replacements.

Example
progressive_clips = []
detelecined = tivtc.TFM(clip, PP=1)
for combed, segment in vsfieldkit.group_by_combed(detelecined):
    if combed:
        progressive_clips.append(
            havsfunc.QTGMC(segment, TFF=False)
        )
    else:
        progressive_clips.append(
            tivtc.TDecimate(segment, tff=False)
        )
vs.core.std.Splice(progressive_clips).set_output()
vsfieldkit.group_by_field_order(clip) Iterator[Tuple[Union[FieldBased, None], VideoNode]]

Generates field orders and clips from the passed in clip split up by changes in field order. Field order is expressed as a FieldBased enumeration or None if field order is not applicable or not available.

This function requests rendered frames and blocks until it gets them. If not needing to remove frames, splice additional frames, or analyze frames, consider using std.FrameEval() or std.ModifyFrame() instead for simple field-order-based frame replacements.

Example
progressive_clips = []
for order, segment in vsfieldkit.group_by_field_order(clip):
    if order == vs.FIELD_TOP:
        progressive_clips.append(
            havsfunc.QTGMC(segment, TFF=True)
        )
    elif order == vs.FIELD_BOTTOM:
        progressive_clips.append(
            havsfunc.QTGMC(segment, TFF=False)
        )
    elif order == vs.PROGRESSIVE:
        progressive_clips.append(
            vsfieldkit.double(segment)
        )
vs.core.std.Splice(progressive_clips).set_output()

Types

class vsfieldkit.ChromaSubsampleScanning(value)

An enumeration.

SCAN_BLENDED = 'SCAN_BLENDED'

Internally, original field chroma is upsampled to have single line color. The final image is then resampled to the original subsampling format, causing each line’s color to be blended with its neighbours. Currently the blending is performed after post-processing to allow post-processors access to the upsampled chroma data.

SCAN_LATEST = 'SCAN_LATEST'

The field that is new in a frame supplies the color for all lines of that frame.

SCAN_UPSAMPLED = 'SCAN_UPSAMPLED'

Returns a clip upsampled to have single line color. For example, if YUV420P8 clip was scanned, the resulting clip would be in YUV422P8 ensure the original colors from each line’s source are maintained.

class vsfieldkit.InterlacedScanPostProcessor(value)

An enumeration.

BLEND_VERTICALLY = 'BLEND_VERTICALLY'

Blends the entire contents vertically to remove comb lines. You effectively lose close to half of the vertical detail as a side effect.

vsfieldkit.Factor

alias of Union[int, float, decimal.Decimal, fractions.Fraction]