.. toc::
.. llms_copy::NLE Timeline
π Full documentation: [github.com/pip-install-python/dash-nle-timeline](https://github.com/pip-install-python/dash-nle-timeline) β complete API reference and examples in the repository. This page is the quick-start overview.
Installation
[Visit GitHub Repo](https://github.com/pip-install-python/dash-nle-timeline) Β· [PyPI](https://pypi.org/project/dash-nle-timeline/)
``bash pip install dash-nle-timeline ``
Introduction
A calendar or scheduler widget thinks in *dates*. A video editor needs an integer frame grid, a real draggable playhead, snapping to clip edges / markers / grid, audio waveforms, and edits expressed as data. dash-nle-timeline is that β a purpose-built non-linear-editor (NLE) timeline for Dash. Only small JSON travels over callbacks (the control plane); your media stays on the media plane (a <video>, an audio engine, a server render) driven *off* the timeline's outputs.
The package ships two components:
DashNleTimelineβ frame ruler, stacked track lanes (video/audio/camera), clip rectangles, a draggable playhead, snapping, trim/resize/split/delete, waveforms from precomputedpeaks,ctrl/β + wheelzoom, keyboard shortcuts, and an imperativecommandprop.DashNleSceneβ a Hybrid DOM + Canvas preview compositor driven byplayhead, with overlaylayers, atilesetbackground, and achildrenslot that pairs with [dash-leaflet2](https://github.com/pip-install-python/dash-leaflet2) for map-focused editing (fly a map from a camera keyframe track while compositing video on top).
Quick Start
Frames β not seconds β are the canonical unit (seconds = frame / fps). Give the timeline an explicit height via style, define tracks, then place clips on them by trackId. Audio lanes paint waveforms from precomputed peaks β the component paints bars only, it never decodes audio.
.. exec::docs.dash_nle_timeline.quick_start :code: false
```python
File: docs/dash_nle_timeline/quick_start.py
import math
import dash_nle_timeline as nle import dash_mantine_components as dmc
Precomputed audio peaks (magnitude format: one positive value per bucket, 0..1).
In a real app you would compute these server-side from the audio file.
PEAKS = [abs(math.sin(i / 7)) * 0.8 + 0.15 for i in range(220)]
component = dmc.Paper( withBorder=True, p="md", children=dmc.Stack( [ dmc.Text("A static three-track timeline β scrub the playhead, " "drag clips, trim edges, or press S to split.", size="sm", c="dimmed"), nle.DashNleTimeline( id="nle-quick-start-tl", fps=30, duration=300, pixelsPerSecond=90, tracks=[ {"id": "v1", "kind": "video", "label": "V1"}, {"id": "v2", "kind": "video", "label": "V2"}, {"id": "a1", "kind": "audio", "label": "A1"}, ], clips=[ {"id": "c1", "trackId": "v1", "start": 0, "duration": 90, "label": "intro.mp4", "color": "indigo"}, {"id": "c2", "trackId": "v1", "start": 100, "duration": 120, "label": "main.mp4", "color": "teal"}, {"id": "c3", "trackId": "v2", "start": 60, "duration": 80, "label": "overlay.png", "color": "orange"}, {"id": "c4", "trackId": "a1", "start": 0, "duration": 220, "label": "music.wav", "peaks": PEAKS, "peaksFormat": "magnitude", "peaksPerFrame": 1}, ], markers=[ {"id": "m1", "frame": 100, "label": "cut", "color": "gold"}, ], style={"width": "100%", "height": "260px"}, ), ], gap="sm", ), ) ```
---
Python Owns Clips β Apply lastEdit in a Callback
This is the core pattern of the component. Every committed edit (move, resize, trim, split, delete, select, seek) is reported as a single lastEdit dict. It carries a timestamp, so a Dash Input on it always fires β even for repeated identical edits.
The component applies edits to its own optimistic copy of clips (so they persist visually without a round-trip), but Python is the source of truth: a callback reads lastEdit, applies it to the clip list, and writes clips back. Writing back is how you validate, override, or persist edits β reject an edit by returning the unmodified list.
.. exec::docs.dash_nle_timeline.edits_callback :code: false
```python
File: docs/dash_nle_timeline/edits_callback.py
import json
import dash_nle_timeline as nle import dash_mantine_components as dmc from dash import callback, Input, Output, State, no_update
component = dmc.Paper( withBorder=True, p="md", children=dmc.Stack( [ dmc.Text("Move, resize, trim, or split a clip β every committed edit " "arrives in Python as one lastEdit dict.", size="sm", c="dimmed"), nle.DashNleTimeline( id="nle-edits-tl", fps=30, duration=240, tracks=[ {"id": "v1", "kind": "video", "label": "V1"}, {"id": "v2", "kind": "video", "label": "V2"}, ], clips=[ {"id": "c1", "trackId": "v1", "start": 0, "duration": 70, "label": "shot A", "color": "indigo"}, {"id": "c2", "trackId": "v1", "start": 90, "duration": 80, "label": "shot B", "color": "teal"}, {"id": "c3", "trackId": "v2", "start": 40, "duration": 60, "label": "title", "color": "orange"}, ], style={"width": "100%", "height": "200px"}, ), dmc.Text("Last edit event:", size="sm", fw=500), dmc.Code(id="nle-edits-out", block=True, children="(interact with the timeline)"), ], gap="sm", ), )
@callback( Output("nle-edits-out", "children"), Input("nle-edits-tl", "lastEdit"), prevent_initial_call=True, ) def show_edit(edit): return json.dumps(edit, indent=2)
Python owns clips: apply each reported edit to the list and feed it back.
The component edits its own copy optimistically, so writing clips back is how
you validate, override, or persist the result.
@callback( Output("nle-edits-tl", "clips"), Input("nle-edits-tl", "lastEdit"), State("nle-edits-tl", "clips"), prevent_initial_call=True, ) def apply_edit(edit, clips): if not edit: return no_update by_id = {c["id"]: dict(c) for c in clips} edit_type = edit.get("type") clip = by_id.get(edit.get("clipId"))
if edit_type == "move" and clip: clip["start"] = edit["start"] clip["trackId"] = edit.get("trackId", clip["trackId"]) elif edit_type in ("resize", "trim") and clip: for key in ("start", "duration", "inPoint"): if key in edit: clip[key] = edit[key] elif edit_type == "delete" and clip: del by_id[clip["id"]] else: # select / seek / split / paste etc. β nothing to write back here return no_update return list(by_id.values()) ```
---
Playback and the Playhead
Setting playing=True starts a requestAnimationFrame loop that advances playhead at fps (writes throttled by playheadThrottleMs). The component plays no media itself β drive a real <video>, an audio engine, or a DashNleScene off the playhead output. The playhead prop round-trips: the component writes it on scrub/seek/playback, and a callback may write it to move the playhead programmatically. loop=True wraps back to frame 0 at duration.
.. exec::docs.dash_nle_timeline.playback :code: false
```python
File: docs/dash_nle_timeline/playback.py
import dash_nle_timeline as nle import dash_mantine_components as dmc from dash import callback, Input, Output, State
FPS = 30 DURATION = 240
component = dmc.Paper( withBorder=True, p="md", children=dmc.Stack( [ dmc.Text("Playback is a requestAnimationFrame loop driven by the " "playing prop β the component plays no media itself, it just " "advances playhead at the configured fps.", size="sm", c="dimmed"), nle.DashNleTimeline( id="nle-playback-tl", fps=FPS, duration=DURATION, loop=True, tracks=[ {"id": "v1", "kind": "video", "label": "V1"}, ], clips=[ {"id": "c1", "trackId": "v1", "start": 0, "duration": 100, "label": "scene 1", "color": "indigo"}, {"id": "c2", "trackId": "v1", "start": 110, "duration": 110, "label": "scene 2", "color": "teal"}, ], style={"width": "100%", "height": "160px"}, ), dmc.Group( [ dmc.Button("Play", id="nle-playback-btn", color="teal", size="sm"), dmc.Text("frame 0 | 0.000s", id="nle-playback-out", size="sm", ff="monospace", c="dimmed"), ], gap="md", ), ], gap="sm", ), )
@callback( Output("nle-playback-tl", "playing"), Output("nle-playback-btn", "children"), Input("nle-playback-btn", "n_clicks"), State("nle-playback-tl", "playing"), prevent_initial_call=True, ) def toggle_play(_n, playing): now_playing = not bool(playing) return now_playing, ("Pause" if now_playing else "Play")
@callback( Output("nle-playback-out", "children"), Input("nle-playback-tl", "playhead"), ) def show_playhead(frame): frame = frame or 0 return f"frame {frame} | {frame / FPS:.3f}s" ```
Keyboard shortcuts (enabled by default via keyboard): Space play/pause, arrows step (Shift = 1s), S split at playhead, Delete, β/Ctrl+C/V/D copy/paste/duplicate, β/Ctrl+A select all, Home/End, +/- zoom. The imperative command prop dispatches the same actions from Python (play, pause, seek, splitAtPlayhead, zoomToFit, β¦), de-duped by a unique id.
---
DashNleScene
DashNleScene is the preview side: a compositor stage that renders, at the current playhead, a background β a dash-leaflet2 Map in children and/or a native XYZ tileset β then overlay layers (image / video / text) with per-layer [start, end) visibility windows and a screen-space camera. Sync playhead and playing from a DashNleTimeline, and drive the tileset.viewport from a camera-kind track's cameraKeyframes to fly the map while compositing video on top.
```python import dash_nle_timeline as nle
scene = nle.DashNleScene( id="nle-scene", fps=30, playhead=0, # sync from DashNleTimeline resolution=[1280, 720], tileset={ "url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", "viewport": {"center": [48.8566, 2.3522], "zoom": 12}, }, layers=[ {"id": "title", "kind": "text", "text": "Paris flyover", "rect": [0.05, 0.05, 0.9, 0.2], "start": 0, "end": 90}, ], style={"width": "100%"}, ) ```
For full map interactivity (pan/zoom/controls/flyTo) put a dash-leaflet2 Map in children instead of using tileset.
---
DashNleTimeline Props
| Property | Type | Default | Description | |:---------|:-----|:--------|:------------| | id | string | - | Component ID for Dash callbacks. | | fps | number | 30 | Frames per second β defines the integer-frame grid and frameβsecond conversions. | | duration | number | 300 | Total timeline length in integer frames. | | playhead | number | - | Current playhead position in integer frames. Written on scrub/seek/playback; writable from callbacks. | | playing | boolean | - | When True, a rAF loop advances playhead at fps. The component plays no media itself. | | loop | boolean | False | Loop back to 0 when the playhead reaches duration. | | playheadThrottleMs | number | 60 | Min ms between playhead writes during playback. | | tracks | list of dicts | - | Track lanes, top-to-bottom. Keys: id*, kind ('video'/'audio'/'camera'), label, height, locked, muted, color. | | clips | list of dicts | - | Clips on tracks. Keys: id*, trackId*, start*, duration* (frames), inPoint, label, color, src, peaks, peaksFormat ('minmax'/'magnitude'), peaksPerFrame, thumbnails, locked. Python is the source of truth. | | lastEdit | dict | - | [Readonly] Most recent committed edit. Keys: type* (move/resize/trim/split/delete/select/seek/copy/paste), timestamp*, clipId, clipIds, trackId, start, duration, inPoint, frame, resultIds, snapped. | | markers | list of dicts | - | Labeled time points: visual guides + snap targets. Keys: id*, frame*, label, color. | | cameraKeyframes | list of dicts | - | Keyframes for 'camera' tracks. Keys: id*, trackId*, time*, center* [lat, lng], zoom*, bearing, easing, label. | | selectedClipIds | list of strings | - | Currently selected clip ids. Round-trips; writable from Python. | | selectedClipId | string | - | [Readonly] The single selected clip id, or None when zero/many selected. | | command | dict | - | Imperative command, de-duped by id. Keys: id*, type* (play/pause/togglePlay/seek/stepFrame/splitAtPlayhead/deleteSelected/copy/paste/duplicate/selectAll/zoomToFit/zoomTo/scrollToFrame), payload. | | pixelsPerSecond | number | 120 | Horizontal zoom as pixels per second. | | minPixelsPerSecond | number | 8 | Lower clamp for wheel-zoom. | | maxPixelsPerSecond | number | 4000 | Upper clamp for wheel-zoom. | | scrollX | number | - | Horizontal scroll offset of the lane viewport, in CSS px from frame 0. | | snapping | boolean | True | Master enable for snapping during move/resize/trim. | | snapTargets | list | ['clips','playhead','markers'] | Snap-eligible line categories: 'playhead', 'grid', 'markers', 'clips'. | | snapThreshold | number | 8 | Snap activation distance in screen px. | | gridFrames | number | 0 | Grid spacing in frames for 'grid' snapping. 0 = off. | | allowCrossTrackMove | boolean | True | Allow dragging a clip onto another lane of the same kind. | | multiSelect | boolean | True | Allow shift/cmd-click multi-selection. | | keyboard | boolean | True | Enable all keyboard shortcuts. | | readOnly | boolean | False | View-only: disable editing (scrub + selection + zoom still work). | | controls | boolean | True | Show the built-in transport bar above the ruler. | | theme | string | 'dark' | Chrome color theme: 'dark' or 'light'. | | headerWidth | number | 140 | Width of the left track-header column in px. | | rulerHeight | number | 28 | Height of the time-ruler row in px. | | className | string | - | CSS class name(s) for the root element. | | style | object | - | Inline CSS styles β give the timeline an explicit width/height. |
DashNleScene Props
| Property | Type | Default | Description | |:---------|:-----|:--------|:------------| | id | string | - | Component ID for Dash callbacks. | | children | node | - | Background slot β e.g. a dash-leaflet2 Map. Rendered behind the tileset and overlay layers, filling the stage. | | playhead | number | - | Current playhead in integer frames (typically synced from a DashNleTimeline). Layers with a [start, end) window show/hide on it. | | playing | boolean | False | When True, video layers play natively; when False they seek to playhead / fps (frame-accurate scrub). Sync from the timeline. | | fps | number | 30 | Maps the playhead to seconds for video layers. | | layers | list of dicts | - | Overlay layers, bottom-to-top by z then array order. Keys: id*, kind ('image'/'video'/'text'), src, text, rect [x,y,w,h] (0..1), opacity, rotation, start, end, srcStart, z, color, fontSize. | | tileset | dict | - | Native XYZ tileset background. Keys: url*, viewport* ({center: [lat,lng], zoom}), tileSize (256), maxZoom (19), opacity (1), subdomains (['a','b','c']). | | camera | dict | - | Screen-space pan/zoom of the overlay layer group: {x: 0, y: 0, zoom: 1}. | | resolution | list | [1280, 720] | Output stage resolution [width, height] in px (sets the aspect ratio). | | background | string | '#000000' | Stage background CSS color (behind everything). | | className | string | - | CSS class name(s) for the root element. | | style | object | - | Inline CSS styles for the component. |
*\* = required key inside the dict.*
Note for AI agents: This is the static, prerendered view of an interactive Dash application served because we detected a non-JS user agent. Full prose docs:
- /pip/dash_nle_timeline/llms.txt β LLM-friendly documentation
- /sitemap.xml
- /robots.txt