Snippets

ftrack Action - Update frame durations

Created by Lucas Correia

File README.rst Added

  • Ignore whitespace
  • Hide word diff
+..
+    :copyright: Copyright (c) 2019 ftrack
+
+*****************************
+Update frame durations action
+*****************************
+
+The action *Update frame durations* will display an interface where the
+duration (in frames) of any reviewable video can be updated.
+
+Using the action
+================
+
+Navigate to a project in the web interface and select a few versions in the 
+spreadsheet and select `Actions` from the context menu. Click on 
+`Update frame duration`.
+
+A list of reviewable versions will be displayed and for each the frame duration
+can be modified.
+
+Playing back the versions in the web player using "Load selected" should show
+each clip only for the specified duration.
+
+Available on
+============
+
+* Project
+* Object (Folder, Shot, Task, etc.)
+* Version
+* Component
+
+Running the action
+==================
+
+Install the ftrack-action-handler:
+
+.. code::
+
+    pip install git+https://bitbucket.org/ftrack/ftrack-action-handler.git
+
+.. tip::
+
+    You can use `virtualenv <https://virtualenv.pypa.io/en/stable/>`_ or similar
+    solutions to avoid installing packages into your system's Python environment.
+
+Start the listener from the terminal using the following command:
+
+.. code::
+
+    python update_frame_duration_action.py
+
+If you wish to see debugging information, set the verbosity level by appending
+``-v debug`` to the command.
+
+Reade more
+==========
+
+For more information in our documentation:
+
+* `Using actions <http://ftrack.rtd.ftrack.com/en/latest/using/actions.html>`_.
+* `Action handler <http://ftrack-action-handler.rtd.ftrack.com/en/latest/index.html>`_.

File update_frame_duration_action.py Added

  • Ignore whitespace
  • Hide word diff
+# :coding: utf-8
+# # :copyright: Copyright (c) 2019 ftrack
+import logging
+import collections
+import json
+
+import ftrack_api
+
+from ftrack_action_handler.action import BaseAction
+
+
+def get_filter_string(entity_ids):
+    '''Return a comma separated string of quoted ids from *entity_ids* list.'''
+    return ', '.join(
+        '"{0}"'.format(entity_id) for entity_id in entity_ids
+    )
+
+
+class UpdateFrameDurationAction(BaseAction):
+    '''Action to update the frame out metadata of reviewable components.'''
+    label = 'Update frame durations'
+    identifier = 'ftrack.update_frame_duration_action'
+
+    def discover(self, session, entities, event):
+        if not self.validate_selection(entities):
+            return super(UpdateFrameDurationAction, self).discover(
+                session, entities,  event
+            )
+
+        return True
+
+    def get_review_components(self, session, entities):
+        '''Return list of components in *entities*.'''
+        component_queries = []
+        entity_groups = collections.defaultdict(list)
+        for entity_type, entity_id in entities:
+            entity_groups[entity_type].append(entity_id)
+
+        if entity_groups['Project']:
+            component_queries.append(
+                'Component where (version.asset.parent.project.id in ({0}) or '
+                'version.asset.parent.id in ({0}))'.format(
+                    get_filter_string(entity_groups['Project'])
+                )
+            )
+
+        if entity_groups['TypedContext']:
+            component_queries.append(
+                'Component where version.asset.parent.ancestors.id '
+                'in ({0})'.format(
+                    get_filter_string(entity_groups['TypedContext'])
+                )
+            )
+
+        if entity_groups['AssetVersion']:
+            component_queries.append(
+                'Component where version_id in ({0})'.format(
+                    get_filter_string(entity_groups['AssetVersion'])
+                )
+            )
+
+        if entity_groups['Component']:
+            component_queries.append(
+                'Component where id in ({0})'.format(
+                    get_filter_string(entity_groups['Component'])
+                )
+            )
+
+        components = set()
+        for query_string in component_queries:
+            components.update(
+                session.query(
+                    'select version.link, metadata from {0} and file_type is ".mp4" and component_locations.location_id is "{1}" and metadata.key is "ftr_meta"'.format(
+                        query_string, ftrack_api.symbol.SERVER_LOCATION_ID
+                    )
+                ).all()
+            )
+
+        self.logger.info(
+            'Found {0} components in selection'.format(len(components))
+        )
+        return list(components)
+
+    def launch(self, session, entities, event):
+        '''Callback method for action.'''
+        self.logger.info(
+            u'Launching action with selection {0}'.format(entities)
+        )
+
+        # Validate selection and abort if not valid
+        if not self.validate_selection(entities):
+            self.logger.warning(
+                'Selection is not valid, aborting action'
+            )
+
+            return
+
+        values = event['data'].get('values', {})
+        self.update_frame_out(session, values)
+
+        try:
+            session.commit()
+        except:
+            # Commit failed, rollback session and re-raise.
+            session.rollback()
+            raise
+
+        return {
+            'success': True,
+            'message': 'Updated frame out on review components',
+        }
+
+    def update_frame_out(self, session, values):
+        '''Find and replace *find* and *replace* in *attribute* for *selection*.'''
+        for component_id, frame_out in values.iteritems():
+            component = session.get('Component', component_id)
+
+            if component:
+                meta = json.loads(component['metadata']['ftr_meta'])
+                meta['frameOut'] = float(frame_out)
+                component['metadata']['ftr_meta'] = json.dumps(meta)
+                self.logger.debug('Updated component {}: {}'.format(component_id, frame_out))
+
+
+    def validate_selection(self, entities):
+        '''Return True if *entities* is valid'''
+        # Replace with custom logic for validating selection.
+        # For example check the length or entityType of items in selection.
+        return len(entities)
+
+    def interface(self, session, entities, event):
+        values = event['data'].get('values', {})
+
+        if not values:
+            components = self.get_review_components(session, entities)
+            if len(components) > 100:
+                raise ValueError('Can not handle more than 100 components at once.')
+
+            items = [{
+                'type': 'label',
+                'value': '## Frame duration for reviewable components'
+            }]
+            for component in components:
+                link = component['version']['link']
+                label = u' / '.join([item['name'] for item in link[-2:]])
+
+                value = ''
+                try:
+                    value = json.loads(component['metadata']['ftr_meta'])['frameOut']
+                except Exception:
+                    self.logger.exception('Failed to get frame out')
+
+                items.append({
+                    'type': 'number',
+                    'label': label,
+                    'name': component['id'],
+                    'value': value,
+                })
+
+            return items
+
+
+def register(session, **kw):
+    '''Register plugin. Called when used as an plugin.'''
+    # Validate that session is an instance of ftrack_api.Session. If not,
+    # assume that register is being called from an old or incompatible API and
+    # return without doing anything.
+    if not isinstance(session, ftrack_api.session.Session):
+        return
+
+    action_handler = UpdateFrameDurationAction(session)
+    action_handler.register()
+
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.INFO)
+    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()
HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.