# Timeline Editing Guide VideoDB provides a non-destructive timeline editor for composing videos from multiple assets, adding text and image overlays, mixing audio tracks, and trimming clips — all server-side without re-encoding or local tools. Use this for trimming, combining clips, overlaying audio/music on video, adding subtitles, and layering text or images. ## Prerequisites Videos, audio, and images **must be uploaded** to a collection before they can be used as timeline assets. For caption overlays, the video must also be **indexed for spoken words**. ## Core Concepts ### Timeline A `Timeline` is a virtual composition layer. Assets are placed on it either **inline** (sequentially on the main track) or as **overlays** (layered at a specific timestamp). Nothing modifies the original media; the final stream is compiled on demand. ```python from videodb.timeline import Timeline timeline = Timeline(conn) ``` ### Assets Every element on a timeline is an **asset**. VideoDB provides five asset types: | Asset | Import | Primary Use | |-------|--------|-------------| | `VideoAsset` | `from videodb.asset import VideoAsset` | Video clips (trim, sequencing) | | `AudioAsset` | `from videodb.asset import AudioAsset` | Music, SFX, narration | | `ImageAsset` | `from videodb.asset import ImageAsset` | Logos, thumbnails, overlays | | `TextAsset` | `from videodb.asset import TextAsset, TextStyle` | Titles, captions, lower-thirds | | `CaptionAsset` | `from videodb.editor import CaptionAsset` | Auto-rendered subtitles (Editor API) | ## Building a Timeline ### Add Video Clips Inline Inline assets play one after another on the main video track. The `add_inline` method only accepts `VideoAsset`: ```python from videodb.asset import VideoAsset video_a = coll.get_video(video_id_a) video_b = coll.get_video(video_id_b) timeline = Timeline(conn) timeline.add_inline(VideoAsset(asset_id=video_a.id)) timeline.add_inline(VideoAsset(asset_id=video_b.id)) stream_url = timeline.generate_stream() ``` ### Trim / Sub-clip Use `start` and `end` on a `VideoAsset` to extract a portion: ```python # Take only seconds 10–30 from the source video clip = VideoAsset(asset_id=video.id, start=10, end=30) timeline.add_inline(clip) ``` ### VideoAsset Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `asset_id` | `str` | required | Video media ID | | `start` | `float` | `0` | Trim start (seconds) | | `end` | `float\|None` | `None` | Trim end (`None` = full) | > **Warning:** The SDK does not validate negative timestamps. Passing `start=-5` is silently accepted but produces broken or unexpected output. Always ensure `start >= 0`, `start < end`, and `end <= video.length` before creating a `VideoAsset`. ## Text Overlays Add titles, lower-thirds, or captions at any point on the timeline: ```python from videodb.asset import TextAsset, TextStyle title = TextAsset( text="Welcome to the Demo", duration=5, style=TextStyle( fontsize=36, fontcolor="white", boxcolor="black", alpha=0.8, font="Sans", ), ) # Overlay the title at the very start (t=0) timeline.add_overlay(0, title) ``` ### TextStyle Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `fontsize` | `int` | `24` | Font size in pixels | | `fontcolor` | `str` | `"black"` | CSS colour name or hex | | `fontcolor_expr` | `str` | `""` | Dynamic font colour expression | | `alpha` | `float` | `1.0` | Text opacity (0.0–1.0) | | `font` | `str` | `"Sans"` | Font family | | `box` | `bool` | `True` | Enable background box | | `boxcolor` | `str` | `"white"` | Background box colour | | `boxborderw` | `str` | `"10"` | Box border width | | `boxw` | `int` | `0` | Box width override | | `boxh` | `int` | `0` | Box height override | | `line_spacing` | `int` | `0` | Line spacing | | `text_align` | `str` | `"T"` | Text alignment within the box | | `y_align` | `str` | `"text"` | Vertical alignment reference | | `borderw` | `int` | `0` | Text border width | | `bordercolor` | `str` | `"black"` | Text border colour | | `expansion` | `str` | `"normal"` | Text expansion mode | | `basetime` | `int` | `0` | Base time for time-based expressions | | `fix_bounds` | `bool` | `False` | Fix text bounds | | `text_shaping` | `bool` | `True` | Enable text shaping | | `shadowcolor` | `str` | `"black"` | Shadow colour | | `shadowx` | `int` | `0` | Shadow X offset | | `shadowy` | `int` | `0` | Shadow Y offset | | `tabsize` | `int` | `4` | Tab size in spaces | | `x` | `str` | `"(main_w-text_w)/2"` | Horizontal position expression | | `y` | `str` | `"(main_h-text_h)/2"` | Vertical position expression | ## Audio Overlays Layer background music, sound effects, or voiceover on top of the video track: ```python from videodb.asset import AudioAsset music = coll.get_audio(music_id) audio_layer = AudioAsset( asset_id=music.id, disable_other_tracks=False, fade_in_duration=2, fade_out_duration=2, ) # Start the music at t=0, overlaid on the video track timeline.add_overlay(0, audio_layer) ``` ### AudioAsset Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `asset_id` | `str` | required | Audio media ID | | `start` | `float` | `0` | Trim start (seconds) | | `end` | `float\|None` | `None` | Trim end (`None` = full) | | `disable_other_tracks` | `bool` | `True` | When True, mutes other audio tracks | | `fade_in_duration` | `float` | `0` | Fade-in seconds (max 5) | | `fade_out_duration` | `float` | `0` | Fade-out seconds (max 5) | ## Image Overlays Add logos, watermarks, or generated images as overlays: ```python from videodb.asset import ImageAsset logo = coll.get_image(logo_id) logo_overlay = ImageAsset( asset_id=logo.id, duration=10, width=120, height=60, x=20, y=20, ) timeline.add_overlay(0, logo_overlay) ``` ### ImageAsset Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `asset_id` | `str` | required | Image media ID | | `width` | `int\|str` | `100` | Display width | | `height` | `int\|str` | `100` | Display height | | `x` | `int` | `80` | Horizontal position (px from left) | | `y` | `int` | `20` | Vertical position (px from top) | | `duration` | `float\|None` | `None` | Display duration (seconds) | ## Caption Overlays There are two ways to add captions to video. ### Method 1: Subtitle Workflow (simplest) Use `video.add_subtitle()` to burn subtitles directly onto a video stream. This uses the `videodb.timeline.Timeline` internally: ```python from videodb import SubtitleStyle # Video must have spoken words indexed first (force=True skips if already done) video.index_spoken_words(force=True) # Add subtitles with default styling stream_url = video.add_subtitle() # Or customise the subtitle style stream_url = video.add_subtitle(style=SubtitleStyle( font_name="Arial", font_size=22, primary_colour="&H00FFFFFF", bold=True, )) ``` ### Method 2: Editor API (advanced) The Editor API (`videodb.editor`) provides a track-based composition system with `CaptionAsset`, `Clip`, `Track`, and its own `Timeline`. This is a separate API from the `videodb.timeline.Timeline` used above. ```python from videodb.editor import ( CaptionAsset, Clip, Track, Timeline as EditorTimeline, FontStyling, BorderAndShadow, Positioning, CaptionAnimation, ) # Video must have spoken words indexed first (force=True skips if already done) video.index_spoken_words(force=True) # Create a caption asset caption = CaptionAsset( src="auto", font=FontStyling(name="Clear Sans", size=30), primary_color="&H00FFFFFF", back_color="&H00000000", border=BorderAndShadow(outline=1), position=Positioning(margin_v=30), animation=CaptionAnimation.box_highlight, ) # Build an editor timeline with tracks and clips editor_tl = EditorTimeline(conn) track = Track() track.add_clip(start=0, clip=Clip(asset=caption, duration=video.length)) editor_tl.add_track(track) stream_url = editor_tl.generate_stream() ``` ### CaptionAsset Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `src` | `str` | `"auto"` | Caption source (`"auto"` or base64 ASS string) | | `font` | `FontStyling\|None` | `FontStyling()` | Font styling (name, size, bold, italic, etc.) | | `primary_color` | `str` | `"&H00FFFFFF"` | Primary text colour (ASS format) | | `secondary_color` | `str` | `"&H000000FF"` | Secondary text colour (ASS format) | | `back_color` | `str` | `"&H00000000"` | Background colour (ASS format) | | `border` | `BorderAndShadow\|None` | `BorderAndShadow()` | Border and shadow styling | | `position` | `Positioning\|None` | `Positioning()` | Caption alignment and margins | | `animation` | `CaptionAnimation\|None` | `None` | Animation effect (e.g., `box_highlight`, `reveal`, `karaoke`) | ## Compiling & Streaming After assembling a timeline, compile it into a streamable URL. Streams are generated instantly - no render wait times. ```python stream_url = timeline.generate_stream() print(f"Stream: {stream_url}") ``` For more streaming options (segment streams, search-to-stream, audio playback), see [streaming.md](streaming.md). ## Complete Workflow Examples ### Highlight Reel with Title Card ```python import videodb from videodb import SearchType from videodb.exceptions import InvalidRequestError from videodb.timeline import Timeline from videodb.asset import VideoAsset, TextAsset, TextStyle conn = videodb.connect() coll = conn.get_collection() video = coll.get_video("your-video-id") # 1. Search for key moments video.index_spoken_words(force=True) try: results = video.search("product announcement", search_type=SearchType.semantic) shots = results.get_shots() except InvalidRequestError as exc: if "No results found" in str(exc): shots = [] else: raise # 2. Build timeline timeline = Timeline(conn) # Title card title = TextAsset( text="Product Launch Highlights", duration=4, style=TextStyle(fontsize=48, fontcolor="white", boxcolor="#1a1a2e", alpha=0.95), ) timeline.add_overlay(0, title) # Append each matching clip for shot in shots: asset = VideoAsset(asset_id=shot.video_id, start=shot.start, end=shot.end) timeline.add_inline(asset) # 3. Generate stream stream_url = timeline.generate_stream() print(f"Highlight reel: {stream_url}") ``` ### Logo Overlay with Background Music ```python import videodb from videodb.timeline import Timeline from videodb.asset import VideoAsset, AudioAsset, ImageAsset conn = videodb.connect() coll = conn.get_collection() main_video = coll.get_video(main_video_id) music = coll.get_audio(music_id) logo = coll.get_image(logo_id) timeline = Timeline(conn) # Main video track timeline.add_inline(VideoAsset(asset_id=main_video.id)) # Background music — disable_other_tracks=False to mix with video audio timeline.add_overlay( 0, AudioAsset(asset_id=music.id, disable_other_tracks=False, fade_in_duration=3), ) # Logo in top-right corner for first 10 seconds timeline.add_overlay( 0, ImageAsset(asset_id=logo.id, duration=10, x=1140, y=20, width=120, height=60), ) stream_url = timeline.generate_stream() print(f"Final video: {stream_url}") ``` ### Multi-Clip Montage from Multiple Videos ```python import videodb from videodb.timeline import Timeline from videodb.asset import VideoAsset, TextAsset, TextStyle conn = videodb.connect() coll = conn.get_collection() clips = [ {"video_id": "vid_001", "start": 5, "end": 15, "label": "Scene 1"}, {"video_id": "vid_002", "start": 0, "end": 20, "label": "Scene 2"}, {"video_id": "vid_003", "start": 30, "end": 45, "label": "Scene 3"}, ] timeline = Timeline(conn) timeline_offset = 0.0 for clip in clips: # Add a label as an overlay on each clip label = TextAsset( text=clip["label"], duration=2, style=TextStyle(fontsize=32, fontcolor="white", boxcolor="#333333"), ) timeline.add_inline( VideoAsset(asset_id=clip["video_id"], start=clip["start"], end=clip["end"]) ) timeline.add_overlay(timeline_offset, label) timeline_offset += clip["end"] - clip["start"] stream_url = timeline.generate_stream() print(f"Montage: {stream_url}") ``` ## Two Timeline APIs VideoDB has two separate timeline systems. They are **not interchangeable**: | | `videodb.timeline.Timeline` | `videodb.editor.Timeline` (Editor API) | |---|---|---| | **Import** | `from videodb.timeline import Timeline` | `from videodb.editor import Timeline as EditorTimeline` | | **Assets** | `VideoAsset`, `AudioAsset`, `ImageAsset`, `TextAsset` | `CaptionAsset`, `Clip`, `Track` | | **Methods** | `add_inline()`, `add_overlay()` | `add_track()` with `Track` / `Clip` | | **Best for** | Video composition, overlays, multi-clip editing | Caption/subtitle styling with animations | Do not mix assets from one API into the other. `CaptionAsset` only works with the Editor API. `VideoAsset` / `AudioAsset` / `ImageAsset` / `TextAsset` only work with `videodb.timeline.Timeline`. ## Limitations & Constraints The timeline editor is designed for **non-destructive linear composition**. The following operations are **not supported**: ### Not Possible | Limitation | Detail | |---|---| | **No transitions or effects** | No crossfades, wipes, dissolves, or transitions between clips. All cuts are hard cuts. | | **No video-on-video (picture-in-picture)** | `add_inline()` only accepts `VideoAsset`. You cannot overlay one video stream on top of another. Image overlays can approximate static PiP but not live video. | | **No speed or playback control** | No slow-motion, fast-forward, reverse playback, or time remapping. `VideoAsset` has no `speed` parameter. | | **No crop, zoom, or pan** | Cannot crop a region of a video frame, apply zoom effects, or pan across a frame. `video.reframe()` is for aspect-ratio conversion only. | | **No video filters or color grading** | No brightness, contrast, saturation, hue, or color correction adjustments. | | **No animated text** | `TextAsset` is static for its full duration. No fade-in/out, movement, or animation. For animated captions, use `CaptionAsset` with the Editor API. | | **No mixed text styling** | A single `TextAsset` has one `TextStyle`. Cannot mix bold, italic, or colors within a single text block. | | **No blank or solid-color clips** | Cannot create a solid color frame, black screen, or standalone title card. Text and image overlays require a `VideoAsset` beneath them on the inline track. | | **No audio volume control** | `AudioAsset` has no `volume` parameter. Audio is either full volume or muted via `disable_other_tracks`. Cannot mix at a reduced level. | | **No keyframe animation** | Cannot change overlay properties over time (e.g., move an image from position A to B). | ### Constraints | Constraint | Detail | |---|---| | **Audio fade max 5 seconds** | `fade_in_duration` and `fade_out_duration` are capped at 5 seconds each. | | **Overlay positioning is absolute** | Overlays use absolute timestamps from the timeline start. Rearranging inline clips does not move their overlays. | | **Inline track is video only** | `add_inline()` only accepts `VideoAsset`. Audio, image, and text must use `add_overlay()`. | | **No overlay-to-clip binding** | Overlays are placed at a fixed timeline timestamp. There is no way to attach an overlay to a specific inline clip so it moves with it. | ## Tips - **Non-destructive**: Timelines never modify source media. You can create multiple timelines from the same assets. - **Overlay stacking**: Multiple overlays can start at the same timestamp. Audio overlays mix together; image/text overlays layer in add-order. - **Inline is VideoAsset only**: `add_inline()` only accepts `VideoAsset`. Use `add_overlay()` for `AudioAsset`, `ImageAsset`, and `TextAsset`. - **Trim precision**: `start`/`end` on `VideoAsset` and `AudioAsset` are in seconds. - **Muting video audio**: Set `disable_other_tracks=True` on `AudioAsset` to mute the original video audio when overlaying music or narration. - **Fade limits**: `fade_in_duration` and `fade_out_duration` on `AudioAsset` have a maximum of 5 seconds. - **Generated media**: Use `coll.generate_music()`, `coll.generate_sound_effect()`, `coll.generate_voice()`, and `coll.generate_image()` to create media that can be used as timeline assets immediately.