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.7.0.tar.gz (17.2 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.7.0-py3-none-any.whl (17.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: vidsrc_dlp-0.7.0.tar.gz
  • Upload date:
  • Size: 17.2 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.7.0.tar.gz
Algorithm Hash digest
SHA256 9fa8fea9a628b7216e0f0e8bdd411612502f03d8a89a01d4388d8a9c0cd55104
MD5 0e34fe3858e756e1da9045f7f49531dd
BLAKE2b-256 c0723b4269cf04177d0472f14b939cdca78b86aee66bef402288421fb6c1192a

See more details on using hashes here.

File details

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

File metadata

  • Download URL: vidsrc_dlp-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 17.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.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f7c0add0b449af12bb86fecca49cfd1f37babb2b69a051f95e8168853293e0f8
MD5 36ecdfaae23aaf6d90ad9b316f1c9717
BLAKE2b-256 390d6efeff4c33898f71a2ed0d5c35b5ce14a8b197981d73500304a84af61b86

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