> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pictory.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Generate a Transition Between Two Frames

> Create an AI-generated video that starts on one image and ends on another

This guide shows you how to generate a video that begins on a first frame image and finishes on an end frame image. The model animates the change between the two, guided by your prompt. It is the natural fit for morphs, reveals, before-and-after shots, and joining two stills into one motion.

## What You Will Build

<CardGroup cols={2}>
  <Card title="Two Anchored Frames" icon="images">
    Fix both the opening and the closing image of the clip
  </Card>

  <Card title="Directed Motion" icon="play">
    Describe how the scene should travel from one frame to the other
  </Card>

  <Card title="Seamless Joins" icon="link">
    Bridge two separately generated stills or clips with one transition
  </Card>

  <Card title="Chainable" icon="repeat">
    Use the end frame as the first frame of the next request
  </Card>
</CardGroup>

## Before You Begin

Make sure you have:

* A [Pictory API key](https://app.pictory.ai/api-access)
* Node.js or Python installed on your machine
* The required packages installed
* Publicly accessible URLs for both the first frame and the end frame image

<CodeGroup>
  ```bash npm theme={null}
  npm install axios
  ```

  ```bash pip theme={null}
  pip install requests
  ```
</CodeGroup>

<Tip>
  First and end frame transitions are supported by `pixverse6` and `pixverse5.6`. Both frames should share the same aspect ratio; the output follows the frames.
</Tip>

## Step-by-Step Guide

### Step 1: Set Up Your Request

Prepare your API credentials, the two frame URLs, and a prompt that describes the motion between them.

<CodeGroup>
  ```javascript Node.js theme={null}
  import axios from "axios";

  const API_BASE_URL = "https://api.pictory.ai/pictoryapis";
  const API_KEY = "YOUR_API_KEY"; // Replace with your actual API key

  // Transition between two frames
  const videoRequest = {
    prompt: "The empty office fills with morning light as people arrive, settle at their desks, and the room comes alive",
    firstFrameImageUrl: "https://example.com/images/office-empty-dawn.png",
    endFrameImageUrl: "https://example.com/images/office-busy-morning.png",
    model: "pixverse6",
    aspectRatio: "16:9",
    duration: "8s"
  };
  ```

  ```python Python theme={null}
  import requests
  import time

  API_BASE_URL = "https://api.pictory.ai/pictoryapis"
  API_KEY = "YOUR_API_KEY"  # Replace with your actual API key

  # Transition between two frames
  video_request = {
      "prompt": "The empty office fills with morning light as people arrive, settle at their desks, and the room comes alive",
      "firstFrameImageUrl": "https://example.com/images/office-empty-dawn.png",
      "endFrameImageUrl": "https://example.com/images/office-busy-morning.png",
      "model": "pixverse6",
      "aspectRatio": "16:9",
      "duration": "8s"
  }
  ```
</CodeGroup>

<Note>
  `endFrameImageUrl` requires `firstFrameImageUrl`. Neither can be combined with `extendVideoUrl` or `referenceImageUrls`. Sending `endFrameImageUrl` on a model without transition support returns a `400` validation error before any credits are reserved.
</Note>

### Step 2: Submit the Video Generation Request

Send the request to the AI Studio video generation endpoint.

<CodeGroup>
  ```javascript Node.js theme={null}
  async function generateTransition() {
    try {
      console.log("Submitting transition generation request...");

      const response = await axios.post(
        `${API_BASE_URL}/v1/aistudio/videos`,
        videoRequest,
        {
          headers: {
            "Content-Type": "application/json",
            Authorization: API_KEY,
          },
        }
      );

      const jobId = response.data.data.jobId;
      console.log("Video generation started.");
      console.log("Job ID:", jobId);

      return jobId;
    } catch (error) {
      console.error("Error submitting request:", error.response?.data || error.message);
      throw error;
    }
  }
  ```

  ```python Python theme={null}
  def generate_transition():
      try:
          print("Submitting transition generation request...")

          response = requests.post(
              f"{API_BASE_URL}/v1/aistudio/videos",
              json=video_request,
              headers={
                  "Content-Type": "application/json",
                  "Authorization": API_KEY
              }
          )
          response.raise_for_status()

          job_id = response.json()["data"]["jobId"]
          print("Video generation started.")
          print(f"Job ID: {job_id}")

          return job_id

      except requests.exceptions.RequestException as error:
          print(f"Error submitting request: {error}")
          raise
  ```
</CodeGroup>

### Step 3: Poll for the Result

Check the job status at regular intervals until the video is ready.

<CodeGroup>
  ```javascript Node.js theme={null}
  async function waitForVideo(jobId) {
    console.log("\nPolling for video generation result...");

    while (true) {
      const response = await axios.get(
        `${API_BASE_URL}/v1/jobs/${jobId}`,
        {
          headers: { Authorization: API_KEY },
        }
      );

      const data = response.data;
      const status = data.data.status;
      console.log("Status:", status);

      if (status === "completed") {
        console.log("\nVideo generated successfully!");
        console.log("Video URL:", data.data.url);
        console.log("Duration:", data.data.duration);
        console.log("Last Frame URL:", data.data.lastFrameImageUrl);
        console.log("AI Credits Used:", data.data.aiCreditsUsed);
        return data;
      }

      if (status === "failed") {
        throw new Error("Video generation failed: " + JSON.stringify(data));
      }

      // Wait 15 seconds before polling again
      await new Promise(resolve => setTimeout(resolve, 15000));
    }
  }

  // Run the complete workflow
  generateTransition()
    .then(jobId => waitForVideo(jobId))
    .then(result => console.log("\nDone!"))
    .catch(error => console.error("Error:", error));
  ```

  ```python Python theme={null}
  def wait_for_video(job_id):
      print("\nPolling for video generation result...")

      while True:
          response = requests.get(
              f"{API_BASE_URL}/v1/jobs/{job_id}",
              headers={"Authorization": API_KEY}
          )
          response.raise_for_status()

          data = response.json()
          status = data["data"]["status"]
          print(f"Status: {status}")

          if status == "completed":
              print("\nVideo generated successfully!")
              print(f"Video URL: {data['data']['url']}")
              print(f"Duration: {data['data']['duration']}")
              print(f"Last Frame URL: {data['data']['lastFrameImageUrl']}")
              print(f"AI Credits Used: {data['data']['aiCreditsUsed']}")
              return data

          if status == "failed":
              raise Exception(f"Video generation failed: {data}")

          # Wait 15 seconds before polling again
          time.sleep(15)

  # Run the complete workflow
  if __name__ == "__main__":
      job_id = generate_transition()
      result = wait_for_video(job_id)
      print("\nDone!")
  ```
</CodeGroup>

## Understanding the Parameters

| Parameter | Type | Required | Default | Description |
| - | - | - | - | - |
| `prompt` | string | Yes | - | A text description of how the scene moves from the first frame to the end frame. Must be between 5 and 5,000 characters. |
| `firstFrameImageUrl` | string | Yes for a transition | - | A publicly accessible URL of the opening image. Must be a valid URI. |
| `endFrameImageUrl` | string | No | - | A publicly accessible URL of the closing image. Requires `firstFrameImageUrl`. Supported by `pixverse6` and `pixverse5.6`. |
| `model` | string | No | `pixverse5.6` | The AI model to use for generation. Transitions require `pixverse6` or `pixverse5.6`. See [Generate Video API](/api-reference/ai-studio/generate-video) for model capabilities and pricing. |
| `aspectRatio` | string | No | First supported ratio of the selected model | The output aspect ratio. For transitions, match it to the frames. |
| `duration` | string | No | First supported duration of the selected model | The video length. `pixverse6` supports `5s`, `8s`, `10s`, `15s`; `pixverse5.6` supports `5s`, `8s`, `10s`. |
| `audio` | boolean | No | `false` | Generate synchronized audio with the transition. Billed at the model's with-audio rate. |
| `trainingPreset` | string | No | - | `sales-training`, `product-training`, or `soft-skills-training`. Steers prompt enhancement and always turns it on, even when `enhancePrompt` is `false`. |
| `enhancePrompt` | boolean | No | `false` | Set to `true` to rewrite the prompt with the AI enhancer before generation. Off by default: the prompt is sent verbatim. |
| `webhook` | string | No | - | A URL to receive a POST notification when the job completes. Must be a valid URI. |

## Input Frames and the Delivered Last Frame

Two similarly named fields mean different things:

* `endFrameImageUrl` is the image you send. The generated video ends on it.
* `lastFrameImageUrl` is returned on the completed job and in [Get Generated Videos](/api-reference/ai-studio/get-videos). It is the last frame extracted from the delivered video, ready to use as the `firstFrameImageUrl` of a follow-up request.

For a transition the two will look alike, but only the returned one is guaranteed to match the delivered file pixel for pixel.

## Tips for Transitions

* **Describe the journey, not the endpoints.** The frames already fix where the clip starts and ends. Spend the prompt on what happens in between: the camera move, the pace, and the order in which things change.
* **Keep the frames compatible.** Similar framing, lighting, and aspect ratio between the two images give the model less to reconcile and produce smoother motion.
* **Generate the frames first.** Use the [Generate Image API](/api-reference/ai-studio/generate-image) with reference images to produce a consistent "before" and "after" still, then animate between them here.
* **Pick the duration for the distance.** Small changes read well at `5s`; a large scene change benefits from `8s` or `10s`.

## Next Steps

* [Generate Video from First Frame](/guides/ai-studio/video-from-first-frame) to animate from a single starting image
* [Generate Video from Reference Images](/guides/ai-studio/video-from-reference-images) to keep subjects consistent across clips
* [Generate Video with Audio](/guides/ai-studio/video-with-audio) to add sound to the transition
* [Generate Video API Reference](/api-reference/ai-studio/generate-video) for the complete parameter documentation


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.