Skip to main content

Inverse-telecine plugin for VapourSynth — Zig port of IT, with the original Avisynth parameters restored

Project description

zit — Inverse Telecine for VapourSynth (Zig port)

A Zig port of the VapourSynth-IT plugin (3:2-pulldown removal for NTSC), with the Avisynth-original parameters restored, full frame-property support, and @Vector-based SIMD.

Verified bit-exact against the upstream --c reference path across the integration test grid and on real-world telecined NTSC VOB samples.

Status

Item State
Algorithm port (8 modules, ~2200 LoC Zig)
Bit-exact vs upstream C path ✅ (198 fixture + 720 real-VOB frames)
All Avisynth params (ref, blend, diMode)
Frame properties ✅ standard + diagnostic
SIMD via @Vector ✅ ~2× over scalar, ~4× over VIVTC VFM
Cross-compile Linux / macOS / Windows x86_64
CI workflow ✅ (lint + unit + cross + best-effort integration)
v1.3.0 release
AI-assisted port ✅ Anthropic's Claude — verified byte-for-byte against the upstream C reference

Quick start

import vapoursynth as vs
core = vs.core

clip = core.bs.VideoSource("source.vob")
clip = core.zit.IT(clip)          # default: fps=24, ref="TOP", diMode=3
clip.set_output()

Plugin reference

Plugin namespace: zit. Function: IT.

Full signature:

core.zit.IT(
    clip,
    fps=24,
    threshold=20,
    pthreshold=75,
    ref="TOP",
    blend=0,
    diMode=3,
)

Parameters

Name Type Default Description
clip vnode Input clip. Must be YUV420P8, width a multiple of 16, height even, width ≤ 8192.
fps int 24 24 = inverse telecine (decimate 5→4, output 24000/1001 fps), 30 = field-matching only (input fps preserved).
threshold int 20 Field-match decision sensitivity. Lower = more aggressive matching.
pthreshold int 75 Progressive-classification threshold for _Combed. Adjusted internally for resolution. Below: frame is ip='P' (clean match); above: ip='I' (deinterlaced).
ref data "TOP" Field-order / match-search direction (case-insensitive). One of: TOP, BOTTOM, ALL, NONE (see below).
blend int (0/1) 0 When 1 and fps=24, blends adjacent post-matched frames with a triangular kernel for smoother 24p output. Motion-gated — only fires on high-motion 5-frame blocks. Ignored when fps=30.
diMode int 3 Deinterlace strategy applied when a frame is classified ip='I'. See below.

ref values

Value Avisynth name Effect
"TOP" REF_PREV Field match looks at the previous frame as the bottom-field source. Standard for TFF source. Same as the VapourSynth upstream's behaviour.
"BOTTOM" REF_NEXT Field match looks at the next frame. Use for BFF source.
"ALL" REF_ALL Evaluates both prev and next, picks the one with stronger evidence.
"NONE" REF_NONE Skips field-matching entirely; every frame is treated as interlaced and dispatched to the deinterlacer.

diMode values

Value Avisynth name Behaviour
0 DI_MODE_NONE No deinterlace. Just field-copy from the chosen match (same as the clean-match path).
1 DI_MODE_DEINTERLACE The full Avisynth deinterlacer: per-pixel scores C, P, N, avg(C,P), avg(C,N) and picks the lowest interlace score, with motion-gated vertical-average fallback.
2 DI_MODE_SIMPLE_BLUR Vertical (T + 2·C + B) / 4 blur on pixels flagged by the motion map.
3 DI_MODE_ONE_FIELD Default. Field-interpolation using motion + simple-blur maps. The VapourSynth upstream hardcodes this mode.

Frame properties on output

Standard (always set)

Key Type Description
_FieldBased int Always 0. Output is progressive after IVTC.
_Combed int 0 if the algorithm matched cleanly (ip='P'), 1 if it had to deinterlace (ip='I').
_DurationNum, _DurationDen int Per-frame duration derived from the output framerate. At fps=24 mode this becomes 1001 / 24000 per frame instead of the source's 1001 / 30000.
_Matrix, _Transfer, _Primaries, _ChromaLocation, _Range/_ColorRange, _SARNum/_SARDen int Inherited from the source frame via propSrc. Pass-through unchanged.

Diagnostic (set for inspection/scripting)

Key Type Description
ITMatch utf8 (1 char) Match decision: 'C', 'P', 'N' (uppercase = strong, lowercase = weak), 'U' if not evaluated.
ITMflag utf8 (1 char) Decimation code in fps=24 mode: 'D'/'d'/'x'/'y'/'z'/'+'/'.'. 'U' in fps=30 mode.
ITIpFlag utf8 (1 char) 'P' (progressive) or 'I' (interlaced); 'U' if not run.
ITIvC, ITIvP, ITIvN, ITIvM int Interlace-evidence counters from EvalIV against C, P, N, and the chosen match M.
ITDiffP0, ITDiffP1, ITDiffS0, ITDiffS1 int Motion-map stats: rough motion (P0/P1) and saturated motion (S0/S1) on even/odd field rows.
ITBlended int (0/1) 1 if the blend=true code path produced this output frame.

