VapourSynth Plugin API¶
The low-level VapourSynth plugin is registered under the analog namespace.
It can be called directly from VapourSynth scripts without the Python wrapper.
Decoding¶
- core.analog.decode_4fsc_video(composite_or_luma_source [, chroma_or_pb_source] [, pr_source] [, decoder] [, color_family] [, color_difference_precision] [, broadcast_scaling_precision] [, model_path] [, onnx_provider] [, model_chroma_bandpass=1] [, model_input_scale=1.0] [, reverse_fields=0] [, chroma_gain=1.0] [, chroma_phase=0.0] [, chroma_nr=0.0] [, luma_nr=0.0] [, phase_compensation=1] [, first_active_sample] [, last_active_sample] [, first_active_line] [, last_active_line] [, dropout_correct=0] [, dropout_overcorrect=0] [, dropout_intra=0] [, dropout_composite_or_luma_extra_sources] [, dropout_chroma_extra_sources] [, annotate_dropouts=0])¶
Decodes 4𝑓𝑠𝑐 (four times subcarrier frequency) sampled analog video signals to a digital video clip. The signal data must be orthogonal video system lines with well-formed blanking and syncing structure at a stable time base, such as those produced by ld-decode and vhs-decode. These files normally have a
.tbcextension indicating they are time-base-corrected and must have a metadata sidecar file in JSON or SQLite format. The newer CVBS format is also read, detected by extension:.cvbs, or.cvbsy/.cvbscfor separated luma/chroma (the pre-1.5.0.compositeand.y/.cspellings are still accepted). RAW CVBS encodings are rejected.Returns a 32-bit float clip:
YUV444PS(default),RGBS,GRAYS, orYUV440PSfor SECAM, percolor_family.Note: this is the low-level interface. Neural-network
decodervalues need an explicitmodel_path; the Python wrapper resolves bundled models bymodel_versionfor you.- Parameters:
composite_or_luma_source (str) – Path to the composite or luma-only capture (
.tbc/.cvbs).chroma_or_pb_source (str) – Path to a separate chroma
.tbcfile, for Y/C-separated sources such as S-Video or VHS color-under.pr_source (str) – Path to the Pr component
.tbcfile (component video, not yet supported).decoder (str) – Chroma decoder to use. See Decoder Options below. When not specified, the decoder is chosen automatically based on the video system in TBC metadata. When
chroma_or_pb_sourceis supplied, this decoder applies to the chroma TBC only; the luma TBC is read with themonodecoder so it isn’t run through Y/C separation a second time. Add"secam"and the neural-network decoders ("nntransform3d","ldzeug2_color_cnn","ldzeug2_luma_sep","ldzeug2_luma_sep_frame", all NTSC only).color_family (str) – Output family:
"yuv"(default),"rgb", or"gray". RGB is rejected for SECAM.color_difference_precision (str) –
"classic"or"modern".broadcast_scaling_precision (str) –
"classic","modern"or"scientific".model_path (str) – Path to NN model weights (
.onnx, or.mlpackageon macOS). Required for a neural-networkdecoder.onnx_provider (str) – Execution provider:
auto,cpu,cuda/gpu,tensorrt/trt,migraphx,directml,coreml. Falls back to CPU when unavailable.model_precision (str) – Compute precision the backend may use:
fp32(default) orfp16. Only a backend that compiles the model into a device engine acts on it — TensorRT, which then builds mixed fp16/fp32 kernels and caches those engines separately. Setfp16only for weights whose input contract keeps every tensor in fp16 range; the Python wrapper knows which bundled models qualify and sets this for you.model_chroma_bandpass (int) – I/Q low-pass toggle for
ldzeug2_luma_sep/ldzeug2_luma_sep_frame(default1).model_input_scale (float) – Input magnitude divisor for
nntransform3d(default1.0).reverse_fields (int) – Set to 1 to swap field order.
chroma_gain (float) – Chroma gain multiplier for saturation adjustment. Default
1.0.chroma_phase (float) – Chroma phase adjustment in degrees. Default
0.0.chroma_nr (float) – Chroma noise-reduction level. Only applies to NTSC decoders. Default
0.0.luma_nr (float) – Luma noise-reduction level. Default
0.0.phase_compensation (int) – Burst-locked NTSC chroma demodulation, recovering the subcarrier phase from each line’s colorburst instead of assuming it’s locked to the 4𝑓𝑠𝑐 sample grid. Set to 0 to force fixed-phase demodulation. Default
1. The PAL decoders are burst-locked by design and ignore this.first_active_sample (int)
last_active_sample (int) – Inclusive horizontal crop, in the sample numbering of the 4𝑓𝑠𝑐 interface standards: sample 0 is the first sample of the digital active line, and negative numbers reach back into the line blanking ahead of it (see Active Window below). Defaults to the whole digital active line.
first_active_line (int)
last_active_line (int) – Inclusive vertical crop, in the standards’ field-sequential signal line numbers.
first_active_lineis the window’s topmost line. Defaults to the video system’s standard active picture.dropout_correct (int) – Set to 1 to enable dropout correction using metadata-identified dropouts. See Dropout Correction below. Default
0.dropout_overcorrect (int) – Set to 1 to extend dropout boundaries by +/-24 samples. For heavily damaged sources. Default
0.dropout_intra (int) – Set to 1 to force intra-field-only correction, avoiding inter-field borrowing artifacts on high-motion content. Default
0.dropout_composite_or_luma_extra_sources (str[]) – Additional composite or luma
.tbcfiles for multi-source dropout correction.annotate_dropouts (int) – Set to 1 to record each frame’s dropout regions in the
AnalogDropoutSpansframe property, for use with create_dropouts_mask. Independent ofdropout_correct. See Dropout Annotation below. Default0.dropout_chroma_extra_sources (str[]) – Additional chroma
.tbcfiles for multi-source dropout correction (for color-under formats).
Examples:
import vapoursynth as vs
from vapoursynth import core
# Basic composite decode:
clip = core.analog.decode_4fsc_video("/path/to/capture.tbc")
# Y/C-separated decode:
clip = core.analog.decode_4fsc_video("luma.tbc", "chroma.tbc")
Dropouts¶
- core.analog.create_dropouts_mask(clip[, origins])¶
Rasterises a clip’s annotated dropout regions into a mask clip.
clipmust have been decoded withannotate_dropouts=1; a clip without theAnalogDropoutSpansproperty is an error. The result is a single-plane clip matchingclip’s dimensions and precision —0for clean samples, full scale (1.0, or2**bits - 1) for dropped ones — which drops straight intocore.std.MaskedMergeand the otherstdmask functions.The mask is full-size even for subsampled clips, which is what
MaskedMergerequires: it resamples the mask for the chroma planes itself, honouring the clip’s_ChromaLocation. For SECAM’s 4:4:0 output that resampling is bilinear, so a one-line dropout softens across two chroma rows.Because the regions travel as frame properties, they survive
std.Trim,std.Interleaveand splicing — build the mask before or after editing the decoded clip and it stays aligned either way.- Parameters:
clip (vnode) – An annotated clip, 8-16 bit integer or 32-bit float.
origins (int[]) – Restrict the mask to regions of particular origin:
0for regions flagged in the source metadata,8for decoder concealment. Defaults to every origin. Maskingorigins=[8]alone shows exactly which chroma samples SECAM click concealment replaced.
Examples:
import vapoursynth as vs
core = vs.core
# Locate the dropouts without concealing them, then repair them elsewhere.
clip = core.analog.decode_4fsc_video(
"capture.tbc", dropout_correct=0, annotate_dropouts=1)
mask = core.analog.create_dropouts_mask(clip)
repaired = core.std.MaskedMerge(clip, inpainted, mask)
# Grow the mask a little to cover the ringing at a dropout's edges.
grown = core.std.Maximum(mask).std.Inflate()
Colorimetry¶
- core.analog.modernize_chromaticity(clip [, primaries_in_s] [, transfer_in_s] [, matrix_in_s] [, primaries_s] [, transfer_s] [, matrix_s] [, output_preset] [, resample_filter_uv] [, filter_param_a_uv] [, filter_param_b_uv] [, chromatic_adaptation=0] [, nominal_luminance=100.0] [, contrast_in=1.0] [, brightness_in=0.0] [, contrast=1.0] [, brightness=0.0])¶
Converts analog-era colorimetry and photometry to a modern target, intended downstream of
decode_4fsc_videoor of a source plugin (such as BestSource) reading previously captured video. Color only — no geometry conversions and no dithering. Input is YUV or RGB (integer up to 16 bits, or 32-bit float). The color math always runs at 4:4:4: subsampled Y’CbCr input is upsampled through the resize plugin (seeresample_filter_uv), converted, and returned to its original subsampling on Y’CbCr output — the same round trip theresizefunctions perform for colorimetry changes.Parameters share names with VapourSynth’s
resizefunctions where the meaning is similar, with one deliberate difference: the*_inparameters here override frame properties. When an*_inparameter is omitted, the value is inferred from the matching frame property (_Primaries,_Transfer,_Matrix), and frame requests fail if the property is absent,unspecified, or has no supported equivalent.Because there is no dithering, prefer high-bit-depth input and output — ideally the 32-bit float that
decode_4fsc_videoproduces — and leave quantization to a proper dithering step at the end of the pipeline. There are likewise norange/range_inparameters: input range comes from the_ColorRangeframe property (defaulting to studio range for Y’CbCr and full range for RGB), and output range follows the output matrix’s convention the same way.To switch color family (YUV to RGB or the reverse), specify
matrix_sor anoutput_preset; RGB input without either yields RGB output.- Parameters:
clip (vnode) – Input clip: YUV 4:4:4 or RGB, constant format.
primaries_in_s (str) –
Input chromaticity (CIE 1931 primaries + white point). Options sharing a line are equivalent:
ntsc-1953,bt470m,470m,fcc— the 1953 NTSC chromaticity (Illuminant C white)bt470-japan,470m93,ntscj— NTSC-1953 primaries with the Japanese “D93” whitebt1700-japan,170j— SMPTE C primaries with the Japanese whitepal,ebu,bbc,470bg— the 625-line EBU chromaticitysmpte-c,st170,170m— SMPTE RP 145 / ST 170code-point-22— the mystery IEC/ITU primaries code point 22 (neither JEDEC P22 nor EBU Tech 3213)ecia-xxa,p22…ecia-xxg— the seven registered phosphor sets of ECIA/TEPAC/JEDEC group XX (“P22”), in registration order;xxais RCA’s 1954 originalrca-sulfide-8500k,rca-sulfide-9300k-27mpcd,rca-sulfide-c— RCA’s all-sulfide commercial mix under three assumed white points (never published)rca-p22-4-67,rca-p22-5-61,rca-p22-9-65— phosphor sets from RCA’s own P22 taxonomysony-p22— Sony’s CRT phosphor set (Japanese white; never a JEDEC/TEPAC/ECIA registration)studio-japan— ARIB TR-B9 pre-1996 Japanese studio practicenederland-proposal— the CCIR Doc. XI/194 compromise primaries
All Japanese entries use ITU-R BT.2035’s reference D93 white point. Inferred from
_Primariesvalues 4, 5, 6/7 and 22 when omitted.transfer_in_s (str) –
Input transfer characteristics:
linearntsc-1953,bt470m,470m,fcc,gamma22— assumed 2.2-gamma displaybt470bg,470bg,tube,gamma28— assumed 2.8-gamma displayst170-scene,st170-oetf,bt601,601— scene-referred inverse of the ST 170 / BT.601 camera OETFst170-display,st170-eotf— ST 170’s reference reproducer EOTF (§5.2). This is the exact inverse of the camera OETF, so it linearizes identically tost170-scene; the two names exist to document intent.bt1886-annex-1,1886,lcd,gamma24— the BT.1886 reference EOTF; honourscontrast_in/brightness_inbt1886-appendix-1,1886a,crt— BT.1886’s informative CRT-matching EOTF; honourscontrast_in/brightness_insrgb,iec-61966-2-1
Inferred from
_Transferwhen omitted: 1 and 6 →bt1886-annex-1, 4 → 2.2 gamma, 5 → 2.8 gamma, 8 →linear, 13 →srgb. BT.709’s reference EOTF is BT.1886, and_Transfer=6(SMPTE ST 170) deliberately gets the same reading rather thanst170-display: ST 170’s idealized inverse-OETF reproducer was rarely what displays actually did — the era’s displays were CRTs, the response BT.1886’s Annex 1 EOTF was later written to approximate so that flat-panel displays with digital processing could emulate it. This matches howresize/zimg linearize code 6 (equivalent to code 1, display-referred BT.1886). Settransfer_in_s="st170-display"explicitly for a literal §5.2 display, or"crt"(BT.1886 Appendix 1) for a closer CRT match.matrix_in_s (str) –
Input Y’CbCr matrix (Y’CbCr input only):
analog-classic,ntsc-1953,fcc— the 0.30/0.11 luma weights of NTSC-1953analog-modern,bt470,bt1700,st170,170m,bt601,601— the 0.299/0.114 weights
Inferred from
_Matrixwhen omitted: 4 → classic, 5/6 → modern.primaries_s (str) – Output chromaticity:
bt709/709,bt2020/2020,p3dci/st431-2,p3d65/st432-1, orxyz/st428(CIE XYZ tristimulus output; requiresmatrix_s="rgb").transfer_s (str) – Output transfer characteristics:
linear;bt1886-annex-1/1886/lcd/gamma24(honourscontrast/brightness);srgb/iec-61966-2-1(IEC 61966-2-1 Equations 7 and 8);pq/st2084/2084; orhlg/std-b67(display-referred, BT.2100 1.2-power OOTF).matrix_s (str) –
Output matrix:
rgb(RGB color family output),bt709/709,bt2020ncl/bt2100/2020ncl/2020/2100, or2020cl(BT.2020 constant luminance, H.273 matrix 10), orchromacl/chromaticity-derived-cl(H.273 matrix 13: constant luminance whose luma weights are derived fromprimaries_sper H.273 Equations E-22 to E-27, so it pairs with any output chromaticity —2020clitself means BT.2020’s weights by definition and so requiresbt2020primaries).Constant luminance is not a transfer characteristic: BT.2020 Table 4 gives CL and NCL the same OETF and differs only in where it is applied. NCL encodes each channel and then matrixes; CL matrixes the linear tristimulus and encodes the result, so luminance survives chroma subsampling intact. The color-difference normalizers (PB/NB/PR/NR) follow the transfer curve rather than fixing it — H.273 Equations E-62 to E-65 define them as the transfer characteristic function applied to expressions in KB and KR — so
transfer_sstays free and is applied to the CL constants and the picture alike. BT.2020 Table 4’s published numbers are that derivation under BT.2020’s own OETF, which is what a CL matrix falls back to whentransfer_sis unset.Pairing CL with the display-referred
bt1886-annex-1is supported on the strength of H.273’s note that the BT.709/BT.2020 OETF code points, though defined as an OETF, take BT.1886 as their corresponding reference EOTF. Be aware that decoders which read those code points strictly as the OETF (zimg does, treating CL as always scene-referred) will derive different normalizers than a display-referred flow.output_preset (str) –
Bundles the three output parameters:
hdtv,bt709— BT.709 primaries, BT.1886 Annex 1 transfer, BT.709 matrixuhdtv,bt2100-pq— BT.2020 primaries, PQ transfer, BT.2020 NCL matrixbt2100-hlg— BT.2020 primaries, HLG transfer, BT.2020 NCL matrixbt2020-sdr— BT.2020 primaries, BT.1886 Annex 1 transfer, BT.2020 NCL matrixsrgb,iec-61966-2-1— BT.709 primaries, sRGB transfer, RGB output (the standard defines no matrix; addmatrix_sfor Y’CbCr)
Explicit
primaries_s/transfer_s/matrix_swin over the preset, somatrix_s="rgb"combined with a preset keeps its colorimetry but emits RGB.resample_filter_uv (str) – Kernel for the internal chroma round trip on subsampled input:
point,bilinear,bicubic(default, as inresample_secam),spline16,spline36,spline64, orlanczos. The upsample sites against the frame’s_ChromaLocation; Y’CbCr output is brought back to the input subsampling with the same kernel, and_ChromaLocationis preserved. Unused for 4:4:4 and RGB input. 4:4:0 input is rejected: SECAM fromdecode_4fsc_videocarries a line-sequential Db/Dr lattice that plain resampling would blend — realign it withresample_secam, orfill_secam_by_delayfor the classic delay-line treatment.filter_param_a_uv (float)
filter_param_b_uv (float) – Kernel tuning for
resample_filter_uv, as in theresizefunctions: the bicubic b/c coefficients, or the lanczos tap count (filter_param_a_uvonly).chromatic_adaptation (int) –
Set to 1 to apply a Bradford chromatic adaptation from the input white point to the output one. Default 0 (unlike some converters): whites keep their original tint, so e.g. a D93-mastered picture stays cool on a D65 display, as it looked in its era.
Beware that with adaptation off, extremes can clip unintentionally in SDR output: a full-strength white under a non-D65 input white point lands outside the output’s unit RGB range — NTSC-1953’s Illuminant C white overshoots BT.709’s red and blue channels by roughly 5-10% linear. Integer output clamps those channels (tinting the brightest highlights); float output carries the overshoot to whatever clips next. PQ and HLG output have headroom above SDR reference white and are unaffected. Enable
chromatic_adaptation— or attenuate before conversion — when unclipped SDR highlights matter.nominal_luminance (float) – Physical luminance in cd/m² that linear 1.0 (SDR reference white) maps to for PQ and HLG output. Default
100.0.contrast_in (float) – BT.1886 user gain (legacy “contrast”) for the input EOTF: the screen white luminance LW as a 0.0-1.0 fraction of reference white. Default
1.0.brightness_in (float) – BT.1886 black lift (legacy “brightness”) for the input EOTF: Annex 1’s LB, or Appendix 1’s
b, as a 0.0-1.0 fraction. Default0.0.contrast (float)
brightness (float) – Output-side counterparts, valid only with the
bt1886-annex-1output transfer.
Output frames are tagged with the matching
_Matrix,_Transfer,_Primaries,_ColorRangeand_Rangeproperties._ChromaLocationis preserved when the output keeps the input’s subsampling and removed otherwise (siting is moot at 4:4:4). The BT.1886 output transfer is signalled as BT.709 (_Transfer=1), or as the BT.2020 10/12-bit OETF tag when paired with BT.2020 primaries, since H.273 has no display-side code point for it.
Examples:
import vapoursynth as vs
from vapoursynth import core
clip = core.analog.decode_4fsc_video("/path/to/capture.tbc")
# Straight to Rec. 709 SDTV-successor colorimetry:
hd = core.analog.modernize_chromaticity(clip, output_preset="hdtv")
# A LaserDisc mastered on a 1969-registered RCA tube, to BT.2100 PQ:
pq = core.analog.modernize_chromaticity(
clip, primaries_in_s="ecia-xxd", transfer_in_s="crt",
output_preset="bt2100-pq")
# Quantize to the 10-bit 4:2:0 an HDR10 deliverable calls for. This filter
# only converts colorimetry, never depth, so the dither is yours to place.
hdr10 = core.resize.Spline36(pq, format=vs.YUV420P10,
dither_type="error_diffusion")
The remaining HDR10 ingredients — ST 2086 mastering-display metadata and
MaxCLL/MaxFALL — are static metadata carried alongside the picture rather than
in it, so they are set at encode time (x265’s --master-display and
--max-cll), not by anything in the filter graph.
- core.analog.amplify_chroma(clip, gain [, resample_filter_uv] [, filter_param_a_uv] [, filter_param_b_uv])¶
Amplifies or attenuates the color-difference signals: the post-decode counterpart of
decode_4fsc_video’schroma_gain, which scales the demodulated color differences on their way out of the decoder. Because it works on a clip rather than a decode, a conventional capture loaded through a source plugin (BestSource and the like) is as valid an input as a fresh decode.Think of it as a saturation control, but saturation as the analog video domain defines it — a gain on E’Cb/E’Cr — rather than the saturation axis of an HSV or HLS model.
Frames that already carry E’Y E’Cb E’Cr — 32-bit float Y’CbCr tagged
_Matrix=4(NTSC-1953),_Matrix=5(BT.470 BG) or_Matrix=6(SMPTE ST 170), asdecode_4fsc_videoemits — are scaled in place, leaving luma, chroma siting and every frame property alone. All three are the same luma/chroma split: the coefficients NTSC-1953 derived from its primaries at Illuminant C, code 4 at the precision they were first published to and codes 5 and 6 at the higher one later systems restated them to. Those later systems kept the coefficients even though their primaries and white had moved, which fits their own chromaticity no better — but it is how the signals were built, so it is what a gain on them means.Frames carrying those same color differences in another format — integer Y’CbCr of any depth — go through a 32-bit float intermediate of the same subsampling and back, both conversions running through the resize plugin. That trip names no matrix, so
resizeconverts the samples and nothing else: no chroma is resampled, whichever analog matrix the frame carries, and even the 4:4:0 lattice comes back untouched.Frames on other axes take the same trip to a
_Matrix=6intermediate instead, which is a real color-difference change and so does resample the chroma of a subsampled clip (seeresample_filter_uv). RGB always takes that path, having no color differences of its own. The choice is made per frame from the frame’s own_Matrix, which frames fail on if absent orunspecified.On a frame whose matrix isn’t an analog one, the round trip holds the analog luma constant, which is what the decoder’s own gain does. Its own Y’ therefore shifts a little as the color is scaled — a BT.709 frame taken to
gain=0.0lands on the analog luma of its colors, not on its BT.709 one — while the chroma comes out scaled by exactlygaineither way.Only analog-era colorimetry is accepted. A frame’s
_Primariesmust be 4 (NTSC-1953), 5 (EBU), 6 (SMPTE ST 170) or 7 (SMPTE ST 240), or absent orunspecified(2) — untagged captures are taken at their word. Anything newer is rejected: no signal was ever built by splitting luma from chroma with the analog coefficients on those primaries, and there is no telling what such a picture was originally broadcast in. Amplify beforemodernize_chromaticity(or another conversion) rather than after.- Parameters:
clip (vnode) – Input clip: YUV or RGB, constant format. GRAY carries no chroma and is rejected.
gain (float) – Multiplier applied to both color differences. Above
1.0amplifies and below1.0attenuates;0.0leaves a monochrome picture, and1.0returns the clip untouched, without the conversion round trip a unity gain could only lose precision to. Must be0.0or greater.resample_filter_uv (str) –
Kernel for the chroma resampling a matrix change entails:
point,bilinear,bicubic(default, as inresample_secam),spline16,spline36,spline64, orlanczos. Only subsampled frames on non-analog axes use it — the matrix change happens at 4:4:4 and is resampled back to the source subsampling, sited against the frame’s_ChromaLocation. Frames already carrying analog color differences never reach it, in any format or subsampling.4:4:0 frames needing that matrix change are refused rather than resampled: SECAM from
decode_4fsc_videocarries a line-sequential Db/Dr lattice that plain resampling would blend, so realign it withresample_secam(orfill_secam_by_delay) first. Analog-matrix 4:4:0 is amplified normally, integer included, and needs no realignment — the gain is per sample.filter_param_a_uv (float)
filter_param_b_uv (float) – Kernel tuning for
resample_filter_uv, as in theresizefunctions: the bicubic b/c coefficients, or the lanczos tap count (filter_param_a_uvonly).
Output keeps the input’s format, dimensions and frame properties. Integer output is rounded without dithering and clamps at the format’s bounds, so amplifying an already saturated picture can flatten its most colorful areas; float clips nothing.
Examples:
import vapoursynth as vs
from vapoursynth import core
clip = core.analog.decode_4fsc_video("/path/to/capture.tbc")
# A washed-out capture, given back some color:
livelier = core.analog.amplify_chroma(clip, 1.2)
# Equally at home on a conventional capture, whatever its colorimetry:
vhs = core.bs.VideoSource("/path/to/vhs_capture.mkv")
calmer = core.analog.amplify_chroma(vhs, 0.85)
Logging¶
- core.analog.set_log_level(level)¶
Sets the threshold for the decoder’s diagnostic messages.
Diagnostics that describe how a decode went but don’t stop it — an accelerated neural-network backend falling back to CPU, a SECAM field ident that disagrees with the sidecar, a capture that isn’t at a 4𝑓𝑠𝑐 sample rate — are emitted as VapourSynth log messages, so
core.add_log_handlerreceives them alongside every other filter’s. Failures are reported the other way, as an error on the call that failed.levelis one ofdebug,info(the default),warning,criticaloroff. The setting is process-wide rather than per clip.Note
Under
vspipe, or any other host that installs its own log handler, these messages go straight to that handler. Under a plain Python interpreter with no handler registered, VapourSynth’s Python module forwards them to the standard library’slogginginstead, under the logger named"vapoursynth". Without alogging.basicConfig()call (or some other handler attached), onlywarningand above will actually reach stderr —debugandinfoare silently discarded. See the logging HOWTO for how to configure it.
Active Window¶
Output geometry is pinned to the interface standard for the detected video system, rather than inherited from whatever crop the source declares, so a given system always decodes to the same raster:
Video System |
Samples |
Lines |
Output |
Field Order |
|---|---|---|---|---|
NTSC / PAL-M |
0..767 (768) |
283..263 (486) |
768x486 |
Bottom first |
PAL / SECAM |
0..947 (948) |
23..623 (576) |
948x576 |
Top first |
Both axes are given in the numbering of the standards themselves, and both bounds are inclusive. Samples are numbered as SMPTE ST 244 (525-line) and EBU Tech 3280-E (625-line) do, from the start of the digital active line. Lines are the field-sequential signal line numbers of SMPTE ST 170, ITU-R BT.470 / BT.1700 and EBU Tech 3280.
The horizontal window is the digital active line, which is deliberately wider than the analog picture and so carries the blanking transition on each side (PAL 948 samples versus 922 of picture; NTSC 768 versus 754). The vertical window is the standard active picture: SMPTE ST 170’s 486 lines for 525-line systems and ITU-R BT.1700’s 576 lines for 625-line systems.
These numbers are not positions within a stored row or frame — the decoder
translates them into the source’s own coordinates. That matters horizontally,
because captures disagree about where a row is cut: an ld-decode or vhs-decode
.tbc starts each row at 0H (the half-amplitude point of the falling edge of
line sync), while a subcarrier-locked capture such as ld-chroma-encoder
--sc-locked output starts it at the first digital blanking sample instead, a
few samples ahead of 0H. The same first_active_sample lands on the same
picture either way.
Line numbering runs field by field rather than down the raster, so the two
fields’ numbers interleave in the woven frame. first_active_line names the
window’s topmost line, which need not be the lower number: 525-line output
is bottom-field-first, because ST 170’s active picture is field 1 lines 21-263
and field 2 lines 283-525, and since field 2 begins at line 264 its first
active line sits half a line above field 1’s. Line 283 is therefore the
frame’s top row and line 263 its bottom. 625-line output begins on a field 1
line and is top-field-first.
To crop tighter, set the bounds explicitly:
# ld-chroma-decoder's picture crop (the pre-libchromadec default)
core.analog.decode_4fsc_video(src, first_active_sample=9,
last_active_sample=768) # NTSC, 760 wide
core.analog.decode_4fsc_video(src, first_active_sample=8,
last_active_sample=929) # PAL, 922 wide
# Analog active line (~52.66 us NTSC, ~52.0 us PAL); nominal, since line
# blanking carries a few samples of tolerance
core.analog.decode_4fsc_video(src, first_active_sample=9,
last_active_sample=762) # NTSC, 754 wide
core.analog.decode_4fsc_video(src, first_active_sample=7,
last_active_sample=928) # PAL, 922 wide
# Field 1's active picture only, on a 525-line source
core.analog.decode_4fsc_video(src, first_active_line=21,
last_active_line=263)
A negative first_active_sample widens the window leftwards out of the
digital active line and into the line blanking before it, which is how to see
sync and color burst: on a 525-line source first_active_sample=-125 starts
the window at 0H. Widening rightwards past the end of the digital active line
works on a 0H-cut source but not on a subcarrier-locked one, whose stored rows
end there; the call fails rather than wrap round to the front of the row.
Each bound is independent, so setting only one keeps the standard value for the
other three. The resolved window is reported on every frame as
AnalogFirstActiveSample / AnalogLastActiveSample /
AnalogFirstActiveLine / AnalogLastActiveLine, in these same standards
coordinates. The frame is exactly that window and is never padded, so pixel
(0, 0) is AnalogFirstActiveSample of AnalogFirstActiveLine. Add borders
downstream with std.AddBorders() if a codec needs particular dimensions.
Decoder Options¶
The decoder parameter accepts the following values:
Decoder |
Video System |
Description |
|---|---|---|
|
NTSC |
1D comb filter |
|
NTSC |
2D comb filter (default for NTSC) |
|
NTSC |
3D adaptive comb filter |
|
NTSC |
3D comb filter without adaptation |
|
PAL |
2D PALcolour filter (default for PAL) |
|
PAL |
2D Transform PAL frequency-domain filter |
|
PAL |
3D Transform PAL frequency-domain filter |
|
SECAM |
Line-sequential FM chroma; outputs |
|
NTSC |
Neural 3D transform Y/C separation (needs a model) |
|
NTSC |
Neural chroma separation/color CNN (needs a model) |
|
NTSC |
Neural luma separation, field mode (needs a model) |
|
NTSC |
Neural luma separation, frame mode (needs a model) |
|
Any |
Luma-only decode (no chroma) |
If not specified, the decoder is auto-selected based on the video system. The
neural-network decoders are NTSC-only and require a model — see
model_path/onnx_provider above, or the Python wrapper’s
model_version.
Dropout Correction¶
Setting dropout_correct=1 replaces signal dropout regions identified in the
TBC metadata with data from nearby clean lines, using the algorithm libchromadec
carries over from ld-decode-tools’ ld-dropout-correct. Luma and chroma are sourced independently using FIR
frequency separation to find the closest match for each.
For multi-source correction, pass additional TBC captures of the same content
via dropout_composite_or_luma_extra_sources (and
dropout_chroma_extra_sources for Y/C-separated formats). Sources are aligned
using VBI frame numbers when available (laserdisc CAV/CLV), falling back to
sequential frame alignment for sources without VBI data (e.g. VHS-decode
output).
When dropout correction is enabled, the following frame properties are set on each output frame:
Property |
Type |
Description |
|---|---|---|
|
int |
Dropout regions successfully replaced |
|
int |
Dropout regions where no replacement was found |
|
int |
Sum of line distances for all replacements |
Dropout Annotation¶
Concealment hides dropouts; annotate_dropouts=1 instead reports where they
are, in the AnalogDropoutSpans frame property, so another filter can act on
them. It is independent of dropout_correct: annotate without correcting to
hand the damaged regions to an in-painter, or alongside it to see what was
concealed. Note that the regions describe detected damage — with
dropout_correct=1 they mark what has already been repaired, not what still
needs repairing.
The property is a flat integer array holding four values per region —
y, x_start, x_end, origin — in the decoded clip’s own pixel
coordinates, with x half-open and regions sorted by y then x_start.
One array rather than four parallel ones keeps its length off 1, which
VapourSynth’s Python layer would hand back as a bare int instead of a list. The
property is present but empty on a frame with no dropouts, which is what
distinguishes an annotated clip from an unannotated one.
origin is 0 for a region flagged upstream by ld-decode / vhs-decode and
stored in the sidecar, or 8 for one the decoder detected and concealed
itself — currently SECAM FM click concealment, which exists only because the
frame was decoded, and so has no counterpart in the sidecar.
dropout_overcorrect=1 widens the reported regions to the footprint
overcorrect-mode correction would touch, exactly as it widens what correction
overwrites. The widening happens against the full signal line before the active
crop is applied, so a dropout lying entirely in the blanking either side of the
picture can extend into the frame under dropout_overcorrect=1 while being
absent altogether without it.
Metadata Sidecars¶
Each source signal file must have a corresponding metadata sidecar file with
the same base name. For .tbc sources that is a .db (SQLite) or
.json file, .db taking precedence when both are present; CVBS sources
carry a .meta sidecar. Both TBC forms are read directly and neither is
converted or written back — no .db is created alongside a .json source.