Metadata-Version: 2.4
Name: vidsrc-dlp
Version: 0.12.0
Summary: CLI to search TMDB, resolve VidSrc streams, and download via yt-dlp
Author: Jeevan
License-Expression: MIT
Project-URL: Homepage, https://github.com/tecknet-gg/vidsrc-dlp
Project-URL: Source, https://github.com/tecknet-gg/vidsrc-dlp
Project-URL: BugTracker, https://github.com/tecknet-gg/vidsrc-dlp/issues
Keywords: vidsrc,tmdb,yt-dlp,video,downloader
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: End Users/Desktop
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Video
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: requests>=2.31.0
Requires-Dist: yt-dlp>=2024.0.0
Provides-Extra: playwright
Requires-Dist: playwright>=1.40; extra == "playwright"
Requires-Dist: playwright-stealth>=1.0; extra == "playwright"
Dynamic: license-file

# vidsrc-dlp

**DISCLAIMER:**

**This project was vibecoded (using Opencode), to a fit a niche that I had in my personal Jellyfin setup. The code here is as is, and by using it you accept the risks of running unverified, AI generated code. Maybe I'll rewrite it myself if I get time.**

---

Search TMDB, resolve a playable HLS stream, and download via yt-dlp. Automatically tries multiple providers for the best quality available.

## Install

```bash
pip install vidsrc-dlp
cp .env.example .env  # add your TMDB_API_KEY
```

For 4K quality (requires Playwright):
```bash
pip install vidsrc-dlp[playwright]
playwright install chromium
```

Or for local development:

```bash
git clone https://github.com/tecknet-gg/vidsrc-dlp
cd vidsrc-dlp
python -m venv .venv && source .venv/bin/activate
pip install -e .
cp .env.example .env  # add your TMDB_API_KEY
```

## CLI Reference

### General

| Flag | Type | Default | Description |
|------|------|---------|-------------|
| `query` | `str` | *required* | Movie or TV show title |
| `--type` | `movie` / `tv` | `movie` | Media type |
| `--year` | `int` | — | Filter by release / air year |
| `--quality` | `str` | `1080p` | Target resolution. `best` = highest available (may be very large). E.g. `1080p`, `720p` |
| `--provider` | `auto` / `vidsrc` | `auto` | `auto` tries 15 Playwright sources → Cineby → legacy vidsrc. `vidsrc` uses legacy single-domain |
| `--season` | `int` | — | Season number (required for TV) |
| `--episode` | `int` | — | Episode number (required for TV) |
| `--movies-dir` | `str` | from `.env` | Override movie output directory |
| `--tv-dir` | `str` | from `.env` | Override TV output directory |
| `--no-confirm` | flag | — | Skip download confirmation prompt |
| `--parallel` | `int` | `3` | Download N HLS fragments concurrently (0 = sequential). Speeds up larger downloads |
| `--verbose` | flag | — | Enable debug logging |
| `--verbose-ytdlp` | flag | — | Show yt-dlp output |

### Examples

```bash
# Simple movie download (1080p default, ~6 GiB for 2.5hr)
vidsrc-dlp "Spider-Man: No Way Home"

# 4K quality (requires Playwright, ~18 GiB for 2.5hr)
vidsrc-dlp "Interstellar" --quality best

# Custom quality + skip prompt
vidsrc-dlp "Inception" --quality 720p --no-confirm

# TV episode
vidsrc-dlp "Breaking Bad" --type tv --season 1 --episode 1

# Custom output directories
vidsrc-dlp "Inception" --movies-dir "/Volumes/Media/Movies"
vidsrc-dlp "Breaking Bad" --type tv --season 1 --episode 1 --tv-dir "/Volumes/Media/TV"

# Debug mode
vidsrc-dlp "Inception" --verbose --no-confirm

# Parallel fragment download (default: 3 concurrent)
vidsrc-dlp "Inception"                            # 3 concurrent (default)
vidsrc-dlp "Inception" --parallel 8                # 8 concurrent for faster downloads
vidsrc-dlp "Inception" --parallel 0                # sequential (one fragment at a time)
```

### Quality selection

Default quality is `1080p`, capping downloads at ~6 GiB for a 2.5hr film. Use `--quality best` to select the highest available variant (may be 4K/2160p, ~18 GiB). The detected resolutions are logged:

