"Component open" and "DJV launcher" actions are working. NOT PROPERLY!!!

This commit is contained in:
Jakub Trllo 2018-11-06 17:27:31 +01:00
parent 490bf1c3ff
commit 94515dea68
5 changed files with 408 additions and 125 deletions

View file

@ -24,8 +24,7 @@ class ComponentOpen(BaseAction):
def discover(self, session, entities, event):
''' Validation '''
if len(entities) != 1 or entities[0].entity_type != 'Component':
if len(entities) != 1 or entities[0].entity_type != 'FileComponent':
return False
return True
@ -43,10 +42,14 @@ class ComponentOpen(BaseAction):
}
# Get component filepath
# TODO with locations it will be different???
fpath = entity['component_locations'][0]['resource_identifier']
items = fpath.split(os.sep)
items.pop(-1)
fpath = os.sep.join(items)
if os.path.isfile(fpath):
if sys.platform == 'win': # windows
if os.path.isdir(fpath):
if 'win' in sys.platform: # windows
subprocess.Popen('explorer "%s"' % fpath)
elif sys.platform == 'darwin': # macOS
subprocess.Popen(['open', fpath])
@ -63,7 +66,7 @@ class ComponentOpen(BaseAction):
return {
'success': True,
'message': 'Component Opened'
'message': 'Component folder Opened'
}

View file

@ -4,7 +4,6 @@ import sys
import argparse
import logging
import os
import json
import ftrack_api
from ftrack_action_handler import BaseAction
@ -161,9 +160,7 @@ class SyncToAvalon(BaseAction):
job = session.create('Job', {
'user': user,
'status': 'running',
'data': json.dumps({
'description': 'Synch Ftrack to Avalon.'
})
'data': {'description': 'Synch Ftrack to Avalon.'}
})
try:

View file

@ -33,7 +33,22 @@ class TestAction(BaseAction):
def launch(self, session, entities, event):
for entity in entities:
print("TEST")
index = 0
name = entity['components'][index]['name']
filetype = entity['components'][index]['file_type']
path = entity['components'][index]['component_locations'][0]['resource_identifier']
# entity['components'][index]['component_locations'][0]['resource_identifier'] = r"C:\Users\jakub.trllo\Desktop\test\exr\int_c022_lighting_v001_main_AO.%04d.exr"
location = entity['components'][0]['component_locations'][0]['location']
component = entity['components'][0]
# print(location.get_filesystem_path(component))
# for k in p:
# print(100*"-")
# print(k)
# print(p[k])
return True

View file

@ -0,0 +1,383 @@
import logging
import subprocess
import sys
import pprint
import os
import getpass
import re
from operator import itemgetter
import ftrack_api
class DJVViewAction(object):
"""Launch DJVView action."""
identifier = "djvview-launch-action"
# label = "DJV View"
# icon = "http://a.fsdn.com/allura/p/djv/icon"
def __init__(self, session):
'''Expects a ftrack_api.Session instance'''
self.logger = logging.getLogger(
'{0}.{1}'.format(__name__, self.__class__.__name__)
)
if self.identifier is None:
raise ValueError(
'Action missing identifier.'
)
self.session = session
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 not self.is_valid_selection(event):
return
items = []
applications = self.get_applications()
applications = sorted(
applications, key=lambda application: application["label"]
)
for application in applications:
self.djv_path = application.get("path", None)
applicationIdentifier = application["identifier"]
label = application["label"]
items.append({
"actionIdentifier": self.identifier,
"label": label,
"variant": application.get("variant", None),
"description": application.get("description", None),
"icon": application.get("icon", "default"),
"applicationIdentifier": applicationIdentifier
})
return {
"items": items
}
def register(self):
'''Registers the action, subscribing the 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
)
self.session.event_hub.subscribe(
'topic=ftrack.action.launch and data.actionIdentifier={0} and source.user.username={1}'.format(
self.identifier,
self.session.api_user
),
self.launch
)
print("----- action - <" + self.__class__.__name__ + "> - Has been registered -----")
def get_applications(self):
applications = []
label="DJVView {version}"
versionExpression=re.compile(r"(?P<version>\d+.\d+.\d+)")
applicationIdentifier="djvview"
description="DJV View Launcher"
icon="http://a.fsdn.com/allura/p/djv/icon"
expression = []
if sys.platform == "win32":
expression = ["C:\\", "Program Files", "djv-\d.+",
"bin", "djv_view.exe"]
elif sys.platform == "darwin":
expression = ["Application", "DJV.app", "Contents", "MacOS", "DJV"]
## Linuxs
else:
expression = ["usr", "local", "djv", "djv_view"]
pieces = expression[:]
start = pieces.pop(0)
if sys.platform == 'win32':
# On Windows C: means current directory so convert roots that look
# like drive letters to the C:\ format.
if start and start[-1] == ':':
start += '\\'
if not os.path.exists(start):
raise ValueError(
'First part "{0}" of expression "{1}" must match exactly to an '
'existing entry on the filesystem.'
.format(start, expression)
)
expressions = list(map(re.compile, pieces))
expressionsCount = len(expression)-1
for location, folders, files in os.walk(start, topdown=True, followlinks=True):
level = location.rstrip(os.path.sep).count(os.path.sep)
expression = expressions[level]
if level < (expressionsCount - 1):
# If not yet at final piece then just prune directories.
folders[:] = [folder for folder in folders
if expression.match(folder)]
else:
# Match executable. Note that on OSX executable might equate to
# a folder (.app).
for entry in folders + files:
match = expression.match(entry)
if match:
# Extract version from full matching path.
path = os.path.join(start, location, entry)
versionMatch = versionExpression.search(path)
if versionMatch:
version = versionMatch.group('version')
applications.append({
'identifier': applicationIdentifier.format(
version=version
),
'path': path,
'version': version,
'label': label.format(version=version),
'icon': icon,
# 'variant': variant.format(version=version),
'description': description
})
else:
self.logger.debug(
'Discovered application executable, but it '
'does not appear to o contain required version '
'information: {0}'.format(path)
)
# Don't descend any further as out of patterns to match.
del folders[:]
return applications
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')
if (
alias_for and isinstance(alias_for, str) and
alias_for.lower() == entity_type
):
return schema['id']
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 These should be obtained in another way
start = 375
end = 379
fps = 24
# TODO issequence is probably already built-in validation in ftrack
isseq = re.findall( '%[0-9]*d', filename )
if len(isseq) > 0:
padding = re.findall( '%[0-9]*d', filename ).pop()
range = ( padding % start ) + '-' + ( padding % end )
filename = re.sub( '%[0-9]*d', range, filename )
cmd = []
# DJV path
cmd.append( os.path.normpath( self.djv_path ) )
### DJV Options Start ################################################
# cmd.append( '-file_layer (value)' ) #layer name
cmd.append( '-file_proxy 1/2' ) #Proxy scale: 1/2, 1/4, 1/8
cmd.append( '-file_cache True' ) # Cache: True, False.
# cmd.append( '-window_fullscreen' ) #Start in full screen
# cmd.append("-window_toolbar False") # Toolbar controls: False, True.
# cmd.append("-window_playbar False") # Window controls: False, True.
# cmd.append("-view_grid None") # Grid overlay: None, 1x1, 10x10, 100x100.
# cmd.append("-view_hud True") # Heads up display: True, False.
cmd.append("-playback Forward") # Playback: Stop, Forward, Reverse.
# cmd.append("-playback_frame (value)") # Frame.
cmd.append("-playback_speed " + str(fps))
# cmd.append("-playback_timer (value)") # Timer: Sleep, Timeout. Value: Sleep.
# cmd.append("-playback_timer_resolution (value)") # Timer resolution (seconds): 0.001.
cmd.append("-time_units Frames") # Time units: Timecode, Frames.
### DJV Options End ##################################################
# PATH TO COMPONENT
cmd.append( os.path.normpath( filename ) )
# Run DJV with these commands
subprocess.Popen( ' '.join( cmd ) )
return {
'success': True,
'message': 'DJV View started.'
}
if 'items' not in event["data"]:
event["data"]['items'] = []
try:
for entity in entities:
versions = []
allowed_types = ["img", "mov", "exr"]
if entity.entity_type.lower() == "assetversion":
if entity['components'][0]['file_type'] in allowed_types:
versions.append(entity)
if 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
if version['components'][0]['file_type'] 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:
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!!!
# NOT WORKING RIGHT NOW
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:
# This is NOT proper way
file_path = component['component_locations'][0]['resource_identifier']
event["data"]["items"].append(
{"label": label, "value": file_path}
)
except Exception as e:
return {
'success': False,
'message': str(e)
}
return {
"items": [
{
"label": "Items to view",
"type": "enumerator",
"name": "path",
"data": sorted(
event["data"]['items'],
key=itemgetter("label"),
reverse=True
)
}
]
}
def register(session, **kw):
"""Register hooks."""
if not isinstance(session, ftrack_api.session.Session):
return
action = DJVViewAction(session)
action.register()
def main(arguments=None):
'''Set up logging and register action.'''
if arguments is None:
arguments = []
parser = argparse.ArgumentParser()
# Allow setting of logging level from arguments.
loggingLevels = {}
for level in (
logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING,
logging.ERROR, logging.CRITICAL
):
loggingLevels[logging.getLevelName(level).lower()] = level
parser.add_argument(
'-v', '--verbosity',
help='Set the logging output verbosity.',
choices=loggingLevels.keys(),
default='info'
)
namespace = parser.parse_args(arguments)
# Set up basic logging
logging.basicConfig(level=loggingLevels[namespace.verbosity])
session = ftrack_api.Session()
register(session)
# Wait for events
logging.info(
'Registered actions and listening for events. Use Ctrl-C to abort.'
)
session.event_hub.wait()
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View file

@ -1,115 +0,0 @@
import os
import logging
import json
import ftrack
import ftrack_api
import clique
import ftrack_template
log = logging.getLogger(__name__)
def modify_launch(session, event):
"""Modify the application launch command with potential files to open"""
# Collect published paths
data = {}
for item in event["data"].get("selection", []):
versions = []
if entity.entity_type == "Assetversion":
version = ftrack.AssetVersion(item["entityId"])
if version.getAsset().getType().getShort() in ["img", "mov"]:
versions.append(version)
# Add latest version of "img" and "mov" type from tasks.
if item["entityType"] == "task":
task = ftrack.Task(item["entityId"])
for asset in task.getAssets(assetTypes=["img", "mov"]):
versions.append(asset.getVersions()[-1])
for version in versions:
for component in version.getComponents():
component_list = data.get(component.getName(), [])
component_list.append(component)
data[component.getName()] = component_list
label = "v{0} - {1} - {2}"
label = label.format(
str(version.getVersion()).zfill(3),
version.getAsset().getType().getName(),
component.getName()
)
file_path = component.getFilesystemPath()
if component.isSequence():
if component.getMembers():
frame = int(component.getMembers()[0].getName())
file_path = file_path % frame
event["data"]["items"].append(
{"label": label, "value": file_path}
)
# Collect workspace paths
session = ftrack_api.Session()
for item in event["data"].get("selection", []):
if item["entityType"] == "task":
templates = ftrack_template.discover_templates()
task_area, template = ftrack_template.format(
{}, templates, entity=session.get("Task", item["entityId"])
)
# Traverse directory and collect collections from json files.
instances = []
for root, dirs, files in os.walk(task_area):
for f in files:
if f.endswith(".json"):
with open(os.path.join(root, f)) as json_data:
for data in json.load(json_data):
instances.append(data)
check_values = []
for data in instances:
if "collection" in data:
# Check all files in the collection
collection = clique.parse(data["collection"])
for f in list(collection):
if not os.path.exists(f):
collection.remove(f)
if list(collection):
value = list(collection)[0]
# Check if value already exists
if value in check_values:
continue
else:
check_values.append(value)
# Add workspace items
event["data"]["items"].append(
{
"label": "{0} - {1}".format(
data["name"],
os.path.basename(collection.format())
),
"value": value
}
)
return event
def register(session, **kw):
# Validate session
if not isinstance(session, ftrack_api.session.Session):
return
session.event_hub.subscribe(
'topic=djvview.launch',
modify_launch(session)
)