Convention follows VFM/VDecimate (camelCase, plugin-name prefix, no dots — VS API 4 silently rejects keys with dots).

Building from source

Requires Zig 0.16.0+. Headers (VapourSynth API 4) are vendored in vendor/vapoursynth/.

zig build --release=fast            # native shared library -> zig-out/lib/libzit.so
zig build test                       # 35 unit tests
zig build cross                      # cross-compiled artefacts under zig-out/{linux,macos,windows}/

Cross-compile produces:

  • zig-out/linux/libzit.so (x86_64, ~170 KB)
  • zig-out/macos/libzit.dylib (x86_64)
  • zig-out/windows/zit.dll (x86_64) + .pdb

Installation

Via pip (recommended)

pip install vapoursynth-zit

The wheel installs the platform-matched binary into VapourSynth's auto-discovery path (site-packages/vapoursynth/plugins/), so core.zit.IT(...) is available without any further LoadPlugin calls.

Pre-built wheels are available for Linux (x86_64, aarch64), macOS (x86_64, aarch64), and Windows (x86_64).

Manual install (zip from a release)

If you'd rather not pull in pip, grab the matching *.zip from the Releases page and drop the binary into a VapourSynth plugin directory:

  • Linux: /usr/local/lib/vapoursynth/
  • macOS: ~/Library/ApplicationSupport/VapourSynth/plugins/
  • Windows: vapoursynth64/plugins/ next to vsedit.exe

Performance

500 frames of a 720×480 NTSC telecined VOB, ReleaseFast build:

Pipeline fps
core.zit.IT(clip, fps=30) 2697
core.zit.IT(clip, fps=24) 2236
core.zit.IT(clip, fps=24, diMode=1) 2355
core.zit.IT(clip, fps=24, diMode=2) 2459
core.zit.IT(clip, fps=24, blend=1) 1725
core.vivtc.VFM(clip, order=1) 596
core.vivtc.VFMcore.vivtc.VDecimate 1271

zit fps=30 is ~4.5× faster than vivtc.VFM; zit fps=24 is ~1.8× faster than vivtc.VFM + VDecimate.

Differences from the VapourSynth upstream

The VapourSynth upstream (HomeOfVapourSynthEvolution/VapourSynth-IT) ported only a subset of the Avisynth original. This port reintroduces everything plus fixes a few latent bugs:

  1. Avisynth-original parameters back: ref (TOP/BOTTOM/ALL/NONE), blend, diMode (0/1/2/3). The upstream hardcoded ref="TOP"/blend=false/diMode=3 and removed the parameters.
  2. Frame properties (_FieldBased, _Combed, _Duration*, inheritance of source props, plus IT* diagnostics). Upstream calls newVideoFrame(propSrc=null) so output frames carry no metadata, which breaks downstream core.resize.* colorspace handling.
  3. Threading: registered as fmParallelRequests. Upstream uses fmParallel despite sharing mutable per-instance state across calls — a latent race condition that VS R55+ exposes more often.
  4. Frame request range: widened to cover what the algorithm actually reads ([base-2, base+6] for fps=24, [n-2, n+2] for fps=30 — plus [base-3, base+7] when blend=true). Upstream relied on the API 3 sync getFrame to retrieve neighbours, which is no longer permitted from inside getFrameFilter under API 4.
  5. Edge clamping of frame indices passed to getFrameFilter. The upstream algorithm fetches n-1 even at n=0; under API 3 the core silently clamped, under API 4 it returns null and dereferences crash.

The reference build under reference/vapoursynth-cpp-api4/ applies the same framework-level fixes (otherwise it would segfault under VS R76) and is what the bit-exact comparison test runs against.

Repository layout

src/
├── plugin.zig    # VapourSynth plugin entry (VapourSynthPluginInit2)
├── filter.zig    # Filter instance + getFrame lifecycle + frame-prop setting
├── c.zig         # @cImport of vendored VS API 4 headers
├── state.zig     # CFrameInfo, CTFblockInfo, CallState
├── plane.zig     # syp/dyp accessors, adjPara, clipFrame/X/Y/YH
├── edge.zig      # makeDeMap                (SIMD)
├── eval_iv.zig   # evalIv                    (SIMD)
├── motion.zig    # makeMotionMap, makeMotionMap2Max/Min, makeSimpleBlurMap (SIMD)
├── scene.zig     # checkSceneChange          (SIMD)
├── decide.zig    # compCp / compCn / decide / setFt
├── output.zig    # copyCPNField / deintOneField / simpleBlur / deinterlace
├── blend.zig     # BlendFrame_YV12 port
└── simd.zig      # @Vector helpers (pavgb, absDiff, subSat, expandPairs)

