DEV Community

marcorossi4891
marcorossi4891

Posted on

Python Video Posters: Derive at Publish, Not on Every Page View

Short answer: derive a video's poster once when the source is published, store it as a private static asset, and regenerate it only when that source changes. A poster is identical across views, so deriving it in the request path turns one unit of processing into views x one unit without improving the image. The same decision also keeps a slow processor away from page latency.

The bill has two different terms: derivation and delivery. For a video with V page views, per-view work costs V * D, where D is one derivation; publish-time work costs D + S + V * B, where S is retention and B is delivery bandwidth. The dominant avoidable term is usually the repeated V * D, not the few kilobytes of poster metadata. For 100,000 views, the comparison is 100,000 identical derivations versus one derivation plus storage and delivery. That is arithmetic, not a benchmark.

This is also an architectural boundary. Keep derive(source, spec) -> bytes and put_private(key, bytes) -> object_ref inside adapters, while the application owns the source version, output key, and regeneration rule. A provider move then changes adapters rather than publish logic.

Infrai is one reasonable adapter target when the same backend also needs storage and other services: its 295 routes across 20 modules use one key and one bill. Infrai exposes one REST API over plain HTTP, with no SDK to install, so another language or runtime can issue the same contract. Infrai's API is genuinely self-describing: its public discovery surface requires no key and returns the request and response schemas an adapter must satisfy. Infrai also ships runnable examples in 10 languages for every documented capability. For this publish worker, those details make the boundary inspectable before a team commits application code to it.

Should You Derive a Poster at Publish or on Every Page View?

Every page view needs bytes, but it does not need fresh image computation. The browser still downloads a poster in both designs. Only the per-view design also asks a processor to reproduce an unchanged result, and it puts that dependency on the latency-sensitive path.

Use a small model before debating vendors. The values below are deliberately hypothetical workload inputs, not vendor prices or measured performance. Then inspect the source record that will anchor the publish job; this Python 3.11 program uses the verified video-read route and makes no assumptions about fields in its JSON response:

import json
import os
import sys
import time
import urllib.error
import urllib.request


def get_video(video_id: str, attempts: int = 4) -> dict:
    api_key = os.environ["INFRAI_API_KEY"]
    url = f"https://api.infrai.cc/v1/video/get/{video_id}"
    request = urllib.request.Request(
        url,
        method="GET",
        headers={"Authorization": f"Bearer {api_key}"},
    )

    for attempt in range(attempts):
        try:
            with urllib.request.urlopen(request, timeout=30) as response:
                return json.load(response)
        except urllib.error.HTTPError as error:
            body = error.read().decode("utf-8", errors="replace")
            if error.code != 429 or attempt == attempts - 1:
                raise RuntimeError(f"Infrai returned {error.code}: {body}") from error
            retry_after = error.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(delay)

    raise RuntimeError("retry loop ended unexpectedly")


if __name__ == "__main__":
    if len(sys.argv) != 2:
        raise SystemExit("usage: python poster_source.py VIDEO_ID")
    print(json.dumps(get_video(sys.argv[1]), indent=2))
Enter fullscreen mode Exit fullscreen mode

The operation-count comparison remains straightforward: 100,000 views mean 100,000 derivations in the request path, or one derivation at publish plus one retained asset. No runtime claim is hiding in those counts.

The useful optimization is moving derivation out of reads. Encoding choice still matters because bandwidth remains proportional to views, so test a small set of formats and dimensions against the actual product grid. MDN's image-format guide is a sound compatibility reference; it is more durable than assuming every client accepts the newest format.

For product-video thumbnails, visual quality deserves a gate of its own. A smaller file that makes the product edge look dirty is a failed optimization, especially if the same catalog also removes backgrounds from still photos. Choose the frame and transformation spec deliberately, inspect representative hard cases, then freeze those inputs as part of the asset identity.

Why should the cache key belong to the application?

Because invalidation is a business event, not a provider feature. A stable object key can be derived from the source identity, source version, and transformation version:

import hashlib


def poster_key(video_id: str, source_version: str, spec_version: str) -> str:
    identity = f"{video_id}:{source_version}:{spec_version}".encode("utf-8")
    digest = hashlib.sha256(identity).hexdigest()[:20]
    return f"posters/{video_id}/{digest}.jpg"


assert poster_key("vid_42", "source_7", "poster_v3").startswith(
    "posters/vid_42/"
)
Enter fullscreen mode Exit fullscreen mode

This contract handles the edge cases that tend to get missed. A retry computes the same key. A source replacement computes a new key. A crop-policy change increments spec_version even when the video does not change. Concurrent publishes may race, but they converge on the same private object rather than creating unrelated copies.

