A one-photo video tool has a small interface, but a surprisingly demanding backend. The user uploads a picture, pays, and expects a finished file. Behind that interaction are several separate questions: what exactly was promised, was a generation request accepted, and is the returned video safe to deliver?
This is a technical walkthrough of decisions in my project, Angry Dance Filter AI. It turns a photo into a specific kitchen dance routine. The useful engineering details are the boundaries around the generation request.
Start with one defined output
The product accepts one photo and targets a 14-second, vertical 9:16, 720p video with audio. The routine and kitchen come from a reference clip; the photo supplies the person's appearance. Users do not write a prompt or choose a different background.
That narrow scope makes the output contract concrete. In the TypeScript implementation, it is represented as:
export const PUBLIC_OUTPUT = {
durationSeconds: 14,
aspectRatio: '9:16',
resolution: '720p',
audio: true,
} as const;
Runtime validation checks deployment settings against that contract. If they disagree, new generation is unavailable rather than silently selling a different format.
This matters beyond the model call. A resolution change also affects checkout text, emails, translated copy, and documentation. A shared contract reduces drift, although prose that repeats those values still needs review.
Let the reference clip carry the motion
The generation request uses NanoAPI's Genjutsu model in motion-control mode. The server builds the request from a signed photo URL, a reference video URL, a server-owned prompt, an explicit resolution, and a webhook URL.
In this integration, the reference clip supplies the length, framing, motion, and audio. There is no separate duration or audio parameter in the task body, and no extra audio-merging stage in this path.
The tradeoff is limited creative control. The same scene and routine are part of the product contract. It also does not make identity preservation deterministic: faces, clothing details, and hands can still drift. The interface needs to explain those limits before someone uploads a photo.
A timeout is an unknown outcome
The most consequential failure case happens when the provider might have accepted a paid request, but the response never reaches the application.
Automatically repeating that POST can create a second task. Returning the user's credit immediately can also be wrong if the original task is still running.
The job processor therefore records a submission marker before sending the request and persists the exact provider request body. Once a task ID is available, subsequent processing reads that task instead of creating another one.
If submission was attempted but no task ID was recovered, the job is held for support review. The allowance remains reserved. A definitely rejected request follows a different path and can restore the allowance.
This is a deliberate tradeoff: an ambiguous job may need manual reconciliation. The marker does not create exactly-once delivery across two systems. In particular, a crash between recording the marker and sending the request can leave a job needing review even though nothing reached the provider.
Treat webhooks as a reason to check
The callback handler verifies a signature over the raw request body before parsing it. It also bounds the body size and checks the timestamp.
After validation, it uses the task ID to locate the local job and trigger processing. It does not trust a callback's success flag or output URL as the final result. The processor fetches the task through the provider API and checks its ID and model before advancing the job.
Signature verification and an authoritative task read solve different problems. The first authenticates the notification; the second determines the current task state. Neither replaces guarded updates in the local job processor.
Make failure behavior explicit in tests
The repository includes regression cases for ambiguous submissions, repeated processing, request snapshots, invalid webhook signatures, and mismatched task IDs or models.
One particularly useful assertion is that a simulated network timeout followed by another processing pass still produces only one provider submission. Another supplies a signed callback claiming success while the task API reports failure; the task API state wins.
For this kind of application, the small upload form is only the entrance. The design work is in preserving the output contract and distinguishing confirmed failure from uncertainty, without quietly creating another paid operation.
Top comments (0)