From e22e8102e33cf4c33db5993e0dd3ec8f97871f3e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Mar 2019 17:36:05 +0100 Subject: [PATCH] djv converted from scratch to action --- pype/ftrack/actions/action_djvview.py | 453 ++++++++++---------------- 1 file changed, 172 insertions(+), 281 deletions(-) diff --git a/pype/ftrack/actions/action_djvview.py b/pype/ftrack/actions/action_djvview.py index 80f1105a96..1b602abd2c 100644 --- a/pype/ftrack/actions/action_djvview.py +++ b/pype/ftrack/actions/action_djvview.py @@ -6,97 +6,58 @@ import logging import subprocess from operator import itemgetter import ftrack_api -from pype.ftrack import BaseHandler +from pype.ftrack import BaseAction from app.api import Logger -from pype import lib +from pype import pypelib + log = Logger.getLogger(__name__) -class DJVViewAction(BaseHandler): +class DJVViewAction(BaseAction): """Launch DJVView action.""" identifier = "djvview-launch-action" label = "DJV View" + description = "DJV View Launcher" icon = "http://a.fsdn.com/allura/p/djv/icon" type = 'Application' def __init__(self, session): '''Expects a ftrack_api.Session instance''' super().__init__(session) - self.variant = None self.djv_path = None self.config_data = None - self.items = [] - if self.config_data is None: - self.load_config_data() + self.load_config_data() + self.set_djv_path() - application = self.get_application() - if application is None: - return - - applicationIdentifier = application["identifier"] - label = application["label"] - self.items.append({ - "actionIdentifier": self.identifier, - "label": label, - "variant": application.get("variant", None), - "description": application.get("description", None), - "icon": application.get("icon", "default"), - "applicationIdentifier": applicationIdentifier - }) - - if self.identifier is None: - raise ValueError( - 'Action missing identifier.' - ) - - def is_valid_selection(self, event): - selection = event["data"].get("selection", []) - - if not selection: - return - - entityType = selection[0]["entityType"] - - if entityType not in ["assetversion", "task"]: - return False - - return True - - def discover(self, event): - """Return available actions based on *event*. """ if self.djv_path is None: return - if not self.is_valid_selection(event): - return - return { - "items": self.items - } + self.allowed_types = self.config_data.get( + 'file_ext', ["img", "mov", "exr"] + ) def register(self): - '''Registers the action, subscribing the discover and launch topics.''' - self.session.event_hub.subscribe( - 'topic=ftrack.action.discover and source.user.username={0}'.format( - self.session.api_user - ), self.discover - ) - launch_subscription = ( - 'topic=ftrack.action.launch' - ' and data.actionIdentifier={0}' - ' and source.user.username={1}' - ) - self.session.event_hub.subscribe( - launch_subscription.format( - self.identifier, - self.session.api_user - ), - self.launch + assert (self.djv_path is not None), ( + 'DJV View is not installed' + ' or paths in presets are not set correctly' ) + super().register() + + def discover(self, session, entities, event): + """Return available actions based on *event*. """ + selection = event["data"].get("selection", []) + if len(selection) != 1: + return False + + entityType = selection[0].get("entityType", None) + if entityType in ["assetversion", "task"]: + return True + return False def load_config_data(self): - path_items = [lib.get_presets_path(), 'djv_view', 'config.json'] + path_items = [pypelib.get_presets_path(), 'djv_view', 'config.json'] filepath = os.path.sep.join(path_items) data = dict() @@ -110,245 +71,175 @@ class DJVViewAction(BaseHandler): self.config_data = data - def get_application(self): - applicationIdentifier = "djvview" - description = "DJV View Launcher" - - possible_paths = self.config_data.get("djv_paths", []) - for path in possible_paths: + def set_djv_path(self): + for path in self.config_data.get("djv_paths", []): if os.path.exists(path): self.djv_path = path break - if self.djv_path is None: - log.debug("DJV View application was not found") - return None + def interface(self, session, entities, event): + if event['data'].get('values', {}): + return - application = { - 'identifier': applicationIdentifier, - 'label': self.label, - 'icon': self.icon, - 'description': description - } - - versionExpression = re.compile(r"(?P\d+.\d+.\d+)") - versionMatch = versionExpression.search(self.djv_path) - if versionMatch: - new_label = '{} {}'.format( - application['label'], versionMatch.group('version') - ) - application['label'] = new_label - - return application - - def translate_event(self, session, event): - '''Return *event* translated structure to be used with the API.''' - - selection = event['data'].get('selection', []) - - entities = list() - for entity in selection: - entities.append( - (session.get( - self.get_entity_type(entity), entity.get('entityId') - )) - ) - - return entities - - def get_entity_type(self, entity): - entity_type = entity.get('entityType').replace('_', '').lower() - - for schema in self.session.schemas: - alias_for = schema.get('alias_for') + entity = entities[0] + versions = [] + entity_type = entity.entity_type.lower() + if entity_type == "assetversion": if ( - alias_for and isinstance(alias_for, str) and - alias_for.lower() == entity_type + entity[ + 'components' + ][0]['file_type'][1:] in self.allowed_types ): - return schema['id'] + versions.append(entity) + else: + master_entity = entity + if entity_type == "task": + master_entity = entity['parent'] - for schema in self.session.schemas: - if schema['id'].lower() == entity_type: - return schema['id'] - - raise ValueError( - 'Unable to translate entity type: {0}.'.format(entity_type) - ) - - def launch(self, event): - """Callback method for DJVView action.""" - session = self.session - entities = self.translate_event(session, event) - - # Launching application - if "values" in event["data"]: - filename = event['data']['values']['path'] - - # TODO Is this proper way? - try: - fps = int(entities[0]['custom_attributes']['fps']) - except Exception: - fps = 24 - - cmd = [] - # DJV path - cmd.append(os.path.normpath(self.djv_path)) - # DJV Options Start ############################################## - '''layer name''' - # cmd.append('-file_layer (value)') - ''' Proxy scale: 1/2, 1/4, 1/8''' - # cmd.append('-file_proxy 1/2') - ''' Cache: True, False.''' - cmd.append('-file_cache True') - ''' Start in full screen ''' - # cmd.append('-window_fullscreen') - ''' Toolbar controls: False, True.''' - # cmd.append("-window_toolbar False") - ''' Window controls: False, True.''' - # cmd.append("-window_playbar False") - ''' Grid overlay: None, 1x1, 10x10, 100x100.''' - # cmd.append("-view_grid None") - ''' Heads up display: True, False.''' - # cmd.append("-view_hud True") - ''' Playback: Stop, Forward, Reverse.''' - cmd.append("-playback Forward") - ''' Frame.''' - # cmd.append("-playback_frame (value)") - cmd.append("-playback_speed " + str(fps)) - ''' Timer: Sleep, Timeout. Value: Sleep.''' - # cmd.append("-playback_timer (value)") - ''' Timer resolution (seconds): 0.001.''' - # cmd.append("-playback_timer_resolution (value)") - ''' Time units: Timecode, Frames.''' - cmd.append("-time_units Frames") - # DJV Options End ################################################ - - # PATH TO COMPONENT - cmd.append(os.path.normpath(filename)) - - try: - # Run DJV with these commands - subprocess.Popen(' '.join(cmd)) - except FileNotFoundError: - return { - 'success': False, - 'message': 'File "{}" was not found.'.format( - os.path.basename(filename) - ) - } - - return { - 'success': True, - 'message': 'DJV View started.' - } - - if 'items' not in event["data"]: - event["data"]['items'] = [] - - try: - for entity in entities: - versions = [] - self.load_config_data() - default_types = ["img", "mov", "exr"] - allowed_types = self.config_data.get('file_ext', default_types) - - if entity.entity_type.lower() == "assetversion": + for asset in master_entity['assets']: + for version in asset['versions']: + # Get only AssetVersion of selected task if ( - entity[ - 'components' - ][0]['file_type'][1:] in allowed_types + entity_type == "task" and + version['task']['id'] != entity['id'] ): - versions.append(entity) + continue + # Get only components with allowed type + filetype = version['components'][0]['file_type'] + if filetype[1:] in self.allowed_types: + versions.append(version) - elif entity.entity_type.lower() == "task": - # AssetVersions are obtainable only from shot! - shotentity = entity['parent'] - - for asset in shotentity['assets']: - for version in asset['versions']: - # Get only AssetVersion of selected task - if version['task']['id'] != entity['id']: - continue - # Get only components with allowed type - filetype = version['components'][0]['file_type'] - if filetype[1:] in allowed_types: - versions.append(version) - - # Raise error if no components were found - if len(versions) < 1: - raise ValueError('There are no Asset Versions to open.') - - for version in versions: - logging.info(version['components']) - for component in version['components']: - label = "v{0} - {1} - {2}" - - label = label.format( - str(version['version']).zfill(3), - version['asset']['type']['name'], - component['name'] - ) - - try: - # TODO This is proper way to get filepath!!! - location = component[ - 'component_locations' - ][0]['location'] - file_path = location.get_filesystem_path(component) - # if component.isSequence(): - # if component.getMembers(): - # frame = int( - # component.getMembers()[0].getName() - # ) - # file_path = file_path % frame - except Exception: - # This works but is NOT proper way - file_path = component[ - 'component_locations' - ][0]['resource_identifier'] - - dirpath = os.path.dirname(file_path) - if os.path.isdir(dirpath): - event["data"]["items"].append( - {"label": label, "value": file_path} - ) - - # Raise error if any component is playable - if len(event["data"]["items"]) == 0: - raise ValueError( - 'There are no Asset Versions with accessible path.' - ) - - except Exception as e: + if len(versions) < 1: return { 'success': False, - 'message': str(e) + 'message': 'There are no Asset Versions to open.' } - return { - "items": [ - { - "label": "Items to view", - "type": "enumerator", - "name": "path", - "data": sorted( - event["data"]['items'], - key=itemgetter("label"), - reverse=True - ) - } - ] - } + items = [] + base_label = "v{0} - {1} - {2}" + default_component = self.config_data.get( + 'default_component', None + ) + last_available = None + select_value = None + for version in versions: + for component in version['components']: + label = base_label.format( + str(version['version']).zfill(3), + version['asset']['type']['name'], + component['name'] + ) + try: + location = component[ + 'component_locations' + ][0]['location'] + file_path = location.get_filesystem_path(component) + except Exception: + file_path = component[ + 'component_locations' + ][0]['resource_identifier'] + + if os.path.isdir(os.path.dirname(file_path)): + last_available = file_path + if component['name'] == default_component: + select_value = file_path + items.append( + {'label': label, 'value': file_path} + ) + + if len(items) == 0: + return { + 'success': False, + 'message': ( + 'There are no Asset Versions with accessible path.' + ) + } + + item = { + 'label': 'Items to view', + 'type': 'enumerator', + 'name': 'path', + 'data': sorted( + items, + key=itemgetter('label'), + reverse=True + ) + } + if select_value is not None: + item['value'] = select_value + else: + item['value'] = last_available + + return {'items': [item]} + + def launch(self, session, entities, event): + """Callback method for DJVView action.""" + + # Launching application + if "values" not in event["data"]: + return + filename = event['data']['values']['path'] + + fps = entities[0].get('custom_attributes', {}).get('fps', None) + + cmd = [] + # DJV path + cmd.append(os.path.normpath(self.djv_path)) + # DJV Options Start ############################################## + # '''layer name''' + # cmd.append('-file_layer (value)') + # ''' Proxy scale: 1/2, 1/4, 1/8''' + # cmd.append('-file_proxy 1/2') + # ''' Cache: True, False.''' + # cmd.append('-file_cache True') + # ''' Start in full screen ''' + # cmd.append('-window_fullscreen') + # ''' Toolbar controls: False, True.''' + # cmd.append("-window_toolbar False") + # ''' Window controls: False, True.''' + # cmd.append("-window_playbar False") + # ''' Grid overlay: None, 1x1, 10x10, 100x100.''' + # cmd.append("-view_grid None") + # ''' Heads up display: True, False.''' + # cmd.append("-view_hud True") + ''' Playback: Stop, Forward, Reverse.''' + cmd.append("-playback Forward") + # ''' Frame.''' + # cmd.append("-playback_frame (value)") + if fps is not None: + cmd.append("-playback_speed {}".format(int(fps))) + # ''' Timer: Sleep, Timeout. Value: Sleep.''' + # cmd.append("-playback_timer (value)") + # ''' Timer resolution (seconds): 0.001.''' + # cmd.append("-playback_timer_resolution (value)") + ''' Time units: Timecode, Frames.''' + cmd.append("-time_units Frames") + # DJV Options End ################################################ + + # PATH TO COMPONENT + cmd.append(os.path.normpath(filename)) + + try: + # Run DJV with these commands + subprocess.Popen(' '.join(cmd)) + except FileNotFoundError: + return { + 'success': False, + 'message': 'File "{}" was not found.'.format( + os.path.basename(filename) + ) + } + + return True def register(session): """Register hooks.""" if not isinstance(session, ftrack_api.session.Session): return - action = DJVViewAction(session) - action.register() + DJVViewAction(session).register() def main(arguments=None):