Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OTIO Editing API Design #719

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

mccartnm
Copy link

This is the relevant PR for Issue 771

This lays out the initial design specs for the edit platform within OTIO.

@mccartnm mccartnm changed the title Openedit Design OTIO Editing API Design May 27, 2020
@codecov-commenter
Copy link

codecov-commenter commented May 27, 2020

Codecov Report

Merging #719 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master     #719   +/-   ##
=======================================
  Coverage   81.88%   81.88%           
=======================================
  Files          72       72           
  Lines        2755     2755           
=======================================
  Hits         2256     2256           
  Misses        499      499           
Flag Coverage Δ
#py27 81.86% <ø> (ø)
#py36 81.86% <ø> (ø)
#py37 81.86% <ø> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update eaccea3...9b59fe9. Read the comment docs.

## Dependencies / Namespaces
The `openedit` library will have to communicate with both `opentime` and `openedit`.

This will warrant either a new namespace `namespace openedit { ... }` or using `opentimelineio` again. The latter makes some of the bellow comments less challenging but again, might pollute an otherwise clean namespace.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo 'below'

## Overwrite

![Overwrite](../_static/edit/01_overwrite.png)
- Anything overlapping is destroyed on contact. This may split an item.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to specify things more pedantically using the nomenclature we've established in the recent Allen's Interval Algebra commit. eg.

for all items i
   if e contains i, or i begins e, or e ends i : delete i
   if i contains e : split i at e.start, yielding {i0, i1}. Split i1 at en.end, yielding {i2, i3}, delete i, i1 and i2, return i0 and i3.
  if i overlaps e...
  else skip 

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've specified the rest of the API more formally. I felt, given our new algebraic nomenclature, overlapping wasn't specific enough, also destroyed on contact was open to a bit of interpretation..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably also worth clarifying intent for the audiences of this. I'm expecting it will be consumed by:

  1. The implementer(s) of the API
  2. Future users of the API - TDs and DCC developers
  3. Future maintainers (not necessarily the people from No. 1)

I think the formal definitions will be key for the implementers and useful reference for maintainers. The human-friendly descriptions should be targeted at being approachable for API consumers and provide a framing of the mental model for future maintainers.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - I've got ASCII art and more explicit instructions per-command on the way (taking in the additional notes where possible). I'll add the audience information to the top along with the replaced "elevator pitch" that @ssteinbach recommended which sums it up beautifully 👍

Copy link
Collaborator

@meshula meshula left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the looks of this proposal ~ I have a few initial thoughts.

## Overwrite

![Overwrite](../_static/edit/01_overwrite.png)
- Anything overlapping is destroyed on contact. This may split an item.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've specified the rest of the API more formally. I felt, given our new algebraic nomenclature, overlapping wasn't specific enough, also destroyed on contact was open to a bit of interpretation..

docs/design/editorial_design.md Show resolved Hide resolved
docs/design/editorial_design.md Show resolved Hide resolved
Copy link
Collaborator

@reinecke reinecke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great start!
Once we feel like this is pretty fleshed out I think it could be helpful to come up with some real world example cases and see how they might work using this API. Would these operations be targeting enabling lightweight NLE implementations with OTIO as a backing data model?
Glad to see someone interested in taking this on!

`- ...
```

The benefit for the extra library is the edit suite will inevitably grow and will begin crowding the core model, which ultimately should be able to run without it.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should assert here that opentime and opentimelineio encapsulate the stored data model and openedit implements operations on that data model but should not define any new schema objects.
This will hopefully create clarity around how their responsibilities are separate.

## Overwrite

![Overwrite](../_static/edit/01_overwrite.png)
- Anything overlapping is destroyed on contact. This may split an item.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably also worth clarifying intent for the audiences of this. I'm expecting it will be consumed by:

  1. The implementer(s) of the API
  2. Future users of the API - TDs and DCC developers
  3. Future maintainers (not necessarily the people from No. 1)

I think the formal definitions will be key for the implementers and useful reference for maintainers. The human-friendly descriptions should be targeted at being approachable for API consumers and provide a framing of the mental model for future maintainers.

