Image to Video API: Build a Product Demo Generator

Build a Python script that turns product images into polished video demos using image-to-video APIs. Comparison of Kling, MiniMax, Luma with costs and code.

Image to Video API: Build a Product Demo Generator in Python

Last quarter, our product team was burning through $500/month paying a freelancer to animate static product mockups into short video clips for landing pages. Twelve frames of a phone rotating, a SaaS dashboard scrolling—nothing fancy. When I found out each 5-second clip cost $40 and took 3 days to deliver, I spent a weekend wiring up an image to video API pipeline that now generates the same output in 90 seconds for $0.14 per clip. This article shows you exactly how I built it.

TL;DR: You can turn any product screenshot into a 5-second animated demo video using a Python script that calls an image-to-video AI API. The full pipeline costs ~$14 for 100 videos/month. I’ll compare four APIs, show complete runnable code, and explain the failure modes I hit along the way.

What Image-to-Video APIs Actually Do

An image to video API accepts a single static image (PNG/JPEG, typically 1024×1024 or 1280×720) and a text prompt describing the desired motion, then returns a short video (usually 5-10 seconds, 24-30fps).

Under the hood, these services run diffusion-based video generation models. The image acts as the first frame (or a strong conditioning signal), and the model hallucinates plausible future frames guided by your text prompt. The output is an MP4 file, typically 720p or 1080p.

The key parameters you control:

  • Input image — the reference frame. Higher resolution ≠ better output; 1024×1024 is the sweet spot.
  • Prompt — describes the motion, not the scene. “Camera slowly rotates 15 degrees clockwise” works better than “a beautiful product shot.”
  • Duration — 5s is standard. Longer clips (10s) cost 2x and often degrade in quality.
  • Mode/Tier — most APIs offer speed vs. quality trade-offs (turbo, standard, pro).

What they do NOT do: they won’t add UI elements, overlay text, or composite multiple images. Those are post-processing steps you handle yourself.

API Comparison: Price, Speed, and Quality

I tested four image to video AI services with the same input: a 1024×1024 product shot of a mobile app screen, prompted with “Screen scrolls up slowly, revealing content below the fold.”

ProviderModelPrice/Video (5s)Generation TimeResolutionBest For
Kling Video 3Turbo$0.07~30s720pFast iteration, drafts
Kling Video 3Standard$0.14~120s1080pProduction product demos
Kling Video 3Pro$0.28~180s1080pComplex camera movements
MiniMax H3Default$0.10~90s2KText-guided scene changes
Luma Dream MachineDefault$0.15~60s1080pSmooth organic motion
Gemini Flash VideoDefault$0.03~15s720pPrototyping, lowest cost

My recommendation: For product demo generation specifically, Kling Video 3 Standard hits the best balance. The motion is controlled and predictable—exactly what you want when animating a UI screenshot. MiniMax H3 produces impressive results but occasionally hallucinates UI elements that weren’t in the source image. Luma excels at organic/physical motion (products on tables, rotating objects) but struggles with screen-based content.

Gemini Flash Video is tempting at $0.03, but the quality gap is noticeable. Fine for internal prototypes, not for customer-facing landing pages.

The Product Demo Generator: Full Python Code

Here’s the complete pipeline. It takes a product image, generates 3 video variants with different motion prompts, and lets you pick the best one.

Prerequisites

pip install httpx asyncio pathlib

You’ll need an API key from your chosen provider. I’m using the Kling Video API here since it gave the most consistent results for product screenshots.

Core Implementation

import httpx
import asyncio
import time
import json
from pathlib import Path

# Configuration
API_BASE_URL = "https://api.example.com/v1"  # Replace with your provider endpoint
API_KEY = "your-api-key-here"

HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

# Motion prompts optimized for product demos
DEMO_PROMPTS = [
    "Camera slowly zooms in on the screen while content scrolls up smoothly",
    "Gentle 10-degree clockwise rotation revealing the product from a slight angle",
    "Subtle parallax effect, background shifts slightly while product stays centered",
]


async def submit_video_generation(
    client: httpx.AsyncClient,
    image_url: str,
    prompt: str,
    duration: int = 5,
    mode: str = "standard",
) -> str:
    """Submit an image-to-video generation task. Returns task ID."""
    payload = {
        "model": "kling-video-3",
        "mode": mode,
        "input": {
            "image_url": image_url,
            "prompt": prompt,
        },
        "duration": duration,
        "output_format": "mp4",
    }

    response = await client.post(
        f"{API_BASE_URL}/video/image-to-video",
        headers=HEADERS,
        json=payload,
        timeout=30.0,
    )
    response.raise_for_status()
    data = response.json()
    return data["task_id"]