```
[INFO] Available qualities: 480p, 720p, 1080p, 2160p
[INFO] Format: bestvideo[height<=1080]+bestaudio/best[height<=1080]/best
```

Pass `--quality 720p` or `--quality 480p` to constrain further.

### Provider selection

`--provider auto` (default) tries in order:
1. **Vidsrc.win sources** — 15 Playwright-based embed sources (videasy, vidking, mapple, vidsrcme, vidsrc.su, vidsrc.cc, etc.), up to 4K
2. **Cineby** — requires Playwright, up to 4K@16Mbps
3. **vidsrc** — legacy scrape, up to 800p

If Playwright is not installed, steps 1 and 2 are skipped automatically.

### `.env` configuration

```
TMDB_API_KEY=your_api_key_here
MOVIES_DIR=./downloads/movies
TV_DIR=./downloads/tv
```

CLI flags `--movies-dir` and `--tv-dir` override the `.env` values when provided.

---

## Inline API (Python)

### Public functions

#### `search_movie(query, year=None, api_key=None) → list[Media]`

| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `query` | `str` | — | Movie title to search for |
| `year` | `int` | `None` | Filter by release year |
| `api_key` | `str` | `None` | TMDB key (falls back to `.env`) |

```python
>>> from vidsrc_dlp import search_movie
>>> movies = search_movie("Inception", year=2010)
>>> movies[0].title
'Inception'
```

---

#### `search_tv(query, year=None, api_key=None) → list[Media]`

| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `query` | `str` | — | TV show title to search for |
| `year` | `int` | `None` | Filter by first air year |
| `api_key` | `str` | `None` | TMDB key (falls back to `.env`) |

```python
>>> from vidsrc_dlp import search_tv
>>> shows = search_tv("Breaking Bad")
>>> shows[0].title
'Breaking Bad'
```

---

#### `get_movie_details(tmdb_id, api_key=None) → Media | None`

| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `tmdb_id` | `int` | — | TMDB movie ID |
| `api_key` | `str` | `None` | TMDB key (falls back to `.env`) |

Returns enriched metadata (IMDb ID, genres, rating, poster path).

```python
>>> from vidsrc_dlp import get_movie_details
>>> m = get_movie_details(27205)
>>> m.imdb_id
'tt1375666'
>>> m.genres
['Action', 'Science Fiction', 'Adventure']
>>> m.vote_average
8.4
```

---

#### `get_tv_details(tmdb_id, api_key=None) → Media | None`

| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `tmdb_id` | `int` | — | TMDB TV show ID |
| `api_key` | `str` | `None` | TMDB key (falls back to `.env`) |

```python
>>> from vidsrc_dlp import get_tv_details
>>> show = get_tv_details(1396)
>>> show.imdb_id
'tt0903747'
```

---

#### `resolve(media, season=None, episode=None) → StreamInfo | None`

| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `media` | `Media` | — | Movie or TV show to resolve |
| `season` | `int` | `None` | Season (reads from `media.season`) |
| `episode` | `int` | `None` | Episode (reads from `media.episode`) |

```python
>>> from vidsrc_dlp import search_movie, resolve
>>> movie = search_movie("Inception")[0]
>>> stream = resolve(movie)
>>> stream.url.startswith("http")
True
```

---

#### `download(stream, media, quality="1080p", movies_dir=None, tv_dir=None) → bool`

| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `stream` | `StreamInfo` | — | Stream from `resolve()` |
| `media` | `Media` | — | Movie or TV show metadata |
| `quality` | `str` | `1080p` | Target resolution. `best` = highest. E.g. `1080p`, `720p` |
| `movies_dir` | `str` | from `.env` | Override movie output path |
| `tv_dir` | `str` | from `.env` | Override TV output path |

```python
>>> from vidsrc_dlp import search_movie, resolve, download
>>> movie = search_movie("Inception")[0]
>>> stream = resolve(movie)
>>> download(stream, movie)
True
```

---

### Data types

#### `Media`

| Field | Type | Description |
|-------|------|-------------|
| `id` | `int` | TMDB ID |
| `title` | `str` | Movie or show name |
| `media_type` | `MediaType` | `MOVIE` or `TV` |
| `year` | `int \| None` | Release / air year |
| `release_date` | `str` | Full date string |
| `overview` | `str` | Plot synopsis |
| `season` | `int \| None` | Season number (for TV) |
| `episode` | `int \| None` | Episode number (for TV) |
| `episode_title` | `str \| None` | Episode name |
| `imdb_id` | `str \| None` | IMDb ID (after `get_*_details()`) |
| `genres` | `list[str]` | Genre names |
| `vote_average` | `float \| None` | TMDB rating |
| `poster_path` | `str \| None` | TMDB poster path |

