Skip to main content

🎬 cinebox

A terminal-based movie streaming tool. Like ani-cli, but for cinema.

Python License Ruff tests PyPI version

record with vhs / asciinema — replace docs/demo.gif with your recording

cinebox demo — search, stream, and play in the terminal

A terminal-based tool to search, stream, and download movies and TV shows — inspired by ani-cli, built in Python with a rich interactive TUI and a provider-based streaming architecture.

Watch in mpv or VLC, download via yt-dlp, fetch subtitles — all without a single API key.

┌─────────┐   Rich TUI    ┌──────────┐    resolve    ┌──────────────┐   play    ┌───────┐
│  IMDb   │ ─────────────▶│ cinebox  │ ─────────────▶│   provider   │──────────▶│  mpv  │
│ suggest │               └──────────┘               │ (imdb_su ·   │          │  vlc  │
└─────────┘                    │                     │  vidsrc · …) │          └───────┘
                               └──▶ yt-dlp ───▶ .mp4/.mkv   (download)

✨ Features

  • No API keys, no accounts — search runs against IMDb's public suggestion endpoint.
  • Rich TUI — arrow-key navigation, themed panels, full-screen menus, ANSI poster art.
  • Stream or download movies and TV episodes via a provider fallback chain.
  • Batch download — mark multiple TV episodes and grab them all at once.
  • Subtitles — anonymous fetching for movies and TV (Arabic + more via TUI settings).
  • History & Favorites — relaunch titles from a watch list or save them for later.
  • Settings menu — configure player, download paths, quality, theme, and subtitles from the TUI.
  • 17 color themes — blue, red, purple, sunset, mint, coral, and more.
  • Discord Rich Presence — now-playing status (optional, needs a client ID).
  • Headless-friendly — numbered menu fallback for piped/SSH sessions.
  • Open to extensions — drop a new provider file into providers/ and it registers itself automatically.

📦 Requirements

  • Python 3.11+
  • A media player: mpv (default) or vlc
  • yt-dlp + ffmpeg on your PATH for downloads
  • Optional: pypresence for Discord Rich Presence

🚀 Installation

pipx install cinebox     # cleanest: isolated tool install
uv tool install cinebox  # same thing, uv-managed
pip install cinebox      # or plain pip

pipx keeps cinebox and its dependencies in an isolated environment so they never clash with your other Python packages. Install pipx with pipx --help → pipx install pipx if you don't have it yet.

Local checkout (development)

git clone https://github.com/hosam00/cinebox
cd cinebox
uv sync --group dev    # create .venv + install all deps
uv run cinebox --help  # verify

🎮 Quick start

Interactive home screen (TUI)

cinebox

Type a query to search, or use the home keys below. Everything else is driven by arrow keys, Enter, D for download, S for settings.

Stream directly by IMDb ID

cinebox play tt0133093                      # The Matrix (1999)
cinebox play tt0944947 -t tv -s 1 -e 1      # GoT S01E01

Download

cinebox download "the matrix" -q 720p
cinebox download "breaking bad" -t tv
echo 1 | cinebox search "inception" -t movie

⌨️ Key bindings

Key Context Action
↑ / ↓ All menus Navigate
Enter Results Play selected
Enter Quality menu Watch
D Quality menu Download at highlighted quality
D Episode picker Download marked (or highlighted)
Space Episode picker Toggle mark for batch download
F Search results Toggle favorite
b Any menu Back
q Any menu Quit

⚙️ Configuration

First run creates ~/.config/cinebox/config.toml. Notable options:

[player]
default = "mpv"        # mpv | vlc
fullscreen = true
extra_args = []        # extra args passed to the player (e.g. ["--mute"])

[download]
movies_dir = "~/Movies"
tv_dir = "~/Movies/TV"
quality = "1080p"
parallel_fragments = 3 # 1–16 concurrent download segments

[ui]
theme = "blue"         # one of 17 themes

[discord]
enabled = false        # requires a registered client ID
client_id = ""

[subtitle]
enabled = true
source = "auto"        # auto | provider | yifysubtitles | subsource
default_lang = "en"
languages = ["en", "ar"]
auto_download = false  # auto-fetch subs on watch (adds latency)

Any unset key falls back to its default. Everything above the [discord] section is also editable from the TUI settings menu (S).

🔧 How it works

  1. Search — GET https://v3.sg.media-imdb.com/suggestion/{char}/{query}.json returns matching titles with IMDb IDs. No auth. Keyword-based providers (e.g. Stardima) are merged automatically when present in the provider chain.
  2. Resolve — providers turn an IMDb ID into a playable HLS/MP4 URL:
    • imdb_su (primary) — follows the embed player chain to the vaplayer.ru stream API; headers are spoofed so the CDN serves child manifests.
    • vidsrc (best-effort) — vidsrc.to → vsembed.ru → vidsrc data API. Encrypted results that need a browser are skipped.
    • multiembed (best-effort) — currently Turnstile-gated and skipped. Providers are auto-discovered when dropped into src/cinebox/providers/. Set priority on your class to control chain order (lower runs first).
  3. Play — mpv/VLC receive the URL plus the required Referer/Origin/ User-Agent headers, so the stream actually plays.
  4. Download — the same resolved URL is handed to yt-dlp (optionally with subtitle muxing) and saved under your configured movies/TV directories.