async def poll_task_status(
    client: httpx.AsyncClient,
    task_id: str,
    max_wait: int = 300,
    poll_interval: int = 10,
) -> dict:
    """Poll until task completes or times out."""
    elapsed = 0
    while elapsed < max_wait:
        response = await client.get(
            f"{API_BASE_URL}/video/tasks/{task_id}",
            headers=HEADERS,
            timeout=15.0,
        )
        response.raise_for_status()
        result = response.json()

        status = result["status"]
        if status == "completed":
            return result
        elif status == "failed":
            raise RuntimeError(f"Task {task_id} failed: {result.get('error', 'unknown')}")

        await asyncio.sleep(poll_interval)
        elapsed += poll_interval

    raise TimeoutError(f"Task {task_id} timed out after {max_wait}s")


async def download_video(
    client: httpx.AsyncClient,
    video_url: str,
    output_path: Path,
) -> Path:
    """Download generated video to local file."""
    response = await client.get(video_url, timeout=60.0)
    response.raise_for_status()
    output_path.write_bytes(response.content)
    return output_path


async def generate_product_demos(
    image_url: str,
    output_dir: str = "./output",
    mode: str = "standard",
) -> list[Path]:
    """Generate 3 video variants from a single product image."""
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    results = []

    async with httpx.AsyncClient() as client:
        # Submit all 3 variants in parallel
        print(f"Submitting 3 video generation tasks ({mode} mode)...")
        tasks = []
        for i, prompt in enumerate(DEMO_PROMPTS):
            task_id = await submit_video_generation(
                client, image_url, prompt, duration=5, mode=mode
            )
            tasks.append((i, task_id, prompt))
            print(f"  Variant {i+1}: task_id={task_id}")

        # Poll all tasks concurrently
        print("Waiting for generation to complete...")
        start_time = time.time()

        async def process_task(idx, task_id, prompt):
            result = await poll_task_status(client, task_id)
            video_url = result["output"]["video_url"]
            file_path = output_path / f"demo_variant_{idx+1}.mp4"
            await download_video(client, video_url, file_path)
            elapsed = time.time() - start_time
            print(f"  Variant {idx+1} done in {elapsed:.1f}s → {file_path}")
            return file_path

        completed = await asyncio.gather(
            *[process_task(idx, tid, p) for idx, tid, p in tasks],
            return_exceptions=True,
        )

        for item in completed:
            if isinstance(item, Exception):
                print(f"  WARNING: One variant failed: {item}")
            else:
                results.append(item)

    total_time = time.time() - start_time
    print(f"\nDone. {len(results)}/3 variants generated in {total_time:.1f}s")
    print(f"Estimated cost: ${len(results) * 0.14:.2f} (standard mode)")
    return results


# Usage
if __name__ == "__main__":
    IMAGE_URL = "https://your-bucket.s3.amazonaws.com/product-screenshot.png"

    videos = asyncio.run(generate_product_demos(IMAGE_URL, mode="standard"))
    print(f"\nGenerated {len(videos)} videos. Review them and pick the best one.")

What This Code Does

  1. Parallel submission — All 3 variants go out simultaneously, so total wait time equals the slowest single generation (~120s), not 3× that.
  2. Async polling — Non-blocking status checks every 10 seconds.
  3. Graceful failure handling — If one variant fails (which happens ~5% of the time), the others still complete.
  4. Cost tracking — Prints estimated cost after each run.

Extending It: Batch Processing

For teams running this on a product catalog:

async def batch_generate(image_urls: list[str], output_base: str = "./output"):
    """Process multiple product images, 3 variants each."""
    all_results = {}
    for i, url in enumerate(image_urls):
        print(f"\n--- Product {i+1}/{len(image_urls)} ---")
        output_dir = f"{output_base}/product_{i+1}"
        videos = await generate_product_demos(url, output_dir=output_dir)
        all_results[url] = videos
        # Rate limiting: most APIs allow 5-10 concurrent tasks
        await asyncio.sleep(2)
    return all_results

Cost Math: 100 Product Demos Per Month

Here’s what the monthly bill looks like at different scales:

ScaleModeVideos GeneratedMonthly CostCost per Final Demo
100 demosStandard (3 variants each)300$42.00$0.42
100 demosTurbo (3 variants each)300$21.00$0.21
100 demosStandard (1 variant)100$14.00$0.14
50 demosPro (3 variants each)150$42.00$0.84

