Python-based video processing backend running on Modal serverless GPU infrastructure.
The backend is responsible for the entire video processing pipeline:
- Video transcription with WhisperX
- AI-powered moment identification with Google Gemini
- Active speaker detection with LR-ASD
- Vertical video creation with smart cropping
- Karaoke-style subtitle generation and burn-in
- Runtime: Python 3.12
- Framework: FastAPI
- Infrastructure: Modal (Serverless GPU - L40S)
- AI Models:
- WhisperX large-v2 (speech-to-text)
- Google Gemini 2.5 Pro (content analysis)
- LR-ASD (active speaker detection)
- Video Processing:
- FFmpeg / ffmpegcv (encoding/decoding)
- OpenCV (image manipulation)
- PyTorch 2.4.1 + CUDA 12.4
- Storage: AWS S3 (boto3)
- Python 3.12+
- Modal account (sign up)
- Google Gemini API key
- AWS S3 bucket and credentials
# Clone and navigate to backend
cd backend
# Create virtual environment
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Install Modal CLI
pip install modal
# Authenticate with Modal
modal token new# From root directory
git submodule update --init --recursive- Copy
.env.exampleto.env:
cp .env.example .env-
Configure environment variables (see
.env.example) -
Create Modal secrets:
modal secret create clipzz-secrets \
AWS_ACCESS_KEY_ID="your-key" \
AWS_SECRET_ACCESS_KEY="your-secret" \
GEMINI_API_KEY="your-gemini-key" \
AUTH_TOKEN="your-auth-token"# Serve function locally (for development)
modal serve main.pyThis will output a local endpoint URL you can test with:
curl -X POST "https://your-app--dev.modal.run/process_video" \
-H "Authorization: Bearer your-auth-token" \
-H "Content-Type: application/json" \
-d '{"s3_key": "user-id/video-id/video.mp4", "num_clips": 3}'modal deploy main.pyCopy the production endpoint URL to your frontend's .env:
PROCESS_VIDEO_ENDPOINT="https://your-app--clipzz-video-processor-process-video.modal.run"app = modal.App("clipzz-video-processor")
# Persistent volume for model caching
volume = modal.Volume.from_name("clipzz-models", create_if_missing=True)
# Custom CUDA image with all dependencies
image = (
modal.Image.debian_slim(python_version="3.12")
.apt_install(["ffmpeg", "git", "libsndfile1", ...])
.pip_install(["torch==2.4.1+cu124", "whisperx", ...])
.run_commands([...])
)
@app.function(
gpu="L40S", # NVIDIA L40S GPU
timeout=900, # 15 minutes
image=image,
volumes={"/root/.cache": volume} # Cache models between runs
)
def process_video(request: ProcessVideoRequest, token: str):
# Main processing logic
...┌──────────────────────────────────────────────────────────┐
│ 1. Download Video from S3 │
└───────────────────┬──────────────────────────────────────┘
│
┌───────────────────▼──────────────────────────────────────┐
│ 2. Transcribe with WhisperX │
│ - Detect language (auto) │
│ - Transcribe audio → text │
│ - Align word-level timestamps │
│ - Transliterate Hindi (if detected) │
└───────────────────┬──────────────────────────────────────┘
│
┌───────────────────▼──────────────────────────────────────┐
│ 3. Identify Moments with Gemini │
│ - Analyze full transcript │
│ - Extract engaging Q&A segments │
│ - Return 30-60s clip boundaries │
└───────────────────┬──────────────────────────────────────┘
│
▼
┌──────────────────────┐
│ For Each Clip │
└──────────┬───────────┘
│
┌──────────────▼──────────────────┐
│ 4. Extract Video Segment │
│ - Cut video based on timing │
│ - Extract audio track │
└──────────────┬──────────────────┘
│
┌──────────────▼──────────────────┐
│ 5. Active Speaker Detection │
│ - Run LR-ASD model │
│ - Track faces across frames │
│ - Score speaking activity │
└──────────────┬──────────────────┘
│
┌──────────────▼──────────────────┐
│ 6. Create Vertical Video │
│ - Target: 1080x1920 │
│ - Crop to active speaker │
│ - Or blur background │
└──────────────┬──────────────────┘
│
┌──────────────▼──────────────────┐
│ 7. Generate Subtitles │
│ - Group words (5 per line) │
│ - Calculate karaoke timing │
│ - Create ASS subtitle file │
└──────────────┬──────────────────┘
│
┌──────────────▼──────────────────┐
│ 8. Burn Subtitles │
│ - Merge subtitle + video │
│ - Final encode │
└──────────────┬──────────────────┘
│
┌──────────────▼──────────────────┐
│ 9. Upload to S3 │
│ - Save as clip_N.mp4 │
└─────────────────────────────────┘
Transcribes video audio using WhisperX.
Features:
- Automatic language detection
- Word-level timestamp alignment
- Hindi transliteration (Devanagari → Roman)
- Handles missing timestamps with interpolation
Returns:
{
"segments": [
{
"start": 10.5,
"end": 15.2,
"text": "This is an example",
"words": [
{"word": "This", "start": 10.5, "end": 10.8},
{"word": "is", "start": 10.9, "end": 11.0}
]
}
]
}Uses Google Gemini 2.5 Pro to identify engaging moments.
Input: Full transcript as text Output: List of clip boundaries
[
{
"clip_number": 1,
"start_seconds": 125.0,
"end_seconds": 178.0,
"description": "Discussion about AI in education"
}
]Prompt Strategy:
- Focus on Q&A format conversations
- 30-60 second optimal length
- No overlapping clips
- Exclude greetings and intros
Processes a single clip from start to finish.
Steps:
- Extract video segment with ffmpeg
- Extract audio track
- Run LR-ASD active speaker detection
- Create vertical video with smart cropping
- Generate ASS subtitles with karaoke effect
- Burn subtitles into video
- Upload to S3
Parameters:
base_dir: Working directory for temp filesoriginal_video_path: Path to source videos3_key: S3 key of original videostart_time: Clip start timestamp (seconds)end_time: Clip end timestamp (seconds)clip_index: Clip number (1, 2, 3, ...)transcript_segments: Word-level transcript data
Converts horizontal video to vertical format with active speaker tracking.
Target Resolution: 1080x1920 (9:16 aspect ratio)
Algorithm:
- Load face tracks and speaking scores from LR-ASD
- For each frame:
- Calculate average score over 60-frame window
- Identify highest-scoring face (active speaker)
- Crop Mode: If speaker detected, crop 1080px around face
- Resize Mode: Otherwise, blur background and letterbox
- GPU-accelerated encoding with ffmpegcv
Window Size: 60 frames (~2.4 seconds at 25fps) for stable tracking
Generates karaoke-style subtitles with word-level highlighting.
Format: ASS (Advanced SubStation Alpha)
Features:
- Groups words into 5-word sentences
- Yellow highlight on current word (
&H00FFFF&) - White text with shadow and outline
- Bottom-center positioning
- Anton font, 140pt
Karaoke Timing:
{\k50}Word1 {\k30}Word2 {\k40}Word3
Numbers are centiseconds (50 = 0.5 seconds).
Burn-In: Uses ffmpeg subtitles filter (hardware-accelerated if available)
Processes a video and generates clips.
Authentication: Bearer token (required)
Authorization: Bearer your-auth-token
Request Body:
{
"s3_key": "user-id/upload-id/original.mp4",
"num_clips": 3
}Response (Success):
{
"status": "success",
"clips_processed": 3
}Response (Error):
{
"status": "error",
"message": "Error description"
}Status Codes:
200 OK: Processing successful401 Unauthorized: Invalid or missing auth token500 Internal Server Error: Processing failed
backend/
├── main.py # Main Modal app with processing logic
├── requirements.txt # Python dependencies
├── .env.example # Environment variable template
├── .env # Your local config (gitignored)
├── README.md # This file
└── LR-ASD/ # Active speaker detection (git submodule)
├── Columbia_test.py # ASD inference script
├── model/ # Model weights
└── ...
fastapi[standard]- Web frameworkmodal- Serverless platformboto3- AWS S3 client
torch==2.4.1+cu124- PyTorch with CUDA 12.4torchvision==0.19.1+cu124- Vision modelstransformers- Hugging Face modelsaccelerate- Model optimizationwhisperx- Speech recognitiongoogle-generativeai- Gemini API
ffmpegcv- GPU-accelerated video I/Oopencv-python- Image processingscenedetect[opencv]- Scene detectionpysubs2- Subtitle manipulation
python_speech_features- Audio featuresscipy- Scientific computing
pandas- Data manipulationtqdm- Progress barsgdown- Google Drive downloadsindic-transliteration- Hindi transliteration
See .env.example for detailed setup.
Required:
AWS_ACCESS_KEY_ID- S3 access keyAWS_SECRET_ACCESS_KEY- S3 secret keyAWS_REGION- S3 bucket regionS3_BUCKET_NAME- S3 bucket nameGEMINI_API_KEY- Google Gemini API keyAUTH_TOKEN- API authentication token
Adjust resources in main.py:
@app.function(
gpu="L40S", # GPU type (L4, A10G, A100, L40S, H100)
timeout=900, # Max execution time (seconds)
memory=16384, # RAM in MB
cpu=4, # CPU cores
)GPU Options:
L4- Cheapest, slower ($0.50/hr)A10G- Good balance ($1.10/hr)L40S- Fast, good value ($2.50/hr) ← CurrentA100- Very fast ($4.00/hr)H100- Fastest ($8.00/hr)
Cause: First run downloads models (WhisperX, LR-ASD weights) Solution: Wait for download to complete. Models are cached in persistent volume.
Cause: GPU memory exhausted Solution:
- Reduce batch size in WhisperX:
batch_size=8 - Use smaller GPU (A10G) or larger (A100)
- Process shorter videos
Cause: ffmpeg not in container image
Solution: Ensure .apt_install(["ffmpeg"]) in image definition
Cause: Invalid credentials or permissions Solution:
- Verify AWS credentials in Modal secrets
- Check S3 bucket permissions
- Ensure bucket exists in specified region
Cause: Processing exceeds 900s limit Solution:
- Increase timeout:
timeout=1800(30 minutes) - Process fewer clips per request
- Use faster GPU (L40S → A100)
Cause: Submodule not initialized Solution:
git submodule update --init --recursiveOn L40S GPU for 1-hour podcast:
- Transcription: ~3-5 minutes
- Moment identification: ~10 seconds
- Per clip (60s):
- ASD: ~2 minutes
- Vertical video: ~1 minute
- Subtitles: ~30 seconds
- Total for 3 clips: ~12-15 minutes
L40S GPU @ $2.50/hour:
- 15-minute job = $0.625
- 100 jobs/month = $62.50
Optimization Tips:
- Use persistent volumes to cache models
- Batch multiple clips in single request
- Consider L4 GPU for non-urgent processing
- API Authentication: Bearer token required for all requests
- S3 Access: Use IAM user with minimal permissions (S3 only)
- Secrets: Store in Modal Secrets, never hardcode
- Temporary Files: Cleaned up after processing
- No Data Retention: Videos not stored on Modal infrastructure
# Serve locally
modal serve main.py
# Test with curl
curl -X POST "http://localhost:8000/process_video" \
-H "Authorization: Bearer test-token" \
-H "Content-Type: application/json" \
-d '{"s3_key": "test/video.mp4", "num_clips": 1}'Add print statements (they appear in Modal logs):
print(f"Processing clip {clip_index}...")View logs:
modal logs clipzz-video-processor# Test locally first
modal serve main.py
# Deploy to production
modal deploy main.py- Support more video formats (AVI, MOV, MKV)
- Add configurable subtitle styles
- Implement progress callbacks (webhooks)
- Add video quality options (720p, 1080p, 4K)
- Support multiple languages in subtitles
- Optimize batch processing (multiple videos at once)
- Add face recognition for consistent speaker labeling
- Implement chapter detection for long videos
For more information: