15 KiB
Meta-Stremio
Stremio addon for the MetaMesh stack. Reads file metadata from meta-core's HTTP API (no direct Redis access), reads files via meta-core's WebDAV (or a direct mount), and serves HLS with on-the-fly FFmpeg transcoding plus the standard Stremio addon protocol.
External port in the dev stack: 8182 (auth-gated, via Caddy + nginx-hash-lock). Internal container port is 7000. Debug-direct port (bypasses the perimeter): 18182. Container name: metastremio-app (backend) / metastremio (hash-lock proxy).
Overview
┌─────────────────────────────────────────────────────────────────────┐
│ MetaMesh Ecosystem │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ meta-sort │ │ meta-stremio │ │ meta-fuse │ │
│ │ [write meta] │ │ [read meta] │ │ [read meta] │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ meta-core (owns Redis; /urls + /meta APIs) │ │
│ │ located over UDP multicast (beacon v2) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ File access via WebDAV (or direct mount) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Features
- HLS transcoding — adaptive preset+CRF targeting a 60–80% transcode ratio, prefetch of N segments ahead, persistent segment cache.
- Stremio protocol — manifest, catalog, meta, stream; one HLS stream per audio track; subtitles extracted to VTT.
- Direct file serving — range-request capable, for clients that can play the source directly.
- meta-core over HTTP —
LeaderStoragelocates meta-core via beacon v2 (UDP multicast239.255.99.1:9099; seebeacon-v2.md) or a pinnedMETA_CORE_URL, reads every record through meta-core's/meta/*API, and keeps its cache fresh from the SSE meta-stream. The WebDAV URL is taken from meta-core's announce//urls— nothing is configured by hand and no/meta-corevolume is mounted. (RedisStorageremains insrc/storage/as a legacy, unused backend; meta-core no longer publishes a Redis URL.) - Neighbour nav — this service announces itself on the discovery group and serves its own neighbour map at
/api/neighborsfor the dashboard's shared<meta-service-menu>. - Path-token gate for addon URLs — when
HASH_API_SEEDis set, addon paths must be prefixed with/s/<token>. Dashboard/browser paths are protected separately by the upstream hash-lock + OIDC sidecar (Stremio clients can't carry cookies, so they get the path-token instead). - Language-configurable manifest —
displayLanguagein the per-install config (base64-in-path) selects whichtitlestranslation meta-sort's TMDB plugin populated.
Installation
Docker (in the MetaMesh dev stack)
cd dev
./scripts/start.sh
This brings up metastremio-app on port 18182 (debug-direct) and the auth-gated entry on https://metastremio-dev.localhost:8182.
Docker (standalone)
docker build -t meta-stremio packages/meta-stremio
# Against an existing meta-core (found over UDP on the same Docker network,
# or pinned with META_CORE_URL)
docker run -d \
-p 8182:7000 \
-v /path/to/media:/files:ro \
-e STORAGE_MODE=direct \
-e META_CORE_URL=http://metacore-app:9000 \
meta-stremio
The container always listens on port 7000 internally — map it to whatever you want externally. The image bundles a meta-core binary (copied from ghcr.io/worph/meta-core, overridable with --build-arg META_CORE_IMAGE=…): with the image default STORAGE_MODE=leader, docker/start.sh starts it as an in-container sidecar first; any other value (the dev stack and the CasaOS app use direct) skips it and uses an external meta-core. Published images: ghcr.io/worph/meta-stremio (semver tags from v* git tags; in this repo :latest follows main — see .github/workflows/docker-publish.yml); the CasaOS app is MetaStremio in MetaAppStore.
Standalone (development)
# Requires Python 3.9+ and FFmpeg.
pip install -r requirements.txt # redis, watchdog, Pillow, requests
cd src
python server.py # finds meta-core over UDP, or set META_CORE_URL=<meta-core API URL>
Configuration
Environment variables
| Variable | Default | Description |
|---|---|---|
PORT |
7000 |
Internal HTTP listen port (the dev stack maps 18182 / 8182 to this). |
BASE_URL |
— | External URL emitted in manifests / poster URLs (e.g. https://metastremio-dev.localhost:8182). |
MEDIA_DIR |
/files/watch |
Media directory (consumed by transcoder.py). |
CACHE_DIR |
/data/cache |
Transcoded segment cache. |
PUBLIC_URL |
— | Browser-facing URL announced to neighbours (nav menu); wins over BASE_URL for the announce. |
STORAGE_MODE |
leader (image) |
Read only by docker/start.sh: leader starts the bundled meta-core sidecar in-container, anything else (e.g. direct) skips it. The Python server always uses LeaderStorage. |
META_CORE_URL |
— | Pins meta-core's HTTP API (e.g. http://metacore-app:9000); when set, UDP discovery never overrides it. Unset → meta-core is located over UDP. |
ENABLE_UDP_DISCOVERY |
true |
beacon v2 announce/listen. |
LEADER_WAIT_TIMEOUT |
0 |
Seconds to wait for meta-core at startup (0 = forever); on timeout, keeps retrying in the background. |
LEADER_RETRY_INTERVAL |
5 |
Seconds between meta-core lookup attempts. |
FILES_PATH |
/files |
Files volume (local fallback when WebDAV is not configured). |
SCHEME |
auto |
URL scheme for generated URLs: http, https, or auto. |
SEGMENT_DURATION |
4 |
HLS segment length (s). |
PREFETCH_SEGMENTS |
4 |
Segments to prefetch ahead. |
HASH_API_SEED |
— | When set, derives a 16-char path-token; all addon routes must be prefixed with /s/<token>. Dashboard routes bypass. |
META_CORE_PATH, REDIS_URL and REDIS_PREFIX are still set by the Dockerfile but are vestigial: meta-core is no longer found through the /meta-core volume, and reads go over meta-core's HTTP API rather than Redis.
API endpoints
All routes below are matched in src/server.py. When HASH_API_SEED is set, every addon path is prefixed with /s/<token>; the dashboard paths in the first table are not prefixed (they're protected by hash-lock/OIDC upstream).
Dashboard / browser
| Endpoint | Method | Description |
|---|---|---|
/ , /index.html |
GET | Setup dashboard |
/configure |
GET | Per-install language configuration page |
/health |
GET | Liveness + storage status |
/api/stats |
GET | Library statistics |
/api/library |
GET | Full library list (videos + count) |
/api/neighbors |
GET | This peer's UDP neighbour map (beacon v2) for the nav menu |
/api/services |
GET | Alias of /api/neighbors (kept for older dashboards) |
/api/languages |
GET | Languages selectable from the /configure page |
/meta-service-menu.js |
GET | Shared <meta-service-menu> nav element loaded by the dashboard |
/transcode/metrics |
GET | Transcoder metrics (also listed below; exempt from the path-token) |
Stremio addon protocol
/manifest.json and /stremio/manifest.json are aliases; same for catalog/meta/stream. Stremio per-install configuration is encoded as URL-safe base64 JSON between the /s/<token> prefix and the addon path (e.g. /s/<token>/<configB64>/manifest.json).
| Endpoint | Method | Description |
|---|---|---|
/manifest.json |
GET, HEAD | Addon manifest |
/stremio/manifest.json |
GET, HEAD | Alias of /manifest.json |
/catalog/:type/:id.json |
GET | Catalog (with optional …/:extra.json) |
/stremio/catalog/:type/:id.json |
GET | Alias |
/meta/:type/:id.json |
GET | Video metadata |
/stremio/meta/:type/:id.json |
GET | Alias |
/stream/:type/:id.json |
GET | Stream URLs |
/stremio/stream/:type/:id.json |
GET | Alias |
File serving
| Endpoint | Method | Description |
|---|---|---|
/file/{cid} |
GET, HEAD | Serve file by CID |
/file/{cid}/w{width} |
GET, HEAD | Serve resized image by CID (uses Pillow) |
/poster/{cid} |
GET, HEAD | Legacy alias of /file/{cid} |
/poster/{cid}/w{width} |
GET, HEAD | Legacy alias of /file/{cid}/w{width} |
/direct/{path} |
GET, HEAD | Direct file serving with HTTP Range support |
Transcoder
| Endpoint | Method | Description |
|---|---|---|
/transcode/{path}/master.m3u8 |
GET, HEAD | ABR master playlist |
/transcode/{path}/master_{resolution}.m3u8 |
GET, HEAD | Master playlist for a single resolution |
/transcode/{path}/master_a{audio}.m3u8 |
GET, HEAD | Master playlist for a single audio track |
/transcode/{path}/master_{resolution}_a{audio}.m3u8 |
GET, HEAD | Combined variant |
/transcode/{path}/stream_a{audio}_{resolution}.m3u8 |
GET, HEAD | Per-variant stream playlist |
/transcode/{path}/seg_a{audio}_{resolution}_{n}.ts |
GET, HEAD | Video segment |
/transcode/{path}/subtitle_{idx}.m3u8 |
GET, HEAD | Subtitle playlist |
/transcode/{path}/subtitle_{idx}.vtt |
GET, HEAD | Subtitle VTT |
/transcode/metrics |
GET | Transcoder metrics (includes total_files) |
/transcode/reset-metrics |
POST | Reset transcoder metrics |
Project structure
meta-stremio/
├── README.md
├── Dockerfile
├── docker-compose.yml
├── docker/ # container entry scripts
├── requirements.txt # redis, watchdog, Pillow, requests
├── src/
│ ├── server.py # HTTP server + routing (entry point)
│ ├── stremio.py # Stremio handlers + library/language helpers
│ ├── transcoder.py # HLS transcoding engine + metrics + segment/subtitle managers
│ ├── fileserver.py # serve_file(cid, width) — file/poster endpoints
│ ├── poster.py # poster URL/CID helpers
│ ├── webdav_client.py # WebDAV client (file_exists, stream_file, stream_range, get_file_size)
│ └── storage/
│ ├── __init__.py
│ ├── provider.py # StorageProvider + VideoMetadata abstract base
│ ├── leader_storage.py # Storage backend in use: meta-core HTTP API + SSE cache invalidation
│ ├── leader_client.py # Locates meta-core (UDP or META_CORE_URL pin), exposes its /urls set
│ ├── meshdisco.py # beacon v2 (Python port of the Go reference in meta-core)
│ ├── meta_consumer.py # SSE consumer from meta-core /meta stream
│ ├── meta_core_api_client.py # HTTP client for meta-core REST API
│ └── redis_storage.py # Legacy direct-Redis backend (unused)
└── www/
├── index.html # Dashboard
└── meta-service-menu.js # Shared nav element
The /configure page is rendered inline by server.py (there is no www/configure.html).
KV store schema
meta-stremio reads the per-file record /file/{cid} (via meta-core's API) populated by meta-sort and its plugins. Field semantics (videoType, originalTitle, titles, season, episode, movieYear, cid_*, etc.) are documented in the repo-root METADATA_KEYS.md — that is the single source of truth, and matches the @metazla/meta-interface types.
Stream types
For each video, the addon advertises multiple streams:
- Direct file — original bytes, served via
/direct/...withRangesupport. Best quality, may not play on every device. - HLS original — transcoded at source resolution, H.264/AAC, one stream per audio track.
- HLS ABR — adaptive ladder (rungs above the source height are skipped). Presets are baked into
transcoder.py: video is x264 CRF-based (the adaptive controller shifts preset and CRF offset at runtime), audio is AAC 128 kbps stereo.BANDWIDTHis only the hint written to the master playlist.
| Rung | Base CRF | Master-playlist BANDWIDTH hint |
|---|---|---|
| original | 23 | — |
| 1080p | 23 | 4000 kbps |
| 720p | 24 | 2500 kbps |
| 480p | 25 | 1200 kbps |
| 360p | 26 | 800 kbps |
Adding to Stremio
- Open the dashboard at
https://metastremio-dev.localhost:8182/(or debug-directhttp://localhost:18182/). - Pick your display language on
/configureif you want non-English titles. - Use the Install in Stremio button (uses the
stremio://protocol handler) or copy the manifest URL into Stremio: Settings → Addons → Add Addon.
Debugging
# Container logs
docker compose -f dev/docker-compose.yml logs -f metastremio-app
# Live health
curl -k https://metastremio-dev.localhost:8182/health
curl http://localhost:18182/health # debug-direct (no auth, no TLS)
# Transcoder metrics
curl http://localhost:18182/transcode/metrics
curl -X POST http://localhost:18182/transcode/reset-metrics
Container lifecycle: do not docker restart metastremio-app to apply config — use the dev-stack reload script (./scripts/reload-stremio.sh) so supervisord restarts the right process inside the container.
Related projects
- meta-sort — file indexer; writes the metadata meta-stremio reads.
- meta-core — owns Redis; the HTTP API + WebDAV meta-stremio reads from.
- meta-fuse — virtual filesystem; reads the same records.
- meta-watch — browser video client for the meta-share network (server-side zero media work, unlike this addon's transcoder).
License
MIT