Sign inSign up

tanel/tethys

By tanel

Updated 25 days ago

Image
0

64

tanel/tethys repository overview

tethys

A personal media library tool: list, search, play, cast, convert, and clean up video files across one or more directories — as a local CLI, a small HTTP server for remote access, or both from the same binary.

Originally a Python (Falcon) API server plus a separate Go CLI client. This is a from-scratch Go rewrite that merges both into one binary, with no implicit personal defaults — every directory it scans is one you tell it about explicitly.

Build

go build -o tethys ./cmd/tethys

Or run directly with go run ./cmd/tethys ... during development.

Concepts

  • A directory is any folder you want tethys to scan, given a label. There are two ways to add one, and you can mix both:

    • Explicit: -directory label:/path/to/folder (-D for short), repeatable. Directories don't need to share a parent — different drives/mounts are fine.
    • Auto-discovered: -directories /path/to/parent (-R for short), repeatable. Every immediate subdirectory of that parent becomes its own scanned directory, labeled by its own name — handy when you'd rather mount one parent folder than list every subfolder by hand (see Docker below). Re-checked on every listing, so a folder added later shows up without restarting tethys serve.

    Nothing is scanned unless you configure at least one of these — there's no default directory.

  • ~ and $HOME are expanded by tethys itself, not just relied on from the shell — needed because -D label:~/path moves the ~ out of the one position (start of a shell word) where your shell would normally expand it, and because ~/.tor.yaml config values go through no shell at all.

  • A label starting with . (e.g. -D .private:/path, or a subdirectory literally named .private under a -directories parent) is hidden by default — but the dot is just a marker, not part of the name: it shows up (and is revealed via -s all or -s private) as private, not .private. This is a naming convention, not a permission — it just keeps a folder out of your everyday listing. Labels are always lowercased.

  • The data directory (-data) holds tethysdb.json: which files you've marked downloaded/hidden, plus bandwidth stats, with rotating weekly backups. It's created automatically on first run. Not dotfile-hidden on purpose — it's small and plain-text, worth being able to ls/cat without -a. (JSON was chosen for exactly that reason: this data is tiny — a couple of hash lists and a small stats map — so a real embedded database would be more moving parts for no real benefit. If it ever needs proper transactions or grows past "flat file" scale, a pure-Go, no-cgo option like modernc.org/sqlite or etcd-io/bbolt would be the natural upgrade — pure Go matters here since a cgo-linked SQLite driver adds a real cross-compilation/Docker-build burden a single JSON file doesn't have.)

Local mode

Scans directories directly — no server needed.

tethys -directory movies:/data/Movies -directory "shows:/data/TV Shows"
# auto-discovery: /data/Media/Movies, /data/Media/Shows, /data/Media/.Private
# become labels "movies", "shows", "private" (hidden) — no per-folder flag
tethys -R /data/Media
# short flag, search, JSON output
tethys -D movies:/data/Movies -search "die hard" -json
# a folder you don't want cluttering the default listing
tethys -D movies:/data/Movies -D .private:/data/private-stash
tethys -D movies:/data/Movies -D .private:/data/private-stash -s private
# hide/delete/cleanup (need a real hash from a listing first)
tethys -D movies:/data/Movies -hide abc12345
tethys -D movies:/data/Movies -d abc12345
tethys -D movies:/data/Movies -cleanup
# play (mpv, or vlc with -vlc), cast, convert, download
# note: flags must come before the hash — Go's flag parser treats
# everything after the first non-flag argument as positional
tethys -D movies:/data/Movies abc12345          # play by hash
tethys -D movies:/data/Movies -play-all -search movie
tethys -D movies:/data/Movies -cast -cast-uuid <chromecast-uuid> abc12345
tethys -D movies:/data/Movies -convert 480 abc12345
tethys -D movies:/data/Movies -wget abc12345

Server + remote mode

Run the scanner as an HTTP API (e.g. on a machine with the actual files), then talk to it from anywhere with the same CLI:

tethys serve -D movies:/data/Movies -addr :8080 -token mysecret
tethys -remote http://your-server:8080 -token mysecret -json

-remote mode doesn't scan locally at all — -directory/-D and -directories/-R only matter on the serve side.

