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

# Image Asset

> Add still images to an Editor timeline for branding, slideshows, overlays, and layered compositions

<a href="https://colab.research.google.com/github/video-db/videodb-cookbook/blob/main/editor/feature/image_asset_usage.ipynb" target="_blank">
  <img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab" noZoom />
</a>

`ImageAsset` places an uploaded image on an Editor timeline. The snippets below assume `conn`, a base `video`, and the named images already exist in your collection.

## ImageAsset fields

| Field  | Python                    | Node.js                                  | Description               |
| :----- | :------------------------ | :--------------------------------------- | :------------------------ |
| `id`   | `ImageAsset(id=image.id)` | `new EditorImageAsset({ id: image.id })` | Required image identifier |
| `crop` | `crop=Crop(left=0.3)`     | `crop: new Crop({ left: 0.3 })`          | Optional edge crop        |

<CodeGroup>
  ```python Python theme={null}
  from videodb.editor import Crop, ImageAsset

  image_asset = ImageAsset(
      id=landscape_image.id,
      crop=Crop(left=0.3),
  )
  ```

  ```javascript Node.js theme={null}
  import { Crop, EditorImageAsset } from 'videodb';

  const imageAsset = new EditorImageAsset({
    id: landscapeImage.id,
    crop: new Crop({ left: 0.3 }),
  });
  ```
</CodeGroup>

`Fit`, `Position`, `Offset`, and clip-level controls such as duration, scale, opacity, filters, and transitions belong to the clip. See [Aspect Ratio Control](/pages/act/programmable-editing/aspect-ratio-control) and [Clip Parameters](/pages/act/programmable-editing/clip-parameters) for those controls.

## Add a watermark

Place the base video on one track, then add a later image track for the logo. Later visual tracks render above earlier ones.

<CodeGroup>
  ```python Python theme={null}
  from videodb.editor import Clip, Fit, ImageAsset, Offset, Position, Timeline, Track, VideoAsset

  timeline = Timeline(conn)

  video_track = Track()
  video_track.add_clip(0, Clip(asset=VideoAsset(id=video.id), duration=10))

  logo_track = Track()
  logo_track.add_clip(
      0,
      Clip(
          asset=ImageAsset(id=logo_image.id),
          duration=10,
          fit=Fit.none,
          position=Position.top_right,
          scale=0.04,
          offset=Offset(x=-0.05, y=0.05),
          opacity=0.8,
      ),
  )

  timeline.add_track(video_track)
  timeline.add_track(logo_track)
  stream_url = timeline.generate_stream()
  ```

  ```javascript Node.js theme={null}
  import {
    Clip, EditorImageAsset, EditorTimeline, EditorVideoAsset,
    Fit, Offset, Position, Track
  } from 'videodb';

  const timeline = new EditorTimeline(conn);

  const videoTrack = new Track();
  videoTrack.addClip(0, new Clip({
    asset: new EditorVideoAsset({ id: video.id }),
    duration: 10,
  }));

  const logoTrack = new Track();
  logoTrack.addClip(0, new Clip({
    asset: new EditorImageAsset({ id: logoImage.id }),
    duration: 10,
    fit: Fit.none,
    position: Position.topRight,
    scale: 0.04,
    offset: new Offset({ x: -0.05, y: 0.05 }),
    opacity: 0.8,
  }));

  timeline.addTrack(videoTrack);
  timeline.addTrack(logoTrack);
  const streamUrl = await timeline.generateStream();
  ```
</CodeGroup>

<video controls width="100%">
  <source src="https://play.videodb.io/v1/2cb82d66-da71-4914-8c81-7731ec6d6de6.m3u8" type="application/x-mpegURL" />
</video>

## Build a slideshow

Put still-image clips back to back on one track. A short fade transition softens each handoff.

<CodeGroup>
  ```python Python theme={null}
  from videodb.editor import Clip, ImageAsset, Timeline, Track, Transition

  timeline = Timeline(conn)
  slides = Track()

  for start, image in [(0, landscape_image), (5, portrait_image), (10, square_image)]:
      slides.add_clip(
          start,
          Clip(
              asset=ImageAsset(id=image.id),
              duration=5,
              transition=Transition(in_="fade", out="fade", duration=1),
          ),
      )

  timeline.add_track(slides)
  stream_url = timeline.generate_stream()
  ```

  ```javascript Node.js theme={null}
  import { Clip, EditorImageAsset, EditorTimeline, Track, Transition } from 'videodb';

  const timeline = new EditorTimeline(conn);
  const slides = new Track();

  [
    [0, landscapeImage],
    [5, portraitImage],
    [10, squareImage],
  ].forEach(([start, image]) => {
    slides.addClip(start, new Clip({
      asset: new EditorImageAsset({ id: image.id }),
      duration: 5,
      transition: new Transition({ in: 'fade', out: 'fade', duration: 1 }),
    }));
  });

  timeline.addTrack(slides);
  const streamUrl = await timeline.generateStream();
  ```
</CodeGroup>

<video controls width="100%">
  <source src="https://play.videodb.io/v1/e9a7b96f-e3ad-44a3-bec2-487509b6872c.m3u8" type="application/x-mpegURL" />
</video>

## Create a staged zoom

