> ## 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.

# Track Layering

> Control sequencing, simultaneous playback, and z-order across video, audio, image, text, and caption clips

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

Tracks control two dimensions of an Editor composition:

* **Time:** A clip's start value determines when it plays.
* **Layering:** The order in which visual tracks are added determines what appears on top.

Each track can contain multiple clips. Use one track for a simple sequence or multiple tracks to organize video, audio, graphics, text, and captions.

## Place Clips in Time

The first argument to `add_clip()` or `addClip()` is the clip's start time in the final timeline.

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

  track = Track()
  track.add_clip(0, intro_clip)    # Starts at 0s
  track.add_clip(4, main_clip)     # Starts at 4s
  track.add_clip(24, outro_clip)   # Starts at 24s
  ```

  ```javascript Node.js theme={null}
  import { Track } from 'videodb';

  const track = new Track();
  track.addClip(0, introClip);     // Starts at 0s
  track.addClip(4, mainClip);      // Starts at 4s
  track.addClip(24, outroClip);    // Starts at 24s
  ```
</CodeGroup>

Clip start times are explicit. They can create sequential playback, gaps, or overlaps.

## Sequence, Gap, or Overlap

Assume each clip is five seconds long:

| Placement                    | Result                |
| :--------------------------- | :-------------------- |
| Starts at `0`, `5`, and `10` | Back-to-back sequence |
| Starts at `0`, `7`, and `14` | Two-second gaps       |
| Starts at `0`, `3`, and `6`  | Two-second overlaps   |

Audio clips that overlap are mixed. Overlapping visual clips are composed according to track and clip layering.

## Visual Z-Order

Tracks added later render above earlier visual tracks.

<CodeGroup>
  ```python Python theme={null}
  timeline.add_track(video_track)  # Base layer
  timeline.add_track(image_track)  # Above video
  timeline.add_track(text_track)   # Above image
  timeline.add_track(caption_track)  # Top layer
  ```

  ```javascript Node.js theme={null}
  timeline.addTrack(videoTrack);    // Base layer
  timeline.addTrack(imageTrack);    // Above video
  timeline.addTrack(textTrack);     // Above image
  timeline.addTrack(captionTrack);  // Top layer
  ```
</CodeGroup>

<Warning>
  If a full-frame image is added after a text track, it can cover the text. Add background content first and overlays afterward.
</Warning>

## Build a Multi-Layer Composition

The following pattern combines a muted base video, background music, a logo, and a title.

<CodeGroup>
  ```python Python theme={null}
  from videodb.editor import (
      Timeline, Track, Clip,
      VideoAsset, AudioAsset, ImageAsset, TextAsset,
      Font, Background, Alignment,
      HorizontalAlignment, VerticalAlignment,
      Fit, Position, Offset
  )

  timeline = Timeline(conn)
  timeline.background = "#000000"
  timeline.resolution = "1280x720"

  # Layer 1: base video
  video_track = Track()
  video_track.add_clip(
      0,
      Clip(
          asset=VideoAsset(id=video.id, volume=0),
          duration=15,
      ),
  )

  # Layer 2: background music
  audio_track = Track()
  audio_track.add_clip(
      0,
      Clip(
          asset=AudioAsset(id=music.id, volume=0.2),
          duration=15,
      ),
  )

  # Layer 3: logo
  image_track = Track()
  image_track.add_clip(
      0,
      Clip(
          asset=ImageAsset(id=logo.id),
          duration=15,
          fit=Fit.none,
          position=Position.top_right,
          scale=0.12,
          offset=Offset(x=-0.04, y=0.04),
      ),
  )

  # Layer 4: title
  title = TextAsset(
      text="Product Update",
      font=Font(size=52, color="#FFFFFF"),
      background=Background(color="#111827", opacity=0.8),
      alignment=Alignment(
          horizontal=HorizontalAlignment.center,
          vertical=VerticalAlignment.top,
      ),
  )
  text_track = Track()
  text_track.add_clip(1, Clip(asset=title, duration=5))

  timeline.add_track(video_track)
  timeline.add_track(audio_track)
  timeline.add_track(image_track)
  timeline.add_track(text_track)

  stream_url = timeline.generate_stream()
  ```

  ```javascript Node.js theme={null}
  import {
    EditorTimeline, Track, Clip,
    EditorVideoAsset, EditorAudioAsset, EditorImageAsset, EditorTextAsset,
    Font, Background, Alignment,
    HorizontalAlignment, VerticalAlignment,
    Fit, Position, Offset
  } from 'videodb';

  const timeline = new EditorTimeline(conn);
  timeline.background = '#000000';
  timeline.resolution = '1280x720';

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

  const audioTrack = new Track();
  audioTrack.addClip(
    0,
    new Clip({
      asset: new EditorAudioAsset({ id: music.id, volume: 0.2 }),
      duration: 15
    })
  );

  const imageTrack = new Track();
  const logoClip = new Clip({
    asset: new EditorImageAsset({ id: logo.id }),
    duration: 15,
    position: Position.topRight,
    scale: 0.12,
    offset: new Offset({ x: -0.04, y: 0.04 })
  });
  logoClip.fit = Fit.none;
  imageTrack.addClip(0, logoClip);

  const title = new EditorTextAsset({
    text: 'Product Update',
    font: new Font({ size: 52, color: '#FFFFFF' }),
    background: new Background({ color: '#111827', opacity: 0.8 }),
    alignment: new Alignment({
      horizontal: HorizontalAlignment.center,
      vertical: VerticalAlignment.top
    })
  });
  const textTrack = new Track();
  textTrack.addClip(1, new Clip({ asset: title, duration: 5 }));

  timeline.addTrack(videoTrack);
  timeline.addTrack(audioTrack);
  timeline.addTrack(imageTrack);
  timeline.addTrack(textTrack);

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

