# Editing a timeline

> The ten edit operations, what each one does to its neighbours, and which of them change a track's length.

Appending clips to a track builds a cut. Changing one is a different problem,
because every change has a question attached: what happens to everything
after it?

There are ten operations, and they are the ones a non-linear editor has.
They are functions rather than methods, because each is about two objects and
belongs to neither — which is how upstream arranges them too.

Rust:

```rust
use opentime::{RationalTime, TimeRange};
use otio_core::schema::{Base, Clip, ItemData, Track};
use otio_core::{Document, Node, NodeId};

/// One second of picture, named.
fn second(document: &mut Document, name: &str) -> NodeId {
    document.insert(Node::Clip(Clip {
        item: ItemData {
            base: Base {
                name: name.into(),
                ..Base::default()
            },
            source_range: Some(TimeRange::new(
                RationalTime::new(0.0, 24.0),
                RationalTime::new(24.0, 24.0),
            )),
            ..ItemData::new()
        },
        ..Clip::default()
    }))
}

fn show(document: &Document, track: NodeId) -> Result<(), otio_core::Error> {
    let names: Vec<&str> = document
        .children_of(track)?
        .iter()
        .map(|id| document.try_get(*id).map(Node::name).unwrap_or(""))
        .collect();
    println!("{} - {} frames", names.join(" "), document.duration(track)?.value());
    Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut document = Document::new();
    let track = document.insert(Node::Track(Track {
        kind: "Video".into(),
        ..Track::default()
    }));
    for name in ["A", "B", "C"] {
        let clip = second(&mut document, name);
        document.append_child(track, clip)?;
    }
    show(&document, track)?;

    // Insert makes room: everything from the insertion point onwards moves
    // later, and the track gets longer.
    let inserted = second(&mut document, "D");
    otio_core::edit::insert(
        &mut document,
        inserted,
        track,
        RationalTime::new(24.0, 24.0),
        false,
        None,
    )?;
    show(&document, track)?;

    // Overwrite does not: it lays an item over a span and whatever was in
    // that span gives way. The track is the same length afterwards.
    let laid = second(&mut document, "E");
    otio_core::edit::overwrite(
        &mut document,
        laid,
        track,
        TimeRange::new(RationalTime::new(48.0, 24.0), RationalTime::new(24.0, 24.0)),
        false,
        None,
    )?;
    show(&document, track)?;

    Ok(())
}
```

<sub>Source: https://github.com/alchemist-editor/otio-rust/blob/main/site/content/samples/edit-operations/rust.rs</sub>

Python:

```python
import opentimelineio as otio


def second(name):
    """One second of picture, named."""
    return otio.schema.Clip(
        name=name,
        source_range=otio.opentime.TimeRange(
            otio.opentime.RationalTime(0, 24), otio.opentime.RationalTime(24, 24)
        ),
    )


def show(track):
    names = " ".join(child.name for child in track)
    print(names, "-", track.duration().value, "frames")


track = otio.schema.Track(name="V1", kind="Video")
for name in ["A", "B", "C"]:
    track.append(second(name))
show(track)

# Insert makes room: everything from the insertion point onwards moves later,
# and the track gets longer.
otio.algorithms.insert(
    second("D"), track, otio.opentime.RationalTime(24, 24), remove_transitions=False
)
show(track)

# Overwrite does not: it lays an item over a span and whatever was in that
# span gives way. The track is the same length afterwards.
otio.algorithms.overwrite(
    second("E"),
    track,
    otio.opentime.TimeRange(
        otio.opentime.RationalTime(48, 24), otio.opentime.RationalTime(24, 24)
    ),
    remove_transitions=False,
)
show(track)
```

<sub>Source: https://github.com/alchemist-editor/otio-rust/blob/main/site/content/samples/edit-operations/python.py</sub>

TypeScript:

```typescript
import { init, Clip, edit, RationalTime, TimeRange, Track } from "@alchemist-edit/otio";

await init();

/** One second of picture, named. */
function second(name: string): Clip {
  const clip = new Clip({ name });
  clip.sourceRange = new TimeRange(
    new RationalTime(0, 24),
    new RationalTime(24, 24),
  );
  return clip;
}

function show(track: Track) {
  const names = track.children().map((child) => child.name).join(" ");
  console.log(names, "-", track.duration().value, "frames");
}

const track = new Track({ name: "V1", kind: "Video" });
for (const name of ["A", "B", "C"]) track.appendChild(second(name));
show(track);

// Insert makes room: everything from the insertion point onwards moves later,
// and the track gets longer.
edit.insert(second("D"), track, new RationalTime(24, 24), false);
show(track);

// Overwrite does not: it lays an item over a span and whatever was in that
// span gives way. The track is the same length afterwards.
edit.overwrite(
  second("E"),
  track,
  new TimeRange(new RationalTime(48, 24), new RationalTime(24, 24)),
  false,
);
show(track);
```

