2DManim is now a TypeScript Next.js application for creating AI-powered Manim animation jobs from natural language prompts.
The current implementation follows the MVP contracts in SYSTEM_DESIGN.md:
- Next.js app router for UI and API routes
- token auth backed by Postgres users
- Prisma-backed chat/message/job/video records
POST /api/generationscreates a queued generation job and returns quicklyGET /api/jobs/:idpolls job progress and returns job metadata- BullMQ enqueues generation work into Redis
- worker service generates Manim code and optionally renders video through Docker
- App: Next.js 14 + React + TypeScript
- UI: Material UI
- Local auth: HMAC-signed bearer tokens
- Database: Postgres + Prisma
- Queue: Redis + BullMQ
- Rendering: Dockerized Manim Community image
- Storage: S3-compatible object storage, including MinIO locally
Install dependencies:
npm installStart the app:
npm run devOpen:
http://localhost:3000
Build check:
npm run buildIf you do not use Docker Compose, run Postgres locally with:
docker run --name 2dmanim-postgres \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=2dmanim \
-p 5432:5432 \
-d postgres:16Your local .env should include:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/2dmanim
If you do not use Docker Compose, run Redis locally with:
docker run --name 2dmanim-redis \
-p 6379:6379 \
-d redis:7 redis-server --appendonly yesYour local .env should include:
REDIS_URL=redis://localhost:6379
Useful Redis checks:
docker exec -it 2dmanim-redis redis-cli ping
docker logs -f 2dmanim-redisDocker Compose starts MinIO for local S3-compatible object storage:
API: http://localhost:9000
Console: http://localhost:9001
Login: minioadmin / minioadmin
Bucket: 2dmanim-videos
If you run MinIO manually instead of Compose:
docker run --name 2dmanim-minio \
-p 9000:9000 \
-p 9001:9001 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
-d minio/minio server /data --console-address ":9001"Then create a public-download bucket:
docker run --rm --network container:2dmanim-minio minio/mc \
sh -c 'mc alias set local http://127.0.0.1:9000 minioadmin minioadmin && mc mb -p local/2dmanim-videos && mc anonymous set download local/2dmanim-videos'Copy the example file if needed:
cp .env.example .envImportant local variables:
AUTH_SECRETDATABASE_URLGEMINI_API_KEYGEMINI_MODELMISTRAL_API_KEYMISTRAL_MODELENABLE_DOCKER_RENDERMANIM_DOCKER_IMAGEMANIM_OUTPUT_DIRREDIS_URLOBJECT_STORAGE_BUCKETOBJECT_STORAGE_REGIONOBJECT_STORAGE_ENDPOINTOBJECT_STORAGE_FORCE_PATH_STYLEOBJECT_STORAGE_ACCESS_KEY_IDOBJECT_STORAGE_SECRET_ACCESS_KEYOBJECT_STORAGE_PUBLIC_BASE_URLCDN_BASE_URL
OBJECT_STORAGE_* is provider-neutral. It works with MinIO, Cloudflare R2, DigitalOcean Spaces, Backblaze B2, Wasabi, AWS S3, and other S3-compatible providers. The older S3_* names are still accepted as aliases.
The Prisma schema lives at:
prisma/schema.prisma
Generate the Prisma client:
npm run db:generateValidate the schema:
npm run db:validateApply the schema to a local Postgres database:
npm run db:pushCreate a migration once the schema is ready to commit as a database change:
npm run db:migrateStart the BullMQ generation worker:
npm run worker:devThe worker currently:
- listens on the
generation-jobsBullMQ queue - reads
{ jobId }from each queue job - atomically claims Postgres jobs whose status is
QUEUEDorFAILED_RETRYABLE - generates Manim Python code with Gemini or Mistral when an API key is configured
- falls back to local mock Manim code when no AI provider key exists
- validates that generated code defines
GeneratedScene(Scene)and avoids blocked Python APIs - writes
JobStatusHistoryrows for each state transition - marks jobs as code-only completed when Docker rendering is disabled
- renders MP4s through Docker when
ENABLE_DOCKER_RENDER=true - uploads rendered MP4s to S3-compatible object storage
- marks exhausted BullMQ retries as
DEAD_LETTER_QUEUE
Set one of these for real AI code generation:
GEMINI_API_KEY=...
# or
MISTRAL_API_KEY=...
Without an API key, the worker still completes jobs using deterministic local preview code.
Pull the Manim Docker image:
docker pull manimcommunity/manim:stableEnable rendering in .env:
ENABLE_DOCKER_RENDER=true
MANIM_DOCKER_IMAGE=manimcommunity/manim:stable
MANIM_OUTPUT_DIR=public/generated/videos
MANIM_PUBLIC_BASE_PATH=/generated/videos
OBJECT_STORAGE_BUCKET=2dmanim-videos
OBJECT_STORAGE_REGION=us-east-1
OBJECT_STORAGE_ENDPOINT=http://localhost:9000
OBJECT_STORAGE_FORCE_PATH_STYLE=true
OBJECT_STORAGE_ACCESS_KEY_ID=minioadmin
OBJECT_STORAGE_SECRET_ACCESS_KEY=minioadmin
OBJECT_STORAGE_PUBLIC_BASE_URL=http://localhost:9000/2dmanim-videos
Rendered local videos are written to:
public/generated/videos/<jobId>/output.mp4
The worker uploads that MP4 to object storage and stores the playable URL in Postgres. With local MinIO, the stored URL looks like:
http://localhost:9000/2dmanim-videos/videos/<userId>/<jobId>/output.mp4
When running the worker inside Docker Compose, the worker container mounts /var/run/docker.sock so it can start Manim render containers on the host Docker daemon.
For a non-Amazon provider, set OBJECT_STORAGE_ENDPOINT to the provider's S3-compatible endpoint and keep OBJECT_STORAGE_FORCE_PATH_STYLE=true if that provider expects path-style bucket URLs.
Example shape:
OBJECT_STORAGE_BUCKET=2dmanim-videos-prod
OBJECT_STORAGE_REGION=auto
OBJECT_STORAGE_ENDPOINT=https://<provider-s3-endpoint>
OBJECT_STORAGE_FORCE_PATH_STYLE=true
OBJECT_STORAGE_ACCESS_KEY_ID=...
OBJECT_STORAGE_SECRET_ACCESS_KEY=...
OBJECT_STORAGE_PUBLIC_BASE_URL=https://<public-bucket-or-r2-domain>
CDN_BASE_URL=https://cdn.example.com
If CDN_BASE_URL is set, it is preferred for video playback URLs. Otherwise OBJECT_STORAGE_PUBLIC_BASE_URL is used.
Run Postgres, Redis, the Next.js app, and the worker:
docker compose up --buildRun only the worker with Compose:
docker compose up --build worker