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

Reflection King: a scrape-and-transcode backend that feeds video to the VRChat player

// created

Reflection King is a backend that feeds video to the VRChat player. That player is a picky eater: it only accepts well-behaved media addresses it can GET directly, and it just gives up on anything that needs a login, needs JS to parse, or uses an encoding it doesn’t recognize. This thing’s job is simple: throw a page URL at it, and it parses and transcodes the source, landing it as a raw address on the server that the player can grab and play directly. No more running yt-dlp by hand and uploading to an image host every single time.

The backend is Axum, with a React console up front to watch task status. At the core is a persistent task queue stored in SQLite, so tasks survive a process restart. A URL comes in and first goes through resource discovery, which chains yt-dlp, you-get, and streamlink internally: if it can extract a stream address, it does. Complex sites it can’t crack get handed off to a Playwright sidecar that spins up a real browser to sniff. The discovery stage usually turns up more than one candidate, so each candidate gets scored, and the obvious dead ends get filtered out early. Region locks, DRM, and injected ads all get flagged as risks, so you don’t find out you hit a dead end halfway through transcoding. Once a candidate is chosen, ffmpeg takes over: transcode when it has to, remux when it can, and finally add faststart to a uniform MP4 output so playback can start while the file is still downloading. The result is served at the raw URL /media/{id}/{file}, with HTTP Range support so scrubbing the timeline stays smooth.

Three things bit me. First, security: this is fundamentally “you give it a URL and it goes and requests it,” a textbook SSRF breeding ground. Private IPs and internal network ranges have to be blocked before the request goes out, otherwise a public deployment is basically a wide-open portal into your internal network. The admin interface is forced to carry a key, and none of the sidecar’s CDP/VNC/debug ports can ever be exposed. Second, auth state: some sources are invisible without a login, so I added server-side browser Profile/Cookie import to let the sidecar probe with a session. This part is the most sensitive: .env, Cookie JSON, browser Profiles, and the SQLite database absolutely never go into Git. Third, site adaptation: the generic parser doesn’t cover everything, so Bilibili, Hanime1, and a batch of MacCMS resource sites need their own adapters written separately. This is the most time-consuming and least rewarding part, and every time a site redesigns, the adapter has to follow.

Deploying to a public VPS is one line:

curl -fsSL https://raw.githubusercontent.com/dwgx/Reflection_King/master/install.sh | sudo bash

If you’d rather not compile Rust + Playwright locally, pull the prebuilt image from GHCR and run docker compose, which is faster. Keep in mind that RK_PUBLIC_BASE_URL must be set to an address that external clients can actually reach, otherwise /media/... is only valid on the local machine and the VRChat side won’t be able to play it even after it receives it. Finer details are in the repo’s docs/DEPLOYMENT.md and docs/SECURITY.md.

The project is split into a few crates: reflection-core holds config, models, task storage, URL safety, and download/transcode; reflection-api is the HTTP API plus queue scheduling; browser probing is broken out into the reflection-browser sidecar; and the console is a standalone React app. There’s also a stub for reflection-worker, meant for later when the worker gets split out to run independently, but it’s not there yet.