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.2-py3-none-win_amd64.whl (76.7 kB view details)

Uploaded Python 3Windows x86-64

vapoursynth_zit-1.3.2-py3-none-macosx_11_0_arm64.whl (37.0 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

vapoursynth_zit-1.3.2-py3-none-macosx_10_9_x86_64.whl (39.4 kB view details)

Uploaded Python 3macOS 10.9+ x86-64

File details

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

File metadata

File hashes

Hashes for vapoursynth_zit-1.3.2-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 82ce44aab0172530386be265f5fa92640c153d7c1769e82492887cf8034ae9ad
MD5 c2552f9a104e75f0871351cfb0589461
BLAKE2b-256 577d8f14d752d1d8aca5e597c537acc069eb61a84b761299d2bdaaf4f1902369

See more details on using hashes here.

Provenance

The following attestation bundles were made for vapoursynth_zit-1.3.2-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.2-py3-none-manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for vapoursynth_zit-1.3.2-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 c258edeae53f66fb51fd7067013fdf2621eb0cd5b68abf891df15f5b0aa675b3
MD5 605cf6a1ae3f1990ddd684b726d818f2
BLAKE2b-256 de64b5f4c3ed672821027fb7e395fa3aa3c5df8bd20a3c1f242068caf93107a8

See more details on using hashes here.

Provenance

The following attestation bundles were made for vapoursynth_zit-1.3.2-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.2-py3-none-manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for vapoursynth_zit-1.3.2-py3-none-manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 918909d8edc7557aa7c845c39435bce6591cea2a9384652845f522002e1e2179
MD5 abad08248757c694c949482a19927a19
BLAKE2b-256 cb015a3b6b463a4d3a8c2581a9869244021fc9ebc2471772bfe123250b72a724

See more details on using hashes here.

Provenance

The following attestation bundles were made for vapoursynth_zit-1.3.2-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.2-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for vapoursynth_zit-1.3.2-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 0322fd4176f04ecf4d88bcd689ac5ac08cb26b7a39ad7da3a6576915ac55c737
MD5 7a9ba77e9ce3d7c9c611e15b2b8d150e
BLAKE2b-256 7a668c059c66470fa10399d73a1e32c533c71a082d4e24a56d052abd82ad097f

See more details on using hashes here.

Provenance

The following attestation bundles were made for vapoursynth_zit-1.3.2-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.2-py3-none-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for vapoursynth_zit-1.3.2-py3-none-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 4d500d4f3a7bb56e432a226adc2a4ad58e683062fe78d8f0db243d52007f691a
MD5 a9e0f1d2dd0873b9fd20d7a0fbb788f4
BLAKE2b-256 d2484e50a332c7c53517cc32f97b7fce2c97048a1db10f96096e5935a3b8a560

See more details on using hashes here.

Provenance

The following attestation bundles were made for vapoursynth_zit-1.3.2-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