Django example on GitHub
Django 6, no database, started with one
uv run command.Quickstart
1
Add your library credentials
.env
.env on its own, and uv run --env-file .env manage.py runserver loads it for you.2
Create the Bunny Stream module
urllib covers the two API calls. The upload signature is a SHA-256 of the library ID, API key, expiry, and video ID.uploads/bunny_stream.py
3
Add the views
create_upload re-signs an unfinished video when the browser sends its ID, and creates a new one otherwise.uploads/views.py
config/urls.py
4
Upload from the browser
Render the CSRF token into the page. The CSRF middleware rejects the There’s no build step. tus-js-client comes from jsDelivr, and the token travels as tus-js-client sends the credentials as headers with every request. The video ID goes into
POST without it.uploads/templates/uploads/index.html
X-CSRFToken.uploads/static/uploads/video-uploader.js
localStorage against the file, which is how a reload finds its way back to the same upload.abort() pauses. start() picks up from the last chunk we acknowledged.A 401 from the TUS endpoint means the signature doesn’t match the headers. Check that the library ID and API key belong to the same library. Re-signing keeps the upload’s original expiry, as the TUS FAQ explains.Play it once it’s encoded
We start encoding when the last chunk arrives. Poll your status route untilstatus reaches 4 (finished), 5 or 6 (failed), then embed embedUrl.
encodeProgress gives you a percentage to show in the meantime. A webhook tells your server when encoding finishes.
Before you deploy
Wrap both views inlogin_required and record who owns each video ID. The example’s settings.py is for development, and production needs DJANGO_SECRET_KEY, DEBUG = False, and ALLOWED_HOSTS.
Troubleshooting
/api/uploads returns 403 with CSRF verification failed
/api/uploads returns 403 with CSRF verification failed
The template needs the
csrf-token meta tag, and the fetch needs the X-CSRFToken header.