# From a photo to a talking clip

> Two calls. Make the picture you want, then make it speak.

Source: https://docs.nocamclub.com/guides/photo-to-clip

A common job: take one photo, put the person in a new place, and have them say something. Doing it in two steps gives you a look at the frame before you pay for the clip.

The commands use [jq](https://jqlang.org) to pick fields out of the answers. `RUN` makes this run's idempotency keys its own, so you can run the guide more than once:

```bash
RUN=$(date +%s)
```

## 1. Upload the photo

```bash
PHOTO_ID=$(curl -s https://api.nocamclub.com/v1/uploads \
  -H "Authorization: Bearer $NOCAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://docs.nocamclub.com/examples/mascot.jpg" }' | jq -r .id)
```

Use your own photo's address instead. The `id` works for 48 hours.

## 2. Make the frame

Vertical, since that's what the clip will be. Say what to keep.

```bash
FRAME_URL=$(curl -s https://api.nocamclub.com/v1/generations \
  -H "Authorization: Bearer $NOCAM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Prefer: wait=60" \
  -H "Idempotency-Key: rooftop-frame-$RUN" \
  -d '{
    "model": "nano-banana-2",
    "input": {
      "prompt": "Keep the face and hair of img1. The same person on a city rooftop at sunset, facing the camera, waist up. Phone photo.",
      "images": ["'"$PHOTO_ID"'"],
      "resolution": "2K",
      "aspect_ratio": "9:16"
    }
  }' | jq -r '.outputs[0].url')
```

Look at the result. If it isn't right, change the prompt and make another: a photo costs little and a clip costs more.

## 3. Make it speak

A result is a public link, so it goes straight to the next model.

Muse always makes 10 seconds with sound, and its shape follows the photo. About 2 words a second fits the length.

```bash
CLIP_ID=$(curl -s https://api.nocamclub.com/v1/generations \
  -H "Authorization: Bearer $NOCAM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: rooftop-clip-$RUN" \
  -d '{
    "model": "muse-video",
    "input": {
      "prompt": "img1 looks straight into the camera, smiles and says clearly: \"I made this whole video from one selfie.\" The camera does not move.",
      "images": ["'"$FRAME_URL"'"]
    }
  }' | jq -r .id)
```

Then poll `GET /v1/generations/$CLIP_ID` until `status` is `succeeded` or `failed`, usually a minute or two.

## Why two steps

- **You check the expensive part's input.** A bad frame costs a few credits. A bad clip costs more, and takes longer to find out.
- **You choose per step.** Use the model that's best at photos, then the one that's best at clips.
- **Failures stay small.** If the clip fails, the credits come back and the frame is still there.

## Keep it working in production

- Send a new `Idempotency-Key` per clip, and reuse it for retries of that clip.
- Read `error.code`: on `content_refused`, tell the person. On `provider_unavailable`, try again in a few minutes.
- Download the result: links stop after 7 days.
