Ultimate Video Enhancer

Custom Filter Codes — Documentation

A beginner-friendly guide for writing, importing and debugging SVG, GLSL, Canvas 2D and Audio/Speech filters for GVF.

← Back to Library
📋 Contents
Start Here Filter Types Label, Category, Tags SVG Basics SVG Examples GLSL Basics Uniforms & Sliders Groups & Checkboxes GLSL Examples Canvas 2D Basics Canvas 2D Examples Audio / Speech Basics Audio Examples Recommended Order Common Errors Performance Tips Quick Templates

Start Here

A Custom Filter Code is a small snippet that GVF imports and applies to videos in real time. You do not need to build a browser extension or edit the userscript manually.

  1. Open the GVF Filter Library.
  2. Click Add Custom Filter.
  3. Select the correct type: SVG, WebGL/GLSL, Canvas 2D or Audio/Speech.
  4. Paste your code.
  5. Add a clear label, short description, category and tags.
  6. Submit or install the filter.
Quick guide: SVG filters are best for color and simple effects. GLSL is best for real pixel processing like sharpen, denoise and upscaling. Canvas 2D is best for experimental JavaScript effects. Audio/Speech is for microphone and audio processing.

Filter Types

TypeBest forStrengthLimitations
SVG Filter Color, contrast, blur, simple sharpen, glow, creative effects Works on many pages, often DRM-safe No real custom pixel loops or AI upscaling
WebGL / GLSL Sharpen, denoise, FSR-like effects, Anime4K-like effects, chroma reconstruction Most powerful video processing mode Usually blocked on DRM video; not all mpv shaders work 1:1
Canvas 2D JavaScript-driven overlays, visual experiments, simple frame effects Easy to write if you know JS Slower than GLSL for heavy pixel processing
Audio / Speech Speech presets, gain, EQ, noise suppression logic Good for voice tools and presets Not a video image filter

Label, Description, Tags and Category

Label

The label should be short and clear — it appears in the filter list and in the manager.

Anime Restore Mild
Smart Denoise
Krig Chroma Restore
CAS Sharpen

Description

Explain what the filter does in one sentence.

Edge-aware denoise and mild sharpening for compressed anime and streaming video.

Tags

Use only a few useful tags. Too many tags make search messy.

denoise, sharpen, anime

Category

Use one category that describes the main purpose.

Denoise
Sharpening
Color Grading
Chroma Reconstruction
Creative / FX
Repair / Utility

SVG Filter Basics

SVG filter code is not a full SVG document. Paste only the inner filter primitives such as <feGaussianBlur> or <feColorMatrix>. GVF wraps them in a <filter> automatically.

Correct

<feColorMatrix type="saturate" values="1.25"/>

Wrong

<svg>
  <filter>
    <feColorMatrix type="saturate" values="1.25"/>
  </filter>
</svg>
SVG filters are useful on DRM pages because they affect the final composited video element without reading protected pixel data — canvas readback is never required.

SVG Examples

1. Mild Sharpen

SVG · Sharpening
<feGaussianBlur stdDeviation="0.45" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="arithmetic" k1="0" k2="1.35" k3="-0.35" k4="0"/>

2. Strong Contrast S-Curve

SVG · Contrast
<feComponentTransfer>
  <feFuncR type="table" tableValues="0 0.04 0.18 0.42 0.68 0.88 1"/>
  <feFuncG type="table" tableValues="0 0.04 0.18 0.42 0.68 0.88 1"/>
  <feFuncB type="table" tableValues="0 0.04 0.18 0.42 0.68 0.88 1"/>
</feComponentTransfer>

3. Warm White Balance

SVG · Color
<feColorMatrix type="matrix" values="
1.06 0    0    0  0.02
0    1.00 0    0  0
0    0    0.92 0 -0.02
0    0    0    1  0"/>

4. Debanding Dither

SVG · Repair
<feTurbulence type="fractalNoise" baseFrequency="0.75" numOctaves="1" result="noise"/>
<feColorMatrix in="noise" type="saturate" values="0" result="mono"/>
<feComposite in="SourceGraphic" in2="mono" operator="arithmetic" k1="0" k2="1" k3="0.012" k4="-0.006"/>

5. Bloom / Glow

SVG · Creative
<feGaussianBlur stdDeviation="3" result="blur"/>
<feBlend in="SourceGraphic" in2="blur" mode="screen"/>

GLSL Basics

GLSL filters process pixels on the GPU using WebGL2. In GVF you write a fragment shader using the built-in variables below. The pipeline is GLSL ES 3.0 (#version 300 es is added automatically).

NameTypeMeaning
u_videosampler2DThe current video frame as a texture
u_video_rawsampler2DUnprocessed source frame (before the filter chain)
v_uvvec2Current pixel UV coordinate, 0.0 → 1.0
u_resvec2Canvas/video resolution in pixels
u_timefloatElapsed time in seconds — for animated effects
u_strengthfloatGlobal strength slider (0–1)
u_mousevec2Mouse position in UV space (0–1)
u_avg_lumfloatAverage luma of the current frame
fragColorout vec4Final output color to write

Minimal GLSL Filter

void main() {
    vec4 color = texture(u_video, v_uv);
    fragColor = color;
}
Do not paste HLSL directly. HLSL syntax like sampler s0 : register(s0), tex2D(), float2, lerp() and saturate() must be converted to GLSL equivalents (texture(), vec2, mix(), clamp(x,0.,1.)).

GLSL Uniforms and Sliders

Add sliders to your filter by placing // @uniform comment lines above your shader code. GVF parses them and builds a live slider UI automatically.

Syntax

// @uniform float u_strength 0.8 0.0 2.0 "Strength"
PartExampleDescription
typefloatCurrently the only supported type for slider values
nameu_strengthVariable name used inside the GLSL shader — must not clash with built-ins
default0.8Initial slider value on first load
min0.0Lowest slider value
max2.0Highest slider value
label"Strength"Visible label in the UI — use slash for grouping

Example with slider

// @uniform float u_brightness 1.05 0.5 1.5 "Brightness"

void main() {
    vec4 color = texture(u_video, v_uv);
    color.rgb *= u_brightness;
    fragColor = color;
}
Never redeclare a built-in uniform name. The following are already declared by GVF and must not appear in @uniform lines: u_strength, u_layers, u_zoom, u_time, u_mouse, u_res, u_video, u_video_raw, u_avg_lum, u_avg_r, u_avg_g, u_avg_b, u_contrast.

Slider Groups and Checkboxes

Groups

Use a slash in the label string to group sliders visually in the UI:

// @uniform float u_strength 1.0 0.0 2.0 "Sharpen / Strength"
// @uniform float u_radius   1.0 0.5 2.0 "Sharpen / Radius"
// @uniform float u_mix      0.8 0.0 1.0 "Output / Effect Mix"

This renders as:

SHARPEN
  Strength  ──────●──────
  Radius    ──────●──────

OUTPUT
  Effect Mix ─────●──────

Checkboxes

Sliders with a label containing Enable, Debug, Use, Toggle or Active are rendered as checkboxes. Internally they remain floats:

// @uniform float u_enable 1.0 0.0 1.0 "Denoise / Enable"
// @uniform float u_debug  0.0 0.0 1.0 "Debug / Enable"
0.0 = off (unchecked)
1.0 = on  (checked)

In GLSL, branch on them like this:

if (u_enable > 0.5) {
    // apply effect
}
Never use #if u_enable == 1 — preprocessor directives cannot read runtime uniform values. Always use a runtime if() branch.

GLSL Examples

1. Brightness / Contrast / Saturation

GLSL · Color
// @uniform float u_brightness  1.02 0.5 1.5  "Color / Brightness"
// @uniform float u_contrast    1.08 0.5 1.8  "Color / Contrast"
// @uniform float u_saturation  1.08 0.0 2.0  "Color / Saturation"
// @uniform float u_mix         1.0  0.0 1.0  "Output / Effect Mix"

float luma(vec3 c) { return dot(c, vec3(0.299, 0.587, 0.114)); }

void main() {
    vec4 src = texture(u_video, v_uv);
    vec3 c   = src.rgb;

    c *= u_brightness;
    c  = (c - 0.5) * u_contrast + 0.5;

    float y = luma(c);
    c = mix(vec3(y), c, u_saturation);

    fragColor = vec4(mix(src.rgb, clamp(c, 0.0, 1.0), u_mix), src.a);
}

2. Simple Sharpen

GLSL · Sharpen
// @uniform float u_strength 0.45 0.0 2.0 "Sharpen / Strength"
// @uniform float u_radius   1.0  0.5 2.0 "Sharpen / Radius"
// @uniform float u_mix      0.8  0.0 1.0 "Output / Effect Mix"

vec3 sampleVideo(vec2 uv) {
    return texture(u_video, clamp(uv, vec2(0.0), vec2(1.0))).rgb;
}

void main() {
    vec2 px  = u_radius / u_res;
    vec4 src = texture(u_video, v_uv);

    vec3 n = sampleVideo(v_uv + vec2(0.0,   -px.y));
    vec3 s = sampleVideo(v_uv + vec2(0.0,    px.y));
    vec3 e = sampleVideo(v_uv + vec2( px.x,  0.0));
    vec3 w = sampleVideo(v_uv + vec2(-px.x,  0.0));

    vec3 blur  = (n + s + e + w) * 0.25;
    vec3 sharp = src.rgb + (src.rgb - blur) * u_strength;

    fragColor = vec4(mix(src.rgb, clamp(sharp, 0.0, 1.0), u_mix), src.a);
}

3. Bilateral Denoise

GLSL · Denoise
// @uniform float u_enable  1.0  0.0 1.0 "Denoise / Enable"
// @uniform float u_spatial 1.2  0.1 3.0 "Denoise / Spatial Radius"
// @uniform float u_color   0.12 0.01 0.5 "Denoise / Color Tolerance"
// @uniform float u_mix     0.65 0.0 1.0 "Output / Effect Mix"

void main() {
    vec2 px   = 1.0 / u_res;
    vec4 src  = texture(u_video, v_uv);
    vec3 center = src.rgb;
    vec3 sum  = vec3(0.0);
    float wSum = 0.0;

    for (int y = -1; y <= 1; y++) {
        for (int x = -1; x <= 1; x++) {
            vec2  o  = vec2(float(x), float(y));
            vec3  s  = texture(u_video, v_uv + o * px).rgb;
            float sw = exp(-dot(o, o) / max(2.0 * u_spatial * u_spatial, 0.0001));
            float cd = length(s - center);
            float cw = exp(-(cd * cd) / max(2.0 * u_color * u_color, 0.0001));
            float w  = sw * cw;
            sum += s * w;
            wSum += w;
        }
    }

    vec3 denoised = sum / max(wSum, 0.0001);
    if (u_enable < 0.5) denoised = center;
    fragColor = vec4(mix(center, denoised, u_mix), src.a);
}

4. Vignette

GLSL · Creative
// @uniform float u_amount   0.35 0.0 1.0 "Vignette / Amount"
// @uniform float u_softness 0.55 0.1 1.5 "Vignette / Softness"

void main() {
    vec4  src = texture(u_video, v_uv);
    vec2  p   = v_uv * 2.0 - 1.0;
    float d   = dot(p, p);
    float vig = smoothstep(1.2, u_softness, d);
    fragColor = vec4(mix(src.rgb, src.rgb * vig, u_amount), src.a);
}

5. Animated RGB Shift

GLSL · Creative
// @uniform float u_amount 0.45 0.0 1.0 "RGB Shift / Amount"
// @uniform float u_speed  1.0  0.0 5.0 "RGB Shift / Speed"
// @uniform float u_radius 1.0  0.0 4.0 "RGB Shift / Radius"

void main() {
    vec2  px   = 1.0 / u_res;
    float wave = sin(v_uv.y * u_res.y * 0.04 + u_time * u_speed) * u_radius;

    float r = texture(u_video, v_uv + vec2(px.x * wave, 0.0)).r;
    float g = texture(u_video, v_uv).g;
    float b = texture(u_video, v_uv - vec2(px.x * wave, 0.0)).b;

    vec4 src = texture(u_video, v_uv);
    fragColor = vec4(mix(src.rgb, vec3(r, g, b), u_amount), src.a);
}

Canvas 2D Basics

Canvas 2D filters are JavaScript snippets that run on every frame via requestAnimationFrame. GVF injects the following variables into your code automatically.

NameTypeMeaning
ctxCanvasRenderingContext2DThe 2D drawing context of the overlay canvas
canvasHTMLCanvasElementThe overlay canvas element — use canvas._myState for persistent state
imgHTMLImageElementSource image / frame reference
widthnumberCanvas width in pixels (device pixels)
heightnumberCanvas height in pixels (device pixels)
framenumberDelta time in milliseconds since last frame — use for physics/animation
u_mouse{x, y}Mouse position in UV space (0–1)
u_zoomnumberCurrent zoom level

Canvas 2D Parameters

Canvas filters support slider and select parameters using comment annotations:

// @param strength 0.8 0.0 2.0 "Strength"
// @paramselect mode normal "Mode" normal:Normal,screen:Screen,overlay:Overlay
Store persistent per-filter state on canvas._myState — this object survives across frames and is unique to each filter instance.

Canvas 2D Examples

1. Simple Draw

Canvas 2D · Basic
ctx.clearRect(0, 0, width, height);
ctx.drawImage(img, 0, 0, width, height);

2. CSS Filter Through Canvas

Canvas 2D · Color
// @param contrast   1.1 0.5 2.0 "Contrast"
// @param saturation 1.15 0.0 2.0 "Saturation"

ctx.clearRect(0, 0, width, height);
ctx.filter = `contrast(${contrast}) saturate(${saturation})`;
ctx.drawImage(img, 0, 0, width, height);
ctx.filter = 'none';

3. Scanline Overlay

Canvas 2D · FX
// @param opacity 0.18 0.0 0.6 "Opacity"
// @param spacing 4 2 12 "Spacing"

ctx.clearRect(0, 0, width, height);
ctx.drawImage(img, 0, 0, width, height);

ctx.fillStyle = `rgba(0,0,0,${opacity})`;
for (let y = 0; y < height; y += spacing) {
  ctx.fillRect(0, y, width, 1);
}

4. Vignette Overlay

Canvas 2D · Creative
// @param amount 0.45 0.0 1.0 "Amount"

ctx.clearRect(0, 0, width, height);
ctx.drawImage(img, 0, 0, width, height);

const g = ctx.createRadialGradient(
  width / 2, height / 2, Math.min(width, height) * 0.2,
  width / 2, height / 2, Math.max(width, height) * 0.65
);
g.addColorStop(0, 'rgba(0,0,0,0)');
g.addColorStop(1, `rgba(0,0,0,${amount})`);

ctx.fillStyle = g;
ctx.fillRect(0, 0, width, height);

Audio / Speech Basics

Audio/Speech filters configure audio processing via Web Audio API snippets or JSON presets. They run through a separate pipeline from the video filters and do not have access to GLSL or Canvas 2D variables.

Keep audio filters clearly separated from video filters. Audio code must not access u_video, v_uv, ctx or any other video/canvas variables.
VariableAvailable in Audio filters
ctxCanvas 2D context — not available
u_videoVideo texture — not available
videoHTMLVideoElement reference — available
canvasOverlay canvas — available (for cleanup hooks)
canvas._gvfAudioCleanupFunction to call on filter teardown — available

Audio / Speech Examples

1. Speech Enhancer Preset JSON

Audio · Speech
{
  "name": "Speech Enhancer",
  "type": "speech",
  "gain": 1.15,
  "highpassHz": 90,
  "presenceBoostDb": 3,
  "deEss": 0.25,
  "noiseGate": 0.12
}

2. Web Audio EQ Concept

Audio · EQ
// Web Audio based EQ filter.
// GVF provides audioContext and sourceNode via window.__gvfAudio(video).

const highpass = audioContext.createBiquadFilter();
highpass.type = 'highpass';
highpass.frequency.value = 90;

const presence = audioContext.createBiquadFilter();
presence.type = 'peaking';
presence.frequency.value = 3200;
presence.Q.value = 1.0;
presence.gain.value = 3;

sourceNode.connect(highpass);
highpass.connect(presence);
presence.connect(audioContext.destination);

3. Noise Gate Preset

Audio · Utility
{
  "name": "Light Noise Gate",
  "type": "noise-gate",
  "threshold": -42,
  "attackMs": 8,
  "releaseMs": 120,
  "makeupGain": 1.0
}

Recommended Filter Order

Anime / Cartoons

1. Chroma Reconstruction
2. Denoise
3. Upscale / Restore
4. Sharpen
5. Color / LUT
6. Grain  (optional)

Real Video / Twitch / YouTube

1. Mild Denoise
2. FSR / RAVU / Restore
3. Very Light Sharpen
4. Contrast / Gamma
5. LUT  (optional)

DRM Pages (Netflix, Disney+, etc.)

1. Native GPU video enhancement (e.g. RTX VSR, Intel XeSS)
2. SVG / CSS Contrast
3. SVG / CSS Color
4. LUT-like SVG color matrix
Do not stack strong upscalers and sharpeners. One main upscaler plus one mild sharpen is usually enough. Stacking FSR + Anime4K + ACNet + CAS simultaneously will destroy detail and hammer GPU usage.

Common Errors and Fixes

input_size undeclared

Cause: mpv/libplacebo shader variable not supported in raw WebGL.

Fix: Use GVF compatibility aliases or rewrite as a single-pass WebGL shader.

macro redefined

Cause: multiple mpv passes define the same macro names.

Fix: Remove duplicate #define blocks or use a GVF single-pass fallback version.

wrong operand types

Cause: mixing int/float or vec2/vec3/vec4 incorrectly.

Wrong: 2 * color          Wrong: vec2Value * vec4Value
Right: 2.0 * color        Right: vec2Value * vec4Value.xy

sampler undeclared

Cause: HLSL shader pasted into a GLSL field.

HLSL: sampler s0 : register(s0);  tex2D(s0, uv)
GLSL: uniform sampler2D u_video;  texture(u_video, v_uv)

unexpected token after conditional expression

Cause: #if trying to branch on a runtime uniform value.

Wrong:
#if u_enable == 1
    // ...
#endif

Right:
if (u_enable > 0.5) {
    // ...
}

u_contrast / u_strength already declared

Cause: using a built-in uniform name in a @uniform line.

Wrong:  // @uniform float u_contrast 1.0 0.5 2.0 "Contrast"
Right:  // @uniform float u_my_contrast 1.0 0.5 2.0 "Contrast"

Performance Tips

  • Use the smallest kernel radius that still looks good — a 3×3 loop is far cheaper than 5×5.
  • Avoid 5×5 or 7×7 sample loops on integrated or weak GPUs.
  • Do not stack multiple denoise filters — one good bilateral is better than three weak blurs.
  • Do not stack multiple sharpen filters — they amplify each other's halos.
  • Prefer SVG for simple color and contrast effects — zero GPU overhead.
  • Use GLSL only when real multi-pixel sampling is needed.
  • On DRM sites, SVG/CSS + native GPU video enhancement (RTX VSR, XeSS, etc.) is the only viable path.
  • Use the GLSL Mode selector in the Settings HUD to reduce render rate to 24 fps if the effect doesn't need to be 60 fps.

Good chain

Denoise mild → Restore/Upscale → Sharpen mild → Color

Bad chain

FSR → Anime4K → ACNet → CUT → CAS → Adaptive Sharpen

Quick Templates

SVG Template

SVG · Template
<feColorMatrix type="saturate" values="1.2"/>

GLSL Template

GLSL · Template
// @uniform float u_mix 1.0 0.0 1.0 "Output / Effect Mix"

void main() {
    vec4 src = texture(u_video, v_uv);
    vec3 fx  = src.rgb;

    // edit fx here

    fragColor = vec4(mix(src.rgb, fx, u_mix), src.a);
}

Canvas 2D Template

Canvas 2D · Template
// @param opacity 0.5 0.0 1.0 "Opacity"

ctx.clearRect(0, 0, width, height);
ctx.drawImage(img, 0, 0, width, height);

ctx.fillStyle = `rgba(0,0,0,${opacity})`;
ctx.fillRect(0, 0, width, height);

Audio Preset Template

Audio · Template
{
  "name": "My Audio Preset",
  "type": "speech",
  "gain": 1.0,
  "noiseGate": 0.1
}