Reddit2Tube is a Python automation tool that finds highly-rated videos in a
public subreddit, downloads them with yt-dlp, renders YouTube metadata from
templates, and optionally uploads the videos through the YouTube Data API.
The project is designed as a small, testable automation pipeline rather than a hosted service. It is useful for learning and personal workflows, while still demonstrating configuration validation, dependency injection, SQLite idempotency, retries, logging, and offline testing.
Reddit API
-> filter video submissions
-> render templates
-> SQLite duplicate check
-> yt-dlp download
-> record pending upload (SQLite)
-> YouTube upload (optional)
-> success: record the upload
-> failure: keep the pending row for --retry-failed
In upload mode the application records a submission in two stages. It writes a
pending row before uploading and marks the submission as uploaded only once
YouTube returns an upload ID. A failed or interrupted upload keeps its pending
row, so it can be retried later with --retry-failed regardless of whether the
submission is still in the listing window.
Download-only runs neither write pending rows nor mark submissions as uploaded. Re-uploading a download-only video later therefore still depends on the submission appearing in the top submissions again.
- Python 3.10 or newer
- Reddit API credentials
- YouTube Data API v3 credentials for upload mode
- Permission to download and republish the selected content
Reddit2Tube does not bypass access controls. Respect Reddit, YouTube, and content-owner terms, copyright, rate limits, and community rules.
Install uv using the instructions at https://docs.astral.sh/uv/getting-started/installation/ before running the commands below.
git clone https://github.com/roperi/Reddit2Tube.git
cd Reddit2Tube
make install
make install-hooks
cp .env.example .env
mkdir -p templates
cp examples/templates/* templates/Edit .env with your Reddit credentials. The application creates the SQLite
database, tables, log directory, and download directory automatically.
Run the test suite and inspect the CLI:
make check
python -m reddit2tube --help.env.example is the complete configuration reference. The most important
values are:
| Variable | Purpose |
|---|---|
DATABASE_FILE |
SQLite file used to prevent duplicate processing |
DOWNLOAD_DIR |
Directory for downloaded media |
REDDIT_CLIENT_ID / REDDIT_CLIENT_SECRET |
Reddit application credentials |
REDDIT_USERNAME / REDDIT_PASSWORD |
Reddit account used by PRAW |
REDDIT_USER_AGENT |
Descriptive Reddit API user agent |
CLIENT_SECRETS_FILE |
Google OAuth desktop-client JSON file |
SCOPES |
Comma-separated YouTube OAuth scopes |
For Reddit anti-bot challenges, configure exactly one of
REDDIT_COOKIES_FROM_BROWSER or REDDIT_COOKIES_FILE. Cookie contents are
never logged. Keep .env, config/, and cookie files private.
Reddit-hosted videos are downloaded from the direct v.redd.it media URL
returned by the Reddit API, so yt-dlp does not call Reddit's JSON metadata
endpoint. This avoids the Your IP address is unable to access the Reddit API
error that can occur even when browser cookies are valid. Cookies are only
necessary when a submission has no direct media URL.
If yt-dlp reports Account authentication is required, log into Reddit in
Firefox on the machine running Reddit2Tube and add this to .env:
REDDIT_COOKIES_FROM_BROWSER=firefoxUse chrome instead if that is your logged-in browser. To select a specific
profile, use firefox:/absolute/path/to/profile. Chromium users should use
chromium; for the Ubuntu Snap installation, use
chromium:~/snap/chromium/common/chromium/Default.
For a server without a local
browser, export the logged-in Reddit cookies in Mozilla/Netscape format and set
REDDIT_COOKIES_FILE=/absolute/path/to/reddit-cookies.txt instead. Set only one
cookie option, then rerun your original command. If authentication still fails,
refresh the browser login or export fresh cookies.
The REDDIT_USERNAME and REDDIT_PASSWORD settings authenticate PRAW's API
requests; yt-dlp uses a separate session and needs these browser cookies.
See the yt-dlp cookie FAQ
for export instructions.
Create a script application at https://www.reddit.com/prefs/apps/ and copy
its client ID and secret into .env. Use a descriptive user agent such as:
REDDIT_USER_AGENT=Reddit2Tube/1.0 by your-reddit-usernameIn Google Cloud:
- Create or select a project.
- Enable YouTube Data API v3.
- Create an OAuth client ID for a desktop application.
- Download the JSON file to
config/client_secret.json. - Add the YouTube upload scope to the OAuth consent configuration.
The first upload run opens a browser for authorization. The resulting token is
stored under config/ and is ignored by Git.
Copy the examples into templates/ and customize them:
mkdir -p templates
cp examples/templates/* templates/Both templates use Python str.format fields. Available fields include:
title, id, author, selftext, url, score, num_comments,
created_utc, subreddit, is_self, link_flair_text, and locked.
Deleted Reddit accounts render as [deleted]. Titles longer than 70 characters
are shortened before template rendering.
The original script entrypoint remains supported:
uv run --locked python Reddit2Tube.py \
--subreddit_name cats \
--reddit_num_submissions 1 \
--time_filter day \
--privacy_status privateThis runs the original script entrypoint inside the uv-managed project environment.
The installed command and module entrypoints are equivalent:
uv run --locked reddit2tube --subreddit_name cats --reddit_num_submissions 1
uv run --locked python -m reddit2tube --subreddit_name cats --just_downloadUseful options:
| Option | Default | Description |
|---|---|---|
--subreddit_name |
— | Subreddit to query |
--reddit_num_submissions |
2 |
Number of top submissions to inspect |
--time_filter |
day |
Reddit period, such as day or week |
--category |
22 |
YouTube category ID |
--privacy_status |
private |
private, unlisted, or public |
--made_for_kids |
off | Set YouTube’s self-declared kids flag |
--just_download |
off | Skip YouTube authentication and upload |
--verbose |
off | Enable debug logging |
--troubleshooting-upload |
off | Private, long-form-only uploads with a six-attempt UTC-day cap |
--retry-failed |
off | Re-upload videos recorded as pending after a failure |
The troubleshooting upload profile is temporary and opt-in. It validates and
fully decodes each candidate with ffprobe/ffmpeg, skips likely Shorts, and
records upload-attempt reservations in SQLite before starting an upload. It
requires both ffprobe and ffmpeg to be available on PATH.
The process exits non-zero for invalid configuration or failed submissions. Independent submissions continue to be attempted, and the final log includes a run summary.
Uploads that fail are recorded in the pending_uploads table in the SQLite
database. Retry them with:
uv run --locked reddit2tube --retry-failedA retry reuses the recorded local file when it still exists, otherwise it
downloads the video again from the recorded URL. Pending rows written by older
versions store a Reddit permalink; those are resolved to a direct v.redd.it
media URL through the Reddit API when credentials are available. Because the
source URL and the rendered title and description are stored, retrying does not
need the submission to still be in the listing window. Retries use the current
command-line options for category, privacy status, and made-for-kids.
Add --subreddit_name to retry failures first and then process new submissions
in the same run:
uv run --locked reddit2tube --retry-failed --subreddit_name cats --time_filter dayRetry mode needs YouTube credentials, plus Reddit cookies only when a video has
to be downloaded again. It cannot be combined with --just_download. When
--troubleshooting-upload is also passed, retries respect the same six-attempt
UTC-day cap.
Install the project with its development tools:
make installmake install uses uv to create or update the project environment from the
committed uv.lock file. The project environment lives in .venv/; activate
it manually only when needed, or use uv run for individual commands.
Run the complete local quality gate:
make checkThe repository also provides make format, make lint, make test,
make coverage, and make hooks-check for individual checks. After
make install-hooks, Ruff runs before commits and the complete offline test
suite runs before pushes.
Tests are intentionally offline. Reddit, yt-dlp, and YouTube are isolated behind injectable boundaries and replaced with fakes in workflow tests. CI runs the quality gate on Python 3.10 through 3.13.
For scheduled personal automation, call the installed command from cron or a systemd timer with an absolute working directory and a private environment file. Keep the process single-instance if multiple schedules could overlap.
Example cron entry:
0 5 * * 1 cd /path/to/Reddit2Tube && uv run --locked reddit2tube --subreddit_name cats --reddit_num_submissions 3 --time_filter week --privacy_status privateStart with private or unlisted uploads while validating templates, quota,
and content policy. YouTube upload quota is finite and can make a failed retry
expensive.
A failed upload is recorded rather than lost when the listing moves on. To recover failures automatically, schedule a follow-up retry:
30 6 * * * cd /path/to/Reddit2Tube && uv run --locked reddit2tube --retry-failedNever commit .env, OAuth client secrets, OAuth tokens, Reddit cookies,
databases, downloaded media, or logs. The repository ignores these runtime
artifacts and includes SECURITY.md with handling guidance.
Contributions should follow CONTRIBUTING.md. See the CHANGELOG for project evolution. The project is released under the MIT license; see LICENSE.