reference/
├── avisynth/                # original IT_YV12 0.1.03 source (read-only)
├── vapoursynth-cpp/         # upstream VS-IT @ 6fc9be8 (read-only)
└── vapoursynth-cpp-api4/    # mechanical API3→API4 port of upstream; build for bit-exact comparison

tests/integration/           # 58 pytest cases — property checks, golden hashes, upstream-compare
scripts/                     # gen_testclip / regen_golden / compare_upstream / compare_vivtc / test_real_video
docs/upstream_reference.md   # design notes

Credits

  • Original IT 0.051 — thejam79 (2002)
  • Avisynth IT_YV12 0.1.03 — minamina (2003)
  • 64-bit / 8k mod — poodle
  • VapourSynth port — msg7086 (2014)
  • Zig port — this repo

License

GPL-2.0-or-later (inherited from upstream). See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

vapoursynth_zit-1.3.1-py3-none-win_amd64.whl (133.5 kB view details)

Uploaded Python 3Windows x86-64

vapoursynth_zit-1.3.1-py3-none-manylinux2014_x86_64.whl (110.7 kB view details)

Uploaded Python 3

vapoursynth_zit-1.3.1-py3-none-macosx_11_0_arm64.whl (38.0 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

vapoursynth_zit-1.3.1-py3-none-macosx_10_9_x86_64.whl (40.2 kB view details)

Uploaded Python 3macOS 10.9+ x86-64

File details

Details for the file vapoursynth_zit-1.3.1-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for vapoursynth_zit-1.3.1-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 0d3825f0472b559905d92f8b4d533d1c7152a54b52cd62901cc3a09e41c89339
MD5 9c02d9186868ef59a91328d6c96f138b
BLAKE2b-256 75f62bdfdb62ea187f7b87c42a6c5bf588e3d3699bae2d323525b687afc9e8b5

See more details on using hashes here.

Provenance

The following attestation bundles were made for vapoursynth_zit-1.3.1-py3-none-win_amd64.whl:

Publisher: release.yml on theChaosCoder/vapoursynth-it

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vapoursynth_zit-1.3.1-py3-none-manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for vapoursynth_zit-1.3.1-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 95b8d34dda507195b493a95bdb4923fad8c0172bc07a2f29171576cfa2c953e0
MD5 863105b66a47777b80cb707be5c08334
BLAKE2b-256 aab231e97f7b508d05b5e17e1bb7f96e2fe95ae03e491b3e43a11b03e9b1fbc2

See more details on using hashes here.

Provenance

The following attestation bundles were made for vapoursynth_zit-1.3.1-py3-none-manylinux2014_x86_64.whl:

Publisher: release.yml on theChaosCoder/vapoursynth-it

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vapoursynth_zit-1.3.1-py3-none-manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for vapoursynth_zit-1.3.1-py3-none-manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 dc77d90a6c2d1c1080000c3709761bd60da764714a6d0821023de0b5063c3ce4
MD5 501c38693fc7881e8f3eb33945600df3
BLAKE2b-256 9ee11a81dc56bad560cefa63531d68b28bc3b8aa68d25d706ca4ee210f3606c8

See more details on using hashes here.

Provenance

The following attestation bundles were made for vapoursynth_zit-1.3.1-py3-none-manylinux2014_aarch64.whl:

Publisher: release.yml on theChaosCoder/vapoursynth-it

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vapoursynth_zit-1.3.1-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for vapoursynth_zit-1.3.1-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 7b2477cde48f1514f1613e21f9b555473ad48aa4177c38aee5998d516d93d3ce
MD5 20addf1e13456c83953aa3fb7cdba707
BLAKE2b-256 202777ef346f653b78f1b0dcd52fb44263c885512996309d3175412c0a64fbe2

See more details on using hashes here.

Provenance

The following attestation bundles were made for vapoursynth_zit-1.3.1-py3-none-macosx_11_0_arm64.whl:

Publisher: release.yml on theChaosCoder/vapoursynth-it

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file vapoursynth_zit-1.3.1-py3-none-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for vapoursynth_zit-1.3.1-py3-none-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 da44dfe25c853ed3c01b55bc88d86abd64befa481a646033f7682d89ef64c0e8
MD5 169d6ae05ad2ea276f4d4b0b55aa85d7
BLAKE2b-256 adf9000031bbe0f351f03419d6109f77f0b9cd99a9507e76600ff6096f4cfaef

See more details on using hashes here.

Provenance

The following attestation bundles were made for vapoursynth_zit-1.3.1-py3-none-macosx_10_9_x86_64.whl:

Publisher: release.yml on theChaosCoder/vapoursynth-it

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page