tethys serve serves the files themselves, not just listings — no separate static file server (nginx, Caddy, etc.) needed. GET /get/<label>/<relpath> streams the raw bytes straight from wherever that label's directory actually lives, resolved fresh on every request (nothing is pre-scanned or cached at startup, so a multi-hundred-GB library that's constantly growing, shrinking, or being reorganized is never stale). play/cast/convert/wget work with zero extra setup as a result — -file-base-url is only for the rarer case where you genuinely want files served from somewhere else (a CDN, a caching reverse proxy) instead.

Since mpv/vlc/wget/go-chromecast consume a bare URL and can't attach a custom header, the token travels as a ?token=... query parameter on /get/ URLs (the Token header still works too, e.g. for a browser extension or curl). The JSON API (/api/files) is unaffected — it only ever used the header.

Config file

Repeating -directory/-data/-token etc. on every invocation gets old — put them in ~/.tor.yaml instead, under a tethys: key (other tools can have their own top-level keys in the same file):

tethys:
  data: /data
  directories:
    - "movies:/data/Movies"
    - "shows:/data/TV Shows"
    - ".private:/data/private-stash"
  auto_directories:
    - "/data/Media"
  token: mysecret
  addr: ":8080"

Precedence is flag > environment variable > config file > built-in default. $TETHYS_CONFIG overrides the config file's path if you don't want ~/.tor.yaml.

FlagEnv varConfig key
-dataTETHYS_DATAdata
-directory/-Ddirectories
-directories/-Rauto_directories
-remoteTETHYS_REMOTEremote
-tokenTETHYS_TOKENtoken
-file-base-urlTETHYS_FILE_BASE_URLfile_base_url
-cast-uuidTETHYS_CAST_UUIDcast_uuid
-addr (serve)addr

Run tethys -h / tethys serve -h for the full flag list.

Docker

tethys serve is the piece that makes sense in a container — the CLI's play/cast/convert/wget need mpv/vlc/ffmpeg/go-chromecast and a real display/network presence, so build/run that part locally instead.

-directories/-R (auto-discovery) is the natural fit here: mount one parent folder — laid out as movies/, shows/, .private/, etc. — to one container path, instead of one volume + one -directory per label. Add a new subfolder on the host later and it shows up without recreating the container, since discovery happens fresh on every request.

docker build -t tethys .
docker run -d -p 8080:8080 \
  -v tethys-data:/data \
  -v /path/to/your/media:/media \
  -e TETHYS_TOKEN=mysecret \
  tethys serve -data /data -directories /media

(the trailing command overrides the Dockerfile's default CMD entirely, so -data /data has to be repeated here — otherwise the mounted volume goes unused and state lives in the container's ephemeral filesystem instead.)

If your directories genuinely live in separate places on the host (not under one common parent), use -directory/-D instead — one -v + -directory label:/container/path per directory. Both flags work together if you need some explicit and some auto-discovered.

With Compose — copy docker-compose.yml, add a .env file next to it:

TETHYS_TOKEN=some-long-random-string
MEDIA_DIR=/path/to/your/media

then:

docker compose up -d

docker-compose.yml mounts $MEDIA_DIR to /media and passes -directories /media — arrange $MEDIA_DIR as movies/, shows/, .private/, etc. and each becomes its own label automatically. Edit it to add explicit -directory mounts instead/as well if you need directories that don't share a parent.

What's intentionally different from the original

  • No hardcoded personal directories, hostnames, device UUIDs, or file paths anywhere — everything is a flag, environment variable, or config file entry with no default tied to one person's machine.
  • No fixed -downloads root with a hardcoded personal category list (folder1, folder2, eskalaator, etanker, nsfw). Auto-discovery is back (-directories/-R), but generalized: any parent you point it at, not one baked-in path, and it now also composes with explicit -directory/-D roots that don't need to share that (or any) parent.
  • No trakt/"watched" integration (was out of scope for this rewrite).
  • OpenDB auto-creates a fresh tethysdb.json if none exists, rather than requiring one to be seeded by hand — needed for a first run against an empty Docker volume to just work. It's also no longer dotfile-hidden.

Tag summary

Content type

Image

Digest

sha256:39b16459c

Size

6.1 MB

Last updated

25 days ago

docker pull tanel/tethys