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, high-bit-depth and 4:2:2/4:4:4 support, full
frame-property support, and @Vector-based SIMD.
Verified bit-exact against the upstream --c reference path across the
integration test grid (8-bit 4:2:0, the only format upstream supports)
and on real-world telecined NTSC VOB samples.
Status
| Item | State |
|---|---|
| Algorithm port (~5200 LoC Zig) | ✅ |
| Bit-exact vs upstream C path | ✅ 8-bit 4:2:0 (198 fixture + 720 real-VOB frames) |
| 10/12/16-bit, 4:2:2, 4:4:4 | ✅ decisions bit-depth-deterministic; no external oracle exists (upstream is YV12-only) — guarded by consistency + golden tests |
All Avisynth params (ref, blend, diMode) |
✅ |
| Frame properties | ✅ standard + diagnostic |
SIMD via @Vector |
✅ ~2× over scalar, ~4× over VIVTC VFM |
| Cross-compile Linux / macOS (x86_64 + aarch64), Windows x86_64 | ✅ |
| CI workflow | ✅ lint + unit (Debug & ReleaseFast) + cross + gating integration suite |
| 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. Integer YUV, 8/10/12/16-bit, 4:2:0 / 4:2:2 / 4:4:4. Width a multiple of 16 and ≤ 8192; height even (a multiple of 4 for 4:2:0), ≤ 8192; constant format and frame rate. |
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. Valid range 0–100000. |
pthreshold |
int |
75 |
Progressive-classification threshold for _Combed. Adjusted internally for resolution. Below: frame is ip='P' (clean match); above: ip='I' (deinterlaced). Valid range 0–100000. |
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+. The VapourSynth API 4 bindings come from the
vapoursynth-zig package
pinned in build.zig.zon.
zig build --release=fast # native shared library -> zig-out/lib/libzit.so
zig build test # unit tests (also run with --release=fast in CI)
zig build cross # release artefacts for all five targets
Cross-compile produces (ReleaseFast, stripped):
zig-out/linux-x86_64/libzit.so,zig-out/linux-aarch64/libzit.sozig-out/macos-x86_64/libzit.dylib,zig-out/macos-aarch64/libzit.dylibzig-out/windows-x86_64/zit.dll
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 tovsedit.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.VFM → core.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:
- Avisynth-original parameters back:
ref(TOP/BOTTOM/ALL/NONE),blend,diMode(0/1/2/3). The upstream hardcodedref="TOP"/blend=false/diMode=3and removed the parameters. - Frame properties (
_FieldBased,_Combed,_Duration*, inheritance of source props, plusIT*diagnostics). Upstream callsnewVideoFrame(propSrc=null)so output frames carry no metadata, which breaks downstreamcore.resize.*colorspace handling. - Threading: registered as
fmParallelRequests. Upstream usesfmParalleldespite sharing mutable per-instance state across calls — a latent race condition that VS R55+ exposes more often. - 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]whenblend=true). Upstream relied on the API 3 syncgetFrameto retrieve neighbours, which is no longer permitted from insidegetFrameFilterunder API 4. - Edge clamping of frame indices passed to
getFrameFilter. The upstream algorithm fetchesn-1even atn=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
├── state.zig # CFrameInfo, CTFblockInfo, CallState
├── plane.zig # syp/dyp accessors, adjPara, clipFrame/X/Y
├── 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)
└── scalar.zig # scalar twins of the SIMD helpers (pavgbScore, toMapByte)
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/ # pytest suite — properties, golden hashes, upstream-compare, determinism, bit depth
scripts/ # gen_testclip / param_grid / regen_golden / compare_upstream / compare_vivtc / ...
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
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vapoursynth_zit-1.4.0-py3-none-win_amd64.whl.
File metadata
- Download URL: vapoursynth_zit-1.4.0-py3-none-win_amd64.whl
- Upload date:
- Size: 945.4 kB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
44c937c5b5dfc4298c3f35f9624e3cd9d5a6fbada87c6171325a67d078f37755
|
|
| MD5 |
392b561ecd1370389f3d9d3ee0c43162
|
|
| BLAKE2b-256 |
e175e03df899718ec6bf4f53b09384ec090e8d5e3d37a84810dbb23249646030
|
Provenance
The following attestation bundles were made for vapoursynth_zit-1.4.0-py3-none-win_amd64.whl:
Publisher:
release.yml on theChaosCoder/vapoursynth-it
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vapoursynth_zit-1.4.0-py3-none-win_amd64.whl -
Subject digest:
44c937c5b5dfc4298c3f35f9624e3cd9d5a6fbada87c6171325a67d078f37755 - Sigstore transparency entry: 1790973620
- Sigstore integration time:
-
Permalink:
theChaosCoder/vapoursynth-it@eddfc74b02767cd10cc3b0180993bb77351a0dd1 -
Branch / Tag:
refs/tags/v1.4.0 - Owner: https://github.com/theChaosCoder
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eddfc74b02767cd10cc3b0180993bb77351a0dd1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file vapoursynth_zit-1.4.0-py3-none-manylinux2014_x86_64.whl.
File metadata
- Download URL: vapoursynth_zit-1.4.0-py3-none-manylinux2014_x86_64.whl
- Upload date:
- Size: 872.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ddd818c247d7c28ce9c348ecfd683b52ae90efa2514a8be38ac43adf53e00bd
|
|
| MD5 |
be39be898521753b8a0446202726e6e4
|
|
| BLAKE2b-256 |
5e866a7aa87c69913049a2633ba6138cf86b3c72173956732c869cb80343d72d
|
Provenance
The following attestation bundles were made for vapoursynth_zit-1.4.0-py3-none-manylinux2014_x86_64.whl:
Publisher:
release.yml on theChaosCoder/vapoursynth-it
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vapoursynth_zit-1.4.0-py3-none-manylinux2014_x86_64.whl -
Subject digest:
5ddd818c247d7c28ce9c348ecfd683b52ae90efa2514a8be38ac43adf53e00bd - Sigstore transparency entry: 1790973567
- Sigstore integration time:
-
Permalink:
theChaosCoder/vapoursynth-it@eddfc74b02767cd10cc3b0180993bb77351a0dd1 -
Branch / Tag:
refs/tags/v1.4.0 - Owner: https://github.com/theChaosCoder
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eddfc74b02767cd10cc3b0180993bb77351a0dd1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file vapoursynth_zit-1.4.0-py3-none-manylinux2014_aarch64.whl.
File metadata
- Download URL: vapoursynth_zit-1.4.0-py3-none-manylinux2014_aarch64.whl
- Upload date:
- Size: 657.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c513c47f9721f670852662b322a78e398b8c81a252026f1777e7bee1fdd9f737
|
|
| MD5 |
6dc77c627e716b9d94309fc9ea0ce711
|
|
| BLAKE2b-256 |
033bf355f6f2a7b1b5ace6f0d11bd12981908df8b6d1b6e3dabcb1a40ee423fa
|
Provenance
The following attestation bundles were made for vapoursynth_zit-1.4.0-py3-none-manylinux2014_aarch64.whl:
Publisher:
release.yml on theChaosCoder/vapoursynth-it
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vapoursynth_zit-1.4.0-py3-none-manylinux2014_aarch64.whl -
Subject digest:
c513c47f9721f670852662b322a78e398b8c81a252026f1777e7bee1fdd9f737 - Sigstore transparency entry: 1790973402
- Sigstore integration time:
-
Permalink:
theChaosCoder/vapoursynth-it@eddfc74b02767cd10cc3b0180993bb77351a0dd1 -
Branch / Tag:
refs/tags/v1.4.0 - Owner: https://github.com/theChaosCoder
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eddfc74b02767cd10cc3b0180993bb77351a0dd1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file vapoursynth_zit-1.4.0-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: vapoursynth_zit-1.4.0-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 616.7 kB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7908cd3c1eadababb570663466b7a848def4b2298e5e81ae6a01134b66fbc033
|
|
| MD5 |
02f4c06ffc70139bc06842ede4e42bbe
|
|
| BLAKE2b-256 |
b5587de1da6f3f28d93c4500c3773337aca6ec84158b13d0b567045aa954f4ae
|
Provenance
The following attestation bundles were made for vapoursynth_zit-1.4.0-py3-none-macosx_11_0_arm64.whl:
Publisher:
release.yml on theChaosCoder/vapoursynth-it
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vapoursynth_zit-1.4.0-py3-none-macosx_11_0_arm64.whl -
Subject digest:
7908cd3c1eadababb570663466b7a848def4b2298e5e81ae6a01134b66fbc033 - Sigstore transparency entry: 1790973519
- Sigstore integration time:
-
Permalink:
theChaosCoder/vapoursynth-it@eddfc74b02767cd10cc3b0180993bb77351a0dd1 -
Branch / Tag:
refs/tags/v1.4.0 - Owner: https://github.com/theChaosCoder
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eddfc74b02767cd10cc3b0180993bb77351a0dd1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file vapoursynth_zit-1.4.0-py3-none-macosx_10_9_x86_64.whl.
File metadata
- Download URL: vapoursynth_zit-1.4.0-py3-none-macosx_10_9_x86_64.whl
- Upload date:
- Size: 842.1 kB
- Tags: Python 3, macOS 10.9+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
20f9473759ac7f26ef759a1a1ff7521c472fcb157c23b93f367afb443e7dec76
|
|
| MD5 |
113febeb81453f825405622f4ddef624
|
|
| BLAKE2b-256 |
a994bb73b93547de05e3f0ae38c404570f0a65b004e04d3c13cf64ebb5e79b4a
|
Provenance
The following attestation bundles were made for vapoursynth_zit-1.4.0-py3-none-macosx_10_9_x86_64.whl:
Publisher:
release.yml on theChaosCoder/vapoursynth-it
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vapoursynth_zit-1.4.0-py3-none-macosx_10_9_x86_64.whl -
Subject digest:
20f9473759ac7f26ef759a1a1ff7521c472fcb157c23b93f367afb443e7dec76 - Sigstore transparency entry: 1790973470
- Sigstore integration time:
-
Permalink:
theChaosCoder/vapoursynth-it@eddfc74b02767cd10cc3b0180993bb77351a0dd1 -
Branch / Tag:
refs/tags/v1.4.0 - Owner: https://github.com/theChaosCoder
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eddfc74b02767cd10cc3b0180993bb77351a0dd1 -
Trigger Event:
push
-
Statement type: