YT-PAO analyzes YouTube playlists and produces reports in multiple formats. It started as a terminal tool and now includes a simple web frontend and a FastAPI backend. This repository contains both the original CLI utilities and the web UI + API, plus Docker configuration for running the whole stack.
- Analyze playlists and extract titles, URLs, durations, uploaders, view counts, and availability status.
- Produce reports in
cmd,txt,json,csv,htmlor save directly to a MySQL database. - Generate multiple report formats in a single run, for example
mySQLandhtmltogether. - In the web frontend, export the currently selected MySQL report snapshot to
csv,sql,txt,json, orhtmlwithout triggering a new backend generation run. - The frontend export controls live in a collapsible panel in the playlist timeline so they do not cover the full page.
- Playlists can be disabled from the dashboard; disabled playlists are hidden and a later CLI/frontend import of the same playlist URL re-enables them.
- When a playlist has more than five reports, the timeline shows a compact mini chart of video counts from the first report to the latest one.
- Work modes:
all,available,unavailable. - CLI utilities for one-off reports and a web interface for browsing playlists and reports.
- Backend: FastAPI application (
api.py) — serves API endpoints and static files. - Frontend: React + Vite in
frontend/(development with Vite, production build served with Nginx in Docker). - Database: MySQL (optional in
docker-compose, or use an external MySQL instance via environment variables). - Docker:
Dockerfile,frontend/Dockerfileanddocker-compose.ymlincluded for easy local deployment.
Python 3.11+ for the backend and Node.js (recommended 18+) for the frontend development. Install Python dependencies with:
pip install -r requirements.txtFor frontend development:
cd frontend
npm installDocker and Docker Compose are recommended to run the full stack locally.
git clone https://github.com/your-repo/YT-PAO.git
cd YT-PAO
pip install -r requirements.txtDue to aggressive anti-bot protections, YouTube limits unauthenticated API requests to a maximum of 100 videos per playlist and aggressively removes metadata like Uploaders and View Counts (showing as Unknown or 0).
To bypass this and fetch full playlists (e.g., 1000+ videos), you must authenticate yt-dlp using a valid browser session cookie.
- Open your browser and log in to YouTube (a secondary/dummy account is fine).
- Play any video for about 5-10 seconds. This is a crucial step to generate required "PO Tokens" that prove you are not a bot.
- Use a browser extension like "Get cookies.txt LOCALLY" (available for Chrome/Firefox) while on the YouTube tab.
- Export the cookies and save the file exactly as
cookies.txtin the root directory of this project (next tomain.py). - Restart your Docker containers. The
docker-compose.ymlfile is configured to mount this file directly into the backend container.
Note: Cookies expire over time or when you manually log out of that session in your browser. If you start seeing 100-video limits again, simply repeat the process to refresh your cookies.txt.
CLI (original terminal mode):
python main.py --playlistLink <playlist_link> --resultFormats <cmd|txt|json|csv|html|mySQL> [<cmd|txt|json|csv|html|mySQL> ...] --listMode <all|available|unavailable>You can pass more than one format in the same invocation:
python main.py --playlistLink <playlist_link> --resultFormats mySQL html --listMode allIf you still use the legacy single-format flag, --resultFormat, it is treated as a compatibility alias for one format.
When multiple formats are requested, each format is processed independently. A failure in one format does not stop the others, and the CLI prints a per-format result.
Thumbnail repair (repair missing thumbnail files from database):
# Scan database and re-download missing thumbnail files
python main.py --repair-thumbnails
# In Docker:
docker exec yt-pao-backend-1 python main.py --repair-thumbnailsThis is useful when thumbnail files are missing from disk but still referenced in the database. The repair operation will:
- Scan all stored thumbnails in the database
- Check if files physically exist on disk
- Re-download missing files from their original source URL
- Update SHA256 hashes in the database
Web API (development):
python -m uvicorn api:app --reload --port 8000
# API examples: http://localhost:8000/api/playlistsThe playlist report endpoint accepts a JSON body with a formats array, for example:
{ "formats": ["mySQL", "html"] }The web UI exposes the same multi-format selection before starting report generation.
The playlist detail page also includes a local export panel. It uses the currently selected timeline snapshot from MySQL and lets you download the report in csv, sql, txt, json, or html format. This export is client-side and does not start a new report job.
The app footer and exported HTML reports display the program version, which is read automatically from Git using git describe --tags --dirty --always and falls back to the short commit hash when tags are not available.
The dashboard now supports soft-delete by setting a playlist as disabled. This hides the playlist from the main list without removing its history, and re-importing the same playlist URL through the CLI or frontend sets it back to enabled automatically.
Frontend (dev):
cd frontend
npm run dev
# Vite dev server: http://localhost:5173Copy .env.example to .env and edit if you want to use an external DB or change defaults.
If your MySQL server is outside Docker, set DB_HOST to that server's address. If it runs on the host machine, use host.docker.internal on Docker Desktop/Windows or the host's IP/DNS name on Linux.
cp .env.example .env
docker compose up --buildThe backend container runs by default as the host user with UID/GID 1000:1000. This prevents Permission denied errors when writing to mounted volumes. Override UID and GID in .env when needed.
- Frontend: http://localhost:3002
- Backend API: http://localhost:8001
- The bundled MySQL container is not published on host port 3306 anymore, so it will not clash with a local MySQL install.
To run without the bundled MySQL, set DB_HOST in .env to your DB host and start only backend and frontend:
DB_HOST=1.2.3.4
docker compose up --build backend frontendThe backend configuration is loaded from environment variables (including .env) via pydantic-settings.
Use these variables for database configuration:
DB_HOSTDB_PORTDB_USERDB_PASSWORDDB_NAME
Copy .env.example to .env and adjust values for your environment.
When using Docker, UID and GID can be set in .env to override the default backend container user (1000:1000).
api.py— FastAPI app and API endpointsmain.py— original CLI entrypoint and helpersmySQL_manager.py— database utilitiesfrontend/— React + Vite frontendweb_template/— HTML templates used by CLI HTML outputfrontend/src/utils/reportExporters.js— client-side export helpers for the selected playlist snapshotdocker-compose.yml,Dockerfile,frontend/Dockerfile— docker configurationrequirements.txt— Python dependencies
The HTML report style now follows the dark, card-based frontend look and is shared between the CLI and the frontend export helper. Both outputs use the same overall visual language so exported HTML looks consistent with the playlist detail page.
YT-PAO can compare latest reports for playlists stored in MySQL to identify missing videos between playlists. The SQL example below demonstrates the query pattern used to compute differences between two playlist snapshots.
WITH params AS (
SELECT 2 AS p1, 19 AS p2 -- Replace 2 and 19 with your playlist IDs
),
last_reports AS (
SELECT r.playlist_id, r.report_id
FROM ytp_reports r
INNER JOIN (
SELECT playlist_id, MAX(report_date) AS max_date
FROM ytp_reports, params
WHERE playlist_id IN (SELECT p1 FROM params UNION SELECT p2 FROM params)
GROUP BY playlist_id
) latest
ON r.playlist_id = latest.playlist_id
AND r.report_date = latest.max_date
),
videos_in_playlists AS (
SELECT rd.video_id, v.video_title, v.video_url, r.playlist_id
FROM ytp_report_details rd
JOIN ytp_reports r ON rd.report_id = r.report_id
JOIN ytp_videos v ON rd.video_id = v.video_id
WHERE r.report_id IN (SELECT report_id FROM last_reports)
)
-- Videos in Playlist p1 missing from p2
SELECT
v1.video_id,
v1.video_title,
v1.video_url,
(SELECT p1 FROM params) AS playlist_source
FROM (SELECT * FROM videos_in_playlists WHERE playlist_id = (SELECT p1 FROM params)) v1
LEFT JOIN (SELECT * FROM videos_in_playlists WHERE playlist_id = (SELECT p2 FROM params)) v2
ON v1.video_id = v2.video_id
WHERE v2.video_id IS NULL
UNION
-- Videos in Playlist p2 missing from p1
SELECT
v2.video_id,
v2.video_title,
v2.video_url,
(SELECT p2 FROM params) AS playlist_source
FROM (SELECT * FROM videos_in_playlists WHERE playlist_id = (SELECT p2 FROM params)) v2
LEFT JOIN (SELECT * FROM videos_in_playlists WHERE playlist_id = (SELECT p1 FROM params)) v1
ON v2.video_id = v1.video_id
WHERE v1.video_id IS NULL;
The core of this project is the Python/FastAPI backend, MySQL database design, and the data processing logic (which originally evolved from my custom CLI scripts). The React frontend was quickly scaffolded with the help of AI purely to visualize the backend data, as my main focus as a developer is backend engineering and infrastructure.
- Kordight (Sebastian Legieziński) - GitHub