-
Notifications
You must be signed in to change notification settings - Fork 288
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
base: main
Are you sure you want to change the base?
Conversation
Codecov Report
@@ Coverage Diff @@
## master #719 +/- ##
=======================================
Coverage 81.88% 81.88%
=======================================
Files 72 72
Lines 2755 2755
=======================================
Hits 2256 2256
Misses 499 499
Continue to review full report at Codecov.
|
docs/design/editorial_design.md
Outdated
## 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo 'below'
docs/design/editorial_design.md
Outdated
## Overwrite | ||
|
||
![Overwrite](../_static/edit/01_overwrite.png) | ||
- Anything overlapping is destroyed on contact. This may split an item. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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..
There was a problem hiding this comment.
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:
- The implementer(s) of the API
- Future users of the API - TDs and DCC developers
- 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.
There was a problem hiding this comment.
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 👍
There was a problem hiding this 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.
docs/design/editorial_design.md
Outdated
## Overwrite | ||
|
||
![Overwrite](../_static/edit/01_overwrite.png) | ||
- Anything overlapping is destroyed on contact. This may split an item. |
There was a problem hiding this comment.
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..
There was a problem hiding this 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!
docs/design/editorial_design.md
Outdated
`- ... | ||
``` | ||
|
||
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. |
There was a problem hiding this comment.
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.
docs/design/editorial_design.md
Outdated
## Overwrite | ||
|
||
![Overwrite](../_static/edit/01_overwrite.png) | ||
- Anything overlapping is destroyed on contact. This may split an item. |
There was a problem hiding this comment.
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:
- The implementer(s) of the API
- Future users of the API - TDs and DCC developers
- 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( |
There was a problem hiding this comment.
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
.
docs/design/editorial_design.md
Outdated
otio.algorithms.insert( | ||
item: Item, | ||
track: Track, | ||
composition_time: RationalTime, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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
docs/design/editorial_design.md
Outdated
otio.algorithms.fill( | ||
item: Item, | ||
track: Track, | ||
replace: Item = None, # Alternatively, we could have a parent-coord |
There was a problem hiding this comment.
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 | ||
) |
There was a problem hiding this comment.
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.)
There was a problem hiding this 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.
docs/design/editorial_design.md
Outdated
# 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
docs/design/editorial_design.md
Outdated
# 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. |
There was a problem hiding this comment.
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.
docs/design/editorial_design.md
Outdated
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. | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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/
docs/design/editorial_design.md
Outdated
### 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>; | ||
... | ||
``` |
There was a problem hiding this comment.
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
docs/design/editorial_design.md
Outdated
## 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) | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
docs/design/editorial_design.md
Outdated
## 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. |
There was a problem hiding this comment.
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)
docs/design/editorial_design.md
Outdated
@@ -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 |
There was a problem hiding this comment.
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.
docs/design/editorial_design.md
Outdated
composition's limit | ||
track's current duration | ||
|
||
Example: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great, thanks!
This is the relevant PR for Issue 771
This lays out the initial design specs for the edit platform within OTIO.