#### `StreamInfo`

| Field | Type | Description |
|-------|------|-------------|
| `url` | `str` | Resolved HLS m3u8 URL |
| `headers` | `dict[str, str]` | HTTP headers for download (`User-Agent`) |
| `referer` | `str` | Referer URL required by CDN |
| `stream_type` | `str` | Always `"hls"` |
| `urls` | `list[str]` | All resolved variant URLs (for quality selection) |

#### `MediaType` (enum)

```
MediaType.MOVIE
MediaType.TV
```

---

### Full workflow example

```python
from vidsrc_dlp import (
    search_movie, get_movie_details, resolve, download,
    search_tv, get_tv_details, MediaType, Media,
)

# === Movie ===
movies = search_movie("Inception", year=2010)      # → list[Media]
movie = get_movie_details(movies[0].id)             # → Media (enriched)
print(movie.imdb_id, movie.genres, movie.year)      # tt1375666, [...]

stream = resolve(movie)                              # → StreamInfo
download(stream, movie, quality="1080p")             # → bool

# === TV ===
shows = search_tv("Breaking Bad")
show = get_tv_details(shows[0].id)
show.season = 1                                       # set before resolve()
show.episode = 1
show.episode_title = "Pilot"

stream = resolve(show)
download(stream, show)

# === Pass api_key inline (skip .env) ===
movies = search_movie("Inception", api_key="your_key")
stream = resolve(movies[0])
download(stream, movies[0], movies_dir="/custom/path")
```

## Architecture

```
┌─────────────┐    ┌──────────────────────────────────────┐    ┌────────────┐
│   TMDB API  │───▶│  Multi-Provider Stream Resolver      │───▶│  yt-dlp    │
└─────────────┘    │                                      │    │  + ffmpeg  │
                   │  1. Vidsrc.win (15 Playwright sources)│    └────────────┘
                   │  2. Cineby   (Playwright, 4K@16Mbps) │
                   │  3. vidsrc   (legacy scrape, 800p)   │
                   └──────────────────────────────────────┘
```

## How the resolvers work

### Vidsrc.win (up to 4K, requires Playwright)
```
vidsrc.win React SPA contains 40+ hardcoded embed sources
  → 15 most reliable extracted and ranked:
    1. 4K      (player.videasy.net)
    2. 4K2     (www.vidking.net/embed)
    3. 4KHD    (mapple.uk/watch)
    4–15. vidsrcme, vidsrc.su, vidsrc.cc, vidrush,
          vidlink, anyembed, autoembed, rivestream,
          vidify, vidzee, vidplus, …
  → each source gets a Playwright headless Chromium browser
    → anti-detection (stealth + --disable-blink-features)
    → response interception for application/vnd.apple.mpegurl
    → returns first captured m3u8 URL
```

### Cineby (4K, requires Playwright)
```
cineby.at/movie/{tmdb_id}?play=true
  → Playwright headless Chromium (stealth + system Chrome fallback)
    → api.speedracelight.com/seed (Cloudflare bypassed by browser)
      → client-side JS decrypts sources
        → moon.ironwallnet.net/vd/... (4K HLS master playlist)
```

### VidSrc (800p, legacy fallback)
```
vidsrc.to/embed/{movie|tv}/{tmdb_id}[/{season}/{episode}]
  → vsembed.ru iframe
    → cloudorchestranova.com/rcp/{hash}
      → cloudorchestranova.com/prorcp/{hash}
        → generate.php endpoints (JWT tokens)
          → final m3u8 URL
```

## Adding a new stream provider

Implement `StreamProvider` from `vidsrc_dlp.utils` and register it in `MultiDomainResolver.resolve()`.

## Acknowledgements

- **[MaheshSharan/vidsrc](https://github.com/MaheshSharan/vidsrc)** — The request-based 4-hop resolution chain was reverse-engineered from this open-source PHP scraper.
- **[TMDB](https://www.themoviedb.org/)** — Movie and TV metadata API.
- **[yt-dlp](https://github.com/yt-dlp/yt-dlp)** — Download engine for HLS fetching, decryption, and ffmpeg transcoding.