<sub>Source: https://github.com/alchemist-editor/otio-rust/blob/main/site/content/samples/edit-operations/typescript.ts</sub>

Go:

```go
package main

import (
	"fmt"
	"log"
	"strings"

	otio "github.com/alchemist-editor/otio-rust/sdk/go"
)

// second makes one second of picture, named.
func second(name string) otio.Clip {
	clip, err := otio.NewClip(name)
	if err != nil {
		log.Fatal(err)
	}
	if err := clip.SetSourceRange(otio.TimeRange{
		StartTime: otio.RationalTime{Value: 0, Rate: 24},
		Duration:  otio.RationalTime{Value: 24, Rate: 24},
	}); err != nil {
		log.Fatal(err)
	}
	return clip
}

func show(track otio.Track) {
	children, err := track.Children()
	if err != nil {
		log.Fatal(err)
	}
	names := make([]string, 0, len(children))
	for _, child := range children {
		name, _ := child.Name()
		names = append(names, name)
	}
	duration, _ := track.Duration()
	fmt.Printf("%s - %v frames\n", strings.Join(names, " "), duration.Value)
}

func main() {
	track, err := otio.NewTrack("V1", "Video")
	if err != nil {
		log.Fatal(err)
	}
	for _, name := range []string{"A", "B", "C"} {
		if err := track.AppendChild(second(name).Node); err != nil {
			log.Fatal(err)
		}
	}
	show(track)

	// Insert makes room: everything from the insertion point onwards moves
	// later, and the track gets longer.
	at := otio.RationalTime{Value: 24, Rate: 24}
	if err := otio.Insert(second("D").Node, track.Node, at, false, nil); err != nil {
		log.Fatal(err)
	}
	show(track)

	// Overwrite does not: it lays an item over a span and whatever was in
	// that span gives way. The track is the same length afterwards.
	over := otio.TimeRange{
		StartTime: otio.RationalTime{Value: 48, Rate: 24},
		Duration:  otio.RationalTime{Value: 24, Rate: 24},
	}
	if err := otio.Overwrite(second("E").Node, track.Node, over, false, nil); err != nil {
		log.Fatal(err)
	}
	show(track)
}
```

<sub>Source: https://github.com/alchemist-editor/otio-rust/blob/main/site/content/samples/edit-operations/go.go</sub>

Swift:

```swift
import OpenTimelineIO

/// One second of picture, named.
func second(_ name: String) throws -> Clip {
    let clip = try Clip(name: name)
    try clip.setSourceRange(
        TimeRange(
            startTime: RationalTime(value: 0, rate: 24),
            duration: RationalTime(value: 24, rate: 24)))
    return clip
}

func show(_ track: Track) throws {
    let names = try track.children().map { try $0.name() }.joined(separator: " ")
    print(names, "-", try track.duration().value, "frames")
}

let track = try Track(name: "V1", kind: "Video")
for name in ["A", "B", "C"] {
    try track.appendChild(try second(name))
}
try show(track)

// Insert makes room: everything from the insertion point onwards moves later,
// and the track gets longer.
try OTIO.insert(
    try second("D"), composition: track,
    time: RationalTime(value: 24, rate: 24), removeTransitions: false)
try show(track)

// Overwrite does not: it lays an item over a span and whatever was in that
// span gives way. The track is the same length afterwards.
try OTIO.overwrite(
    try second("E"), composition: track,
    range: TimeRange(
        startTime: RationalTime(value: 48, rate: 24),
        duration: RationalTime(value: 24, rate: 24)),
    removeTransitions: false)
try show(track)
```

<sub>Source: https://github.com/alchemist-editor/otio-rust/blob/main/site/content/samples/edit-operations/swift.swift</sub>

C++:

