AcademySoftwareFoundation / OpenTimelineIO

Open Source API and interchange format for editorial timeline information.
http://opentimeline.io
Apache License 2.0
1.47k stars 293 forks source link

Common Edit Command Library #711

Open mccartnm opened 4 years ago

mccartnm commented 4 years ago

Overview

The industry is full of timeline management software but nearly all of them include a fundamental set of tools for manipulation. The most well known being:

OpenTimelineIO, while not seeking to become a fully realized NLE, could benefit from having a simple toolkit to help automate building/modifying timelines based on time rather than index alone.

Proposal

For editing commands, we should strive to do all validation and assert that everything works before committing anything on the timeline. I'm proposing the EditEvent which might something like:

class EditEvent {
public:
    EditEventKind kind; // e.g. Insert, Append, Remove, Modify
    Retainer<Composition> parent;
    Retainer<Composable> composable;

    // ... Additional fields to execute the above as required

    bool run(ErrorStatus* error_status);
    bool revert();
};

An event is atomic in nature and does "one thing" to a Composable. Each edit maneuver (e.g. otio.edit.place(...)) would generate a vector of these events that can be played forward and, possibly, backward. The result of them collectively is the commands result.

for (EditEvent &event: events) {
    event.run(error_status);
    if (*error_status) {
        for (auto it = completed_events.rbegin(); /*...*/)
            (*it).revert();
        break;
    }
    completed_events.push_back(event);
}

Alternatively, we could build a more robust transaction system since all items can be cloned but there are memory considerations for that and beyond the scope of this initial proposal.

API

The API might look like:

otio.edit.place(item, into=track, at=rat_time, placement_kind=otio.edit.PlacementKind)
otio.edit.trim(item, edge=otio.edit.ItemEdge)
etc.

Additional commands can be added as the editing foundation is built.

Pros:

Cons:

Questions/Considerations:

ssteinbach commented 4 years ago

Couple of suggestions:

mccartnm commented 4 years ago

Thanks for the feedback! I agree that the transaction system is a whole extra dragon to slay. I'll try to proceed with the edit layer sans-transactions and keep lookout for a nice way to transition into a layer atop/aside it.

As for the initial edit commands, I've generated some images and assessed what their possible API call might look like. It's a rather long message so I've just linked the images. For documentation I would set them all together.

Overwrite

mccartnm commented 4 years ago

One additional command might be a general swap. This can effectively be a "delete" and will replace an item with a gap or some fill template.

ssteinbach commented 4 years ago

I think there is enough here, and assuming you're interested in doing the work, that a good way to go would be to put this into a .md file in the documentation folder and make a PR to start iterating on it. It will be easier to make notes on the md using github's code review system, given how long this is. We took a similar approach with the C++ core, first making a PR with documentation and design and then following up with implementation.

The images (which are great!) that you made can also be added inline in that case.

The document ^^ can also serve to seed the future document which is more user facing describing how to use these functions to execute common editing operations on OTIO files.

Some notes:

jminor commented 4 years ago

This is fantastic, and fills a gap (pun intended) in OTIO that we have talked about for ages.

A couple of notes:

When I was writing the flatten algorithm, I wished I had these. And in fact, there are some flaws in the flatten algorithm that could be addressed once we have these. That's more evidence that maybe flatten should be in the same module namespace as these.

mccartnm commented 4 years ago

Thanks again for the notes! I'm excited to take on the challenge. I'll work on a design doc in a PR next. The structure is definitely something I want to get right. The otio.algorithms module could be a great spot to expose the library, as long as we make it clear that it hosts the edit toolkit.

@ssteinbach

All of these commands start with a single item and the edit algorithm decides what other elements need augmenting/removal. Because of that, the second way speaks to me more but I'm open to either path. I'll whip up some additional diagrams for this in the design doc to gain consensus.

@jminor

** - Now that I read it again - one major exception is slice because that have to be either the child coordinates or parent coordinates explicitly. I'm in favor of argument modifiers to do the math for the end-user automatically.

meshula commented 4 years ago

A few more notes on the notion of a transaction system, and a set of editing operations, and their relationships.

I would like to be able to use such a system of transacted edits as follows:

In order to filter the edit, the list of added objects would have to reference the parent objects. For example, if I propose a split, and wish to accept every split except those referencing audio media, I'd want to filter the audio related elements from the proposed deletes, and the corresponding new elements from the proposed adds.

I think these separable operations are implicit in the proposal, but I'd like to call them out explicitly, because another possibility for implementation is that an operation such as split occurs invisibly, so in order to learn what's changed, one would need to do some kind of memoization before and after and attempt a diff.

mccartnm commented 4 years ago

@meshula This is the kind of use case I hope to cover with the EditEvent system. Essentially this might boil down to two exposed "layers":

  1. Common Edit Commands - Simple tasks with a clean OOP feel "e.g. Slice this item at time X in it's parent time coordinates" - the commands above represent this layer.
  2. Raw Edit Commands - the underlying methods that do the work of the common commands. These allow the user to filter/modify the event list to their liking, or generate their own event list from scratch to make additional procedures.

* It's worth pointing out that all commands from both layers would return a vector of events. The CEC just returns them already processed, so they can be introspected afterwards. The REC would have the ability to generate event lists for later processing. Which may lead to otio.algorithms.process(events) or class AbstractEdit(...): of some sort.

I've got an initial sketch of the design doc here: https://github.com/mccartnm/OpenTimelineIO/blob/openedit_design/docs/design/editorial_design.md

I'm just waiting on the CLA response to start the PR but in the mean time I'll add some additional details about this.

darbyjohnston commented 1 year ago

I recently came across this and noticed there hasn't been any activity for awhile? It looks really interesting so as an experiment I tried implementing a couple of the edit commands to get a feel for the work involved: https://github.com/AcademySoftwareFoundation/OpenTimelineIO/pull/1518

Great images BTW, they were very helpful for visualizing the operations. The code was fairly tricky to get working correctly so I wrote the tests first and then iterated until everything passed. I haven't added error handling and there are probably corner cases I missed.

One thing that occurred to me while working on this is that handling transitions or any future subclass of Composable that returns true for overlapping(), is going to make these much more complicated. Especially trying to satisfy the constraints mentioned in the documentation: https://opentimelineio.readthedocs.io/en/latest/tutorials/otio-timeline-structure.html#transitions