Live site: https://commercialbrainz.org/
CommercialBrainz is an open commercial video database modeled after MusicBrainz. Each entry represents one YouTube video of a single commercial, with rich metadata, community edits, voting, and a scrape-friendly public API.
Testing / staging: https://commercialbrainz.duckdns.org/
- Rich data model — Advertisers, agencies, commercials, videos, credits, tags, airings
- One entry = one video — Each record links a commercial to a specific YouTube upload
- MusicBrainz-style edits — Community submissions with 7-day voting periods
- Roles — Users, mods (auto-editors), and admins
- DMCA takedown system — Link suppression with audit trail and mod review queue
- Scrape-friendly API — Versioned JSON, ETags, rate limits, nightly dumps (CC0)
Public help pages (/about, /help, /help/basic-usage, /help/api, /help/become-a-mod, /donate, /terms overview, /dmca policy) are Markdown under docs/site/. The web UI mirrors them at build time. Click Edit on GitHub on any of those pages, or see docs/site/README.md.
cp .env.example .env
docker compose -f infra/docker-compose.yml up --build- Web UI: http://localhost:5173
- API docs: http://localhost:8000/docs
- Health: http://localhost:8000/health
Seed an admin user:
docker compose -f infra/docker-compose.yml exec api \
commercialbrainz seed-admin --email admin@example.com --username admin --password yourpasswordFor bare-metal install on Ubuntu 22.04+ / Debian:
chmod +x scripts/setup-linux.sh
SEED_ADMIN=1 ./scripts/setup-linux.shThen start services:
source .venv/bin/activate
cd backend && uvicorn app.main:app --reload --port 8000
cd backend && arq app.workers.settings.WorkerSettings # worker
cd frontend && npm run dev # frontendThe cheapest way to run CommercialBrainz on GCP uses the Always Free e2-micro instance (1 vCPU, 1 GB RAM) in us-west1, us-central1, or us-east1. Everything runs on one VM via Docker Compose.
Prerequisites: gcloud CLI, billing account (free tier still requires one).
chmod +x scripts/setup-gcloud-vm.sh
GCP_PROJECT_ID=your-project ./scripts/setup-gcloud-vm.shThe script automatically tries alternate free-tier zones if us-central1-a (or your chosen zone) has no e2-micro capacity. To pin a specific zone:
GCP_ZONE=us-east1-b GCP_AUTO_ZONE=0 GCP_PROJECT_ID=your-project ./scripts/setup-gcloud-vm.shOptional admin seed on first boot:
GCP_PROJECT_ID=your-project \
ADMIN_EMAIL=admin@example.com \
ADMIN_USERNAME=admin \
ADMIN_PASSWORD=yourpassword \
./scripts/setup-gcloud-vm.shAfter 10–20 minutes (Docker build on e2-micro is slow):
- Web UI:
http://EXTERNAL_IP/ - API docs:
http://EXTERNAL_IP:8000/docs
Monitor startup:
gcloud compute ssh commercialbrainz-vm --zone=us-central1-a \
--command='sudo tail -f /var/log/commercialbrainz-startup.log'See scripts/setup-gcloud-vm.sh and infra/gcloud/vm-startup.sh.
- Create a subdomain at duckdns.org and copy your token.
- Deploy the VM with DuckDNS (updates DNS immediately + every 5 min on the VM):
GCP_PROJECT_ID=your-project \
DUCKDNS_DOMAIN=commercialbrainz \
DUCKDNS_TOKEN=your-duckdns-token \
./scripts/setup-gcloud-vm.sh- After startup use port 80 (port
:8000is often blocked by ISPs):- Web:
http://commercialbrainz.duckdns.org/ - API docs:
http://commercialbrainz.duckdns.org/docs
- Web:
Add DuckDNS to an existing VM (zone is auto-detected if the VM was created in another region):
DUCKDNS_DOMAIN=commercialbrainz DUCKDNS_TOKEN=your-token \
GCP_PROJECT_ID=your-project ./scripts/setup-duckdns-gcloud.shDuckDNS is ideal with ephemeral GCE IPs (no static IP cost).
Requires DuckDNS pointing at your VM and port 443 open in GCP firewall.
New VM — include your email when deploying:
GCP_PROJECT_ID=your-project \
DUCKDNS_DOMAIN=commercialbrainz \
DUCKDNS_TOKEN=your-token \
ACME_EMAIL=you@example.com \
./scripts/setup-gcloud-vm.shExisting VM:
ACME_EMAIL=you@example.com DUCKDNS_DOMAIN=commercialbrainz \
GCP_PROJECT_ID=your-project ./scripts/setup-https-gcloud.shThen use:
Caddy obtains and renews certificates automatically (HTTP-01 challenge on port 80).
Troubleshooting: run GCP_PROJECT_ID=your-project ./scripts/diagnose-gcloud-vm.sh
Use a second GCE VM for production (testing stays on DuckDNS):
| Branch | VM | URL |
|---|---|---|
testing |
commercialbrainz-vm |
https://commercialbrainz.duckdns.org/ |
cloudflare |
commercialbrainz-public |
https://commercialbrainz.org/ |
Create the public VM once (static IP; no DuckDNS):
- GitHub Actions: Actions → Setup GCE VM → target
cloudflare→ Run workflow
(uses Workload Identity; grants deploy OS Login; prints the IP in the log) - Laptop:
GCP_PROJECT_ID=commercialbrainz \
ACME_EMAIL=you@example.com \
ADMIN_EMAIL=you@example.com \
ADMIN_USERNAME=admin \
ADMIN_PASSWORD='…' \
./scripts/setup-cloudflare-vm.shOptional Action secrets: VM_ADMIN_EMAIL, VM_ADMIN_USERNAME, VM_ADMIN_PASSWORD, ACME_EMAIL.
Then Cloudflare Free + Origin CA (Full strict). See docs/cloudflare-domain.md:
GCP_PROJECT_ID=your-project \
DOMAIN=commercialbrainz.org \
ACME_EMAIL=you@example.com \
ORIGIN_CERT=$HOME/cb-origin.crt \
ORIGIN_KEY=$HOME/cb-origin.key \
./scripts/setup-cloudflare-domain.shCost: Always Free covers one e2-micro (commercialbrainz-vm). The second VM is billed (still cheap). Prefer a static IP on commercialbrainz-public so Cloudflare A records stay stable.
See docs/branches.md. Open all PRs against testing. Promote to cloudflare for the public site.
GitHub Actions .github/workflows/deploy.yml deploys to the matching GCE VM after CI succeeds (testing → testing VM, cloudflare → public VM). Also runnable manually from the Actions tab.
- Create a GCP service account that can SSH to the VM, e.g.:
PROJECT_ID=your-project
gcloud iam service-accounts create github-deploy \
--display-name="GitHub Actions deploy"
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:github-deploy@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/compute.instanceAdmin.v1"
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:github-deploy@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/compute.osAdminLogin"
# Allow the SA to act as itself for OS Login / SSH
gcloud iam service-accounts add-iam-policy-binding \
"github-deploy@${PROJECT_ID}.iam.gserviceaccount.com" \
--member="serviceAccount:github-deploy@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/iam.serviceAccountUser"
gcloud iam service-accounts keys create github-deploy-key.json \
--iam-account="github-deploy@${PROJECT_ID}.iam.gserviceaccount.com"- On each deploy VM, allow GitHub Actions SSH (metadata keys as user
runner). Deploy runsscripts/ensure-gcloud-vm-ssh.shautomatically. If Actions still getsPermission denied (publickey), run once from your laptop:
# Login first (browser)
gcloud auth login
gcloud config set project commercialbrainz
# Testing VM
./scripts/fix-deploy-ssh-from-laptop.sh
# Public VM (after it exists)
VM_NAME=commercialbrainz-public ZONE=YOUR_ZONE ./scripts/fix-deploy-ssh-from-laptop.shThat script disables OS Login on the instance (CI needs metadata SSH), clears
block-project-ssh-keys, ensures firewall/tag, and probes SSH.
- In the GitHub repo: Settings → Secrets and variables → Actions, add:
GCP_PROJECT_ID— your GCP project id (variable; defaultcommercialbrainz)- Workload Identity is preferred (see existing deploy workflow); or
GCP_SA_KEYif using a JSON key
Optional repository variables:
| Variable | Default |
|---|---|
VM_NAME_TESTING |
commercialbrainz-vm (legacy: VM_NAME_GOOGLE) |
VM_NAME_CLOUDFLARE |
commercialbrainz-public |
VM_NAME |
legacy alias for the testing VM |
Manual deploy from your laptop:
# Testing
GCP_PROJECT_ID=your-project APP_BRANCH=testing ./scripts/deploy-gcloud-vm.sh
# Public
GCP_PROJECT_ID=your-project APP_BRANCH=cloudflare ./scripts/deploy-gcloud-vm.shPrerequisites: gcloud CLI, billing enabled, Docker.
chmod +x scripts/setup-gcloud.sh
GCP_PROJECT_ID=your-project ./scripts/setup-gcloud.shThis provisions Cloud SQL, Secret Manager, Artifact Registry, Cloud Run (API + web), and a GCS dump bucket. See scripts/setup-gcloud.sh for the post-deploy checklist.
┌─────────────┐ ┌──────────────┐ ┌────────────┐
│ React Web │────▶│ FastAPI │────▶│ PostgreSQL │
│ (Vite) │ │ /api/v1 │ │ │
└─────────────┘ └──────┬───────┘ └────────────┘
│
┌──────▼───────┐ ┌────────────┐
│ ARQ Worker │────▶│ Redis │
│ edits/dumps │ │ │
└──────────────┘ └────────────┘
Base URL: /api/v1
| Endpoint | Description |
|---|---|
GET /videos/{sbid} |
Video with nested commercial/advertiser |
GET /commercials/{sbid} |
Commercial with linked videos |
GET /advertisers/{sbid} |
Advertiser with commercials |
GET /search?query=&type=video |
Full-text search |
GET /hashes/types |
Media hash types available for grab/lookup |
GET /hashes |
Paginated catalog of video hashes |
GET /hashes/videos/{sbid} |
Grab hashes for a video by CBID |
GET /hashes/youtube/{youtube_id} |
Grab hashes for a video by YouTube ID |
GET /hashes/lookup?phash= |
Lookup videos by perceptual hash (Hamming) |
GET /hashes/lookup?file_sha256= |
Lookup videos by file SHA-256 (exact) |
POST /hashes/lookup |
Same lookups; use for long Chromaprint values |
GET /browse/videos?advertiser=&tag= |
Filtered browse |
GET /edits/{id} |
Public edit history |
GET /dumps/latest |
Latest data dump info |
- User-Agent required:
YourApp/1.0 (contact@example.com) - Rate limits: 1 req/s anonymous, 5 req/s authenticated
- Use ETags: Send
If-None-Matchwith cached ETag for 304 responses - Bulk data: Download nightly dumps from
/api/v1/dumps/latest
OpenAPI spec: /docs or /openapi.json
- Register and log in
- Submit a commercial video via the web UI or
POST /api/v1/edits/submit-video - Edits stay open for 7 days unless resolved early
- 3 unanimous Yes → applied; 3 unanimous No → rejected
- After 7 days: more Yes than No → applied; otherwise rejected
- Voting requires account age ≥ 14 days and ≥ 10 accepted edits
- Mods and admins can auto-apply edits instantly
Copyright holders may submit takedown notices via /dmca or POST /api/v1/dmca. Valid claims hide the YouTube link from public API responses while preserving archival metadata. Contact: commercialbrainz@outlook.com
CommercialBrainz data is released under CC0 1.0 Universal (public domain dedication).
Issues are enabled. Use the forms under New issue:
- Bug report — site, API, auth, or deploy failures
- Feature request — product ideas
- Data / metadata issue — wrong or missing commercials (prefer an on-site edit when you can)
Security vulnerabilities: see SECURITY.md (private advisory — not public issues).
Optional labels (repo admin once):
chmod +x scripts/setup-github-labels.sh
./scripts/setup-github-labels.sh# Backend
cd backend
pip install -e ".[dev]"
alembic upgrade head
uvicorn app.main:app --reload
pytest --cov=app
ruff check app tests
# Frontend
cd frontend
npm ci
npm test
npm run dev
npm run build
# Worker
cd backend
arq app.workers.settings.WorkerSettings
# CLI
commercialbrainz seed-admin --email admin@example.com --username admin
commercialbrainz expire-edits
commercialbrainz generate-dump
commercialbrainz check-youtube-links # monthly dead-link scan (also cron day=1 04:00 UTC)├── backend/ FastAPI + SQLAlchemy + Alembic
├── frontend/ React + Vite + TypeScript
├── infra/ Docker, nginx, GCloud configs
├── scripts/ setup-linux.sh, setup-gcloud.sh
└── .github/workflows/ CI pipeline
Every entity has a CommercialBrainz ID (CBID) — UUID v4 used in URLs and API:
/commercial/550e8400-e29b-41d4-a716-446655440000
/api/v1/videos/550e8400-e29b-41d4-a716-446655440000