Skip to content
GitHub

Architecture

Internal design of lexigram-multimedia-interpolate and how it fits the Lexigram multimedia subsystem.


lexigram-multimedia-interpolate is the frame-interpolation member of the lexigram-multimedia umbrella. It owns two jobs:

  1. Frame-pair interpolation — two MediaAsset frames in, one synthesized midpoint out, via a local RIFE reference server.
  2. Whole-video interpolation — doubling or quadrupling a clip’s frame rate by composing itself with a VideoProcessor (provided by lexigram-multimedia-video) through contracts only.

The package depends exclusively on lexigram and lexigram-contracts — the only lexigram-multimedia-video connection is the VideoProcessor protocol, never an import. The reference server (rife_server.py) is an optional extra so PyTorch never contaminates the framework runtime.

lexigram-contracts ← lexigram ← lexigram-multimedia-interpolate → (entry points) lexigram-multimedia
├── VideoProcessor (protocol, resolved) ── lexigram-multimedia-video
└── HTTP ── RIFE server (aiohttp, [rife-server] extra)

ComponentFilePurpose
InterpolationModulemodule.pyconfigure() / stub() factories returning DynamicModule
InterpolationGenerationProviderdi/provider.pyConstructs/registers the backend + task + optional video service; health checks
RifeInterpolationProviderproviders/rife.pyBase64 frame payload → POST /interpolate → midpoint MediaAsset
VideoInterpolationServicevideo_interpolation_service.pyFrame extraction → N doubling passes → reassembly at higher fps
InterpolationTasktasks.pylexigram-tasks job handler (dict in, dict out)
rife_server.pyservers/rife_server.pyReference aiohttp server: RifeModel loaded once, /interpolate + /health
InterpolationConfigconfig.pyDataclass config, config_section = "multimedia_interpolate"

InterpolationModule.configure()
└─ DynamicModule(providers=[InterpolationGenerationProvider(config)])
│ register(container)
├─ singleton(InterpolationConfig, config)
├─ resolve_optional: RetryPolicyProtocol · CircuitBreakerProtocol
├─ switch backend == "rife" → RifeInterpolationProvider(base_url, timeout, retry, cb)
│ else → raise ProviderNotInstalledError
├─ singleton(InterpolationProvider, backend)
├─ singleton(InterpolationTask, InterpolationTask(backend))
└─ if resolve_optional(VideoProcessor):
singleton(VideoInterpolationService,
VideoInterpolationService(interpolation_provider, video_processor))
  • Registration order matters: VideoInterpolationService is registered after InterpolationProvider so it can reuse the exact backend instance.
  • Consumers resolve InterpolationProvider for pair work, or VideoInterpolationService when they hold a whole video.
  • boot() is empty — everything was already wired in register().

EntityRegistersNotes
InterpolationGenerationProviderInterpolationConfig, InterpolationProvider, InterpolationTask, VideoInterpolationService (conditional)Provider name = "interpolate"; entry point lexigram.multimedia.subsystemsinterpolate
InterpolationModuleRe-exported via lexigram.multimedia.modules entry point interpolateExports [InterpolationProvider, InterpolationTask]

The VideoInterpolationService registration is conditional on VideoProcessor presence — resolve via resolve_optional during register(). No video package, no extra registration.


ContractLocationUsed By
InterpolationProviderlexigram.contracts.multimedia.protocolsRifeInterpolationProvider (structural impl); consumed by VideoInterpolationService
VideoProcessorlexigram.contracts.multimedia.protocolsOptional dependency: extract_frames, assemble_frames; gates service registration
RetryPolicyProtocol / CircuitBreakerProtocollexigram.contracts.infra.resilience.protocolsOptional; wrap every /interpolate call
InterpolationRequest / MediaAssetlexigram.contracts.multimedia.typesFrozen dataclasses; both in/out types
MultimediaErrorlexigram.contracts.multimedia.exceptionsThe Err type for interpolate() and interpolate_video()

