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 via VidSrc's 4-hop chain, and download via yt-dlp.

Install

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

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 best Target resolution. best = highest available. E.g. 1080p, 720p
--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
--verbose flag Enable debug logging
--verbose-ytdlp flag Show yt-dlp output

Examples

# Simple movie download
vidsrc-dlp "Inception"
vidsrc-dlp "The Matrix" --year 1999

# Custom quality + skip prompt
vidsrc-dlp "Interstellar" --quality 1080p --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

Quality selection

When --quality best (the default), the stream is inspected with yt-dlp before downloading and the highest resolution variant is auto-selected. The detected resolutions are logged so you can see what's available:

[INFO] Available qualities: 1080p, 720p, 480p, 360p
[INFO] Format: bestvideo+bestaudio/best

Pass a specific height via --quality 1080p or --quality 720p to override and constrain the selection.

.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="best", 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 best Target resolution. best = highest available. 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"

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

  1. TMDB — search API for movie/TV metadata
  2. VidSrc Resolverrequests-only 4-hop chain to extract an HLS URL
  3. Downloaderyt-dlp + ffmpeg to download and transcode to MP4

How the resolver works

vidsrc.to/embed/{movie|tv}/{tmdb_id}[/{season}/{episode}]
  → vsembed.ru iframe (parse src attr)
    → cloudorchestranova.com/rcp/{hash} (parse prorcp hash)
      → cloudorchestranova.com/prorcp/{hash} (m3u8 URLs with __TOKEN__ placeholders)
        → generate.php endpoints (JWT tokens)
          → final m3u8 URL

Adding a new stream provider

Implement StreamProvider from vidsrc_dlp.utils.

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.4.0.tar.gz (16.0 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.4.0-py3-none-any.whl (15.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: vidsrc_dlp-0.4.0.tar.gz
  • Upload date:
  • Size: 16.0 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.4.0.tar.gz
Algorithm Hash digest
SHA256 e4add65570ebd00b6e1c5b35ff96aa79984d06feb2dbb24c2de2609fb5a77968
MD5 62ddb5d17f8ecc3d5cebd41f2e13b976
BLAKE2b-256 2a051f68ba2d4f052ebc1ee3d615433e0e6d684857e3946962aaa3d4b8740818

See more details on using hashes here.

File details

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

File metadata

  • Download URL: vidsrc_dlp-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 15.8 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.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 995e7bb054a5b9a8195266d17e213de3ae0e9ab3e139c076f91795e26bdb8e9a
MD5 5bd877e55b4646bb95becdc5936073b0
BLAKE2b-256 58b041ce024af6e17e1c9eb5d8a8f969db03fa7a06a7aecc37851c4bb1924ce1

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