* Refactor READMEs and clarify optional features Co-authored-by: mathieumoran1 <[email protected]> * Add video auth details and Let's Encrypt helper Co-authored-by: mathieumoran1 <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]>
Python video server (Range streaming + JWT + Mongo)
This is a small standalone server that mirrors the app’s /api/video/:id behavior:
- Looks up
movies._id == :idin MongoDB and reads thevideo_filefield - Streams the file from
VIDEO_FILES_DIRwith HTTP Range support (seeking works) - Auth via the same JWT (
Authorization: Bearer ...), plus avideo_authcookie for<video>
Run
python -m pip install -r python_video_server/requirements.txt
export MONGO_URI="..."
export MONGO_DB_NAME="..." # optional; required if MONGO_URI doesn't include /<dbName>
export JWT_SECRET="..."
export VIDEO_FILES_DIR="/absolute/path/to/public/video" # optional (default: ./public/video)
export VIDEO_SESSION_MAX_AGE_SECONDS="28800" # optional (default: 8h)
uvicorn python_video_server.server:app --host 0.0.0.0 --port 8000
Extra environment variables (optional)
# CORS (needed when the main app is on a different origin)
export CORS_ALLOW_ORIGINS="https://your-app.example.com" # default: "*"
# Cookie behavior for /api/video/session
export VIDEO_COOKIE_SAMESITE="Lax" # Lax|Strict|None (default: Lax)
# Mongo TLS / connectivity troubleshooting (Atlas, odd hosts, etc.)
export MONGO_TLS_CA_FILE="/path/to/ca.pem" # prefer a valid CA bundle
export MONGO_TLS_FORCE_TLS12="1" # force TLS 1.2 if TLS 1.3 breaks
export MONGO_TLS_INSECURE="1" # NOT recommended; debug only
export MONGO_SERVER_SELECTION_TIMEOUT_MS="8000"
export MONGO_CONNECT_TIMEOUT_MS="8000"
export MONGO_SOCKET_TIMEOUT_MS="20000"
Video auth / usage
GET /api/video/{movieId} accepts auth in three ways (in priority order):
video_authcookie (best for browser<video>tags)Authorization: Bearer <token>header?token=<token>query param (debug only; tokens in URLs leak via logs/referrers)
Browser <video> (cookie flow)
Because HTML <video> cannot send an Authorization header, the pattern is:
- Your JS calls
POST /api/video/sessionwithAuthorization: Bearer <token> - The server responds
204and sets avideo_authcookie (scoped to/api/video) - Your
<video>loads/api/video/{movieId}and the cookie is used automatically
Example (curl):
# 1) exchange header token -> cookie
curl -i -X POST "https://video.example.com/api/video/session" \
-H "Authorization: Bearer $JWT"
# 2) then the <video> element can simply use:
# https://video.example.com/api/video/<movieId>
Cross-site note (frontend on a different origin):
- Set
CORS_ALLOW_ORIGINSto your exact app origin (not*) and ensure your frontend usescredentials: "include"on the/api/video/sessionrequest. - If the video host is cross-site, you’ll usually need
VIDEO_COOKIE_SAMESITE=Noneand HTTPS, otherwise browsers may block the cookie.
Non-browser clients (header flow)
curl -I "https://video.example.com/api/video/<movieId>" \
-H "Authorization: Bearer $JWT"
Token in URL (debug flow)
curl -I "https://video.example.com/api/video/<movieId>?token=$JWT"
Seeking / Range requests (example)
curl -i "https://video.example.com/api/video/<movieId>" \
-H "Authorization: Bearer $JWT" \
-H "Range: bytes=0-1023"
HTTPS (optional)
You can run behind a reverse proxy/ingress, or run uvicorn with TLS directly:
uvicorn python_video_server.server:app --host 0.0.0.0 --port 443 \
--ssl-certfile /path/to/fullchain.pem \
--ssl-keyfile /path/to/privkey.pem
Notes:
- If you’re behind a proxy, forward
X-Forwarded-Proto: httpsso/api/video/sessioncan setSecurecookies correctly.
Let’s Encrypt certs (python-only helper)
This repo includes python_video_server/letsencrypt.py, a wrapper around the certbot Python package (no system certbot needed).
Certificates (and certbot state) are stored under:
python_video_server/certs/config/live/<primary-domain>/fullchain.pempython_video_server/certs/config/live/<primary-domain>/privkey.pem
You can override the storage directory with LETSENCRYPT_BASE_DIR (optional).
Issue a cert (HTTP-01 standalone, easiest)
Requirements:
- DNS
A/AAAArecords point to this host - Port 80 reachable from the internet
- You can bind port 80 (often requires
sudo)
python -m pip install -r python_video_server/requirements.txt
# staging by default (recommended first)
sudo -E python -m python_video_server.letsencrypt certonly \
--email [email protected] \
--domains example.com,www.example.com
# production (real cert)
sudo -E python -m python_video_server.letsencrypt certonly \
--email [email protected] \
--domains example.com,www.example.com \
--production
Then run uvicorn with those certs:
sudo -E uvicorn python_video_server.server:app --host 0.0.0.0 --port 443 \
--ssl-certfile python_video_server/certs/config/live/example.com/fullchain.pem \
--ssl-keyfile python_video_server/certs/config/live/example.com/privkey.pem
Issue a cert (DNS-01 manual TXT, no port 80)
Use this if you cannot expose/bind port 80. Certbot will prompt you to create a TXT record.
python -m pip install -r python_video_server/requirements.txt
python -m python_video_server.letsencrypt certonly \
--challenge dns \
--email [email protected] \
--domains example.com,www.example.com \
--production
Renew certs
# dry-run (safe)
sudo -E python -m python_video_server.letsencrypt renew --dry-run
# real renewal
sudo -E python -m python_video_server.letsencrypt renew --production
Endpoints
-
POST /api/video/session
Setsvideo_authcookie (sendAuthorization: Bearer <token>). -
GET|HEAD /api/video/{movieId}
Streams the movie file (supportsRange: bytes=...). -
GET /healthz