def __validate(self, value, validate_transforms): """ Test if a value is valid for this key. :param value: Value to test :param validate_transforms: If true, then validate that transforms that mutate the value of a key are valid and can be applied. :returns: True if valid, false if not. """ u_value = value if not isinstance(u_value, six.text_type): # handle non-ascii characters correctly by # decoding to unicode assuming utf-8 encoding u_value = value.decode("utf-8") if self._filter_regex_u: # first check our std filters. These filters are negated # so here we are checking that there are occurances of # that pattern in the string if self._filter_regex_u.search(u_value): self._last_error = ( "%s Illegal value '%s' does not fit filter_by '%s'" % (self, value, self.filter_by) ) return False elif self._custom_regex_u: # check for any user specified regexes if self._custom_regex_u.match(u_value) is None: self._last_error = ( "%s Illegal value '%s' does not fit filter_by '%s'" % (self, value, self.filter_by) ) return False # check subset regex if self._subset_regex and validate_transforms: regex_match = self._subset_regex.match(u_value) if regex_match is None: self._last_error = ( "%s Illegal value '%s' does not fit " "subset expression '%s'" % (self, value, self.subset) ) return False # validate that the formatting can be applied to the input value if self._subset_format: try: # perform the formatting in unicode space to cover all cases six.ensure_text(self._subset_format).format(*regex_match.groups()) except Exception as e: self._last_error = ( "%s Illegal value '%s' does not fit subset '%s' with format '%s': %s" % (self, value, self.subset, self.subset_format, e) ) return False return super(StringKey, self).validate(value)
def execute_action(self, name, params, sg_publish_data): """ Execute a given action. The data sent to this be method will represent one of the actions enumerated by the generate_actions method. :param name: Action name string representing one of the items returned by generate_actions. :param params: Params data, as specified by generate_actions. :param sg_publish_data: Shotgun data dictionary with all the standard publish fields. """ app = self.parent app.log_debug("Execute action called for action %s. " "Parameters: %s. Publish Data: %s" % (name, params, sg_publish_data)) # resolve path # toolkit uses utf-8 encoded strings internally and the Photoshop API expects unicode # so convert the path to ensure filenames containing complex characters are supported path = six.ensure_text(self.get_publish_path(sg_publish_data)) if not os.path.exists(path): raise Exception("File not found on disk - '%s'" % path) if name == _OPEN_FILE: self._open_file(path, sg_publish_data) if name == _ADD_AS_A_LAYER: self._place_file(path, sg_publish_data)
def _as_string(self, value): """ Converts the given value to a string representation. :param value: value of any type to convert. Value is never None. :returns: string representation for this object. """ str_value = value if isinstance(value, six.string_types) else str(value) if self._subset_regex: # process substring computation. # we want to do this in unicode. if isinstance(str_value, six.binary_type): # convert to unicode input_is_utf8 = True value_to_convert = str_value.decode("utf-8") else: # already unicode input_is_utf8 = False value_to_convert = str_value # now perform extraction and concat match = self._subset_regex.match(value_to_convert) if match is None: # no match. return empty string # validate should prevent this from happening resolved_value = u"" elif self._subset_format: # we have an explicit format string we want to apply to the # match. Do the formatting as unicode. resolved_value = six.ensure_text( self._subset_format).format(*match.groups()) else: # we have a match object. concatenate the groups resolved_value = "".join(match.groups()) # resolved value is now unicode. Convert it # so that it is consistent with input if isinstance(resolved_value, six.text_type) and input_is_utf8: # input was utf-8, regex resut is unicode, cast it back str_value = resolved_value.encode("utf-8") else: str_value = resolved_value return str_value
def execute_action(self, name, params, sg_data): """ Execute a given action. The data sent to this be method will represent one of the actions enumerated by the generate_actions method. :param name: Action name string representing one of the items returned by generate_actions. :param params: Params data, as specified by generate_actions. :param sg_data: Shotgun data dictionary with all the standard publish fields. """ app = self.parent app.logger.debug( "Execute action called for action %s. " "Parameters: %s. Publish Data: %s" % (name, params, sg_data) ) # This hook only implements the add to comp and project logic, fall back to the # base class for anything else. if name in [_ADD_TO_COMP, _ADD_TO_PROJECT]: # resolve path # toolkit uses utf-8 encoded strings internally and the After Effects API expects unicode # so convert the path to ensure filenames containing complex characters are supported path = six.ensure_text(self.get_publish_path(sg_data)) if self.parent.engine.is_adobe_sequence(path): frame_range = self.parent.engine.find_sequence_range(path) if frame_range: glob_path = re.sub( r"[\[]?([#@]+|%0\d+d)[\]]?", "*{}".format(frame_range[0]), path ) for each_path in sorted(glob.glob(glob_path)): path = each_path break if not os.path.exists(path): raise Exception("File not found on disk - '%s'" % path) if name == _ADD_TO_COMP: self._add_to_comp(path) if name == _ADD_TO_PROJECT: self.parent.engine.import_filepath(path) else: try: HookBaseClass.execute_action(self, name, params, sg_data) except AttributeError: # base class doesn't have the method, so ignore and continue pass
def finalize(self, settings, item): """ Execute the finalization pass. This pass executes once all the publish tasks have completed, and can for example be used to version up files. :param settings: Dictionary of Settings. The keys are strings, matching the keys returned in the settings property. The values are `Setting` instances. :param item: Item to process """ publisher = self.parent thumb = item.get_thumbnail_as_path() upload_thumb = True version = item.properties["sg_version_data"] finalize_tasks = item.properties.get("version_finalize") if version and finalize_tasks: if finalize_tasks["update"]: publisher.shotgun.update("Version", version["id"], finalize_tasks["update"]) if finalize_tasks["upload"]: for field, path in finalize_tasks["upload"].iteritems(): self.logger.info("Uploading content...") # on windows, ensure the path is utf-8 encoded to avoid issues with # the shotgun api if sgtk.util.is_windows(): upload_path = six.ensure_text(path) else: upload_path = path publisher.shotgun.upload("Version", version["id"], upload_path, field) upload_thumb = False if upload_thumb: # only upload thumb if we are not uploading the content. with # uploaded content, the thumb is automatically extracted. self.logger.info("Uploading thumbnail...") publisher.shotgun.upload_thumbnail("Version", version["id"], thumb)
def _get_latest_by_pattern(self, pattern): """ Returns a descriptor object that represents the latest version, but based on a version pattern. :param pattern: Version patterns are on the following forms: - v1.2.3 (can return this v1.2.3 but also any forked version under, eg. v1.2.3.2) - v1.2.x (examples: v1.2.4, or a forked version v1.2.4.2) - v1.x.x (examples: v1.3.2, a forked version v1.3.2.2) - v1.2.3.x (will always return a forked version, eg. v1.2.3.2) :returns: IODescriptorGitTag object """ try: # clone the repo, list all tags # for the repository, across all branches commands = ["tag"] git_tags = six.ensure_text( self._tmp_clone_then_execute_git_commands(commands) ).split("\n") except Exception as e: raise TankDescriptorError( "Could not get list of tags for %s: %s" % (self._path, e) ) if len(git_tags) == 0: raise TankDescriptorError( "Git repository %s doesn't have any tags!" % self._path ) latest_tag = self._find_latest_tag_by_pattern(git_tags, pattern) if latest_tag is None: raise TankDescriptorError( "'%s' does not have a version matching the pattern '%s'. " "Available versions are: %s" % (self.get_system_name(), pattern, ", ".join(git_tags)) ) return latest_tag
def execute_action(self, name, params, sg_publish_data): """ Execute a given action. The data sent to this be method will represent one of the actions enumerated by the generate_actions method. :param name: Action name string representing one of the items returned by generate_actions. :param params: Params data, as specified by generate_actions. :param sg_publish_data: Shotgun data dictionary with all the standard publish fields. :returns: No return value expected. """ app = self.parent app.log_debug( "Execute action called for action %s. " "Parameters: %s. Publish Data: %s" % (name, params, sg_publish_data) ) # resolve path # toolkit uses utf-8 encoded strings internally and the 3dsmax API expects unicode # so convert the path to ensure filenames containing complex characters are supported path = six.ensure_text(self.get_publish_path(sg_publish_data)) # If this is an Alembic cache, then we can import that. if path.lower().endswith(".abc"): # Note that native Alembic support is only available in Max 2016+. if app.engine._max_version_to_year(app.engine._get_max_version()) >= 2016: self._import_alembic(path) else: app.log_warning( "Alembic imports are not available in Max 2015, skipping." ) elif name == "merge": self._merge(path, sg_publish_data) elif name == "xref_scene": self._xref_scene(path, sg_publish_data) elif name == "texture_node": self._create_texture_node(path, sg_publish_data)
def publish(self, settings, item): """ Executes the publish logic for the given item and settings. :param settings: Dictionary of Settings. The keys are strings, matching the keys returned in the settings property. The values are `Setting` instances. :param item: Item to process """ publisher = self.parent path = item.properties["path"] # allow the publish name to be supplied via the item properties. this is # useful for collectors that have access to templates and can determine # publish information about the item that doesn't require further, fuzzy # logic to be used here (the zero config way) publish_name = item.properties.get("publish_name") if not publish_name: self.logger.debug("Using path info hook to determine publish name.") # use the path's filename as the publish name path_components = publisher.util.get_file_path_components(path) publish_name = path_components["filename"] self.logger.debug("Publish name: %s" % (publish_name,)) self.logger.info("Creating Version...") version_data = { "project": item.context.project, "code": publish_name, "description": item.description, "entity": self._get_version_entity(item), "sg_task": item.context.task, } if "sg_publish_data" in item.properties: publish_data = item.properties["sg_publish_data"] version_data["published_files"] = [publish_data] if settings["Link Local File"].value: version_data["sg_path_to_movie"] = path # log the version data for debugging self.logger.debug( "Populated Version data...", extra={ "action_show_more_info": { "label": "Version Data", "tooltip": "Show the complete Version data dictionary", "text": "<pre>%s</pre>" % (pprint.pformat(version_data),), } }, ) # Create the version version = publisher.shotgun.create("Version", version_data) self.logger.info("Version created!") # stash the version info in the item just in case item.properties["sg_version_data"] = version thumb = item.get_thumbnail_as_path() if settings["Upload"].value: self.logger.info("Uploading content...") # on windows, ensure the path is utf-8 encoded to avoid issues with # the shotgun api if sgtk.util.is_windows(): upload_path = six.ensure_text(path) else: upload_path = path self.parent.shotgun.upload( "Version", version["id"], upload_path, "sg_uploaded_movie" ) elif thumb: # only upload thumb if we are not uploading the content. with # uploaded content, the thumb is automatically extracted. self.logger.info("Uploading thumbnail...") self.parent.shotgun.upload_thumbnail("Version", version["id"], thumb) self.logger.info("Upload complete!")
def publish(self, settings, item): """ Executes the publish logic for the given item and settings. :param settings: Dictionary of Settings. The keys are strings, matching the keys returned in the settings property. The values are `Setting` instances. :param item: Item to process """ publisher = self.parent path = item.properties["path"] # allow the publish name to be supplied via the item properties. this is # useful for collectors that have access to templates and can determine # publish information about the item that doesn't require further, fuzzy # logic to be used here (the zero config way) publish_name = item.properties.get("publish_name") if not publish_name: self.logger.debug("Using path info hook to determine publish name.") # use the path's filename as the publish name path_components = publisher.util.get_file_path_components(path) publish_name = path_components["filename"] self.logger.debug("Publish name: %s" % (publish_name,)) self.logger.info("Creating Version...") def get_path_to_frames(): change_folder = path.replace('\mov', '/fullres') change_file_extension = change_folder.replace('.mov', '.####.exr') return change_file_extension def get_userID(): import os userName = os.environ['USERNAME'] userDict = {'bgil': 286, 'daniel': 61, 'david': 58, 'dnicolas': 70, 'dperea': 152, 'hector': 62, 'jaime': 53, 'jordi': 54, 'jalvarez': 72, 'jgomez': 151, 'lgarcia': 118, 'lucia': 63, 'mduque': 187, 'mmartinez': 319, 'pedro': 51, 'pibanez': 153, 'freelance': 385, 'fernando': 67} sg_user = userDict.get(userName) return sg_user version_data = { "sg_path_to_frames": get_path_to_frames(), "project": item.context.project, "code": publish_name, "description": item.description, "entity": self._get_version_entity(item), "sg_task": item.context.task, "user": {'type': 'HumanUser', 'id': get_userID()} } if "sg_publish_data" in item.properties: publish_data = item.properties["sg_publish_data"] version_data["published_files"] = [publish_data] if settings["Link Local File"].value: version_data["sg_path_to_movie"] = path # log the version data for debugging self.logger.debug( "Populated Version data...", extra={ "action_show_more_info": { "label": "Version Data", "tooltip": "Show the complete Version data dictionary", "text": "<pre>%s</pre>" % (pprint.pformat(version_data),), } }, ) # Create the version version = publisher.shotgun.create("Version", version_data) self.logger.info("Version created!") # stash the version info in the item just in case item.properties["sg_version_data"] = version thumb = item.get_thumbnail_as_path() if settings["Upload"].value: self.logger.info("Uploading content...") # on windows, ensure the path is utf-8 encoded to avoid issues with # the shotgun api if sgtk.util.is_windows(): upload_path = six.ensure_text(path) else: upload_path = path self.parent.shotgun.upload( "Version", version["id"], upload_path, "sg_uploaded_movie" ) elif thumb: # only upload thumb if we are not uploading the content. with # uploaded content, the thumb is automatically extracted. self.logger.info("Uploading thumbnail...") self.parent.shotgun.upload_thumbnail("Version", version["id"], thumb) self.logger.info("Upload complete!")
def publish(self, settings, item): """ Executes the publish logic for the given item and settings. :param settings: Dictionary of Settings. The keys are strings, matching the keys returned in the settings property. The values are `Setting` instances. :param item: Item to process """ publisher = self.parent path = item.properties["path"] # get tk object to do some out of scope calls tk = publisher.sgtk # allow the publish name to be supplied via the item properties. this is # useful for collectors that have access to templates and can determine # publish information about the item that doesn't require further, fuzzy # logic to be used here (the zero config way) publish_name = item.properties.get("publish_name") if not publish_name: self.logger.debug("Using path info hook to determine publish name.") # use the path's filename as the publish name path_components = publisher.util.get_file_path_components(path) publish_name = path_components["filename"] self.logger.debug("Publish name: %s" % (publish_name,)) self.logger.info("Creating Version...") version_data = { "project": item.context.project, "code": publish_name, "description": item.description, "entity": self._get_version_entity(item), "sg_task": item.context.task, } # if correlates are defined at all, look to see if any matched and then use them if settings.get('Source Correlate'): correlate = item.properties.get('correlate') if correlate: update = correlate.get('rule').get('update') if update and update.get('entity').lower() == "version": # if there is a destination field for putting the correlated path version_data[update.get('field')] = correlate.get('path') self.logger.info("Correlate media set in %s.%s: %s" % (update.get('entity'), update.get('field'), correlate.get('path'))) if all(key in item.properties for key in ('first_frame', 'last_frame')): first_frame = item.properties["first_frame"] last_frame = item.properties["last_frame"] version_data["sg_first_frame"] = first_frame version_data["sg_last_frame"] = last_frame version_data["frame_count"] = int(last_frame - first_frame + 1) version_data["frame_range"] = "{}-{}".format(first_frame, last_frame) if "sg_publish_data" in item.properties: publish_data = item.properties["sg_publish_data"] version_data["published_files"] = [publish_data] # make local media link dependent on publish file path if settings["Link Local File"].value: version_data["sg_path_to_movie"] = publish_data.get('path').get('local_path_linux') # log the version data for debugging self.logger.debug( "Populated Version data...", extra={ "action_show_more_info": { "label": "Version Data", "tooltip": "Show the complete Version data dictionary", "text": "<pre>%s</pre>" % (pprint.pformat(version_data),), } }, ) # Create the version version = publisher.shotgun.create("Version", version_data) self.logger.info("Version created!") # stash the version info in the item just in case item.properties["sg_version_data"] = version thumb = item.get_thumbnail_as_path() if settings["Upload"].value: self.logger.info("Uploading content...") # on windows, ensure the path is utf-8 encoded to avoid issues with # the shotgun api if sgtk.util.is_windows(): upload_path = six.ensure_text(path) else: upload_path = path self.parent.shotgun.upload( "Version", version["id"], upload_path, "sg_uploaded_movie" ) elif thumb: # only upload thumb if we are not uploading the content. with # uploaded content, the thumb is automatically extracted. self.logger.info("Uploading thumbnail...") self.parent.shotgun.upload_thumbnail("Version", version["id"], thumb) self.logger.info("Upload complete!")