cinebox 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cinebox-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,335 @@
1
+ Metadata-Version: 2.4
2
+ Name: cinebox
3
+ Version: 0.1.0
4
+ Summary: Terminal tool to search, stream, and download movies & TV shows
5
+ Keywords: movie,streaming,hls,mpv,vlc,imdb,cli,terminal,tv,video
6
+ Author: Hossam Gamal
7
+ Author-email: Hossam Gamal <hosamgamal33345@gmail.com>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: End Users/Desktop
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Multimedia :: Video
20
+ Classifier: Topic :: Terminals
21
+ Requires-Dist: httpx>=0.27
22
+ Requires-Dist: rich>=13.0
23
+ Requires-Dist: click>=8.1
24
+ Requires-Dist: pydantic>=2.0
25
+ Requires-Dist: pillow>=10.0
26
+ Requires-Dist: numpy>=1.24
27
+ Requires-Dist: pypresence>=4.5 ; extra == 'discord'
28
+ Requires-Dist: pillow>=10.0 ; extra == 'posters'
29
+ Requires-Dist: numpy>=1.24 ; extra == 'posters'
30
+ Requires-Python: >=3.11
31
+ Project-URL: Homepage, https://github.com/hosam00/cinebox
32
+ Project-URL: Repository, https://github.com/hosam00/cinebox
33
+ Project-URL: Issues, https://github.com/hosam00/cinebox/issues
34
+ Project-URL: Documentation, https://github.com/hosam00/cinebox#readme
35
+ Provides-Extra: discord
36
+ Provides-Extra: posters
37
+ Description-Content-Type: text/markdown
38
+
39
+ <div align="center">
40
+
41
+ # 🎬 cinebox
42
+
43
+ **A terminal-based movie streaming tool. Like ani-cli, but for cinema.**
44
+
45
+ [![Python](https://img.shields.io/badge/Python-3.11%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/)
46
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
47
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
48
+ [![tests](https://img.shields.io/badge/tests-126%20passing-brightgreen)]()
49
+ [![PyPI version](https://img.shields.io/pypi/v/cinebox?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/cinebox/)
50
+
51
+ </div>
52
+
53
+ <p align="center">
54
+ <em>record with vhs / asciinema — replace <code>docs/demo.gif</code> with your recording</em>
55
+ </p>
56
+
57
+ <p align="center">
58
+ <img src="docs/demo.gif" alt="cinebox demo — search, stream, and play in the terminal" width="700" />
59
+ </p>
60
+
61
+ A terminal-based tool to search, stream, and download movies and TV shows — inspired by [ani-cli](https://github.com/pystardust/ani-cli), built in Python with a rich interactive TUI and a provider-based streaming architecture.
62
+
63
+ Watch in **mpv** or **VLC**, download via **yt-dlp**, fetch **subtitles** — all without a single API key.
64
+
65
+ ```
66
+ ┌─────────┐ Rich TUI ┌──────────┐ resolve ┌──────────────┐ play ┌───────┐
67
+ │ IMDb │ ─────────────▶│ cinebox │ ─────────────▶│ provider │──────────▶│ mpv │
68
+ │ suggest │ └──────────┘ │ (imdb_su · │ │ vlc │
69
+ └─────────┘ │ │ vidsrc · …) │ └───────┘
70
+ └──▶ yt-dlp ───▶ .mp4/.mkv (download)
71
+ ```
72
+
73
+ ## ✨ Features
74
+
75
+ - **No API keys, no accounts** — search runs against IMDb's public suggestion endpoint.
76
+ - **Rich TUI** — arrow-key navigation, themed panels, full-screen menus, ANSI poster art.
77
+ - **Stream or download** movies and TV episodes via a provider fallback chain.
78
+ - **Batch download** — mark multiple TV episodes and grab them all at once.
79
+ - **Subtitles** — anonymous fetching for movies and TV (Arabic + more via TUI settings).
80
+ - **History & Favorites** — relaunch titles from a watch list or save them for later.
81
+ - **Settings menu** — configure player, download paths, quality, theme, and subtitles from the TUI.
82
+ - **17 color themes** — blue, red, purple, sunset, mint, coral, and more.
83
+ - **Discord Rich Presence** — now-playing status (optional, needs a client ID).
84
+ - **Headless-friendly** — numbered menu fallback for piped/SSH sessions.
85
+ - **Open to extensions** — drop a new provider file into `providers/` and it registers itself automatically.
86
+
87
+ ## 📦 Requirements
88
+
89
+ - Python 3.11+
90
+ - A media player: **mpv** (default) or **vlc**
91
+ - `yt-dlp` + `ffmpeg` on your `PATH` for downloads
92
+ - Optional: `pypresence` for Discord Rich Presence
93
+
94
+ ## 🚀 Installation
95
+
96
+ ### PyPI (recommended)
97
+
98
+ ```bash
99
+ pipx install cinebox # cleanest: isolated tool install
100
+ uv tool install cinebox # same thing, uv-managed
101
+ pip install cinebox # or plain pip
102
+ ```
103
+
104
+ > **`pipx`** keeps cinebox and its dependencies in an isolated environment
105
+ > so they never clash with your other Python packages. Install `pipx` with
106
+ > `pipx --help` → `pipx install pipx` if you don't have it yet.
107
+
108
+ ### Local checkout (development)
109
+
110
+ ```bash
111
+ git clone https://github.com/hosam00/cinebox
112
+ cd cinebox
113
+ uv sync --group dev # create .venv + install all deps
114
+ uv run cinebox --help # verify
115
+ ```
116
+
117
+ ## 🎮 Quick start
118
+
119
+ ### Interactive home screen (TUI)
120
+
121
+ ```bash
122
+ cinebox
123
+ ```
124
+
125
+ Type a query to search, or use the home keys below. Everything else is driven
126
+ by arrow keys, `Enter`, `D` for download, `S` for settings.
127
+
128
+ ### Stream directly by IMDb ID
129
+
130
+ ```bash
131
+ cinebox play tt0133093 # The Matrix (1999)
132
+ cinebox play tt0944947 -t tv -s 1 -e 1 # GoT S01E01
133
+ ```
134
+
135
+ ### Download
136
+
137
+ ```bash
138
+ cinebox download "the matrix" -q 720p
139
+ cinebox download "breaking bad" -t tv
140
+ ```
141
+
142
+ ### Headless search
143
+
144
+ ```bash
145
+ echo 1 | cinebox search "inception" -t movie
146
+ ```
147
+
148
+ ## ⌨️ Key bindings
149
+
150
+ | Key | Context | Action |
151
+ |-----------|----------------|------------------------------------------|
152
+ | `↑` / `↓` | All menus | Navigate |
153
+ | `Enter` | Results | Play selected |
154
+ | `Enter` | Quality menu | Watch |
155
+ | `D` | Quality menu | Download at highlighted quality |
156
+ | `D` | Episode picker | Download marked (or highlighted) |
157
+ | `Space` | Episode picker | Toggle mark for batch download |
158
+ | `F` | Search results | Toggle favorite |
159
+ | `b` | Any menu | Back |
160
+ | `q` | Any menu | Quit |
161
+
162
+ ## ⚙️ Configuration
163
+
164
+ First run creates `~/.config/cinebox/config.toml`. Notable options:
165
+
166
+ ```toml
167
+ [player]
168
+ default = "mpv" # mpv | vlc
169
+ fullscreen = true
170
+ extra_args = [] # extra args passed to the player (e.g. ["--mute"])
171
+
172
+ [download]
173
+ movies_dir = "~/Movies"
174
+ tv_dir = "~/Movies/TV"
175
+ quality = "1080p"
176
+ parallel_fragments = 3 # 1–16 concurrent download segments
177
+
178
+ [ui]
179
+ theme = "blue" # one of 17 themes
180
+
181
+ [discord]
182
+ enabled = false # requires a registered client ID
183
+ client_id = ""
184
+
185
+ [subtitle]
186
+ enabled = true
187
+ source = "auto" # auto | provider | yifysubtitles | subsource
188
+ default_lang = "en"
189
+ languages = ["en", "ar"]
190
+ auto_download = false # auto-fetch subs on watch (adds latency)
191
+ ```
192
+
193
+ Any unset key falls back to its default. Everything above the `[discord]`
194
+ section is also editable from the TUI settings menu (`S`).
195
+
196
+ ## 🔧 How it works
197
+
198
+ 1. **Search** — `GET https://v3.sg.media-imdb.com/suggestion/{char}/{query}.json`
199
+ returns matching titles with IMDb IDs. No auth. Keyword-based providers
200
+ (e.g. Stardima) are merged automatically when present in the provider chain.
201
+ 2. **Resolve** — providers turn an IMDb ID into a playable HLS/MP4 URL:
202
+ - `imdb_su` (primary) — follows the embed player chain to the `vaplayer.ru`
203
+ stream API; headers are spoofed so the CDN serves child manifests.
204
+ - `vidsrc` (best-effort) — `vidsrc.to` → `vsembed.ru` → vidsrc data API.
205
+ Encrypted results that need a browser are skipped.
206
+ - `multiembed` (best-effort) — currently Turnstile-gated and skipped.
207
+ Providers are **auto-discovered** when dropped into `src/cinebox/providers/`.
208
+ Set `priority` on your class to control chain order (lower runs first).
209
+ 3. **Play** — mpv/VLC receive the URL plus the required `Referer`/`Origin`/
210
+ `User-Agent` headers, so the stream actually plays.
211
+ 4. **Download** — the same resolved URL is handed to `yt-dlp` (optionally with
212
+ subtitle muxing) and saved under your configured movies/TV directories.
213
+
214
+ Providers are tried in order; if one fails, the next takes over. Some providers
215
+ can vanish or change overnight — resolution is best-effort by design.
216
+
217
+ ## 🗂️ Project layout
218
+
219
+ ```
220
+ src/cinebox/
221
+ ├── __init__.py click CLI + TUI orchestration (search / play / download)
222
+ ├── config.py TOML config: defaults, paths, endpoints
223
+ ├── search.py IMDb suggestion endpoint + merged search_all()
224
+ ├── models.py pydantic models (SearchResult, StreamInfo, MediaType)
225
+ ├── metadata.py best-effort IMDb title-page enrichment (cached)
226
+ ├── history.py watch history persistence
227
+ ├── favorites.py favorites persistence
228
+ ├── settings.py interactive settings catalog + editor
229
+ ├── discord_rpc.py optional Discord Rich Presence
230
+ ├── utils.py logging, HTTP client, retry helpers
231
+ ├── player.py mpv / VLC launch command builders
232
+ ├── downloader.py yt-dlp wrapper + quality mapping
233
+ ├── subtitles.py anonymous subtitle fetch (movies + TV)
234
+ ├── ui/
235
+ │ ├── themes.py 17 color themes
236
+ │ ├── art.py ASCII header + goodbye art
237
+ │ ├── terminal.py raw terminal I/O helpers
238
+ │ ├── components.py menus, loading, episode picker, error panel, layout
239
+ │ └── poster.py ANSI truecolor poster rendering (Pillow + numpy)
240
+ └── providers/
241
+ ├── base.py BaseProvider, ProviderRegistry, ResolutionError
242
+ ├── discovery.py auto-discovery of concrete providers
243
+ ├── keyword.py KeywordProvider ABC for keyword-search platforms
244
+ ├── imdb_su.py primary provider (vaplayer stream API)
245
+ ├── vidsrc.py best-effort fallback
246
+ └── multiembed.py best-effort fallback (Turnstile-gated)
247
+ ```
248
+
249
+ ## 🧪 Development
250
+
251
+ ```bash
252
+ uv sync --group dev
253
+ uv run pytest # 126 hermetic tests, no network
254
+ uv run ruff check src tests
255
+ ```
256
+
257
+ To run the tool headlessly against the live network while developing:
258
+
259
+ ```bash
260
+ env HOME=/tmp/fakehome uv run cinebox search "the matrix" -t movie
261
+ ```
262
+
263
+ ### Adding a new provider
264
+
265
+ Drop a Python file into `src/cinebox/providers/` (e.g. `stardima.py`):
266
+
267
+ ```python
268
+ from __future__ import annotations
269
+ from cinebox.providers.base import BaseProvider
270
+ from cinebox.models import MediaType, StreamInfo
271
+
272
+ class StardiMaProvider(BaseProvider):
273
+ name = "stardima"
274
+ priority = 40 # lower number = runs first in the chain
275
+
276
+ def resolve(self, imdb_id: str, media_type: MediaType,
277
+ season: int | None = None, episode: int | None = None) -> StreamInfo:
278
+ # ... fetch the stream URL from stardima ...
279
+ ...
280
+ ```
281
+
282
+ That's it — **no registry edits required**. `discovery.py` picks up any
283
+ concrete `BaseProvider` subclass in the package and adds it to the chain.
284
+ Set `priority` to control position (default `100`; `imdb_su` is `10`,
285
+ `vidsrc` `20`, `multiembed` `30`).
286
+
287
+ #### Keyword-search platforms (e.g. Stardima)
288
+
289
+ If your source resolves streams by raw keywords rather than IMDb IDs,
290
+ subclass `KeywordProvider` instead:
291
+
292
+ ```python
293
+ from cinebox.providers.keyword import KeywordProvider
294
+ from cinebox.models import SearchResult
295
+
296
+ class StardiMaProvider(KeywordProvider):
297
+ scheme = "stardima"
298
+ priority = 40
299
+
300
+ def search(self, query, media_type=None):
301
+ # return list[SearchResult] — use self.synthetic_id("12345") for
302
+ # IDs the platform can't map back to IMDb
303
+ ...
304
+
305
+ def resolve(self, imdb_id, media_type, season=None, episode=None):
306
+ # imdb_id may be synthetic: "stardima:12345"
307
+ ...
308
+ ```
309
+
310
+ Keyword providers are merged into search results automatically alongside
311
+ IMDb suggestions. See `src/cinebox/providers/keyword.py` for the full interface.
312
+
313
+ ## ✅ Roadmap
314
+
315
+ - [x] PyPI release via Trusted Publishing
316
+ - [x] Keyword-search provider interface (Stardima-ready)
317
+ - [x] Auto-discovery for drop-in providers
318
+ - [ ] `fzf` integration / fuzzy search over history & favorites
319
+ - [ ] More subtitle sources (incl. forced/embedded options)
320
+ - [ ] Provider config: per-provider enable/disable + health checks
321
+ - [ ] A full Stardima keyword provider (PR welcome)
322
+
323
+ ## ⚠️ Disclaimer
324
+
325
+ For content you have the right to consume. The tool indexes publicly
326
+ accessible streams and hosts no media. Providers and their CDNs may change or
327
+ break at any time — use at your own risk, and always respect copyright.
328
+
329
+ Read the full [MIT license](LICENSE).
330
+
331
+ ---
332
+
333
+ <p align="center">
334
+ <sub>Made with ❤️· Rich · yt-dlp · Python</sub>
335
+ </p>
@@ -0,0 +1,297 @@
1
+ <div align="center">
2
+
3
+ # 🎬 cinebox
4
+
5
+ **A terminal-based movie streaming tool. Like ani-cli, but for cinema.**
6
+
7
+ [![Python](https://img.shields.io/badge/Python-3.11%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/)
8
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
9
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
10
+ [![tests](https://img.shields.io/badge/tests-126%20passing-brightgreen)]()
11
+ [![PyPI version](https://img.shields.io/pypi/v/cinebox?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/cinebox/)
12
+
13
+ </div>
14
+
15
+ <p align="center">
16
+ <em>record with vhs / asciinema — replace <code>docs/demo.gif</code> with your recording</em>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <img src="docs/demo.gif" alt="cinebox demo — search, stream, and play in the terminal" width="700" />
21
+ </p>
22
+
23
+ A terminal-based tool to search, stream, and download movies and TV shows — inspired by [ani-cli](https://github.com/pystardust/ani-cli), built in Python with a rich interactive TUI and a provider-based streaming architecture.
24
+
25
+ Watch in **mpv** or **VLC**, download via **yt-dlp**, fetch **subtitles** — all without a single API key.
26
+
27
+ ```
28
+ ┌─────────┐ Rich TUI ┌──────────┐ resolve ┌──────────────┐ play ┌───────┐
29
+ │ IMDb │ ─────────────▶│ cinebox │ ─────────────▶│ provider │──────────▶│ mpv │
30
+ │ suggest │ └──────────┘ │ (imdb_su · │ │ vlc │
31
+ └─────────┘ │ │ vidsrc · …) │ └───────┘
32
+ └──▶ yt-dlp ───▶ .mp4/.mkv (download)
33
+ ```
34
+
35
+ ## ✨ Features
36
+
37
+ - **No API keys, no accounts** — search runs against IMDb's public suggestion endpoint.
38
+ - **Rich TUI** — arrow-key navigation, themed panels, full-screen menus, ANSI poster art.
39
+ - **Stream or download** movies and TV episodes via a provider fallback chain.
40
+ - **Batch download** — mark multiple TV episodes and grab them all at once.
41
+ - **Subtitles** — anonymous fetching for movies and TV (Arabic + more via TUI settings).
42
+ - **History & Favorites** — relaunch titles from a watch list or save them for later.
43
+ - **Settings menu** — configure player, download paths, quality, theme, and subtitles from the TUI.
44
+ - **17 color themes** — blue, red, purple, sunset, mint, coral, and more.
45
+ - **Discord Rich Presence** — now-playing status (optional, needs a client ID).
46
+ - **Headless-friendly** — numbered menu fallback for piped/SSH sessions.
47
+ - **Open to extensions** — drop a new provider file into `providers/` and it registers itself automatically.
48
+
49
+ ## 📦 Requirements
50
+
51
+ - Python 3.11+
52
+ - A media player: **mpv** (default) or **vlc**
53
+ - `yt-dlp` + `ffmpeg` on your `PATH` for downloads
54
+ - Optional: `pypresence` for Discord Rich Presence
55
+
56
+ ## 🚀 Installation
57
+
58
+ ### PyPI (recommended)
59
+
60
+ ```bash
61
+ pipx install cinebox # cleanest: isolated tool install
62
+ uv tool install cinebox # same thing, uv-managed
63
+ pip install cinebox # or plain pip
64
+ ```
65
+
66
+ > **`pipx`** keeps cinebox and its dependencies in an isolated environment
67
+ > so they never clash with your other Python packages. Install `pipx` with
68
+ > `pipx --help` → `pipx install pipx` if you don't have it yet.
69
+
70
+ ### Local checkout (development)
71
+
72
+ ```bash
73
+ git clone https://github.com/hosam00/cinebox
74
+ cd cinebox
75
+ uv sync --group dev # create .venv + install all deps
76
+ uv run cinebox --help # verify
77
+ ```
78
+
79
+ ## 🎮 Quick start
80
+
81
+ ### Interactive home screen (TUI)
82
+
83
+ ```bash
84
+ cinebox
85
+ ```
86
+
87
+ Type a query to search, or use the home keys below. Everything else is driven
88
+ by arrow keys, `Enter`, `D` for download, `S` for settings.
89
+
90
+ ### Stream directly by IMDb ID
91
+
92
+ ```bash
93
+ cinebox play tt0133093 # The Matrix (1999)
94
+ cinebox play tt0944947 -t tv -s 1 -e 1 # GoT S01E01
95
+ ```
96
+
97
+ ### Download
98
+
99
+ ```bash
100
+ cinebox download "the matrix" -q 720p
101
+ cinebox download "breaking bad" -t tv
102
+ ```
103
+
104
+ ### Headless search
105
+
106
+ ```bash
107
+ echo 1 | cinebox search "inception" -t movie
108
+ ```
109
+
110
+ ## ⌨️ Key bindings
111
+
112
+ | Key | Context | Action |
113
+ |-----------|----------------|------------------------------------------|
114
+ | `↑` / `↓` | All menus | Navigate |
115
+ | `Enter` | Results | Play selected |
116
+ | `Enter` | Quality menu | Watch |
117
+ | `D` | Quality menu | Download at highlighted quality |
118
+ | `D` | Episode picker | Download marked (or highlighted) |
119
+ | `Space` | Episode picker | Toggle mark for batch download |
120
+ | `F` | Search results | Toggle favorite |
121
+ | `b` | Any menu | Back |
122
+ | `q` | Any menu | Quit |
123
+
124
+ ## ⚙️ Configuration
125
+
126
+ First run creates `~/.config/cinebox/config.toml`. Notable options:
127
+
128
+ ```toml
129
+ [player]
130
+ default = "mpv" # mpv | vlc
131
+ fullscreen = true
132
+ extra_args = [] # extra args passed to the player (e.g. ["--mute"])
133
+
134
+ [download]
135
+ movies_dir = "~/Movies"
136
+ tv_dir = "~/Movies/TV"
137
+ quality = "1080p"
138
+ parallel_fragments = 3 # 1–16 concurrent download segments
139
+
140
+ [ui]
141
+ theme = "blue" # one of 17 themes
142
+
143
+ [discord]
144
+ enabled = false # requires a registered client ID
145
+ client_id = ""
146
+
147
+ [subtitle]
148
+ enabled = true
149
+ source = "auto" # auto | provider | yifysubtitles | subsource
150
+ default_lang = "en"
151
+ languages = ["en", "ar"]
152
+ auto_download = false # auto-fetch subs on watch (adds latency)
153
+ ```
154
+
155
+ Any unset key falls back to its default. Everything above the `[discord]`
156
+ section is also editable from the TUI settings menu (`S`).
157
+
158
+ ## 🔧 How it works
159
+
160
+ 1. **Search** — `GET https://v3.sg.media-imdb.com/suggestion/{char}/{query}.json`
161
+ returns matching titles with IMDb IDs. No auth. Keyword-based providers
162
+ (e.g. Stardima) are merged automatically when present in the provider chain.
163
+ 2. **Resolve** — providers turn an IMDb ID into a playable HLS/MP4 URL:
164
+ - `imdb_su` (primary) — follows the embed player chain to the `vaplayer.ru`
165
+ stream API; headers are spoofed so the CDN serves child manifests.
166
+ - `vidsrc` (best-effort) — `vidsrc.to` → `vsembed.ru` → vidsrc data API.
167
+ Encrypted results that need a browser are skipped.
168
+ - `multiembed` (best-effort) — currently Turnstile-gated and skipped.
169
+ Providers are **auto-discovered** when dropped into `src/cinebox/providers/`.
170
+ Set `priority` on your class to control chain order (lower runs first).
171
+ 3. **Play** — mpv/VLC receive the URL plus the required `Referer`/`Origin`/
172
+ `User-Agent` headers, so the stream actually plays.
173
+ 4. **Download** — the same resolved URL is handed to `yt-dlp` (optionally with
174
+ subtitle muxing) and saved under your configured movies/TV directories.
175
+
176
+ Providers are tried in order; if one fails, the next takes over. Some providers
177
+ can vanish or change overnight — resolution is best-effort by design.
178
+
179
+ ## 🗂️ Project layout
180
+
181
+ ```
182
+ src/cinebox/
183
+ ├── __init__.py click CLI + TUI orchestration (search / play / download)
184
+ ├── config.py TOML config: defaults, paths, endpoints
185
+ ├── search.py IMDb suggestion endpoint + merged search_all()
186
+ ├── models.py pydantic models (SearchResult, StreamInfo, MediaType)
187
+ ├── metadata.py best-effort IMDb title-page enrichment (cached)
188
+ ├── history.py watch history persistence
189
+ ├── favorites.py favorites persistence
190
+ ├── settings.py interactive settings catalog + editor
191
+ ├── discord_rpc.py optional Discord Rich Presence
192
+ ├── utils.py logging, HTTP client, retry helpers
193
+ ├── player.py mpv / VLC launch command builders
194
+ ├── downloader.py yt-dlp wrapper + quality mapping
195
+ ├── subtitles.py anonymous subtitle fetch (movies + TV)
196
+ ├── ui/
197
+ │ ├── themes.py 17 color themes
198
+ │ ├── art.py ASCII header + goodbye art
199
+ │ ├── terminal.py raw terminal I/O helpers
200
+ │ ├── components.py menus, loading, episode picker, error panel, layout
201
+ │ └── poster.py ANSI truecolor poster rendering (Pillow + numpy)
202
+ └── providers/
203
+ ├── base.py BaseProvider, ProviderRegistry, ResolutionError
204
+ ├── discovery.py auto-discovery of concrete providers
205
+ ├── keyword.py KeywordProvider ABC for keyword-search platforms
206
+ ├── imdb_su.py primary provider (vaplayer stream API)
207
+ ├── vidsrc.py best-effort fallback
208
+ └── multiembed.py best-effort fallback (Turnstile-gated)
209
+ ```
210
+
211
+ ## 🧪 Development
212
+
213
+ ```bash
214
+ uv sync --group dev
215
+ uv run pytest # 126 hermetic tests, no network
216
+ uv run ruff check src tests
217
+ ```
218
+
219
+ To run the tool headlessly against the live network while developing:
220
+
221
+ ```bash
222
+ env HOME=/tmp/fakehome uv run cinebox search "the matrix" -t movie
223
+ ```
224
+
225
+ ### Adding a new provider
226
+
227
+ Drop a Python file into `src/cinebox/providers/` (e.g. `stardima.py`):
228
+
229
+ ```python
230
+ from __future__ import annotations
231
+ from cinebox.providers.base import BaseProvider
232
+ from cinebox.models import MediaType, StreamInfo
233
+
234
+ class StardiMaProvider(BaseProvider):
235
+ name = "stardima"
236
+ priority = 40 # lower number = runs first in the chain
237
+
238
+ def resolve(self, imdb_id: str, media_type: MediaType,
239
+ season: int | None = None, episode: int | None = None) -> StreamInfo:
240
+ # ... fetch the stream URL from stardima ...
241
+ ...
242
+ ```
243
+
244
+ That's it — **no registry edits required**. `discovery.py` picks up any
245
+ concrete `BaseProvider` subclass in the package and adds it to the chain.
246
+ Set `priority` to control position (default `100`; `imdb_su` is `10`,
247
+ `vidsrc` `20`, `multiembed` `30`).
248
+
249
+ #### Keyword-search platforms (e.g. Stardima)
250
+
251
+ If your source resolves streams by raw keywords rather than IMDb IDs,
252
+ subclass `KeywordProvider` instead:
253
+
254
+ ```python
255
+ from cinebox.providers.keyword import KeywordProvider
256
+ from cinebox.models import SearchResult
257
+
258
+ class StardiMaProvider(KeywordProvider):
259
+ scheme = "stardima"
260
+ priority = 40
261
+
262
+ def search(self, query, media_type=None):
263
+ # return list[SearchResult] — use self.synthetic_id("12345") for
264
+ # IDs the platform can't map back to IMDb
265
+ ...
266
+
267
+ def resolve(self, imdb_id, media_type, season=None, episode=None):
268
+ # imdb_id may be synthetic: "stardima:12345"
269
+ ...
270
+ ```
271
+
272
+ Keyword providers are merged into search results automatically alongside
273
+ IMDb suggestions. See `src/cinebox/providers/keyword.py` for the full interface.
274
+
275
+ ## ✅ Roadmap
276
+
277
+ - [x] PyPI release via Trusted Publishing
278
+ - [x] Keyword-search provider interface (Stardima-ready)
279
+ - [x] Auto-discovery for drop-in providers
280
+ - [ ] `fzf` integration / fuzzy search over history & favorites
281
+ - [ ] More subtitle sources (incl. forced/embedded options)
282
+ - [ ] Provider config: per-provider enable/disable + health checks
283
+ - [ ] A full Stardima keyword provider (PR welcome)
284
+
285
+ ## ⚠️ Disclaimer
286
+
287
+ For content you have the right to consume. The tool indexes publicly
288
+ accessible streams and hosts no media. Providers and their CDNs may change or
289
+ break at any time — use at your own risk, and always respect copyright.
290
+
291
+ Read the full [MIT license](LICENSE).
292
+
293
+ ---
294
+
295
+ <p align="center">
296
+ <sub>Made with ❤️· Rich · yt-dlp · Python</sub>
297
+ </p>