ynput / ayon-resolve

AYON Addon for Blackmagic DaVinci Resolve
Apache License 2.0
4 stars 3 forks source link

Integration fails on publishing with recent OpenTimelineIO versions (0.16.0+) #2

Open BigRoy opened 3 months ago

BigRoy commented 3 months ago

Is there an existing issue for this?

Current Behavior:

As can be seen in this community forum post the Resolve publishing logic fails on Precollect Instances:

image

Expected Behavior:

Recent OpenTimelineIO versions should work fine.

But, for now we could also update documentation that it requires OpenTimelineIO 0.15.0, so along the lines of:

pip install OpenTimelineIO==0.15.0

Version

1.0.0

What platform you are running on?

Windows

Steps To Reproduce:

  1. Use e.g. Python 3.9 (or other Py versions like 3.10) with recent OpenTimelineIO, e.g. 0.16.0 or 0.17.0
  2. Run Resolve
  3. Create publishable clip
  4. Publish

Are there any labels you wish to add?

Relevant log output:

Traceback (most recent call last):
  File "C:\Users\User\AppData\Local\Ynput\AYON\dependency_packages\ayon_2403061937_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process
    runner(*args)
  File "E:\dev\ayon-core\server_addon\resolve\client\ayon_resolve\plugins\publish\precollect_instances.py", line 114, in process
  File "E:\dev\ayon-core\server_addon\resolve\client\ayon_resolve\api\lib.py", line 912, in get_otio_clip_instance_data
    for otio_clip in otio_timeline.each_clip():
AttributeError: 'opentimelineio._otio.Timeline' object has no attribute 'each_clip'

Additional context:

No response

BigRoy commented 3 months ago

This is a part of the help(timeline) using OpenTimelineIO==0.16.0:

class Timeline(SerializableObjectWithMetadata)
 |  Method resolution order:
 |      Timeline
 |      SerializableObjectWithMetadata
 |      SerializableObject
 |      pybind11_builtins.pybind11_object
 |      builtins.object
 |  
 |  Methods defined here:
 |  
 |  __init__(...)
 |      __init__(self: opentimelineio._otio.Timeline, name: str = '', tracks: Optional[List[opentimelineio._otio.Composable]] = None, global_start_time: Optional[opentimelineio._opentime.RationalTime] = None, metadata: object = None) -> None
 |  
 |  __repr__(self)
 |  
 |  __str__(self)
 |  
 |  audio_tracks(...)
 |      audio_tracks(self: opentimelineio._otio.Timeline) -> List[opentimelineio._otio.Track]
 |  
 |  duration(...)
 |      duration(self: opentimelineio._otio.Timeline) -> opentimelineio._opentime.RationalTime
 |  
 |  find_children(...)
 |      find_children(self: opentimelineio._otio.Timeline, descended_from_type: object = None, search_range: Optional[opentimelineio._opentime.TimeRange] = None, shallow_search: bool = False) -> List[opentimelineio._otio.SerializableObject]
 |  
 |  find_clips(...)
 |      find_clips(self: opentimelineio._otio.Timeline, search_range: Optional[opentimelineio._opentime.TimeRange] = None, shallow_search: bool = False) -> List[opentimelineio._otio.SerializableObject]
 |  
 |  range_of_child(...)
 |      range_of_child(self: opentimelineio._otio.Timeline, arg0: opentimelineio._otio.Composable) -> opentimelineio._opentime.TimeRange
 |  
 |  video_tracks(...)
 |      video_tracks(self: opentimelineio._otio.Timeline) -> List[opentimelineio._otio.Track]

I believe we might need to use timeline.find_clips() which does seem to work.


Yet that does not work in OpenTimelineIO==0.15.0. Error:

Traceback (most recent call last):
  File "C:\Users\User\AppData\Local\Ynput\AYON\dependency_packages\ayon_2403061937_windows.zip\dependencies\pyblish\plugin.py", line 527, in __explicit_process
    runner(*args)
  File "E:\dev\ayon-core\server_addon\resolve\client\ayon_resolve\plugins\publish\precollect_instances.py", line 119, in process
  File "E:\dev\ayon-core\server_addon\resolve\client\ayon_resolve\api\lib.py", line 913, in get_otio_clip_instance_data
    for otio_clip in otio_timeline.find_clips():
AttributeError: 'opentimelineio._otio.Timeline' object has no attribute 'find_clips'

And some info about the timeline object:

