Skip to main content

CLI to search TMDB, resolve VidSrc streams, and download via yt-dlp

Project description

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

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

For 4K quality (requires Playwright):

pip install vidsrc-dlp[playwright]
playwright install chromium

Or for local development:

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 Cineby → Mapple → 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

# 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. Cineby — requires Playwright, up to 4K@16Mbps
  2. Mapple — no browser needed, up to 1080p@8Mbps
  3. vidsrc — legacy scrape, up to 800p

If Playwright is not installed, Cineby is 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)
>>> 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)
>>> 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).

>>> 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)
>>> 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)
>>> 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
>>> 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

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. Cineby (Playwright, 4K@16Mbps)   │    └────────────┘
                   │  2. Mapple  (direct API, 1080p@8Mbps)│
                   │  3. vidsrc  (legacy scrape, 800p)     │
                   └──────────────────────────────────────┘

How the resolvers work

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 — The request-based 4-hop resolution chain was reverse-engineered from this open-source PHP scraper.
  • TMDB — Movie and TV metadata API.
  • yt-dlp — Download engine for HLS fetching, decryption, and ffmpeg transcoding.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vidsrc_dlp-0.10.0.tar.gz (19.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vidsrc_dlp-0.10.0-py3-none-any.whl (19.1 kB view details)

Uploaded Python 3

File details

Details for the file vidsrc_dlp-0.10.0.tar.gz.

File metadata

  • Download URL: vidsrc_dlp-0.10.0.tar.gz
  • Upload date:
  • Size: 19.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for vidsrc_dlp-0.10.0.tar.gz
Algorithm Hash digest
SHA256 20e10ce6932a179502c96c81ece38b846d0f31a1da66649ad38c449e5201ec1f
MD5 ecb25b5ddae65474bcb61f41cb67909c
BLAKE2b-256 afa5f9b766b055c21245b3cadc8caaadce16681923b8c53c1c1158ae98deedad

See more details on using hashes here.

File details

Details for the file vidsrc_dlp-0.10.0-py3-none-any.whl.

File metadata

  • Download URL: vidsrc_dlp-0.10.0-py3-none-any.whl
  • Upload date:
  • Size: 19.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for vidsrc_dlp-0.10.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d764ac10bbfba54b985b74073a846368e7cd0637c8e725a2ad9e405c7269c340
MD5 bbffffe6a69b09a5701cefdcf33a3379
BLAKE2b-256 6d3f0be25f54877d20487e599a14749df26d255f3f80d91e3d246b6fd6fa140e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page