Castor

Latest Release

Go Reference

Homebrew

License

Build Status

Castor

Smart TVs won't cast arbitrary web video, and screen mirroring is laggy and drops resolution. Castor casts the real stream instead, at full quality, from your terminal.

I built it because I couldn't cast web video from my laptop to my TV: no Chromecast, no AirPlay.

Point it at any web page and Castor finds the video, extracts the stream, transcodes it for your TV, and casts in real time. It also takes a direct stream URL or an IMDB/TMDB id, and can burn in auto-generated subtitles.

Run castor cast to browse and search titles, inspect posters and metadata, then cast, without leaving the terminal.

Browsing titles in the castor TUI

Note

How extraction works

Castor launches headless Chrome with a randomized fingerprint and stealth scripts to hide automation. It watches all network traffic over the Chrome DevTools Protocol to capture the video stream, then runs a short action pipeline: click the page, navigate into the largest iframe, solve a Cloudflare Turnstile if one appears, and click again as a fallback.

This works on most streaming sites but won't beat sophisticated bot protection.

Installation

The recommended way to run Castor is the native binary. It runs directly on your machine, so it shares your TV's network, which device discovery needs. It requires Chrome/Chromium (headless extraction), ffmpeg (transcoding), and ffprobe (format detection) on your PATH. Docker is an optional alternative that bundles all three, but only works from a Linux host.

Docker

Homebrew (macOS)

See Quick start to create the one-time config.yaml (which TV, which sources). After that, casting is a single command, no URL, just an IMDB/TMDB id:

Quick start

From source

Needs Go 1.26+ and cmake (the whisper.cpp bindings are cgo and link a locally built libwhisper.a):

go install won't work: the vendored whisper.cpp bindings come in through a local replace and need that prebuilt static lib.

Docker (optional)

Warning

Docker can only reach your TV from a Linux host on the same LAN. Discovery is SSDP multicast and the TV streams back from Castor's replay server, and neither survives Docker's bridge network, so --network host is required. But on Docker Desktop (macOS/Windows) --network host is a silent no-op: the container lands on Docker Desktop's internal VM subnet (e.g. 192.168.65.x), never your real LAN, so scan finds nothing and cast fails with device "…" (type dlna) not found even though the TV is up. No docker run flag fixes this. On macOS/Windows, run the native binary instead. Or point Docker at a Linux VM bridged onto your LAN (e.g. Lima + socket_vmnet), which is the only way a container gets a real address on your network.

native binary

On a Linux box or NAS on the same network as the TV, the prebuilt ghcr.io/stupside/castor image bundles Chrome, ffmpeg and ffprobe so you don't install them by hand:

The -v "$PWD/config.yaml:/config.yaml" mount is what makes this work: the container reads your device and sources from config.yaml at /config.yaml, so run every command from the directory holding it. The castor-cache volume keeps the auto-downloaded whisper models (~75 MB) between runs; swap :latest for any release tag to pin a version.

config.yaml

Supported devices

DLNA / UPnP

Any TV implementing the DLNA/UPnP MediaRenderer:1 profile works, which covers virtually every smart TV sold in the last decade: Samsung (tested), LG, Sony Bravia, Panasonic Viera, Philips, Hisense, TCL, VIZIO, Sharp. Networked players like Kodi, VLC, and Plex also work.

Run castor scan to discover devices on your network.

Chromecast

Warning

Experimental: implemented but untested. Contributions welcome.

Quick start

Castor requires a config.yaml in the current directory (or pass --config). Everything mechanical ships with working defaults, so a minimal file only has to say which device to cast to and which sources to cast from. A TMDB API key is optional, needed only for the interactive browser.

TMDB API key

Create config.yaml with that name:

That's all you need to cast by id, the quickest path with no TMDB key:

Note

Sources can change. cast movie resolves the id against the proxies you set in config.yaml. These are external sites, so one can go offline or move without notice. If a cast stops resolving, update that entry in the proxies list or add another.

config.yaml

Prefer to browse? Add a tmdb.api_key and run castor cast for an interactive TUI. It first asks which device to cast to: every DLNA/UPnP renderer on your network, discovered on the fly and with your configured device pre-selected:

Selecting a cast target in the castor TUI

Then it opens a TMDB-backed browser: filter by genre, search, inspect posters and metadata, drill into a series' episodes, and cast the one you pick.

Usage

Configuration

Quick start covers the required keys. Beyond those, everything mechanical (timeouts, probing, capture, transcoding, Chrome discovery) ships with working defaults. Override any of it in config.yaml, point at a different file with --config, drop secrets like your TMDB key into a git-ignored sibling config.local.yaml (it overlays config.yaml), or set CASTOR_SECTION__FIELD environment variables.

Quick start

The one opt-in worth calling out is auto-generated subtitles, burned into the video:

Disclaimer

Castor hosts no video and ships no content of its own. It's a general tool for casting a stream to your TV, not tied to any particular website. The sources in the example config.yaml are just that, examples; which sites you point it at, and staying within the law and their terms of use, is your responsibility. Only cast content you have the right to access.

Contributing

See CONTRIBUTING.md.

CONTRIBUTING.md