Skip to content

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?

  1. EC2 worker dependency — custom polling daemon, fork-based memory management, auto-shutdown scripts
  2. Single-tenant per instance — each daemon locks to one case via GlobalNpcaseIdHandler
  3. No horizontal scaling — scaling means spinning up more EC2 instances
  4. FFmpeg is compute-heavy — transcoding benefits from dedicated compute, not shared Sidekiq
  5. Memory management is fragileForkingMemoryWatcher kills 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

  1. Phase 1: Build ECS Fargate task for transcoding (largest workload)
  2. Phase 2: Add stitching support (FFmpeg concat in same container)
  3. Phase 3: Extract sync parsing to Lambda
  4. Phase 4: Update Video.create callback to emit SNS event instead of creating ProcessingJob
  5. 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 the Architecture ×

Ask questions about Nextpoint architecture, patterns, rules, or any module. Powered by Claude Opus 4.6.