## Add Timed Captions as the Top Layer

Create a timed spoken-word artifact from the source video, then use those word timings to build an explicit subtitle source for the caption clip.

```python Python theme={null}
from videodb.editor import (
    Track,
    Clip,
    CaptionAsset,
    CaptionAnimation,
    Positioning,
    CaptionAlignment,
    FontStyling,
)

# Create a timed spoken-word artifact for the caption track
caption_understanding = video.understand(
    analyzers=[
        {
            "type": "spoken_words",
            "name": "caption_words",
        }
    ]
)
caption_understanding.wait_until_complete()

caption_analyzer = caption_understanding.get_analyzer("caption_words")
caption_output = caption_analyzer.get_output()

caption_words = [
    {
        "text": word["text"],
        "start": word["start"],
        "end": word["end"],
    }
    for scene in caption_output["scenes"]
    for word in scene["data"]["words"]
]

caption_words.sort(key=lambda word: (word["start"], word["end"]))

print(f"✓ Timed caption words ready: {len(caption_words)}")

import base64

source_start = 0
source_end = 15

def format_srt_time(seconds):
    total_ms = max(0, int(round(float(seconds) * 1000)))
    hours, remainder = divmod(total_ms, 3_600_000)
    minutes, remainder = divmod(remainder, 60_000)
    secs, milliseconds = divmod(remainder, 1000)
    return f"{hours:02}:{minutes:02}:{secs:02},{milliseconds:03}"

def build_caption_cues(words, interval_start, interval_end, max_words=8):
    selected_words = []
    for word in words:
        if word["end"] <= interval_start or word["start"] >= interval_end:
            continue

        text = " ".join(word["text"].split())
        start = max(word["start"], interval_start)
        end = min(word["end"], interval_end)
        if text and end > start:
            selected_words.append({"text": text, "start": start, "end": end})

    cues = []
    group = []
    for word in selected_words:
        group.append(word)
        closes_sentence = word["text"].endswith((".", "!", "?"))
        if len(group) >= max_words or closes_sentence:
            cues.append(
                {
                    "start": group[0]["start"] - interval_start,
                    "end": group[-1]["end"] - interval_start,
                    "text": " ".join(item["text"] for item in group),
                }
            )
            group = []

    if group:
        cues.append(
            {
                "start": group[0]["start"] - interval_start,
                "end": group[-1]["end"] - interval_start,
                "text": " ".join(item["text"] for item in group),
            }
        )

    return [cue for cue in cues if cue["end"] > cue["start"]]

caption_cues = build_caption_cues(caption_words, source_start, source_end)
newline = chr(10)
caption_srt = newline.join(
    f"{number}{newline}"
    f"{format_srt_time(cue['start'])} --> {format_srt_time(cue['end'])}{newline}"
    f"{cue['text']}{newline}"
    for number, cue in enumerate(caption_cues, start=1)
)
caption_srt_base64 = base64.b64encode(
    caption_srt.encode("utf-8")
).decode("utf-8")

# Create the caption clip from the explicit timed subtitle source
caption_asset = CaptionAsset(
    src=caption_srt_base64,
    animation=CaptionAnimation.supersize,
    primary_color="&H00FFFFFF",  # White text (ASS format)
    secondary_color="&H0000D7FF",  # Orange for emphasized text
    position=Positioning(
        alignment=CaptionAlignment.bottom_center
    ),
    font=FontStyling(
        bold=True,
        size=32
    )
)

caption_clip = Clip(
    asset=caption_asset,
    duration=15
)

# Add to a new track
caption_track = Track()
caption_track.add_clip(0, caption_clip)

# Add this track to the timeline (topmost layer)
timeline.add_track(caption_track)

print("✓ Layer 5 added: Timed captions (bottom-center)")
```

## The Two Start Values

Source trimming and timeline placement are independent:

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

  track = Track()
  video_clip = Clip(
      asset=VideoAsset(
          id=video.id,
          start=10,  # Skip 10s in the source
      ),
      duration=8,
  )

  track.add_clip(3, video_clip)  # Show it at 3s in the output
  ```

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

  const track = new Track();
  const videoClip = new Clip({
    asset: new EditorVideoAsset({
      id: video.id,
      start: 10  // Skip 10s in the source
    }),
    duration: 8
  });

  track.addClip(3, videoClip);   // Show it at 3s in the output
  ```
</CodeGroup>

The output remains blank or shows lower layers for the first three seconds. At three seconds, the clip appears using media from the ten-second mark of the source.

## Track Organization Patterns

* Keep a base video sequence on the first visual track.
* Put persistent branding on a later image or text track.
* Use dedicated tracks for captions and timed callouts.
* Separate audio by role when the composition has several music, voice, or effect layers.
* Add tracks to the timeline in deliberate bottom-to-top order.

## Next Steps

<CardGroup cols={2}>
  <Card title="Track Layering Notebook" href="https://colab.research.google.com/github/video-db/videodb-cookbook/blob/main/editor/feature/tracks_layering_model.ipynb" icon="book-open">
    Build a complete video, audio, image, text, and caption composition and compare z-order changes.
  </Card>

  <Card title="Timeline Architecture" href="/pages/act/programmable-editing/timeline-architecture" icon="workflow">
    Understand how assets, clips, tracks, and timelines fit together.
  </Card>
</CardGroup>