```cpp
#include <iostream>
#include <string>

#include <opentimelineio/otio.hpp>

/// One second of picture, named.
static otio::Clip second(const std::string &name) {
    otio::Clip clip = otio::Clip::create(name);
    clip.set_source_range(
        otio::TimeRange(otio::RationalTime(0, 24), otio::RationalTime(24, 24)));
    return clip;
}

static void show(const otio::Track &track) {
    std::string names;
    for (const otio::SerializableObject &child : track.children()) {
        if (!names.empty()) names += " ";
        names += child.name();
    }
    std::cout << names << " - " << track.duration().value << " frames\n";
}

int main() {
    otio::Track track = otio::Track::create("V1", "Video");
    for (const char *name : {"A", "B", "C"}) {
        track.append_child(second(name));
    }
    show(track);

    // Insert makes room: everything from the insertion point onwards moves
    // later, and the track gets longer.
    otio::insert(second("D"), track, otio::RationalTime(24, 24), false);
    show(track);

    // Overwrite does not: it lays an item over a span and whatever was in
    // that span gives way. The track is the same length afterwards.
    otio::overwrite(
        second("E"), track,
        otio::TimeRange(otio::RationalTime(48, 24), otio::RationalTime(24, 24)),
        false);
    show(track);

    return 0;
}
```

<sub>Source: https://github.com/alchemist-editor/otio-rust/blob/main/site/content/samples/edit-operations/cpp.cpp</sub>

Zig:

```zig
const std = @import("std");
const otio = @import("otio");

/// One second of picture, named.
fn second(document: *otio.Document, name: [:0]const u8) !otio.Clip {
    const clip = try otio.Clip.init(document, name);
    try clip.setSourceRange(.{
        .start_time = .{ .value = 0, .rate = 24 },
        .duration = .{ .value = 24, .rate = 24 },
    });
    return clip;
}

fn show(allocator: std.mem.Allocator, track: otio.Track) !void {
    // Anything the library hands back that has to be freed is copied into an
    // allocator you pass, and freed with `allocator.free`.
    const children = try track.children(allocator);
    defer allocator.free(children);

    for (children, 0..) |child, index| {
        const name = try child.name(allocator);
        defer allocator.free(name);
        if (index > 0) std.debug.print(" ", .{});
        std.debug.print("{s}", .{name});
    }

    const duration = try track.duration();
    std.debug.print(" - {d} frames\n", .{duration.value});
}

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    const document = try otio.Document.init();
    defer document.deinit();

    const track = try otio.Track.init(document, "V1", "Video");
    for ([_][:0]const u8{ "A", "B", "C" }) |name| {
        try track.appendChild((try second(document, name)).asNode());
    }
    try show(allocator, track);

    // Insert makes room: everything from the insertion point onwards moves
    // later, and the track gets longer.
    const inserted = try second(document, "D");
    try document.insert(inserted.asNode(), track.asNode(), .{ .value = 24, .rate = 24 }, false, null);
    try show(allocator, track);

    // Overwrite does not: it lays an item over a span and whatever was in
    // that span gives way. The track is the same length afterwards.
    const laid = try second(document, "E");
    try document.overwrite(laid.asNode(), track.asNode(), .{
        .start_time = .{ .value = 48, .rate = 24 },
        .duration = .{ .value = 24, .rate = 24 },
    }, false, null);
    try show(allocator, track);
}
```

<sub>Source: https://github.com/alchemist-editor/otio-rust/blob/main/site/content/samples/edit-operations/zig.zig</sub>

C:

```c
#include <stdio.h>
#include <stdlib.h>

#include "otio.h"

/* One second of picture, named. */
static OtioNode second(OtioDocument *document, const char *name) {
    OtioNode clip;
    otio_clip_new(document, name, &clip, NULL);

    OtioTimeRange span;
    span.start_time.value = 0.0;
    span.start_time.rate = 24.0;
    span.duration.value = 24.0;
    span.duration.rate = 24.0;
    otio_item_set_source_range(document, clip, span, NULL);
    return clip;
}

static void show(OtioDocument *document, OtioNode track) {
    size_t count = 0;
    otio_node_children(document, track, NULL, 0, &count, NULL);

    OtioNode *children = malloc(count * sizeof *children);
    otio_node_children(document, track, children, count, &count, NULL);

    for (size_t index = 0; index < count; index += 1) {
        OtioBuffer name;
        if (otio_node_name(document, children[index], &name, NULL) == OTIO_STATUS_OK) {
            printf("%s%s", index == 0 ? "" : " ", name.data);
            otio_buffer_free(name);
        }
    }
    free(children);

    OtioRationalTime duration;
    otio_item_duration(document, track, &duration, NULL);
    printf(" - %g frames\n", duration.value);
}

int main(void) {
    OtioDocument *document = otio_document_new();

    OtioNode track;
    otio_track_new(document, "V1", "Video", &track, NULL);

    const char *names[] = {"A", "B", "C"};
    for (size_t index = 0; index < 3; index += 1) {
        otio_composition_append_child(document, track, second(document, names[index]), NULL);
    }
    show(document, track);

    /* Insert makes room: everything from the insertion point onwards moves
     * later, and the track gets longer. */
    OtioRationalTime at = {24.0, 24.0};
    otio_edit_insert(document, second(document, "D"), track, at, false, otio_node_none(), NULL);
    show(document, track);

    /* Overwrite does not: it lays an item over a span and whatever was in
     * that span gives way. The track is the same length afterwards. */
    OtioTimeRange over;
    over.start_time.value = 48.0;
    over.start_time.rate = 24.0;
    over.duration.value = 24.0;
    over.duration.rate = 24.0;
    otio_edit_overwrite(document, second(document, "E"), track, over, false, otio_node_none(), NULL);
    show(document, track);

    otio_document_free(document);
    return 0;
}
```

