<filename>anima/env/fusion/__init__.py
# -*- coding: utf-8 -*-
# Copyright (c) 2012-2020, <NAME>
#
# This module is part of anima and is released under the MIT
# License: http://www.opensource.org/licenses/MIT

import os
import uuid
try:
    # for Fusion 6 and 7
    import PeyeonScript as bmf
except ImportError:
    # for Fusion 8+
    import BlackmagicFusion as bmf

from anima import logger
from anima.env import empty_reference_resolution
from anima.env.base import EnvironmentBase
from anima.recent import RecentFileManager


class Fusion(EnvironmentBase):
    """the fusion environment class
    """

    name = "Fusion"
    extensions = ['.comp']

    fusion_formats = {
        "Multimedia": {
            "id": 0,
            "Width": 320,
            "Height": 240,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 15.0
        },
        "NTSC (D1)": {
            "id": 1,
            "Width": 720,
            "Height": 486,
            "AspectX": 0.9,
            "AspectY": 1.0,
            "Rate": 29.97
        },
        "NTSC (DV)": {
            "id": 2,
            "Width": 720,
            "Height": 480,
            "AspectX": 0.9,
            "AspectY": 1.0,
            "Rate": 29.97
        },
        "NTSC (Perception)": {
            "id": 3,
            "Width": 720,
            "Height": 480,
            "AspectX": 0.9,
            "AspectY": 1.0,
            "Rate": 29.97
        },
        "NTSC (Square Pixel)": {
            "id": 4,
            "Width": 640,
            "Height": 480,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 29.97
        },
        "NTSC 16:9": {
            "id": 5,
            "Width": 720,
            "Height": 486,
            "AspectX": 1.2,
            "AspectY": 1.0,
            "Rate": 29.97
        },
        "PAL / SECAM (D1)": {
            "id": 6,
            "Width": 720,
            "Height": 576,
            "AspectX": 1.0,
            "AspectY": 0.9375,
            "Rate": 25
        },
        "PAL / SECAM (Square Pixel)": {
            "id": 7,
            "Width": 768,
            "Height": 576,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 25
        },
        "PALplus 16:9": {
            "id": 8,
            "Width": 720,
            "Height": 576,
            "AspectX": 1.0,
            "AspectY": 0.703125,
            "Rate": 25
        },
        "HDTV 720": {
            "id": 9,
            "Width": 1280,
            "Height": 720,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 30
        },
        "HDTV 1080": {
            "id": 10,
            "Width": 1920,
            "Height": 1080,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 30
        },
        "D16": {
            "id": 11,
            "Width": 2880,
            "Height": 2304,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "2K Full Aperture (Super 35)": {
            "id": 12,
            "Width": 2048,
            "Height": 1556,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "4K Full Aperture (Super 35)": {
            "id": 13,
            "Width": 4096,
            "Height": 3112,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "2K Academy (Regular 35)": {
            "id": 14,
            "Width": 1828,
            "Height": 1332,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "4K Academy (Regular 35)": {
            "id": 15,
            "Width": 3656,
            "Height": 2664,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "2K Academy in Full Aperture": {
            "id": 16,
            "Width": 2048,
            "Height": 1556,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "4K Academy in Full Aperture": {
            "id": 17,
            "Width": 4096,
            "Height": 3112,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "2K Anamorphic (CinemaScope)": {
            "id": 18,
            "Width": 1828,
            "Height": 1556,
            "AspectX": 2.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "4K Anamorphic (CinemaScope)": {
            "id": 19,
            "Width": 3656,
            "Height": 3112,
            "AspectX": 2.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "2K 1.85": {
            "id": 20,
            "Width": 1828,
            "Height": 988,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "4K 1.85": {
            "id": 21,
            "Width": 3656,
            "Height": 1976,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "3K VistaVision": {
            "id": 22,
            "Width": 3072,
            "Height": 2048,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "6K VistaVision": {
            "id": 23,
            "Width": 6144,
            "Height": 4096,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "5K IMAX 70mm": {
            "id": 24,
            "Width": 5464,
            "Height": 4096,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
    }

    def __init__(self, name='', version=None):
        """fusion specific init
        """
        super(Fusion, self).__init__(name=name, version=version)
        # and add you own modifications to __init__

        # from anima.env import blackmagic as bmd
        # self.fusion = bmd.scriptapp("Fusion")
        # self.fusion = bmd.get_fusion()

        self.fusion = bmf.scriptapp("Fusion")
        self.fusion_prefs = self.fusion.GetPrefs()['Global']

        # update name with version
        self.name = 'Fusion%s' % self.fusion.GetAttrs("FUSIONS_Version").split('.')[0]

        self.comp = self.fusion.GetCurrentComp()
        self.comp_prefs = self.comp.GetPrefs()['Comp']

        self._main_output_node_name = "Main_Output"

    def save_as(self, version, run_pre_publishers=True):
        """"the save action for fusion environment

        uses Fusions own python binding
        """
        # set the extension to '.comp'
        # refresh the current comp
        self.comp = self.fusion.GetCurrentComp()
        from stalker import Version
        assert isinstance(version, Version)
        # its a new version please update the paths
        version.update_paths()
        version.extension = self.extensions[0]
        version.created_with = self.name

        # set project_directory
        self.project_directory = os.path.dirname(version.absolute_path)

        # create the main write node
        self.create_main_saver_node(version)

        # set range from the shot
        self.set_range_from_shot(version)

        # replace read and write node paths
        # self.replace_external_paths()

        # create the path before saving
        try:
            os.makedirs(version.absolute_path)
        except OSError:
            # path already exists OSError
            pass

        version_full_path = os.path.normpath(version.absolute_full_path)

        self.comp.Lock()
        self.comp.Save(version_full_path.encode())
        self.comp.Unlock()

        # create a local copy
        self.create_local_copy(version)

        rfm = RecentFileManager()
        rfm.add(self.name, version.absolute_full_path)

        return True

    def set_range_from_shot(self, version):
        """sets the frame range from the Shot entity if this version is related
        to one.

        :param version:
        :return:
        """
        # check if this is a shot related task
        is_shot_related_task = False
        shot = None
        from stalker import Shot
        for task in version.task.parents:
            if isinstance(task, Shot):
                is_shot_related_task = True
                shot = task
                break

        fps = None
        imf = None
        if not is_shot_related_task:
            # use the Project image_format
            fps = version.task.project.fps
            imf = version.task.project.image_format
        else:
            # use the shot image_format
            if shot:
                fps = shot.fps
                imf = shot.image_format

                # set frame ranges
                self.set_frame_range(
                    start_frame=shot.cut_in,
                    end_frame=shot.cut_out,
                )

        # set comp resolution and fps
        if imf:
            self.comp.SetPrefs({
                # Image Format
                "Comp.FrameFormat.Width": imf.width,
                "Comp.FrameFormat.Height": imf.height,
                "Comp.FrameFormat.AspectY": imf.pixel_aspect,
                "Comp.FrameFormat.AspectX": imf.pixel_aspect,

                # FPS
                "Comp.FrameFormat.Rate": fps,

                # set project frame format to 16bit
                "Comp.FrameFormat.DepthFull": 2.0,
                "Comp.FrameFormat.DepthLock": True,
            })

    def export_as(self, version):
        """the export action for nuke environment
        """
        # its a new version please update the paths
        version.update_paths()
        # set the extension to '.comp'
        version.extension = self.extensions[0]
        version.created_with = self.name

        raise NotImplementedError(
            'export_as() is not implemented yet for Fusion'
        )

        # # create a local copy
        # self.create_local_copy(version)

    def open(self, version, force=False, representation=None,
             reference_depth=0, skip_update_check=False):
        """the open action for nuke environment
        """
        version_full_path = os.path.normpath(version.absolute_full_path)

        # # delete all the comps and open new one
        # comps = self.fusion.GetCompList().values()
        # for comp_ in comps:
        #     comp_.Close()

        self.fusion.LoadComp(version_full_path.encode())

        rfm = RecentFileManager()
        rfm.add(self.name, version.absolute_full_path)

        # # set the project_directory
        # self.project_directory = os.path.dirname(version.absolute_path)

        # TODO: file paths in different OS'es should be replaced with the current one
        # Check if the file paths are starting with a string matching one of
        # the OS'es project_directory path and replace them with a relative one
        # matching the current OS

        # # replace paths
        # self.replace_external_paths()

        # return True to specify everything was ok and an empty list
        # for the versions those needs to be updated
        return empty_reference_resolution()

    def import_(self, version):
        """the import action for nuke environment
        """
        # nuke.nodePaste(version.absolute_full_path)
        return True

    def get_current_version(self):
        """Finds the Version instance from the current open file.

        If it can't find any then returns None.

        :return: :class:`~oyProjectManager.models.version.Version`
        """
        # full_path = self._root.knob('name').value()
        full_path = os.path.normpath(
            self.comp.GetAttrs()['COMPS_FileName']
        ).replace('\\', '/')
        return self.get_version_from_full_path(full_path)

    def get_version_from_recent_files(self):
        """It will try to create a
        :class:`~oyProjectManager.models.version.Version` instance by looking
        at the recent files list.

        It will return None if it can not find one.

        :return: :class:`~oyProjectManager.models.version.Version`
        """
        # full_path = self.fusion_prefs["LastCompFile"]
        # return self.get_version_from_full_path(full_path)

        version = None
        rfm = RecentFileManager()

        try:
            recent_files = rfm[self.name]
        except KeyError:
            logger.debug('no recent files')
            recent_files = None

        if recent_files is not None:
            for i in range(len(recent_files)):
                version = self.get_version_from_full_path(recent_files[i])
                if version is not None:
                    break

            logger.debug("version from recent files is: %s" % version)

        return version

    def get_version_from_project_dir(self):
        """Tries to find a Version from the current project directory

        :return: :class:`~oyProjectManager.models.version.Version`
        """
        versions = self.get_versions_from_path(self.project_directory)
        version = None

        if versions and len(versions):
            version = versions[0]

        return version

    def get_last_version(self):
        """gets the file name from nuke
        """
        version = self.get_current_version()

        # read the recent file list
        if version is None:
            version = self.get_version_from_recent_files()

        # get the latest possible Version instance by using the workspace path
        if version is None:
            version = self.get_version_from_project_dir()

        return version

    def get_frame_range(self):
        """returns the current frame range
        """
        # self._root = self.get_root_node()
        # startFrame = int(self._root.knob('first_frame').value())
        # endFrame = int(self._root.knob('last_frame').value())
        start_frame = self.comp.GetAttrs()['COMPN_GlobalStart']
        end_frame = self.comp.GetAttrs()['COMPN_GlobalEnd']
        return start_frame, end_frame

    def set_frame_range(self, start_frame=1, end_frame=100,
                        adjust_frame_range=False):
        """sets the start and end frame range
        """
        self.comp.SetAttrs(
            {
                "COMPN_GlobalStart": start_frame,
                "COMPN_RenderStart": start_frame,
                "COMPN_GlobalEnd": end_frame,
                "COMPN_RenderEnd": end_frame,
            }
        )

    def set_fps(self, fps=25):
        """sets the current fps
        """
        #        self._root.knob('fps').setValue(fps)
        pass

    def get_fps(self):
        """returns the current fps
        """
        #return int(self._root.knob('fps').getValue())
        return None

    def get_main_saver_node(self):
        """Returns the main saver nodes in the scene or an empty list.
        :return: list
        """
        # list all the saver nodes in the current file
        all_saver_nodes = self.comp.GetToolList(False, 'Saver').values()

        saver_nodes = []
        for saver_node in all_saver_nodes:
            if saver_node.GetAttrs('TOOLS_Name').startswith(
               self._main_output_node_name):
                saver_nodes.append(saver_node)

        return saver_nodes

    def create_node_tree(self, node_tree):
        """Creates a node tree from the given node tree.

        The node_tree is a Python dictionary showing node types and attribute
        values. Also it can be a list of dictionaries to create more complex
        trees.

        Each node_tree can create only one shading network. The format of the
        dictionary should be as follows.

        node_tree: {
            'type': <- The fusion node type of the toppest shader
            'attr': {
                <- A dictionary that contains attribute names and values.
                'Input': {
                    'type': --- type name of the connected node
                    'attr': {
                        <- attribute values ->
                    }
                }
            },
        }

        :param [dict, list] node_tree: A dictionary showing the node tree
          attributes.

        :return:
        """
        # allow it to accept both a list or dict
        if isinstance(node_tree, list):
            created_root_nodes = []
            for item in node_tree:
                created_root_nodes.append(
                    self.create_node_tree(item)
                )
            return created_root_nodes

        node_type = node_tree['type']

        self.comp.Lock()
        node = self.comp.AddTool(node_type)
        self.comp.Unlock()

        # attributes
        if 'attr' in node_tree:
            attributes = node_tree['attr']
            for key in attributes:
                value = attributes[key]
                if isinstance(value, dict):
                    new_node = self.create_node_tree(value)
                    node.Input = new_node
                else:
                    node.SetAttrs({key: value})

        # input lists
        if 'input_list' in node_tree:
            input_list = node_tree['input_list']
            for key in input_list:
                node_input_list = node.GetInputList()
                for input_entry_key in node_input_list.keys():
                    input_entry = node_input_list[input_entry_key]
                    input_id = input_entry.GetAttrs()['INPS_ID']
                    if input_id == key:
                        value = input_list[key]
                        input_entry[0] = value
                        break

        # ref_id
        if 'ref_id' in node_tree:
            node.SetData('ref_id', node_tree['ref_id'])

        # connected to
        if 'connected_to' in node_tree:
            connected_to = node_tree['connected_to']
            if 'Input' in connected_to:
                input_node = self.create_node_tree(connected_to['Input'])
                node.Input = input_node
            elif 'ref_id' in node_tree['connected_to']:
                ref_id = node_tree['connected_to']['ref_id']
                print('ref_id: %s' % ref_id)
                # find a node with ref_id equals to ref_id that is given in the
                # node tree
                all_nodes = self.comp.GetToolList().values()
                for r_node in all_nodes:
                    node_ref_id = r_node.GetData('ref_id')
                    print('node_ref_id: %s' % node_ref_id)
                    if node_ref_id == ref_id:
                        node.Input = r_node
                        break

        return node

    def output_path_generator(self, version, file_format):
        """helper function to generate the output path

        :param version: Stalker Version instance
        :param str file_format: A string showing the file format. Ex: tga, exr
          etc.
        :return:
        """
        # generate the data needed
        # the output path
        file_name_buffer = []
        template_kwargs = {}

        # if this is a shot related task set it to shots resolution
        include_project_code = True
        if version.nice_name.startswith(version.task.project.code):
            include_project_code = False

        version_sig_name = self.get_significant_name(
            version,
            include_project_code=include_project_code
        )

        file_name_buffer.append(
            '%(version_sig_name)s.001.%(format)s'
        )
        template_kwargs.update({
            'version_sig_name': version_sig_name,
            'format': file_format
        })

        output_file_name = ''.join(file_name_buffer) % template_kwargs

        # check if it is a stereo comp
        # if it is enable separate view rendering
        output_file_path = os.path.join(
            version.absolute_path,
            'Outputs',
            version.take_name,
            # 'v%03d' % version.version_number,
            file_format
        )

        # create the dir
        try:
            os.makedirs(output_file_path)
        except OSError:
            # path exists
            pass

        output_file_full_path = os.path.join(
            output_file_path,
            output_file_name
        ).replace('\\', '/')

        # make the path Project: relative
        output_file_full_path = 'Project:%s' % os.path.relpath(
            output_file_full_path,
            os.path.dirname(version.absolute_path)
        )

        # set the output path
        return '%s' % os.path.normpath(
            output_file_full_path
        ).encode()

    def output_node_name_generator(self, file_format):
        return '%s_%s' % (self._main_output_node_name, file_format)

    def create_main_saver_node(self, version):
        """Creates the default saver node if there is no created before.

        Creates the default saver nodes if there isn't any existing outputs,
        and updates the ones that is already created
        """
        fps = 25
        if version:
            project = version.task.project
            fps = project.fps

        random_ref_id = uuid.uuid4().hex

        output_format_data = [
            {
                'name': 'jpg',
                'node_tree': {
                    'type': 'Saver',
                    'attr': {
                        'TOOLS_Name': self.output_node_name_generator('jpg'),
                    },
                    'input_list': {
                        'Clip': self.output_path_generator(version, 'jpg'),
                        'CreateDir': 1,
                        'ProcessRed': 1,
                        'ProcessGreen': 1,
                        'ProcessBlue': 1,
                        'ProcessAlpha': 0,
                        'OutputFormat': 'JPEGFormat',
                        'JpegFormat.Quality': 85,
                    },
                    'connected_to': {
                        'Input': {
                            "type": "OCIOColorSpace",
                            "ref_id": random_ref_id,
                            "input_list": {
                                "OCIOConfig": "LUTs:/OpenColorIO-Configs/aces_1.1/config.ocio",
                                "SourceSpace": "ACES - ACEScg",
                                "OutputSpace": "Output - Rec.709",
                            },
                            'connected_to': {
                                'Input': {
                                    "type": "OCIOColorSpace",
                                    "input_list": {
                                        "OCIOConfig": "LUTs:/OpenColorIO-Configs/aces_1.1/config.ocio",
                                        "SourceSpace": "Utility - Linear - sRGB",
                                        "OutputSpace": "ACES - ACEScg",
                                    },
                                }
                            }
                        }
                    }
                },
            },
            {
                'name': 'tga',
                'node_tree': {
                    'type': 'Saver',
                    'attr': {
                        'TOOLS_Name': self.output_node_name_generator('tga'),
                    },
                    'input_list': {
                        'Clip': self.output_path_generator(version, 'tga'),
                        'CreateDir': 1,
                        'ProcessRed': 1,
                        'ProcessGreen': 1,
                        'ProcessBlue': 1,
                        'ProcessAlpha': 0,
                        'OutputFormat': 'TGAFormat',
                    },
                    'connected_to': {
                        'ref_id': random_ref_id
                    }
                },
            },
            {
                'name': 'exr',
                'node_tree': {
                    'type': 'Saver',
                    'attr': {
                        'TOOLS_Name': self.output_node_name_generator('exr'),
                    },
                    'input_list': {
                        'Clip': self.output_path_generator(version, 'exr'),
                        'CreateDir': 1,
                        'ProcessRed': 1,
                        'ProcessGreen': 1,
                        'ProcessBlue': 1,
                        'ProcessAlpha': 1,
                        'OutputFormat': 'OpenEXRFormat',
                        'OpenEXRFormat.Depth': 1,  # 16-bit float
                        'OpenEXRFormat.RedEnable': 1,
                        'OpenEXRFormat.GreenEnable': 1,
                        'OpenEXRFormat.BlueEnable': 1,
                        'OpenEXRFormat.AlphaEnable': 1,
                        'OpenEXRFormat.ZEnable': 0,
                        'OpenEXRFormat.CovEnable': 0,
                        'OpenEXRFormat.ObjIDEnable': 0,
                        'OpenEXRFormat.MatIDEnable': 0,
                        'OpenEXRFormat.UEnable': 0,
                        'OpenEXRFormat.VEnable': 0,
                        'OpenEXRFormat.XNormEnable': 0,
                        'OpenEXRFormat.YNormEnable': 0,
                        'OpenEXRFormat.ZNormEnable': 0,
                        'OpenEXRFormat.XVelEnable': 0,
                        'OpenEXRFormat.YVelEnable': 0,
                        'OpenEXRFormat.XRevVelEnable': 0,
                        'OpenEXRFormat.YRevVelEnable': 0,
                        'OpenEXRFormat.XPosEnable': 0,
                        'OpenEXRFormat.YPosEnable': 0,
                        'OpenEXRFormat.ZPosEnable': 0,
                        'OpenEXRFormat.XDispEnable': 0,
                        'OpenEXRFormat.YDispEnable': 0,
                    },
                    'connected_to': {
                        'ref_id': random_ref_id
                    }
                }
            },
            {
                'name': 'mp4',
                'node_tree': {
                    'type': 'Saver',
                    'attr': {
                        'TOOLS_Name': self.output_node_name_generator('mp4'),
                    },
                    'input_list': {
                        'Clip': self.output_path_generator(version, 'mp4'),
                        'CreateDir': 1,
                        'ProcessRed': 1,
                        'ProcessGreen': 1,
                        'ProcessBlue': 1,
                        'ProcessAlpha': 0,
                        'OutputFormat': 'QuickTimeMovies',
                        'ProcessMode': 'Auto',
                        'SaveFrames': 'Full',
                        'QuickTimeMovies.Compression': 'H.264_avc1',
                        'QuickTimeMovies.Quality': 95.0,
                        'QuickTimeMovies.FrameRateFps': fps,
                        'QuickTimeMovies.KeyFrames': 5,
                        'StartRenderScript': 'frames_at_once = comp:GetPrefs("Comp.Memory.FramesAtOnce")\ncomp:SetPrefs("Comp.Memory.FramesAtOnce", 1)',
                        'EndRenderScript': 'comp:SetPrefs("Comp.Memory.FramesAtOnce", frames_at_once)',
                    },
                    'connected_to': {
                        'ref_id': random_ref_id
                    }
                }
            },
            {
                'name': 'mov',
                'node_tree': {
                    'type': 'Saver',
                    'attr': {
                        'TOOLS_Name': self.output_node_name_generator('mov'),
                    },
                    'input_list': {
                        'Clip': self.output_path_generator(version, 'mov'),
                        'CreateDir': 1,
                        'ProcessRed': 1,
                        'ProcessGreen': 1,
                        'ProcessBlue': 1,
                        'ProcessAlpha': 0,
                        'OutputFormat': 'QuickTimeMovies',
                        'ProcessMode': 'Auto',
                        'SaveFrames': 'Full',
                        'QuickTimeMovies.Compression': 'Apple ProRes 422 HQ_apch',
                        'QuickTimeMovies.Quality': 95.0,
                        'QuickTimeMovies.FrameRateFps': fps,
                        'QuickTimeMovies.KeyFrames': 5,

                        'QuickTimeMovies.LimitDataRate': 0.0,
                        'QuickTimeMovies.DataRateK': 1000.0,
                        'QuickTimeMovies.Advanced': 1.0,
                        'QuickTimeMovies.Primaries': 0.0,
                        'QuickTimeMovies.Transfer': 0.0,
                        'QuickTimeMovies.Matrix': 0.0,
                        'QuickTimeMovies.PixelAspectRatio': 0.0,
                        'QuickTimeMovies.ErrorDiffusion': 1.0,
                        'QuickTimeMovies.SaveAlphaChannel': 1.0,

                        'StartRenderScript': 'frames_at_once = comp:GetPrefs("Comp.Memory.FramesAtOnce")\ncomp:SetPrefs("Comp.Memory.FramesAtOnce", 1)',
                        'EndRenderScript': 'comp:SetPrefs("Comp.Memory.FramesAtOnce", frames_at_once)',

                    },
                    'connected_to': {
                        'ref_id': random_ref_id
                    }
                }
            },
        ]

        if version.task.type and version.task.type.name == 'Plate':
            # create a different type of outputs
            output_format_data = [
                {
                    'name': 'jpg',
                    'node_tree': {
                        'type': 'Saver',
                        'attr': {
                            'TOOLS_Name': self.output_node_name_generator('jpg'),
                        },
                        'input_list': {
                            'Clip': self.output_path_generator(version, 'jpg'),
                            'CreateDir': 1,
                            'ProcessRed': 1,
                            'ProcessGreen': 1,
                            'ProcessBlue': 1,
                            'ProcessAlpha': 0,
                            'OutputFormat': 'JPEGFormat',
                            'JpegFormat.Quality': 85,
                        },
                    },
                },
                {
                    'name': 'exr',
                    'node_tree': {
                        'type': 'Saver',
                        'attr': {
                            'TOOLS_Name': self.output_node_name_generator('exr'),
                        },
                        'input_list': {
                            'Clip': self.output_path_generator(version, 'exr'),
                            'CreateDir': 1,
                            'ProcessRed': 1,
                            'ProcessGreen': 1,
                            'ProcessBlue': 1,
                            'ProcessAlpha': 0,
                            'OutputFormat': 'OpenEXRFormat',
                            'OpenEXRFormat.Depth': 1,  # 16-bit float
                            'OpenEXRFormat.RedEnable': 1,
                            'OpenEXRFormat.GreenEnable': 1,
                            'OpenEXRFormat.BlueEnable': 1,
                            'OpenEXRFormat.AlphaEnable': 0,
                            'OpenEXRFormat.ZEnable': 0,
                            'OpenEXRFormat.CovEnable': 0,
                            'OpenEXRFormat.ObjIDEnable': 0,
                            'OpenEXRFormat.MatIDEnable': 0,
                            'OpenEXRFormat.UEnable': 0,
                            'OpenEXRFormat.VEnable': 0,
                            'OpenEXRFormat.XNormEnable': 0,
                            'OpenEXRFormat.YNormEnable': 0,
                            'OpenEXRFormat.ZNormEnable': 0,
                            'OpenEXRFormat.XVelEnable': 0,
                            'OpenEXRFormat.YVelEnable': 0,
                            'OpenEXRFormat.XRevVelEnable': 0,
                            'OpenEXRFormat.YRevVelEnable': 0,
                            'OpenEXRFormat.XPosEnable': 0,
                            'OpenEXRFormat.YPosEnable': 0,
                            'OpenEXRFormat.ZPosEnable': 0,
                            'OpenEXRFormat.XDispEnable': 0,
                            'OpenEXRFormat.YDispEnable': 0,
                        },
                    },
                },
                # {
                #     'name': 'mp4',
                #     'node_tree': {
                #         'type': 'Saver',
                #         'attr': {
                #             'TOOLS_Name': self.output_node_name_generator('mp4'),
                #         },
                #         'input_list': {
                #             'Clip': self.output_path_generator(version, 'mp4'),
                #             'CreateDir': 1,
                #             'ProcessRed': 1,
                #             'ProcessGreen': 1,
                #             'ProcessBlue': 1,
                #             'ProcessAlpha': 0,
                #             'OutputFormat': 'QuickTimeMovies',
                #             'ProcessMode': 'Auto',
                #             'SaveFrames': 'Full',
                #             'QuickTimeMovies.Compression': 'H.264_avc1',
                #             'QuickTimeMovies.Quality': 95.0,
                #             'QuickTimeMovies.FrameRateFps': fps,
                #             'QuickTimeMovies.KeyFrames': 5,
                #             'StartRenderScript': 'frames_at_once = comp:GetPrefs("Comp.Memory.FramesAtOnce")\ncomp:SetPrefs("Comp.Memory.FramesAtOnce", 1)',
                #             'EndRenderScript': 'comp:SetPrefs("Comp.Memory.FramesAtOnce", frames_at_once)',
                #         },
                #     },
                # },
                {
                    'name': 'mov',
                    'node_tree': {
                        'type': 'Saver',
                        'attr': {
                            'TOOLS_Name': self.output_node_name_generator('mov'),
                        },
                        'input_list': {
                            'Clip': self.output_path_generator(version, 'mov'),
                            'CreateDir': 1,
                            'ProcessRed': 1,
                            'ProcessGreen': 1,
                            'ProcessBlue': 1,
                            'ProcessAlpha': 0,
                            'OutputFormat': 'QuickTimeMovies',
                            'ProcessMode': 'Auto',
                            'SaveFrames': 'Full',
                            'QuickTimeMovies.Compression': 'Apple ProRes 422 HQ_apch',
                            'QuickTimeMovies.Quality': 95.0,
                            'QuickTimeMovies.FrameRateFps': fps,
                            'QuickTimeMovies.KeyFrames': 5,

                            'QuickTimeMovies.LimitDataRate': 0.0,
                            'QuickTimeMovies.DataRateK': 1000.0,
                            'QuickTimeMovies.Advanced': 1.0,
                            'QuickTimeMovies.Primaries': 0.0,
                            'QuickTimeMovies.Transfer': 0.0,
                            'QuickTimeMovies.Matrix': 0.0,
                            'QuickTimeMovies.PixelAspectRatio': 0.0,
                            'QuickTimeMovies.ErrorDiffusion': 1.0,
                            'QuickTimeMovies.SaveAlphaChannel': 1.0,

                            'StartRenderScript': 'frames_at_once = comp:GetPrefs("Comp.Memory.FramesAtOnce")\ncomp:SetPrefs("Comp.Memory.FramesAtOnce", 1)',
                            'EndRenderScript': 'comp:SetPrefs("Comp.Memory.FramesAtOnce", frames_at_once)',
                        },
                    },
                },
            ]

        # selectively generate output format
        saver_nodes = self.get_main_saver_node()

        for data in output_format_data:
            format_name = data['name']
            node_tree = data['node_tree']

            # now check if a node with the same name exists
            format_node = None
            format_node_name = self.output_node_name_generator(format_name)
            for node in saver_nodes:
                node_name = node.GetAttrs('TOOLS_Name')
                if node_name.startswith(format_node_name):
                    format_node = node
                    break

            # create the saver node for this format if missing
            if not format_node:
                self.create_node_tree(node_tree)
            else:
                # just update the input_lists
                if 'input_list' in node_tree:
                    input_list = node_tree['input_list']
                    for key in input_list:
                        node_input_list = format_node.GetInputList()
                        for input_entry_key in node_input_list.keys():
                            input_entry = node_input_list[input_entry_key]
                            input_id = input_entry.GetAttrs()['INPS_ID']
                            if input_id == key:
                                value = input_list[key]
                                input_entry[0] = value
                                break

            try:
                os.makedirs(
                    os.path.dirname(
                        self.output_path_generator(version, format_name)
                    )
                )
            except OSError:
                # path already exists
                pass

    @property
    def project_directory(self):
        """The project directory.

        Set it to the project root, and set all your paths relative to this
        directory.
        """

        # try to figure it out from the maps
        # search for Project path

        project_dir = None
        maps = self.comp_prefs['Paths'].get('Map', None)
        if maps:
            project_dir = maps.get('Project:', None)

        # if not project_dir:
        #     # set the map for the project dir
        #     if self.version:
        #         project_dir = os.path.dirname(self.version.absolute_path)
        #         self.project_directory = project_dir

        return project_dir

    @project_directory.setter
    def project_directory(self, project_directory_in):
        """Sets project directory

        :param str project_directory_in: the project directory
        :return:
        """

        project_directory_in = os.path.normpath(project_directory_in)
        print('setting project directory to: %s' % project_directory_in)

        # set a path map
        self.comp.SetPrefs(
            {
                'Comp.Paths.Map': {
                    'Project:': project_directory_in.encode()
                }
            }
        )

<filename>anima/env/fusion/__init__.py
# -*- coding: utf-8 -*-
# Copyright (c) 2012-2020, <NAME>
#
# This module is part of anima and is released under the MIT
# License: http://www.opensource.org/licenses/MIT

import os
import uuid
try:
    # for Fusion 6 and 7
    import PeyeonScript as bmf
except ImportError:
    # for Fusion 8+
    import BlackmagicFusion as bmf

from anima import logger
from anima.env import empty_reference_resolution
from anima.env.base import EnvironmentBase
from anima.recent import RecentFileManager


class Fusion(EnvironmentBase):
    """the fusion environment class
    """

    name = "Fusion"
    extensions = ['.comp']

    fusion_formats = {
        "Multimedia": {
            "id": 0,
            "Width": 320,
            "Height": 240,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 15.0
        },
        "NTSC (D1)": {
            "id": 1,
            "Width": 720,
            "Height": 486,
            "AspectX": 0.9,
            "AspectY": 1.0,
            "Rate": 29.97
        },
        "NTSC (DV)": {
            "id": 2,
            "Width": 720,
            "Height": 480,
            "AspectX": 0.9,
            "AspectY": 1.0,
            "Rate": 29.97
        },
        "NTSC (Perception)": {
            "id": 3,
            "Width": 720,
            "Height": 480,
            "AspectX": 0.9,
            "AspectY": 1.0,
            "Rate": 29.97
        },
        "NTSC (Square Pixel)": {
            "id": 4,
            "Width": 640,
            "Height": 480,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 29.97
        },
        "NTSC 16:9": {
            "id": 5,
            "Width": 720,
            "Height": 486,
            "AspectX": 1.2,
            "AspectY": 1.0,
            "Rate": 29.97
        },
        "PAL / SECAM (D1)": {
            "id": 6,
            "Width": 720,
            "Height": 576,
            "AspectX": 1.0,
            "AspectY": 0.9375,
            "Rate": 25
        },
        "PAL / SECAM (Square Pixel)": {
            "id": 7,
            "Width": 768,
            "Height": 576,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 25
        },
        "PALplus 16:9": {
            "id": 8,
            "Width": 720,
            "Height": 576,
            "AspectX": 1.0,
            "AspectY": 0.703125,
            "Rate": 25
        },
        "HDTV 720": {
            "id": 9,
            "Width": 1280,
            "Height": 720,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 30
        },
        "HDTV 1080": {
            "id": 10,
            "Width": 1920,
            "Height": 1080,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 30
        },
        "D16": {
            "id": 11,
            "Width": 2880,
            "Height": 2304,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "2K Full Aperture (Super 35)": {
            "id": 12,
            "Width": 2048,
            "Height": 1556,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "4K Full Aperture (Super 35)": {
            "id": 13,
            "Width": 4096,
            "Height": 3112,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "2K Academy (Regular 35)": {
            "id": 14,
            "Width": 1828,
            "Height": 1332,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "4K Academy (Regular 35)": {
            "id": 15,
            "Width": 3656,
            "Height": 2664,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "2K Academy in Full Aperture": {
            "id": 16,
            "Width": 2048,
            "Height": 1556,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "4K Academy in Full Aperture": {
            "id": 17,
            "Width": 4096,
            "Height": 3112,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "2K Anamorphic (CinemaScope)": {
            "id": 18,
            "Width": 1828,
            "Height": 1556,
            "AspectX": 2.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "4K Anamorphic (CinemaScope)": {
            "id": 19,
            "Width": 3656,
            "Height": 3112,
            "AspectX": 2.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "2K 1.85": {
            "id": 20,
            "Width": 1828,
            "Height": 988,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "4K 1.85": {
            "id": 21,
            "Width": 3656,
            "Height": 1976,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "3K VistaVision": {
            "id": 22,
            "Width": 3072,
            "Height": 2048,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "6K VistaVision": {
            "id": 23,
            "Width": 6144,
            "Height": 4096,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
        "5K IMAX 70mm": {
            "id": 24,
            "Width": 5464,
            "Height": 4096,
            "AspectX": 1.0,
            "AspectY": 1.0,
            "Rate": 24
        },
    }

    def __init__(self, name='', version=None):
        """fusion specific init
        """
        super(Fusion, self).__init__(name=name, version=version)
        # and add you own modifications to __init__

        # from anima.env import blackmagic as bmd
        # self.fusion = bmd.scriptapp("Fusion")
        # self.fusion = bmd.get_fusion()

        self.fusion = bmf.scriptapp("Fusion")
        self.fusion_prefs = self.fusion.GetPrefs()['Global']

        # update name with version
        self.name = 'Fusion%s' % self.fusion.GetAttrs("FUSIONS_Version").split('.')[0]

        self.comp = self.fusion.GetCurrentComp()
        self.comp_prefs = self.comp.GetPrefs()['Comp']

        self._main_output_node_name = "Main_Output"

    def save_as(self, version, run_pre_publishers=True):
        """"the save action for fusion environment

        uses Fusions own python binding
        """
        # set the extension to '.comp'
        # refresh the current comp
        self.comp = self.fusion.GetCurrentComp()
        from stalker import Version
        assert isinstance(version, Version)
        # its a new version please update the paths
        version.update_paths()
        version.extension = self.extensions[0]
        version.created_with = self.name

        # set project_directory
        self.project_directory = os.path.dirname(version.absolute_path)

        # create the main write node
        self.create_main_saver_node(version)

        # set range from the shot
        self.set_range_from_shot(version)

        # replace read and write node paths
        # self.replace_external_paths()

        # create the path before saving
        try:
            os.makedirs(version.absolute_path)
        except OSError:
            # path already exists OSError
            pass

        version_full_path = os.path.normpath(version.absolute_full_path)

        self.comp.Lock()
        self.comp.Save(version_full_path.encode())
        self.comp.Unlock()

        # create a local copy
        self.create_local_copy(version)

        rfm = RecentFileManager()
        rfm.add(self.name, version.absolute_full_path)

        return True

    def set_range_from_shot(self, version):
        """sets the frame range from the Shot entity if this version is related
        to one.

        :param version:
        :return:
        """
        # check if this is a shot related task
        is_shot_related_task = False
        shot = None
        from stalker import Shot
        for task in version.task.parents:
            if isinstance(task, Shot):
                is_shot_related_task = True
                shot = task
                break

        fps = None
        imf = None
        if not is_shot_related_task:
            # use the Project image_format
            fps = version.task.project.fps
            imf = version.task.project.image_format
        else:
            # use the shot image_format
            if shot:
                fps = shot.fps
                imf = shot.image_format

                # set frame ranges
                self.set_frame_range(
                    start_frame=shot.cut_in,
                    end_frame=shot.cut_out,
                )

        # set comp resolution and fps
        if imf:
            self.comp.SetPrefs({
                # Image Format
                "Comp.FrameFormat.Width": imf.width,
                "Comp.FrameFormat.Height": imf.height,
                "Comp.FrameFormat.AspectY": imf.pixel_aspect,
                "Comp.FrameFormat.AspectX": imf.pixel_aspect,

                # FPS
                "Comp.FrameFormat.Rate": fps,

                # set project frame format to 16bit
                "Comp.FrameFormat.DepthFull": 2.0,
                "Comp.FrameFormat.DepthLock": True,
            })

    def export_as(self, version):
        """the export action for nuke environment
        """
        # its a new version please update the paths
        version.update_paths()
        # set the extension to '.comp'
        version.extension = self.extensions[0]
        version.created_with = self.name

        raise NotImplementedError(
            'export_as() is not implemented yet for Fusion'
        )

        # # create a local copy
        # self.create_local_copy(version)

    def open(self, version, force=False, representation=None,
             reference_depth=0, skip_update_check=False):
        """the open action for nuke environment
        """
        version_full_path = os.path.normpath(version.absolute_full_path)

        # # delete all the comps and open new one
        # comps = self.fusion.GetCompList().values()
        # for comp_ in comps:
        #     comp_.Close()

        self.fusion.LoadComp(version_full_path.encode())

        rfm = RecentFileManager()
        rfm.add(self.name, version.absolute_full_path)

        # # set the project_directory
        # self.project_directory = os.path.dirname(version.absolute_path)

        # TODO: file paths in different OS'es should be replaced with the current one
        # Check if the file paths are starting with a string matching one of
        # the OS'es project_directory path and replace them with a relative one
        # matching the current OS

        # # replace paths
        # self.replace_external_paths()

        # return True to specify everything was ok and an empty list
        # for the versions those needs to be updated
        return empty_reference_resolution()

    def import_(self, version):
        """the import action for nuke environment
        """
        # nuke.nodePaste(version.absolute_full_path)
        return True

    def get_current_version(self):
        """Finds the Version instance from the current open file.

        If it can't find any then returns None.

        :return: :class:`~oyProjectManager.models.version.Version`
        """
        # full_path = self._root.knob('name').value()
        full_path = os.path.normpath(
            self.comp.GetAttrs()['COMPS_FileName']
        ).replace('\\', '/')
        return self.get_version_from_full_path(full_path)

    def get_version_from_recent_files(self):
        """It will try to create a
        :class:`~oyProjectManager.models.version.Version` instance by looking
        at the recent files list.

        It will return None if it can not find one.

        :return: :class:`~oyProjectManager.models.version.Version`
        """
        # full_path = self.fusion_prefs["LastCompFile"]
        # return self.get_version_from_full_path(full_path)

        version = None
        rfm = RecentFileManager()

        try:
            recent_files = rfm[self.name]
        except KeyError:
            logger.debug('no recent files')
            recent_files = None

        if recent_files is not None:
            for i in range(len(recent_files)):
                version = self.get_version_from_full_path(recent_files[i])
                if version is not None:
                    break

            logger.debug("version from recent files is: %s" % version)

        return version

    def get_version_from_project_dir(self):
        """Tries to find a Version from the current project directory

        :return: :class:`~oyProjectManager.models.version.Version`
        """
        versions = self.get_versions_from_path(self.project_directory)
        version = None

        if versions and len(versions):
            version = versions[0]

        return version

    def get_last_version(self):
        """gets the file name from nuke
        """
        version = self.get_current_version()

        # read the recent file list
        if version is None:
            version = self.get_version_from_recent_files()

        # get the latest possible Version instance by using the workspace path
        if version is None:
            version = self.get_version_from_project_dir()

        return version

    def get_frame_range(self):
        """returns the current frame range
        """
        # self._root = self.get_root_node()
        # startFrame = int(self._root.knob('first_frame').value())
        # endFrame = int(self._root.knob('last_frame').value())
        start_frame = self.comp.GetAttrs()['COMPN_GlobalStart']
        end_frame = self.comp.GetAttrs()['COMPN_GlobalEnd']
        return start_frame, end_frame

    def set_frame_range(self, start_frame=1, end_frame=100,
                        adjust_frame_range=False):
        """sets the start and end frame range
        """
        self.comp.SetAttrs(
            {
                "COMPN_GlobalStart": start_frame,
                "COMPN_RenderStart": start_frame,
                "COMPN_GlobalEnd": end_frame,
                "COMPN_RenderEnd": end_frame,
            }
        )

    def set_fps(self, fps=25):
        """sets the current fps
        """
        #        self._root.knob('fps').setValue(fps)
        pass

    def get_fps(self):
        """returns the current fps
        """
        #return int(self._root.knob('fps').getValue())
        return None

    def get_main_saver_node(self):
        """Returns the main saver nodes in the scene or an empty list.
        :return: list
        """
        # list all the saver nodes in the current file
        all_saver_nodes = self.comp.GetToolList(False, 'Saver').values()

        saver_nodes = []
        for saver_node in all_saver_nodes:
            if saver_node.GetAttrs('TOOLS_Name').startswith(
               self._main_output_node_name):
                saver_nodes.append(saver_node)

        return saver_nodes

    def create_node_tree(self, node_tree):
        """Creates a node tree from the given node tree.

        The node_tree is a Python dictionary showing node types and attribute
        values. Also it can be a list of dictionaries to create more complex
        trees.

        Each node_tree can create only one shading network. The format of the
        dictionary should be as follows.

        node_tree: {
            'type': <- The fusion node type of the toppest shader
            'attr': {
                <- A dictionary that contains attribute names and values.
                'Input': {
                    'type': --- type name of the connected node
                    'attr': {
                        <- attribute values ->
                    }
                }
            },
        }

        :param [dict, list] node_tree: A dictionary showing the node tree
          attributes.

        :return:
        """
        # allow it to accept both a list or dict
        if isinstance(node_tree, list):
            created_root_nodes = []
            for item in node_tree:
                created_root_nodes.append(
                    self.create_node_tree(item)
                )
            return created_root_nodes

        node_type = node_tree['type']

        self.comp.Lock()
        node = self.comp.AddTool(node_type)
        self.comp.Unlock()

        # attributes
        if 'attr' in node_tree:
            attributes = node_tree['attr']
            for key in attributes:
                value = attributes[key]
                if isinstance(value, dict):
                    new_node = self.create_node_tree(value)
                    node.Input = new_node
                else:
                    node.SetAttrs({key: value})

        # input lists
        if 'input_list' in node_tree:
            input_list = node_tree['input_list']
            for key in input_list:
                node_input_list = node.GetInputList()
                for input_entry_key in node_input_list.keys():
                    input_entry = node_input_list[input_entry_key]
                    input_id = input_entry.GetAttrs()['INPS_ID']
                    if input_id == key:
                        value = input_list[key]
                        input_entry[0] = value
                        break

        # ref_id
        if 'ref_id' in node_tree:
            node.SetData('ref_id', node_tree['ref_id'])

        # connected to
        if 'connected_to' in node_tree:
            connected_to = node_tree['connected_to']
            if 'Input' in connected_to:
                input_node = self.create_node_tree(connected_to['Input'])
                node.Input = input_node
            elif 'ref_id' in node_tree['connected_to']:
                ref_id = node_tree['connected_to']['ref_id']
                print('ref_id: %s' % ref_id)
                # find a node with ref_id equals to ref_id that is given in the
                # node tree
                all_nodes = self.comp.GetToolList().values()
                for r_node in all_nodes:
                    node_ref_id = r_node.GetData('ref_id')
                    print('node_ref_id: %s' % node_ref_id)
                    if node_ref_id == ref_id:
                        node.Input = r_node
                        break

        return node

    def output_path_generator(self, version, file_format):
        """helper function to generate the output path

        :param version: Stalker Version instance
        :param str file_format: A string showing the file format. Ex: tga, exr
          etc.
        :return:
        """
        # generate the data needed
        # the output path
        file_name_buffer = []
        template_kwargs = {}

        # if this is a shot related task set it to shots resolution
        include_project_code = True
        if version.nice_name.startswith(version.task.project.code):
            include_project_code = False

        version_sig_name = self.get_significant_name(
            version,
            include_project_code=include_project_code
        )

        file_name_buffer.append(
            '%(version_sig_name)s.001.%(format)s'
        )
        template_kwargs.update({
            'version_sig_name': version_sig_name,
            'format': file_format
        })

        output_file_name = ''.join(file_name_buffer) % template_kwargs

        # check if it is a stereo comp
        # if it is enable separate view rendering
        output_file_path = os.path.join(
            version.absolute_path,
            'Outputs',
            version.take_name,
            # 'v%03d' % version.version_number,
            file_format
        )

        # create the dir
        try:
            os.makedirs(output_file_path)
        except OSError:
            # path exists
            pass

        output_file_full_path = os.path.join(
            output_file_path,
            output_file_name
        ).replace('\\', '/')

        # make the path Project: relative
        output_file_full_path = 'Project:%s' % os.path.relpath(
            output_file_full_path,
            os.path.dirname(version.absolute_path)
        )

        # set the output path
        return '%s' % os.path.normpath(
            output_file_full_path
        ).encode()

    def output_node_name_generator(self, file_format):
        return '%s_%s' % (self._main_output_node_name, file_format)

    def create_main_saver_node(self, version):
        """Creates the default saver node if there is no created before.

        Creates the default saver nodes if there isn't any existing outputs,
        and updates the ones that is already created
        """
        fps = 25
        if version:
            project = version.task.project
            fps = project.fps

        random_ref_id = uuid.uuid4().hex

        output_format_data = [
            {
                'name': 'jpg',
                'node_tree': {
                    'type': 'Saver',
                    'attr': {
                        'TOOLS_Name': self.output_node_name_generator('jpg'),
                    },
                    'input_list': {
                        'Clip': self.output_path_generator(version, 'jpg'),
                        'CreateDir': 1,
                        'ProcessRed': 1,
                        'ProcessGreen': 1,
                        'ProcessBlue': 1,
                        'ProcessAlpha': 0,
                        'OutputFormat': 'JPEGFormat',
                        'JpegFormat.Quality': 85,
                    },
                    'connected_to': {
                        'Input': {
                            "type": "OCIOColorSpace",
                            "ref_id": random_ref_id,
                            "input_list": {
                                "OCIOConfig": "LUTs:/OpenColorIO-Configs/aces_1.1/config.ocio",
                                "SourceSpace": "ACES - ACEScg",
                                "OutputSpace": "Output - Rec.709",
                            },
                            'connected_to': {
                                'Input': {
                                    "type": "OCIOColorSpace",
                                    "input_list": {
                                        "OCIOConfig": "LUTs:/OpenColorIO-Configs/aces_1.1/config.ocio",
                                        "SourceSpace": "Utility - Linear - sRGB",
                                        "OutputSpace": "ACES - ACEScg",
                                    },
                                }
                            }
                        }
                    }
                },
            },
            {
                'name': 'tga',
                'node_tree': {
                    'type': 'Saver',
                    'attr': {
                        'TOOLS_Name': self.output_node_name_generator('tga'),
                    },
                    'input_list': {
                        'Clip': self.output_path_generator(version, 'tga'),
                        'CreateDir': 1,
                        'ProcessRed': 1,
                        'ProcessGreen': 1,
                        'ProcessBlue': 1,
                        'ProcessAlpha': 0,
                        'OutputFormat': 'TGAFormat',
                    },
                    'connected_to': {
                        'ref_id': random_ref_id
                    }
                },
            },
            {
                'name': 'exr',
                'node_tree': {
                    'type': 'Saver',
                    'attr': {
                        'TOOLS_Name': self.output_node_name_generator('exr'),
                    },
                    'input_list': {
                        'Clip': self.output_path_generator(version, 'exr'),
                        'CreateDir': 1,
                        'ProcessRed': 1,
                        'ProcessGreen': 1,
                        'ProcessBlue': 1,
                        'ProcessAlpha': 1,
                        'OutputFormat': 'OpenEXRFormat',
                        'OpenEXRFormat.Depth': 1,  # 16-bit float
                        'OpenEXRFormat.RedEnable': 1,
                        'OpenEXRFormat.GreenEnable': 1,
                        'OpenEXRFormat.BlueEnable': 1,
                        'OpenEXRFormat.AlphaEnable': 1,
                        'OpenEXRFormat.ZEnable': 0,
                        'OpenEXRFormat.CovEnable': 0,
                        'OpenEXRFormat.ObjIDEnable': 0,
                        'OpenEXRFormat.MatIDEnable': 0,
                        'OpenEXRFormat.UEnable': 0,
                        'OpenEXRFormat.VEnable': 0,
                        'OpenEXRFormat.XNormEnable': 0,
                        'OpenEXRFormat.YNormEnable': 0,
                        'OpenEXRFormat.ZNormEnable': 0,
                        'OpenEXRFormat.XVelEnable': 0,
                        'OpenEXRFormat.YVelEnable': 0,
                        'OpenEXRFormat.XRevVelEnable': 0,
                        'OpenEXRFormat.YRevVelEnable': 0,
                        'OpenEXRFormat.XPosEnable': 0,
                        'OpenEXRFormat.YPosEnable': 0,
                        'OpenEXRFormat.ZPosEnable': 0,
                        'OpenEXRFormat.XDispEnable': 0,
                        'OpenEXRFormat.YDispEnable': 0,
                    },
                    'connected_to': {
                        'ref_id': random_ref_id
                    }
                }
            },
            {
                'name': 'mp4',
                'node_tree': {
                    'type': 'Saver',
                    'attr': {
                        'TOOLS_Name': self.output_node_name_generator('mp4'),
                    },
                    'input_list': {
                        'Clip': self.output_path_generator(version, 'mp4'),
                        'CreateDir': 1,
                        'ProcessRed': 1,
                        'ProcessGreen': 1,
                        'ProcessBlue': 1,
                        'ProcessAlpha': 0,
                        'OutputFormat': 'QuickTimeMovies',
                        'ProcessMode': 'Auto',
                        'SaveFrames': 'Full',
                        'QuickTimeMovies.Compression': 'H.264_avc1',
                        'QuickTimeMovies.Quality': 95.0,
                        'QuickTimeMovies.FrameRateFps': fps,
                        'QuickTimeMovies.KeyFrames': 5,
                        'StartRenderScript': 'frames_at_once = comp:GetPrefs("Comp.Memory.FramesAtOnce")\ncomp:SetPrefs("Comp.Memory.FramesAtOnce", 1)',
                        'EndRenderScript': 'comp:SetPrefs("Comp.Memory.FramesAtOnce", frames_at_once)',
                    },
                    'connected_to': {
                        'ref_id': random_ref_id
                    }
                }
            },
            {
                'name': 'mov',
                'node_tree': {
                    'type': 'Saver',
                    'attr': {
                        'TOOLS_Name': self.output_node_name_generator('mov'),
                    },
                    'input_list': {
                        'Clip': self.output_path_generator(version, 'mov'),
                        'CreateDir': 1,
                        'ProcessRed': 1,
                        'ProcessGreen': 1,
                        'ProcessBlue': 1,
                        'ProcessAlpha': 0,
                        'OutputFormat': 'QuickTimeMovies',
                        'ProcessMode': 'Auto',
                        'SaveFrames': 'Full',
                        'QuickTimeMovies.Compression': 'Apple ProRes 422 HQ_apch',
                        'QuickTimeMovies.Quality': 95.0,
                        'QuickTimeMovies.FrameRateFps': fps,
                        'QuickTimeMovies.KeyFrames': 5,

                        'QuickTimeMovies.LimitDataRate': 0.0,
                        'QuickTimeMovies.DataRateK': 1000.0,
                        'QuickTimeMovies.Advanced': 1.0,
                        'QuickTimeMovies.Primaries': 0.0,
                        'QuickTimeMovies.Transfer': 0.0,
                        'QuickTimeMovies.Matrix': 0.0,
                        'QuickTimeMovies.PixelAspectRatio': 0.0,
                        'QuickTimeMovies.ErrorDiffusion': 1.0,
                        'QuickTimeMovies.SaveAlphaChannel': 1.0,

                        'StartRenderScript': 'frames_at_once = comp:GetPrefs("Comp.Memory.FramesAtOnce")\ncomp:SetPrefs("Comp.Memory.FramesAtOnce", 1)',
                        'EndRenderScript': 'comp:SetPrefs("Comp.Memory.FramesAtOnce", frames_at_once)',

                    },
                    'connected_to': {
                        'ref_id': random_ref_id
                    }
                }
            },
        ]

        if version.task.type and version.task.type.name == 'Plate':
            # create a different type of outputs
            output_format_data = [
                {
                    'name': 'jpg',
                    'node_tree': {
                        'type': 'Saver',
                        'attr': {
                            'TOOLS_Name': self.output_node_name_generator('jpg'),
                        },
                        'input_list': {
                            'Clip': self.output_path_generator(version, 'jpg'),
                            'CreateDir': 1,
                            'ProcessRed': 1,
                            'ProcessGreen': 1,
                            'ProcessBlue': 1,
                            'ProcessAlpha': 0,
                            'OutputFormat': 'JPEGFormat',
                            'JpegFormat.Quality': 85,
                        },
                    },
                },
                {
                    'name': 'exr',
                    'node_tree': {
                        'type': 'Saver',
                        'attr': {
                            'TOOLS_Name': self.output_node_name_generator('exr'),
                        },
                        'input_list': {
                            'Clip': self.output_path_generator(version, 'exr'),
                            'CreateDir': 1,
                            'ProcessRed': 1,
                            'ProcessGreen': 1,
                            'ProcessBlue': 1,
                            'ProcessAlpha': 0,
                            'OutputFormat': 'OpenEXRFormat',
                            'OpenEXRFormat.Depth': 1,  # 16-bit float
                            'OpenEXRFormat.RedEnable': 1,
                            'OpenEXRFormat.GreenEnable': 1,
                            'OpenEXRFormat.BlueEnable': 1,
                            'OpenEXRFormat.AlphaEnable': 0,
                            'OpenEXRFormat.ZEnable': 0,
                            'OpenEXRFormat.CovEnable': 0,
                            'OpenEXRFormat.ObjIDEnable': 0,
                            'OpenEXRFormat.MatIDEnable': 0,
                            'OpenEXRFormat.UEnable': 0,
                            'OpenEXRFormat.VEnable': 0,
                            'OpenEXRFormat.XNormEnable': 0,
                            'OpenEXRFormat.YNormEnable': 0,
                            'OpenEXRFormat.ZNormEnable': 0,
                            'OpenEXRFormat.XVelEnable': 0,
                            'OpenEXRFormat.YVelEnable': 0,
                            'OpenEXRFormat.XRevVelEnable': 0,
                            'OpenEXRFormat.YRevVelEnable': 0,
                            'OpenEXRFormat.XPosEnable': 0,
                            'OpenEXRFormat.YPosEnable': 0,
                            'OpenEXRFormat.ZPosEnable': 0,
                            'OpenEXRFormat.XDispEnable': 0,
                            'OpenEXRFormat.YDispEnable': 0,
                        },
                    },
                },
                # {
                #     'name': 'mp4',
                #     'node_tree': {
                #         'type': 'Saver',
                #         'attr': {
                #             'TOOLS_Name': self.output_node_name_generator('mp4'),
                #         },
                #         'input_list': {
                #             'Clip': self.output_path_generator(version, 'mp4'),
                #             'CreateDir': 1,
                #             'ProcessRed': 1,
                #             'ProcessGreen': 1,
                #             'ProcessBlue': 1,
                #             'ProcessAlpha': 0,
                #             'OutputFormat': 'QuickTimeMovies',
                #             'ProcessMode': 'Auto',
                #             'SaveFrames': 'Full',
                #             'QuickTimeMovies.Compression': 'H.264_avc1',
                #             'QuickTimeMovies.Quality': 95.0,
                #             'QuickTimeMovies.FrameRateFps': fps,
                #             'QuickTimeMovies.KeyFrames': 5,
                #             'StartRenderScript': 'frames_at_once = comp:GetPrefs("Comp.Memory.FramesAtOnce")\ncomp:SetPrefs("Comp.Memory.FramesAtOnce", 1)',
                #             'EndRenderScript': 'comp:SetPrefs("Comp.Memory.FramesAtOnce", frames_at_once)',
                #         },
                #     },
                # },
                {
                    'name': 'mov',
                    'node_tree': {
                        'type': 'Saver',
                        'attr': {
                            'TOOLS_Name': self.output_node_name_generator('mov'),
                        },
                        'input_list': {
                            'Clip': self.output_path_generator(version, 'mov'),
                            'CreateDir': 1,
                            'ProcessRed': 1,
                            'ProcessGreen': 1,
                            'ProcessBlue': 1,
                            'ProcessAlpha': 0,
                            'OutputFormat': 'QuickTimeMovies',
                            'ProcessMode': 'Auto',
                            'SaveFrames': 'Full',
                            'QuickTimeMovies.Compression': 'Apple ProRes 422 HQ_apch',
                            'QuickTimeMovies.Quality': 95.0,
                            'QuickTimeMovies.FrameRateFps': fps,
                            'QuickTimeMovies.KeyFrames': 5,

                            'QuickTimeMovies.LimitDataRate': 0.0,
                            'QuickTimeMovies.DataRateK': 1000.0,
                            'QuickTimeMovies.Advanced': 1.0,
                            'QuickTimeMovies.Primaries': 0.0,
                            'QuickTimeMovies.Transfer': 0.0,
                            'QuickTimeMovies.Matrix': 0.0,
                            'QuickTimeMovies.PixelAspectRatio': 0.0,
                            'QuickTimeMovies.ErrorDiffusion': 1.0,
                            'QuickTimeMovies.SaveAlphaChannel': 1.0,

                            'StartRenderScript': 'frames_at_once = comp:GetPrefs("Comp.Memory.FramesAtOnce")\ncomp:SetPrefs("Comp.Memory.FramesAtOnce", 1)',
                            'EndRenderScript': 'comp:SetPrefs("Comp.Memory.FramesAtOnce", frames_at_once)',
                        },
                    },
                },
            ]

        # selectively generate output format
        saver_nodes = self.get_main_saver_node()

        for data in output_format_data:
            format_name = data['name']
            node_tree = data['node_tree']

            # now check if a node with the same name exists
            format_node = None
            format_node_name = self.output_node_name_generator(format_name)
            for node in saver_nodes:
                node_name = node.GetAttrs('TOOLS_Name')
                if node_name.startswith(format_node_name):
                    format_node = node
                    break

            # create the saver node for this format if missing
            if not format_node:
                self.create_node_tree(node_tree)
            else:
                # just update the input_lists
                if 'input_list' in node_tree:
                    input_list = node_tree['input_list']
                    for key in input_list:
                        node_input_list = format_node.GetInputList()
                        for input_entry_key in node_input_list.keys():
                            input_entry = node_input_list[input_entry_key]
                            input_id = input_entry.GetAttrs()['INPS_ID']
                            if input_id == key:
                                value = input_list[key]
                                input_entry[0] = value
                                break

            try:
                os.makedirs(
                    os.path.dirname(
                        self.output_path_generator(version, format_name)
                    )
                )
            except OSError:
                # path already exists
                pass

    @property
    def project_directory(self):
        """The project directory.

        Set it to the project root, and set all your paths relative to this
        directory.
        """

        # try to figure it out from the maps
        # search for Project path

        project_dir = None
        maps = self.comp_prefs['Paths'].get('Map', None)
        if maps:
            project_dir = maps.get('Project:', None)

        # if not project_dir:
        #     # set the map for the project dir
        #     if self.version:
        #         project_dir = os.path.dirname(self.version.absolute_path)
        #         self.project_directory = project_dir

        return project_dir

    @project_directory.setter
    def project_directory(self, project_directory_in):
        """Sets project directory

        :param str project_directory_in: the project directory
        :return:
        """

        project_directory_in = os.path.normpath(project_directory_in)
        print('setting project directory to: %s' % project_directory_in)

        # set a path map
        self.comp.SetPrefs(
            {
                'Comp.Paths.Map': {
                    'Project:': project_directory_in.encode()
                }
            }
        )

# -*- coding: utf-8 -*- # Copyright (c) 2012-2020, <NAME> # # This module is part of anima and is released under the MIT # License: http://www.opensource.org/licenses/MIT # for Fusion 6 and 7 # for Fusion 8+ the fusion environment class fusion specific init # and add you own modifications to __init__ # from anima.env import blackmagic as bmd # self.fusion = bmd.scriptapp("Fusion") # self.fusion = bmd.get_fusion() # update name with version "the save action for fusion environment          uses Fusions own python binding # set the extension to '.comp' # refresh the current comp # its a new version please update the paths # set project_directory # create the main write node # set range from the shot # replace read and write node paths # self.replace_external_paths() # create the path before saving # path already exists OSError # create a local copy sets the frame range from the Shot entity if this version is related         to one.          :param version:         :return: # check if this is a shot related task # use the Project image_format # use the shot image_format # set frame ranges # set comp resolution and fps # Image Format # FPS # set project frame format to 16bit the export action for nuke environment # its a new version please update the paths # set the extension to '.comp' # # create a local copy # self.create_local_copy(version) the open action for nuke environment # # delete all the comps and open new one # comps = self.fusion.GetCompList().values() # for comp_ in comps: #     comp_.Close() # # set the project_directory # self.project_directory = os.path.dirname(version.absolute_path) # TODO: file paths in different OS'es should be replaced with the current one # Check if the file paths are starting with a string matching one of # the OS'es project_directory path and replace them with a relative one # matching the current OS # # replace paths # self.replace_external_paths() # return True to specify everything was ok and an empty list # for the versions those needs to be updated the import action for nuke environment # nuke.nodePaste(version.absolute_full_path) Finds the Version instance from the current open file.          If it can't find any then returns None.          :return: :class:`~oyProjectManager.models.version.Version` # full_path = self._root.knob('name').value() It will try to create a         :class:`~oyProjectManager.models.version.Version` instance by looking         at the recent files list.          It will return None if it can not find one.          :return: :class:`~oyProjectManager.models.version.Version` # full_path = self.fusion_prefs["LastCompFile"] # return self.get_version_from_full_path(full_path) Tries to find a Version from the current project directory          :return: :class:`~oyProjectManager.models.version.Version` gets the file name from nuke # read the recent file list # get the latest possible Version instance by using the workspace path returns the current frame range # self._root = self.get_root_node() # startFrame = int(self._root.knob('first_frame').value()) # endFrame = int(self._root.knob('last_frame').value()) sets the start and end frame range sets the current fps #        self._root.knob('fps').setValue(fps) returns the current fps #return int(self._root.knob('fps').getValue()) Returns the main saver nodes in the scene or an empty list.         :return: list # list all the saver nodes in the current file Creates a node tree from the given node tree.          The node_tree is a Python dictionary showing node types and attribute         values. Also it can be a list of dictionaries to create more complex         trees.          Each node_tree can create only one shading network. The format of the         dictionary should be as follows.          node_tree: {             'type': <- The fusion node type of the toppest shader             'attr': {                 <- A dictionary that contains attribute names and values.                 'Input': {                     'type': --- type name of the connected node                     'attr': {                         <- attribute values ->                     }                 }             },         }          :param [dict, list] node_tree: A dictionary showing the node tree           attributes.          :return: # allow it to accept both a list or dict # attributes # input lists # ref_id # connected to # find a node with ref_id equals to ref_id that is given in the # node tree helper function to generate the output path          :param version: Stalker Version instance         :param str file_format: A string showing the file format. Ex: tga, exr           etc.         :return: # generate the data needed # the output path # if this is a shot related task set it to shots resolution # check if it is a stereo comp # if it is enable separate view rendering # 'v%03d' % version.version_number, # create the dir # path exists # make the path Project: relative # set the output path Creates the default saver node if there is no created before.          Creates the default saver nodes if there isn't any existing outputs,         and updates the ones that is already created # 16-bit float # create a different type of outputs # 16-bit float # { #     'name': 'mp4', #     'node_tree': { #         'type': 'Saver', #         'attr': { #             'TOOLS_Name': self.output_node_name_generator('mp4'), #         }, #         'input_list': { #             'Clip': self.output_path_generator(version, 'mp4'), #             'CreateDir': 1, #             'ProcessRed': 1, #             'ProcessGreen': 1, #             'ProcessBlue': 1, #             'ProcessAlpha': 0, #             'OutputFormat': 'QuickTimeMovies', #             'ProcessMode': 'Auto', #             'SaveFrames': 'Full', #             'QuickTimeMovies.Compression': 'H.264_avc1', #             'QuickTimeMovies.Quality': 95.0, #             'QuickTimeMovies.FrameRateFps': fps, #             'QuickTimeMovies.KeyFrames': 5, #             'StartRenderScript': 'frames_at_once = comp:GetPrefs("Comp.Memory.FramesAtOnce")\ncomp:SetPrefs("Comp.Memory.FramesAtOnce", 1)', #             'EndRenderScript': 'comp:SetPrefs("Comp.Memory.FramesAtOnce", frames_at_once)', #         }, #     }, # }, # selectively generate output format # now check if a node with the same name exists # create the saver node for this format if missing # just update the input_lists # path already exists The project directory.          Set it to the project root, and set all your paths relative to this         directory. # try to figure it out from the maps # search for Project path # if not project_dir: #     # set the map for the project dir #     if self.version: #         project_dir = os.path.dirname(self.version.absolute_path) #         self.project_directory = project_dir Sets project directory          :param str project_directory_in: the project directory         :return: # set a path map