mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
djv converted from scratch to action
This commit is contained in:
parent
59b0b7bb66
commit
e22e8102e3
1 changed files with 172 additions and 281 deletions
|
|
@ -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<version>\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):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue