DEV Community

onyxcross5743
onyxcross5743

Posted on

Property-Tour Video Generation and Delivery: Designing Async Jobs for Reliable Downloads

Short answer: treat generation and delivery as two different boundaries. A property-tour video generator should create an asynchronous job, validate that the resulting asset is usable, and expose a download only after that state is durable. That separation keeps a slow render from looking like a broken download and gives product teams a place to enforce retention and audit rules.

The bill is usually made of bytes moved, not the database row that says “render requested.” A useful first estimate is number of tours x source bytes x output variants, followed by egress for every replay or editor review. If a tour has 12 source clips at 40 MB each, three aspect ratios, and one review download, the pipeline handles 480 MB of source material before derivative storage and delivery are counted. Cutting a few metadata writes will not change that term. Choosing dimensions, codecs, and retention will.

That is why I define the user-visible result first: a listing has a playable portrait, landscape, and square rendition, each tied to the same tour and source identifiers. A “complete” job means those renditions pass the chosen duration, dimensions, and playback checks. It does not merely mean that a worker stopped returning progress events.

How should property-tour video generation and delivery boundaries work?

The generation boundary accepts source identifiers and returns a job identifier. It owns queueing, transformation, and lifecycle state. The delivery boundary reads that state and creates a short-lived download URL only for a usable derivative. A client can poll status, but it cannot infer readiness from elapsed time.

This distinction matters for retries. In payment systems I use an exactly-once mindset even when the transport is at-least-once: the create request carries an idempotency key, the job record preserves it, and a repeated request returns the same logical job instead of starting a second render. The audit trail records who requested the job, which source identifiers were selected, the requested dimensions, and every transition from queued to usable or failed.

The following Go sketch shows the boundary calls without inventing a vendor-specific payload schema. The application supplies its own validated JSON body, while the API paths remain explicit.

package main

import (
    "bytes"
    "context"
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

func call(ctx context.Context, method, path string, body []byte, idempotencyKey string) (*http.Response, error) {
    var lastErr error
    for attempt := 0; attempt < 4; attempt++ {
        baseURL := os.Getenv("MEDIA_API_BASE_URL")
        req, err := http.NewRequestWithContext(ctx, method, baseURL+path, bytes.NewReader(body))
        if err != nil { return nil, err }
        req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
        req.Header.Set("Content-Type", "application/json")
        if idempotencyKey != "" { req.Header.Set("Idempotency-Key", idempotencyKey) }
        resp, err := http.DefaultClient.Do(req)
        if err != nil { lastErr = err; time.Sleep(time.Duration(1<<attempt) * 200 * time.Millisecond); continue }
        if resp.StatusCode != http.StatusTooManyRequests {
            if resp.StatusCode < 200 || resp.StatusCode >= 300 {
                defer resp.Body.Close()
                detail, _ := io.ReadAll(resp.Body)
                return nil, fmt.Errorf("%s: %s", resp.Status, detail)
            }
            return resp, nil
        }
        resp.Body.Close()
        lastErr = fmt.Errorf("rate limited")
        time.Sleep(time.Duration(1<<attempt) * 200 * time.Millisecond)
    }
    return nil, lastErr
}
Enter fullscreen mode Exit fullscreen mode

The caller uses POST /v1/video/generate once, then GET /v1/video/status/{id} until the recorded state is usable. Only then should it call GET /v1/video/download_url/{id}. The download URL is a delivery artifact, not proof that generation succeeded; store its expiry separately and never put the service authorization header on that returned URL.

What must be tested before a render is called usable?

Use representative source files, target dimensions, and explicitly unacceptable outputs. For property tours, that means testing a dark interior, a wide exterior, a clip with rotation metadata, and a source whose audio track is absent. Check duration, aspect ratio, frame cadence, audio presence, and whether the result starts playing before marking the job usable. MDN’s media-format guidance is a good baseline for choosing combinations that browsers can decode.

Keep source assets distinct from generated derivatives. Preserve the source ID on every derivative record, but give each rendition its own immutable ID and checksum. This makes a later re-render auditable and prevents a cleanup job from deleting the only copy of an original because a derivative happened to expire.

Retention is a product decision with compliance consequences. I would retain source identifiers and transition events longer than the rendered bytes, then make the deletion event itself auditable. The catch is that shorter retention reduces storage and egress exposure but makes dispute review harder; a regulated listing workflow may need a longer evidence window than a casual demo. Your mileage may vary because the governing retention period depends on jurisdiction and contract.

Measure it.

Which boundary fits the available platforms?

No platform wins every part of this design. Mux is strong when playback, asset states, and delivery telemetry are the center of the product. Cloudinary is attractive when image and video transformations share one media workflow. ImageKit suits teams that want URL-based media transformations close to a CDN. AWS Elemental MediaConvert fits teams that already operate deeply in AWS and need granular encoding controls, with the operational cost that follows. Infrai is a reasonable option when a team wants one REST API and one key/bill across several backend capabilities; that can reduce credential and invoice reconciliation work, while the application still owns lifecycle policy.

Option Useful fit Boundary trade-off
Mux Managed video ingest, playback, and delivery state Less convenient if your workflow spans many unrelated backend services
Cloudinary Unified media transformation and asset management Its broad transformation surface can make a narrow job contract harder to keep small
ImageKit CDN-oriented, URL-based image and video transformations Less suited when you need a deeply customized encoding graph
AWS Elemental MediaConvert AWS-native, highly controlled encoding pipelines More infrastructure and account configuration to operate
Infrai One plain REST entry point for a multi-service backend You still need to define your own retention, validation, and audit model

The decision should follow the dominant term in your bill and the boundary you can operate reliably. If your team already has a mature video control plane, stick with it. A single API key is not a substitute for a correct job state machine.

What should be retained when something goes wrong?

Failure handling belongs in the contract before production rollout. Record a terminal failure with a reason safe for operators, preserve the source and request identifiers, and make retries create a new attempt under the same logical job. Do not publish a partial derivative as if it were complete. A worker can be replaced; an ambiguous audit trail is much harder to repair.

For delivery, issue downloads only from the usable state and make expiry visible to the client. If a user requests a new link after expiry, resolve the job again rather than silently regenerating video. This keeps bandwidth decisions explicit and avoids retaining extra copies merely to make a link permanent.

References

Top comments (0)