Compare this to the $500/month freelancer cost we started with. Even at the highest quality tier with 3 variants per product, you’re spending $42—a 92% reduction.

Hidden costs to budget for:

  • Image hosting (S3 or similar): ~$0.50/month for 100 images
  • Compute for the script: negligible (runs on any machine with Python)
  • Failed generations (~5% failure rate): add 5% to your video count

Realistic monthly budget for 100 product demos: $44

Gotchas and Failure Modes

After running this in production for 3 months, here’s what I’ve learned:

1. Prompt specificity matters more than image quality. “Scroll up” produces inconsistent results. “Screen content scrolls up at a moderate pace, 200 pixels over 5 seconds” gives you reproducible motion. Be explicit about direction, speed, and magnitude.

2. Transparent PNGs cause artifacts. Every API I tested struggles with transparency. Convert to JPEG or add a solid background before submitting. This alone fixed 60% of our “weird output” issues.

3. 10-second videos degrade after frame 150. Diffusion models accumulate errors over time. For product demos, 5 seconds is the sweet spot. If you need longer, generate two 5s clips and stitch them.

4. Rate limits are real and poorly documented. Kling allows 10 concurrent tasks per API key. MiniMax caps at 5. Exceed these and you get 429s with no retry-after header. Build in conservative delays.

5. Aspect ratio must match your input image. Submitting a 16:9 image with a 1:1 output config produces stretched, distorted video. Always match input and output aspect ratios, or crop first.

6. Results are non-deterministic. Same image + same prompt ≠ same output. This is why we generate 3 variants and pick the best. Budget for the extra API calls.

FAQ

Q: Can I use the generated videos commercially? A: Yes. All four APIs discussed here grant commercial usage rights for generated content. Check individual terms of service for specifics around volume and redistribution.

Q: What image resolution should I use? A: 1024×1024 or 1280×720 produces the best results across all providers. Larger images get downscaled anyway, and smaller ones (<512px) produce blurry output. The model’s internal resolution is the bottleneck, not your input size.

Q: How do I add text overlays or branding to the generated videos? A: The AI video from image APIs output raw video without text or branding. Use FFmpeg for post-processing:

ffmpeg -i demo_variant_1.mp4 -vf "drawtext=text='Try Free':fontsize=24:x=40:y=40:fontcolor=white" output_branded.mp4

Q: What happens when the API is down or slow? A: Build retry logic with exponential backoff. In my experience, Kling has ~99.5% uptime but occasional 2-3 minute latency spikes. The polling pattern in the code above handles slow responses gracefully; you just need to set max_wait high enough (300s works in practice).

Q: Can I fine-tune the model for my specific product style? A: Not currently. None of these providers offer fine-tuning for their video generation models. The workaround is prompt engineering—build a library of prompts that work for your product category and reuse them. I maintain 8-10 tested prompts for different UI animation types.

Try These Models

All four image-to-video models mentioned in this article are available through SandBase with a unified OpenAI-compatible API. You can switch between Kling, MiniMax, Luma, and Gemini by changing one model parameter — no separate accounts, no per-provider SDKs.

# Same interface for any video model
response = client.post("/v1/run", json={
    "model": "kwaivgi/kling-video-3-standard",  # or "minimax/h3", "luma/dream-machine"
    "prompt": "Smooth rotation of the product...",
    "input_image": image_url,
    "aspect_ratio": "16:9"
})

Browse available video models: sandbase.ai/models


Key Takeaways

  1. An image to video API replaces expensive manual animation for product demo content. The quality is good enough for landing pages, social media, and sales decks.

  2. Kling Video 3 Standard is the best product video generator for UI/screenshot content. Predictable motion, 1080p output, $0.14 per clip. Use MiniMax H3 for physical product shots and Luma for organic motion.

  3. Generate multiple variants. Video generation is non-deterministic. Budget for 3 variants per final output and pick the winner manually or with a scoring heuristic.

  4. Keep clips to 5 seconds. Quality drops after that. Stitch shorter clips for longer content.

  5. The real savings are in iteration speed. Going from 3-day turnaround to 90 seconds means your product team can test 20 video concepts in the time it took to get one from a freelancer.

The Kling video API and its competitors have matured enough that “good enough” video is now a commodity. The competitive advantage shifts from having video at all to having the right motion, the right framing, and the speed to iterate on both.