This package defines no own exceptions (exceptions.py is empty) — the contracts’ MultimediaError (code LEX_ERR_MM_001) covers all failures, and ProviderNotInstalledError (LEX_ERR_MM_006) is raised by the provider for unknown backends. rife_server.py imports RifeModel/torch lazily inside on_startup — the client package never imports them.


sequenceDiagram
    participant S as Service
    participant V as VideoInterpolationService
    participant VP as VideoProcessor
    participant R as RifeInterpolationProvider
    participant RL as RIFE Server

    S->>V: interpolate_video(asset, factor=2, fps=24)
    V->>VP: extract_frames(asset)
    VP-->>V: Ok([f0, f1, f2])
    loop doubling pass (1 pass for factor=2, 2 for factor=4)
        V->>R: interpolate(InterpolationRequest(f0, f1))
        R->>RL: POST /interpolate (base64 frames)
        RL-->>R: midpoint PNG
        R-->>V: Ok(MediaAsset)
        Note over V: interleave: [f0, mid01, f1, mid12, f2]
    end
    V->>VP: assemble_frames(sequence, fps=48.0)
    VP-->>V: Ok(video MediaAsset)
    V-->>S: Ok(MediaAsset)

The midpoint interleaving lives in VideoInterpolationService._double(): [f0, f1, f2] → [f0, mid01, f1, mid12, f2]. Failures short-circuit: any Err from extraction, a pair interpolation, or assembly aborts the whole video with the first error.


  • register() — bind InterpolationConfig; resolve resilience protocols; construct RifeInterpolationProvider; bind InterpolationProvider + InterpolationTask; conditionally compose and bind VideoInterpolationService.
  • boot() — no-op; no async I/O beyond register().
  • shutdown() — none; the client holds no persistent connections (per-call aiohttp sessions).
  • Server lifecycle (separate process)main() builds the aiohttp app, appends on_startup (model load, CUDA/CPU detection), mounts /interpolate + /health, and serves on port 5500.

  • Contracts-only cross-package compositionVideoInterpolationService depends on InterpolationProvider and VideoProcessor protocols with constructor injection. There is no import of lexigram-multimedia-video anywhere (video_interpolation_service.py docstring states this contract rule explicitly).
  • VideoInterpolationService is deliberately not an InterpolationProvider — different method (interpolate_video vs interpolate), different signature (video + factor vs two frames), mirroring the video-upscaling service pattern.
  • Reference server behind a lazy import + optional extra — torch is heavy; RifeModel loads at server startup only, and the [rife-server] extra keeps framework installs lean.
  • No own exception hierarchy — contracts’ MultimediaError suffices for a two-frame client; the package leafs nothing.
  • Client must not host models — like ComfyUI in the image package, RIFE runs as a persistent external process; the provider is a thin aiohttp client, reusing retry/circuit_breaker if present.
  • Dict-in/dict-out task handlerInterpolationTask returns JSON-serializable shapes for lexigram-tasks’ result store (bytes persist at the umbrella layer).

PointMechanism
New backendImplement the InterpolationProvider shape (interpolate -> Result[MediaAsset, MultimediaError]) and bind it as the singleton in a custom provider; the backend config Literal would need widening
Whole-video workflowsVideoInterpolationService.interpolate_video(asset, factor=2|4, fps=...) — compose with any other VideoProcessor fulfillment (ffmpeg, GPU encoders)
ResilienceRegister RetryPolicyProtocol / CircuitBreakerProtocol — automatic
JobsInterpolationTask.run(params) or your own handler around InterpolationProvider
Custom RIFE hostingPoint rife_base_url at any server speaking the base64 /interpolate wire contract — including vendored RIFE distributions
Umbrella orchestrationlexigram.multimedia.subsystems / lexigram.multimedia.modules entry points (interpolate) for automatic discovery by lexigram-multimedia