Code2Concept is an AI-powered educational video generation platform that transforms any topic into an animated teaching video. It combines deterministic script generation with optional AI enhancement (Google Gemini) and Manim-based mathematical animation rendering β all served through a clean FastAPI backend.
- Project Overview
- Tech Stack
- Directory Structure
- System Architecture
- Component Breakdown
- Data Flow
- API Reference
- Layered Architecture
- Error Handling & Graceful Fallbacks
- Future Improvements & Roadmap
Code2Concept automates the creation of short educational animation videos. A user submits a topic (e.g., "Binary Search"), and the system:
- Generates a structured teaching script with a title and step-by-step explanations
- Optionally enriches the script with AI-powered research via Google Gemini
- Produces Manim animation code that visualizes the teaching content
- Optionally refines the generated code with AI to fix errors and improve quality
- Cleans, saves, and renders the code into an MP4 video using the Manim engine
- Serves the video to the user via a REST API endpoint
The architecture follows a resilient pipeline pattern β the core flow always succeeds using deterministic templates, while AI-powered stages enhance quality when available and fail gracefully when they don't.
| Layer | Technology | Purpose |
|---|---|---|
| Web Framework | FastAPI | Async REST API with automatic OpenAPI docs |
| ASGI Server | Uvicorn | High-performance async server |
| Animation | Manim (Community Edition) | Programmatic math/educational animations |
| AI / LLM | Google Gemini 1.5 Flash | Topic research & code refinement |
| Validation | Pydantic | Request/response data validation |
| Configuration | python-dotenv | Environment variable management |
| Language | Python 3.10+ | Primary development language |
ARCHITECTURE.md # Detailed architecture document
architecture.tldr # Short architecture summary
README.md # Project overview and setup docs
backend/
βββ app/ # FastAPI backend source code
β βββ main.py # App entrypoint and router registration
β βββ routes/
β β βββ generate.py # /generate and /video endpoints
β βββ schemas/
β β βββ request.py # Pydantic request schema
β βββ services/
β β βββ llm_service.py # Deterministic script and Manim code generation
β β βββ research_service.py # Gemini-based topic research
β β βββ refine_service.py # Gemini-based code refinement
β β βββ animation_service.py # Experimental animation generation path
β β βββ manim_service.py # Save scene code and render with Manim CLI
β βββ utils/
β βββ clean_code.py # Removes markdown fences from generated code
βββ generated/
β βββ scene.py # Generated Manim scene (overwritten per request)
β βββ media/ # Intermediate render artifacts from Manim
βββ media/
β βββ images/scene/ # Frame/image artifacts
β βββ texts/ # Manim text cache
β βββ videos/scene/480p15/
β βββ partial_movie_files/
β βββ DemoScene/ # Partial clips and ffmpeg list files
βββ requirements.txt # Python dependencies
βββ .env # Backend environment variables
frontend/
βββ index.html # Vite HTML entrypoint
βββ package.json # Frontend scripts and dependencies
βββ package-lock.json # Locked dependency graph
βββ vite.config.js # Vite configuration
βββ eslint.config.js # ESLint configuration
βββ public/ # Static assets served as-is
βββ src/
βββ main.jsx # React bootstrap
βββ App.jsx # Root app component
βββ api.js # API client helper
βββ App.css # App-level styles
βββ index.css # Global styles
βββ assets/ # Bundled assets
βββ components/
βββ Loader.jsx # Loading indicator component
βββ VideoPlayer.jsx # Video playback component
βββ VideoPlayer.css # Styles for video player
| Directory | Role |
|---|---|
backend/app/routes |
Thin controller layer that orchestrates the generation pipeline and delegates to services |
backend/app/services |
Business logic layer for script generation, research, refinement, and rendering |
backend/app/schemas |
API contracts and input validation via Pydantic |
backend/generated |
Ephemeral generated code and intermediate artifacts |
backend/media |
Render output and caches produced by Manim |
frontend/src |
React UI source, API integration, and reusable components |
The system follows a linear pipeline architecture where a user request flows through a series of processing stages. Each stage transforms or enriches the data before passing it to the next.
flowchart TD
A["π§βπ» Client\nPOST /generate {topic}"] --> B["π‘οΈ FastAPI Router\nValidate request via Pydantic"]
B --> C["π Script Generator\n(llm_service.py)"]
C --> D{"π¬ Research\nEnhancement?"}
D -- "β
Gemini Available" --> E["π€ AI Research\n(research_service.py)\nGemini 1.5 Flash"]
D -- "β Gemini Unavailable" --> F["βοΈ Skip Research\nContinue with base script"]
E --> G["π¬ Manim Code Generator\n(llm_service.py)"]
F --> G
G --> H{"π§ Code\nRefinement?"}
H -- "β
Gemini Available" --> I["π€ AI Refinement\n(refine_service.py)\nGemini 1.5 Flash"]
H -- "β Gemini Unavailable" --> J["βοΈ Skip Refinement\nUse raw generated code"]
I --> K["π§Ή Clean Code\n(clean_code.py)\nStrip markdown fences"]
J --> K
K --> L["πΎ Save Code\n(manim_service.py)\nWrite to generated/scene.py"]
L --> M["π₯ Render Video\n(manim_service.py)\nmanim CLI subprocess"]
M --> N{"πΉ Video\nExists?"}
N -- "β
Yes" --> O["β
Return Success\n{message, topic}"]
N -- "β No" --> P["β Return Error\n{error: 'Video not generated'}"]
O --> Q["π§βπ» Client\nGET /video"]
Q --> R["π€ Serve MP4\nFileResponse"]
style A fill:#4A90D9,stroke:#2C6BAE,color:#FFFFFF
style B fill:#7B68EE,stroke:#5A4FCF,color:#FFFFFF
style C fill:#2ECC71,stroke:#27AE60,color:#FFFFFF
style E fill:#F39C12,stroke:#E67E22,color:#FFFFFF
style G fill:#2ECC71,stroke:#27AE60,color:#FFFFFF
style I fill:#F39C12,stroke:#E67E22,color:#FFFFFF
style K fill:#1ABC9C,stroke:#16A085,color:#FFFFFF
style L fill:#3498DB,stroke:#2980B9,color:#FFFFFF
style M fill:#E74C3C,stroke:#C0392B,color:#FFFFFF
style O fill:#2ECC71,stroke:#27AE60,color:#FFFFFF
style P fill:#E74C3C,stroke:#C0392B,color:#FFFFFF
style R fill:#9B59B6,stroke:#8E44AD,color:#FFFFFF
app = FastAPI()
app.include_router(router)- Creates the FastAPI application instance
- Registers the single router from
routes/generate.py - Serves as the ASGI entry point for Uvicorn (
uvicorn app.main:app)
The central nervous system of the application. This module defines two endpoints and orchestrates the entire video generation pipeline:
| Endpoint | Method | Description |
|---|---|---|
/generate |
POST | Accepts a topic, runs the full pipeline |
/video |
GET | Serves the most recently rendered MP4 video |
The /generate handler executes all pipeline stages sequentially, wrapping optional AI stages in try/except blocks for graceful degradation.
class Query(BaseModel):
topic: str- Defines the data contract for the
/generateendpoint - Leverages Pydantic for automatic validation, serialization, and OpenAPI schema generation
- Rejects requests missing the
topicfield with a 422 Unprocessable Entity response
This is the deterministic backbone of the system. It contains two functions:
| Function | Input | Output |
|---|---|---|
generate_teaching_script(topic) |
Topic string | Dict with title and steps list |
generate_manim_from_script(script) |
Script dict | Raw Manim Python code string |
Script Generation Strategy:
- Maintains a lookup of hardcoded, curated scripts for known topics (
binary search,AI) - Falls back to a generic template for unknown topics
- Zero external dependencies β this function never fails
Code Generation Strategy:
- Builds a Manim
DemoSceneclass programmatically - Iterates over script steps, creating
Textobjects with staggered vertical positioning - Produces syntactically valid Manim code that can render independently of AI services
| Function | Input | Output |
|---|---|---|
generate_research(topic) |
Topic string | Structured JSON string or None |
- Calls Google Gemini 1.5 Flash with a structured prompt requesting JSON output
- Retrieves detailed concept explanations, steps, and examples
- Loads API key from
GEMINI_API_KEYenvironment variable viapython-dotenv - Returns
Noneon any failure β designed to be non-blocking - Currently, research output is retrieved but not yet merged into the script (future enhancement)
| Function | Input | Output |
|---|---|---|
refine_code(code) |
Raw Manim code | Improved Manim code string |
- Sends the generated Manim code to Gemini 1.5 Flash for review and improvement
- Prompt instructs the AI to fix syntax errors, improve animations, and ensure clean structure
- On failure, the calling code falls back to the raw unrefined code
- Creates a new
genai.Client()instance per call (relies on default env-based auth)
| Function | Input | Output |
|---|---|---|
generate_animation_code(research) |
Research data | Manim code string |
- Not currently used in the main pipeline
- Provides an alternative approach: converts raw research data directly into Manim code via Gemini
- Intended for future use when research data is integrated into the generation flow
- Follows the same Gemini 1.5 Flash pattern as other AI services
| Function | Input | Output / Side Effect |
|---|---|---|
save_code(code) |
Code string | Writes to generated/scene.py |
render_video() |
None | Executes Manim CLI, produces MP4 in media/ |
save_code: Creates thegenerated/directory if needed, overwritesscene.pyevery timerender_video: Spawns a subprocess runningmanim generated/scene.py DemoScene -pql-p: Preview flag (opens video player β may need removal in production)-ql: Quality low (480p at 15fps) for fast rendering- Output lands at
media/videos/scene/480p15/DemoScene.mp4
| Function | Input | Output |
|---|---|---|
clean_code(code) |
Raw string | Cleaned string |
- Strips markdown code fences (
```pythonand```) that LLMs commonly wrap around code output - Pure function with no side effects β essential when AI refinement is active
- Ensures the saved
.pyfile contains only valid Python
sequenceDiagram
actor User
participant Router as π‘οΈ FastAPI Router<br/>(generate.py)
participant LLM as π LLM Service<br/>(llm_service.py)
participant Research as π¬ Research Service<br/>(research_service.py)
participant Gemini as π€ Google Gemini<br/>1.5 Flash
participant Refine as π§ Refine Service<br/>(refine_service.py)
participant Clean as π§Ή Clean Code<br/>(clean_code.py)
participant Manim as π₯ Manim Service<br/>(manim_service.py)
participant FS as πΎ File System
participant CLI as β‘ Manim CLI<br/>(subprocess)
User->>+Router: POST /generate {"topic": "Binary Search"}
Note over Router: Pydantic validates Query model
Router->>+LLM: generate_teaching_script("Binary Search")
LLM-->>-Router: {title: "Binary Search", steps: [...]}
Router->>+Research: generate_research("Binary Search")
Research->>+Gemini: Prompt: Explain "Binary Search" as JSON
alt Gemini Available
Gemini-->>Research: Structured JSON explanation
Research-->>Router: JSON research data
else Gemini Unavailable
Gemini-->>Research: Error / Timeout
Research-->>-Router: None (graceful failure)
end
Note over Router: Research retrieved (future: merge into script)
Router->>+LLM: generate_manim_from_script(script)
LLM-->>-Router: Raw Manim Python code
Router->>+Refine: refine_code(raw_code)
Refine->>+Gemini: Prompt: Fix and improve this Manim code
alt Gemini Available
Gemini-->>Refine: Refined Manim code
Refine-->>Router: Refined code
else Gemini Unavailable
Refine-->>-Router: Exception thrown
Note over Router: Catch exception β use raw_code
end
Router->>+Clean: clean_code(final_code)
Clean-->>-Router: Sanitized Python code
Router->>+Manim: save_code(code)
Manim->>FS: Write β generated/scene.py
Manim-->>-Router: Done
Router->>+Manim: render_video()
Manim->>+CLI: manim generated/scene.py DemoScene -pql
CLI->>FS: Write β media/videos/scene/480p15/DemoScene.mp4
CLI-->>-Manim: Process complete
Manim-->>-Router: Done
Router-->>-User: {"message": "Video generated successfully", "topic": "Binary Search"}
Note over User, FS: Video retrieval (separate request)
User->>+Router: GET /video
Router->>FS: Check media/videos/scene/480p15/DemoScene.mp4
FS-->>Router: File exists
Router-->>-User: πΉ MP4 FileResponse (video/mp4)
"Binary Search" β Topic string
β
βΌ
{title, steps[]} β Structured script dictionary
β
βΌ
"from manim import *..." β Raw Manim Python source code
β
βΌ
"from manim import *..." β Refined code (AI-improved or unchanged)
β
βΌ
"from manim import *..." β Cleaned code (markdown fences stripped)
β
βΌ
generated/scene.py β File on disk
β
βΌ
DemoScene.mp4 β Rendered animation video
Triggers the full video generation pipeline for a given topic.
Request Body:
{
"topic": "Binary Search"
}| Field | Type | Required | Description |
|---|---|---|---|
topic |
string | β Yes | The educational topic to animate |
Success Response (200 OK):
{
"message": "Video generated successfully",
"topic": "Binary Search"
}Error Responses:
| Scenario | Response |
|---|---|
| Video not generated | {"error": "Video not generated"} |
| Pipeline exception | {"error": "<exception message>"} |
| Missing/invalid topic | 422 Unprocessable Entity (Pydantic) |
Serves the most recently rendered video file.
Response:
| Scenario | Response |
|---|---|
| Video exists | 200 OK β MP4 binary stream (Content-Type: video/mp4) |
| Video not found | {"error": "Video not found"} |
β οΈ Note: This endpoint always serves the latest generated video. There is no per-request video isolation β concurrent requests will overwrite each other.
block-beta
columns 1
block:CLIENT["π CLIENT LAYER"]
A["HTTP Client (Browser, curl, Frontend App)"]
end
space
block:API["π‘οΈ API LAYER β FastAPI"]
B["main.py β App Factory"]
C["routes/generate.py β Endpoint Definitions"]
D["schemas/request.py β Pydantic Validation"]
end
space
block:CORE["π CORE SERVICE LAYER β Deterministic (Always Works)"]
E["llm_service.py β Script Generation"]
F["llm_service.py β Manim Code Generation"]
end
space
block:AI["π€ AI ENHANCEMENT LAYER β Optional (Graceful Fallback)"]
G["research_service.py β Topic Research"]
H["refine_service.py β Code Refinement"]
I["animation_service.py β Alt Generator (Experimental)"]
end
space
block:INFRA["βοΈ INFRASTRUCTURE LAYER"]
J["manim_service.py β File I/O & Rendering"]
K["clean_code.py β Output Sanitization"]
L["Manim CLI β Subprocess Execution"]
end
space
block:STORAGE["πΎ STORAGE LAYER"]
M["generated/scene.py β Ephemeral Scene File"]
N["media/ β Rendered Video Artifacts"]
end
CLIENT --> API
API --> CORE
API --> AI
CORE --> INFRA
AI --> INFRA
INFRA --> STORAGE
style CLIENT fill:#4A90D9,color:#FFFFFF
style API fill:#7B68EE,color:#FFFFFF
style CORE fill:#2ECC71,color:#FFFFFF
style AI fill:#F39C12,color:#FFFFFF
style INFRA fill:#3498DB,color:#FFFFFF
style STORAGE fill:#95A5A6,color:#FFFFFF
| Layer | Responsibility | Failure Impact |
|---|---|---|
| Client Layer | Sends HTTP requests, receives responses and video files | N/A |
| API Layer | Request validation, pipeline orchestration, response formatting | β Full failure |
| Core Service Layer | Deterministic script and code generation β no external dependencies | β Full failure (critical) |
| AI Enhancement Layer | Optional AI-powered research and refinement β requires Gemini API | β Graceful degradation |
| Infrastructure Layer | File I/O, code cleaning, subprocess management | β Full failure |
| Storage Layer | Ephemeral file system for generated code and rendered video artifacts | β Full failure |
Code2Concept employs a defense-in-depth error handling strategy that separates critical operations from optional enhancements.
flowchart LR
subgraph CRITICAL["π΄ Critical Path β Must Succeed"]
A["Script Generation"] --> B["Code Generation"]
B --> C["Clean Code"]
C --> D["Save to Disk"]
D --> E["Render Video"]
end
subgraph OPTIONAL["π‘ Optional Enhancements β May Fail"]
F["AI Research\n(research_service)"]
G["AI Refinement\n(refine_service)"]
end
F -.->|"Enhances (future)"| A
G -.->|"Improves"| B
style CRITICAL fill:#FDECEA,stroke:#E74C3C,color:#333333
style OPTIONAL fill:#FFF9E6,stroke:#F39C12,color:#333333
| Component | Failure Cause | Handling Strategy | User Impact |
|---|---|---|---|
| Script Generation | None (deterministic) | Always succeeds β uses hardcoded templates | None |
| AI Research | API key missing, network error, quota | try/except β logs warning, continues pipeline |
Slightly less detail |
| Code Generation | None (deterministic) | Always succeeds β builds code from script dict | None |
| AI Refinement | API key missing, network error, quota | try/except β falls back to raw unrefined code |
May have rough edges |
| Code Cleaning | None (string replacement) | Always succeeds | None |
| File Save | Disk permissions, disk full | Exception propagates β top-level catch returns error | Full failure |
| Video Rendering | Manim not installed, syntax error | Subprocess may fail β video file missing β error returned | Full failure |
| Video Serving | File not found | Returns {"error": "Video not found"} |
No video available |
The entire /generate endpoint is wrapped in a master try/except:
@router.post("/generate")
async def generate(q: Query):
try:
# ... full pipeline ...
except Exception as e:
return {"error": str(e)}This ensures the API never returns a 500 Internal Server Error β all exceptions are caught and returned as structured JSON error responses.
| Improvement | Description |
|---|---|
| Merge research into scripts | Feed generate_research() output into script generation for richer, AI-informed content |
| Activate animation_service.py | Integrate the experimental Gemini-based animation generator as an alternative pipeline path |
Remove -p preview flag |
Strip the Manim preview flag for headless server deployment |
| Add request ID tracking | Assign unique IDs to each generation request for logging and video isolation |
| Per-request video storage | Store videos with unique filenames to prevent concurrent request conflicts |
| Improvement | Description |
|---|---|
| Async rendering pipeline | Move video rendering to a background task (Celery/ARQ) with status polling |
| Video quality options | Allow clients to specify resolution/quality (-ql, -qm, -qh, -qk) |
| Caching layer | Cache generated videos by topic to avoid redundant rendering |
| Database integration | Store generation history, scripts, and video metadata in PostgreSQL/SQLite |
| Streaming responses | Return video generation progress via WebSocket or SSE |
| Proper HTTP status codes | Use 404, 500, etc. instead of 200 with error JSON bodies |
| Improvement | Description |
|---|---|
| Multi-scene support | Generate multi-scene videos with transitions for complex topics |
| Custom animation styles | Let users choose visual themes (dark mode, colorful, minimalist) |
| Voice-over integration | Generate narration audio (TTS) and sync it with the animation |
| Frontend application | Build a React/Next.js UI for topic input, progress tracking, and video playback |
| Containerized deployment | Dockerize the full stack (FastAPI + Manim + LaTeX dependencies) for one-command deployment |
| LLM provider abstraction | Support multiple AI providers (OpenAI, Anthropic, local models) behind a unified interface |
Code2Concept β Turning concepts into animations, one topic at a time.