ADR-009: Video Processing Modernization (Litigation Suite)¶
Status¶
Proposed
Date¶
2026-03-19
Context¶
Video processing is part of the Litigation suite (depositions, transcripts, theater presentations). All video processing currently runs on Legacy EC2 worker instances via the custom polling daemon.
Current State¶
Workers (EC2 custom daemon):
| Worker | Purpose | External Tool | Input → Output |
|---|---|---|---|
TranscodeWorker |
Transcode to H.264/AAC MP4 | FFmpeg | Raw video → presentation MP4 + splash JPG |
VideoStitchWorker |
Stitch partial videos | FFmpeg concat | Multiple MP4s → single full MP4 |
VideoSyncWorker |
Parse video sync files | None (parsers) | Syncfile → page/line↔timestamp mapping |
FlvConversionWorker |
Legacy FLV→MP4 migration | FFmpeg | FLV → MP4 |
Rails Sidekiq jobs:
| Job | Purpose | External Tool |
|---|---|---|
DesignationVideoJob |
Export video clips of transcript designations | FFmpeg (Rails-side) |
Processing pipeline:
Upload raw video → Video.create → auto-queue transcode_job
→ TranscodeWorker (FFmpeg H.264, CRF 25, max 1080p)
→ presentation MP4 + splash JPG to S3
→ If multiple segments: VideoStitchWorker (FFmpeg concat)
→ stitched preview (320x180) + presentation MP4
→ VideoSyncWorker (parse syncfile for page-to-timestamp mapping)
→ deposition_video_offsets updated
FFmpeg settings: - H.264 + AAC codec, CRF 25 - Presentation: 30fps, slow preset, max 1080p height - Preview: 320x180, 24fps - Stitching: concat demuxer (no re-encode) - Clips: direct S3 URL streaming, fade transitions for compilations
Video sync formats (5): XML/DVT, CMS, SVI, SBF (zip containing SVI), VID
Why Modernize?¶
- EC2 worker dependency — custom polling daemon, fork-based memory management, auto-shutdown scripts
- Single-tenant per instance — each daemon locks to one case via
GlobalNpcaseIdHandler - No horizontal scaling — scaling means spinning up more EC2 instances
- FFmpeg is compute-heavy — transcoding benefits from dedicated compute, not shared Sidekiq
- Memory management is fragile —
ForkingMemoryWatcherkills workers at 400MB RSS
Decision¶
Migrate video processing to ECS Fargate tasks (same pattern as documentexporter and documentpageservice).
Architecture¶
Rails App
│
├── Video.create (after_create callback)
│ │
│ ▼
│ SNS: VideoProcessingRequested
│ ├── type: transcode | stitch | sync
│ │
│ ▼
│ SQS Queue → Lambda: VideoRouter
│ │
│ ├── transcode → ECS Fargate Task (FFmpeg)
│ │ → presentation MP4 + preview MP4 + splash JPGs
│ │ → SNS: VideoTranscodeCompleted
│ │
│ ├── stitch → ECS Fargate Task (FFmpeg)
│ │ → stitched full video
│ │ → SNS: VideoStitchCompleted
│ │
│ └── sync → Lambda Function (no FFmpeg needed)
│ → parse syncfile, update video offsets
│ → SNS: VideoSyncCompleted
│
│ All events → PSM (Firehose → Athena)
│
└── DesignationVideoJob stays in Rails Sidekiq
(clips from existing transcoded videos, no heavy compute)
Why ECS Fargate (not Lambda)?¶
- FFmpeg transcoding can take 10-60+ minutes for large videos → exceeds Lambda 15-min timeout
- Memory requirements — video processing needs 2-8GB depending on resolution
- Binary dependencies — FFmpeg + FFprobe are large binaries, better suited to containers
- Consistent with existing pattern — documentexporter and documentpageservice already use ECS Fargate
Why Lambda for VideoSync?¶
- No FFmpeg needed — pure parsing of text/XML files
- Fast execution — syncfile parsing completes in seconds
- No binary dependencies — just Python/Ruby parsers
What Stays in Rails¶
| Component | Why |
|---|---|
Video model |
CRUD, S3 path management, status tracking |
DepositionVideo model |
Join table, ordering |
DesignationVideoJob |
Clips from existing transcoded videos — streams directly from S3 URL, no heavy compute |
FlvConversionWorker |
Legacy migration worker — will eventually be unnecessary |
Migration Strategy¶
- Phase 1: Build ECS Fargate task for transcoding (largest workload)
- Phase 2: Add stitching support (FFmpeg concat in same container)
- Phase 3: Extract sync parsing to Lambda
- Phase 4: Update
Video.createcallback to emit SNS event instead of creating ProcessingJob - Legacy cases: Can continue using EC2 workers until fully migrated
Consequences¶
Positive¶
- No EC2 instance management — Fargate handles provisioning, scaling, shutdown
- Cost efficiency — pay only for transcode duration, not idle EC2 instances
- No memory watchdog — Fargate task definitions specify memory limits natively
- Consistent architecture — follows ECS Fargate pattern from documentexporter
- Multi-tenant — Fargate tasks can process any case (no single-tenant lock)
Negative¶
- Container cold start — Fargate cold starts add 30-60s vs already-running EC2 workers
- FFmpeg container image — must build and maintain FFmpeg Docker image
- Video stitching coordination — must wait for all partial transcodes before stitching (orchestration complexity)
Risks¶
- Large video files — some videos exceed 10GB. Must ensure ECS task has sufficient ephemeral storage and S3 streaming works for large files.
- FFmpeg version parity — workers use whatever FFmpeg is installed on EC2 AMI. Must ensure Fargate container FFmpeg produces identical output (same codecs, quality, compatibility).
- Stitching orchestration — current stitching waits for all transcode jobs to complete by checking
has_transcode_jobs. Fargate needs an equivalent coordination mechanism (Step Functions or DynamoDB-based tracking).
Ask questions about Nextpoint architecture, patterns, rules, or any module. Powered by Claude Opus 4.6.