<sub>Source: https://github.com/alchemist-editor/otio-rust/blob/main/site/content/samples/edit-operations/c.c</sub>

C#:

```csharp
using System;
using System.Linq;
using OpenTimelineIO;

// One second of picture, named.
static Clip Second(string name)
{
    var clip = new Clip(name);
    clip.SetSourceRange(new TimeRange(new RationalTime(0, 24), new RationalTime(24, 24)));
    return clip;
}

static void Show(Track track)
{
    var names = string.Join(" ", track.Children().Select(child => child.Name()));
    Console.WriteLine($"{names} - {track.Duration().Value} frames");
}

var track = new Track("V1", "Video");
foreach (var name in new[] { "A", "B", "C" })
{
    track.AppendChild(Second(name));
}
Show(track);

// Insert makes room: everything from the insertion point onwards moves later,
// and the track gets longer.
Otio.Insert(Second("D"), track, new RationalTime(24, 24), false, null);
Show(track);

// Overwrite does not: it lays an item over a span and whatever was in that
// span gives way. The track is the same length afterwards.
Otio.Overwrite(
    Second("E"),
    track,
    new TimeRange(new RationalTime(48, 24), new RationalTime(24, 24)),
    false,
    null);
Show(track);
```

<sub>Source: https://github.com/alchemist-editor/otio-rust/blob/main/site/content/samples/edit-operations/csharp.cs</sub>

Objective-C:

```objectivec
#import <Foundation/Foundation.h>

#import <OpenTimelineIO/OpenTimelineIO.h>

/// One second of picture, named.
static OTIOClip *Second(NSString *name) {
    NSError *error = nil;
    OTIOClip *clip = [OTIOClip clipWithName:name error:&error];
    [clip setSourceRange:OTIOTimeRangeMake(OTIORationalTimeMake(0, 24),
                                           OTIORationalTimeMake(24, 24))
                   error:&error];
    return clip;
}

static void Show(OTIOTrack *track) {
    NSError *error = nil;
    NSMutableArray<NSString *> *names = [NSMutableArray array];
    for (OTIOSerializableObject *child in [track children:&error]) {
        [names addObject:[child name:&error]];
    }

    OTIORationalTime duration;
    [track getDuration:&duration error:&error];
    NSLog(@"%@ - %g frames", [names componentsJoinedByString:@" "], duration.value);
}

int main(void) {
    @autoreleasepool {
        NSError *error = nil;

        OTIOTrack *track = [OTIOTrack trackWithName:@"V1" kind:@"Video" error:&error];
        for (NSString *name in @[ @"A", @"B", @"C" ]) {
            [track appendChild:Second(name) error:&error];
        }
        Show(track);

        // Insert makes room: everything from the insertion point onwards
        // moves later, and the track gets longer.
        OTIOInsert(Second(@"D"), track, OTIORationalTimeMake(24, 24), NO, nil, &error);
        Show(track);

        // Overwrite does not: it lays an item over a span and whatever was
        // in that span gives way. The track is the same length afterwards.
        OTIOOverwrite(Second(@"E"), track,
                      OTIOTimeRangeMake(OTIORationalTimeMake(48, 24),
                                        OTIORationalTimeMake(24, 24)),
                      NO, nil, &error);
        Show(track);
    }
    return 0;
}
```

<sub>Source: https://github.com/alchemist-editor/otio-rust/blob/main/site/content/samples/edit-operations/objectivec.m</sub>

## The ten

| Operation | What it does |
| --- | --- |
| `insert` | Puts an item at an instant, pushing what follows later. |
| `overwrite` | Lays an item over a span, replacing what was there. |
| `trim` | Moves an item's in and out points without moving its neighbours. |
| `ripple` | Moves an item's in and out points, sliding everything after it. |
| `roll` | Moves the cut between an item and its neighbour. |
| `slip` | Moves the media inside an item without moving the item. |
| `slide` | Moves an item along its track, taking the time from its neighbours. |
| `slice` | Cuts whatever sits at an instant into two. |
| `fill` | Drops an item into a gap, fitted as the reference point says. |
| `remove` | Takes whatever sits at an instant out. |

## The question each one answers

The useful way to hold these is by what they do to the track's length and to
the neighbours.

**Length changes.** `insert` makes the track longer by the length of what
went in. `remove` without a fill makes it shorter, because what follows moves
up; `remove` with a fill leaves a gap and the length alone. `ripple` changes
it by however much the in or out point moved.

**Length does not change.** `overwrite` replaces rather than displaces.
`trim`, `roll`, `slip` and `slide` all move boundaries around within the
track without altering how much track there is. `slice` puts a cut in and
changes nothing else — two items where there was one, occupying the same
span.

**Neighbours move.** `insert`, `ripple` and `remove` without a fill. Those
are the three that shift material you did not name.

**Neighbours do not move.** `overwrite`, `trim`, `slip`, `slice` and `fill`.
`roll` and `slide` are the interesting pair: both change a neighbour, but
only by moving the boundary they share, so nothing further along the track
notices.

The sample above is `insert` and `overwrite` side by side, because that is
the distinction people reach for first and the one that decides whether a cut
gets longer.

## Filling what an edit opens

Several of these can open a hole — `overwrite` past the end of a track,
`remove` with a fill, `insert` at a point past the end.

Each takes a **fill template**: the item to put in that hole. Pass nothing
and it is an ordinary `Gap`, which is transparent, so on a multi-track
timeline whatever is underneath shows through. When you want something else —
solid black, a slate, colour bars — pass a clip with a `GeneratorReference`
as the template and that is what fills it instead.

## Slip and slide

These two are named after the editing desk and the names are worth learning
the way round.

**`slip`** keeps the item where it is on the track and moves the media inside
it: same four seconds of the cut, a different four seconds of the shot. The
track does not change at all, and neither does any neighbour.

**`slide`** keeps the media and moves the item along the track: the same four
seconds of the shot, arriving earlier or later. The time has to come from
somewhere, so the neighbours on either side absorb it — the one before grows
or shrinks, and the one after does the opposite.

## Where they are, and where they are not

All ten are in the core, in the C ABI, in every SDK generated from it, and in
Python as `opentimelineio.algorithms.overwrite`, `insert` and the rest, with a
`ReferencePoint` enum for `fill`'s four-point edits.

Upstream's own Python package does not bind them; only its C++ has them. The
Python functions here follow that C++ `editAlgorithm.h` in their names,
parameters and defaults, and raise what its error handler raises. An object an
edit takes out of a track stays usable while Python holds it, with no parent,
as any removed child does.

## What the copied piece holds

`slice`, `insert` and `overwrite` copy the piece of an item a split leaves
over, and `fill` with `ReferencePoint.Sequence` copies the clip it drops in.
The copy is made as upstream makes it, by the equivalent of writing the item
out and reading it back, so it shares nothing with the original: an object
the item holds in two places — one object under two metadata keys, one effect
listed twice, one media reference under two keys — becomes two separate
objects in the copy. The item left in place keeps what it held.

## An item that holds itself

Metadata can hold whole objects, including the item it belongs to:
`clip.metadata["self"] = clip`. `slice`, `insert` and `overwrite` copy the
piece of an item a split leaves over, and `fill` copies the clip it drops in;
here that copy keeps the cycle, so the leftover piece holds itself just as
the original does, and the edit goes through like any other.

That is a deliberate difference from upstream. Upstream copies by writing the
item out and reading it back, which cannot carry a cycle, so the same edits
fail there with a cycle error — and `slice`, `insert` and `overwrite` fail
only after changing the track, leaving the clip cut short and the rest of it
lost. The cycle still cannot be saved: writing it as `.otio` is refused, here
as upstream.

---

Page: https://otio-rust-docs.vercel.app/docs/guides/editing/