to fill in the event that this overwrite extends the end of the
composition's limit
"""
otio.algorithms.overwrite(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may help code clarity if the function names are a bit more explicit like overwrite_in_track.

otio.algorithms.insert(
item: Item,
track: Track,
composition_time: RationalTime,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be track_time to match overwrite?

otio.algorithms.trim(
item: Item,
delta_in: RationalTime = None, #< Duration of change
delta_out: RationalTime = None, #< Duration of change
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be useful to also document error case behavior (e.x. delta_in + delta_out > clip duration)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - Error cases are something I definitely need to focus on more. I'll try to get more of the edge cases documented for my next push

otio.algorithms.fill(
item: Item,
track: Track,
replace: Item = None, # Alternatively, we could have a parent-coord
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like this should maybe be a TimeRange within the track. If there is just a single item to operate on the caller can use range_in_parent to get the value they want to use.

replace: Item = None, # Alternatively, we could have a parent-coord
# RationalTime to find the item for us
reference_point: otio.algorithms.ReferencePoint.Source
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one's a bit tricky!
My instinct is that this may be trying to do too much and could yield unexpected results for users. The reference_point being set to Source is effectively the same as just using overwrite, right?
Also there is some clarification needed about whether it's most important to keep the in point or out point of the inserted item.
I wonder if maybe this operation can be broken down into a few more explicit variants instead (e.x. overwrite at time range trimming item, overwrite at time range retiming item, etc.)

Copy link
Collaborator

@ssteinbach ssteinbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great proposal to start! I had some questions about where you were suggesting putting the code namespace wise, and a bunch of discussion following along with some of the points.

I'd also like to see the edit event api split out into its own PR.

Comment on lines 320 to 338
# Expanded Implementation

## Proposal
For editing commands, we should strive to do all validation and assert that everything "works" before committing anything on the timeline. A simple atomic command structure will provide that level of sophistication.

```cpp
class EditEvent {
public:
EditEventKind kind; // e.g. Insert, Append, Remove, Modify
Retainer<Composition> parent;
Retainer<Item> 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 an `Item`. Each edit maneuver (e.g. `otio.algorithms.overwrite(...)`) would generate a vector of these events that can be played forward and, possibly, backward. The result of them collectively is the commands result.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets factor this out into a second PR. It is an orthogonal concept and also relevant to other non-edit api operations in OTIO, so we should design that as its own piece.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally see it being used more widely. I was hoping to build the common commands on top of this system. A command like overwrite will potentially do quite a few events - something like "sub-edits"

e.g.
- set A.duration = F
- set B.duration = B
- insert C into T' at index 3

If the general feeling is they shouldn't use it, then I'll write them as direct procedures.

Comment on lines 4 to 16
# Design 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:

- Overwrite
- Trim
- Cut
- Slip
- Slide
- Ripple
- Roll
- Fill (3/4 Point Edit)

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be rephrased to be closer to OpenTimelineIO's scope... maybe something like:

OpenTimelineIO includes a suite of tools for operating on objects as raw data structures (append, etc), but is missing a library for performing higher level operations familiar to people from the editing world (Slip, 3/4 point edit, etc). This proposal presents an API for working with OpenTimelineIO objects in those ways.

I don't think they need to be enumerated here, since you're going to be covering them in more detail later.

Comment on lines 19 to 20
The underlying implementation for these edit commands will be in C++, possibly under a new library called `openedit`. This has the benefit of connecting with other languages more simply (Python, Swift, C, etc.) and, hopefully, we only need expose the functionality to the bindings.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean that you'd want a separate c++ DSO? To sit next to OpenTime.so and OpenTimelineIO.so? I kind of think it just belongs in the regular OpenTimelineIO library, but with its own headers. Or are you saying you'd just wrap this subset of the library and not need bindings to OpenTimelineIO?

I would probably just put them under OpenTimelineIO, possibly combined with other operations into an 'algorithms' folder:

- opentimelineio/
         edit/
or 
         algo/

Things like flatten are likely peers of this and I'd like to avoid too many namespaces for people to hunt through.

In python, I was thinking a similar arrangement:
opentimelineio.algorithms.slip()
etc.

Curious to hear your thoughts.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely agree with this. I didn't want to make this my first move without larger consensus but this will work super well and clean up a lot of clunky code I was sketching out. As @reinecke suggested, it'll be worth pointing out that no new schemas should be allowed under algo/

Comment on lines 40 to 57
### Retainer
Ideally, we continue to use the `Retainer<>` where possible to make sure editing is reference counted. Namespaces might make the names a bit clunky, seeing `SerializedObject::Retainer<>` all over:

```
namespace openedit { OPENEDIT_VERSION {

using namespace opentimelineio::OPENTIMELINEIO_VERSION;
auto item = SerializedObject::Retainer<Item>(...);

} }
```

So perhaps establishing some common aliases early might help?
```
using RetainedItem = SerializedObject::Retainer<Item>;
using RetainedComposition = SerializedObject::Retainer<Composition>;
...
```
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RetainedItem seems like a reasonable alias, however, reading this feels like it should all be in the same namespace. The edit library is going to require both OpenTimelineIO and opentime to function, and you'll need constructors, classes, etc from those to setup objects and metadata-- it seems like this might be more analogous to the split in imath between for example ImathBox.h and ImathBoxAlgo.h:
https://github.com/AcademySoftwareFoundation/openexr/blob/master/IlmBase/Imath/ImathBox.h
https://github.com/AcademySoftwareFoundation/openexr/blob/master/IlmBase/Imath/ImathBoxAlgo.h

Comment on lines 110 to 116
## Trim
![Trim](../_static/edit/03_trim.png)
- Adjust a single item's start time or duration.
- Do not affect other clips.
- Fill now-empty time with gap or template.
- Clamps source_range to non-gap boundary (can only overwrite gaps)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if source range isn't set, does the available range become the source range before modifying the values?

This is assuming the item is in track, is it an error condition if the item has no parent?

If the item is directly in a stack, is a track inserted wrapping the item so that gap can be put before or after the item?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope that the common commands are as simple as possible - with explicitly defined implementation or course. Part of that would be, possibly, having it assume the source_range is the available_range if not set before. Implementing it as an error would totally work too but do we gain anything with that?

Comment on lines 187 to 207
## Slip
![Slip](../_static/edit/05_slip.png)
- Adjust the start_time of an item's source_range.
- Do not affect surrounding items.
- Clamp to available_range of media (if available)

#### API
```py
"""
Args:
item: Item that we're slipping
delta: RationalTime to adjust the range by.
"""
otio.algorithms.slip(
item: Item,
delta: RationalTime,
)
```
- In the example image, `C` is `item` and `delta` is the arrow's vector.

> Effectively this is already quite simple in the core API but would be nice to have here.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect the value of some of these functions might be that they hide the underlying complexity of whether or not source_range is set.

Maybe you split these functions into a "maybe" and "Definitely" pile? that will help prioritize them and see if people come out with concerns or use cases.

This might be a controversial position, but I think I'd also prefer to not have things named too close even if they do mean a specific thing (slip/slide for example)... unless that verb means the same thing in all major editing packages.

- openedit scrubbed in favor of opentimelineio/algo
- better overview statement for the purpose of the project
- clarification of audience
- ASCII art and additional constraints for various commands (still more to do)
@@ -62,7 +40,10 @@ Let's go through some of the most common commands and what the python API might
## Overwrite

![Overwrite](../_static/edit/01_overwrite.png)
- Anything overlapping is destroyed on contact. This may split an item.
- Augment the source range of overlapping items
Copy link
Collaborator

@meshula meshula May 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I still feel the wording is not as precise as it could be. Elsewhere in OTIO we are adopting Allen's nomenclature, here, because the intent of the words is unambiguous: https://en.wikipedia.org/wiki/Allen's_interval_algebra

The ASCII art that follows makes things quite clear though, so I'm just going to express a preference for Allen as we continue to iterate.

composition's limit
track's current duration

Example:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, thanks!

@jminor jminor added the roadmap label Jun 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants