Architecture
Internal design of lexigram-multimedia-video and how it fits into the Lexigram multimedia subsystem.
Role in the System
Section titled “Role in the System”flowchart BT
subgraph App[Your Application]
Module[VideoModule]
Caller[App code]
end
subgraph Pkg[lexigram-multimedia-video]
Provider[VideoGenerationProvider]
Gen[VideoGenerationTask]
Proc[VideoProcessingTask]
end
subgraph Backends[Generation Backends]
LH[LocalHttpVideoProvider]
RW[RunwayVideoProvider]
OA[OpenAIVideoProvider]
W2[Wan22VideoProvider]
CX[CogVideoXVideoProvider]
SVD[SVDVideoProvider]
CF[ComfyUiVideoProvider]
end
subgraph ProcLayer[Processing Layer]
FF[FFmpegVideoProcessor]
ARGV[argv.py — pure argv builders]
IO[media_io.py — materialize / probe / read]
end
subgraph Contracts[lexigram-contracts]
VP[VideoProvider]
VPR[VideoProcessor]
Types[MediaAsset · VideoRequest · VideoOperation]
end
subgraph Servers[Reference Servers]
WS[lexigram-video-wan22-serve · :5200]
CS[lexigram-video-cogvideox-serve · :5201]
SS[lexigram-video-svd-serve · :5202]
end
subgraph External[External]
CUI[ComfyUI · :8188]
Cloud[Runway ML · OpenAI gateway]
end
Module --> Provider
Caller -->|resolve VideoProvider / VideoProcessor| Provider
Provider --> Backends
Provider --> FF
Gen --> Backends
Proc --> FF
Backends --> VP
FF --> VPR
FF --> ARGV
FF --> IO
LH -->|POST /generate| Servers
W2 -->|POST /generate| WS
CX -->|POST /generate| CS
SVD -->|POST /generate| SS
CF -->|/prompt · /history · /view| CUI
RW -->|submit + poll| Cloud
OA -->|/v1/videos + poll| Cloud
Two independent surfaces: a generation surface (registry-dispatched backend clients over VideoProvider) and a processing surface (ffmpeg pipeline over VideoProcessor). They share the MediaAsset value object and the provider lifecycle.
Key Components
Section titled “Key Components”Generation
Section titled “Generation”VideoModule— DI module exportingVideoProcessor,VideoProvider,VideoGenerationTask,VideoProcessingTask.stub()pinslocal-httpfor tests.VideoGenerationProvider— DI provider (name = "video").register()selects and constructs the backend, binds tasks, gate-keeps the processor onffmpegavailability. Provideshealth_check().- Seven backend clients (
providers/) — all implementVideoProvider.generate():
| Client | Wire shape | Notes |
|---|---|---|
LocalHttpVideoProvider | single POST /generate | accepts raw bytes or {"url": ...} responses; zero-dependency default |
RunwayVideoProvider | submit (/v1/text_to_video / /v1/image_to_video) + poll (/v1/tasks/{id}), status SUCCEEDED | VideoGenerationAuthenticationError on 401, VideoTimeoutError past poll budget |
OpenAIVideoProvider | submit (/v1/videos) + poll, status completed | gateway-shaped payloads; VideoMode derivation; per-mode validation |
Wan22VideoProvider | POST /generate (:5200) | text-to-video + image-to-video |
CogVideoXVideoProvider | POST /generate (:5201) | primarily text-to-video; image handling is server-side |
SVDVideoProvider | POST /generate (:5202) | requires image_uri, ignores prompt |
ComfyUiVideoProvider | /prompt → /history/{id} → /view | fills default_svd.json workflow; requires image_uri reachable by ComfyUI |
Processing
Section titled “Processing”FFmpegVideoProcessor— subprocess runner for everyVideoOperation. Bounded by a semaphore (max_concurrent_jobs), workdir-per-job (temp_dir, cleaned infinally), hard job timeout (processing.timeout→ kill). Two execution paths:_run(plain) and_run_streaming(-nostats -progress pipe:1, parsesout_time/out_time_msinto0.0 → 1.0callbacks).argv.py— pure argv builders, the heart of processing:build_argv(operation, input_paths, output_path, ffmpeg_binary, clip_durations, subtitle_path)— onematcharm perVideoOperationvariant.build_compose_argv(operation, ...)—ComposeVideofilter graph:setpts=PTS-STARTPTS+<start>/TBstart-aligned layers, overlay enable windowsbetween(t,start,end), per-layerfade=t=in/outon layer PTS, whole-composition fades, base fade-out (base_fade_out),adelay+volume+amixaudio layers, optionalEncodeSpecargs, fast-path plain copy for no-op composes.cues_to_srt(cues)— SRT serialization forBurnSubtitles.- Internal tables:
_COLOR_PRESETS(grayscale/sepia/vintage),_POSITION_EXPR/_OVERLAY_POSITION_EXPR(8 namedOverlayPositions). - Crossfade concat builds a sequential
xfade/acrossfadechain with a1/30sepsilon for hard cuts (a real0.0duration silently truncates the chain in ffmpeg 6.x).
media_io.py— asset↔filesystem bridge:materialize_asset(bytes→temp file,file://passthrough with existence check, other URIs HTTP-GET),read_output_asset(file→MediaAssetbytes),probe_duration,probe_fps(ffprobe),materialize_frames_sequential(frame%06d.pngpattern for ffmpeg-i).
Orchestration
Section titled “Orchestration”VideoGenerationTask/VideoProcessingTask— flat-dict adapters for the async job path;_operation_from_paramsreconstructs all 14 operation variants fromoperation_type(the dataclass class name) + nested asset dicts.
Dependency Flow
Section titled “Dependency Flow”VideoRequest ──► VideoProvider (protocol) ──► selected backend ──► POST /generate ──► MediaAssetVideoOperation ─► VideoProcessor (protocol) ─► FFmpegVideoProcessor └─ build_argv / build_compose_argv (pure) └─ materialize_asset / probe_* (media_io) └─ create_subprocess_exec ffmpeg ──► MediaAsset- Backend selection is a config-key dispatch in
register()(backend == "local-http"…"comfyui"), so adding engines never touches callers. - The processing pipeline is split into pure argv construction and I/O-heavy execution — argv builders are trivially unit-testable without running ffmpeg.
- Everything crosses boundaries as
Result[MediaAsset, VideoGenerationError]; infrastructure-level failures inside providers are returned asErrdomain values, and taskrun()re-raises them so jobs fail loudly.
Providers
Section titled “Providers”| Registration | Binding |
|---|---|
container.singleton(VideoConfig, ...) | Config object |
container.singleton(VideoProvider, backend) | Selected generation backend |
container.singleton(VideoGenerationTask, ...) | Wraps the same backend |
container.singleton(VideoProcessor, FFmpegVideoProcessor) | Only if shutil.which(ffmpeg_binary) — otherwise logs video_processing_disabled |
container.singleton(VideoProcessingTask, ...) | Wraps the processor, same condition |
Resolved optionally at register(): AsyncSecretStoreProtocol (via resolve_credential), RetryPolicyProtocol, CircuitBreakerProtocol (via resolve_optional).
Contracts
Section titled “Contracts”Used from lexigram-contracts (lexigram.contracts.multimedia):
| Contract | Location | Used by |
|---|---|---|
VideoProvider | contracts/multimedia/protocols.py | implemented by all 7 backends |
VideoProcessor | contracts/multimedia/protocols.py | implemented by FFmpegVideoProcessor; consumed by lexigram-multimedia-upscale’s VideoUpscaleService |
MediaAsset, VideoRequest, VideoMode, VideoOperation + variants, TransitionSpec, SubtitleCue, ComposeLayer, ComposeAudioLayer, EncodeSpec, OverlayPosition | contracts/multimedia/types.py | all request/response and operation value objects |
VideoGenerationError, ProviderNotInstalledError | contracts/multimedia/exceptions.py | base + registration errors |
Package leaf exceptions (exceptions.py): VideoTimeoutError (LEX_ERR_MM_VIDEO_001), VideoGenerationAuthenticationError (LEX_ERR_MM_VIDEO_002), VideoProcessingError (LEX_ERR_MM_VIDEO_003) — all extend VideoGenerationError.
Lifecycle
Section titled “Lifecycle”register(container)— merge config; bindVideoConfig; resolve secrets/resilience; construct backend byconfig.backend; bindVideoProvider+VideoGenerationTask; conditionally bindVideoProcessor+VideoProcessingTask(ffmpeg gate).boot(container)— no-op: per-request connections only.shutdown()— inherited base behavior; no long-lived sockets (ComfyUI/Runway/OpenAI sessions are created per call).health_check(timeout=5.0)— HTTP backends:GET /health(/system_statsfor ComfyUI),200 → HEALTHY; API backends:HEALTHYiff a credential resolved; no backend →UNHEALTHY.
Design Decisions
Section titled “Design Decisions”- Protocols, not a base class. The four multimedia protocols are separate structural
Protocols; backends arecasttoVideoProviderwithout inheritance (contracts’ intentional shape). - Thin HTTP clients everywhere. No vendor SDKs in the main dependency list — raw
aiohttpfor local servers, Runway, OpenAI, and ComfyUI alike (uniform resilience wrapping, uniform timeouts). - Registry-style backend dispatch.
register()branches overVideoConfig.backend; unknown values raiseProviderNotInstalledErrorat DI time with an actionable hint. - Secrets by name.
AsyncSecretStoreProtocollookups happen once at registration;_credential_resolvedfeeds health. - Synchronous subprocess discipline. One ffmpeg invocation per operation with a bounded semaphore and hard timeout — no background ffmpeg daemons, no unbounded concurrency; progress comes from
-progress pipe:1. - Pure-argv/side-effecting-media split.
argv.pynever touches disk or subprocesses — ffmpeg commands are unit-tested as data;media_io.pyowns all filesystem/HTTP side effects. - Dataclass-discriminated operations.
operation_type(class name) round-trips through task params, matchingTimeline.from_params()conventions elsewhere in the framework. - Prompt is a request, not config. Duration/resolution/format/factors are per-request (
VideoRequest) — config only selects the engine and its endpoints.
Extension Points
Section titled “Extension Points”| Point | Mechanism |
|---|---|
| New generation engine | Implement VideoProvider.generate(); construct it where backends are selected (custom provider or a VideoModule variant) and bind as VideoProvider |
| New processing engine | Implement VideoProcessor (contract) and bind it — VideoUpscaleService and task adapters adapt automatically |
| New ffmpeg operation | Add the dataclass to VideoOperation in contracts, a match arm in build_argv, a _materialize_inputs case, and a _operation_from_params branch |
| Custom ComfyUI graph | Point comfyui_workflow_path at any template with the documented placeholders |
| Custom reference server | Any HTTP server speaking POST /generate (+ GET /health); add a console script (lexigram-video-*-serve) |
| Retry / fail-fast | Register RetryPolicyProtocol / CircuitBreakerProtocol — backend calls get wrapped automatically |
| Progress UX | Pass a progress_callback to process() |
| Async orchestration | Drive VideoGenerationTask / VideoProcessingTask directly or via the umbrella’s submit() job path |