dwgx@blog:~$dwgx
> cd ../posts

ShadertoyTool: A Little Tool for Ripping Shadertoy Shaders

// created

ShadertoyTool is a single-file Python CLI. Throw it a Shadertoy URL or a 6-character ID and it rips the entire shader in one shot: source code plus textures, audio, and video. The web page only lets you watch the effect, and grabbing all the input assets means manually working around Cloudflare, which is a pain, so I just automated it.

It merges every Render Pass (Image / Buffer / Sound / Common) into a single .glsl, with a comment header in front of each pass marking exactly where it came from:

// ShaderToy ID: XltGDr
// Name: Contra
// Retrieved: 2025-01-01 12:00:00 UTC

// ===== Pass 0: Image (image) =====
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    // ...
}

// ===== Pass 1: Buffer_A (buffer) =====
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    // ...
}

Every referenced texture, audio, video, and cubemap gets downloaded into <shader_id>_assets/, and it also generates a manifest.json recording each asset’s URL, filename, download status, and reference location. The complete scene of a shader is all there.

Usage is straightforward:

python fetch_shadertoy.py https://www.shadertoy.com/view/4djyRD
# or just pass the ID
python fetch_shadertoy.py 4djyRD

If you only want the source and not the assets, add --skip-assets; use -o to change the output directory. On Windows there’s a fetch_shadertoy.cmd that first checks whether requests, browser_cookie3, and playwright are installed, and auto-installs whatever’s missing via pip before running.

The real hassle isn’t parsing the shader, it’s reliably getting the data. Shadertoy sits behind Cloudflare, and direct requests often get shut out, so I built a three-tier fallback, from the easiest to the most brute-force:

  1. API v1: create an app, get a key, and go through the official endpoint /api/v1/shaders/<id>?key=<key>. This completely bypasses Cloudflare and is the most stable, so configuring a key is generally recommended.
  2. Legacy POST: with no key, it emulates the web client by sending an internal request to https://www.shadertoy.com/shadertoy, pulling cookies off your local browser (Chrome / Edge / Brave / Firefox) via browser-cookie3.
  3. Browser fallback: if the first two both fail, it uses Playwright to spin up your local Edge to clear the verification, then calls the same endpoint from within the page via fetch. This can also run --headless.

Asset downloads follow the same idea: first a direct pull with requests, then anything hit by a 403 or Cloudflare gets thrown into the browser context to retry, with automatic retries on 408/429/500. Scraping is at the mercy of Shadertoy’s internal endpoints and Cloudflare’s mood, so the success rate fluctuates with the state of the site, which is why the key-configured path is always the first choice. Just three dependencies: requests, browser-cookie3, playwright, on Python 3.8+.