Short answer: discover capabilities before creating a creator-video job, then process game thumbnails at upload only when the result is predictable and cheap to retain; otherwise generate on demand behind a queue. The choice is a data-contract decision, not a button in a dashboard. I would make the studio's discovery record explicit, run a tiny capability probe, and submit a job only after the requested format, duration, and dimensions have a matching capability.
That sounds fussy. It is less fussy than explaining to a creator why their preview is a black frame.
What should a creator video studio discover before job submission?
Discovery needs to answer four concrete questions: which input media formats are accepted, which output containers and codecs are available, what dimensions and duration limits apply, and whether the operation is synchronous or job-based. The browser's playable set is not the same as an encoder's accepted set. MDN's media formats guide is a useful starting point for separating container, codec, and browser support instead of calling everything “MP4.”
Measure twice.
Store the answer with a capability version and an observed-at timestamp. A request for a 1080p, 30-second trailer should resolve to a capability record before it enters the queue. If there is no match, return a fixable validation message to the editor. Do not create a job that can only fail later.
Here is a deliberately small TypeScript shape. It is an application contract; the transport adapter can map it to whichever service or self-hosted worker you run.
type Capability = {
operation: "thumbnail" | "video-generation";
inputContainers: string[];
outputContainers: string[];
codecs: string[];
maxWidth: number;
maxHeight: number;
maxDurationSeconds: number;
async: boolean;
version: string;
};
type Request = {
operation: Capability["operation"];
inputContainer: string;
outputContainer: string;
codec: string;
width: number;
height: number;
durationSeconds: number;
};
function supports(cap: Capability, req: Request): boolean {
return cap.operation === req.operation &&
cap.inputContainers.includes(req.inputContainer) &&
cap.outputContainers.includes(req.outputContainer) &&
cap.codecs.includes(req.codec) &&
req.width <= cap.maxWidth &&
req.height <= cap.maxHeight &&
req.durationSeconds <= cap.maxDurationSeconds;
}
The important bit is the boolean's evidence. Log the capability version alongside the submission. When a worker later reports a mismatch, you can tell whether the request was invalid, the capability changed, or the adapter translated a field incorrectly. I once chased a “generation timeout” that was really a dimension check missing from the preflight path. The queue was innocent.
Upload-time thumbnails or on-demand work?
Use upload-time processing when every published asset needs a thumbnail, the transform is deterministic, and storage of one derived image is acceptable. The upload transaction should enqueue a thumbnail task after the original is durably recorded; a retryable worker writes an immutable derived asset keyed by the source checksum and transform version. That makes retries idempotent and lets a CDN cache the result.
Choose on-demand generation when many uploads are never viewed, when creators can change crop or aspect ratio repeatedly, or when video generation is expensive and exploratory. Cache by (source, prompt, capabilityVersion, outputSpec), and expire entries according to actual access rather than guessing. The first viewer then pays the latency, so expose a pending state and a cancel path in the studio UI.
| Decision signal | Process at upload | Process on demand |
|---|---|---|
| View probability | Nearly every asset is displayed | Most assets stay private or unused |
| Latency goal | First preview must be ready | A spinner is acceptable |
| Output shape | Fixed dimensions and codec | Creator chooses variants interactively |
| Storage policy | Derived files are retained | Results can be evicted and regenerated |
There is no universal winner. Measure p95 time to first playable thumbnail, queue wait, bytes retained per upload, and the percentage of generated results that are ever viewed. A small sample from one week is better than a confident hunch, but your mileage may vary when a launch changes traffic shape.
How do you submit only after a capability match?
Keep preflight and submission as separate functions. Preflight is pure and fast; submission is the side effect. This lets a CLI show exactly why a request is rejected and makes tests independent of a remote service.
type Decision =
| { ok: true; capabilityVersion: string }
| { ok: false; reason: string };
function preflight(cap: Capability | undefined, req: Request): Decision {
if (!cap) return { ok: false, reason: "No capability record for this operation" };
if (!supports(cap, req)) {
return { ok: false, reason: "Input, output, codec, size, or duration is unsupported" };
}
return { ok: true, capabilityVersion: cap.version };
}
async function submit(cap: Capability | undefined, req: Request) {
const decision = preflight(cap, req);
if (!decision.ok) throw new Error(decision.reason);
return enqueue({ request: req, capabilityVersion: decision.capabilityVersion });
}
declare function enqueue(value: unknown): Promise<{ jobId: string }>;
The queue payload carries the exact decision that allowed submission. Workers should re-check resource limits, but they should not silently reinterpret a creator's request. If a capability is revoked, mark the job blocked with an actionable reason and preserve the original request for editing. Never auto-downgrade a codec or crop: that creates a different creative result while pretending the job succeeded. Keep the original JSON payload, the selected capability record, and the adapter's normalized response together in the job audit trail. When a creator reports a wrong aspect ratio two days later, support can replay the preflight decision without asking which dashboard setting happened to be active. That audit trail also gives a release engineer a clean diff: a changed capability version, a changed request, or a changed worker. Three different fixes. One record makes the distinction visible.
What are the failure modes and the honest fallback?
Capability documents drift. Encoders gain a profile, browser playback changes, and a new game client uploads an unfamiliar container. Treat discovery as a periodically refreshed dependency, not a one-time migration. Keep a canary asset for each output profile and run it through the same path used by production jobs.
The catch is operational complexity. Upload-time derivatives consume storage and reprocessing capacity; on-demand work adds cold-start latency and a more visible failure in the editor. Upload-time processing is not suitable when creators constantly revise specs. Stick with on-demand when retention is uncertain and regeneration is deterministic. Conversely, an always-on preview wall should not make every viewer wait for a cold job.
For observability, emit one event at discovery, one at preflight, one at enqueue, and one at publication. Include request ID, source checksum, capability version, output spec, queue latency, processing duration, and final media metadata. Redact prompts and user content according to your game's privacy policy. Alerts should distinguish unsupported input from worker saturation; they have different owners and different fixes.
The practical rule is simple: reject unknown capability combinations before submission, and choose upload-time or on-demand processing from observed viewing behavior. The studio remains portable because its contract describes intent, while adapters deal with service-specific details.
Top comments (0)