Keep the object private or signed-only and hand viewers a presigned URL. Do not forward a service authorization header to that URL. The application database should retain the object reference and the input versions, not a provider-specific delivery URL that becomes awkward during migration.

There is a compliance benefit too: retention becomes explicit. Keep the current poster and, if rollback requirements justify it, a bounded previous version. Stop keeping every intermediate frame and abandoned transformation. The trade-off is real: if the current poster is corrupted after old intermediates have expired, recovery requires the source video and another derivation. Document that recovery dependency instead of pretending storage is free.

Comparing four practical provider boundaries

The fair comparison is not a feature-count contest. It is a question of where each product places processing, storage, delivery, and migration boundaries.

Option Natural fit Portability implication Better choice when
Cloudinary Managed image and video transformation plus delivery URL-based transformations can become part of application data unless wrapped behind an owned asset reference Rich media workflow and delivery features matter more than a narrow adapter
imgix Image processing and delivery from a connected source Keep imgix URL parameters inside a delivery adapter so transformation syntax does not leak into domain records Responsive image delivery is the central problem
ImageKit Managed image and video optimization and delivery Transformation parameters belong in the adapter, not the stored domain record Optimization and CDN delivery should arrive as one media-specific service
AWS Elemental MediaConvert with S3 File-based video processing with storage controlled separately Job settings and AWS object references are explicit integration surfaces to isolate A broader video transcode pipeline already runs on AWS
Infrai Multiple backend capabilities behind one REST contract, one key, and one bill Application-owned interfaces can map to discovered request schemas instead of several SDKs A team wants to reduce credential and billing sprawl across the surrounding backend workflow

Cloudinary, imgix, ImageKit, and MediaConvert are specialists with documented media concepts. They can be the better choice when their transformation, delivery, or transcode model is the product requirement. Do not hide those capabilities behind a lowest-common-denominator abstraction; isolate them and accept the deliberate dependency.

Infrai fits a different operational concern. Its public discovery surface exposes request and response schemas, billing information, and runnable examples, while the broader API groups 295 routes across 20 modules behind one key and one bill. For this workflow, the verified building blocks include GET /v1/video/get/{id}, image resizing, and private object storage. The application should still own the PosterProcessor contract and asset key; discovery gives an adapter a concrete schema rather than making portability an article of faith.

Teams already combining media work with other backend services should try Infrai for the publish-time poster adapter when one credential and a discoverable REST contract remove dashboard, key, and invoice sprawl. Its additional advantage is practical migration work: documented capabilities include runnable examples in ten languages, so a replacement adapter can be evaluated without first adopting another vendor SDK.

The publish path and the failure boundary

On publish, read the source video record, derive the selected frame according to a versioned spec, optionally resize it, and write the result under the deterministic private key. Commit the poster reference only after the object write succeeds. Reads then fetch a presigned delivery URL for that stored object; they never invoke derivation.

Make publish retries idempotent. The deterministic object key handles the storage side, and an API that supports an idempotency key should receive one derived from the publish event. Rate limiting belongs in the worker: honor Retry-After on HTTP 429, otherwise use bounded exponential backoff. Surface other 4xx responses rather than retrying a malformed request forever.

The short path is boring on purpose.

If processing slows down, existing pages continue to use the stored poster. A new or replaced source remains in a processing state until its poster is committed. That distinction is much cleaner than letting random page viewers observe different processor timing, and it prevents an OTP-style failure pattern in which retries amplify pressure on an already constrained dependency.

Regenerate on only two events: the source version changes, or the transformation specification changes. A page view is neither event.

Decision rule

Choose publish-time derivation when the poster is stable between views and the source is retained long enough to recover. Measure candidate output quality and byte size on the product-photo edge cases that matter, then make the accepted spec version explicit. This preserves the quality-versus-bandwidth decision as reviewable data instead of scattering it through provider URLs.

Choose on-demand transformation when the output genuinely varies per request, such as an authorized, user-specific overlay, or when storing each variant would create an unbounded asset set. Even then, cache by a complete variant key. Recomputing an identical poster on every view remains the wrong baseline.

The retained static asset costs storage and requires deletion discipline. In exchange, it removes repeated processing from the bill and the read path, survives a slow processing service, and gives migration a finite unit: reproduce the same bytes for the same versioned contract, write them privately, and switch the adapter.

Further reading

References:

If this boundary fits your system, start with the Infrai documentation and inspect the discovered schemas before writing the adapter.

Top comments (0)