Skip to content

Latest commit

 

History

318 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Castor

stupside%2Fcastor | Trendshift

Latest Release Go Reference Homebrew License Build Status

Castor

Smart TVs won't cast arbitrary web video, and screen mirroring is laggy. Castor puts the video from a web page or a link on your TV, even when the TV can't play it as it is, with generated subtitles if you want them.

Use it on your computer alone, or run a castor media server on a machine that stays on, such as a NAS, and cast through it from every computer at home.

A general-purpose casting tool: it casts only what you point it at. See Purpose and disclaimer.

Browsing titles in the castor TUI
Run castor cast to browse titles and cast, without leaving the terminal.

Quick start

1. Install (macOS. Other options)

brew install --cask stupside/tap/castor

2. Find your TV

castor scan

3. Save it to config.yaml, in the directory you run castor from

device:
  name: "Living Room TV"   # exact name from `castor scan`
  type: dlna               # or: chromecast, roku

4. Cast

castor cast player https://example.com/watch/some-video

castor scan found nothing? See Troubleshooting.

Commands

Command What it does
castor scan List the devices on your network
castor cast player <url> Cast a web page with an embedded video player
castor cast url <url> Cast a direct stream or video URL
castor cast Browse titles and cast, interactively (needs a TMDB key)
castor cast movie <id> Resolve a movie id against your sources and cast
castor cast episode <id> --season N --episode N Same, for a TV episode
castor media-server Run the media server other computers cast through (another machine)
castor api-server Serve castor's API, for apps and integrations (another machine)

castor cast --dry-run ... prints the streams it found instead of casting. Run castor --help for all flags.

Installation

Castor runs best as a native binary on the same network as your TV. It needs three tools on your PATH:

Tool Version Used for
Chrome / Chromium Any recent Finding the video on a page
ffmpeg 7.1+ (older builds reject flags castor uses) Converting the video for your TV
ffprobe 7.1+ Reading the video's format

Castor converts video on the GPU when ffmpeg can (VideoToolbox, NVENC, Quick Sync, VA-API or AMF, whichever works first), and in software otherwise.

  • macOS: brew install --cask stupside/tap/castor.
  • Linux: download castor_<version>_linux_amd64.tar.gz (or _arm64) from the latest release and put castor on your PATH.
  • Windows: download castor_<version>_windows_amd64.zip (or _arm64), extract castor.exe into a folder on your PATH, and install the tools with winget install Gyan.FFmpeg and winget install Google.Chrome. On first run, SmartScreen may block the unsigned binary (choose More info, then Run anyway), and the firewall asks about network access: allow it on private networks, or Castor finds no devices.
  • From source: see CONTRIBUTING.md.

Configuration

Castor reads config.yaml from the working directory (or --config <path>). Casting from the command line needs device; every other key has a default. Any key can also be set as a CASTOR_SECTION__FIELD environment variable, e.g. CASTOR_CAST__MAX_HEIGHT=720.

Tip

Keep secrets (keys, tokens, passwords) in a git-ignored config.local.yaml, which overlays config.yaml, or in environment variables. See SECURITY.md.

Subtitles

Generated subtitles, burned into the video. The model downloads once to your user cache.

cast:
  subtitles: en            # a language code, or auto to detect it; unset for none
whisper:
  # model_path: ""         # default: ggml-tiny.en (~75 MB, English only)

For another language or auto, point whisper.model_path at a multilingual whisper.cpp model (e.g. ggml-base.bin).

Note

Burn-in applies only to devices castor always streams to, such as DLNA. A device that fetches the video itself (Chromecast, Roku) gets none, even when castor relays it.

Video quality

Set max_height to your TV's vertical resolution: castor picks the tallest stream that fits and scales down anything taller it knows about. Raise it if you'd rather the TV play a taller video as it is.

cast:
  max_height: 2160         # default: 1080

Sources

cast movie, cast episode, and the interactive browser turn a title id into a page URL. Castor bundles no sources: you add your own, for sites you are authorized to use. The id is substituted into your templates under each of your proxies, and the page is cast like cast player.

sources:
  - proxies: ["https://your-source.example"]   # base URLs, tried in order
    templates:
      movie: "/embed/movie/{itemID}"
      episode: "/embed/tv/{itemID}/{season}-{episode}"

So castor cast movie tt12300742 opens https://your-source.example/embed/movie/tt12300742.

TMDB key

Only the interactive browser (castor cast) needs one. Get a free key from themoviedb.org:

tmdb:
  api_key: "<KEY>"

Run castor on another machine

A shared media server

A machine that stays on, such as a NAS, does the heavy work; your computer finds the TV and drives it. On the server, with a token in its config:

castor media-server   # listens on :8410 (server.listen)

On your computer:

server:
  url: http://my-nas:8410
  token: "<a long random string, the same on both>"

