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.6.0.tar.gz (16.6 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.6.0-py3-none-any.whl (16.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: vidsrc_dlp-0.6.0.tar.gz
  • Upload date:
  • Size: 16.6 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.6.0.tar.gz
Algorithm Hash digest
SHA256 b3f6d6f4fe7236b4d6c01147f6934b27103d1532ce5fd74ca4d2971b65752422
MD5 aed227f284e12f602cd26e0fcb993ba2
BLAKE2b-256 85ad5fcf91a4d1ce3e069e44d32b50a78fe6b06fa4c6bb3373b3dea1aa89c9b6

See more details on using hashes here.

File details

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

File metadata

  • Download URL: vidsrc_dlp-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 16.5 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.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 755bb5a74cbd3cfddcbb4f1409bc8261ab7177aa9c303566b20b797de21c7dc9
MD5 70bdef308104edcce3de72aeaa9a2d0f
BLAKE2b-256 ddfc6150332e80b53682b1260ab0355e98851b452b1a50f28c94d38906bafdb8

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