Providers are tried in order; if one fails, the next takes over. Some providers can vanish or change overnight — resolution is best-effort by design.

🗂️ Project layout

src/cinebox/
├── __init__.py          click CLI + TUI orchestration (search / play / download)
├── config.py            TOML config: defaults, paths, endpoints
├── search.py            IMDb suggestion endpoint + merged search_all()
├── models.py            pydantic models (SearchResult, StreamInfo, MediaType)
├── metadata.py          best-effort IMDb title-page enrichment (cached)
├── history.py           watch history persistence
├── favorites.py         favorites persistence
├── settings.py          interactive settings catalog + editor
├── discord_rpc.py       optional Discord Rich Presence
├── utils.py             logging, HTTP client, retry helpers
├── player.py            mpv / VLC launch command builders
├── downloader.py        yt-dlp wrapper + quality mapping
├── subtitles.py         anonymous subtitle fetch (movies + TV)
├── ui/
│   ├── themes.py        17 color themes
│   ├── art.py           ASCII header + goodbye art
│   ├── terminal.py      raw terminal I/O helpers
│   ├── components.py    menus, loading, episode picker, error panel, layout
│   └── poster.py        ANSI truecolor poster rendering (Pillow + numpy)
└── providers/
    ├── base.py          BaseProvider, ProviderRegistry, ResolutionError
    ├── discovery.py     auto-discovery of concrete providers
    ├── keyword.py       KeywordProvider ABC for keyword-search platforms
    ├── imdb_su.py       primary provider (vaplayer stream API)
    ├── vidsrc.py        best-effort fallback
    └── multiembed.py    best-effort fallback (Turnstile-gated)

🧪 Development

uv sync --group dev
uv run pytest            # 126 hermetic tests, no network
uv run ruff check src tests

To run the tool headlessly against the live network while developing:

env HOME=/tmp/fakehome uv run cinebox search "the matrix" -t movie

Adding a new provider

Drop a Python file into src/cinebox/providers/ (e.g. stardima.py):

from __future__ import annotations
from cinebox.providers.base import BaseProvider
from cinebox.models import MediaType, StreamInfo

class StardiMaProvider(BaseProvider):
    name = "stardima"
    priority = 40          # lower number = runs first in the chain

    def resolve(self, imdb_id: str, media_type: MediaType,
                season: int | None = None, episode: int | None = None) -> StreamInfo:
        # ... fetch the stream URL from stardima ...
        ...

That's it — no registry edits required. discovery.py picks up any concrete BaseProvider subclass in the package and adds it to the chain. Set priority to control position (default 100; imdb_su is 10, vidsrc 20, multiembed 30).

Keyword-search platforms (e.g. Stardima)

If your source resolves streams by raw keywords rather than IMDb IDs, subclass KeywordProvider instead:

from cinebox.providers.keyword import KeywordProvider
from cinebox.models import SearchResult

class StardiMaProvider(KeywordProvider):
    scheme = "stardima"
    priority = 40

    def search(self, query, media_type=None):
        # return list[SearchResult] — use self.synthetic_id("12345") for
        # IDs the platform can't map back to IMDb
        ...

    def resolve(self, imdb_id, media_type, season=None, episode=None):
        # imdb_id may be synthetic: "stardima:12345"
        ...

Keyword providers are merged into search results automatically alongside IMDb suggestions. See src/cinebox/providers/keyword.py for the full interface.

✅ Roadmap

  • PyPI release via Trusted Publishing
  • Keyword-search provider interface (Stardima-ready)
  • Auto-discovery for drop-in providers
  • fzf integration / fuzzy search over history & favorites
  • More subtitle sources (incl. forced/embedded options)
  • Provider config: per-provider enable/disable + health checks
  • A full Stardima keyword provider (PR welcome)

⚠️ Disclaimer

For content you have the right to consume. The tool indexes publicly accessible streams and hosts no media. Providers and their CDNs may change or break at any time — use at your own risk, and always respect copyright.

Read the full MIT license.


Made with ❤️· Rich · yt-dlp · Python

Release files for cinebox 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cinebox 0.1.0
File Size Uploaded
cinebox-0.1.0.tar.gz 47.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cinebox 0.1.0
File Interpreter ABI Platform
cinebox-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 106.1 kB

Release files / cinebox-0.1.0.tar.gz

Download URL cinebox-0.1.0.tar.gz
Size 47.4 kB
Tags Source
SHA-256 checksum
How to use checksums
3a52ed27e1e123db24c227f020570097915a9cf81f0ede9040373ee726a88c26
BLAKE2b-256 checksum
How to use checksums
551a1b7a0e4345335cf749a81f23a7663c61c4fbfaf34d90a974ca7faca71900
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / cinebox-0.1.0-py3-none-any.whl

Download URL cinebox-0.1.0-py3-none-any.whl
Size 58.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6ae23b3800d7eb1249b15d2a101f757f06aadcabe5f3c2c859788ef0438b4bd2
BLAKE2b-256 checksum
How to use checksums
ede3067e11e4beb96c4ab83522d7e18c86d98708692918e268284904d84a8e5d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page