Use consecutive clips of the same image at increasing scales. This is a staged zoom with crossfades, not a continuously animated Ken Burns effect.

<CodeGroup>
  ```python Python theme={null}
  from videodb.editor import Clip, Fit, ImageAsset, Position, Timeline, Track, Transition

  timeline = Timeline(conn)
  zoom = Track()

  zoom_steps = [
      (0, 1.0, Transition(out="fade", duration=0.5)),
      (2.5, 1.3, Transition(in_="fade", out="fade", duration=0.5)),
      (5, 1.6, Transition(in_="fade", duration=0.5)),
  ]

  for start, scale, transition in zoom_steps:
      zoom.add_clip(
          start,
          Clip(
              asset=ImageAsset(id=landscape_image.id),
              duration=3,
              scale=scale,
              fit=Fit.crop,
              position=Position.center,
              transition=transition,
          ),
      )

  timeline.add_track(zoom)
  stream_url = timeline.generate_stream()
  ```

  ```javascript Node.js theme={null}
  import {
    Clip, EditorImageAsset, EditorTimeline,
    Fit, Position, Track, Transition
  } from 'videodb';

  const timeline = new EditorTimeline(conn);
  const zoom = new Track();

  const zoomSteps = [
    [0, 1.0, new Transition({ out: 'fade', duration: 0.5 })],
    [2.5, 1.3, new Transition({ in: 'fade', out: 'fade', duration: 0.5 })],
    [5, 1.6, new Transition({ in: 'fade', duration: 0.5 })],
  ];

  zoomSteps.forEach(([start, scale, transition]) => {
    zoom.addClip(start, new Clip({
      asset: new EditorImageAsset({ id: landscapeImage.id }),
      duration: 3,
      scale,
      fit: Fit.crop,
      position: Position.center,
      transition,
    }));
  });

  timeline.addTrack(zoom);
  const streamUrl = await timeline.generateStream();
  ```
</CodeGroup>

<video controls width="100%">
  <source src="https://play.videodb.io/v1/ff9c8777-2040-4105-945c-798f5cd3f2ad.m3u8" type="application/x-mpegURL" />
</video>

## Layer multiple images

Build an overlapping three-layer composition with a base video, a full-frame image treatment, and a logo. Add the tracks in bottom-to-top order to control which visual appears above the others.

<CodeGroup>
  ```python Python theme={null}
  from videodb.editor import (
      Clip, Filter, Fit, ImageAsset, Offset, Position, Timeline, Track, VideoAsset
  )

  timeline = Timeline(conn)

  video_track = Track()
  video_track.add_clip(0, Clip(asset=VideoAsset(id=video.id), duration=10))

  overlay_track = Track()
  overlay_track.add_clip(
      0,
      Clip(
          asset=ImageAsset(id=square_image.id),
          duration=10,
          opacity=0.3,
          filter=Filter.boost,
      ),
  )

  logo_track = Track()
  logo_track.add_clip(
      0,
      Clip(
          asset=ImageAsset(id=logo_image.id),
          duration=10,
          position=Position.bottom_right,
          fit=Fit.none,
          scale=0.04,
          offset=Offset(x=-0.05, y=-0.05),
          opacity=0.9,
      ),
  )

  timeline.add_track(video_track)
  timeline.add_track(overlay_track)
  timeline.add_track(logo_track)
  stream_url = timeline.generate_stream()
  ```

  ```javascript Node.js theme={null}
  import {
    Clip, EditorImageAsset, EditorTimeline, EditorVideoAsset,
    Filter, Fit, Offset, Position, Track
  } from 'videodb';

  const timeline = new EditorTimeline(conn);

  const videoTrack = new Track();
  videoTrack.addClip(0, new Clip({
    asset: new EditorVideoAsset({ id: video.id }),
    duration: 10,
  }));

  const overlayTrack = new Track();
  overlayTrack.addClip(0, new Clip({
    asset: new EditorImageAsset({ id: squareImage.id }),
    duration: 10,
    opacity: 0.3,
    filter: Filter.boost,
  }));

  const logoTrack = new Track();
  logoTrack.addClip(0, new Clip({
    asset: new EditorImageAsset({ id: logoImage.id }),
    duration: 10,
    position: Position.bottomRight,
    fit: Fit.none,
    scale: 0.04,
    offset: new Offset({ x: -0.05, y: -0.05 }),
    opacity: 0.9,
  }));

  timeline.addTrack(videoTrack);
  timeline.addTrack(overlayTrack);
  timeline.addTrack(logoTrack);
  const streamUrl = await timeline.generateStream();
  ```
</CodeGroup>

<video controls width="100%">
  <source src="https://play.videodb.io/v1/1e108ce5-124d-4a89-a4db-dfa16501b100.m3u8" type="application/x-mpegURL" />
</video>

## Next steps

<CardGroup cols={2}>
  <Card title="Image Asset Usage Notebook" href="https://colab.research.google.com/github/video-db/videodb-cookbook/blob/main/editor/feature/image_asset_usage.ipynb" icon="book-open">
    Explore image cropping, overlays, transitions, and layered compositions.
  </Card>

  <Card title="Track Layering" href="/pages/act/programmable-editing/track-layering" icon="layers">
    Control time and visual z-order across tracks.
  </Card>
</CardGroup>
