Files
oscars-pool/python_video_server/README.md
T
Mathieu Moran b6df70864d Update README files with comprehensive feature documentation
- Main README.md:
  - Reorganized features into User Features and Admin Features sections
  - Added missing features: Oscar picks system, category management, winner management, view all picks, database backup/restore
  - Updated database models section to list all 6 models
  - Simplified Python video server section to reference detailed README
  - Kept preview image and existing structure

- python_video_server/README.md:
  - Enhanced Endpoints section with response codes and detailed descriptions
  - Added comprehensive Functions Reference section:
    - Public Endpoints: create_video_session, stream_video, healthz
    - Helper Functions: all 16 internal functions documented with descriptions
  - Verified all features, environment variables, and endpoints match actual code
  - No content removed, only additions for better documentation
2025-12-28 00:59:27 -05:00

7.6 KiB
Raw Blame History

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 == :id in MongoDB and reads the video_file field
  • Streams the file from VIDEO_FILES_DIR with HTTP Range support (seeking works)
  • Auth via the same JWT (Authorization: Bearer ...), plus a video_auth cookie 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):

  1. video_auth cookie (best for browser <video> tags)
  2. Authorization: Bearer <token> header
  3. ?token=<token> query param (debug only; tokens in URLs leak via logs/referrers)

Because HTML <video> cannot send an Authorization header, the pattern is:

  1. Your JS calls POST /api/video/session with Authorization: Bearer <token>
  2. The server responds 204 and sets a video_auth cookie (scoped to /api/video)
  3. 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_ORIGINS to your exact app origin (not *) and ensure your frontend uses credentials: "include" on the /api/video/session request.
  • If the video host is cross-site, you’ll usually need VIDEO_COOKIE_SAMESITE=None and 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: https so /api/video/session can set Secure cookies 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.pem
  • python_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/AAAA records 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
    Sets video_auth cookie (send Authorization: Bearer <token>). Returns 204 No Content.

  • GET|HEAD /api/video/{movieId}
    Streams the movie file (supports Range: bytes=...). Returns 200 (full file) or 206 (partial content) or 404 (not found).

  • GET /healthz
    Health check endpoint. Returns JSON with server status, video files directory, MongoDB database name, and OpenSSL version.

Functions Reference

Public Endpoints

  • create_video_session(request, response, authorization) - POST /api/video/session - Creates video authentication cookie
  • stream_video(movie_id, request, authorization, range) - GET|HEAD /api/video/{movie_id} - Streams video file with Range support
  • healthz() - GET /healthz - Health check endpoint

Helper Functions (Internal)

  • _env_str(name, default) - Get environment variable as string
  • _jwt_secret() - Get JWT secret from environment
  • _mongo_uri() - Get MongoDB connection URI from environment
  • _mongo_db_name() - Get MongoDB database name (from env or parsed from URI)
  • _env_int(name, default) - Get environment variable as integer
  • _video_files_dir() - Get video files directory path (default: ./public/video)
  • _parse_bearer(authorization) - Parse Bearer token from Authorization header
  • _is_safe_relative_file(path) - Security check: validates file path is safe (no directory traversal)
  • _guess_content_type(file_path) - Guess MIME content type from file extension
  • _parse_range(range_header, file_size) - Parse HTTP Range header into start/end tuple
  • _file_iter(path, start, end, chunk_size) - Generator that yields file chunks for streaming
  • _get_token(request, authorization) - Get authentication token from cookie, header, or query string (priority order)
  • _verify_token(token) - Verify JWT token and return decoded payload
  • _mongo_client() - Create MongoDB client with TLS/connection timeout configuration
  • _mongo_client_cached() - Cached MongoDB client (LRU cache, reused across requests)
  • _shutdown() - Event handler for graceful MongoDB client shutdown