The algorithms package provides functions for manipulating timelines, tracks, and stacks. These algorithms are essential for common editorial operations like trimming, flattening, and filtering.
import "github.com/mrjoshuak/gotio/algorithms"Trims a track to a specific time range, keeping only the portions of children that fall within the range.
func TrackTrimmedToRange(track *opentimelineio.Track, trimRange opentime.TimeRange) (*opentimelineio.Track, error)Use Cases:
- Extracting a segment of a timeline
- Creating a subclip
- Removing unwanted content at the beginning or end
Example:
// Original track: 10 seconds
originalTrack := timeline.VideoTracks()[0]
// Trim to frames 48-144 (2-6 seconds at 24fps)
trimRange := opentime.NewTimeRange(
opentime.NewRationalTime(48, 24),
opentime.NewRationalTime(96, 24),
)
trimmedTrack, err := algorithms.TrackTrimmedToRange(originalTrack, trimRange)
if err != nil {
log.Fatal(err)
}
// trimmedTrack now contains only 4 seconds of content
dur, _ := trimmedTrack.Duration()
fmt.Printf("Trimmed duration: %.2fs\n", dur.ToSeconds()) // 4.00sBehavior:
- Clips entirely within the range are kept unchanged
- Clips partially within the range are trimmed
- Clips entirely outside the range are removed
- Gaps are adjusted accordingly
- Transitions at the edges may be removed
Original: [Clip A: 0-72] [Clip B: 72-144] [Clip C: 144-216]
Trim 48-168:
Result: [Clip A: 24-72] [Clip B: 72-144] [Clip C: 144-168]
(trimmed) (unchanged) (trimmed)
Expands transitions to include the media they "borrow" from adjacent clips.
func TrackWithExpandedTransitions(track *opentimelineio.Track) (*opentimelineio.Track, error)Use Cases:
- Analyzing actual media usage including transition handles
- Preparing for format conversion
- Quality checking available handles
Example:
// Track with: [Clip A] [Dissolve 12f] [Clip B]
expandedTrack, err := algorithms.TrackWithExpandedTransitions(originalTrack)
// Now clip source ranges include the transition framesFlattens a multi-layer stack into a single track by resolving what's visible at each point in time.
func FlattenStack(stack *opentimelineio.Stack) (*opentimelineio.Track, error)Use Cases:
- Converting multi-track timelines for formats that only support single tracks
- Analyzing what's actually visible in the final output
- Simplifying complex compositions
Example:
// Stack with multiple tracks
stack := timeline.Tracks()
// Flatten to single track
flatTrack, err := algorithms.FlattenStack(stack)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Flattened to %d items\n", len(flatTrack.Children()))Compositing Rules:
- Higher tracks (later in the stack) obscure lower tracks
- Gaps are transparent (lower tracks show through)
- The result contains the topmost visible content at each time
Track V2: [ ] [Clip B] [ ] <- top
Track V1: [Clip A ] [Clip C] <- bottom
Flattened: [A ] [B ] [C ]
(A shows where V2 has gap)
Flattens multiple tracks into a single track.
func FlattenTracks(tracks []*opentimelineio.Track) (*opentimelineio.Track, error)Example:
videoTracks := timeline.VideoTracks()
flatTrack, err := algorithms.FlattenTracks(videoTracks)Returns the topmost visible clip at a specific time.
func TopClipAtTime(stack *opentimelineio.Stack, t opentime.RationalTime) *opentimelineio.ClipUse Cases:
- Finding what's playing at a specific frame
- Building frame-accurate reports
- Implementing playback simulation
Example:
// What clip is visible at frame 100?
t := opentime.NewRationalTime(100, 24)
clip := algorithms.TopClipAtTime(timeline.Tracks(), t)
if clip != nil {
fmt.Printf("At frame 100: %s\n", clip.Name())
} else {
fmt.Println("At frame 100: (gap)")
}Behavior:
- Searches from top track to bottom
- Returns the first clip that covers the given time
- Returns nil if the time falls in a gap on all tracks
Trims an entire timeline to a specific range.
func TimelineTrimmedToRange(timeline *opentimelineio.Timeline, trimRange opentime.TimeRange) (*opentimelineio.Timeline, error)Example:
// Extract the first minute
trimRange := opentime.NewTimeRange(
opentime.NewRationalTime(0, 24),
opentime.NewRationalTime(1440, 24), // 60 seconds at 24fps
)
trimmed, err := algorithms.TimelineTrimmedToRange(timeline, trimRange)
if err != nil {
log.Fatal(err)
}
dur, _ := trimmed.Duration()
fmt.Printf("Trimmed timeline: %.2fs\n", dur.ToSeconds()) // 60.00sGet only video or audio tracks from a timeline.
func TimelineVideoTracks(timeline *opentimelineio.Timeline) []*opentimelineio.Track
func TimelineAudioTracks(timeline *opentimelineio.Timeline) []*opentimelineio.TrackExample:
videoTracks := algorithms.TimelineVideoTracks(timeline)
audioTracks := algorithms.TimelineAudioTracks(timeline)
fmt.Printf("Video tracks: %d\n", len(videoTracks))
fmt.Printf("Audio tracks: %d\n", len(audioTracks))Creates a new timeline with video tracks flattened to a single track.
func FlattenTimelineVideoTracks(timeline *opentimelineio.Timeline) (*opentimelineio.Timeline, error)Example:
// Original timeline has 5 video tracks
original := loadTimeline("complex_edit.otio")
// Flatten to 1 video track
flattened, err := algorithms.FlattenTimelineVideoTracks(original)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Flattened video tracks: %d\n", len(flattened.VideoTracks())) // 1The filtering functions allow you to traverse and filter compositions based on custom criteria.
A function type for filtering:
type FilterFunc func(obj opentimelineio.SerializableObject) boolReturn true to keep the object, false to remove it.
Filters a composition tree, keeping only objects that match the filter.
func FilteredComposition(
root opentimelineio.SerializableObject,
filter FilterFunc,
typesToPrune []reflect.Type,
) opentimelineio.SerializableObjectParameters:
root- The root object to filterfilter- Function that returns true for objects to keeptypesToPrune- Types to completely remove (including children)
Example: Keep Only Clips
import "reflect"
// Filter to keep only clips
clipFilter := func(obj opentimelineio.SerializableObject) bool {
_, isClip := obj.(*opentimelineio.Clip)
return isClip
}
filtered := algorithms.FilteredComposition(timeline, clipFilter, nil)Example: Remove Gaps
// Remove all gaps
noGapsFilter := func(obj opentimelineio.SerializableObject) bool {
_, isGap := obj.(*opentimelineio.Gap)
return !isGap // Keep everything except gaps
}
filtered := algorithms.FilteredComposition(timeline, noGapsFilter, nil)Keeps everything (useful as a default):
filter := algorithms.KeepFilter()Removes everything:
filter := algorithms.PruneFilter()Keeps objects of specific types:
import "reflect"
// Keep only Clips and Gaps
filter := algorithms.TypeFilter(
reflect.TypeOf(&opentimelineio.Clip{}),
reflect.TypeOf(&opentimelineio.Gap{}),
)
filtered := algorithms.FilteredComposition(timeline, filter, nil)Keeps objects matching a name pattern:
// Keep clips with names starting with "VFX_"
filter := algorithms.NameFilter("VFX_*")
filtered := algorithms.FilteredComposition(timeline, filter, nil)Like FilteredComposition, but the filter receives context about the object's position:
type FilterContext struct {
Index int // Position in parent
Parent opentimelineio.SerializableObject // Parent composition
Neighbors []opentimelineio.SerializableObject // Adjacent items
}
type ContextFilterFunc func(obj opentimelineio.SerializableObject, ctx FilterContext) boolExample: Keep Every Other Clip
everyOther := func(obj opentimelineio.SerializableObject, ctx algorithms.FilterContext) bool {
if _, isClip := obj.(*opentimelineio.Clip); isClip {
return ctx.Index%2 == 0 // Keep clips at even indices
}
return true // Keep non-clips
}
filtered := algorithms.FilteredWithSequenceContext(timeline, everyOther, nil)func extractSegment(timeline *opentimelineio.Timeline, startSec, endSec float64) (*opentimelineio.Timeline, error) {
rate := 24.0 // Assume 24fps
trimRange := opentime.NewTimeRange(
opentime.NewRationalTime(startSec*rate, rate),
opentime.NewRationalTime((endSec-startSec)*rate, rate),
)
return algorithms.TimelineTrimmedToRange(timeline, trimRange)
}
// Extract seconds 10-30
segment, err := extractSegment(timeline, 10, 30)func clipsWithMarkers(timeline *opentimelineio.Timeline) []*opentimelineio.Clip {
var result []*opentimelineio.Clip
for _, clip := range timeline.FindClips(nil, false) {
if len(clip.Markers()) > 0 {
result = append(result, clip)
}
}
return result
}type EditEntry struct {
ClipName string
MediaFile string
InTC string
OutTC string
RecordIn string
RecordOut string
}
func buildEditList(timeline *opentimelineio.Timeline) []EditEntry {
var entries []EditEntry
for _, track := range timeline.VideoTracks() {
recordTime := opentime.NewRationalTime(0, 24)
for _, child := range track.Children() {
clip, ok := child.(*opentimelineio.Clip)
if !ok {
continue
}
sr := clip.SourceRange()
dur, _ := clip.Duration()
var mediaURL string
if ref, ok := clip.MediaReference().(*opentimelineio.ExternalReference); ok {
mediaURL = ref.TargetURL()
}
entry := EditEntry{
ClipName: clip.Name(),
MediaFile: mediaURL,
InTC: formatTC(sr.StartTime()),
OutTC: formatTC(sr.EndTimeExclusive()),
RecordIn: formatTC(recordTime),
RecordOut: formatTC(recordTime.Add(dur)),
}
entries = append(entries, entry)
recordTime = recordTime.Add(dur)
}
}
return entries
}// Replace all media references with new paths
func conformTimeline(timeline *opentimelineio.Timeline, pathMap map[string]string) {
for _, clip := range timeline.FindClips(nil, false) {
ref, ok := clip.MediaReference().(*opentimelineio.ExternalReference)
if !ok {
continue
}
oldPath := ref.TargetURL()
if newPath, found := pathMap[oldPath]; found {
ref.SetTargetURL(newPath)
}
}
}
// Usage
pathMap := map[string]string{
"file:///old/path/video.mov": "file:///new/path/video.mov",
}
conformTimeline(timeline, pathMap)- Flattening is O(n*m) where n = tracks and m = items per track
- TopClipAtTime is O(n) where n = total items in stack
- Filtering creates copies - original objects are not modified
- Large timelines - Consider processing tracks in parallel
// Parallel track processing
var wg sync.WaitGroup
results := make([]*opentimelineio.Track, len(tracks))
for i, track := range tracks {
wg.Add(1)
go func(idx int, t *opentimelineio.Track) {
defer wg.Done()
processed, _ := processTrack(t)
results[idx] = processed
}(i, track)
}
wg.Wait()The edit algorithm suite provides operations for interactive timeline editing. These operations modify compositions in place and handle the complexities of splitting clips, managing transitions, and maintaining timeline integrity.
// ReferencePoint determines how fill operations place clips
type ReferencePoint int
const (
ReferencePointSource ReferencePoint = iota // Use clip's natural duration
ReferencePointSequence // Trim clip to fit gap exactly
ReferencePointFit // Add time warp to stretch/compress
)
// EditError represents an error from edit operations
type EditError struct {
Operation string
Message string
Time *opentime.RationalTime
Item opentimelineio.Composable
}Replaces content in a time range with a new item.
func Overwrite(
item opentimelineio.Item,
composition opentimelineio.Composition,
timeRange opentime.TimeRange,
opts ...OverwriteOption,
) error
// Options
func WithRemoveTransitions(remove bool) OverwriteOption
func WithFillTemplate(template opentimelineio.Item) OverwriteOptionBehavior:
- If range starts after composition end: creates gap, appends item
- If range ends before composition start: inserts item at beginning
- Otherwise: splits items at boundaries, removes items in range, inserts new item
Example:
// Create a clip to insert
clip := opentimelineio.NewClip("new_clip", mediaRef, nil, nil, nil, nil, "", nil)
// Overwrite frames 100-200
overwriteRange := opentime.NewTimeRange(
opentime.NewRationalTime(100, 24),
opentime.NewRationalTime(100, 24),
)
err := algorithms.Overwrite(clip, track, overwriteRange)Inserts an item at a specific time, growing the composition.
func Insert(
item opentimelineio.Item,
composition opentimelineio.Composition,
time opentime.RationalTime,
opts ...InsertOption,
) error
// Options
func WithInsertRemoveTransitions(remove bool) InsertOption
func WithInsertFillTemplate(template opentimelineio.Item) InsertOptionBehavior:
- If time >= composition end: appends (with gap fill if needed)
- If time <= 0: prepends
- Otherwise: splits item at time, inserts between halves
Example:
// Insert a clip at frame 50
insertTime := opentime.NewRationalTime(50, 24)
err := algorithms.Insert(clip, track, insertTime)Cuts an item at a specific time, creating two items.
func Slice(
composition opentimelineio.Composition,
time opentime.RationalTime,
opts ...SliceOption,
) error
// Options
func WithSliceRemoveTransitions(remove bool) SliceOptionBehavior:
- If time is at item boundary: no-op
- If time is within an item: splits into two items with adjusted source ranges
- Does not change composition duration
Example:
// Cut the clip at frame 100
sliceTime := opentime.NewRationalTime(100, 24)
err := algorithms.Slice(track, sliceTime)
// Track now has two clips where there was oneAdjusts an item's in/out points without affecting composition duration. Adjacent items are adjusted to compensate.
func Trim(
item opentimelineio.Item,
composition opentimelineio.Composition,
deltaIn opentime.RationalTime,
deltaOut opentime.RationalTime,
opts ...TrimOption,
) error
// Options
func WithTrimFillTemplate(template opentimelineio.Item) TrimOptionBehavior:
- deltaIn > 0: moves source start forward, previous item expands
- deltaIn < 0: moves source start backward, previous item contracts
- deltaOut > 0: extends duration, next item contracts
- deltaOut < 0: reduces duration, next item expands
Example:
// Trim 10 frames off the head, extend 5 frames at the tail
err := algorithms.Trim(
clip, track,
opentime.NewRationalTime(10, 24), // trim head
opentime.NewRationalTime(5, 24), // extend tail
)Moves an item's playhead through source media without changing position or duration.
func Slip(item opentimelineio.Item, delta opentime.RationalTime) errorBehavior:
- Adds delta to source_range.start_time
- Clamps to available_range if present
- Duration and position in composition unchanged
Example:
// Slip the clip 24 frames forward in source media
err := algorithms.Slip(clip, opentime.NewRationalTime(24, 24))
// Clip shows different content but stays in same positionMoves an item's position by adjusting the previous item's duration.
func Slide(
item opentimelineio.Item,
composition opentimelineio.Composition,
delta opentime.RationalTime,
) errorBehavior:
- Positive delta: expands previous item, moves this item right
- Negative delta: contracts previous item, moves this item left
- Clamps to prevent negative durations
- First item cannot be slid
Example:
// Slide clip 12 frames to the right
err := algorithms.Slide(clip, track, opentime.NewRationalTime(12, 24))Adjusts an item's source range with clamping to available media. Unlike Trim, Ripple does not affect adjacent items.
func Ripple(
item opentimelineio.Item,
deltaIn opentime.RationalTime,
deltaOut opentime.RationalTime,
) errorBehavior:
- deltaIn adjusts source start with clamping
- deltaOut adjusts source end with clamping
- Available range bounds are checked
- No effect on adjacent items (composition duration changes)
Example:
// Extend the clip's in-point by 10 frames
err := algorithms.Ripple(
clip,
opentime.NewRationalTime(-10, 24), // extend head
opentime.NewRationalTime(0, 24), // no tail change
)Moves an edit point, adjusting both adjacent items. Total duration is preserved.
func Roll(
item opentimelineio.Item,
composition opentimelineio.Composition,
deltaIn opentime.RationalTime,
deltaOut opentime.RationalTime,
) errorBehavior:
- deltaIn adjusts current item's head and previous item's tail
- deltaOut adjusts current item's tail and next item's head
- Edit point moves, but total duration is preserved
Example:
// Roll the in-point 10 frames right (current clip shrinks, previous expands)
err := algorithms.Roll(
clip, track,
opentime.NewRationalTime(10, 24), // roll in-point right
opentime.NewRationalTime(0, 24), // no out-point change
)Places an item into a gap using 3/4-point edit logic.
func Fill(
item opentimelineio.Item,
composition opentimelineio.Composition,
trackTime opentime.RationalTime,
referencePoint ReferencePoint,
) errorBehavior by ReferencePoint:
- Source: Use clip's natural duration, perform overwrite from trackTime
- Sequence: Trim clip to fit gap exactly
- Fit: Add LinearTimeWarp effect to stretch/compress clip to gap
Example:
// Fill a gap at frame 100, trimming clip to fit
err := algorithms.Fill(
clip, track,
opentime.NewRationalTime(100, 24),
algorithms.ReferencePointSequence,
)Removes an item at a specific time and optionally fills the space.
func Remove(
composition opentimelineio.Composition,
time opentime.RationalTime,
opts ...RemoveOption,
) error
// Options
func WithFill(fill bool) RemoveOption
func WithRemoveFillTemplate(template opentimelineio.Item) RemoveOptionBehavior:
- If fill=true (default): inserts a gap in place of removed item
- If fill=false: adjacent items become adjacent (composition shrinks)
Example:
// Remove item at frame 100, leaving a gap
err := algorithms.Remove(track, opentime.NewRationalTime(100, 24))
// Remove item at frame 100, collapsing the timeline
err := algorithms.Remove(
track,
opentime.NewRationalTime(100, 24),
algorithms.WithFill(false),
)Removes all items within a time range.
func RemoveRange(
composition opentimelineio.Composition,
timeRange opentime.TimeRange,
opts ...RemoveOption,
) errorBehavior:
- Items completely within range are removed
- Items partially within range are trimmed
- If fill=true: a single gap fills the removed space
- If fill=false: composition shrinks
Example:
// Remove everything between frames 100-200
removeRange := opentime.NewTimeRange(
opentime.NewRationalTime(100, 24),
opentime.NewRationalTime(100, 24),
)
err := algorithms.RemoveRange(track, removeRange, algorithms.WithFill(false))| Operation | Affects Duration | Affects Adjacent | Use Case |
|---|---|---|---|
| Overwrite | No* | Yes | Replace content in timeline |
| Insert | Yes | Yes | Add new content, push existing right |
| Slice | No | No | Cut clip into two pieces |
| Trim | No | Yes | Adjust in/out, adjacent compensates |
| Slip | No | No | Change source content, keep position |
| Slide | No | Yes | Move clip by adjusting previous |
| Ripple | Yes | No | Adjust source range, timeline shrinks/grows |
| Roll | No | Yes | Move edit point between two clips |
| Fill | No* | Yes | Place clip into a gap |
| Remove | Varies | Yes | Delete content from timeline |
*Unless overwriting/filling beyond current duration