From 52feeabb447f4fd0c79ff324fd83e3caafd65678 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Dec 2020 17:47:13 +0100 Subject: [PATCH 1/5] DWAA support Redo of PR on 'master' --- pype/lib/__init__.py | 10 +- pype/lib/plugin_tools.py | 97 +++++++++++++++++++ pype/plugins/global/publish/extract_burnin.py | 48 +++++++-- pype/plugins/global/publish/extract_jpeg.py | 29 ++++-- pype/plugins/global/publish/extract_review.py | 42 ++++++-- 5 files changed, 200 insertions(+), 26 deletions(-) diff --git a/pype/lib/__init__.py b/pype/lib/__init__.py index 78fd69da983..03cab2aad2c 100644 --- a/pype/lib/__init__.py +++ b/pype/lib/__init__.py @@ -29,7 +29,11 @@ filter_pyblish_plugins, source_hash, get_unique_layer_name, - get_background_layers + get_background_layers, + oiio_supported, + decompress, + get_decompress_dir, + should_decompress ) from .path_tools import ( @@ -64,6 +68,10 @@ "filter_pyblish_plugins", "get_unique_layer_name", "get_background_layers", + "oiio_supported", + "decompress", + "get_decompress_dir", + "should_decompress", "version_up", "get_version_from_path", diff --git a/pype/lib/plugin_tools.py b/pype/lib/plugin_tools.py index f5eb354ca3a..2fc99bcf16a 100644 --- a/pype/lib/plugin_tools.py +++ b/pype/lib/plugin_tools.py @@ -5,6 +5,8 @@ import logging import re import json +import pype.api +import tempfile from ..api import config @@ -134,3 +136,98 @@ def get_background_layers(file_url): layer.get("filename")). replace("\\", "/")) return layers + + +def oiio_supported(): + """ + Checks if oiiotool is configured for this platform. + + 'should_decompress' will throw exception if configured, + but not present or working. + """ + return os.getenv("PYPE_OIIO_PATH", "") != "" + + +def decompress(target_dir, file_url, + input_frame_start=None, input_frame_end=None, log=None): + """ + Decompresses DWAA 'file_url' .exr to 'target_dir'. + + Creates uncompressed files in 'target_dir', they need to be cleaned. + + File url could be for single file or for a sequence, in that case + %0Xd will be as a placeholder for frame number AND input_frame* will + be filled. + In that case single oiio command with '--frames' will be triggered for + all frames, this should be faster then looping and running sequentially + + Args: + target_dir (str): extended from stagingDir + file_url (str): full urls to source file (with or without %0Xd) + input_frame_start (int) (optional): first frame + input_frame_end (int) (optional): last frame + log (Logger) (optional): pype logger + """ + is_sequence = input_frame_start is not None and \ + input_frame_end is not None and \ + (int(input_frame_end) > int(input_frame_start)) + + oiio_cmd = [] + oiio_cmd.append(os.getenv("PYPE_OIIO_PATH")) + + oiio_cmd.append("--compression none") + + base_file_name = os.path.basename(file_url) + oiio_cmd.append(file_url) + + if is_sequence: + oiio_cmd.append("--frames {}-{}".format(input_frame_start, + input_frame_end)) + + oiio_cmd.append("-o") + oiio_cmd.append(os.path.join(target_dir, base_file_name)) + + subprocess_exr = " ".join(oiio_cmd) + + if not log: + log = logging.getLogger(__name__) + + log.debug("Decompressing {}".format(subprocess_exr)) + pype.api.subprocess( + subprocess_exr, shell=True, logger=log + ) + + +def get_decompress_dir(): + """ + Creates temporary folder for decompressing. + Its local, in case of farm it is 'local' to the farm machine. + + Should be much faster, needs to be cleaned up later. + """ + return os.path.normpath( + tempfile.mkdtemp(prefix="pyblish_tmp_") + ) + + +def should_decompress(file_url): + """ + Tests that 'file_url' is compressed with DWAA. + + Uses 'oiio_supported' to check that OIIO tool is available for this + platform + + Args: + file_url (str): path to rendered file (in sequence it would be + first file, if that compressed it is expected that whole seq + will be too) + Returns: + (bool): 'file_url' is DWAA compressed and should be decompressed + """ + if oiio_supported(): + output = pype.api.subprocess([os.getenv("PYPE_OIIO_PATH"), + "--info", "-v", file_url]) + return "compression: \"dwaa\"" in output or \ + "compression: \"dwab\"" in output + + return False diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index 5be5060590e..79b02ed01c0 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -6,6 +6,9 @@ import pype.api import pyblish +from pype.lib import oiio_supported, should_decompress, \ + get_decompress_dir, decompress +import shutil class ExtractBurnin(pype.api.Extractor): @@ -28,7 +31,8 @@ class ExtractBurnin(pype.api.Extractor): "premiere", "standalonepublisher", "harmony", - "fusion" + "fusion", + "aftereffects" ] optional = True @@ -54,15 +58,16 @@ class ExtractBurnin(pype.api.Extractor): def process(self, instance): # ffmpeg doesn't support multipart exrs if instance.data.get("multipartExr") is True: - instance_label = ( - getattr(instance, "label", None) - or instance.data.get("label") - or instance.data.get("name") - ) - self.log.info(( - "Instance \"{}\" contain \"multipartExr\". Skipped." - ).format(instance_label)) - return + if not oiio_supported(): + instance_label = ( + getattr(instance, "label", None) + or instance.data.get("label") + or instance.data.get("name") + ) + self.log.info(( + "Instance \"{}\" contain \"multipartExr\". Skipped." + ).format(instance_label)) + return # QUESTION what is this for and should we raise an exception? if "representations" not in instance.data: @@ -212,6 +217,26 @@ def main_process(self, instance): # Prepare paths and files for process. self.input_output_paths(new_repre, temp_data, filename_suffix) + decompressed_dir = '' + full_input_path = temp_data["full_input_path"] + do_decompress = should_decompress(full_input_path) + if do_decompress: + decompressed_dir = get_decompress_dir() + + decompress( + decompressed_dir, + full_input_path, + temp_data["frame_start"], + temp_data["frame_end"], + self.log + ) + + # input path changed, 'decompressed' added + input_file = os.path.basename(full_input_path) + temp_data["full_input_path"] = os.path.join( + decompressed_dir, + input_file) + # Data for burnin script script_data = { "input": temp_data["full_input_path"], @@ -271,6 +296,9 @@ def main_process(self, instance): os.remove(filepath) self.log.debug("Removed: \"{}\"".format(filepath)) + if do_decompress and os.path.exists(decompressed_dir): + shutil.rmtree(decompressed_dir) + def prepare_basic_data(self, instance): """Pick data from instance for processing and for burnin strings. diff --git a/pype/plugins/global/publish/extract_jpeg.py b/pype/plugins/global/publish/extract_jpeg.py index 551e57796af..85bc60ddfca 100644 --- a/pype/plugins/global/publish/extract_jpeg.py +++ b/pype/plugins/global/publish/extract_jpeg.py @@ -3,6 +3,9 @@ import pyblish.api import pype.api import pype.lib +from pype.lib import oiio_supported, should_decompress, \ + get_decompress_dir, decompress +import shutil class ExtractJpegEXR(pyblish.api.InstancePlugin): @@ -22,9 +25,11 @@ def process(self, instance): if 'crypto' in instance.data['subset']: return - # ffmpeg doesn't support multipart exrs + do_decompress = False + # ffmpeg doesn't support multipart exrs, use oiiotool if available if instance.data.get("multipartExr") is True: - return + if not oiio_supported(): + return # Skip review when requested. if not instance.data.get("review", True): @@ -36,10 +41,6 @@ def process(self, instance): # filter out mov and img sequences representations_new = representations[:] - if instance.data.get("multipartExr"): - # ffmpeg doesn't support multipart exrs - return - for repre in representations: tags = repre.get("tags", []) self.log.debug(repre) @@ -60,6 +61,19 @@ def process(self, instance): full_input_path = os.path.join(stagingdir, input_file) self.log.info("input {}".format(full_input_path)) + decompressed_dir = '' + do_decompress = should_decompress(full_input_path) + if do_decompress: + decompressed_dir = get_decompress_dir() + + decompress( + decompressed_dir, + full_input_path) + # input path changed, 'decompressed' added + full_input_path = os.path.join( + decompressed_dir, + input_file) + filename = os.path.splitext(input_file)[0] if not filename.endswith('.'): filename += "." @@ -111,4 +125,7 @@ def process(self, instance): self.log.debug("Adding: {}".format(representation)) representations_new.append(representation) + if do_decompress and os.path.exists(decompressed_dir): + shutil.rmtree(decompressed_dir) + instance.data["representations"] = representations_new diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index aa8d8accb5a..a40a9435590 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -6,6 +6,8 @@ import clique import pype.api import pype.lib +from pype.lib import oiio_supported, should_decompress, \ + get_decompress_dir, decompress class ExtractReview(pyblish.api.InstancePlugin): @@ -14,7 +16,7 @@ class ExtractReview(pyblish.api.InstancePlugin): Compulsory attribute of representation is tags list with "review", otherwise the representation is ignored. - All new represetnations are created and encoded by ffmpeg following + All new representations are created and encoded by ffmpeg following presets found in `pype-config/presets/plugins/global/ publish.json:ExtractReview:outputs`. """ @@ -58,7 +60,9 @@ def process(self, instance): return # ffmpeg doesn't support multipart exrs - if instance.data.get("multipartExr") is True: + if instance.data.get("multipartExr") is True \ + and not oiio_supported(): + instance_label = ( getattr(instance, "label", None) or instance.data.get("label") @@ -318,9 +322,9 @@ def _ffmpeg_arguments(self, output_def, instance, new_repre, temp_data): Args: output_def (dict): Currently processed output definition. instance (Instance): Currently processed instance. - new_repre (dict): Reprensetation representing output of this + new_repre (dict): Representation representing output of this process. - temp_data (dict): Base data for successfull process. + temp_data (dict): Base data for successful process. """ # Get FFmpeg arguments from profile presets @@ -331,9 +335,29 @@ def _ffmpeg_arguments(self, output_def, instance, new_repre, temp_data): ffmpeg_video_filters = out_def_ffmpeg_args.get("video_filters") or [] ffmpeg_audio_filters = out_def_ffmpeg_args.get("audio_filters") or [] + input_files_urls = [os.path.join(new_repre["stagingDir"], f) for f + in new_repre['files']] + do_decompress = should_decompress(input_files_urls[0]) + if do_decompress: + # change stagingDir, decompress first + # calculate all paths with modified directory, used on too many + # places + # will be purged by cleanup.py automatically + orig_staging_dir = new_repre["stagingDir"] + new_repre["stagingDir"] = get_decompress_dir() + # Prepare input and output filepaths self.input_output_paths(new_repre, output_def, temp_data) + if do_decompress: + input_file = temp_data["full_input_path"].\ + replace(new_repre["stagingDir"], orig_staging_dir) + + decompress(new_repre["stagingDir"], input_file, + temp_data["frame_start"], + temp_data["frame_end"], + self.log) + # Set output frames len to 1 when ouput is single image if ( temp_data["output_ext_is_image"] @@ -930,7 +954,7 @@ def compile_list_of_regexes(self, in_list): return regexes def validate_value_by_regexes(self, value, in_list): - """Validates in any regexe from list match entered value. + """Validates in any regex from list match entered value. Args: in_list (list): List with regexes. @@ -955,9 +979,9 @@ def validate_value_by_regexes(self, value, in_list): def profile_exclusion(self, matching_profiles): """Find out most matching profile byt host, task and family match. - Profiles are selectivelly filtered. Each profile should have + Profiles are selectively filtered. Each profile should have "__value__" key with list of booleans. Each boolean represents - existence of filter for specific key (host, taks, family). + existence of filter for specific key (host, tasks, family). Profiles are looped in sequence. In each sequence are split into true_list and false_list. For next sequence loop are used profiles in true_list if there are any profiles else false_list is used. @@ -1036,7 +1060,7 @@ def find_matching_profile(self, host_name, task_name, family): highest_profile_points = -1 # Each profile get 1 point for each matching filter. Profile with most - # points is returnd. For cases when more than one profile will match + # points is returned. For cases when more than one profile will match # are also stored ordered lists of matching values. for profile in self.profiles: profile_points = 0 @@ -1648,7 +1672,7 @@ def legacy_process(self, instance): def add_video_filter_args(self, args, inserting_arg): """ - Fixing video filter argumets to be one long string + Fixing video filter arguments to be one long string Args: args (list): list of string arguments From b74dc3a7a79bdf6067f8c7ea7d11e0088487cf5b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 9 Dec 2020 10:07:18 +0100 Subject: [PATCH 2/5] fix(global): two types on repre["files"] support and better exception oiio_supported didn't test path existence --- pype/lib/plugin_tools.py | 6 +++++- pype/plugins/global/publish/extract_review.py | 12 +++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pype/lib/plugin_tools.py b/pype/lib/plugin_tools.py index 2fc99bcf16a..460665935a3 100644 --- a/pype/lib/plugin_tools.py +++ b/pype/lib/plugin_tools.py @@ -145,7 +145,11 @@ def oiio_supported(): 'should_decompress' will throw exception if configured, but not present or working. """ - return os.getenv("PYPE_OIIO_PATH", "") != "" + oiio_path = os.getenv("PYPE_OIIO_PATH", "") + if not os.path.exists(oiio_path) or not oiio_path: + raise IOError("Files do not exists in `{}`".format(oiio_path)) + + return True def decompress(target_dir, file_url, diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index a40a9435590..26e60fbd485 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -335,9 +335,15 @@ def _ffmpeg_arguments(self, output_def, instance, new_repre, temp_data): ffmpeg_video_filters = out_def_ffmpeg_args.get("video_filters") or [] ffmpeg_audio_filters = out_def_ffmpeg_args.get("audio_filters") or [] - input_files_urls = [os.path.join(new_repre["stagingDir"], f) for f - in new_repre['files']] - do_decompress = should_decompress(input_files_urls[0]) + if isinstance(new_repre['files'], list): + input_files_urls = [os.path.join(new_repre["stagingDir"], f) for f + in new_repre['files']] + do_decompress = should_decompress(input_files_urls[0]) + else: + test_path = os.path.join( + new_repre["stagingDir"], new_repre['files']) + do_decompress = should_decompress(test_path) + if do_decompress: # change stagingDir, decompress first # calculate all paths with modified directory, used on too many From 5ca4f470970bebbc4aea7695a1d7bb7a639b6531 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Dec 2020 11:30:24 +0100 Subject: [PATCH 3/5] Changed order of conditions --- pype/lib/plugin_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/lib/plugin_tools.py b/pype/lib/plugin_tools.py index 460665935a3..57019e6a722 100644 --- a/pype/lib/plugin_tools.py +++ b/pype/lib/plugin_tools.py @@ -146,7 +146,7 @@ def oiio_supported(): but not present or working. """ oiio_path = os.getenv("PYPE_OIIO_PATH", "") - if not os.path.exists(oiio_path) or not oiio_path: + if not oiio_path or not os.path.exists(oiio_path): raise IOError("Files do not exists in `{}`".format(oiio_path)) return True From 7d9a7c4625023ddd4082aea139b5b9d5065c1300 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Dec 2020 21:59:57 +0100 Subject: [PATCH 4/5] Rework oiio_supported - return only boolean Removed multipartExr part, not implemented yet, nothing to do with DWAA Try catch added temporarily to maximalize chance to finish publish, possible failures beause of DWAA and no oiio results in log and empty result, not an exception and hard fail. --- pype/lib/plugin_tools.py | 18 ++++++++++++--- pype/plugins/global/publish/extract_burnin.py | 19 ++++++++-------- pype/plugins/global/publish/extract_jpeg.py | 12 +++++++--- pype/plugins/global/publish/extract_review.py | 22 ++++++++++++------- 4 files changed, 47 insertions(+), 24 deletions(-) diff --git a/pype/lib/plugin_tools.py b/pype/lib/plugin_tools.py index 57019e6a722..c2b938c9bb8 100644 --- a/pype/lib/plugin_tools.py +++ b/pype/lib/plugin_tools.py @@ -142,12 +142,18 @@ def oiio_supported(): """ Checks if oiiotool is configured for this platform. + Expects full path to executable. + 'should_decompress' will throw exception if configured, - but not present or working. + but not present or not working. + Returns: + (bool) """ oiio_path = os.getenv("PYPE_OIIO_PATH", "") if not oiio_path or not os.path.exists(oiio_path): - raise IOError("Files do not exists in `{}`".format(oiio_path)) + log.debug("OIIOTool is not configured or not present at {}". + format(oiio_path)) + return False return True @@ -219,7 +225,12 @@ def should_decompress(file_url): Tests that 'file_url' is compressed with DWAA. Uses 'oiio_supported' to check that OIIO tool is available for this - platform + platform. + + Shouldn't throw exception as oiiotool is guarded by check function. + Currently implemented this way as there is no support for Mac and Linux + In the future, it should be more strict and throws exception on + misconfiguration. Args: file_url (str): path to rendered file (in sequence it would be @@ -227,6 +238,7 @@ def should_decompress(file_url): will be too) Returns: (bool): 'file_url' is DWAA compressed and should be decompressed + and we can decompress (oiiotool supported) """ if oiio_supported(): output = pype.api.subprocess([os.getenv("PYPE_OIIO_PATH"), diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index 79b02ed01c0..d9b12a5dbad 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -58,16 +58,15 @@ class ExtractBurnin(pype.api.Extractor): def process(self, instance): # ffmpeg doesn't support multipart exrs if instance.data.get("multipartExr") is True: - if not oiio_supported(): - instance_label = ( - getattr(instance, "label", None) - or instance.data.get("label") - or instance.data.get("name") - ) - self.log.info(( - "Instance \"{}\" contain \"multipartExr\". Skipped." - ).format(instance_label)) - return + instance_label = ( + getattr(instance, "label", None) + or instance.data.get("label") + or instance.data.get("name") + ) + self.log.info(( + "Instance \"{}\" contain \"multipartExr\". Skipped." + ).format(instance_label)) + return # QUESTION what is this for and should we raise an exception? if "representations" not in instance.data: diff --git a/pype/plugins/global/publish/extract_jpeg.py b/pype/plugins/global/publish/extract_jpeg.py index 85bc60ddfca..f667382665c 100644 --- a/pype/plugins/global/publish/extract_jpeg.py +++ b/pype/plugins/global/publish/extract_jpeg.py @@ -28,8 +28,7 @@ def process(self, instance): do_decompress = False # ffmpeg doesn't support multipart exrs, use oiiotool if available if instance.data.get("multipartExr") is True: - if not oiio_supported(): - return + return # Skip review when requested. if not instance.data.get("review", True): @@ -107,7 +106,14 @@ def process(self, instance): # run subprocess self.log.debug("{}".format(subprocess_jpeg)) - pype.api.subprocess(subprocess_jpeg, shell=True) + try: # temporary until oiiotool is supported cross platform + pype.api.subprocess(subprocess_jpeg, shell=True) + except RuntimeError as exp: + if "Compression" in str(exp): + self.log.debug("Unsupported compression on input files. " + + "Skipping!!!") + return + raise if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 26e60fbd485..e0caba1a204 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -60,9 +60,7 @@ def process(self, instance): return # ffmpeg doesn't support multipart exrs - if instance.data.get("multipartExr") is True \ - and not oiio_supported(): - + if instance.data.get("multipartExr") is True: instance_label = ( getattr(instance, "label", None) or instance.data.get("label") @@ -192,9 +190,17 @@ def main_process(self, instance): temp_data = self.prepare_temp_data(instance, repre, output_def) - ffmpeg_args = self._ffmpeg_arguments( - output_def, instance, new_repre, temp_data - ) + try: # temporary until oiiotool is supported cross platform + ffmpeg_args = self._ffmpeg_arguments( + output_def, instance, new_repre, temp_data + ) + except ZeroDivisionError: + if 'exr' in temp_data["origin_repre"]["ext"]: + self.log.debug("Unsupported compression on input " + + "files. Skipping!!!") + return + raise + subprcs_cmd = " ".join(ffmpeg_args) # run subprocess @@ -338,11 +344,11 @@ def _ffmpeg_arguments(self, output_def, instance, new_repre, temp_data): if isinstance(new_repre['files'], list): input_files_urls = [os.path.join(new_repre["stagingDir"], f) for f in new_repre['files']] - do_decompress = should_decompress(input_files_urls[0]) + test_path = input_files_urls[0] else: test_path = os.path.join( new_repre["stagingDir"], new_repre['files']) - do_decompress = should_decompress(test_path) + do_decompress = should_decompress(test_path) if do_decompress: # change stagingDir, decompress first From 2ca672b7b78ec457b9a4f480ba127078738d74b0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Dec 2020 22:06:53 +0100 Subject: [PATCH 5/5] Hound --- pype/lib/plugin_tools.py | 7 ++++--- pype/plugins/global/publish/extract_burnin.py | 2 +- pype/plugins/global/publish/extract_jpeg.py | 2 +- pype/plugins/global/publish/extract_review.py | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pype/lib/plugin_tools.py b/pype/lib/plugin_tools.py index c2b938c9bb8..d310ac2b8de 100644 --- a/pype/lib/plugin_tools.py +++ b/pype/lib/plugin_tools.py @@ -241,9 +241,10 @@ def should_decompress(file_url): and we can decompress (oiiotool supported) """ if oiio_supported(): - output = pype.api.subprocess([os.getenv("PYPE_OIIO_PATH"), - "--info", "-v", file_url]) + output = pype.api.subprocess([ + os.getenv("PYPE_OIIO_PATH"), + "--info", "-v", file_url]) return "compression: \"dwaa\"" in output or \ - "compression: \"dwab\"" in output + "compression: \"dwab\"" in output return False diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index d9b12a5dbad..f776846eaba 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -6,7 +6,7 @@ import pype.api import pyblish -from pype.lib import oiio_supported, should_decompress, \ +from pype.lib import should_decompress, \ get_decompress_dir, decompress import shutil diff --git a/pype/plugins/global/publish/extract_jpeg.py b/pype/plugins/global/publish/extract_jpeg.py index f667382665c..af90d4366df 100644 --- a/pype/plugins/global/publish/extract_jpeg.py +++ b/pype/plugins/global/publish/extract_jpeg.py @@ -3,7 +3,7 @@ import pyblish.api import pype.api import pype.lib -from pype.lib import oiio_supported, should_decompress, \ +from pype.lib import should_decompress, \ get_decompress_dir, decompress import shutil diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index e0caba1a204..37fe83bf10e 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -6,7 +6,7 @@ import clique import pype.api import pype.lib -from pype.lib import oiio_supported, should_decompress, \ +from pype.lib import should_decompress, \ get_decompress_dir, decompress