YouTube playlist calculator for answering the question most people actually have: "how long will this playlist take?"
It works with playlist URLs or raw playlist IDs. You can see the full playlist length, compare watch times at different speeds, limit the calculation to a video range, plan a daily pace, and export the result.
This is useful for courses, tutorials, and long study playlists where the first question is usually the total time commitment. After that, the planning questions get more specific:
- How long is the whole playlist?
- How much time is left if playback starts from video 12?
- What does the total look like at 1.5x or 1.75x?
- Can this be finished before a certain date?
- Which playlist in a queue is going to take the most time?
The app keeps that workflow simple, while the YouTube API key stays on the server side instead of being shipped to the browser.
- Accepts one playlist or a batch of playlists.
- Parses normal YouTube playlist URLs and raw playlist IDs.
- Shows totals at common speeds and a custom speed.
- Lets you calculate only a selected video range.
- Sorts playlist results with thumbnails, channel names, publish dates, views, and durations.
- Estimates daily pace from a target finish date.
- Copies a text summary or downloads CSV results.
- Includes a deterministic demo mode, so the UI can be checked without API keys.
The frontend is an Astro page with a React calculator mounted inside it. The API runs as Cloudflare Pages Functions and calls the YouTube Data API from there.
Browser
-> Astro page + React calculator
-> Cloudflare Pages Functions (/api/playlist, /api/playlists)
-> YouTube Data API v3
Some details that matter:
YOUTUBE_KEYSis only read by the Functions runtime.- Batch requests de-duplicate playlist IDs and limit concurrency.
- Multiple YouTube keys can be configured so the API can rotate on quota or rate-limit failures.
- Successful responses use a short edge cache.
- The browser keeps UI state only for the current session.
src/pages/index.astromounts the page shell.src/components/App.tsxowns the calculator flow.src/components/PlaylistsTable.tsxrenders playlist results.src/components/ContentAccordion.tsxandsrc/components/FAQAccordion.tsxrender the lower guide/FAQ section. Keep this content useful for real planning questions; theFAQPagestructured data should mirror visible support copy rather than exist as standalone SEO filler.functions/api/playlist.tshandles the single-playlist route.functions/api/playlists.tshandles batch playlist analysis.src/shared/contracts.tskeeps frontend and backend response shapes aligned.
Install dependencies:
bun install
cp .dev.vars.example .dev.varsAdd one or more YouTube Data API keys to .dev.vars:
YOUTUBE_KEYS=YOUR_YOUTUBE_DATA_API_KEY,YOUR_OPTIONAL_FALLBACK_YOUTUBE_DATA_API_KEYStart the Astro app:
bun run devIn another terminal, start the local Pages Functions runtime:
bun run worker:devThe frontend proxies /api/* to http://127.0.0.1:8788, so both processes are needed for real API calls. The sample route works without keys:
http://localhost:4321/?demo=1
| Name | Required | Where | Purpose |
|---|---|---|---|
YOUTUBE_KEYS |
Yes for real API calls | .dev.vars locally; Cloudflare Pages env vars in production |
Comma-separated YouTube Data API keys. |
Do not commit .dev.vars. If a real key is ever committed, revoke or rotate it before publishing.
For reporting security issues or accidental key exposure, see SECURITY.md.
Bug reports and feature requests are tracked in GitHub Issues. Pull request expectations, test policy, and acceptable contribution requirements are documented in CONTRIBUTING.md.
Security issues should follow SECURITY.md, not a detailed public issue.
bun run check
bun run test
bun run buildCurrent tests cover playlist input parsing, range normalization, API key parsing, cache-key generation, and bounded concurrency helpers. There is also a demo smoke test, but fuller E2E coverage is still a good next step.
Static and dynamic analysis evidence for release readiness lives in GitHub Actions: CI, GitHub CodeQL default setup, release, and OWASP ZAP baseline workflows.
GET /api/playlist?list=PLAYLIST_IDGET /api/playlist?list=PLAYLIST_ID&refresh=1GET /api/playlists?lists=ID1,ID2,ID3GET /api/playlists?lists=ID1,ID2,ID3&refresh=1
The single-playlist route returns playlist metadata and ordered video durations. The batch route accepts up to 50 valid playlist IDs and returns separate results, errors, and meta fields.
The production app runs on Cloudflare Pages with Pages Functions.
Cloudflare Pages settings:
- Project name:
playlist-time - Build command:
bun run build - Build output directory:
dist - Functions directory:
functions - Production variable:
YOUTUBE_KEYS
Manual deploy:
bun run build
bun run deploy:pagesProduction secrets should be configured in Cloudflare Pages, not committed to the repository.
Release notes are tracked in CHANGELOG.md. GitHub Releases are created automatically for new semantic version tags:
git tag v1.1.0
git push origin v1.1.0Update CHANGELOG.md before tagging user-visible changes. The release workflow runs install, audit, checks, tests, and build before creating the GitHub Release. The release title is generated by scripts/release-title.mjs; v1.0.0 becomes Public Launch: Playlist Watch-Time Calculator, and later tags are named from the changed areas since the previous tag.
For major production releases, run the Dynamic analysis workflow first so OWASP ZAP scans the public demo route before the tag is pushed.
Preview a title locally:
bun run release:title v1.1.0- Results depend on YouTube Data API availability, quota, and playlist visibility.
- Private, deleted, unavailable, or region-blocked videos can affect totals.
- The rate limiter is lightweight and per-runtime; it is not durable abuse protection.
GNU Affero General Public License v3.0 only (AGPL-3.0-only). See LICENSE.
