From 5bb519a9c41bfb680abbe5ce233d37f70e5f8efc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 25 Jan 2021 11:36:28 +0100 Subject: [PATCH 01/17] hiero: adding readme for setup resolve --- pype/hosts/resolve/README.markdown | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pype/hosts/resolve/README.markdown b/pype/hosts/resolve/README.markdown index 8b13789179..f8d2da0794 100644 --- a/pype/hosts/resolve/README.markdown +++ b/pype/hosts/resolve/README.markdown @@ -1 +1,26 @@ +#### Basic setup +- Install [latest DaVinci Resolve](https://sw.blackmagicdesign.com/DaVinciResolve/v16.2.8/DaVinci_Resolve_Studio_16.2.8_Windows.zip?Key-Pair-Id=APKAJTKA3ZJMJRQITVEA&Signature=EcFuwQFKHZIBu2zDj5LTCQaQDXcKOjhZY7Fs07WGw24xdDqfwuALOyKu+EVzDX2Tik0cWDunYyV0r7hzp+mHmczp9XP4YaQXHdyhD/2BGWDgiMsiTQbNkBgbfy5MsAMFY8FHCl724Rxm8ke1foWeUVyt/Cdkil+ay+9sL72yFhaSV16sncko1jCIlCZeMkHhbzqPwyRuqLGmxmp8ey9KgBhI3wGFFPN201VMaV+RHrpX+KAfaR6p6dwo3FrPbRHK9TvMI1RA/1lJ3fVtrkDW69LImIKAWmIxgcStUxR9/taqLOD66FNiflHd1tufHv3FBa9iYQsjb3VLMPx7OCwLyg==&Expires=1608308139) +- add absolute path to ffmpeg into pype settings + ![image](https://user-images.githubusercontent.com/40640033/102630786-43294f00-414d-11eb-98de-f0ae51f62077.png) +- install Python 3.6 into `%LOCALAPPDATA%/Programs/Python/Python36` (only respected path by Resolve) +- install OpenTimelineIO for 3.6 `%LOCALAPPDATA%\Programs\Python\Python36\python.exe -m pip install git+https://github.com/PixarAnimationStudios/OpenTimelineIO.git@5aa24fbe89d615448876948fe4b4900455c9a3e8` and move builded files from `%LOCALAPPDATA%/Programs/Python/Python36/Lib/site-packages/opentimelineio/cxx-libs/bin and lib` to `%LOCALAPPDATA%/Programs/Python/Python36/Lib/site-packages/opentimelineio/`. I was building it on Win10 machine with Visual Studio Community 2019 and + ![image](https://user-images.githubusercontent.com/40640033/102792588-ffcb1c80-43a8-11eb-9c6b-bf2114ed578e.png) with installed CMake in PATH. +- install PySide2 for 3.6 `%LOCALAPPDATA%\Programs\Python\Python36\python.exe -m pip install PySide2` +- make sure Resovle Fusion (Fusion Tab/menu/Fusion/Fusion Setings) is set to Python 3.6 + ![image](https://user-images.githubusercontent.com/40640033/102631545-280b0f00-414e-11eb-89fc-98ac268d209d.png) + +#### Editorial setup + +This is how it looks on my testing project timeline +![image](https://user-images.githubusercontent.com/40640033/102637638-96ec6600-4156-11eb-9656-6e8e3ce4baf8.png) +Notice I had renamed tracks to `main` (holding metadata markers) and `review` used for generating review data with ffmpeg confersion to jpg sequence. + +1. you need to start Pype menu from Resolve/EditTab/Menu/Workspace/Scripts/**PYPE_MENU** +2. then select any clips in `main` track and change their color to `Chocolate` +3. in Pype Menu select `Create` +4. in Creator select `Create Publishable Clip [New]` (temporary name) +5. set `Rename clips` to True, Master Track to `main` and Use review track to `review` as in picture + ![image](https://user-images.githubusercontent.com/40640033/102643773-0d419600-4160-11eb-919e-9c2be0aecab8.png) +6. after you hit `ok` all clips are colored to `ping` and marked with pype metadata tag +7. git `Publish` on pype menu and see that all had been collected correctly. That is the last step for now as rest is Work in progress. Next steps will follow. From 76d08bf3bad39af3a99b1bd2c3fea8f8ab9dead7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 25 Jan 2021 11:36:46 +0100 Subject: [PATCH 02/17] hiero: sequence loader wip --- pype/plugins/resolve/load/load_sequence.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pype/plugins/resolve/load/load_sequence.py diff --git a/pype/plugins/resolve/load/load_sequence.py b/pype/plugins/resolve/load/load_sequence.py new file mode 100644 index 0000000000..e69de29bb2 From 63b12288d85ab1f10ce89907ff9a9cf8e70f7fe4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 25 Jan 2021 12:26:53 +0100 Subject: [PATCH 03/17] hiero: loading sequence wip --- pype/hosts/resolve/__init__.py | 4 + pype/hosts/resolve/lib.py | 12 ++ pype/plugins/resolve/load/load_sequence.py | 153 +++++++++++++++++++++ 3 files changed, 169 insertions(+) diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index b6c43a58c2..08e7f325b1 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -20,6 +20,7 @@ from .lib import ( get_current_sequence, get_video_track_names, get_current_track_items, + get_track_item_by_name, get_track_item_pype_tag, set_track_item_pype_tag, imprint, @@ -37,6 +38,7 @@ from .lib import ( from .menu import launch_pype_menu from .plugin import ( + SequenceLoader, Creator, PublishClip ) @@ -75,6 +77,7 @@ __all__ = [ "get_current_sequence", "get_video_track_names", "get_current_track_items", + "get_track_item_by_name", "get_track_item_pype_tag", "set_track_item_pype_tag", "imprint", @@ -92,6 +95,7 @@ __all__ = [ "launch_pype_menu", # plugin + "SequenceLoader", "Creator", "PublishClip", diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 2cf228d854..fbc19166a4 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -115,6 +115,18 @@ def get_current_track_items( return selected_clips +def get_track_item_by_name(name: str) -> object: + track_itmes = get_current_track_items() + for _ti in track_itmes: + tag_data = get_track_item_pype_tag(_ti["clip"]["item"]) + tag_name = tag_data.get("name") + if not tag_name: + continue + if tag_data.get("name") in name: + return _ti + return None + + def get_track_item_pype_tag(track_item): """ Get pype track item tag created by creator or loader plugin. diff --git a/pype/plugins/resolve/load/load_sequence.py b/pype/plugins/resolve/load/load_sequence.py index e69de29bb2..670a11ae88 100644 --- a/pype/plugins/resolve/load/load_sequence.py +++ b/pype/plugins/resolve/load/load_sequence.py @@ -0,0 +1,153 @@ +from avalon import io, api +from pype.hosts import resolve + + +class LoadClip(resolve.SequenceLoader): + """Load a subset to timeline as clip + + Place clip to timeline on its asset origin timings collected + during conforming to project + """ + + families = ["render2d", "source", "plate", "render", "review"] + representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"] + + label = "Load as clip" + order = -10 + icon = "code-fork" + color = "orange" + + # for loader multiselection + sequence = None + track = None + + # presets + clip_color_last = "green" + clip_color = "red" + + def load(self, context, name, namespace, options): + + # in case loader uses multiselection + if self.track and self.sequence: + options.update({ + "sequence": self.sequence, + "track": self.track + }) + + # load clip to timeline and get main variables + track_item = phiero.ClipLoader(self, context, **options).load() + namespace = namespace or track_item.name() + version = context['version'] + version_data = version.get("data", {}) + version_name = version.get("name", None) + colorspace = version_data.get("colorspace", None) + object_name = "{}_{}".format(name, namespace) + + # add additional metadata from the version to imprint Avalon knob + add_keys = [ + "frameStart", "frameEnd", "source", "author", + "fps", "handleStart", "handleEnd" + ] + + # move all version data keys to tag data + data_imprint = {} + for key in add_keys: + data_imprint.update({ + key: version_data.get(key, str(None)) + }) + + # add variables related to version context + data_imprint.update({ + "version": version_name, + "colorspace": colorspace, + "objectName": object_name + }) + + # update color of clip regarding the version order + self.set_item_color(track_item, version) + + # deal with multiselection + self.multiselection(track_item) + + self.log.info("Loader done: `{}`".format(name)) + + return resolve.containerise( + track_item, + name, namespace, context, + self.__class__.__name__, + data_imprint) + + def switch(self, container, representation): + self.update(container, representation) + + def update(self, container, representation): + """ Updating previously loaded clips + """ + + # load clip to timeline and get main variables + name = container['name'] + namespace = container['namespace'] + track_item = resolve.get_track_item_by_name(namespace) + version = io.find_one({ + "type": "version", + "_id": representation["parent"] + }) + version_data = version.get("data", {}) + version_name = version.get("name", None) + colorspace = version_data.get("colorspace", None) + object_name = "{}_{}".format(name, namespace) + file = api.get_representation_path(representation).replace("\\", "/") + + # reconnect media to new path + track_item.source().reconnectMedia(file) + + # add additional metadata from the version to imprint Avalon knob + add_keys = [ + "frameStart", "frameEnd", "source", "author", + "fps", "handleStart", "handleEnd" + ] + + # move all version data keys to tag data + data_imprint = {} + for key in add_keys: + data_imprint.update({ + key: version_data.get(key, str(None)) + }) + + # add variables related to version context + data_imprint.update({ + "representation": str(representation["_id"]), + "version": version_name, + "colorspace": colorspace, + "objectName": object_name + }) + + # update color of clip regarding the version order + self.set_item_color(track_item, version) + + return resolve.update_container(track_item, data_imprint) + + @classmethod + def multiselection(cls, track_item): + if not cls.track: + cls.track = track_item.parent() + cls.sequence = cls.track.parent() + + @classmethod + def set_item_color(cls, track_item, version): + + # define version name + version_name = version.get("name", None) + # get all versions in list + versions = io.find({ + "type": "version", + "parent": version["parent"] + }).distinct('name') + + max_version = max(versions) + + # set clip colour + if version_name == max_version: + track_item.source().binItem().setColor(cls.clip_color_last) + else: + track_item.source().binItem().setColor(cls.clip_color) From be8f350f821156f5f17056802042a185e9d61d0b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 25 Jan 2021 13:21:08 +0100 Subject: [PATCH 04/17] resolve: updating loader sequence lib functions --- pype/hosts/resolve/RESOLVE_API_README_NEW.txt | 461 ++++++++++++++++++ pype/hosts/resolve/__init__.py | 2 + pype/hosts/resolve/lib.py | 46 +- pype/hosts/resolve/plugin.py | 230 +++++++++ pype/plugins/resolve/load/load_sequence.py | 6 +- 5 files changed, 735 insertions(+), 10 deletions(-) create mode 100644 pype/hosts/resolve/RESOLVE_API_README_NEW.txt diff --git a/pype/hosts/resolve/RESOLVE_API_README_NEW.txt b/pype/hosts/resolve/RESOLVE_API_README_NEW.txt new file mode 100644 index 0000000000..bcd02a62ae --- /dev/null +++ b/pype/hosts/resolve/RESOLVE_API_README_NEW.txt @@ -0,0 +1,461 @@ +Updated as of 20 October 2020 +----------------------------- +In this package, you will find a brief introduction to the Scripting API for DaVinci Resolve Studio. Apart from this README.txt file, this package contains folders containing the basic import +modules for scripting access (DaVinciResolve.py) and some representative examples. + +From v16.2.0 onwards, the nodeIndex parameters accepted by SetLUT() and SetCDL() are 1-based instead of 0-based, i.e. 1 <= nodeIndex <= total number of nodes. + + +Overview +-------- +As with Blackmagic Design Fusion scripts, user scripts written in Lua and Python programming languages are supported. By default, scripts can be invoked from the Console window in the Fusion page, +or via command line. This permission can be changed in Resolve Preferences, to be only from Console, or to be invoked from the local network. Please be aware of the security implications when +allowing scripting access from outside of the Resolve application. + + +Prerequisites +------------- +DaVinci Resolve scripting requires one of the following to be installed (for all users): + + Lua 5.1 + Python 2.7 64-bit + Python 3.6 64-bit + + +Using a script +-------------- +DaVinci Resolve needs to be running for a script to be invoked. + +For a Resolve script to be executed from an external folder, the script needs to know of the API location. +You may need to set the these environment variables to allow for your Python installation to pick up the appropriate dependencies as shown below: + + Mac OS X: + RESOLVE_SCRIPT_API="/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting" + RESOLVE_SCRIPT_LIB="/Applications/DaVinci Resolve/DaVinci Resolve.app/Contents/Libraries/Fusion/fusionscript.so" + PYTHONPATH="$PYTHONPATH:$RESOLVE_SCRIPT_API/Modules/" + + Windows: + RESOLVE_SCRIPT_API="%PROGRAMDATA%\Blackmagic Design\DaVinci Resolve\Support\Developer\Scripting" + RESOLVE_SCRIPT_LIB="C:\Program Files\Blackmagic Design\DaVinci Resolve\fusionscript.dll" + PYTHONPATH="%PYTHONPATH%;%RESOLVE_SCRIPT_API%\Modules\" + + Linux: + RESOLVE_SCRIPT_API="/opt/resolve/Developer/Scripting" + RESOLVE_SCRIPT_LIB="/opt/resolve/libs/Fusion/fusionscript.so" + PYTHONPATH="$PYTHONPATH:$RESOLVE_SCRIPT_API/Modules/" + (Note: For standard ISO Linux installations, the path above may need to be modified to refer to /home/resolve instead of /opt/resolve) + +As with Fusion scripts, Resolve scripts can also be invoked via the menu and the Console. + +On startup, DaVinci Resolve scans the subfolders in the directories shown below and enumerates the scripts found in the Workspace application menu under Scripts. +Place your script under Utility to be listed in all pages, under Comp or Tool to be available in the Fusion page or under folders for individual pages (Edit, Color or Deliver). Scripts under Deliver are additionally listed under render jobs. +Placing your script here and invoking it from the menu is the easiest way to use scripts. + Mac OS X: + - All users: /Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts + - Specific user: /Users//Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts + Windows: + - All users: %PROGRAMDATA%\Blackmagic Design\DaVinci Resolve\Fusion\Scripts + - Specific user: %APPDATA%\Roaming\Blackmagic Design\DaVinci Resolve\Support\Fusion\Scripts + Linux: + - All users: /opt/resolve/Fusion/Scripts (or /home/resolve/Fusion/Scripts/ depending on installation) + - Specific user: $HOME/.local/share/DaVinciResolve/Fusion/Scripts + +The interactive Console window allows for an easy way to execute simple scripting commands, to query or modify properties, and to test scripts. The console accepts commands in Python 2.7, Python 3.6 +and Lua and evaluates and executes them immediately. For more information on how to use the Console, please refer to the DaVinci Resolve User Manual. + +This example Python script creates a simple project: + #!/usr/bin/env python + import DaVinciResolveScript as dvr_script + resolve = dvr_script.scriptapp("Resolve") + fusion = resolve.Fusion() + projectManager = resolve.GetProjectManager() + projectManager.CreateProject("Hello World") + +The resolve object is the fundamental starting point for scripting via Resolve. As a native object, it can be inspected for further scriptable properties - using table iteration and "getmetatable" +in Lua and dir, help etc in Python (among other methods). A notable scriptable object above is fusion - it allows access to all existing Fusion scripting functionality. + + +Running DaVinci Resolve in headless mode +---------------------------------------- +DaVinci Resolve can be launched in a headless mode without the user interface using the -nogui command line option. When DaVinci Resolve is launched using this option, the user interface is disabled. +However, the various scripting APIs will continue to work as expected. + + +Basic Resolve API +----------------- +Some commonly used API functions are described below (*). As with the resolve object, each object is inspectable for properties and functions. + +Resolve + Fusion() --> Fusion # Returns the Fusion object. Starting point for Fusion scripts. + GetMediaStorage() --> MediaStorage # Returns the media storage object to query and act on media locations. + GetProjectManager() --> ProjectManager # Returns the project manager object for currently open database. + OpenPage(pageName) --> None # Switches to indicated page in DaVinci Resolve. Input can be one of ("media", "cut", "edit", "fusion", "color", "fairlight", "deliver"). + GetProductName() --> string # Returns product name. + GetVersion() --> [version fields] # Returns list of product version fields in [major, minor, patch, build, suffix] format. + GetVersionString() --> string # Returns product version in "major.minor.patch[suffix].build" format. + +ProjectManager + CreateProject(projectName) --> Project # Creates and returns a project if projectName (string) is unique, and None if it is not. + DeleteProject(projectName) --> Bool # Delete project in the current folder if not currently loaded + LoadProject(projectName) --> Project # Loads and returns the project with name = projectName (string) if there is a match found, and None if there is no matching Project. + GetCurrentProject() --> Project # Returns the currently loaded Resolve project. + SaveProject() --> Bool # Saves the currently loaded project with its own name. Returns True if successful. + CloseProject(project) --> Bool # Closes the specified project without saving. + CreateFolder(folderName) --> Bool # Creates a folder if folderName (string) is unique. + DeleteFolder(folderName) --> Bool # Deletes the specified folder if it exists. Returns True in case of success. + GetProjectListInCurrentFolder() --> [project names...] # Returns a list of project names in current folder. + GetFolderListInCurrentFolder() --> [folder names...] # Returns a list of folder names in current folder. + GotoRootFolder() --> Bool # Opens root folder in database. + GotoParentFolder() --> Bool # Opens parent folder of current folder in database if current folder has parent. + GetCurrentFolder() --> string # Returns the current folder name. + OpenFolder(folderName) --> Bool # Opens folder under given name. + ImportProject(filePath) --> Bool # Imports a project from the file path provided. Returns True if successful. + ExportProject(projectName, filePath, withStillsAndLUTs=True) --> Bool # Exports project to provided file path, including stills and LUTs if withStillsAndLUTs is True (enabled by default). Returns True in case of success. + RestoreProject(filePath) --> Bool # Restores a project from the file path provided. Returns True if successful. + GetCurrentDatabase() --> {dbInfo} # Returns a dictionary (with keys 'DbType', 'DbName' and optional 'IpAddress') corresponding to the current database connection + GetDatabaseList() --> [{dbInfo}] # Returns a list of dictionary items (with keys 'DbType', 'DbName' and optional 'IpAddress') corresponding to all the databases added to Resolve + SetCurrentDatabase({dbInfo}) --> Bool # Switches current database connection to the database specified by the keys below, and closes any open project. + # 'DbType': 'Disk' or 'PostgreSQL' (string) + # 'DbName': database name (string) + # 'IpAddress': IP address of the PostgreSQL server (string, optional key - defaults to '127.0.0.1') + +Project + GetMediaPool() --> MediaPool # Returns the Media Pool object. + GetTimelineCount() --> int # Returns the number of timelines currently present in the project. + GetTimelineByIndex(idx) --> Timeline # Returns timeline at the given index, 1 <= idx <= project.GetTimelineCount() + GetCurrentTimeline() --> Timeline # Returns the currently loaded timeline. + SetCurrentTimeline(timeline) --> Bool # Sets given timeline as current timeline for the project. Returns True if successful. + GetName() --> string # Returns project name. + SetName(projectName) --> Bool # Sets project name if given projectname (string) is unique. + GetPresetList() --> [presets...] # Returns a list of presets and their information. + SetPreset(presetName) --> Bool # Sets preset by given presetName (string) into project. + AddRenderJob() --> string # Adds a render job based on current render settings to the render queue. Returns a unique job id (string) for the new render job. + DeleteRenderJob(jobId) --> Bool # Deletes render job for input job id (string). + DeleteAllRenderJobs() --> Bool # Deletes all render jobs in the queue. + GetRenderJobList() --> [render jobs...] # Returns a list of render jobs and their information. + GetRenderPresetList() --> [presets...] # Returns a list of render presets and their information. + StartRendering(jobId1, jobId2, ...) --> Bool # Starts rendering jobs indicated by the input job ids. + StartRendering([jobIds...], isInteractiveMode=False) --> Bool # Starts rendering jobs indicated by the input job ids. + # The optional "isInteractiveMode", when set, enables error feedback in the UI during rendering. + StartRendering(isInteractiveMode=False) --> Bool # Starts rendering all queued render jobs. + # The optional "isInteractiveMode", when set, enables error feedback in the UI during rendering. + StopRendering() --> None # Stops any current render processes. + IsRenderingInProgress() --> Bool # Returns True if rendering is in progress. + LoadRenderPreset(presetName) --> Bool # Sets a preset as current preset for rendering if presetName (string) exists. + SaveAsNewRenderPreset(presetName) --> Bool # Creates new render preset by given name if presetName(string) is unique. + SetRenderSettings({settings}) --> Bool # Sets given settings for rendering. Settings is a dict, with support for the keys: + # "SelectAllFrames": Bool + # "MarkIn": int + # "MarkOut": int + # "TargetDir": string + # "CustomName": string + # "UniqueFilenameStyle": 0 - Prefix, 1 - Suffix. + # "ExportVideo": Bool + # "ExportAudio": Bool + # "FormatWidth": int + # "FormatHeight": int + # "FrameRate": float (examples: 23.976, 24) + # "PixelAspectRatio": string (for SD resolution: "16_9" or "4_3") (other resolutions: "square" or "cinemascope") + # "VideoQuality" possible values for current codec (if applicable): + # 0 (int) - will set quality to automatic + # [1 -> MAX] (int) - will set input bit rate + # ["Least", "Low", "Medium", "High", "Best"] (String) - will set input quality level + # "AudioCodec": string (example: "aac") + # "AudioBitDepth": int + # "AudioSampleRate": int + # "ColorSpaceTag" : string (example: "Same as Project", "AstroDesign") + # "GammaTag" : string (example: "Same as Project", "ACEScct") + GetRenderJobStatus(jobId) --> {status info} # Returns a dict with job status and completion percentage of the job by given jobId (string). + GetSetting(settingName) --> string # Returns value of project setting (indicated by settingName, string). Check the section below for more information. + SetSetting(settingName, settingValue) --> Bool # Sets the project setting (indicated by settingName, string) to the value (settingValue, string). Check the section below for more information. + GetRenderFormats() --> {render formats..} # Returns a dict (format -> file extension) of available render formats. + GetRenderCodecs(renderFormat) --> {render codecs...} # Returns a dict (codec description -> codec name) of available codecs for given render format (string). + GetCurrentRenderFormatAndCodec() --> {format, codec} # Returns a dict with currently selected format 'format' and render codec 'codec'. + SetCurrentRenderFormatAndCodec(format, codec) --> Bool # Sets given render format (string) and render codec (string) as options for rendering. + GetCurrentRenderMode() --> int # Returns the render mode: 0 - Individual clips, 1 - Single clip. + SetCurrentRenderMode(renderMode) --> Bool # Sets the render mode. Specify renderMode = 0 for Individual clips, 1 for Single clip. + GetRenderResolutions(format, codec) --> [{Resolution}] # Returns list of resolutions applicable for the given render format (string) and render codec (string). Returns full list of resolutions if no argument is provided. Each element in the list is a dictionary with 2 keys "Width" and "Height". + RefreshLUTList() --> Bool # Refreshes LUT List + +MediaStorage + GetMountedVolumeList() --> [paths...] # Returns list of folder paths corresponding to mounted volumes displayed in Resolve’s Media Storage. + GetSubFolderList(folderPath) --> [paths...] # Returns list of folder paths in the given absolute folder path. + GetFileList(folderPath) --> [paths...] # Returns list of media and file listings in the given absolute folder path. Note that media listings may be logically consolidated entries. + RevealInStorage(path) --> None # Expands and displays given file/folder path in Resolve’s Media Storage. + AddItemListToMediaPool(item1, item2, ...) --> [clips...] # Adds specified file/folder paths from Media Storage into current Media Pool folder. Input is one or more file/folder paths. Returns a list of the MediaPoolItems created. + AddItemListToMediaPool([items...]) --> [clips...] # Adds specified file/folder paths from Media Storage into current Media Pool folder. Input is an array of file/folder paths. Returns a list of the MediaPoolItems created. + AddClipMattesToMediaPool(MediaPoolItem, [paths], stereoEye) --> Bool # Adds specified media files as mattes for the specified MediaPoolItem. StereoEye is an optional argument for specifying which eye to add the matte to for stereo clips ("left" or "right"). Returns True if successful. + AddTimelineMattesToMediaPool([paths]) --> [MediaPoolItems] # Adds specified media files as timeline mattes in current media pool folder. Returns a list of created MediaPoolItems. + +MediaPool + GetRootFolder() --> Folder # Returns root Folder of Media Pool + AddSubFolder(folder, name) --> Folder # Adds new subfolder under specified Folder object with the given name. + CreateEmptyTimeline(name) --> Timeline # Adds new timeline with given name. + AppendToTimeline(clip1, clip2, ...) --> Bool # Appends specified MediaPoolItem objects in the current timeline. Returns True if successful. + AppendToTimeline([clips]) --> Bool # Appends specified MediaPoolItem objects in the current timeline. Returns True if successful. + AppendToTimeline([{clipInfo}, ...]) --> Bool # Appends list of clipInfos specified as dict of "mediaPoolItem", "startFrame" (int), "endFrame" (int). + CreateTimelineFromClips(name, clip1, clip2,...) --> Timeline # Creates new timeline with specified name, and appends the specified MediaPoolItem objects. + CreateTimelineFromClips(name, [clips]) --> Timeline # Creates new timeline with specified name, and appends the specified MediaPoolItem objects. + CreateTimelineFromClips(name, [{clipInfo}]) --> Timeline # Creates new timeline with specified name, appending the list of clipInfos specified as a dict of "mediaPoolItem", "startFrame" (int), "endFrame" (int). + ImportTimelineFromFile(filePath, {importOptions}) --> Timeline # Creates timeline based on parameters within given file and optional importOptions dict, with support for the keys: + # "timelineName": string, specifies the name of the timeline to be created + # "importSourceClips": Bool, specifies whether source clips should be imported, True by default + # "sourceClipsPath": string, specifies a filesystem path to search for source clips if the media is inaccessible in their original path and if "importSourceClips" is True + # "sourceClipsFolders": List of Media Pool folder objects to search for source clips if the media is not present in current folder and if "importSourceClips" is False + GetCurrentFolder() --> Folder # Returns currently selected Folder. + SetCurrentFolder(Folder) --> Bool # Sets current folder by given Folder. + DeleteClips([clips]) --> Bool # Deletes specified clips or timeline mattes in the media pool + DeleteFolders([subfolders]) --> Bool # Deletes specified subfolders in the media pool + MoveClips([clips], targetFolder) --> Bool # Moves specified clips to target folder. + MoveFolders([folders], targetFolder) --> Bool # Moves specified folders to target folder. + GetClipMatteList(MediaPoolItem) --> [paths] # Get mattes for specified MediaPoolItem, as a list of paths to the matte files. + GetTimelineMatteList(Folder) --> [MediaPoolItems] # Get mattes in specified Folder, as list of MediaPoolItems. + DeleteClipMattes(MediaPoolItem, [paths]) --> Bool # Delete mattes based on their file paths, for specified MediaPoolItem. Returns True on success. + RelinkClips([MediaPoolItem], folderPath) --> Bool # Update the folder location of specified media pool clips with the specified folder path. + UnlinkClips([MediaPoolItem]) --> Bool # Unlink specified media pool clips. + ImportMedia([items...]) --> [MediaPoolItems] # Imports specified file/folder paths into current Media Pool folder. Input is an array of file/folder paths. Returns a list of the MediaPoolItems created. + ExportMetadata(fileName, [clips]) --> Bool # Exports metadata of specified clips to 'fileName' in CSV format. + # If no clips are specified, all clips from media pool will be used. + +Folder + GetClipList() --> [clips...] # Returns a list of clips (items) within the folder. + GetName() --> string # Returns the media folder name. + GetSubFolderList() --> [folders...] # Returns a list of subfolders in the folder. + +MediaPoolItem + GetName() --> string # Returns the clip name. + GetMetadata(metadataType=None) --> string|dict # Returns the metadata value for the key 'metadataType'. + # If no argument is specified, a dict of all set metadata properties is returned. + SetMetadata(metadataType, metadataValue) --> Bool # Sets the given metadata to metadataValue (string). Returns True if successful. + GetMediaId() --> string # Returns the unique ID for the MediaPoolItem. + AddMarker(frameId, color, name, note, duration, --> Bool # Creates a new marker at given frameId position and with given marker information. 'customData' is optional and helps to attach user specific data to the marker. + customData) + GetMarkers() --> {markers...} # Returns a dict (frameId -> {information}) of all markers and dicts with their information. + # Example of output format: {96.0: {'color': 'Green', 'duration': 1.0, 'note': '', 'name': 'Marker 1', 'customData': ''}, ...} + # In the above example - there is one 'Green' marker at offset 96 (position of the marker) + GetMarkerByCustomData(customData) --> {markers...} # Returns marker {information} for the first matching marker with specified customData. + UpdateMarkerCustomData(frameId, customData) --> Bool # Updates customData (string) for the marker at given frameId position. CustomData is not exposed via UI and is useful for scripting developer to attach any user specific data to markers. + GetMarkerCustomData(frameId) --> string # Returns customData string for the marker at given frameId position. + DeleteMarkersByColor(color) --> Bool # Delete all markers of the specified color from the media pool item. "All" as argument deletes all color markers. + DeleteMarkerAtFrame(frameNum) --> Bool # Delete marker at frame number from the media pool item. + DeleteMarkerByCustomData(customData) --> Bool # Delete first matching marker with specified customData. + AddFlag(color) --> Bool # Adds a flag with given color (string). + GetFlagList() --> [colors...] # Returns a list of flag colors assigned to the item. + ClearFlags(color) --> Bool # Clears the flag of the given color if one exists. An "All" argument is supported and clears all flags. + GetClipColor() --> string # Returns the item color as a string. + SetClipColor(colorName) --> Bool # Sets the item color based on the colorName (string). + ClearClipColor() --> Bool # Clears the item color. + GetClipProperty(propertyName=None) --> string|dict # Returns the property value for the key 'propertyName'. + # If no argument is specified, a dict of all clip properties is returned. Check the section below for more information. + SetClipProperty(propertyName, propertyValue) --> Bool # Sets the given property to propertyValue (string). Check the section below for more information. + LinkProxyMedia(propertyName) --> Bool # Links proxy media (absolute path) with the current clip. + UnlinkProxyMedia() --> Bool # Unlinks any proxy media associated with clip. + ReplaceClip(filePath) --> Bool # Replaces the underlying asset and metadata of MediaPoolItem with the specified absolute clip path. + +Timeline + GetName() --> string # Returns the timeline name. + SetName(timelineName) --> Bool # Sets the timeline name if timelineName (string) is unique. Returns True if successful. + GetStartFrame() --> int # Returns the frame number at the start of timeline. + GetEndFrame() --> int # Returns the frame number at the end of timeline. + GetTrackCount(trackType) --> int # Returns the number of tracks for the given track type ("audio", "video" or "subtitle"). + GetItemListInTrack(trackType, index) --> [items...] # Returns a list of timeline items on that track (based on trackType and index). 1 <= index <= GetTrackCount(trackType). + AddMarker(frameId, color, name, note, duration, --> Bool # Creates a new marker at given frameId position and with given marker information. 'customData' is optional and helps to attach user specific data to the marker. + customData) + GetMarkers() --> {markers...} # Returns a dict (frameId -> {information}) of all markers and dicts with their information. + # Example: a value of {96.0: {'color': 'Green', 'duration': 1.0, 'note': '', 'name': 'Marker 1', 'customData': ''}, ...} indicates a single green marker at timeline offset 96 + GetMarkerByCustomData(customData) --> {markers...} # Returns marker {information} for the first matching marker with specified customData. + UpdateMarkerCustomData(frameId, customData) --> Bool # Updates customData (string) for the marker at given frameId position. CustomData is not exposed via UI and is useful for scripting developer to attach any user specific data to markers. + GetMarkerCustomData(frameId) --> string # Returns customData string for the marker at given frameId position. + DeleteMarkersByColor(color) --> Bool # Deletes all timeline markers of the specified color. An "All" argument is supported and deletes all timeline markers. + DeleteMarkerAtFrame(frameNum) --> Bool # Deletes the timeline marker at the given frame number. + DeleteMarkerByCustomData(customData) --> Bool # Delete first matching marker with specified customData. + ApplyGradeFromDRX(path, gradeMode, item1, item2, ...)--> Bool # Loads a still from given file path (string) and applies grade to Timeline Items with gradeMode (int): 0 - "No keyframes", 1 - "Source Timecode aligned", 2 - "Start Frames aligned". + ApplyGradeFromDRX(path, gradeMode, [items]) --> Bool # Loads a still from given file path (string) and applies grade to Timeline Items with gradeMode (int): 0 - "No keyframes", 1 - "Source Timecode aligned", 2 - "Start Frames aligned". + GetCurrentTimecode() --> string # Returns a string timecode representation for the current playhead position, while on Cut, Edit, Color and Deliver pages. + GetCurrentVideoItem() --> item # Returns the current video timeline item. + GetCurrentClipThumbnailImage() --> {thumbnailData} # Returns a dict (keys "width", "height", "format" and "data") with data containing raw thumbnail image data (RGB 8-bit image data encoded in base64 format) for current media in the Color Page. + # An example of how to retrieve and interpret thumbnails is provided in 6_get_current_media_thumbnail.py in the Examples folder. + GetTrackName(trackType, trackIndex) --> string # Returns the track name for track indicated by trackType ("audio", "video" or "subtitle") and index. 1 <= trackIndex <= GetTrackCount(trackType). + SetTrackName(trackType, trackIndex, name) --> Bool # Sets the track name (string) for track indicated by trackType ("audio", "video" or "subtitle") and index. 1 <= trackIndex <= GetTrackCount(trackType). + DuplicateTimeline(timelineName) --> timeline # Duplicates the timeline and returns the created timeline, with the (optional) timelineName, on success. + CreateCompoundClip([timelineItems], {clipInfo}) --> timelineItem # Creates a compound clip of input timeline items with an optional clipInfo map: {"startTimecode" : "00:00:00:00", "name" : "Compound Clip 1"}. It returns the created timeline item. + CreateFusionClip([timelineItems]) --> timelineItem # Creates a Fusion clip of input timeline items. It returns the created timeline item. + Export(fileName, exportType, exportSubtype) --> Bool # Exports timeline to 'fileName' as per input exportType & exportSubtype format. + # exportType can be one of the following constants: + # resolve.EXPORT_AAF + # resolve.EXPORT_DRT + # resolve.EXPORT_EDL + # resolve.EXPORT_FCP_7_XML + # resolve.EXPORT_FCPXML_1_3 + # resolve.EXPORT_FCPXML_1_4 + # resolve.EXPORT_FCPXML_1_5 + # resolve.EXPORT_FCPXML_1_6 + # resolve.EXPORT_FCPXML_1_7 + # resolve.EXPORT_FCPXML_1_8 + # resolve.EXPORT_HDR_10_PROFILE_A + # resolve.EXPORT_HDR_10_PROFILE_B + # resolve.EXPORT_TEXT_CSV + # resolve.EXPORT_TEXT_TAB + # resolve.EXPORT_DOLBY_VISION_VER_2_9 + # resolve.EXPORT_DOLBY_VISION_VER_4_0 + # exportSubtype can be one of the following enums: + # resolve.EXPORT_NONE + # resolve.EXPORT_AAF_NEW + # resolve.EXPORT_AAF_EXISTING + # resolve.EXPORT_CDL + # resolve.EXPORT_SDL + # resolve.EXPORT_MISSING_CLIPS + # Please note that exportSubType is a required parameter for resolve.EXPORT_AAF and resolve.EXPORT_EDL. For rest of the exportType, exportSubtype is ignored. + # When exportType is resolve.EXPORT_AAF, valid exportSubtype values are resolve.EXPORT_AAF_NEW and resolve.EXPORT_AAF_EXISTING. + # When exportType is resolve.EXPORT_EDL, valid exportSubtype values are resolve.EXPORT_CDL, resolve.EXPORT_SDL, resolve.EXPORT_MISSING_CLIPS and resolve.EXPORT_NONE. + # Note: Replace 'resolve.' when using the constants above, if a different Resolve class instance name is used. + GetSetting(settingName) --> string # Returns value of timeline setting (indicated by settingName : string). Check the section below for more information. + SetSetting(settingName, settingValue) --> Bool # Sets timeline setting (indicated by settingName : string) to the value (settingValue : string). Check the section below for more information. + +TimelineItem + GetName() --> string # Returns the item name. + GetDuration() --> int # Returns the item duration. + GetEnd() --> int # Returns the end frame position on the timeline. + GetFusionCompCount() --> int # Returns number of Fusion compositions associated with the timeline item. + GetFusionCompByIndex(compIndex) --> fusionComp # Returns the Fusion composition object based on given index. 1 <= compIndex <= timelineItem.GetFusionCompCount() + GetFusionCompNameList() --> [names...] # Returns a list of Fusion composition names associated with the timeline item. + GetFusionCompByName(compName) --> fusionComp # Returns the Fusion composition object based on given name. + GetLeftOffset() --> int # Returns the maximum extension by frame for clip from left side. + GetRightOffset() --> int # Returns the maximum extension by frame for clip from right side. + GetStart() --> int # Returns the start frame position on the timeline. + AddMarker(frameId, color, name, note, duration, --> Bool # Creates a new marker at given frameId position and with given marker information. 'customData' is optional and helps to attach user specific data to the marker. + customData) + GetMarkers() --> {markers...} # Returns a dict (frameId -> {information}) of all markers and dicts with their information. + # Example: a value of {96.0: {'color': 'Green', 'duration': 1.0, 'note': '', 'name': 'Marker 1', 'customData': ''}, ...} indicates a single green marker at clip offset 96 + GetMarkerByCustomData(customData) --> {markers...} # Returns marker {information} for the first matching marker with specified customData. + UpdateMarkerCustomData(frameId, customData) --> Bool # Updates customData (string) for the marker at given frameId position. CustomData is not exposed via UI and is useful for scripting developer to attach any user specific data to markers. + GetMarkerCustomData(frameId) --> string # Returns customData string for the marker at given frameId position. + DeleteMarkersByColor(color) --> Bool # Delete all markers of the specified color from the timeline item. "All" as argument deletes all color markers. + DeleteMarkerAtFrame(frameNum) --> Bool # Delete marker at frame number from the timeline item. + DeleteMarkerByCustomData(customData) --> Bool # Delete first matching marker with specified customData. + AddFlag(color) --> Bool # Adds a flag with given color (string). + GetFlagList() --> [colors...] # Returns a list of flag colors assigned to the item. + ClearFlags(color) --> Bool # Clear flags of the specified color. An "All" argument is supported to clear all flags. + GetClipColor() --> string # Returns the item color as a string. + SetClipColor(colorName) --> Bool # Sets the item color based on the colorName (string). + ClearClipColor() --> Bool # Clears the item color. + AddFusionComp() --> fusionComp # Adds a new Fusion composition associated with the timeline item. + ImportFusionComp(path) --> fusionComp # Imports a Fusion composition from given file path by creating and adding a new composition for the item. + ExportFusionComp(path, compIndex) --> Bool # Exports the Fusion composition based on given index to the path provided. + DeleteFusionCompByName(compName) --> Bool # Deletes the named Fusion composition. + LoadFusionCompByName(compName) --> fusionComp # Loads the named Fusion composition as the active composition. + RenameFusionCompByName(oldName, newName) --> Bool # Renames the Fusion composition identified by oldName. + AddVersion(versionName, versionType) --> Bool # Adds a new color version for a video clipbased on versionType (0 - local, 1 - remote). + DeleteVersionByName(versionName, versionType) --> Bool # Deletes a color version by name and versionType (0 - local, 1 - remote). + LoadVersionByName(versionName, versionType) --> Bool # Loads a named color version as the active version. versionType: 0 - local, 1 - remote. + RenameVersionByName(oldName, newName, versionType)--> Bool # Renames the color version identified by oldName and versionType (0 - local, 1 - remote). + GetVersionNameList(versionType) --> [names...] # Returns a list of all color versions for the given versionType (0 - local, 1 - remote). + GetMediaPoolItem() --> MediaPoolItem # Returns the media pool item corresponding to the timeline item if one exists. + GetStereoConvergenceValues() --> {keyframes...} # Returns a dict (offset -> value) of keyframe offsets and respective convergence values. + GetStereoLeftFloatingWindowParams() --> {keyframes...} # For the LEFT eye -> returns a dict (offset -> dict) of keyframe offsets and respective floating window params. Value at particular offset includes the left, right, top and bottom floating window values. + GetStereoRightFloatingWindowParams() --> {keyframes...} # For the RIGHT eye -> returns a dict (offset -> dict) of keyframe offsets and respective floating window params. Value at particular offset includes the left, right, top and bottom floating window values. + SetLUT(nodeIndex, lutPath) --> Bool # Sets LUT on the node mapping the node index provided, 1 <= nodeIndex <= total number of nodes. + # The lutPath can be an absolute path, or a relative path (based off custom LUT paths or the master LUT path). + # The operation is successful for valid lut paths that Resolve has already discovered (see Project.RefreshLUTList). + SetCDL([CDL map]) --> Bool # Keys of map are: "NodeIndex", "Slope", "Offset", "Power", "Saturation", where 1 <= NodeIndex <= total number of nodes. + # Example python code - SetCDL({"NodeIndex" : "1", "Slope" : "0.5 0.4 0.2", "Offset" : "0.4 0.3 0.2", "Power" : "0.6 0.7 0.8", "Saturation" : "0.65"}) + AddTake(mediaPoolItem, startFrame=0, endFrame)=0 --> Bool # Adds mediaPoolItem as a new take. Initializes a take selector for the timeline item if needed. By default, the whole clip is added. startFrame and endFrame can be specified as extents. + GetSelectedTakeIndex() --> int # Returns the index of the currently selected take, or 0 if the clip is not a take selector. + GetTakesCount() --> int # Returns the number of takes in take selector, or 0 if the clip is not a take selector. + GetTakeByIndex(idx) --> {takeInfo...} # Returns a dict (keys "startFrame", "endFrame" and "mediaPoolItem") with take info for specified index. + DeleteTakeByIndex(idx) --> Bool # Deletes a take by index, 1 <= idx <= number of takes. + SelectTakeByIndex(idx) --> Bool # Selects a take by index, 1 <= idx <= number of takes. + FinalizeTake() --> Bool # Finalizes take selection. + CopyGrades([tgtTimelineItems]) --> Bool # Copies the current grade to all the items in tgtTimelineItems list. Returns True on success and False if any error occured. + + +List and Dict Data Structures +----------------------------- +Beside primitive data types, Resolve's Python API mainly uses list and dict data structures. Lists are denoted by [ ... ] and dicts are denoted by { ... } above. +As Lua does not support list and dict data structures, the Lua API implements "list" as a table with indices, e.g. { [1] = listValue1, [2] = listValue2, ... }. +Similarly the Lua API implements "dict" as a table with the dictionary key as first element, e.g. { [dictKey1] = dictValue1, [dictKey2] = dictValue2, ... }. + + +Looking up Project and Clip properties +-------------------------------------- +This section covers additional notes for the functions "Project:GetSetting", "Project:SetSetting", "Timeline:GetSetting", "Timeline:SetSetting", "MediaPoolItem:GetClipProperty" and +"MediaPoolItem:SetClipProperty". These functions are used to get and set properties otherwise available to the user through the Project Settings and the Clip Attributes dialogs. + +The functions follow a key-value pair format, where each property is identified by a key (the settingName or propertyName parameter) and possesses a value (typically a text value). Keys and values are +designed to be easily correlated with parameter names and values in the Resolve UI. Explicitly enumerated values for some parameters are listed below. + +Some properties may be read only - these include intrinsic clip properties like date created or sample rate, and properties that can be disabled in specific application contexts (e.g. custom colorspaces +in an ACES workflow, or output sizing parameters when behavior is set to match timeline) + +Getting values: +Invoke "Project:GetSetting", "Timeline:GetSetting" or "MediaPoolItem:GetClipProperty" with the appropriate property key. To get a snapshot of all queryable properties (keys and values), you can call +"Project:GetSetting", "Timeline:GetSetting" or "MediaPoolItem:GetClipProperty" without parameters (or with a NoneType or a blank property key). Using specific keys to query individual properties will +be faster. Note that getting a property using an invalid key will return a trivial result. + +Setting values: +Invoke "Project:SetSetting", "Timeline:SetSetting" or "MediaPoolItem:SetClipProperty" with the appropriate property key and a valid value. When setting a parameter, please check the return value to +ensure the success of the operation. You can troubleshoot the validity of keys and values by setting the desired result from the UI and checking property snapshots before and after the change. + +The following Project properties have specifically enumerated values: +"superScale" - the property value is an enumerated integer between 0 and 3 with these meanings: 0=Auto, 1=no scaling, and 2, 3 and 4 represent the Super Scale multipliers 2x, 3x and 4x. +Affects: +• x = Project:GetSetting('superScale') and Project:SetSetting('superScale', x) + +"timelineFrameRate" - the property value is one of the frame rates available to the user in project settings under "Timeline frame rate" option. Drop Frame can be configured for supported frame rates + by appending the frame rate with "DF", e.g. "29.97 DF" will enable drop frame and "29.97" will disable drop frame +Affects: +• x = Project:GetSetting('timelineFrameRate') and Project:SetSetting('timelineFrameRate', x) + +The following Clip properties have specifically enumerated values: +"superScale" - the property value is an enumerated integer between 1 and 3 with these meanings: 1=no scaling, and 2, 3 and 4 represent the Super Scale multipliers 2x, 3x and 4x. +Affects: +• x = MediaPoolItem:GetClipProperty('Super Scale') and MediaPoolItem:SetClipProperty('Super Scale', x) + + +Deprecated Resolve API Functions +-------------------------------- +The following API functions are deprecated. + +ProjectManager + GetProjectsInCurrentFolder() --> {project names...} # Returns a dict of project names in current folder. + GetFoldersInCurrentFolder() --> {folder names...} # Returns a dict of folder names in current folder. + +Project + GetPresets() --> {presets...} # Returns a dict of presets and their information. + GetRenderJobs() --> {render jobs...} # Returns a dict of render jobs and their information. + GetRenderPresets() --> {presets...} # Returns a dict of render presets and their information. + +MediaStorage + GetMountedVolumes() --> {paths...} # Returns a dict of folder paths corresponding to mounted volumes displayed in Resolve’s Media Storage. + GetSubFolders(folderPath) --> {paths...} # Returns a dict of folder paths in the given absolute folder path. + GetFiles(folderPath) --> {paths...} # Returns a dict of media and file listings in the given absolute folder path. Note that media listings may be logically consolidated entries. + AddItemsToMediaPool(item1, item2, ...) --> {clips...} # Adds specified file/folder paths from Media Storage into current Media Pool folder. Input is one or more file/folder paths. Returns a dict of the MediaPoolItems created. + AddItemsToMediaPool([items...]) --> {clips...} # Adds specified file/folder paths from Media Storage into current Media Pool folder. Input is an array of file/folder paths. Returns a dict of the MediaPoolItems created. + +Folder + GetClips() --> {clips...} # Returns a dict of clips (items) within the folder. + GetSubFolders() --> {folders...} # Returns a dict of subfolders in the folder. + +MediaPoolItem + GetFlags() --> {colors...} # Returns a dict of flag colors assigned to the item. + +Timeline + GetItemsInTrack(trackType, index) --> {items...} # Returns a dict of Timeline items on the video or audio track (based on trackType) at specified + +TimelineItem + GetFusionCompNames() --> {names...} # Returns a dict of Fusion composition names associated with the timeline item. + GetFlags() --> {colors...} # Returns a dict of flag colors assigned to the item. + GetVersionNames(versionType) --> {names...} # Returns a dict of version names by provided versionType: 0 - local, 1 - remote. + + +Unsupported Resolve API Functions +--------------------------------- +The following API (functions and paraameters) are no longer supported. + +Project + StartRendering(index1, index2, ...) --> Bool # Please use unique job ids (string) instead of indices. + StartRendering([idxs...]) --> Bool # Please use unique job ids (string) instead of indices. + DeleteRenderJobByIndex(idx) --> Bool # Please use unique job ids (string) instead of indices. + GetRenderJobStatus(idx) --> {status info} # Please use unique job ids (string) instead of indices. + GetSetting and SetSetting --> {} # settingName "videoMonitorUseRec601For422SDI" is no longer supported. + # Please use "videoMonitorUseMatrixOverrideFor422SDI" and "videoMonitorMatrixOverrideFor422SDI" instead. diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index 08e7f325b1..3db6584dda 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -18,6 +18,7 @@ from .lib import ( get_project_manager, get_current_project, get_current_sequence, + add_clip_to_timeline, get_video_track_names, get_current_track_items, get_track_item_by_name, @@ -75,6 +76,7 @@ __all__ = [ "get_project_manager", "get_current_project", "get_current_sequence", + "add_clip_to_timeline", "get_video_track_names", "get_current_track_items", "get_track_item_by_name", diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index fbc19166a4..d2fcf5d8f0 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -29,6 +29,9 @@ self.pype_marker_duration = 1 self.pype_marker_color = "Mint" self.temp_marker_frame = None +# Pype default timeline +self.pype_timeline_name = "PypeTimeline" + def get_project_manager(): from . import bmdvr @@ -44,14 +47,42 @@ def get_current_project(): return self.project_manager.GetCurrentProject() -def get_current_sequence(): +def get_current_sequence(new=False): # get current project project = get_current_project() + if new: + pmanager = get_project_manager() + new_timeline = pmanager.CreateEmptyTimeline(self.pype_timeline_name) + project.SetCurrentTimeline(new_timeline) + return project.GetCurrentTimeline() -def get_video_track_names(): +def add_clip_to_timeline(mediapool_item: object, frame_start: int, + frame_end: int) -> bool: + """ + Adding mediaPoolItem to current timeline. + + Args: + mediapool_item (resolve.MediaPoolItem): resolve object + frame_start (int): first frame number + frame_end (int): last frame number + + Returns: + bool: True if successful + + """ + pmanager = get_project_manager() + # Add input clip to the current timeline: + return pmanager.AppendToTimeline([{ + "mediaPoolItem": mediapool_item, + "startFrame": frame_start, + "endFrame": frame_end + }]) + + +def get_video_track_names() -> list: tracks = list() track_type = "video" sequence = get_current_sequence() @@ -60,6 +91,7 @@ def get_video_track_names(): selected_track_count = sequence.GetTrackCount(track_type) # loop all tracks and get items + track_index: int for track_index in range(1, (int(selected_track_count) + 1)): track_name = sequence.GetTrackName("video", track_index) tracks.append(track_name) @@ -68,9 +100,9 @@ def get_video_track_names(): def get_current_track_items( - filter=False, - track_type=None, - selecting_color=None): + filter: bool = False, + track_type: str = None, + selecting_color: str = None) -> list: """ Gets all available current timeline track items """ track_type = track_type or "video" @@ -132,10 +164,10 @@ def get_track_item_pype_tag(track_item): Get pype track item tag created by creator or loader plugin. Attributes: - trackItem (resolve.TimelineItem): hiero object + trackItem (resolve.TimelineItem): resolve object Returns: - hiero.core.Tag: hierarchy, orig clip attributes + dict: pype tag data """ return_tag = None diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index fa4559efac..115910b206 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -336,6 +336,236 @@ class SequenceLoader(api.Loader): pass +class ClipLoader: + + active_bin = None + data = dict() + + def __init__(self, cls, context, **options): + """ Initialize object + + Arguments: + cls (avalon.api.Loader): plugin object + context (dict): loader plugin context + options (dict)[optional]: possible keys: + projectBinPath: "path/to/binItem" + + """ + self.__dict__.update(cls.__dict__) + self.context = context + self.active_project = lib.get_current_project() + + # try to get value from options or evaluate key value for `handles` + self.with_handles = options.get("handles") or bool( + options.get("handles") is True) + # try to get value from options or evaluate key value for `load_how` + self.sequencial_load = options.get("sequencially") or bool( + "Sequentially in order" in options.get("load_how", "")) + # try to get value from options or evaluate key value for `load_to` + self.new_sequence = options.get("newSequence") or bool( + "New timeline" in options.get("load_to", "")) + + assert self._populate_data(), str( + "Cannot Load selected data, look into database " + "or call your supervisor") + + # inject asset data to representation dict + self._get_asset_data() + print("__init__ self.data: `{}`".format(self.data)) + + # add active components to class + if self.new_sequence: + if options.get("timeline"): + # if multiselection is set then use options sequence + self.active_timeline = options["timeline"] + else: + # create new sequence + self.active_timeline = lib.get_current_sequence(new=True) + else: + self.active_timeline = lib.get_current_sequence() + + if options.get("track"): + # if multiselection is set then use options track + self.active_track = options["track"] + else: + self.active_track = lib.get_current_track( + self.active_timeline, self.data["track_name"]) + + def _populate_data(self): + """ Gets context and convert it to self.data + data structure: + { + "name": "assetName_subsetName_representationName" + "path": "path/to/file/created/by/get_repr..", + "binPath": "projectBinPath", + } + """ + # create name + repr = self.context["representation"] + repr_cntx = repr["context"] + asset = str(repr_cntx["asset"]) + subset = str(repr_cntx["subset"]) + representation = str(repr_cntx["representation"]) + self.data["clip_name"] = "_".join([asset, subset, representation]) + self.data["track_name"] = "_".join([subset, representation]) + self.data["versionData"] = self.context["version"]["data"] + # gets file path + file = self.fname + if not file: + repr_id = repr["_id"] + print( + "Representation id `{}` is failing to load".format(repr_id)) + return None + self.data["path"] = file.replace("\\", "/") + + # convert to hashed path + if repr_cntx.get("frame"): + self._fix_path_hashes() + + # solve project bin structure path + hierarchy = str("/".join(( + "Loader", + repr_cntx["hierarchy"].replace("\\", "/"), + asset + ))) + + self.data["binPath"] = hierarchy + + return True + + def _fix_path_hashes(self): + """ Convert file path where it is needed padding with hashes + """ + file = self.data["path"] + if "#" not in file: + frame = self.context["representation"]["context"].get("frame") + padding = len(frame) + file = file.replace(frame, "#" * padding) + self.data["path"] = file + + def _get_asset_data(self): + """ Get all available asset data + + joint `data` key with asset.data dict into the representaion + + """ + asset_name = self.context["representation"]["context"]["asset"] + self.data["assetData"] = pype.get_asset(asset_name)["data"] + + def _make_track_item(self, source_bin_item, audio=False): + """ Create track item with """ + + clip = source_bin_item.activeItem() + + # add to track as clip item + if not audio: + track_item = hiero.core.TrackItem( + self.data["clip_name"], hiero.core.TrackItem.kVideo) + else: + track_item = hiero.core.TrackItem( + self.data["clip_name"], hiero.core.TrackItem.kAudio) + + track_item.setSource(clip) + track_item.setSourceIn(self.handle_start) + track_item.setTimelineIn(self.timeline_in) + track_item.setSourceOut(self.media_duration - self.handle_end) + track_item.setTimelineOut(self.timeline_out) + track_item.setPlaybackSpeed(1) + self.active_track.addTrackItem(track_item) + + return track_item + + def load(self): + # create project bin for the media to be imported into + self.active_bin = lib.create_bin(self.data["binPath"]) + + # create mediaItem in active project bin + # create clip media + self.media = hiero.core.MediaSource(self.data["path"]) + self.media_duration = int(self.media.duration()) + + # get handles + self.handle_start = self.data["versionData"].get("handleStart") + self.handle_end = self.data["versionData"].get("handleEnd") + if self.handle_start is None: + self.handle_start = int(self.data["assetData"]["handleStart"]) + if self.handle_end is None: + self.handle_end = int(self.data["assetData"]["handleEnd"]) + + if self.sequencial_load: + last_track_item = lib.get_track_items( + sequence_name=self.active_timeline.name(), + track_name=self.active_track.name()) + if len(last_track_item) == 0: + last_timeline_out = 0 + else: + last_track_item = last_track_item[-1] + last_timeline_out = int(last_track_item.timelineOut()) + 1 + self.timeline_in = last_timeline_out + self.timeline_out = last_timeline_out + int( + self.data["assetData"]["clipOut"] + - self.data["assetData"]["clipIn"]) + else: + self.timeline_in = int(self.data["assetData"]["clipIn"]) + self.timeline_out = int(self.data["assetData"]["clipOut"]) + + # check if slate is included + # either in version data families or by calculating frame diff + slate_on = next( + # check iterate if slate is in families + (f for f in self.context["version"]["data"]["families"] + if "slate" in f), + # if nothing was found then use default None + # so other bool could be used + None) or bool((( + # put together duration of clip attributes + self.timeline_out - self.timeline_in + 1) \ + + self.handle_start \ + + self.handle_end + # and compare it with meda duration + ) > self.media_duration) + + print("__ slate_on: `{}`".format(slate_on)) + + # if slate is on then remove the slate frame from begining + if slate_on: + self.media_duration -= 1 + self.handle_start += 1 + + # create Clip from Media + clip = hiero.core.Clip(self.media) + clip.setName(self.data["clip_name"]) + + # add Clip to bin if not there yet + if self.data["clip_name"] not in [ + b.name() for b in self.active_bin.items()]: + bin_item = hiero.core.BinItem(clip) + self.active_bin.addItem(bin_item) + + # just make sure the clip is created + # there were some cases were hiero was not creating it + source_bin_item = None + for item in self.active_bin.items(): + if self.data["clip_name"] in item.name(): + source_bin_item = item + if not source_bin_item: + print("Problem with created Source clip: `{}`".format( + self.data["clip_name"])) + + # include handles + if self.with_handles: + self.timeline_in -= self.handle_start + self.timeline_out += self.handle_end + self.handle_start = 0 + self.handle_end = 0 + + # make track item from source in bin as item + track_item = self._make_track_item(source_bin_item) + + print("Loading clips: `{}`".format(self.data["clip_name"])) + return track_item + + class Creator(api.Creator): """Creator class wrapper """ diff --git a/pype/plugins/resolve/load/load_sequence.py b/pype/plugins/resolve/load/load_sequence.py index 670a11ae88..489355b72d 100644 --- a/pype/plugins/resolve/load/load_sequence.py +++ b/pype/plugins/resolve/load/load_sequence.py @@ -18,7 +18,7 @@ class LoadClip(resolve.SequenceLoader): color = "orange" # for loader multiselection - sequence = None + timeline = None track = None # presets @@ -28,9 +28,9 @@ class LoadClip(resolve.SequenceLoader): def load(self, context, name, namespace, options): # in case loader uses multiselection - if self.track and self.sequence: + if self.track and self.timeline: options.update({ - "sequence": self.sequence, + "timeline": self.timeline, "track": self.track }) From 5174eed16dae87a95d289301b4951f84d0dfcd42 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Jan 2021 19:12:27 +0100 Subject: [PATCH 05/17] resolve: load sequence wip --- pype/hosts/resolve/RESOLVE_API_README_NEW.txt | 28 +- pype/hosts/resolve/__init__.py | 8 +- pype/hosts/resolve/lib.py | 384 ++++++++++++------ pype/hosts/resolve/otio/utils.py | 15 +- pype/hosts/resolve/plugin.py | 7 +- pype/hosts/resolve/utility_scripts/test.py | 31 +- 6 files changed, 303 insertions(+), 170 deletions(-) diff --git a/pype/hosts/resolve/RESOLVE_API_README_NEW.txt b/pype/hosts/resolve/RESOLVE_API_README_NEW.txt index bcd02a62ae..a24a053cd7 100644 --- a/pype/hosts/resolve/RESOLVE_API_README_NEW.txt +++ b/pype/hosts/resolve/RESOLVE_API_README_NEW.txt @@ -26,7 +26,7 @@ Using a script -------------- DaVinci Resolve needs to be running for a script to be invoked. -For a Resolve script to be executed from an external folder, the script needs to know of the API location. +For a Resolve script to be executed from an external folder, the script needs to know of the API location. You may need to set the these environment variables to allow for your Python installation to pick up the appropriate dependencies as shown below: Mac OS X: @@ -47,9 +47,9 @@ You may need to set the these environment variables to allow for your Python ins As with Fusion scripts, Resolve scripts can also be invoked via the menu and the Console. -On startup, DaVinci Resolve scans the subfolders in the directories shown below and enumerates the scripts found in the Workspace application menu under Scripts. +On startup, DaVinci Resolve scans the subfolders in the directories shown below and enumerates the scripts found in the Workspace application menu under Scripts. Place your script under Utility to be listed in all pages, under Comp or Tool to be available in the Fusion page or under folders for individual pages (Edit, Color or Deliver). Scripts under Deliver are additionally listed under render jobs. -Placing your script here and invoking it from the menu is the easiest way to use scripts. +Placing your script here and invoking it from the menu is the easiest way to use scripts. Mac OS X: - All users: /Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts - Specific user: /Users//Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts @@ -137,7 +137,7 @@ Project StartRendering(jobId1, jobId2, ...) --> Bool # Starts rendering jobs indicated by the input job ids. StartRendering([jobIds...], isInteractiveMode=False) --> Bool # Starts rendering jobs indicated by the input job ids. # The optional "isInteractiveMode", when set, enables error feedback in the UI during rendering. - StartRendering(isInteractiveMode=False) --> Bool # Starts rendering all queued render jobs. + StartRendering(isInteractiveMode=False) --> Bool # Starts rendering all queued render jobs. # The optional "isInteractiveMode", when set, enables error feedback in the UI during rendering. StopRendering() --> None # Stops any current render processes. IsRenderingInProgress() --> Bool # Returns True if rendering is in progress. @@ -245,7 +245,7 @@ MediaPoolItem GetClipColor() --> string # Returns the item color as a string. SetClipColor(colorName) --> Bool # Sets the item color based on the colorName (string). ClearClipColor() --> Bool # Clears the item color. - GetClipProperty(propertyName=None) --> string|dict # Returns the property value for the key 'propertyName'. + GetClipProperty(propertyName=None) --> string|dict # Returns the property value for the key 'propertyName'. # If no argument is specified, a dict of all clip properties is returned. Check the section below for more information. SetClipProperty(propertyName, propertyValue) --> Bool # Sets the given property to propertyValue (string). Check the section below for more information. LinkProxyMedia(propertyName) --> Bool # Links proxy media (absolute path) with the current clip. @@ -335,7 +335,7 @@ TimelineItem DeleteMarkerByCustomData(customData) --> Bool # Delete first matching marker with specified customData. AddFlag(color) --> Bool # Adds a flag with given color (string). GetFlagList() --> [colors...] # Returns a list of flag colors assigned to the item. - ClearFlags(color) --> Bool # Clear flags of the specified color. An "All" argument is supported to clear all flags. + ClearFlags(color) --> Bool # Clear flags of the specified color. An "All" argument is supported to clear all flags. GetClipColor() --> string # Returns the item color as a string. SetClipColor(colorName) --> Bool # Sets the item color based on the colorName (string). ClearClipColor() --> Bool # Clears the item color. @@ -378,7 +378,7 @@ Similarly the Lua API implements "dict" as a table with the dictionary key as fi Looking up Project and Clip properties -------------------------------------- -This section covers additional notes for the functions "Project:GetSetting", "Project:SetSetting", "Timeline:GetSetting", "Timeline:SetSetting", "MediaPoolItem:GetClipProperty" and +This section covers additional notes for the functions "Project:GetSetting", "Project:SetSetting", "Timeline:GetSetting", "Timeline:SetSetting", "MediaPoolItem:GetClipProperty" and "MediaPoolItem:SetClipProperty". These functions are used to get and set properties otherwise available to the user through the Project Settings and the Clip Attributes dialogs. The functions follow a key-value pair format, where each property is identified by a key (the settingName or propertyName parameter) and possesses a value (typically a text value). Keys and values are @@ -387,13 +387,13 @@ designed to be easily correlated with parameter names and values in the Resolve Some properties may be read only - these include intrinsic clip properties like date created or sample rate, and properties that can be disabled in specific application contexts (e.g. custom colorspaces in an ACES workflow, or output sizing parameters when behavior is set to match timeline) -Getting values: -Invoke "Project:GetSetting", "Timeline:GetSetting" or "MediaPoolItem:GetClipProperty" with the appropriate property key. To get a snapshot of all queryable properties (keys and values), you can call -"Project:GetSetting", "Timeline:GetSetting" or "MediaPoolItem:GetClipProperty" without parameters (or with a NoneType or a blank property key). Using specific keys to query individual properties will +Getting values: +Invoke "Project:GetSetting", "Timeline:GetSetting" or "MediaPoolItem:GetClipProperty" with the appropriate property key. To get a snapshot of all queryable properties (keys and values), you can call +"Project:GetSetting", "Timeline:GetSetting" or "MediaPoolItem:GetClipProperty" without parameters (or with a NoneType or a blank property key). Using specific keys to query individual properties will be faster. Note that getting a property using an invalid key will return a trivial result. -Setting values: -Invoke "Project:SetSetting", "Timeline:SetSetting" or "MediaPoolItem:SetClipProperty" with the appropriate property key and a valid value. When setting a parameter, please check the return value to +Setting values: +Invoke "Project:SetSetting", "Timeline:SetSetting" or "MediaPoolItem:SetClipProperty" with the appropriate property key and a valid value. When setting a parameter, please check the return value to ensure the success of the operation. You can troubleshoot the validity of keys and values by setting the desired result from the UI and checking property snapshots before and after the change. The following Project properties have specifically enumerated values: @@ -401,7 +401,7 @@ The following Project properties have specifically enumerated values: Affects: • x = Project:GetSetting('superScale') and Project:SetSetting('superScale', x) -"timelineFrameRate" - the property value is one of the frame rates available to the user in project settings under "Timeline frame rate" option. Drop Frame can be configured for supported frame rates +"timelineFrameRate" - the property value is one of the frame rates available to the user in project settings under "Timeline frame rate" option. Drop Frame can be configured for supported frame rates by appending the frame rate with "DF", e.g. "29.97 DF" will enable drop frame and "29.97" will disable drop frame Affects: • x = Project:GetSetting('timelineFrameRate') and Project:SetSetting('timelineFrameRate', x) @@ -457,5 +457,5 @@ Project StartRendering([idxs...]) --> Bool # Please use unique job ids (string) instead of indices. DeleteRenderJobByIndex(idx) --> Bool # Please use unique job ids (string) instead of indices. GetRenderJobStatus(idx) --> {status info} # Please use unique job ids (string) instead of indices. - GetSetting and SetSetting --> {} # settingName "videoMonitorUseRec601For422SDI" is no longer supported. + GetSetting and SetSetting --> {} # settingName "videoMonitorUseRec601For422SDI" is no longer supported. # Please use "videoMonitorUseMatrixOverrideFor422SDI" and "videoMonitorMatrixOverrideFor422SDI" instead. diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index 3db6584dda..fd1efcc6e1 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -18,16 +18,14 @@ from .lib import ( get_project_manager, get_current_project, get_current_sequence, - add_clip_to_timeline, + create_bin, get_video_track_names, get_current_track_items, - get_track_item_by_name, get_track_item_pype_tag, set_track_item_pype_tag, imprint, set_publish_attribute, get_publish_attribute, - create_current_sequence_media_bin, create_compound_clip, swap_clips, get_pype_clip_metadata, @@ -76,16 +74,14 @@ __all__ = [ "get_project_manager", "get_current_project", "get_current_sequence", - "add_clip_to_timeline", + "create_bin", "get_video_track_names", "get_current_track_items", - "get_track_item_by_name", "get_track_item_pype_tag", "set_track_item_pype_tag", "imprint", "set_publish_attribute", "get_publish_attribute", - "create_current_sequence_media_bin", "create_compound_clip", "swap_clips", "get_pype_clip_metadata", diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index d2fcf5d8f0..7b6325b1b6 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -1,6 +1,8 @@ import sys import json import re +import os +import contextlib from opentimelineio import opentime import pype @@ -12,6 +14,7 @@ log = Logger().get_logger(__name__) self = sys.modules[__name__] self.project_manager = None +self.media_storage = None # Pype sequencial rename variables self.rename_index = 0 @@ -33,6 +36,40 @@ self.temp_marker_frame = None self.pype_timeline_name = "PypeTimeline" +@contextlib.contextmanager +def maintain_current_timeline(to_timeline: object, + from_timeline: object = None): + """Maintain current timeline selection during context + + Attributes: + from_timeline (resolve.Timeline)[optional]: + Example: + >>> print(from_timeline.GetName()) + timeline1 + >>> print(to_timeline.GetName()) + timeline2 + + >>> with maintain_current_timeline(to_timeline): + ... print(get_current_sequence().GetName()) + timeline2 + + >>> print(get_current_sequence().GetName()) + timeline1 + """ + project = get_current_project() + working_timeline = from_timeline or project.GetCurrentTimeline() + + # swith to the input timeline + project.SetCurrentTimeline(to_timeline) + + try: + # do a work + yield + finally: + # put the original working timeline to context + project.SetCurrentTimeline(working_timeline) + + def get_project_manager(): from . import bmdvr if not self.project_manager: @@ -40,6 +77,13 @@ def get_project_manager(): return self.project_manager +def get_media_storage(): + from . import bmdvr + if not self.media_storage: + self.media_storage = bmdvr.GetMediaStorage() + return self.media_storage + + def get_current_project(): # initialize project manager get_project_manager() @@ -52,34 +96,182 @@ def get_current_sequence(new=False): project = get_current_project() if new: - pmanager = get_project_manager() - new_timeline = pmanager.CreateEmptyTimeline(self.pype_timeline_name) + media_pool = project.GetMediaPool() + new_timeline = media_pool.CreateEmptyTimeline(self.pype_timeline_name) project.SetCurrentTimeline(new_timeline) return project.GetCurrentTimeline() -def add_clip_to_timeline(mediapool_item: object, frame_start: int, - frame_end: int) -> bool: +def create_bin(name: str, root: object = None) -> object: """ - Adding mediaPoolItem to current timeline. + Create media pool's folder. + + Return folder object and if the name does not exist it will create a new. + If the input name is with forward or backward slashes then it will create + all parents and return the last child bin object Args: - mediapool_item (resolve.MediaPoolItem): resolve object - frame_start (int): first frame number - frame_end (int): last frame number + name (str): name of folder / bin, or hierarchycal name "parent/name" + root (resolve.Folder)[optional]: root folder / bin object Returns: - bool: True if successful - + object: resolve.Folder """ - pmanager = get_project_manager() - # Add input clip to the current timeline: - return pmanager.AppendToTimeline([{ - "mediaPoolItem": mediapool_item, - "startFrame": frame_start, - "endFrame": frame_end - }]) + # get all variables + media_pool = get_current_project().GetMediaPool() + root_bin = root or media_pool.GetRootFolder() + + # create hierarchy of bins in case there is slash in name + if "/" in name.replace("\\", "/"): + child_bin = None + for bname in name.split("/"): + child_bin = create_bin(bname, child_bin or root_bin) + if child_bin: + return child_bin + else: + created_bin = None + for subfolder in root_bin.GetSubFolderList(): + if subfolder.GetName() in name: + created_bin = subfolder + + if not created_bin: + new_folder = media_pool.AddSubFolder(root_bin, name) + media_pool.SetCurrentFolder(new_folder) + else: + media_pool.SetCurrentFolder(created_bin) + + return media_pool.GetCurrentFolder() + + +def create_media_pool_item(fpath: str, + root: object = None) -> object: + """ + Create media pool item. + + Args: + fpath (str): absolute path to a file + root (resolve.Folder)[optional]: root folder / bin object + + Returns: + object: resolve.MediaPoolItem + """ + # get all variables + media_storage = get_media_storage() + media_pool = get_current_project().GetMediaPool() + root_bin = root or media_pool.GetRootFolder() + + # try to search in bin if the clip does not exist + existing_mpi = get_media_pool_item(fpath, root_bin) + + if not existing_mpi: + media_pool_item = media_storage.AddItemsToMediaPool(fpath) + # pop the returned dict on first item as resolve data object is such + return media_pool_item.pop(1.0) + else: + return existing_mpi + + +def get_media_pool_item(fpath, root: object = None) -> object: + """ + Return clip if found in folder with use of input file path. + + Args: + fpath (str): absolute path to a file + root (resolve.Folder)[optional]: root folder / bin object + + Returns: + object: resolve.MediaPoolItem + """ + media_pool = get_current_project().GetMediaPool() + root = root or media_pool.GetRootFolder() + fname = os.path.basename(fpath) + + for _mpi in root.GetClipList(): + _mpi_name = _mpi.GetClipProperty("File Name")["File Name"] + _mpi_name = get_reformated_path(_mpi_name, first=True) + if fname in _mpi_name: + return _mpi + return None + + +def create_timeline_item(media_pool_item: object, + timeline: object = None, + source_start: int = None, + source_end: int = None) -> object: + """ + Add media pool item to current or defined timeline. + + Args: + media_pool_item (resolve.MediaPoolItem): resolve's object + timeline (resolve.Timeline)[optional]: resolve's object + source_start (int)[optional]: media source input frame (sequence frame) + source_end (int)[optional]: media source output frame (sequence frame) + + Returns: + object: resolve.TimelineItem + """ + # get all variables + project = get_current_project() + media_pool = project.GetMediaPool() + clip_property = media_pool_item.GetClipProperty() + clip_name = clip_property["File Name"] + timeline = timeline or get_current_sequence() + source_start = source_start or 1003 + source_end = source_end or 1005 + + # if timeline was used then switch it to current timeline + with maintain_current_timeline(timeline): + # Add input mediaPoolItem to clip data + clip_data = {"mediaPoolItem": media_pool_item} + + # add source time range if input was given + if source_start is not None: + clip_data.update({"startFrame": source_start}) + if source_end is not None: + clip_data.update({"endFrame": source_end}) + + print(clip_data) + # add to timeline + media_pool.AppendToTimeline([clip_data]) + + output_timeline_item = get_timeline_item( + media_pool_item, timeline) + + assert output_timeline_item, AssertionError( + "Track Item with name `{}` doesnt exist on the timeline: `{}`".format( + clip_name, timeline.GetName() + )) + return output_timeline_item + + +def get_timeline_item(media_pool_item: object, + timeline: object = None) -> object: + """ + Returns clips related to input mediaPoolItem. + + Args: + media_pool_item (resolve.MediaPoolItem): resolve's object + timeline (resolve.Timeline)[optional]: resolve's object + + Returns: + object: resolve.TimelineItem + """ + clip_property = media_pool_item.GetClipProperty() + clip_name = clip_property["File Name"] + output_timeline_item = None + timeline = timeline or get_current_sequence() + + with maintain_current_timeline(timeline): + # search the timeline for the added clip + + for _ti_data in get_current_track_items(): + _ti_clip = _ti_data["clip"]["item"] + _ti_clip_property = _ti_clip.GetMediaPoolItem().GetClipProperty() + if clip_name in _ti_clip_property["File Name"]: + output_timeline_item = _ti_clip + + return output_timeline_item def get_video_track_names() -> list: @@ -102,6 +294,7 @@ def get_video_track_names() -> list: def get_current_track_items( filter: bool = False, track_type: str = None, + track_name: str = None, selecting_color: str = None) -> list: """ Gets all available current timeline track items """ @@ -117,7 +310,13 @@ def get_current_track_items( # loop all tracks and get items _clips = dict() for track_index in range(1, (int(selected_track_count) + 1)): - track_name = sequence.GetTrackName(track_type, track_index) + _track_name = sequence.GetTrackName(track_type, track_index) + + # filter out all unmathed track names + if track_name: + if _track_name not in track_name: + continue + track_track_items = sequence.GetItemListInTrack( track_type, track_index) _clips[track_index] = track_track_items @@ -126,7 +325,7 @@ def get_current_track_items( "project": project, "sequence": sequence, "track": { - "name": track_name, + "name": _track_name, "index": track_index, "type": track_type} } @@ -147,7 +346,7 @@ def get_current_track_items( return selected_clips -def get_track_item_by_name(name: str) -> object: +def get_pype_track_item_by_name(name: str) -> object: track_itmes = get_current_track_items() for _ti in track_itmes: tag_data = get_track_item_pype_tag(_ti["clip"]["item"]) @@ -315,100 +514,6 @@ def delete_pype_marker(track_item): self.temp_marker_frame = None -def create_current_sequence_media_bin(sequence): - seq_name = sequence.GetName() - media_pool = get_current_project().GetMediaPool() - root_folder = media_pool.GetRootFolder() - sub_folders = root_folder.GetSubFolderList() - testing_names = list() - - print(f"_ sub_folders: {sub_folders}") - for subfolder in sub_folders: - subf_name = subfolder.GetName() - if seq_name in subf_name: - testing_names.append(subfolder) - else: - testing_names.append(False) - - matching = next((f for f in testing_names if f is not False), None) - - if not matching: - new_folder = media_pool.AddSubFolder(root_folder, seq_name) - media_pool.SetCurrentFolder(new_folder) - else: - media_pool.SetCurrentFolder(matching) - - return media_pool.GetCurrentFolder() - - -def get_name_with_data(clip_data, presets): - """ - Take hierarchy data from presets and build name with parents data - - Args: - clip_data (dict): clip data from `get_current_track_items()` - presets (dict): data from create plugin - - Returns: - list: name, data - - """ - def _replace_hash_to_expression(name, text): - _spl = text.split("#") - _len = (len(_spl) - 1) - _repl = f"{{{name}:0>{_len}}}" - new_text = text.replace(("#" * _len), _repl) - return new_text - - # presets data - clip_name = presets["clipName"] - hierarchy = presets["hierarchy"] - hierarchy_data = presets["hierarchyData"].copy() - count_from = presets["countFrom"] - steps = presets["steps"] - - # reset rename_add - if self.rename_add < count_from: - self.rename_add = count_from - - # shot num calculate - if self.rename_index == 0: - shot_num = self.rename_add - else: - shot_num = self.rename_add + steps - - print(f"shot_num: {shot_num}") - - # clip data - _data = { - "sequence": clip_data["sequence"].GetName(), - "track": clip_data["track"]["name"].replace(" ", "_"), - "shot": shot_num - } - - # solve # in test to pythonic explression - for k, v in hierarchy_data.items(): - if "#" not in v: - continue - hierarchy_data[k] = _replace_hash_to_expression(k, v) - - # fill up pythonic expresisons - for k, v in hierarchy_data.items(): - hierarchy_data[k] = v.format(**_data) - - # fill up clip name and hierarchy keys - hierarchy = hierarchy.format(**hierarchy_data) - clip_name = clip_name.format(**hierarchy_data) - - self.rename_add = shot_num - print(f"shot_num: {shot_num}") - - return (clip_name, { - "hierarchy": hierarchy, - "hierarchyData": hierarchy_data - }) - - def create_compound_clip(clip_data, name, folder): """ Convert timeline object into nested timeline object @@ -477,18 +582,13 @@ def create_compound_clip(clip_data, name, folder): if c.GetName() in name), None) print(f"_ cct created: {cct}") - # Set current timeline to created timeline: - project.SetCurrentTimeline(cct) - - # Add input clip to the current timeline: - mp.AppendToTimeline([{ - "mediaPoolItem": mp_item, - "startFrame": mp_first_frame, - "endFrame": mp_last_frame - }]) - - # Set current timeline to the working timeline: - project.SetCurrentTimeline(sq_origin) + with maintain_current_timeline(cct, sq_origin): + # Add input clip to the current timeline: + mp.AppendToTimeline([{ + "mediaPoolItem": mp_item, + "startFrame": mp_first_frame, + "endFrame": mp_last_frame + }]) # Add collected metadata and attributes to the comound clip: if mp_item.GetMetadata(self.pype_tag_name): @@ -747,3 +847,35 @@ def get_otio_clip_instance_data(otio_timeline, track_item_data): return {"otioClip": otio_clip} return None + + +def get_reformated_path(path, padded=False, first=False): + """ + Return fixed python expression path + + Args: + path (str): path url or simple file name + + Returns: + type: string with reformated path + + Example: + get_reformated_path("plate.[0001-1008].exr") > plate.%04d.exr + + """ + num_pattern = r"(\[\d+\-\d+\])" + padding_pattern = r"(\d+)(?=-)" + first_frame_pattern = re.compile(r"\[(\d+)\-\d+\]") + + if "[" in path: + padding = len(re.findall(padding_pattern, path).pop()) + if padded: + path = re.sub(num_pattern, f"%0{padding}d", path) + elif first: + first_frame = re.findall(first_frame_pattern, path, flags=0) + if len(first_frame) >= 1: + first_frame = first_frame[0] + path = re.sub(num_pattern, first_frame, path) + else: + path = re.sub(num_pattern, "%d", path) + return path diff --git a/pype/hosts/resolve/otio/utils.py b/pype/hosts/resolve/otio/utils.py index ec514289f5..7d8089e055 100644 --- a/pype/hosts/resolve/otio/utils.py +++ b/pype/hosts/resolve/otio/utils.py @@ -17,7 +17,7 @@ def frames_to_secons(frames, framerate): return otio.opentime.to_seconds(rt) -def get_reformated_path(path, padded=True): +def get_reformated_path(path, padded=True, first=False): """ Return fixed python expression path @@ -31,14 +31,21 @@ def get_reformated_path(path, padded=True): get_reformated_path("plate.[0001-1008].exr") > plate.%04d.exr """ - num_pattern = "(\\[\\d+\\-\\d+\\])" - padding_pattern = "(\\d+)(?=-)" + num_pattern = r"(\[\d+\-\d+\])" + padding_pattern = r"(\d+)(?=-)" + first_frame_pattern = re.compile(r"\[(\d+)\-\d+\]") + if "[" in path: padding = len(re.findall(padding_pattern, path).pop()) if padded: path = re.sub(num_pattern, f"%0{padding}d", path) + elif first: + first_frame = re.findall(first_frame_pattern, path, flags=0) + if len(first_frame) >= 1: + first_frame = first_frame[0] + path = re.sub(num_pattern, first_frame, path) else: - path = re.sub(num_pattern, f"%d", path) + path = re.sub(num_pattern, "%d", path) return path diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 115910b206..a3c5b51fc0 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -362,7 +362,7 @@ class ClipLoader: self.sequencial_load = options.get("sequencially") or bool( "Sequentially in order" in options.get("load_how", "")) # try to get value from options or evaluate key value for `load_to` - self.new_sequence = options.get("newSequence") or bool( + self.new_timeline = options.get("newTimeline") or bool( "New timeline" in options.get("load_to", "")) assert self._populate_data(), str( @@ -374,7 +374,7 @@ class ClipLoader: print("__init__ self.data: `{}`".format(self.data)) # add active components to class - if self.new_sequence: + if self.new_timeline: if options.get("timeline"): # if multiselection is set then use options sequence self.active_timeline = options["timeline"] @@ -493,8 +493,7 @@ class ClipLoader: self.handle_end = int(self.data["assetData"]["handleEnd"]) if self.sequencial_load: - last_track_item = lib.get_track_items( - sequence_name=self.active_timeline.name(), + last_track_item = lib.get_current_track_items( track_name=self.active_track.name()) if len(last_track_item) == 0: last_timeline_out = 0 diff --git a/pype/hosts/resolve/utility_scripts/test.py b/pype/hosts/resolve/utility_scripts/test.py index a76e4dc501..f431b44623 100644 --- a/pype/hosts/resolve/utility_scripts/test.py +++ b/pype/hosts/resolve/utility_scripts/test.py @@ -1,24 +1,23 @@ #! python3 import sys -import DaVinciResolveScript as bmdvr +import avalon.api as avalon +import pype def main(): - resolve = bmdvr.scriptapp('Resolve') - print(f"resolve: {resolve}") - project_manager = resolve.GetProjectManager() - project = project_manager.GetCurrentProject() - media_pool = project.GetMediaPool() - root_folder = media_pool.GetRootFolder() - ls_folder = root_folder.GetClipList() - timeline = project.GetCurrentTimeline() - timeline_name = timeline.GetName() - for tl in ls_folder: - if tl.GetName() not in timeline_name: - continue - print(tl.GetName()) - print(tl.GetMetadata()) - print(tl.GetClipProperty()) + import pype.hosts.resolve as bmdvr + # Registers pype's Global pyblish plugins + pype.install() + + # activate resolve from pype + avalon.install(bmdvr) + + fpath = r"C:\CODE\_PYPE_testing\testing_data\2d_shots\sh010\plate_sh010.00999.exr" + media_pool_item = bmdvr.lib.create_media_pool_item(fpath) + print(media_pool_item) + + track_item = bmdvr.lib.create_timeline_item(media_pool_item) + print(track_item) if __name__ == "__main__": From a64ddded968a0978adb7057e77277f1a0f05d4b7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Jan 2021 20:03:01 +0100 Subject: [PATCH 06/17] resolve: sequence loading done --- pype/hosts/resolve/plugin.py | 151 +++------------------ pype/plugins/resolve/load/load_sequence.py | 91 ++++++------- 2 files changed, 62 insertions(+), 180 deletions(-) diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index a3c5b51fc0..a94042c03e 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -1,5 +1,6 @@ import re from avalon import api +import pype.api as pype from pype.hosts import resolve from avalon.vendor import qargparse from . import lib @@ -303,16 +304,6 @@ class SequenceLoader(api.Loader): ], default=0, help="Where do you want clips to be loaded?" - ), - qargparse.Choice( - "load_how", - label="How to load clips", - items=[ - "original timing", - "sequential in order" - ], - default=0, - help="Would you like to place it at orignal timing?" ) ] @@ -358,9 +349,6 @@ class ClipLoader: # try to get value from options or evaluate key value for `handles` self.with_handles = options.get("handles") or bool( options.get("handles") is True) - # try to get value from options or evaluate key value for `load_how` - self.sequencial_load = options.get("sequencially") or bool( - "Sequentially in order" in options.get("load_how", "")) # try to get value from options or evaluate key value for `load_to` self.new_timeline = options.get("newTimeline") or bool( "New timeline" in options.get("load_to", "")) @@ -384,12 +372,7 @@ class ClipLoader: else: self.active_timeline = lib.get_current_sequence() - if options.get("track"): - # if multiselection is set then use options track - self.active_track = options["track"] - else: - self.active_track = lib.get_current_track( - self.active_timeline, self.data["track_name"]) + cls.timeline = self.active_timeline def _populate_data(self): """ Gets context and convert it to self.data @@ -407,7 +390,6 @@ class ClipLoader: subset = str(repr_cntx["subset"]) representation = str(repr_cntx["representation"]) self.data["clip_name"] = "_".join([asset, subset, representation]) - self.data["track_name"] = "_".join([subset, representation]) self.data["versionData"] = self.context["version"]["data"] # gets file path file = self.fname @@ -418,10 +400,6 @@ class ClipLoader: return None self.data["path"] = file.replace("\\", "/") - # convert to hashed path - if repr_cntx.get("frame"): - self._fix_path_hashes() - # solve project bin structure path hierarchy = str("/".join(( "Loader", @@ -433,16 +411,6 @@ class ClipLoader: return True - def _fix_path_hashes(self): - """ Convert file path where it is needed padding with hashes - """ - file = self.data["path"] - if "#" not in file: - frame = self.context["representation"]["context"].get("frame") - padding = len(frame) - file = file.replace(frame, "#" * padding) - self.data["path"] = file - def _get_asset_data(self): """ Get all available asset data @@ -452,117 +420,40 @@ class ClipLoader: asset_name = self.context["representation"]["context"]["asset"] self.data["assetData"] = pype.get_asset(asset_name)["data"] - def _make_track_item(self, source_bin_item, audio=False): - """ Create track item with """ - - clip = source_bin_item.activeItem() - - # add to track as clip item - if not audio: - track_item = hiero.core.TrackItem( - self.data["clip_name"], hiero.core.TrackItem.kVideo) - else: - track_item = hiero.core.TrackItem( - self.data["clip_name"], hiero.core.TrackItem.kAudio) - - track_item.setSource(clip) - track_item.setSourceIn(self.handle_start) - track_item.setTimelineIn(self.timeline_in) - track_item.setSourceOut(self.media_duration - self.handle_end) - track_item.setTimelineOut(self.timeline_out) - track_item.setPlaybackSpeed(1) - self.active_track.addTrackItem(track_item) - - return track_item - def load(self): # create project bin for the media to be imported into self.active_bin = lib.create_bin(self.data["binPath"]) # create mediaItem in active project bin # create clip media - self.media = hiero.core.MediaSource(self.data["path"]) - self.media_duration = int(self.media.duration()) + media_pool_item = lib.create_media_pool_item( + self.data["path"], self.active_bin) + clip_property = media_pool_item.GetClipProperty() + + source_in = int(clip_property["Start"]) + source_out = int(clip_property["End"]) # get handles - self.handle_start = self.data["versionData"].get("handleStart") - self.handle_end = self.data["versionData"].get("handleEnd") - if self.handle_start is None: - self.handle_start = int(self.data["assetData"]["handleStart"]) - if self.handle_end is None: - self.handle_end = int(self.data["assetData"]["handleEnd"]) - - if self.sequencial_load: - last_track_item = lib.get_current_track_items( - track_name=self.active_track.name()) - if len(last_track_item) == 0: - last_timeline_out = 0 - else: - last_track_item = last_track_item[-1] - last_timeline_out = int(last_track_item.timelineOut()) + 1 - self.timeline_in = last_timeline_out - self.timeline_out = last_timeline_out + int( - self.data["assetData"]["clipOut"] - - self.data["assetData"]["clipIn"]) - else: - self.timeline_in = int(self.data["assetData"]["clipIn"]) - self.timeline_out = int(self.data["assetData"]["clipOut"]) - - # check if slate is included - # either in version data families or by calculating frame diff - slate_on = next( - # check iterate if slate is in families - (f for f in self.context["version"]["data"]["families"] - if "slate" in f), - # if nothing was found then use default None - # so other bool could be used - None) or bool((( - # put together duration of clip attributes - self.timeline_out - self.timeline_in + 1) \ - + self.handle_start \ - + self.handle_end - # and compare it with meda duration - ) > self.media_duration) - - print("__ slate_on: `{}`".format(slate_on)) - - # if slate is on then remove the slate frame from begining - if slate_on: - self.media_duration -= 1 - self.handle_start += 1 - - # create Clip from Media - clip = hiero.core.Clip(self.media) - clip.setName(self.data["clip_name"]) - - # add Clip to bin if not there yet - if self.data["clip_name"] not in [ - b.name() for b in self.active_bin.items()]: - bin_item = hiero.core.BinItem(clip) - self.active_bin.addItem(bin_item) - - # just make sure the clip is created - # there were some cases were hiero was not creating it - source_bin_item = None - for item in self.active_bin.items(): - if self.data["clip_name"] in item.name(): - source_bin_item = item - if not source_bin_item: - print("Problem with created Source clip: `{}`".format( - self.data["clip_name"])) + handle_start = self.data["versionData"].get("handleStart") + handle_end = self.data["versionData"].get("handleEnd") + if handle_start is None: + handle_start = int(self.data["assetData"]["handleStart"]) + if handle_end is None: + handle_end = int(self.data["assetData"]["handleEnd"]) # include handles if self.with_handles: - self.timeline_in -= self.handle_start - self.timeline_out += self.handle_end - self.handle_start = 0 - self.handle_end = 0 + source_in -= handle_start + source_out += handle_end + handle_start = 0 + handle_end = 0 # make track item from source in bin as item - track_item = self._make_track_item(source_bin_item) + timeline_item = lib.create_timeline_item( + media_pool_item, self.active_timeline, source_in, source_out) print("Loading clips: `{}`".format(self.data["clip_name"])) - return track_item + return timeline_item class Creator(api.Creator): diff --git a/pype/plugins/resolve/load/load_sequence.py b/pype/plugins/resolve/load/load_sequence.py index 489355b72d..6cbda26025 100644 --- a/pype/plugins/resolve/load/load_sequence.py +++ b/pype/plugins/resolve/load/load_sequence.py @@ -19,24 +19,23 @@ class LoadClip(resolve.SequenceLoader): # for loader multiselection timeline = None - track = None # presets - clip_color_last = "green" - clip_color = "red" + clip_color_last = "Olive" + clip_color = "Orange" def load(self, context, name, namespace, options): # in case loader uses multiselection - if self.track and self.timeline: + if self.timeline: options.update({ "timeline": self.timeline, - "track": self.track }) # load clip to timeline and get main variables - track_item = phiero.ClipLoader(self, context, **options).load() - namespace = namespace or track_item.name() + timeline_item = resolve.plugin.ClipLoader( + self, context, **options).load() + namespace = namespace or timeline_item.GetName() version = context['version'] version_data = version.get("data", {}) version_name = version.get("name", None) @@ -64,15 +63,12 @@ class LoadClip(resolve.SequenceLoader): }) # update color of clip regarding the version order - self.set_item_color(track_item, version) - - # deal with multiselection - self.multiselection(track_item) + self.set_item_color(timeline_item, version) self.log.info("Loader done: `{}`".format(name)) return resolve.containerise( - track_item, + timeline_item, name, namespace, context, self.__class__.__name__, data_imprint) @@ -87,7 +83,7 @@ class LoadClip(resolve.SequenceLoader): # load clip to timeline and get main variables name = container['name'] namespace = container['namespace'] - track_item = resolve.get_track_item_by_name(namespace) + timeline_item = resolve.lib.get_pype_track_item_by_name(namespace) version = io.find_one({ "type": "version", "_id": representation["parent"] @@ -98,43 +94,38 @@ class LoadClip(resolve.SequenceLoader): object_name = "{}_{}".format(name, namespace) file = api.get_representation_path(representation).replace("\\", "/") - # reconnect media to new path - track_item.source().reconnectMedia(file) - - # add additional metadata from the version to imprint Avalon knob - add_keys = [ - "frameStart", "frameEnd", "source", "author", - "fps", "handleStart", "handleEnd" - ] - - # move all version data keys to tag data - data_imprint = {} - for key in add_keys: - data_imprint.update({ - key: version_data.get(key, str(None)) - }) - - # add variables related to version context - data_imprint.update({ - "representation": str(representation["_id"]), - "version": version_name, - "colorspace": colorspace, - "objectName": object_name - }) - - # update color of clip regarding the version order - self.set_item_color(track_item, version) - - return resolve.update_container(track_item, data_imprint) + # TODO: implement update + # # reconnect media to new path + # track_item.source().reconnectMedia(file) + # + # # add additional metadata from the version to imprint Avalon knob + # add_keys = [ + # "frameStart", "frameEnd", "source", "author", + # "fps", "handleStart", "handleEnd" + # ] + # + # # move all version data keys to tag data + # data_imprint = {} + # for key in add_keys: + # data_imprint.update({ + # key: version_data.get(key, str(None)) + # }) + # + # # add variables related to version context + # data_imprint.update({ + # "representation": str(representation["_id"]), + # "version": version_name, + # "colorspace": colorspace, + # "objectName": object_name + # }) + # + # # update color of clip regarding the version order + # self.set_item_color(timeline_item, version) + # + # return resolve.set_track_item_pype_tag(timeline_item, data_imprint) @classmethod - def multiselection(cls, track_item): - if not cls.track: - cls.track = track_item.parent() - cls.sequence = cls.track.parent() - - @classmethod - def set_item_color(cls, track_item, version): + def set_item_color(cls, timeline_item, version): # define version name version_name = version.get("name", None) @@ -148,6 +139,6 @@ class LoadClip(resolve.SequenceLoader): # set clip colour if version_name == max_version: - track_item.source().binItem().setColor(cls.clip_color_last) + timeline_item.SetClipColor(cls.clip_color_last) else: - track_item.source().binItem().setColor(cls.clip_color) + timeline_item.SetClipColor(cls.clip_color) From b5f2f5e37e2ee99f1c8a060d971d1461e7cf2bca Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 28 Jan 2021 12:02:48 +0100 Subject: [PATCH 07/17] resolve: restructure host folder position --- pype/hosts/resolve/__init__.py | 12 +- pype/hosts/resolve/{ => api}/README.markdown | 0 .../resolve/{ => api}/RESOLVE_API_README.txt | 0 .../{ => api}/RESOLVE_API_README_NEW.txt | 0 pype/hosts/resolve/api/__init__.py | 0 pype/hosts/resolve/{ => api}/action.py | 0 pype/hosts/resolve/{ => api}/lib.py | 0 pype/hosts/resolve/{ => api}/menu.py | 0 pype/hosts/resolve/{ => api}/menu_style.qss | 0 pype/hosts/resolve/{ => api}/pipeline.py | 0 pype/hosts/resolve/{ => api}/plugin.py | 0 .../resolve/{ => api}/preload_console.py | 0 .../hosts/resolve/{ => api}/todo-rendering.py | 0 pype/hosts/resolve/{ => api}/utils.py | 0 pype/hosts/resolve/{ => api}/workio.py | 0 .../_publish/collect_clip_resolution.py | 0 .../plugins}/_publish/collect_clips.py | 0 .../plugins}/create/create_shot_clip.py | 0 .../hosts/resolve/plugins/create_shot_clip.py | 263 ++++++++++++++++++ .../resolve/plugins}/load/load_sequence.py | 0 .../plugins}/publish/collect_instances.py | 0 .../plugins}/publish/collect_workfile.py | 0 .../plugins}/publish/extract_workfile.py | 0 23 files changed, 269 insertions(+), 6 deletions(-) rename pype/hosts/resolve/{ => api}/README.markdown (100%) rename pype/hosts/resolve/{ => api}/RESOLVE_API_README.txt (100%) rename pype/hosts/resolve/{ => api}/RESOLVE_API_README_NEW.txt (100%) create mode 100644 pype/hosts/resolve/api/__init__.py rename pype/hosts/resolve/{ => api}/action.py (100%) rename pype/hosts/resolve/{ => api}/lib.py (100%) rename pype/hosts/resolve/{ => api}/menu.py (100%) rename pype/hosts/resolve/{ => api}/menu_style.qss (100%) rename pype/hosts/resolve/{ => api}/pipeline.py (100%) rename pype/hosts/resolve/{ => api}/plugin.py (100%) rename pype/hosts/resolve/{ => api}/preload_console.py (100%) rename pype/hosts/resolve/{ => api}/todo-rendering.py (100%) rename pype/hosts/resolve/{ => api}/utils.py (100%) rename pype/hosts/resolve/{ => api}/workio.py (100%) rename pype/{plugins/resolve => hosts/resolve/plugins}/_publish/collect_clip_resolution.py (100%) rename pype/{plugins/resolve => hosts/resolve/plugins}/_publish/collect_clips.py (100%) rename pype/{plugins/resolve => hosts/resolve/plugins}/create/create_shot_clip.py (100%) create mode 100644 pype/hosts/resolve/plugins/create_shot_clip.py rename pype/{plugins/resolve => hosts/resolve/plugins}/load/load_sequence.py (100%) rename pype/{plugins/resolve => hosts/resolve/plugins}/publish/collect_instances.py (100%) rename pype/{plugins/resolve => hosts/resolve/plugins}/publish/collect_workfile.py (100%) rename pype/{plugins/resolve => hosts/resolve/plugins}/publish/extract_workfile.py (100%) diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index fd1efcc6e1..f1f8bd2a4a 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -1,9 +1,9 @@ -from .utils import ( +from .api.utils import ( setup, get_resolve_module ) -from .pipeline import ( +from .api.pipeline import ( install, uninstall, ls, @@ -13,7 +13,7 @@ from .pipeline import ( maintained_selection ) -from .lib import ( +from .api.lib import ( publish_clip_color, get_project_manager, get_current_project, @@ -34,15 +34,15 @@ from .lib import ( get_otio_clip_instance_data ) -from .menu import launch_pype_menu +from .api.menu import launch_pype_menu -from .plugin import ( +from .api.plugin import ( SequenceLoader, Creator, PublishClip ) -from .workio import ( +from .api.workio import ( open_file, save_file, current_file, diff --git a/pype/hosts/resolve/README.markdown b/pype/hosts/resolve/api/README.markdown similarity index 100% rename from pype/hosts/resolve/README.markdown rename to pype/hosts/resolve/api/README.markdown diff --git a/pype/hosts/resolve/RESOLVE_API_README.txt b/pype/hosts/resolve/api/RESOLVE_API_README.txt similarity index 100% rename from pype/hosts/resolve/RESOLVE_API_README.txt rename to pype/hosts/resolve/api/RESOLVE_API_README.txt diff --git a/pype/hosts/resolve/RESOLVE_API_README_NEW.txt b/pype/hosts/resolve/api/RESOLVE_API_README_NEW.txt similarity index 100% rename from pype/hosts/resolve/RESOLVE_API_README_NEW.txt rename to pype/hosts/resolve/api/RESOLVE_API_README_NEW.txt diff --git a/pype/hosts/resolve/api/__init__.py b/pype/hosts/resolve/api/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pype/hosts/resolve/action.py b/pype/hosts/resolve/api/action.py similarity index 100% rename from pype/hosts/resolve/action.py rename to pype/hosts/resolve/api/action.py diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/api/lib.py similarity index 100% rename from pype/hosts/resolve/lib.py rename to pype/hosts/resolve/api/lib.py diff --git a/pype/hosts/resolve/menu.py b/pype/hosts/resolve/api/menu.py similarity index 100% rename from pype/hosts/resolve/menu.py rename to pype/hosts/resolve/api/menu.py diff --git a/pype/hosts/resolve/menu_style.qss b/pype/hosts/resolve/api/menu_style.qss similarity index 100% rename from pype/hosts/resolve/menu_style.qss rename to pype/hosts/resolve/api/menu_style.qss diff --git a/pype/hosts/resolve/pipeline.py b/pype/hosts/resolve/api/pipeline.py similarity index 100% rename from pype/hosts/resolve/pipeline.py rename to pype/hosts/resolve/api/pipeline.py diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/api/plugin.py similarity index 100% rename from pype/hosts/resolve/plugin.py rename to pype/hosts/resolve/api/plugin.py diff --git a/pype/hosts/resolve/preload_console.py b/pype/hosts/resolve/api/preload_console.py similarity index 100% rename from pype/hosts/resolve/preload_console.py rename to pype/hosts/resolve/api/preload_console.py diff --git a/pype/hosts/resolve/todo-rendering.py b/pype/hosts/resolve/api/todo-rendering.py similarity index 100% rename from pype/hosts/resolve/todo-rendering.py rename to pype/hosts/resolve/api/todo-rendering.py diff --git a/pype/hosts/resolve/utils.py b/pype/hosts/resolve/api/utils.py similarity index 100% rename from pype/hosts/resolve/utils.py rename to pype/hosts/resolve/api/utils.py diff --git a/pype/hosts/resolve/workio.py b/pype/hosts/resolve/api/workio.py similarity index 100% rename from pype/hosts/resolve/workio.py rename to pype/hosts/resolve/api/workio.py diff --git a/pype/plugins/resolve/_publish/collect_clip_resolution.py b/pype/hosts/resolve/plugins/_publish/collect_clip_resolution.py similarity index 100% rename from pype/plugins/resolve/_publish/collect_clip_resolution.py rename to pype/hosts/resolve/plugins/_publish/collect_clip_resolution.py diff --git a/pype/plugins/resolve/_publish/collect_clips.py b/pype/hosts/resolve/plugins/_publish/collect_clips.py similarity index 100% rename from pype/plugins/resolve/_publish/collect_clips.py rename to pype/hosts/resolve/plugins/_publish/collect_clips.py diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/hosts/resolve/plugins/create/create_shot_clip.py similarity index 100% rename from pype/plugins/resolve/create/create_shot_clip.py rename to pype/hosts/resolve/plugins/create/create_shot_clip.py diff --git a/pype/hosts/resolve/plugins/create_shot_clip.py b/pype/hosts/resolve/plugins/create_shot_clip.py new file mode 100644 index 0000000000..19e613ee7a --- /dev/null +++ b/pype/hosts/resolve/plugins/create_shot_clip.py @@ -0,0 +1,263 @@ +# from pprint import pformat +from pype.hosts import resolve +from pype.hosts.resolve import lib + + +class CreateShotClip(resolve.Creator): + """Publishable clip""" + + label = "Create Publishable Clip" + family = "clip" + icon = "film" + defaults = ["Main"] + + gui_tracks = resolve.get_video_track_names() + gui_name = "Pype publish attributes creator" + gui_info = "Define sequential rename and fill hierarchy data." + gui_inputs = { + "renameHierarchy": { + "type": "section", + "label": "Shot Hierarchy And Rename Settings", + "target": "ui", + "order": 0, + "value": { + "hierarchy": { + "value": "{folder}/{sequence}", + "type": "QLineEdit", + "label": "Shot Parent Hierarchy", + "target": "tag", + "toolTip": "Parents folder for shot root folder, Template filled with `Hierarchy Data` section", # noqa + "order": 0}, + "clipRename": { + "value": False, + "type": "QCheckBox", + "label": "Rename clips", + "target": "ui", + "toolTip": "Renaming selected clips on fly", # noqa + "order": 1}, + "clipName": { + "value": "{sequence}{shot}", + "type": "QLineEdit", + "label": "Clip Name Template", + "target": "ui", + "toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa + "order": 2}, + "countFrom": { + "value": 10, + "type": "QSpinBox", + "label": "Count sequence from", + "target": "ui", + "toolTip": "Set when the sequence number stafrom", # noqa + "order": 3}, + "countSteps": { + "value": 10, + "type": "QSpinBox", + "label": "Stepping number", + "target": "ui", + "toolTip": "What number is adding every new step", # noqa + "order": 4}, + } + }, + "hierarchyData": { + "type": "dict", + "label": "Shot Template Keywords", + "target": "tag", + "order": 1, + "value": { + "folder": { + "value": "shots", + "type": "QLineEdit", + "label": "{folder}", + "target": "tag", + "toolTip": "Name of folder used for root of generated shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 0}, + "episode": { + "value": "ep01", + "type": "QLineEdit", + "label": "{episode}", + "target": "tag", + "toolTip": "Name of episode.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 1}, + "sequence": { + "value": "sq01", + "type": "QLineEdit", + "label": "{sequence}", + "target": "tag", + "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 2}, + "track": { + "value": "{_track_}", + "type": "QLineEdit", + "label": "{track}", + "target": "tag", + "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 3}, + "shot": { + "value": "sh###", + "type": "QLineEdit", + "label": "{shot}", + "target": "tag", + "toolTip": "Name of shot. `#` is converted to paded number. \nAlso could be used with usable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa + "order": 4} + } + }, + "verticalSync": { + "type": "section", + "label": "Vertical Synchronization Of Attributes", + "target": "ui", + "order": 2, + "value": { + "vSyncOn": { + "value": True, + "type": "QCheckBox", + "label": "Enable Vertical Sync", + "target": "ui", + "toolTip": "Switch on if you want clips above each other to share its attributes", # noqa + "order": 0}, + "vSyncTrack": { + "value": gui_tracks, # noqa + "type": "QComboBox", + "label": "Master track", + "target": "ui", + "toolTip": "Select driving track name which should be mastering all others", # noqa + "order": 1} + } + }, + "publishSettings": { + "type": "section", + "label": "Publish Settings", + "target": "ui", + "order": 3, + "value": { + "subsetName": { + "value": ["", "main", "bg", "fg", "bg", + "animatic"], + "type": "QComboBox", + "label": "Subset Name", + "target": "ui", + "toolTip": "chose subset name patern, if is selected, name of track layer will be used", # noqa + "order": 0}, + "subsetFamily": { + "value": ["plate", "take"], + "type": "QComboBox", + "label": "Subset Family", + "target": "ui", "toolTip": "What use of this subset is for", # noqa + "order": 1}, + "reviewTrack": { + "value": ["< none >"] + gui_tracks, + "type": "QComboBox", + "label": "Use Review Track", + "target": "ui", + "toolTip": "Generate preview videos on fly, if `< none >` is defined nothing will be generated.", # noqa + "order": 2}, + "audio": { + "value": False, + "type": "QCheckBox", + "label": "Include audio", + "target": "tag", + "toolTip": "Process subsets with corresponding audio", # noqa + "order": 3}, + "sourceResolution": { + "value": False, + "type": "QCheckBox", + "label": "Source resolution", + "target": "tag", + "toolTip": "Is resloution taken from timeline or source?", # noqa + "order": 4}, + } + }, + "shotAttr": { + "type": "section", + "label": "Shot Attributes", + "target": "ui", + "order": 4, + "value": { + "workfileFrameStart": { + "value": 1001, + "type": "QSpinBox", + "label": "Workfiles Start Frame", + "target": "tag", + "toolTip": "Set workfile starting frame number", # noqa + "order": 0}, + "handleStart": { + "value": 0, + "type": "QSpinBox", + "label": "Handle start (head)", + "target": "tag", + "toolTip": "Handle at start of clip", # noqa + "order": 1}, + "handleEnd": { + "value": 0, + "type": "QSpinBox", + "label": "Handle end (tail)", + "target": "tag", + "toolTip": "Handle at end of clip", # noqa + "order": 2}, + } + } + } + + presets = None + + def process(self): + # get key pares from presets and match it on ui inputs + for k, v in self.gui_inputs.items(): + if v["type"] in ("dict", "section"): + # nested dictionary (only one level allowed + # for sections and dict) + for _k, _v in v["value"].items(): + if self.presets.get(_k) is not None: + self.gui_inputs[k][ + "value"][_k]["value"] = self.presets[_k] + if self.presets.get(k): + self.gui_inputs[k]["value"] = self.presets[k] + + # open widget for plugins inputs + widget = self.widget(self.gui_name, self.gui_info, self.gui_inputs) + widget.exec_() + + if len(self.selected) < 1: + return + + if not widget.result: + print("Operation aborted") + return + + self.rename_add = 0 + + # get ui output for track name for vertical sync + v_sync_track = widget.result["vSyncTrack"]["value"] + + # sort selected trackItems by + sorted_selected_track_items = list() + unsorted_selected_track_items = list() + for track_item_data in self.selected: + if track_item_data["track"]["name"] in v_sync_track: + sorted_selected_track_items.append(track_item_data) + else: + unsorted_selected_track_items.append(track_item_data) + + sorted_selected_track_items.extend(unsorted_selected_track_items) + + # sequence attrs + sq_frame_start = self.sequence.GetStartFrame() + sq_markers = self.sequence.GetMarkers() + + # create media bin for compound clips (trackItems) + mp_folder = resolve.create_current_sequence_media_bin(self.sequence) + + kwargs = { + "ui_inputs": widget.result, + "avalon": self.data, + "mp_folder": mp_folder, + "sq_frame_start": sq_frame_start, + "sq_markers": sq_markers + } + + for i, track_item_data in enumerate(sorted_selected_track_items): + self.rename_index = i + + # convert track item to timeline media pool item + track_item = resolve.PublishClip( + self, track_item_data, **kwargs).convert() + track_item.SetClipColor(lib.publish_clip_color) diff --git a/pype/plugins/resolve/load/load_sequence.py b/pype/hosts/resolve/plugins/load/load_sequence.py similarity index 100% rename from pype/plugins/resolve/load/load_sequence.py rename to pype/hosts/resolve/plugins/load/load_sequence.py diff --git a/pype/plugins/resolve/publish/collect_instances.py b/pype/hosts/resolve/plugins/publish/collect_instances.py similarity index 100% rename from pype/plugins/resolve/publish/collect_instances.py rename to pype/hosts/resolve/plugins/publish/collect_instances.py diff --git a/pype/plugins/resolve/publish/collect_workfile.py b/pype/hosts/resolve/plugins/publish/collect_workfile.py similarity index 100% rename from pype/plugins/resolve/publish/collect_workfile.py rename to pype/hosts/resolve/plugins/publish/collect_workfile.py diff --git a/pype/plugins/resolve/publish/extract_workfile.py b/pype/hosts/resolve/plugins/publish/extract_workfile.py similarity index 100% rename from pype/plugins/resolve/publish/extract_workfile.py rename to pype/hosts/resolve/plugins/publish/extract_workfile.py From fb86bc4c8cae4f60754daa19e58bf01c7dc0b6f9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 28 Jan 2021 13:01:35 +0100 Subject: [PATCH 08/17] resolve: structure fix and testing workflow kick of --- pype/hosts/resolve/{api => }/README.markdown | 0 ...xt => RESOLVE_API_README_v16.2.0_down.txt} | 0 ....txt => RESOLVE_API_README_v16.2.0_up.txt} | 0 .../utility_scripts/resolve_dev_scriping.py | 22 ------ .../utility_scripts/tests/test_utils.py | 71 +++++++++++++++++++ ...testing_create_timeline_item_from_path.py} | 0 6 files changed, 71 insertions(+), 22 deletions(-) rename pype/hosts/resolve/{api => }/README.markdown (100%) rename pype/hosts/resolve/{api/RESOLVE_API_README.txt => RESOLVE_API_README_v16.2.0_down.txt} (100%) rename pype/hosts/resolve/{api/RESOLVE_API_README_NEW.txt => RESOLVE_API_README_v16.2.0_up.txt} (100%) delete mode 100644 pype/hosts/resolve/utility_scripts/resolve_dev_scriping.py create mode 100644 pype/hosts/resolve/utility_scripts/tests/test_utils.py rename pype/hosts/resolve/utility_scripts/{test.py => tests/testing_create_timeline_item_from_path.py} (100%) diff --git a/pype/hosts/resolve/api/README.markdown b/pype/hosts/resolve/README.markdown similarity index 100% rename from pype/hosts/resolve/api/README.markdown rename to pype/hosts/resolve/README.markdown diff --git a/pype/hosts/resolve/api/RESOLVE_API_README.txt b/pype/hosts/resolve/RESOLVE_API_README_v16.2.0_down.txt similarity index 100% rename from pype/hosts/resolve/api/RESOLVE_API_README.txt rename to pype/hosts/resolve/RESOLVE_API_README_v16.2.0_down.txt diff --git a/pype/hosts/resolve/api/RESOLVE_API_README_NEW.txt b/pype/hosts/resolve/RESOLVE_API_README_v16.2.0_up.txt similarity index 100% rename from pype/hosts/resolve/api/RESOLVE_API_README_NEW.txt rename to pype/hosts/resolve/RESOLVE_API_README_v16.2.0_up.txt diff --git a/pype/hosts/resolve/utility_scripts/resolve_dev_scriping.py b/pype/hosts/resolve/utility_scripts/resolve_dev_scriping.py deleted file mode 100644 index bd9fe593e0..0000000000 --- a/pype/hosts/resolve/utility_scripts/resolve_dev_scriping.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python - - -def main(): - import pype.hosts.resolve as bmdvr - bmdvr.utils.get_resolve_module() - - tracks = list() - track_type = "video" - sequence = bmdvr.get_current_sequence() - - # get all tracks count filtered by track type - selected_track_count = sequence.GetTrackCount(track_type) - - # loop all tracks and get items - for track_index in range(1, (int(selected_track_count) + 1)): - track_name = sequence.GetTrackName("video", track_index) - tracks.append(track_name) - - -if __name__ == "__main__": - main() diff --git a/pype/hosts/resolve/utility_scripts/tests/test_utils.py b/pype/hosts/resolve/utility_scripts/tests/test_utils.py new file mode 100644 index 0000000000..699adab6e2 --- /dev/null +++ b/pype/hosts/resolve/utility_scripts/tests/test_utils.py @@ -0,0 +1,71 @@ +#! python3 +import os +resolve = bmd.scriptapp("Resolve") # noqa +fu = resolve.Fusion() +ui = fu.UIManager +disp = bmd.UIDispatcher(fu.UIManager) # noqa + + + +title_font = ui.Font({"PixelSize": 18}) +dlg = disp.AddWindow( + { + "WindowTitle": "Get Testing folder", + "ID": "TestingWin", + "Geometry": [250, 250, 250, 100], + "Spacing": 0, + "Margin": 10 + }, + [ + ui.VGroup( + { + "Spacing": 2 + }, + [ + ui.Button( + { + "ID": "inputTestSourcesFolder", + "Text": "Select folder with testing medias", + "Weight": 1.25, + "ToolTip": "Chose folder with videos, sequences, single images, nested folders with medias", + "Flat": False + } + ), + ui.VGap(), + ui.Button( + { + "ID": "openButton", + "Text": "Open", + "Weight": 2, + "ToolTip": "Open and run test on the folder", + "Flat": False + } + ) + ] + ) + ] +) + +itm = dlg.GetItems() + + +def _close_window(event): + disp.ExitLoop() + + +def _import_button(event): + otio_import.read_from_file(itm["inputTestSourcesFolder"].Text) + _close_window(None) + + +def _import_file_pressed(event): + selected_path = fu.RequestFile(os.path.expanduser("~")) + itm["inputTestSourcesFolder"].Text = selected_path + + +dlg.On.TestingWin.Close = _close_window +dlg.On.inputTestSourcesFolder.Clicked = _import_file_pressed +dlg.On.openButton.Clicked = _import_button +dlg.Show() +disp.RunLoop() +dlg.Hide() diff --git a/pype/hosts/resolve/utility_scripts/test.py b/pype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py similarity index 100% rename from pype/hosts/resolve/utility_scripts/test.py rename to pype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py From 9da2d0a48851b8e29087c38e7dd6d2b489c73eb1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 28 Jan 2021 17:26:04 +0100 Subject: [PATCH 09/17] resolve: track_item to timeline_item --- pype/hosts/resolve/__init__.py | 30 ++-- pype/hosts/resolve/api/__init__.py | 8 ++ pype/hosts/resolve/api/lib.py | 135 +++++++----------- pype/hosts/resolve/api/pipeline.py | 57 ++++---- pype/hosts/resolve/api/plugin.py | 50 +++---- .../testing_create_timeline_item_from_path.py | 4 +- 6 files changed, 139 insertions(+), 145 deletions(-) diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index f1f8bd2a4a..aa0d54ff06 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -14,15 +14,21 @@ from .api.pipeline import ( ) from .api.lib import ( + maintain_current_timeline, publish_clip_color, get_project_manager, get_current_project, get_current_sequence, create_bin, + get_media_pool_item, + create_media_pool_item, + create_timeline_item, + get_timeline_item, get_video_track_names, - get_current_track_items, - get_track_item_pype_tag, - set_track_item_pype_tag, + get_current_timeline_items, + get_pype_timeline_item_by_name, + get_timeline_item_pype_tag, + set_timeline_item_pype_tag, imprint, set_publish_attribute, get_publish_attribute, @@ -30,8 +36,8 @@ from .api.lib import ( swap_clips, get_pype_clip_metadata, set_project_manager_to_folder_name, - get_reformated_path, - get_otio_clip_instance_data + get_otio_clip_instance_data, + get_reformated_path ) from .api.menu import launch_pype_menu @@ -70,15 +76,21 @@ __all__ = [ "get_resolve_module", # lib + "maintain_current_timeline", "publish_clip_color", "get_project_manager", "get_current_project", "get_current_sequence", "create_bin", + "get_media_pool_item", + "create_media_pool_item", + "create_timeline_item", + "get_timeline_item", "get_video_track_names", - "get_current_track_items", - "get_track_item_pype_tag", - "set_track_item_pype_tag", + "get_current_timeline_items", + "get_pype_timeline_item_by_name", + "get_timeline_item_pype_tag", + "set_timeline_item_pype_tag", "imprint", "set_publish_attribute", "get_publish_attribute", @@ -86,8 +98,8 @@ __all__ = [ "swap_clips", "get_pype_clip_metadata", "set_project_manager_to_folder_name", - "get_reformated_path", "get_otio_clip_instance_data", + "get_reformated_path", # menu "launch_pype_menu", diff --git a/pype/hosts/resolve/api/__init__.py b/pype/hosts/resolve/api/__init__.py index e69de29bb2..42e92bc109 100644 --- a/pype/hosts/resolve/api/__init__.py +++ b/pype/hosts/resolve/api/__init__.py @@ -0,0 +1,8 @@ +""" +resolve api +""" +import os + +API_DIR = os.path.dirname(os.path.abspath(__file__)) +HOST_DIR = os.path.dirname(API_DIR) +PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") diff --git a/pype/hosts/resolve/api/lib.py b/pype/hosts/resolve/api/lib.py index 7b6325b1b6..9e8321b312 100644 --- a/pype/hosts/resolve/api/lib.py +++ b/pype/hosts/resolve/api/lib.py @@ -265,7 +265,7 @@ def get_timeline_item(media_pool_item: object, with maintain_current_timeline(timeline): # search the timeline for the added clip - for _ti_data in get_current_track_items(): + for _ti_data in get_current_timeline_items(): _ti_clip = _ti_data["clip"]["item"] _ti_clip_property = _ti_clip.GetMediaPoolItem().GetClipProperty() if clip_name in _ti_clip_property["File Name"]: @@ -291,7 +291,7 @@ def get_video_track_names() -> list: return tracks -def get_current_track_items( +def get_current_timeline_items( filter: bool = False, track_type: str = None, track_name: str = None, @@ -317,9 +317,9 @@ def get_current_track_items( if _track_name not in track_name: continue - track_track_items = sequence.GetItemListInTrack( + timeline_items = sequence.GetItemListInTrack( track_type, track_index) - _clips[track_index] = track_track_items + _clips[track_index] = timeline_items _data = { "project": project, @@ -346,10 +346,10 @@ def get_current_track_items( return selected_clips -def get_pype_track_item_by_name(name: str) -> object: - track_itmes = get_current_track_items() +def get_pype_timeline_item_by_name(name: str) -> object: + track_itmes = get_current_timeline_items() for _ti in track_itmes: - tag_data = get_track_item_pype_tag(_ti["clip"]["item"]) + tag_data = get_timeline_item_pype_tag(_ti["clip"]["item"]) tag_name = tag_data.get("name") if not tag_name: continue @@ -358,7 +358,7 @@ def get_pype_track_item_by_name(name: str) -> object: return None -def get_track_item_pype_tag(track_item): +def get_timeline_item_pype_tag(timeline_item): """ Get pype track item tag created by creator or loader plugin. @@ -371,9 +371,9 @@ def get_track_item_pype_tag(track_item): return_tag = None if self.pype_marker_workflow: - return_tag = get_pype_marker(track_item) + return_tag = get_pype_marker(timeline_item) else: - media_pool_item = track_item.GetMediaPoolItem() + media_pool_item = timeline_item.GetMediaPoolItem() # get all tags from track item _tags = media_pool_item.GetMetadata() @@ -387,9 +387,9 @@ def get_track_item_pype_tag(track_item): return return_tag -def set_track_item_pype_tag(track_item, data=None): +def set_timeline_item_pype_tag(timeline_item, data=None): """ - Set pype track item tag to input track_item. + Set pype track item tag to input timeline_item. Attributes: trackItem (resolve.TimelineItem): resolve api object @@ -400,18 +400,18 @@ def set_track_item_pype_tag(track_item, data=None): data = data or dict() # get available pype tag if any - tag_data = get_track_item_pype_tag(track_item) + tag_data = get_timeline_item_pype_tag(timeline_item) if self.pype_marker_workflow: # delete tag as it is not updatable if tag_data: - delete_pype_marker(track_item) + delete_pype_marker(timeline_item) tag_data.update(data) - set_pype_marker(track_item, tag_data) + set_pype_marker(timeline_item, tag_data) else: if tag_data: - media_pool_item = track_item.GetMediaPoolItem() + media_pool_item = timeline_item.GetMediaPoolItem() # it not tag then create one tag_data.update(data) media_pool_item.SetMetadata( @@ -420,19 +420,19 @@ def set_track_item_pype_tag(track_item, data=None): tag_data = data # if pype tag available then update with input data # add it to the input track item - track_item.SetMetadata(self.pype_tag_name, json.dumps(tag_data)) + timeline_item.SetMetadata(self.pype_tag_name, json.dumps(tag_data)) return tag_data -def imprint(track_item, data=None): +def imprint(timeline_item, data=None): """ Adding `Avalon data` into a hiero track item tag. Also including publish attribute into tag. Arguments: - track_item (hiero.core.TrackItem): hiero track item object + timeline_item (hiero.core.TrackItem): hiero track item object data (dict): Any data which needst to be imprinted Examples: @@ -444,39 +444,39 @@ def imprint(track_item, data=None): """ data = data or {} - set_track_item_pype_tag(track_item, data) + set_timeline_item_pype_tag(timeline_item, data) # add publish attribute - set_publish_attribute(track_item, True) + set_publish_attribute(timeline_item, True) -def set_publish_attribute(track_item, value): +def set_publish_attribute(timeline_item, value): """ Set Publish attribute in input Tag object Attribute: tag (hiero.core.Tag): a tag object value (bool): True or False """ - tag_data = get_track_item_pype_tag(track_item) + tag_data = get_timeline_item_pype_tag(timeline_item) tag_data["publish"] = value # set data to the publish attribute - set_track_item_pype_tag(track_item, tag_data) + set_timeline_item_pype_tag(timeline_item, tag_data) -def get_publish_attribute(track_item): +def get_publish_attribute(timeline_item): """ Get Publish attribute from input Tag object Attribute: tag (hiero.core.Tag): a tag object value (bool): True or False """ - tag_data = get_track_item_pype_tag(track_item) + tag_data = get_timeline_item_pype_tag(timeline_item) return tag_data["publish"] -def set_pype_marker(track_item, tag_data): - source_start = track_item.GetLeftOffset() - item_duration = track_item.GetDuration() +def set_pype_marker(timeline_item, tag_data): + source_start = timeline_item.GetLeftOffset() + item_duration = timeline_item.GetDuration() frame = int(source_start + (item_duration / 2)) # marker attributes @@ -486,7 +486,7 @@ def set_pype_marker(track_item, tag_data): note = json.dumps(tag_data) duration = (self.pype_marker_duration / 10) * 10 - track_item.AddMarker( + timeline_item.AddMarker( frameId, color, name, @@ -495,12 +495,12 @@ def set_pype_marker(track_item, tag_data): ) -def get_pype_marker(track_item): - track_item_markers = track_item.GetMarkers() - for marker_frame in track_item_markers: - note = track_item_markers[marker_frame]["note"] - color = track_item_markers[marker_frame]["color"] - name = track_item_markers[marker_frame]["name"] +def get_pype_marker(timeline_item): + timeline_item_markers = timeline_item.GetMarkers() + for marker_frame in timeline_item_markers: + note = timeline_item_markers[marker_frame]["note"] + color = timeline_item_markers[marker_frame]["color"] + name = timeline_item_markers[marker_frame]["name"] print(f"_ marker data: {marker_frame} | {name} | {color} | {note}") if name == self.pype_marker_name and color == self.pype_marker_color: self.temp_marker_frame = marker_frame @@ -509,8 +509,8 @@ def get_pype_marker(track_item): return dict() -def delete_pype_marker(track_item): - track_item.DeleteMarkerAtFrame(self.temp_marker_frame) +def delete_pype_marker(timeline_item): + timeline_item.DeleteMarkerAtFrame(self.temp_marker_frame) self.temp_marker_frame = None @@ -652,7 +652,7 @@ def swap_clips(from_clip, to_clip, to_clip_name, to_in_frame, to_out_frame): return False -def validate_tc(x): +def _validate_tc(x): # Validate and reformat timecode string if len(x) != 11: @@ -734,7 +734,7 @@ def set_project_manager_to_folder_name(folder_name): # go back to root folder if self.project_manager.GotoRootFolder(): log.info(f"Testing existing folder: {folder_name}") - folders = convert_resolve_list_type( + folders = _convert_resolve_list_type( self.project_manager.GetFoldersInCurrentFolder()) log.info(f"Testing existing folders: {folders}") # get me first available folder object @@ -761,7 +761,7 @@ def set_project_manager_to_folder_name(folder_name): return False -def convert_resolve_list_type(resolve_list): +def _convert_resolve_list_type(resolve_list): """ Resolve is using indexed dictionary as list type. `{1.0: 'vaule'}` This will convert it to normal list class @@ -772,52 +772,27 @@ def convert_resolve_list_type(resolve_list): return [resolve_list[i] for i in sorted(resolve_list.keys())] -def get_reformated_path(path, padded=True): - """ - Return fixed python expression path - - Args: - path (str): path url or simple file name - - Returns: - type: string with reformated path - - Example: - get_reformated_path("plate.[0001-1008].exr") > plate.%04d.exr - - """ - num_pattern = "(\\[\\d+\\-\\d+\\])" - padding_pattern = "(\\d+)(?=-)" - if "[" in path: - padding = len(re.findall(padding_pattern, path).pop()) - if padded: - path = re.sub(num_pattern, f"%0{padding}d", path) - else: - path = re.sub(num_pattern, f"%d", path) - return path - - -def create_otio_time_range_from_track_item_data(track_item_data): - track_item = track_item_data["clip"]["item"] - project = track_item_data["project"] - timeline = track_item_data["sequence"] +def create_otio_time_range_from_timeline_item_data(timeline_item_data): + timeline_item = timeline_item_data["clip"]["item"] + project = timeline_item_data["project"] + timeline = timeline_item_data["sequence"] timeline_start = timeline.GetStartFrame() - frame_start = int(track_item.GetStart() - timeline_start) - frame_duration = int(track_item.GetDuration()) + frame_start = int(timeline_item.GetStart() - timeline_start) + frame_duration = int(timeline_item.GetDuration()) fps = project.GetSetting("timelineFrameRate") return otio_export.create_otio_time_range( frame_start, frame_duration, fps) -def get_otio_clip_instance_data(otio_timeline, track_item_data): +def get_otio_clip_instance_data(otio_timeline, timeline_item_data): """ Return otio objects for timeline, track and clip Args: - track_item_data (dict): track_item_data from list returned by - resolve.get_current_track_items() + timeline_item_data (dict): timeline_item_data from list returned by + resolve.get_current_timeline_items() otio_timeline (otio.schema.Timeline): otio object Returns: @@ -825,17 +800,17 @@ def get_otio_clip_instance_data(otio_timeline, track_item_data): """ - track_item = track_item_data["clip"]["item"] - track_name = track_item_data["track"]["name"] - timeline_range = create_otio_time_range_from_track_item_data( - track_item_data) + timeline_item = timeline_item_data["clip"]["item"] + track_name = timeline_item_data["track"]["name"] + timeline_range = create_otio_time_range_from_timeline_item_data( + timeline_item_data) for otio_clip in otio_timeline.each_clip(): track_name = otio_clip.parent().name parent_range = otio_clip.range_in_parent() if track_name not in track_name: continue - if otio_clip.name not in track_item.GetName(): + if otio_clip.name not in timeline_item.GetName(): continue if pype.lib.is_overlapping_otio_ranges( parent_range, timeline_range, strict=True): diff --git a/pype/hosts/resolve/api/pipeline.py b/pype/hosts/resolve/api/pipeline.py index 2517c29426..b980c78901 100644 --- a/pype/hosts/resolve/api/pipeline.py +++ b/pype/hosts/resolve/api/pipeline.py @@ -9,20 +9,19 @@ from avalon import api as avalon from avalon import schema from avalon.pipeline import AVALON_CONTAINER_ID from pyblish import api as pyblish -import pype from pype.api import Logger from . import lib - +from . import PLUGINS_DIR log = Logger().get_logger(__name__) AVALON_CONFIG = os.environ["AVALON_CONFIG"] -LOAD_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "load") -CREATE_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "create") -INVENTORY_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "inventory") +LOAD_PATH = os.path.join(PLUGINS_DIR, "resolve", "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "resolve", "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "resolve", "inventory") PUBLISH_PATH = os.path.join( - pype.PLUGINS_DIR, "resolve", "publish" + PLUGINS_DIR, "resolve", "publish" ).replace("\\", "/") AVALON_CONTAINERS = ":AVALON_CONTAINERS" @@ -90,7 +89,7 @@ def uninstall(): pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled) -def containerise(track_item, +def containerise(timeline_item, name, namespace, context, @@ -102,14 +101,14 @@ def containerise(track_item, for loaded assets. Arguments: - track_item (hiero.core.TrackItem): object to imprint as container + timeline_item (hiero.core.TrackItem): object to imprint as container name (str): Name of resulting assembly namespace (str): Namespace under which to host container context (dict): Asset information loader (str, optional): Name of node used to produce this container. Returns: - track_item (hiero.core.TrackItem): containerised object + timeline_item (hiero.core.TrackItem): containerised object """ @@ -127,9 +126,9 @@ def containerise(track_item, data_imprint.update({k: v}) print("_ data_imprint: {}".format(data_imprint)) - lib.set_track_item_pype_tag(track_item, data_imprint) + lib.set_timeline_item_pype_tag(timeline_item, data_imprint) - return track_item + return timeline_item def ls(): @@ -144,20 +143,20 @@ def ls(): """ # get all track items from current timeline - all_track_items = lib.get_current_track_items(filter=False) + all_timeline_items = lib.get_current_timeline_items(filter=False) - for track_item_data in all_track_items: - track_item = track_item_data["clip"]["item"] - container = parse_container(track_item) + for timeline_item_data in all_timeline_items: + timeline_item = timeline_item_data["clip"]["item"] + container = parse_container(timeline_item) if container: yield container -def parse_container(track_item, validate=True): - """Return container data from track_item's pype tag. +def parse_container(timeline_item, validate=True): + """Return container data from timeline_item's pype tag. Args: - track_item (hiero.core.TrackItem): A containerised track item. + timeline_item (hiero.core.TrackItem): A containerised track item. validate (bool)[optional]: validating with avalon scheme Returns: @@ -165,7 +164,7 @@ def parse_container(track_item, validate=True): """ # convert tag metadata to normal keys names - data = lib.get_track_item_pype_tag(track_item) + data = lib.get_timeline_item_pype_tag(timeline_item) if validate and data and data.get("schema"): schema.validate(data) @@ -182,19 +181,19 @@ def parse_container(track_item, validate=True): container = {key: data[key] for key in required} - container["objectName"] = track_item.name() + container["objectName"] = timeline_item.name() # Store reference to the node object - container["_track_item"] = track_item + container["_timeline_item"] = timeline_item return container -def update_container(track_item, data=None): - """Update container data to input track_item's pype tag. +def update_container(timeline_item, data=None): + """Update container data to input timeline_item's pype tag. Args: - track_item (hiero.core.TrackItem): A containerised track item. + timeline_item (hiero.core.TrackItem): A containerised track item. data (dict)[optional]: dictionery with data to be updated Returns: @@ -203,7 +202,7 @@ def update_container(track_item, data=None): """ data = data or dict() - container = lib.get_track_item_pype_tag(track_item) + container = lib.get_timeline_item_pype_tag(timeline_item) for _key, _value in container.items(): try: @@ -211,8 +210,8 @@ def update_container(track_item, data=None): except KeyError: pass - log.info("Updating container: `{}`".format(track_item)) - return bool(lib.set_track_item_pype_tag(track_item, container)) + log.info("Updating container: `{}`".format(timeline_item)) + return bool(lib.set_timeline_item_pype_tag(timeline_item, container)) def launch_workfiles_app(*args): @@ -260,5 +259,5 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): ) # Whether instances should be passthrough based on new value - track_item = instance.data["item"] - set_publish_attribute(track_item, new_value) + timeline_item = instance.data["item"] + set_publish_attribute(timeline_item, new_value) diff --git a/pype/hosts/resolve/api/plugin.py b/pype/hosts/resolve/api/plugin.py index a94042c03e..bee470a21d 100644 --- a/pype/hosts/resolve/api/plugin.py +++ b/pype/hosts/resolve/api/plugin.py @@ -475,9 +475,9 @@ class Creator(api.Creator): self.sequence = resolve.get_current_sequence() if (self.options or {}).get("useSelection"): - self.selected = resolve.get_current_track_items(filter=True) + self.selected = resolve.get_current_timeline_items(filter=True) else: - self.selected = resolve.get_current_track_items(filter=False) + self.selected = resolve.get_current_timeline_items(filter=False) self.widget = CreatorWidget @@ -487,7 +487,7 @@ class PublishClip: Convert a track item to publishable instance Args: - track_item (hiero.core.TrackItem): hiero track item object + timeline_item (hiero.core.TrackItem): hiero track item object kwargs (optional): additional data needed for rename=True (presets) Returns: @@ -518,24 +518,24 @@ class PublishClip: vertical_sync_default = False driving_layer_default = "" - def __init__(self, cls, track_item_data, **kwargs): + def __init__(self, cls, timeline_item_data, **kwargs): # populate input cls attribute onto self.[attr] self.__dict__.update(cls.__dict__) # get main parent objects - self.track_item_data = track_item_data - self.track_item = track_item_data["clip"]["item"] - sequence_name = track_item_data["sequence"].GetName() + self.timeline_item_data = timeline_item_data + self.timeline_item = timeline_item_data["clip"]["item"] + sequence_name = timeline_item_data["sequence"].GetName() self.sequence_name = str(sequence_name).replace(" ", "_") # track item (clip) main attributes - self.ti_name = self.track_item.GetName() - self.ti_index = int(track_item_data["clip"]["index"]) + self.ti_name = self.timeline_item.GetName() + self.ti_index = int(timeline_item_data["clip"]["index"]) # get track name and index - track_name = track_item_data["track"]["name"] + track_name = timeline_item_data["track"]["name"] self.track_name = str(track_name).replace(" ", "_") - self.track_index = int(track_item_data["track"]["index"]) + self.track_index = int(timeline_item_data["track"]["index"]) # adding tag.family into tag if kwargs.get("avalon"): @@ -548,7 +548,7 @@ class PublishClip: self.mp_folder = kwargs.get("mp_folder") # populate default data before we get other attributes - self._populate_track_item_default_data() + self._populate_timeline_item_default_data() # use all populated default data to create all important attributes self._populate_attributes() @@ -577,25 +577,25 @@ class PublishClip: if not lib.pype_marker_workflow: # create compound clip workflow lib.create_compound_clip( - self.track_item_data, + self.timeline_item_data, self.tag_data["asset"], self.mp_folder ) - # add track_item_data selection to tag + # add timeline_item_data selection to tag self.tag_data.update({ - "track_data": self.track_item_data["track"] + "track_data": self.timeline_item_data["track"] }) - # create pype tag on track_item and add data - lib.imprint(self.track_item, self.tag_data) + # create pype tag on timeline_item and add data + lib.imprint(self.timeline_item, self.tag_data) - return self.track_item + return self.timeline_item - def _populate_track_item_default_data(self): + def _populate_timeline_item_default_data(self): """ Populate default formating data from track item. """ - self.track_item_default_data = { + self.timeline_item_default_data = { "_folder_": "shots", "_sequence_": self.sequence_name, "_track_": self.track_name, @@ -607,8 +607,8 @@ class PublishClip: def _populate_attributes(self): """ Populate main object attributes. """ # track item frame range and parent track name for vertical sync check - self.clip_in = int(self.track_item.GetStart()) - self.clip_out = int(self.track_item.GetEnd()) + self.clip_in = int(self.timeline_item.GetStart()) + self.clip_out = int(self.timeline_item.GetEnd()) # define ui inputs if non gui mode was used self.shot_num = self.ti_index @@ -624,7 +624,7 @@ class PublishClip: "hierarchy", {}).get("value") or self.hierarchy_default self.hierarchy_data = self.ui_inputs.get( "hierarchyData", {}).get("value") or \ - self.track_item_default_data.copy() + self.timeline_item_default_data.copy() self.count_from = self.ui_inputs.get( "countFrom", {}).get("value") or self.count_from_default self.count_steps = self.ui_inputs.get( @@ -673,7 +673,7 @@ class PublishClip: self.count_steps *= self.rename_index hierarchy_formating_data = dict() - _data = self.track_item_default_data.copy() + _data = self.timeline_item_default_data.copy() if self.ui_inputs: # adding tag metadata from ui for _k, _v in self.ui_inputs.items(): @@ -772,7 +772,7 @@ class PublishClip: return { "entity_type": entity_type, "entity_name": self.hierarchy_data[key]["value"].format( - **self.track_item_default_data + **self.timeline_item_default_data ) } diff --git a/pype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py b/pype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py index f431b44623..70ae0f904a 100644 --- a/pype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py +++ b/pype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py @@ -13,10 +13,10 @@ def main(): avalon.install(bmdvr) fpath = r"C:\CODE\_PYPE_testing\testing_data\2d_shots\sh010\plate_sh010.00999.exr" - media_pool_item = bmdvr.lib.create_media_pool_item(fpath) + media_pool_item = bmdvr.create_media_pool_item(fpath) print(media_pool_item) - track_item = bmdvr.lib.create_timeline_item(media_pool_item) + track_item = bmdvr.create_timeline_item(media_pool_item) print(track_item) From ad5d2370632cb3663be81859d4db52e0841cd4c7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 28 Jan 2021 18:12:17 +0100 Subject: [PATCH 10/17] resolve: fixing module imports after restructure --- pype/hosts/resolve/__init__.py | 5 ++- pype/hosts/resolve/api/__init__.py | 3 ++ pype/hosts/resolve/api/action.py | 2 +- pype/hosts/resolve/api/lib.py | 2 +- pype/hosts/resolve/api/pipeline.py | 16 +++------- pype/hosts/resolve/api/utils.py | 31 ++++++++++++------- pype/hosts/resolve/api/workio.py | 2 +- .../resolve/hooks}/pre_resolve_setup.py | 2 +- .../resolve/plugins/load/load_sequence.py | 2 +- .../plugins/publish/collect_instances.py | 24 +++++++------- 10 files changed, 47 insertions(+), 42 deletions(-) rename pype/{hooks/resolve => hosts/resolve/hooks}/pre_resolve_setup.py (98%) diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index aa0d54ff06..e1e81a4b71 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -43,6 +43,7 @@ from .api.lib import ( from .api.menu import launch_pype_menu from .api.plugin import ( + ClipLoader, SequenceLoader, Creator, PublishClip @@ -57,9 +58,6 @@ from .api.workio import ( work_root ) -bmdvr = None -bmdvf = None - __all__ = [ # pipeline "install", @@ -105,6 +103,7 @@ __all__ = [ "launch_pype_menu", # plugin + "ClipLoader", "SequenceLoader", "Creator", "PublishClip", diff --git a/pype/hosts/resolve/api/__init__.py b/pype/hosts/resolve/api/__init__.py index 42e92bc109..48bd938e57 100644 --- a/pype/hosts/resolve/api/__init__.py +++ b/pype/hosts/resolve/api/__init__.py @@ -3,6 +3,9 @@ resolve api """ import os +bmdvr = None +bmdvf = None + API_DIR = os.path.dirname(os.path.abspath(__file__)) HOST_DIR = os.path.dirname(API_DIR) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") diff --git a/pype/hosts/resolve/api/action.py b/pype/hosts/resolve/api/action.py index a9803cef4e..f8f338a850 100644 --- a/pype/hosts/resolve/api/action.py +++ b/pype/hosts/resolve/api/action.py @@ -21,7 +21,7 @@ class SelectInvalidAction(pyblish.api.Action): def process(self, context, plugin): try: - from . import get_project_manager + from .lib import get_project_manager pm = get_project_manager() self.log.debug(pm) except ImportError: diff --git a/pype/hosts/resolve/api/lib.py b/pype/hosts/resolve/api/lib.py index 9e8321b312..a2549eafcd 100644 --- a/pype/hosts/resolve/api/lib.py +++ b/pype/hosts/resolve/api/lib.py @@ -6,7 +6,7 @@ import contextlib from opentimelineio import opentime import pype -from .otio import davinci_export as otio_export +from ..otio import davinci_export as otio_export from pype.api import Logger diff --git a/pype/hosts/resolve/api/pipeline.py b/pype/hosts/resolve/api/pipeline.py index b980c78901..14eb0d4a76 100644 --- a/pype/hosts/resolve/api/pipeline.py +++ b/pype/hosts/resolve/api/pipeline.py @@ -14,18 +14,12 @@ from . import lib from . import PLUGINS_DIR log = Logger().get_logger(__name__) -AVALON_CONFIG = os.environ["AVALON_CONFIG"] - -LOAD_PATH = os.path.join(PLUGINS_DIR, "resolve", "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "resolve", "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "resolve", "inventory") - -PUBLISH_PATH = os.path.join( - PLUGINS_DIR, "resolve", "publish" -).replace("\\", "/") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") AVALON_CONTAINERS = ":AVALON_CONTAINERS" -# IS_HEADLESS = not hasattr(cmds, "about") or cmds.about(batch=True) def install(): @@ -39,7 +33,7 @@ def install(): See the Maya equivalent for inspiration on how to implement this. """ - from . import get_resolve_module + from .. import get_resolve_module # Disable all families except for the ones we explicitly want to see family_states = [ diff --git a/pype/hosts/resolve/api/utils.py b/pype/hosts/resolve/api/utils.py index 2a3f78a2df..32ac5b2aa9 100644 --- a/pype/hosts/resolve/api/utils.py +++ b/pype/hosts/resolve/api/utils.py @@ -7,7 +7,7 @@ Resolve's tools for setting environment import sys import os import shutil - +from . import HOST_DIR from pype.api import Logger log = Logger().get_logger(__name__) @@ -15,10 +15,10 @@ log = Logger().get_logger(__name__) def get_resolve_module(): from pype.hosts import resolve # dont run if already loaded - if resolve.bmdvr: + if resolve.api.bmdvr: log.info(("resolve module is assigned to " - f"`pype.hosts.resolve.bmdvr`: {resolve.bmdvr}")) - return resolve.bmdvr + f"`pype.hosts.resolve.api.bmdvr`: {resolve.api.bmdvr}")) + return resolve.api.bmdvr try: """ The PYTHONPATH needs to be set correctly for this import @@ -71,12 +71,12 @@ def get_resolve_module(): # assign global var and return bmdvr = bmd.scriptapp("Resolve") # bmdvf = bmd.scriptapp("Fusion") - resolve.bmdvr = bmdvr - resolve.bmdvf = bmdvr.Fusion() + resolve.api.bmdvr = bmdvr + resolve.api.bmdvf = bmdvr.Fusion() log.info(("Assigning resolve module to " - f"`pype.hosts.resolve.bmdvr`: {resolve.bmdvr}")) + f"`pype.hosts.resolve.api.bmdvr`: {resolve.api.bmdvr}")) log.info(("Assigning resolve module to " - f"`pype.hosts.resolve.bmdvf`: {resolve.bmdvf}")) + f"`pype.hosts.resolve.api.bmdvf`: {resolve.api.bmdvf}")) def _sync_utility_scripts(env=None): @@ -93,7 +93,7 @@ def _sync_utility_scripts(env=None): us_env = env.get("RESOLVE_UTILITY_SCRIPTS_SOURCE_DIR") us_dir = env.get("RESOLVE_UTILITY_SCRIPTS_DIR", "") us_paths = [os.path.join( - os.path.dirname(__file__), + HOST_DIR, "utility_scripts" )] @@ -115,7 +115,10 @@ def _sync_utility_scripts(env=None): for s in os.listdir(us_dir): path = os.path.join(us_dir, s) log.info(f"Removing `{path}`...") - os.remove(path) + if os.path.isdir(path): + shutil.rmtree(path, onerror=None) + else: + os.remove(path) # copy scripts into Resolve's utility scripts dir for d, sl in scripts.items(): @@ -125,7 +128,13 @@ def _sync_utility_scripts(env=None): src = os.path.join(d, s) dst = os.path.join(us_dir, s) log.info(f"Copying `{src}` to `{dst}`...") - shutil.copy2(src, dst) + if os.path.isdir(src): + shutil.copytree( + src, dst, symlinks=False, + ignore=None, ignore_dangling_symlinks=False + ) + else: + shutil.copy2(src, dst) def setup(env=None): diff --git a/pype/hosts/resolve/api/workio.py b/pype/hosts/resolve/api/workio.py index 18936df018..59381ae50c 100644 --- a/pype/hosts/resolve/api/workio.py +++ b/pype/hosts/resolve/api/workio.py @@ -2,7 +2,7 @@ import os from pype.api import Logger -from . import ( +from .. import ( get_project_manager, get_current_project, set_project_manager_to_folder_name diff --git a/pype/hooks/resolve/pre_resolve_setup.py b/pype/hosts/resolve/hooks/pre_resolve_setup.py similarity index 98% rename from pype/hooks/resolve/pre_resolve_setup.py rename to pype/hosts/resolve/hooks/pre_resolve_setup.py index 19a0817a0d..d19c56fe09 100644 --- a/pype/hooks/resolve/pre_resolve_setup.py +++ b/pype/hosts/resolve/hooks/pre_resolve_setup.py @@ -1,7 +1,7 @@ import os import importlib from pype.lib import PreLaunchHook -from pype.hosts.resolve import utils +from pype.hosts.resolve.api import utils class ResolvePrelaunch(PreLaunchHook): diff --git a/pype/hosts/resolve/plugins/load/load_sequence.py b/pype/hosts/resolve/plugins/load/load_sequence.py index 6cbda26025..735f60dcff 100644 --- a/pype/hosts/resolve/plugins/load/load_sequence.py +++ b/pype/hosts/resolve/plugins/load/load_sequence.py @@ -33,7 +33,7 @@ class LoadClip(resolve.SequenceLoader): }) # load clip to timeline and get main variables - timeline_item = resolve.plugin.ClipLoader( + timeline_item = resolve.ClipLoader( self, context, **options).load() namespace = namespace or timeline_item.GetName() version = context['version'] diff --git a/pype/hosts/resolve/plugins/publish/collect_instances.py b/pype/hosts/resolve/plugins/publish/collect_instances.py index 76332b03c2..b1eafd512e 100644 --- a/pype/hosts/resolve/plugins/publish/collect_instances.py +++ b/pype/hosts/resolve/plugins/publish/collect_instances.py @@ -14,20 +14,20 @@ class CollectInstances(pyblish.api.ContextPlugin): def process(self, context): otio_timeline = context.data["otioTimeline"] - selected_track_items = resolve.get_current_track_items( + selected_timeline_items = resolve.get_current_timeline_items( filter=True, selecting_color=resolve.publish_clip_color) self.log.info( "Processing enabled track items: {}".format( - len(selected_track_items))) + len(selected_timeline_items))) - for track_item_data in selected_track_items: + for timeline_item_data in selected_timeline_items: data = dict() - track_item = track_item_data["clip"]["item"] + timeline_item = timeline_item_data["clip"]["item"] # get pype tag data - tag_data = resolve.get_track_item_pype_tag(track_item) + tag_data = resolve.get_timeline_item_pype_tag(timeline_item) self.log.debug(f"__ tag_data: {pformat(tag_data)}") if not tag_data: @@ -36,7 +36,7 @@ class CollectInstances(pyblish.api.ContextPlugin): if tag_data.get("id") != "pyblish.avalon.instance": continue - media_pool_item = track_item.GetMediaPoolItem() + media_pool_item = timeline_item.GetMediaPoolItem() clip_property = media_pool_item.GetClipProperty() self.log.debug(f"clip_property: {clip_property}") @@ -57,15 +57,15 @@ class CollectInstances(pyblish.api.ContextPlugin): data.update({ "name": "{} {} {}".format(asset, subset, families), "asset": asset, - "item": track_item, + "item": timeline_item, "families": families, - "publish": resolve.get_publish_attribute(track_item), + "publish": resolve.get_publish_attribute(timeline_item), "fps": context.data["fps"] }) # otio clip data otio_data = resolve.get_otio_clip_instance_data( - otio_timeline, track_item_data) or {} + otio_timeline, timeline_item_data) or {} data.update(otio_data) # add resolution @@ -75,7 +75,7 @@ class CollectInstances(pyblish.api.ContextPlugin): instance = context.create_instance(**data) # create shot instance for shot attributes create/update - self.create_shot_instance(context, track_item, **data) + self.create_shot_instance(context, timeline_item, **data) self.log.info("Creating instance: {}".format(instance)) self.log.debug( @@ -101,7 +101,7 @@ class CollectInstances(pyblish.api.ContextPlugin): "pixelAspect": otio_tl_metadata["pixelAspect"] }) - def create_shot_instance(self, context, track_item, **data): + def create_shot_instance(self, context, timeline_item, **data): master_layer = data.get("masterLayer") hierarchy_data = data.get("hierarchyData") @@ -123,7 +123,7 @@ class CollectInstances(pyblish.api.ContextPlugin): "asset": asset, "family": family, "families": [], - "publish": resolve.get_publish_attribute(track_item) + "publish": resolve.get_publish_attribute(timeline_item) }) context.create_instance(**data) From 8ac491230cc89dd12d58e524ff09e6b7b4b2763e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 28 Jan 2021 18:45:56 +0100 Subject: [PATCH 11/17] resolve loader improvements --- pype/hosts/resolve/api/lib.py | 3 +- pype/hosts/resolve/api/plugin.py | 106 ++++++++++++++++--------------- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/pype/hosts/resolve/api/lib.py b/pype/hosts/resolve/api/lib.py index a2549eafcd..3e428bff2e 100644 --- a/pype/hosts/resolve/api/lib.py +++ b/pype/hosts/resolve/api/lib.py @@ -217,8 +217,6 @@ def create_timeline_item(media_pool_item: object, clip_property = media_pool_item.GetClipProperty() clip_name = clip_property["File Name"] timeline = timeline or get_current_sequence() - source_start = source_start or 1003 - source_end = source_end or 1005 # if timeline was used then switch it to current timeline with maintain_current_timeline(timeline): @@ -232,6 +230,7 @@ def create_timeline_item(media_pool_item: object, clip_data.update({"endFrame": source_end}) print(clip_data) + print(clip_property) # add to timeline media_pool.AppendToTimeline([clip_data]) diff --git a/pype/hosts/resolve/api/plugin.py b/pype/hosts/resolve/api/plugin.py index bee470a21d..3322d17350 100644 --- a/pype/hosts/resolve/api/plugin.py +++ b/pype/hosts/resolve/api/plugin.py @@ -279,54 +279,6 @@ class Spacer(QtWidgets.QWidget): self.setLayout(layout) -class SequenceLoader(api.Loader): - """A basic SequenceLoader for Resolve - - This will implement the basic behavior for a loader to inherit from that - will containerize the reference and will implement the `remove` and - `update` logic. - - """ - - options = [ - qargparse.Toggle( - "handles", - label="Include handles", - default=0, - help="Load with handles or without?" - ), - qargparse.Choice( - "load_to", - label="Where to load clips", - items=[ - "Current timeline", - "New timeline" - ], - default=0, - help="Where do you want clips to be loaded?" - ) - ] - - def load( - self, - context, - name=None, - namespace=None, - options=None - ): - pass - - def update(self, container, representation): - """Update an existing `container` - """ - pass - - def remove(self, container): - """Remove an existing `container` - """ - pass - - class ClipLoader: active_bin = None @@ -430,9 +382,6 @@ class ClipLoader: self.data["path"], self.active_bin) clip_property = media_pool_item.GetClipProperty() - source_in = int(clip_property["Start"]) - source_out = int(clip_property["End"]) - # get handles handle_start = self.data["versionData"].get("handleStart") handle_end = self.data["versionData"].get("handleEnd") @@ -441,6 +390,13 @@ class ClipLoader: if handle_end is None: handle_end = int(self.data["assetData"]["handleEnd"]) + source_in = int(clip_property["Start"]) + source_out = int(clip_property["End"]) + + if clip_property["Type"] == "Video": + source_in += handle_start + source_out -= handle_end + # include handles if self.with_handles: source_in -= handle_start @@ -456,6 +412,54 @@ class ClipLoader: return timeline_item +class SequenceLoader(api.Loader): + """A basic SequenceLoader for Resolve + + This will implement the basic behavior for a loader to inherit from that + will containerize the reference and will implement the `remove` and + `update` logic. + + """ + + options = [ + qargparse.Toggle( + "handles", + label="Include handles", + default=0, + help="Load with handles or without?" + ), + qargparse.Choice( + "load_to", + label="Where to load clips", + items=[ + "Current timeline", + "New timeline" + ], + default=0, + help="Where do you want clips to be loaded?" + ) + ] + + def load( + self, + context, + name=None, + namespace=None, + options=None + ): + pass + + def update(self, container, representation): + """Update an existing `container` + """ + pass + + def remove(self, container): + """Remove an existing `container` + """ + pass + + class Creator(api.Creator): """Creator class wrapper """ From 0de70c1d9267c6b5880aa7f3cca6f46b8ddd1511 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 28 Jan 2021 18:57:27 +0100 Subject: [PATCH 12/17] resolve: wip update container fix ls(), rename SequenceLoader to TimelineItemLoader --- pype/hosts/resolve/__init__.py | 4 ++-- pype/hosts/resolve/api/pipeline.py | 2 +- pype/hosts/resolve/api/plugin.py | 2 +- .../plugins/load/{load_sequence.py => load_clip.py} | 9 +++++---- 4 files changed, 9 insertions(+), 8 deletions(-) rename pype/hosts/resolve/plugins/load/{load_sequence.py => load_clip.py} (95%) diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index e1e81a4b71..0e01645cb8 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -44,7 +44,7 @@ from .api.menu import launch_pype_menu from .api.plugin import ( ClipLoader, - SequenceLoader, + TimelineItemLoader, Creator, PublishClip ) @@ -104,7 +104,7 @@ __all__ = [ # plugin "ClipLoader", - "SequenceLoader", + "TimelineItemLoader", "Creator", "PublishClip", diff --git a/pype/hosts/resolve/api/pipeline.py b/pype/hosts/resolve/api/pipeline.py index 14eb0d4a76..c46c9c07a8 100644 --- a/pype/hosts/resolve/api/pipeline.py +++ b/pype/hosts/resolve/api/pipeline.py @@ -175,7 +175,7 @@ def parse_container(timeline_item, validate=True): container = {key: data[key] for key in required} - container["objectName"] = timeline_item.name() + container["objectName"] = timeline_item.GetName() # Store reference to the node object container["_timeline_item"] = timeline_item diff --git a/pype/hosts/resolve/api/plugin.py b/pype/hosts/resolve/api/plugin.py index 3322d17350..f0c774397b 100644 --- a/pype/hosts/resolve/api/plugin.py +++ b/pype/hosts/resolve/api/plugin.py @@ -412,7 +412,7 @@ class ClipLoader: return timeline_item -class SequenceLoader(api.Loader): +class TimelineItemLoader(api.Loader): """A basic SequenceLoader for Resolve This will implement the basic behavior for a loader to inherit from that diff --git a/pype/hosts/resolve/plugins/load/load_sequence.py b/pype/hosts/resolve/plugins/load/load_clip.py similarity index 95% rename from pype/hosts/resolve/plugins/load/load_sequence.py rename to pype/hosts/resolve/plugins/load/load_clip.py index 735f60dcff..0a92293660 100644 --- a/pype/hosts/resolve/plugins/load/load_sequence.py +++ b/pype/hosts/resolve/plugins/load/load_clip.py @@ -2,7 +2,7 @@ from avalon import io, api from pype.hosts import resolve -class LoadClip(resolve.SequenceLoader): +class LoadClip(resolve.TimelineItemLoader): """Load a subset to timeline as clip Place clip to timeline on its asset origin timings collected @@ -83,7 +83,7 @@ class LoadClip(resolve.SequenceLoader): # load clip to timeline and get main variables name = container['name'] namespace = container['namespace'] - timeline_item = resolve.lib.get_pype_track_item_by_name(namespace) + timeline_item = resolve.get_pype_timeline_item_by_name(namespace) version = io.find_one({ "type": "version", "_id": representation["parent"] @@ -92,8 +92,9 @@ class LoadClip(resolve.SequenceLoader): version_name = version.get("name", None) colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) - file = api.get_representation_path(representation).replace("\\", "/") - + file = api.get_representation_path(representation) + print(timeline_item) + print(file) # TODO: implement update # # reconnect media to new path # track_item.source().reconnectMedia(file) From e43cab520b0b7729a25fc87dd14a2d3dff0c3789 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 29 Jan 2021 17:29:48 +0100 Subject: [PATCH 13/17] resolve: testing utils and update loader clip wip --- pype/hosts/resolve/__init__.py | 7 +- pype/hosts/resolve/api/lib.py | 1 + pype/hosts/resolve/api/testing_utils.py | 73 +++++++++++++++++++ pype/hosts/resolve/plugins/load/load_clip.py | 11 ++- .../utility_scripts/tests/test_utils.py | 71 ------------------ .../testing_create_timeline_item_from_path.py | 71 ++++++++++++++---- 6 files changed, 146 insertions(+), 88 deletions(-) create mode 100644 pype/hosts/resolve/api/testing_utils.py delete mode 100644 pype/hosts/resolve/utility_scripts/tests/test_utils.py diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index 0e01645cb8..59e348f626 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -58,6 +58,9 @@ from .api.workio import ( work_root ) +from .api.testing_utils import TestGUI + + __all__ = [ # pipeline "install", @@ -116,7 +119,5 @@ __all__ = [ "file_extensions", "work_root", - # singleton with black magic resolve module - "bmdvr", - "bmdvf" + "TestGUI" ] diff --git a/pype/hosts/resolve/api/lib.py b/pype/hosts/resolve/api/lib.py index 3e428bff2e..25eb4fcd75 100644 --- a/pype/hosts/resolve/api/lib.py +++ b/pype/hosts/resolve/api/lib.py @@ -166,6 +166,7 @@ def create_media_pool_item(fpath: str, if not existing_mpi: media_pool_item = media_storage.AddItemsToMediaPool(fpath) + print(media_pool_item) # pop the returned dict on first item as resolve data object is such return media_pool_item.pop(1.0) else: diff --git a/pype/hosts/resolve/api/testing_utils.py b/pype/hosts/resolve/api/testing_utils.py new file mode 100644 index 0000000000..b30fc7c24e --- /dev/null +++ b/pype/hosts/resolve/api/testing_utils.py @@ -0,0 +1,73 @@ +#! python3 +import os + + +class TestGUI: + resolve = bmd.scriptapp("Resolve") # noqa + fu = resolve.Fusion() + ui = fu.UIManager + disp = bmd.UIDispatcher(fu.UIManager) # noqa + title_font = ui.Font({"PixelSize": 18}) + _dialogue = disp.AddWindow( + { + "WindowTitle": "Get Testing folder", + "ID": "TestingWin", + "Geometry": [250, 250, 250, 100], + "Spacing": 0, + "Margin": 10 + }, + [ + ui.VGroup( + { + "Spacing": 2 + }, + [ + ui.Button( + { + "ID": "inputTestSourcesFolder", + "Text": "Select folder with testing medias", + "Weight": 1.25, + "ToolTip": ( + "Chose folder with videos, sequences, " + "single images, nested folders with " + "medias" + ), + "Flat": False + } + ), + ui.VGap(), + ui.Button( + { + "ID": "openButton", + "Text": "Process Test", + "Weight": 2, + "ToolTip": "Run the test...", + "Flat": False + } + ) + ] + ) + ] + ) + + def __init__(self): + self._widgets = self._dialogue.GetItems() + self._dialogue.On.TestingWin.Close = self._close_window + self._dialogue.On.inputTestSourcesFolder.Clicked = self._open_dir_button_pressed # noqa + self._dialogue.On.openButton.Clicked = self.process + + def _close_window(self, event): + self.disp.ExitLoop() + + def process(self, event): + # placeholder function this supposed to be run from child class + pass + + def _open_dir_button_pressed(self, event): + # placeholder function this supposed to be run from child class + pass + + def show_gui(self): + self._dialogue.Show() + self.disp.RunLoop() + self._dialogue.Hide() diff --git a/pype/hosts/resolve/plugins/load/load_clip.py b/pype/hosts/resolve/plugins/load/load_clip.py index 0a92293660..00150f1d28 100644 --- a/pype/hosts/resolve/plugins/load/load_clip.py +++ b/pype/hosts/resolve/plugins/load/load_clip.py @@ -83,7 +83,8 @@ class LoadClip(resolve.TimelineItemLoader): # load clip to timeline and get main variables name = container['name'] namespace = container['namespace'] - timeline_item = resolve.get_pype_timeline_item_by_name(namespace) + timeline_item_data = resolve.get_pype_timeline_item_by_name(namespace) + timeline_item = timeline_item_data["clip"]["item"] version = io.find_one({ "type": "version", "_id": representation["parent"] @@ -95,6 +96,14 @@ class LoadClip(resolve.TimelineItemLoader): file = api.get_representation_path(representation) print(timeline_item) print(file) + media_pool_item = resolve.create_media_pool_item(file) + + resolve.swap_clips( + timeline_item, media_pool_item, + object_name, timeline_item.GetLeftOffset(), + timeline_item.GetRightOffset() + ) + # TODO: implement update # # reconnect media to new path # track_item.source().reconnectMedia(file) diff --git a/pype/hosts/resolve/utility_scripts/tests/test_utils.py b/pype/hosts/resolve/utility_scripts/tests/test_utils.py deleted file mode 100644 index 699adab6e2..0000000000 --- a/pype/hosts/resolve/utility_scripts/tests/test_utils.py +++ /dev/null @@ -1,71 +0,0 @@ -#! python3 -import os -resolve = bmd.scriptapp("Resolve") # noqa -fu = resolve.Fusion() -ui = fu.UIManager -disp = bmd.UIDispatcher(fu.UIManager) # noqa - - - -title_font = ui.Font({"PixelSize": 18}) -dlg = disp.AddWindow( - { - "WindowTitle": "Get Testing folder", - "ID": "TestingWin", - "Geometry": [250, 250, 250, 100], - "Spacing": 0, - "Margin": 10 - }, - [ - ui.VGroup( - { - "Spacing": 2 - }, - [ - ui.Button( - { - "ID": "inputTestSourcesFolder", - "Text": "Select folder with testing medias", - "Weight": 1.25, - "ToolTip": "Chose folder with videos, sequences, single images, nested folders with medias", - "Flat": False - } - ), - ui.VGap(), - ui.Button( - { - "ID": "openButton", - "Text": "Open", - "Weight": 2, - "ToolTip": "Open and run test on the folder", - "Flat": False - } - ) - ] - ) - ] -) - -itm = dlg.GetItems() - - -def _close_window(event): - disp.ExitLoop() - - -def _import_button(event): - otio_import.read_from_file(itm["inputTestSourcesFolder"].Text) - _close_window(None) - - -def _import_file_pressed(event): - selected_path = fu.RequestFile(os.path.expanduser("~")) - itm["inputTestSourcesFolder"].Text = selected_path - - -dlg.On.TestingWin.Close = _close_window -dlg.On.inputTestSourcesFolder.Clicked = _import_file_pressed -dlg.On.openButton.Clicked = _import_button -dlg.Show() -disp.RunLoop() -dlg.Hide() diff --git a/pype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py b/pype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py index 70ae0f904a..da1f980388 100644 --- a/pype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py +++ b/pype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py @@ -1,25 +1,70 @@ #! python3 +import os import sys import avalon.api as avalon import pype +from pype.hosts.resolve import TestGUI +import pype.hosts.resolve as bmdvr +import clique -def main(): - import pype.hosts.resolve as bmdvr - # Registers pype's Global pyblish plugins - pype.install() +class ThisTestGUI(TestGUI): + extensions = [".exr", ".jpg", ".mov", ".png", ".mp4", ".ari", ".arx"] - # activate resolve from pype - avalon.install(bmdvr) + def __init__(self): + super(ThisTestGUI, self).__init__() + # Registers pype's Global pyblish plugins + pype.install() + # activate resolve from pype + avalon.install(bmdvr) - fpath = r"C:\CODE\_PYPE_testing\testing_data\2d_shots\sh010\plate_sh010.00999.exr" - media_pool_item = bmdvr.create_media_pool_item(fpath) - print(media_pool_item) + def _open_dir_button_pressed(self, event): + # selected_path = self.fu.RequestFile(os.path.expanduser("~")) + selected_path = self.fu.RequestDir(os.path.expanduser("~")) + self._widgets["inputTestSourcesFolder"].Text = selected_path - track_item = bmdvr.create_timeline_item(media_pool_item) - print(track_item) + # main function + def process(self, event): + self.input_dir_path = self._widgets["inputTestSourcesFolder"].Text + + self.dir_processing(self.input_dir_path) + + # at the end close the window + self._close_window(None) + + def dir_processing(self, dir_path): + collections, reminders = clique.assemble(os.listdir(dir_path)) + + # process reminders + for _rem in reminders: + _rem_path = os.path.join(dir_path, _rem) + + # go deeper if directory + if os.path.isdir(_rem_path): + print(_rem_path) + self.dir_processing(_rem_path) + else: + self.file_processing(_rem_path) + + # process collections + for _coll in collections: + _coll_path = os.path.join(dir_path, list(_coll).pop()) + self.file_processing(_coll_path) + + def file_processing(self, fpath): + print(f"_ fpath: `{fpath}`") + _base, ext = os.path.splitext(fpath) + # skip if unwanted extension + if ext not in self.extensions: + return + media_pool_item = bmdvr.create_media_pool_item(fpath) + print(media_pool_item) + + track_item = bmdvr.create_timeline_item(media_pool_item) + print(track_item) if __name__ == "__main__": - result = main() - sys.exit(not bool(result)) + test_gui = ThisTestGUI() + test_gui.show_gui() + sys.exit(not bool(True)) From 5339aad2c20d5fd0781d39a5ed33ee35ade29d4e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 29 Jan 2021 17:30:56 +0100 Subject: [PATCH 14/17] resolve: loader plugin added mov extension --- pype/hosts/resolve/plugins/load/load_clip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/hosts/resolve/plugins/load/load_clip.py b/pype/hosts/resolve/plugins/load/load_clip.py index 00150f1d28..90ed542555 100644 --- a/pype/hosts/resolve/plugins/load/load_clip.py +++ b/pype/hosts/resolve/plugins/load/load_clip.py @@ -10,7 +10,7 @@ class LoadClip(resolve.TimelineItemLoader): """ families = ["render2d", "source", "plate", "render", "review"] - representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"] + representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264", ".mov"] label = "Load as clip" order = -10 From 5c73fc0713532514f07e39c86969fde2aa717005 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 29 Jan 2021 18:03:57 +0100 Subject: [PATCH 15/17] resolve: rename `sequence` namespace to `timeline` --- pype/hosts/resolve/__init__.py | 4 +- pype/hosts/resolve/api/lib.py | 34 +-- pype/hosts/resolve/api/pipeline.py | 3 + pype/hosts/resolve/api/plugin.py | 12 +- .../_publish/collect_clip_resolution.py | 38 --- .../resolve/plugins/_publish/collect_clips.py | 162 ----------- .../plugins/create/create_shot_clip.py | 6 +- .../hosts/resolve/plugins/create_shot_clip.py | 263 ------------------ .../plugins/publish/collect_workfile.py | 3 +- 9 files changed, 32 insertions(+), 493 deletions(-) delete mode 100644 pype/hosts/resolve/plugins/_publish/collect_clip_resolution.py delete mode 100644 pype/hosts/resolve/plugins/_publish/collect_clips.py delete mode 100644 pype/hosts/resolve/plugins/create_shot_clip.py diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index 59e348f626..895123f05e 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -18,7 +18,7 @@ from .api.lib import ( publish_clip_color, get_project_manager, get_current_project, - get_current_sequence, + get_current_timeline, create_bin, get_media_pool_item, create_media_pool_item, @@ -81,7 +81,7 @@ __all__ = [ "publish_clip_color", "get_project_manager", "get_current_project", - "get_current_sequence", + "get_current_timeline", "create_bin", "get_media_pool_item", "create_media_pool_item", diff --git a/pype/hosts/resolve/api/lib.py b/pype/hosts/resolve/api/lib.py index 25eb4fcd75..834ec89984 100644 --- a/pype/hosts/resolve/api/lib.py +++ b/pype/hosts/resolve/api/lib.py @@ -50,10 +50,10 @@ def maintain_current_timeline(to_timeline: object, timeline2 >>> with maintain_current_timeline(to_timeline): - ... print(get_current_sequence().GetName()) + ... print(get_current_timeline().GetName()) timeline2 - >>> print(get_current_sequence().GetName()) + >>> print(get_current_timeline().GetName()) timeline1 """ project = get_current_project() @@ -91,7 +91,7 @@ def get_current_project(): return self.project_manager.GetCurrentProject() -def get_current_sequence(new=False): +def get_current_timeline(new=False): # get current project project = get_current_project() @@ -217,7 +217,7 @@ def create_timeline_item(media_pool_item: object, media_pool = project.GetMediaPool() clip_property = media_pool_item.GetClipProperty() clip_name = clip_property["File Name"] - timeline = timeline or get_current_sequence() + timeline = timeline or get_current_timeline() # if timeline was used then switch it to current timeline with maintain_current_timeline(timeline): @@ -260,7 +260,7 @@ def get_timeline_item(media_pool_item: object, clip_property = media_pool_item.GetClipProperty() clip_name = clip_property["File Name"] output_timeline_item = None - timeline = timeline or get_current_sequence() + timeline = timeline or get_current_timeline() with maintain_current_timeline(timeline): # search the timeline for the added clip @@ -277,15 +277,15 @@ def get_timeline_item(media_pool_item: object, def get_video_track_names() -> list: tracks = list() track_type = "video" - sequence = get_current_sequence() + timeline = get_current_timeline() # get all tracks count filtered by track type - selected_track_count = sequence.GetTrackCount(track_type) + selected_track_count = timeline.GetTrackCount(track_type) # loop all tracks and get items track_index: int for track_index in range(1, (int(selected_track_count) + 1)): - track_name = sequence.GetTrackName("video", track_index) + track_name = timeline.GetTrackName("video", track_index) tracks.append(track_name) return tracks @@ -301,29 +301,29 @@ def get_current_timeline_items( track_type = track_type or "video" selecting_color = selecting_color or "Chocolate" project = get_current_project() - sequence = get_current_sequence() + timeline = get_current_timeline() selected_clips = list() # get all tracks count filtered by track type - selected_track_count = sequence.GetTrackCount(track_type) + selected_track_count = timeline.GetTrackCount(track_type) # loop all tracks and get items _clips = dict() for track_index in range(1, (int(selected_track_count) + 1)): - _track_name = sequence.GetTrackName(track_type, track_index) + _track_name = timeline.GetTrackName(track_type, track_index) # filter out all unmathed track names if track_name: if _track_name not in track_name: continue - timeline_items = sequence.GetItemListInTrack( + timeline_items = timeline.GetItemListInTrack( track_type, track_index) _clips[track_index] = timeline_items _data = { "project": project, - "sequence": sequence, + "timeline": timeline, "track": { "name": _track_name, "index": track_index, @@ -529,7 +529,7 @@ def create_compound_clip(clip_data, name, folder): """ # get basic objects form data project = clip_data["project"] - sequence = clip_data["sequence"] + timeline = clip_data["timeline"] clip = clip_data["clip"] # get details of objects @@ -560,7 +560,7 @@ def create_compound_clip(clip_data, name, folder): out_frame = opentime.to_frames(mp_out_rc) # keep original sequence - sq_origin = sequence + tl_origin = timeline # Set current folder to input media_pool_folder: mp.SetCurrentFolder(folder) @@ -582,7 +582,7 @@ def create_compound_clip(clip_data, name, folder): if c.GetName() in name), None) print(f"_ cct created: {cct}") - with maintain_current_timeline(cct, sq_origin): + with maintain_current_timeline(cct, tl_origin): # Add input clip to the current timeline: mp.AppendToTimeline([{ "mediaPoolItem": mp_item, @@ -775,7 +775,7 @@ def _convert_resolve_list_type(resolve_list): def create_otio_time_range_from_timeline_item_data(timeline_item_data): timeline_item = timeline_item_data["clip"]["item"] project = timeline_item_data["project"] - timeline = timeline_item_data["sequence"] + timeline = timeline_item_data["timeline"] timeline_start = timeline.GetStartFrame() frame_start = int(timeline_item.GetStart() - timeline_start) diff --git a/pype/hosts/resolve/api/pipeline.py b/pype/hosts/resolve/api/pipeline.py index c46c9c07a8..2d08203650 100644 --- a/pype/hosts/resolve/api/pipeline.py +++ b/pype/hosts/resolve/api/pipeline.py @@ -38,6 +38,9 @@ def install(): # Disable all families except for the ones we explicitly want to see family_states = [ "imagesequence", + "render2d", + "plate", + "render", "mov", "clip" ] diff --git a/pype/hosts/resolve/api/plugin.py b/pype/hosts/resolve/api/plugin.py index f0c774397b..9bb801653f 100644 --- a/pype/hosts/resolve/api/plugin.py +++ b/pype/hosts/resolve/api/plugin.py @@ -320,9 +320,9 @@ class ClipLoader: self.active_timeline = options["timeline"] else: # create new sequence - self.active_timeline = lib.get_current_sequence(new=True) + self.active_timeline = lib.get_current_timeline(new=True) else: - self.active_timeline = lib.get_current_sequence() + self.active_timeline = lib.get_current_timeline() cls.timeline = self.active_timeline @@ -476,7 +476,7 @@ class Creator(api.Creator): # adding basic current context resolve objects self.project = resolve.get_current_project() - self.sequence = resolve.get_current_sequence() + self.timeline = resolve.get_current_timeline() if (self.options or {}).get("useSelection"): self.selected = resolve.get_current_timeline_items(filter=True) @@ -529,8 +529,8 @@ class PublishClip: # get main parent objects self.timeline_item_data = timeline_item_data self.timeline_item = timeline_item_data["clip"]["item"] - sequence_name = timeline_item_data["sequence"].GetName() - self.sequence_name = str(sequence_name).replace(" ", "_") + timeline_name = timeline_item_data["timeline"].GetName() + self.timeline_name = str(timeline_name).replace(" ", "_") # track item (clip) main attributes self.ti_name = self.timeline_item.GetName() @@ -601,7 +601,7 @@ class PublishClip: self.timeline_item_default_data = { "_folder_": "shots", - "_sequence_": self.sequence_name, + "_sequence_": self.timeline_name, "_track_": self.track_name, "_clip_": self.ti_name, "_trackIndex_": self.track_index, diff --git a/pype/hosts/resolve/plugins/_publish/collect_clip_resolution.py b/pype/hosts/resolve/plugins/_publish/collect_clip_resolution.py deleted file mode 100644 index 3bea68c677..0000000000 --- a/pype/hosts/resolve/plugins/_publish/collect_clip_resolution.py +++ /dev/null @@ -1,38 +0,0 @@ -import pyblish.api - - -class CollectClipResolution(pyblish.api.InstancePlugin): - """Collect clip geometry resolution""" - - order = pyblish.api.CollectorOrder - 0.1 - label = "Collect Clip Resoluton" - hosts = ["resolve"] - families = ["clip"] - - def process(self, instance): - sequence = instance.context.data['activeSequence'] - item = instance.data["item"] - source_resolution = instance.data.get("sourceResolution", None) - - resolution_width = int(sequence.format().width()) - resolution_height = int(sequence.format().height()) - pixel_aspect = sequence.format().pixelAspect() - - # source exception - if source_resolution: - resolution_width = int(item.source().mediaSource().width()) - resolution_height = int(item.source().mediaSource().height()) - pixel_aspect = item.source().mediaSource().pixelAspect() - - resolution_data = { - "resolutionWidth": resolution_width, - "resolutionHeight": resolution_height, - "pixelAspect": pixel_aspect - } - # add to instacne data - instance.data.update(resolution_data) - - self.log.info("Resolution of instance '{}' is: {}".format( - instance, - resolution_data - )) diff --git a/pype/hosts/resolve/plugins/_publish/collect_clips.py b/pype/hosts/resolve/plugins/_publish/collect_clips.py deleted file mode 100644 index f86e5c8384..0000000000 --- a/pype/hosts/resolve/plugins/_publish/collect_clips.py +++ /dev/null @@ -1,162 +0,0 @@ -import os -from pyblish import api -from pype.hosts import resolve -import json - - -class CollectClips(api.ContextPlugin): - """Collect all Track items selection.""" - - order = api.CollectorOrder + 0.01 - label = "Collect Clips" - hosts = ["resolve"] - - def process(self, context): - # create asset_names conversion table - if not context.data.get("assetsShared"): - self.log.debug("Created `assetsShared` in context") - context.data["assetsShared"] = dict() - - projectdata = context.data["projectEntity"]["data"] - selection = resolve.get_current_track_items( - filter=True, selecting_color="Pink") - - for clip_data in selection: - data = dict() - - # get basic objects form data - project = clip_data["project"] - sequence = clip_data["sequence"] - clip = clip_data["clip"] - - # sequence attrs - sq_frame_start = sequence.GetStartFrame() - self.log.debug(f"sq_frame_start: {sq_frame_start}") - - sq_markers = sequence.GetMarkers() - - # get details of objects - clip_item = clip["item"] - track = clip_data["track"] - - mp = project.GetMediaPool() - - # get clip attributes - clip_metadata = resolve.get_pype_clip_metadata(clip_item) - clip_metadata = json.loads(clip_metadata) - self.log.debug(f"clip_metadata: {clip_metadata}") - - compound_source_prop = clip_metadata["sourceProperties"] - self.log.debug(f"compound_source_prop: {compound_source_prop}") - - asset_name = clip_item.GetName() - mp_item = clip_item.GetMediaPoolItem() - mp_prop = mp_item.GetClipProperty() - source_first = int(compound_source_prop["Start"]) - source_last = int(compound_source_prop["End"]) - source_duration = compound_source_prop["Frames"] - fps = float(mp_prop["FPS"]) - self.log.debug(f"source_first: {source_first}") - self.log.debug(f"source_last: {source_last}") - self.log.debug(f"source_duration: {source_duration}") - self.log.debug(f"fps: {fps}") - - source_path = os.path.normpath( - compound_source_prop["File Path"]) - source_name = compound_source_prop["File Name"] - source_id = clip_metadata["sourceId"] - self.log.debug(f"source_path: {source_path}") - self.log.debug(f"source_name: {source_name}") - self.log.debug(f"source_id: {source_id}") - - clip_left_offset = int(clip_item.GetLeftOffset()) - clip_right_offset = int(clip_item.GetRightOffset()) - self.log.debug(f"clip_left_offset: {clip_left_offset}") - self.log.debug(f"clip_right_offset: {clip_right_offset}") - - # source in/out - source_in = int(source_first + clip_left_offset) - source_out = int(source_first + clip_right_offset) - self.log.debug(f"source_in: {source_in}") - self.log.debug(f"source_out: {source_out}") - - clip_in = int(clip_item.GetStart() - sq_frame_start) - clip_out = int(clip_item.GetEnd() - sq_frame_start) - clip_duration = int(clip_item.GetDuration()) - self.log.debug(f"clip_in: {clip_in}") - self.log.debug(f"clip_out: {clip_out}") - self.log.debug(f"clip_duration: {clip_duration}") - - is_sequence = False - - self.log.debug( - "__ assets_shared: {}".format( - context.data["assetsShared"])) - - # Check for clips with the same range - # this is for testing if any vertically neighbouring - # clips has been already processed - clip_matching_with_range = next( - (k for k, v in context.data["assetsShared"].items() - if (v.get("_clipIn", 0) == clip_in) - and (v.get("_clipOut", 0) == clip_out) - ), False) - - # check if clip name is the same in matched - # vertically neighbouring clip - # if it is then it is correct and resent variable to False - # not to be rised wrong name exception - if asset_name in str(clip_matching_with_range): - clip_matching_with_range = False - - # rise wrong name exception if found one - assert (not clip_matching_with_range), ( - "matching clip: {asset}" - " timeline range ({clip_in}:{clip_out})" - " conflicting with {clip_matching_with_range}" - " >> rename any of clips to be the same as the other <<" - ).format( - **locals()) - - if ("[" in source_name) and ("]" in source_name): - is_sequence = True - - data.update({ - "name": "_".join([ - track["name"], asset_name, source_name]), - "item": clip_item, - "source": mp_item, - # "timecodeStart": str(source.timecodeStart()), - "timelineStart": sq_frame_start, - "sourcePath": source_path, - "sourceFileHead": source_name, - "isSequence": is_sequence, - "track": track["name"], - "trackIndex": track["index"], - "sourceFirst": source_first, - - "sourceIn": source_in, - "sourceOut": source_out, - "mediaDuration": source_duration, - "clipIn": clip_in, - "clipOut": clip_out, - "clipDuration": clip_duration, - "asset": asset_name, - "subset": "plateMain", - "family": "clip", - "families": [], - "handleStart": projectdata.get("handleStart", 0), - "handleEnd": projectdata.get("handleEnd", 0)}) - - instance = context.create_instance(**data) - - self.log.info("Created instance: {}".format(instance)) - self.log.info("Created instance.data: {}".format(instance.data)) - - context.data["assetsShared"][asset_name] = { - "_clipIn": clip_in, - "_clipOut": clip_out - } - self.log.info( - "context.data[\"assetsShared\"]: {}".format( - context.data["assetsShared"])) diff --git a/pype/hosts/resolve/plugins/create/create_shot_clip.py b/pype/hosts/resolve/plugins/create/create_shot_clip.py index 19e613ee7a..09b2b73775 100644 --- a/pype/hosts/resolve/plugins/create/create_shot_clip.py +++ b/pype/hosts/resolve/plugins/create/create_shot_clip.py @@ -240,11 +240,11 @@ class CreateShotClip(resolve.Creator): sorted_selected_track_items.extend(unsorted_selected_track_items) # sequence attrs - sq_frame_start = self.sequence.GetStartFrame() - sq_markers = self.sequence.GetMarkers() + sq_frame_start = self.timeline.GetStartFrame() + sq_markers = self.timeline.GetMarkers() # create media bin for compound clips (trackItems) - mp_folder = resolve.create_current_sequence_media_bin(self.sequence) + mp_folder = resolve.create_current_sequence_media_bin(self.timeline) kwargs = { "ui_inputs": widget.result, diff --git a/pype/hosts/resolve/plugins/create_shot_clip.py b/pype/hosts/resolve/plugins/create_shot_clip.py deleted file mode 100644 index 19e613ee7a..0000000000 --- a/pype/hosts/resolve/plugins/create_shot_clip.py +++ /dev/null @@ -1,263 +0,0 @@ -# from pprint import pformat -from pype.hosts import resolve -from pype.hosts.resolve import lib - - -class CreateShotClip(resolve.Creator): - """Publishable clip""" - - label = "Create Publishable Clip" - family = "clip" - icon = "film" - defaults = ["Main"] - - gui_tracks = resolve.get_video_track_names() - gui_name = "Pype publish attributes creator" - gui_info = "Define sequential rename and fill hierarchy data." - gui_inputs = { - "renameHierarchy": { - "type": "section", - "label": "Shot Hierarchy And Rename Settings", - "target": "ui", - "order": 0, - "value": { - "hierarchy": { - "value": "{folder}/{sequence}", - "type": "QLineEdit", - "label": "Shot Parent Hierarchy", - "target": "tag", - "toolTip": "Parents folder for shot root folder, Template filled with `Hierarchy Data` section", # noqa - "order": 0}, - "clipRename": { - "value": False, - "type": "QCheckBox", - "label": "Rename clips", - "target": "ui", - "toolTip": "Renaming selected clips on fly", # noqa - "order": 1}, - "clipName": { - "value": "{sequence}{shot}", - "type": "QLineEdit", - "label": "Clip Name Template", - "target": "ui", - "toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa - "order": 2}, - "countFrom": { - "value": 10, - "type": "QSpinBox", - "label": "Count sequence from", - "target": "ui", - "toolTip": "Set when the sequence number stafrom", # noqa - "order": 3}, - "countSteps": { - "value": 10, - "type": "QSpinBox", - "label": "Stepping number", - "target": "ui", - "toolTip": "What number is adding every new step", # noqa - "order": 4}, - } - }, - "hierarchyData": { - "type": "dict", - "label": "Shot Template Keywords", - "target": "tag", - "order": 1, - "value": { - "folder": { - "value": "shots", - "type": "QLineEdit", - "label": "{folder}", - "target": "tag", - "toolTip": "Name of folder used for root of generated shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 0}, - "episode": { - "value": "ep01", - "type": "QLineEdit", - "label": "{episode}", - "target": "tag", - "toolTip": "Name of episode.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 1}, - "sequence": { - "value": "sq01", - "type": "QLineEdit", - "label": "{sequence}", - "target": "tag", - "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 2}, - "track": { - "value": "{_track_}", - "type": "QLineEdit", - "label": "{track}", - "target": "tag", - "toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 3}, - "shot": { - "value": "sh###", - "type": "QLineEdit", - "label": "{shot}", - "target": "tag", - "toolTip": "Name of shot. `#` is converted to paded number. \nAlso could be used with usable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa - "order": 4} - } - }, - "verticalSync": { - "type": "section", - "label": "Vertical Synchronization Of Attributes", - "target": "ui", - "order": 2, - "value": { - "vSyncOn": { - "value": True, - "type": "QCheckBox", - "label": "Enable Vertical Sync", - "target": "ui", - "toolTip": "Switch on if you want clips above each other to share its attributes", # noqa - "order": 0}, - "vSyncTrack": { - "value": gui_tracks, # noqa - "type": "QComboBox", - "label": "Master track", - "target": "ui", - "toolTip": "Select driving track name which should be mastering all others", # noqa - "order": 1} - } - }, - "publishSettings": { - "type": "section", - "label": "Publish Settings", - "target": "ui", - "order": 3, - "value": { - "subsetName": { - "value": ["", "main", "bg", "fg", "bg", - "animatic"], - "type": "QComboBox", - "label": "Subset Name", - "target": "ui", - "toolTip": "chose subset name patern, if is selected, name of track layer will be used", # noqa - "order": 0}, - "subsetFamily": { - "value": ["plate", "take"], - "type": "QComboBox", - "label": "Subset Family", - "target": "ui", "toolTip": "What use of this subset is for", # noqa - "order": 1}, - "reviewTrack": { - "value": ["< none >"] + gui_tracks, - "type": "QComboBox", - "label": "Use Review Track", - "target": "ui", - "toolTip": "Generate preview videos on fly, if `< none >` is defined nothing will be generated.", # noqa - "order": 2}, - "audio": { - "value": False, - "type": "QCheckBox", - "label": "Include audio", - "target": "tag", - "toolTip": "Process subsets with corresponding audio", # noqa - "order": 3}, - "sourceResolution": { - "value": False, - "type": "QCheckBox", - "label": "Source resolution", - "target": "tag", - "toolTip": "Is resloution taken from timeline or source?", # noqa - "order": 4}, - } - }, - "shotAttr": { - "type": "section", - "label": "Shot Attributes", - "target": "ui", - "order": 4, - "value": { - "workfileFrameStart": { - "value": 1001, - "type": "QSpinBox", - "label": "Workfiles Start Frame", - "target": "tag", - "toolTip": "Set workfile starting frame number", # noqa - "order": 0}, - "handleStart": { - "value": 0, - "type": "QSpinBox", - "label": "Handle start (head)", - "target": "tag", - "toolTip": "Handle at start of clip", # noqa - "order": 1}, - "handleEnd": { - "value": 0, - "type": "QSpinBox", - "label": "Handle end (tail)", - "target": "tag", - "toolTip": "Handle at end of clip", # noqa - "order": 2}, - } - } - } - - presets = None - - def process(self): - # get key pares from presets and match it on ui inputs - for k, v in self.gui_inputs.items(): - if v["type"] in ("dict", "section"): - # nested dictionary (only one level allowed - # for sections and dict) - for _k, _v in v["value"].items(): - if self.presets.get(_k) is not None: - self.gui_inputs[k][ - "value"][_k]["value"] = self.presets[_k] - if self.presets.get(k): - self.gui_inputs[k]["value"] = self.presets[k] - - # open widget for plugins inputs - widget = self.widget(self.gui_name, self.gui_info, self.gui_inputs) - widget.exec_() - - if len(self.selected) < 1: - return - - if not widget.result: - print("Operation aborted") - return - - self.rename_add = 0 - - # get ui output for track name for vertical sync - v_sync_track = widget.result["vSyncTrack"]["value"] - - # sort selected trackItems by - sorted_selected_track_items = list() - unsorted_selected_track_items = list() - for track_item_data in self.selected: - if track_item_data["track"]["name"] in v_sync_track: - sorted_selected_track_items.append(track_item_data) - else: - unsorted_selected_track_items.append(track_item_data) - - sorted_selected_track_items.extend(unsorted_selected_track_items) - - # sequence attrs - sq_frame_start = self.sequence.GetStartFrame() - sq_markers = self.sequence.GetMarkers() - - # create media bin for compound clips (trackItems) - mp_folder = resolve.create_current_sequence_media_bin(self.sequence) - - kwargs = { - "ui_inputs": widget.result, - "avalon": self.data, - "mp_folder": mp_folder, - "sq_frame_start": sq_frame_start, - "sq_markers": sq_markers - } - - for i, track_item_data in enumerate(sorted_selected_track_items): - self.rename_index = i - - # convert track item to timeline media pool item - track_item = resolve.PublishClip( - self, track_item_data, **kwargs).convert() - track_item.SetClipColor(lib.publish_clip_color) diff --git a/pype/hosts/resolve/plugins/publish/collect_workfile.py b/pype/hosts/resolve/plugins/publish/collect_workfile.py index 8c8e2b66c8..f7f90c9689 100644 --- a/pype/hosts/resolve/plugins/publish/collect_workfile.py +++ b/pype/hosts/resolve/plugins/publish/collect_workfile.py @@ -22,7 +22,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin): project = resolve.get_current_project() fps = project.GetSetting("timelineFrameRate") - active_sequence = resolve.get_current_sequence() + active_timeline = resolve.get_current_timeline() video_tracks = resolve.get_video_track_names() # adding otio timeline to context @@ -42,7 +42,6 @@ class CollectWorkfile(pyblish.api.ContextPlugin): # update context with main project attributes context_data = { "activeProject": project, - "activeSequence": active_sequence, "otioTimeline": otio_timeline, "videoTracks": video_tracks, "currentFile": project.GetName(), From c7b78dad5be6930cd5e338bcd16d186434d340a2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 29 Jan 2021 19:07:47 +0100 Subject: [PATCH 16/17] resolve: fix testing utils import modules --- pype/hosts/resolve/api/testing_utils.py | 94 +++++++++---------- .../utility_scripts/tests/test_otio_as_xml.py | 47 ++++++++++ 2 files changed, 93 insertions(+), 48 deletions(-) create mode 100644 pype/hosts/resolve/utility_scripts/tests/test_otio_as_xml.py diff --git a/pype/hosts/resolve/api/testing_utils.py b/pype/hosts/resolve/api/testing_utils.py index b30fc7c24e..98ad6abcf1 100644 --- a/pype/hosts/resolve/api/testing_utils.py +++ b/pype/hosts/resolve/api/testing_utils.py @@ -1,56 +1,54 @@ #! python3 -import os class TestGUI: - resolve = bmd.scriptapp("Resolve") # noqa - fu = resolve.Fusion() - ui = fu.UIManager - disp = bmd.UIDispatcher(fu.UIManager) # noqa - title_font = ui.Font({"PixelSize": 18}) - _dialogue = disp.AddWindow( - { - "WindowTitle": "Get Testing folder", - "ID": "TestingWin", - "Geometry": [250, 250, 250, 100], - "Spacing": 0, - "Margin": 10 - }, - [ - ui.VGroup( - { - "Spacing": 2 - }, - [ - ui.Button( - { - "ID": "inputTestSourcesFolder", - "Text": "Select folder with testing medias", - "Weight": 1.25, - "ToolTip": ( - "Chose folder with videos, sequences, " - "single images, nested folders with " - "medias" - ), - "Flat": False - } - ), - ui.VGap(), - ui.Button( - { - "ID": "openButton", - "Text": "Process Test", - "Weight": 2, - "ToolTip": "Run the test...", - "Flat": False - } - ) - ] - ) - ] - ) - def __init__(self): + resolve = bmd.scriptapp("Resolve") # noqa + self.fu = resolve.Fusion() + ui = self.fu.UIManager + self.disp = bmd.UIDispatcher(self.fu.UIManager) # noqa + self.title_font = ui.Font({"PixelSize": 18}) + self._dialogue = self.disp.AddWindow( + { + "WindowTitle": "Get Testing folder", + "ID": "TestingWin", + "Geometry": [250, 250, 250, 100], + "Spacing": 0, + "Margin": 10 + }, + [ + ui.VGroup( + { + "Spacing": 2 + }, + [ + ui.Button( + { + "ID": "inputTestSourcesFolder", + "Text": "Select folder with testing medias", + "Weight": 1.25, + "ToolTip": ( + "Chose folder with videos, sequences, " + "single images, nested folders with " + "medias" + ), + "Flat": False + } + ), + ui.VGap(), + ui.Button( + { + "ID": "openButton", + "Text": "Process Test", + "Weight": 2, + "ToolTip": "Run the test...", + "Flat": False + } + ) + ] + ) + ] + ) self._widgets = self._dialogue.GetItems() self._dialogue.On.TestingWin.Close = self._close_window self._dialogue.On.inputTestSourcesFolder.Clicked = self._open_dir_button_pressed # noqa diff --git a/pype/hosts/resolve/utility_scripts/tests/test_otio_as_xml.py b/pype/hosts/resolve/utility_scripts/tests/test_otio_as_xml.py new file mode 100644 index 0000000000..1fe0451003 --- /dev/null +++ b/pype/hosts/resolve/utility_scripts/tests/test_otio_as_xml.py @@ -0,0 +1,47 @@ +#! python3 +import os +import sys +import avalon.api as avalon +import pype +import opentimelineio as otio +from opentimelineio_contrib import adapters as otio_adapters +from pype.hosts.resolve import TestGUI +import pype.hosts.resolve as bmdvr +from pype.hosts.resolve.otio import davinci_export as otio_export + + +class ThisTestGUI(TestGUI): + extensions = [".exr", ".jpg", ".mov", ".png", ".mp4", ".ari", ".arx"] + + def __init__(self): + super(ThisTestGUI, self).__init__() + # Registers pype's Global pyblish plugins + pype.install() + # activate resolve from pype + avalon.install(bmdvr) + + def _open_dir_button_pressed(self, event): + # selected_path = self.fu.RequestFile(os.path.expanduser("~")) + selected_path = self.fu.RequestDir(os.path.expanduser("~")) + self._widgets["inputTestSourcesFolder"].Text = selected_path + + # main function + def process(self, event): + self.input_dir_path = self._widgets["inputTestSourcesFolder"].Text + project = bmdvr.get_current_project() + otio_timeline = otio_export.create_otio_timeline(project) + print(f"_ otio_timeline: `{otio_timeline}`") + aaf_path = os.path.join(self.input_dir_path, "this_file_name.aaf") + print(f"_ aaf_path: `{aaf_path}`") + # xml_string = otio_adapters.fcpx_xml.write_to_string(otio_timeline) + # print(f"_ xml_string: `{xml_string}`") + otio.adapters.write_to_file( + otio_timeline, aaf_path, adapter_name="AAF") + # at the end close the window + self._close_window(None) + + +if __name__ == "__main__": + test_gui = ThisTestGUI() + test_gui.show_gui() + sys.exit(not bool(True)) From 8218735ab11e17f4130d237881454cd2de19b7d7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 1 Feb 2021 12:27:19 +0100 Subject: [PATCH 17/17] resolve: load and update clip final --- pype/hosts/resolve/__init__.py | 2 + pype/hosts/resolve/api/lib.py | 6 +- pype/hosts/resolve/api/plugin.py | 32 +++++++++ pype/hosts/resolve/plugins/load/load_clip.py | 70 +++++++++---------- ...est_otio_as_xml.py => test_otio_as_edl.py} | 10 +-- 5 files changed, 76 insertions(+), 44 deletions(-) rename pype/hosts/resolve/utility_scripts/tests/{test_otio_as_xml.py => test_otio_as_edl.py} (81%) diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index 895123f05e..734e0bc5df 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -8,6 +8,7 @@ from .api.pipeline import ( uninstall, ls, containerise, + update_container, publish, launch_workfiles_app, maintained_selection @@ -67,6 +68,7 @@ __all__ = [ "uninstall", "ls", "containerise", + "update_container", "reload_pipeline", "publish", "launch_workfiles_app", diff --git a/pype/hosts/resolve/api/lib.py b/pype/hosts/resolve/api/lib.py index 834ec89984..11bf8a3217 100644 --- a/pype/hosts/resolve/api/lib.py +++ b/pype/hosts/resolve/api/lib.py @@ -609,13 +609,13 @@ def create_compound_clip(clip_data, name, folder): cct.SetClipProperty("Start TC", mp_props["Start TC"]) # swap clips on timeline - swap_clips(clip_item, cct, name, in_frame, out_frame) + swap_clips(clip_item, cct, in_frame, out_frame) cct.SetClipColor("Pink") return cct -def swap_clips(from_clip, to_clip, to_clip_name, to_in_frame, to_out_frame): +def swap_clips(from_clip, to_clip, to_in_frame, to_out_frame): """ Swaping clips on timeline in timelineItem @@ -632,6 +632,8 @@ def swap_clips(from_clip, to_clip, to_clip_name, to_in_frame, to_out_frame): bool: True if successfully replaced """ + clip_prop = to_clip.GetClipProperty() + to_clip_name = clip_prop["File Name"] # add clip item as take to timeline take = from_clip.AddTake( to_clip, diff --git a/pype/hosts/resolve/api/plugin.py b/pype/hosts/resolve/api/plugin.py index 9bb801653f..2f7c516c8f 100644 --- a/pype/hosts/resolve/api/plugin.py +++ b/pype/hosts/resolve/api/plugin.py @@ -411,6 +411,38 @@ class ClipLoader: print("Loading clips: `{}`".format(self.data["clip_name"])) return timeline_item + def update(self, timeline_item): + # create project bin for the media to be imported into + self.active_bin = lib.create_bin(self.data["binPath"]) + + # create mediaItem in active project bin + # create clip media + media_pool_item = lib.create_media_pool_item( + self.data["path"], self.active_bin) + clip_property = media_pool_item.GetClipProperty() + clip_name = clip_property["File Name"] + + # get handles + handle_start = self.data["versionData"].get("handleStart") + handle_end = self.data["versionData"].get("handleEnd") + if handle_start is None: + handle_start = int(self.data["assetData"]["handleStart"]) + if handle_end is None: + handle_end = int(self.data["assetData"]["handleEnd"]) + + source_in = int(clip_property["Start"]) + source_out = int(clip_property["End"]) + + resolve.swap_clips( + timeline_item, + media_pool_item, + source_in, + source_out + ) + + print("Loading clips: `{}`".format(self.data["clip_name"])) + return timeline_item + class TimelineItemLoader(api.Loader): """A basic SequenceLoader for Resolve diff --git a/pype/hosts/resolve/plugins/load/load_clip.py b/pype/hosts/resolve/plugins/load/load_clip.py index 90ed542555..13f713a64f 100644 --- a/pype/hosts/resolve/plugins/load/load_clip.py +++ b/pype/hosts/resolve/plugins/load/load_clip.py @@ -1,5 +1,6 @@ from avalon import io, api from pype.hosts import resolve +from copy import deepcopy class LoadClip(resolve.TimelineItemLoader): @@ -81,6 +82,8 @@ class LoadClip(resolve.TimelineItemLoader): """ # load clip to timeline and get main variables + context = deepcopy(representation["context"]) + context.update({"representation": representation}) name = container['name'] namespace = container['namespace'] timeline_item_data = resolve.get_pype_timeline_item_by_name(namespace) @@ -93,46 +96,37 @@ class LoadClip(resolve.TimelineItemLoader): version_name = version.get("name", None) colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) - file = api.get_representation_path(representation) - print(timeline_item) - print(file) - media_pool_item = resolve.create_media_pool_item(file) + self.fname = api.get_representation_path(representation) + context["version"] = {"data": version_data} - resolve.swap_clips( - timeline_item, media_pool_item, - object_name, timeline_item.GetLeftOffset(), - timeline_item.GetRightOffset() - ) + loader = resolve.ClipLoader(self, context) + timeline_item = loader.update(timeline_item) - # TODO: implement update - # # reconnect media to new path - # track_item.source().reconnectMedia(file) - # - # # add additional metadata from the version to imprint Avalon knob - # add_keys = [ - # "frameStart", "frameEnd", "source", "author", - # "fps", "handleStart", "handleEnd" - # ] - # - # # move all version data keys to tag data - # data_imprint = {} - # for key in add_keys: - # data_imprint.update({ - # key: version_data.get(key, str(None)) - # }) - # - # # add variables related to version context - # data_imprint.update({ - # "representation": str(representation["_id"]), - # "version": version_name, - # "colorspace": colorspace, - # "objectName": object_name - # }) - # - # # update color of clip regarding the version order - # self.set_item_color(timeline_item, version) - # - # return resolve.set_track_item_pype_tag(timeline_item, data_imprint) + # add additional metadata from the version to imprint Avalon knob + add_keys = [ + "frameStart", "frameEnd", "source", "author", + "fps", "handleStart", "handleEnd" + ] + + # move all version data keys to tag data + data_imprint = {} + for key in add_keys: + data_imprint.update({ + key: version_data.get(key, str(None)) + }) + + # add variables related to version context + data_imprint.update({ + "representation": str(representation["_id"]), + "version": version_name, + "colorspace": colorspace, + "objectName": object_name + }) + + # update color of clip regarding the version order + self.set_item_color(timeline_item, version) + + return resolve.update_container(timeline_item, data_imprint) @classmethod def set_item_color(cls, timeline_item, version): diff --git a/pype/hosts/resolve/utility_scripts/tests/test_otio_as_xml.py b/pype/hosts/resolve/utility_scripts/tests/test_otio_as_edl.py similarity index 81% rename from pype/hosts/resolve/utility_scripts/tests/test_otio_as_xml.py rename to pype/hosts/resolve/utility_scripts/tests/test_otio_as_edl.py index 1fe0451003..f6f9454625 100644 --- a/pype/hosts/resolve/utility_scripts/tests/test_otio_as_xml.py +++ b/pype/hosts/resolve/utility_scripts/tests/test_otio_as_edl.py @@ -4,7 +4,6 @@ import sys import avalon.api as avalon import pype import opentimelineio as otio -from opentimelineio_contrib import adapters as otio_adapters from pype.hosts.resolve import TestGUI import pype.hosts.resolve as bmdvr from pype.hosts.resolve.otio import davinci_export as otio_export @@ -31,12 +30,15 @@ class ThisTestGUI(TestGUI): project = bmdvr.get_current_project() otio_timeline = otio_export.create_otio_timeline(project) print(f"_ otio_timeline: `{otio_timeline}`") - aaf_path = os.path.join(self.input_dir_path, "this_file_name.aaf") - print(f"_ aaf_path: `{aaf_path}`") + edl_path = os.path.join(self.input_dir_path, "this_file_name.edl") + print(f"_ edl_path: `{edl_path}`") # xml_string = otio_adapters.fcpx_xml.write_to_string(otio_timeline) # print(f"_ xml_string: `{xml_string}`") otio.adapters.write_to_file( - otio_timeline, aaf_path, adapter_name="AAF") + otio_timeline, edl_path, adapter_name="cmx_3600") + project = bmdvr.get_current_project() + media_pool = project.GetMediaPool() + timeline = media_pool.ImportTimelineFromFile(edl_path) # at the end close the window self._close_window(None)