Once the TV plays, closing the terminal leaves the cast playing; Ctrl+C stops it. Subtitles, quality and delivery come from your computer's config; the whisper model is the server's. For a server outside your home network, set where the TV reaches it with server.advertise (e.g. http://castor.example.com:8410) and put it behind HTTPS.

The API, for apps and integrations

Everything castor does to your TVs is an API: list them, cast a link or a page on one, follow the cast, stop it. Run it on a machine on your TV's network, casting through a media server (the shared one, or one beside it on the same machine):

castor media-server   # listens on :8410 (server.listen)
castor api-server     # listens on :8411 (api.listen), with server.url: http://localhost:8410
curl -X POST http://localhost:8411/castor.v1.DeviceService/ListDevices \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{}'

curl -X POST http://localhost:8411/castor.v1.CastService/Cast \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"target": {"deviceId": "<id from ListDevices>"}, "source": {"stream": {"url": "https://example.com/video.m3u8"}}}'

Set api.token once the API leaves your machine; the header is needed only then. A page instead of a link is "source": {"pages": {"urls": ["https://example.com/watch"]}}. End a cast with Stop. Watch follows a cast to its end; it is a server stream, so use buf curl, grpcurl, or a client generated from the contract with buf. Point other castor commands at it with api.url and api.token.

Supported devices

Protocol Works with Status
DLNA / UPnP (MediaRenderer:1) Most smart TVs, and players like Kodi, VLC, and Plex Tested on Samsung
Chromecast Google Cast devices Experimental, not yet tried on real hardware
Roku Roku TVs and players, through a sideloaded channel Experimental, not yet tried on real hardware

Roku setup

Roku can't play an arbitrary URL from a preinstalled app, so Castor installs a small channel of its own:

  1. Turn on Developer Mode (once, by hand): on the remote press Home x3, Up x2, Right, Left, Right, Left, Right, enable developer mode, and set a web-server password. The device reboots.
  2. Put the password in your config (out of git, see Configuration):
    devices:
      roku:
        password: "<dev-web-server-password>"   # first cast only
  3. Cast. Castor sideloads its channel automatically; later casts reuse it.

Already published the channel to your account? Set devices.roku.app_id to its numeric id instead: no dev mode, no password. When castor relays the video, a Roku plays about 30 s behind.

Troubleshooting

castor scan finds nothing: pin the device by IP

Discovery doesn't cross VLANs or subnets, and is blocked on Android/Termux (netlinkrib: permission denied). Pinning a host reaches the device directly, and skips the discovery wait.

device:
  name: "Living Room TV"   # now just a label
  type: dlna
  host: 192.168.0.3        # the device's LAN IP

If a DLNA TV doesn't answer at its IP, use its full description URL instead (e.g. http://192.168.0.3:9197/dmr). On Android/Termux, also leave network.interface empty (the default): pinning one needs the same blocked interface lookup.

The page won't play

Castor plays the page to find its video, so it only works on pages whose video starts without a click. DRM-protected streams are refused.

The device loads the stream but plays nothing

Have castor always send the video itself, rather than handing the TV the link:

cast:
  delivery: serve   # "auto" (the default) decides per source

Relaying costs bandwidth and CPU, so try it once first with CASTOR_CAST__DELIVERY=serve. castor --debug logs which way each cast went and why.

Docker

The ghcr.io/stupside/castor image is the full castor with Chrome, ffmpeg, and ffprobe. It converts video on an Intel GPU through VA-API when you pass --device /dev/dri, and in software otherwise.

Warning

Discovery and the API server need --network host, which Docker Desktop (macOS/Windows) ignores, so scan finds nothing there. Use the native binary instead.

docker run --rm --network host ghcr.io/stupside/castor:latest scan

docker run --rm --network host --device /dev/dri \
  -v "$PWD/config.yaml:/config.yaml" \
  -v castor-cache:/root/.cache \
  ghcr.io/stupside/castor:latest \
  cast player https://example.com/watch/some-video

The castor-cache volume keeps downloaded whisper models. To keep a server running, pass -d with server (and -e CASTOR_SERVER__TOKEN=<token>) or api (and -e CASTOR_API__TOKEN=<token>). A lone server can also run without host networking: publish -p 8410:8410 and set server.advertise. Health checks (grpc.health.v1.Health) need no token, so an orchestrator can probe either server.

docker-compose.yml runs media-server and api-server as separate services on the host network: put CASTOR_SERVER__TOKEN and CASTOR_API__TOKEN in a git-ignored .env, then docker compose up -d.

Tags: :latest (stable), :canary (preview), or a pinned :vX.Y.Z.

Purpose and disclaimer

  • It hosts nothing. No bundled video, catalog, or sources. Castor casts only what you supply and are authorized to use.
  • It does not touch DRM. It never decrypts or circumvents DRM, and refuses protected streams.
  • Lawful use is your responsibility. Check a site's terms and your local law. Provided as-is for lawful, personal, and educational use.

Contributing

See CONTRIBUTING.md, and ARCHITECTURE.md for how castor works inside.

About

Point it at any web page and it finds the video, extracts the stream, transcodes it and casts in real time to your TV. It even burns subtitles….

Topics

Resources

Contributing

Security policy

Stars

2.4k stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages