A Python tool that automatically keeps a YouTube playlist in sync with new uploads from a given channel, with an optional duration filter to skip YouTube Shorts.
- Python 3.10+
- A Google Cloud project with the YouTube Data API v3 enabled
- OAuth 2.0 credentials (type: Desktop app) downloaded as
credentials.json
git clone https://github.com/CorbeusUltra/playlist-updater.git
cd playlist-updater
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtCreate a .env file at the root of the project with the following variables:
CHANNEL_ID=UCxxxxxxxxxxxxxxxxxxxxxxxx # YouTube channel ID (starts with UC)
PLAYLIST_ID=PLxxxxxxxxxxxxxxxxxxxxxxxx # Target playlist ID (starts with PL)
# Videos shorter than this duration are excluded. # Set to 0 to keep all videos, including Shorts. SHORTS_MIN_SECONDS=75
# Set to true to preview changes without modifying the playlist.
DRY_RUN=false
# Port used by the local OAuth callback server.
# Set to 0 or leave unset to automatically select a free port.
# Use a fixed port, such as 8080, only if your Google OAuth client requires
# a redirect URI such as http://localhost:8080.
OAUTH_PORT=0- Go to Google Cloud Console and create a new project (or select an existing one).
- In the left menu, go to APIs & Services > Library. Search for YouTube Data API v3 and click Enable.
- Go to APIs & Services > OAuth consent screen. Choose External, fill in the required fields (app name, support email), and save.
- Go to APIs & Services > Credentials. Click Create credentials > OAuth client ID. Select Desktop app as the application type.
- Download the generated JSON file and rename it
credentials.json. Place it in thecredentials/folder at the root of the project.
The first time you run the script, a browser window will open asking you to authorize the application. After authorization, a
token.jsonfile is saved incredentials/and reused for all subsequent runs.About the redirect URI: if you created your OAuth client as a Desktop app, Google accepts any
http://localhost:<port>redirect automatically and you can leaveOAUTH_PORTunset (the script will pick a free port). If your OAuth client is a Web application, you must either (a) recreate it as a Desktop app, or (b) declare an explicit authorized redirect URI likehttp://localhost:8080in the Cloud Console and setOAUTH_PORT=8080in your.envso the script binds to the matching port.
python script/run.pyOn the first run, a browser window will open for OAuth authorization. Subsequent runs are fully non-interactive.
To verify your setup without modifying the playlist, keep DRY_RUN=true.
.venv/bin/python -m pytest tests/ -vplaylist_updater/
├── script/run.py # Entry point
├── src/playlist_updater/
│ ├── auth_manager.py # OAuth 2.0 lifecycle (token generation, refresh)
│ ├── config.py # Environment variable parsing and validation
│ ├── main.py # Orchestration logic and local state management
│ └── youtube_service.py # YouTube Data API v3 wrapper
├── tests/ # pytest test suite
├── data/playlist_data.json # Local state cache (auto-created on first run)
└── credentials/ # credentials.json and token.json (not versioned)
The script authenticates with Google using OAuth 2.0 the first time it runs, then persists the token locally so that all subsequent runs are non-interactive — the browser window only opens once. On the very first execution (bootstrap), the current contents of the target playlist are downloaded and saved into a local JSON file (data/playlist_data.json), which becomes the reference point used to detect new uploads on every later run.
From that point on, each run fetches the channel's recent uploads and keeps only the videos published strictly after the most recent video already known locally. Videos shorter than the SHORTS_MIN_SECONDS threshold are filtered out — this is the mechanism used to exclude YouTube Shorts — and the remaining new videos are appended to the target playlist in chronological order (oldest first). A DRY_RUN mode runs the entire pipeline without actually modifying the playlist, which is the recommended way to verify your configuration before the first real run.
The script is deliberately quota-friendly: it reads the channel's system uploads playlist (1 API unit per page) instead of calling search() (which costs 100 units per query), and it stops paginating as soon as it encounters a video older than the local reference date.
- Channel ID: Go to the channel page on YouTube. The URL contains
/channel/UCxxxxxxx— copy theUC...part. If the channel uses a custom URL, open the page source and search for"channelId". - Playlist ID: Open the playlist on YouTube. The URL contains
list=PLxxxxxxx— copy thePL...part.
MIT — see LICENSE for details.