class Timeline(SerializableObjectWithMetadata)
 |  Method resolution order:
 |      Timeline
 |      SerializableObjectWithMetadata
 |      SerializableObject
 |      pybind11_builtins.pybind11_object
 |      builtins.object
 |  
 |  Methods defined here:
 |  
 |  __init__(...)
 |      __init__(self: opentimelineio._otio.Timeline, name: str = '', tracks: object = None, global_start_time: Optional[opentimelineio._opentime.RationalTime] = None, metadata: object = None) -> None
 |  
 |  __repr__(self)
 |  
 |  __str__(self)
 |  
 |  audio_tracks(...)
 |      audio_tracks(self: opentimelineio._otio.Timeline) -> List[opentimelineio._otio.Track]
 |  
 |  children_if(...)
 |      children_if(self: opentimelineio._otio.Timeline, descended_from_type: object = None, search_range: Optional[opentimelineio._opentime.TimeRange] = None, shallow_search: bool = False) -> List[opentimelineio._otio.SerializableObject]
 |  
 |  clip_if(...)
 |      clip_if(self: opentimelineio._otio.Timeline, search_range: Optional[opentimelineio._opentime.TimeRange] = None, shallow_search: bool = False) -> List[opentimelineio._otio.SerializableObject]
 |  
 |  duration(...)
 |      duration(self: opentimelineio._otio.Timeline) -> opentimelineio._opentime.RationalTime
 |  
 |  each_child(self, search_range=None, descended_from_type=<class 'opentimelineio._otio.Composable'>, shallow_search=False)
 |      Generator that returns each child contained in the timeline
 |      in the order in which it is found.
 |      
 |      .. deprecated:: 0.14.0
 |          Use :meth:`children_if` instead.
 |      
 |      :param TimeRange search_range: if specified, only children whose range overlaps
 |                                     with the search range will be yielded.
 |      :param type descended_from_type: if specified, only children who are a descendent
 |                                       of the descended_from_type will be yielded.
 |      :param bool shallow_search: if True, will only search children of self and not
 |                                  recurse into children of children.
 |  
 |  each_clip(self, search_range=None, shallow_search=False)
 |      Generator that returns each clip contained in the timeline
 |      in the order in which it is found.
 |      
 |      .. deprecated:: 0.14.0
 |          Use :meth:`clip_if` instead.
 |      
 |      :param TimeRange search_range: if specified, only children whose range overlaps
 |                                     with the search range will be yielded.
 |      :param bool shallow_search: if True, will only search children of self and not
 |                                  recurse into children of children.
 |  
 |  range_of_child(...)
 |      range_of_child(self: opentimelineio._otio.Timeline, arg0: opentimelineio._otio.Composable) -> opentimelineio._opentime.TimeRange
 |  
 |  video_tracks(...)
 |      video_tracks(self: opentimelineio._otio.Timeline) -> List[opentimelineio._otio.Track]

That help shows that each_clip was indeed deprecated there and recommends using clip_if which does work in 0.15.0 but actually does not work in 0.16.0

image

So much fun. We might just need to use a wrapper function to pick the right method based on version or available methods. :)

Like for example:

def each_clip(timeline):
    if hasattr(timeline, "find_clips"):
        # OpenTimelineIO 0.16.0+
        return timeline.find_clips()
    elif hasattr(timeline, "clip_if"):
        # OpenTimelineIO 0.15.0+
        return timeline.clip_if()
    else:
        # OpenTimelineIO - older?
        return timeline.each_clip()
BigRoy commented 3 months ago

@antirotor what would you like to do here? :)

BigRoy commented 2 months ago

This may be more urgent now - we've gotten a report that Resolve's python interpreter just 'crashes' and logs nothing more as soon as it imports OpenTimelineIO version 0.17.0 (tested on Windows).

  1. We will want to at least update the docs to ensure 0.15.0 gets installed by most people? (or do it in another way?)
  2. Find some way to safeguard against the crash and report invalid versions.

I posted elsewhere the todo:

  1. Definitely something we should track as issue so that the logs are clearer for the script

    • Force a print statement before the imports to ensure we know at least the script run started (for future debugging)
    • Somehow safeguard the import of OpenTimelineIO
    • Preferably report if a "known invalid" OpenTimelineIO version is installed (if we can do that without importing it, because importing it.. crashes it) | of course to get there we may want to first reproduce the crash on our end to with 0.17.0 to confirm)
  2. Improve the README and installation instruction for Resolve to state to install OpenTimelineIO 0.15.0 to avoid hard to debug issues.