def _remove_layers(self, layer_names=None, layer_ids=None, layers=None): if not layer_names and not layer_ids: self.log.warning("Got empty layer names list.") return if layers is None: layers = lib.layers_data() available_ids = set(layer["layer_id"] for layer in layers) if layer_ids is None: # Backwards compatibility (layer ids were stored instead of names) layer_names_are_ids = True for layer_name in layer_names: if (not isinstance(layer_name, int) and not layer_name.isnumeric()): layer_names_are_ids = False break if layer_names_are_ids: layer_ids = layer_names layer_ids_to_remove = [] if layer_ids is not None: for layer_id in layer_ids: if layer_id in available_ids: layer_ids_to_remove.append(layer_id) else: layers_by_name = collections.defaultdict(list) for layer in layers: layers_by_name[layer["name"]].append(layer) for layer_name in layer_names: layers = layers_by_name[layer_name] if len(layers) == 1: layer_ids_to_remove.append(layers[0]["layer_id"]) if not layer_ids_to_remove: self.log.warning("No layers to delete.") return george_script_lines = [] for layer_id in layer_ids_to_remove: line = "tv_layerkill {}".format(layer_id) george_script_lines.append(line) george_script = "\n".join(george_script_lines) lib.execute_george_through_file(george_script)
def load(self, context, name, namespace, options): stretch = options.get("stretch", self.defaults["stretch"]) timestretch = options.get("timestretch", self.defaults["timestretch"]) preload = options.get("preload", self.defaults["preload"]) load_options = [] if stretch: load_options.append("\"STRETCH\"") if timestretch: load_options.append("\"TIMESTRETCH\"") if preload: load_options.append("\"PRELOAD\"") load_options_str = "" for load_option in load_options: load_options_str += (load_option + " ") # Prepare layer name asset_name = context["asset"]["name"] version_name = context["version"]["name"] layer_name = "{}_{}_v{:0>3}".format( asset_name, name, version_name ) # Fill import script with filename and layer name # - filename mus not contain backwards slashes george_script = self.import_script.format( self.fname.replace("\\", "/"), layer_name, load_options_str ) return lib.execute_george_through_file(george_script)
def load(self, context, name, namespace, options): stretch = options.get("stretch", self.defaults["stretch"]) timestretch = options.get("timestretch", self.defaults["timestretch"]) preload = options.get("preload", self.defaults["preload"]) load_options = [] if stretch: load_options.append("\"STRETCH\"") if timestretch: load_options.append("\"TIMESTRETCH\"") if preload: load_options.append("\"PRELOAD\"") load_options_str = "" for load_option in load_options: load_options_str += (load_option + " ") # Prepare layer name asset_name = context["asset"]["name"] subset_name = context["subset"]["name"] layer_name = self.get_unique_layer_name(asset_name, subset_name) # Fill import script with filename and layer name # - filename mus not contain backwards slashes george_script = self.import_script.format( self.fname.replace("\\", "/"), layer_name, load_options_str) lib.execute_george_through_file(george_script) loaded_layer = None layers = lib.layers_data() for layer in layers: if layer["name"] == layer_name: loaded_layer = layer break if loaded_layer is None: raise AssertionError( "Loading probably failed during execution of george script.") layer_names = [loaded_layer["name"]] namespace = namespace or layer_name return pipeline.containerise(name=name, namespace=namespace, members=layer_names, context=context, loader=self.__class__.__name__)
def render_review(self, filename_template, output_dir, frame_start, frame_end): """ Export images from TVPaint using `tv_savesequence` command. Args: filename_template (str): Filename template of an output. Template should already contain extension. Template may contain only keyword argument `{frame}` or index argument (for same value). Extension in template must match `save_mode`. output_dir (str): Directory where files will be stored. first_frame (int): Starting frame from which export will begin. last_frame (int): On which frame export will end. Retruns: tuple: With 2 items first is list of filenames second is path to thumbnail. """ self.log.debug("Preparing data for rendering.") first_frame_filepath = os.path.join( output_dir, filename_template.format(frame=frame_start)) mark_in = frame_start - 1 mark_out = frame_end - 1 george_script_lines = [ "tv_SaveMode \"PNG\"", "export_path = \"{}\"".format( first_frame_filepath.replace("\\", "/")), "tv_savesequence '\"'export_path'\"' {} {}".format( mark_in, mark_out) ] lib.execute_george_through_file("\n".join(george_script_lines)) output = [] first_frame_filepath = None for frame in range(frame_start, frame_end + 1): filename = filename_template.format(frame=frame) output.append(filename) if first_frame_filepath is None: first_frame_filepath = os.path.join(output_dir, filename) thumbnail_filepath = os.path.join(output_dir, "thumbnail.jpg") if first_frame_filepath and os.path.exists(first_frame_filepath): source_img = Image.open(first_frame_filepath) thumbnail_obj = Image.new("RGB", source_img.size, (255, 255, 255)) thumbnail_obj.paste(source_img) thumbnail_obj.save(thumbnail_filepath) return output, thumbnail_filepath
def process(self): self.log.debug("Query data from workfile.") instances = pipeline.list_instances() layers_data = lib.layers_data() self.log.debug("Checking for selection groups.") # Collect group ids from selection group_ids = set() for layer in layers_data: if layer["selected"]: group_ids.add(layer["group_id"]) # Raise if there is no selection if not group_ids: raise AssertionError("Nothing is selected.") # This creator should run only on one group if len(group_ids) > 1: raise AssertionError("More than one group is in selection.") group_id = tuple(group_ids)[0] # If group id is `0` it is `default` group which is invalid if group_id == 0: raise AssertionError( "Selection is not in group. Can't mark selection as Beauty." ) self.log.debug(f"Selected group id is \"{group_id}\".") self.data["group_id"] = group_id family = self.data["family"] # Extract entered name name = self.data["subset"][len(family):] self.log.info(f"Extracted name from subset name \"{name}\".") self.data["name"] = name # Change subset name by template subset_name = self.subset_template.format(**{ "family": self.family, "name": name }) self.log.info(f"New subset name \"{subset_name}\".") self.data["subset"] = subset_name # Check for instances of same group existing_instance = None existing_instance_idx = None # Check if subset name is not already taken same_subset_instance = None same_subset_instance_idx = None for idx, instance in enumerate(instances): if instance["family"] == family: if instance["group_id"] == group_id: existing_instance = instance existing_instance_idx = idx elif instance["subset"] == subset_name: same_subset_instance = instance same_subset_instance_idx = idx if ( same_subset_instance_idx is not None and existing_instance_idx is not None ): break if same_subset_instance_idx is not None: if self._ask_user_subset_override(same_subset_instance): instances.pop(same_subset_instance_idx) else: return if existing_instance is not None: self.log.info( f"Beauty instance for group id {group_id} already exists" ", overriding" ) instances[existing_instance_idx] = self.data else: instances.append(self.data) self.write_instances(instances) if not self.rename_group: self.log.info("Group rename function is turned off. Skipping") return self.log.debug("Querying groups data from workfile.") groups_data = lib.groups_data() self.log.debug("Changing name of the group.") selected_group = None for group_data in groups_data: if group_data["group_id"] == group_id: selected_group = group_data # Rename TVPaint group (keep color same) # - groups can't contain spaces new_group_name = name.replace(" ", "_") rename_script = self.rename_script_template.format( clip_id=selected_group["clip_id"], group_id=selected_group["group_id"], r=selected_group["red"], g=selected_group["green"], b=selected_group["blue"], name=new_group_name ) lib.execute_george_through_file(rename_script) self.log.info( f"Name of group with index {group_id}" f" was changed to \"{new_group_name}\"." )
def _render_layer(self, layer, tmp_filename_template, output_dir, behavior, mark_in_index, mark_out_index): layer_id = layer["layer_id"] frame_start_index = layer["frame_start"] frame_end_index = layer["frame_end"] exposure_frames = lib.get_exposure_frames(layer_id, frame_start_index, frame_end_index) if frame_start_index not in exposure_frames: exposure_frames.append(frame_start_index) layer_files_by_frame = {} george_script_lines = ["tv_SaveMode \"PNG\""] layer_position = layer["position"] for frame_idx in exposure_frames: filename = tmp_filename_template.format(pos=layer_position, frame=frame_idx) dst_path = "/".join([output_dir, filename]) layer_files_by_frame[frame_idx] = os.path.normpath(dst_path) # Go to frame george_script_lines.append("tv_layerImage {}".format(frame_idx)) # Store image to output george_script_lines.append("tv_saveimage \"{}\"".format(dst_path)) self.log.debug("Rendering Exposure frames {} of layer {} ({})".format( str(exposure_frames), layer_id, layer["name"])) # Let TVPaint render layer's image lib.execute_george_through_file("\n".join(george_script_lines)) # Fill frames between `frame_start_index` and `frame_end_index` self.log.debug( ("Filling frames between first and last frame of layer ({} - {})." ).format(frame_start_index + 1, frame_end_index + 1)) _debug_filled_frames = [] prev_filepath = None for frame_idx in range(frame_start_index, frame_end_index + 1): if frame_idx in layer_files_by_frame: prev_filepath = layer_files_by_frame[frame_idx] continue if prev_filepath is None: raise ValueError("BUG: First frame of layer was not rendered!") _debug_filled_frames.append(frame_idx) filename = tmp_filename_template.format(pos=layer_position, frame=frame_idx) new_filepath = "/".join([output_dir, filename]) self._copy_image(prev_filepath, new_filepath) layer_files_by_frame[frame_idx] = new_filepath self.log.debug("Filled frames {}".format(str(_debug_filled_frames))) # Fill frames by pre/post behavior of layer pre_behavior = behavior["pre"] post_behavior = behavior["post"] self.log.debug( ("Completing image sequence of layer by pre/post behavior." " PRE: {} | POST: {}").format(pre_behavior, post_behavior)) # Pre behavior self._fill_frame_by_pre_behavior(layer, pre_behavior, mark_in_index, layer_files_by_frame, tmp_filename_template, output_dir) self._fill_frame_by_post_behavior(layer, post_behavior, mark_out_index, layer_files_by_frame, tmp_filename_template, output_dir) return layer_files_by_frame
def update(self, container, representation): """Replace container with different version. New layers are loaded as first step. Then is tried to change data in new layers with data from old layers. When that is done old layers are removed. """ # Create new containers first context = get_representation_context(representation) # Get layer ids from previous container old_layer_names = self.get_members_from_container(container) # Backwards compatibility (layer ids were stored instead of names) old_layers_are_ids = True for name in old_layer_names: if isinstance(name, int) or name.isnumeric(): continue old_layers_are_ids = False break old_layers = [] layers = lib.layers_data() previous_layer_ids = set(layer["layer_id"] for layer in layers) if old_layers_are_ids: for layer in layers: if layer["layer_id"] in old_layer_names: old_layers.append(layer) else: layers_by_name = collections.defaultdict(list) for layer in layers: layers_by_name[layer["name"]].append(layer) for layer_name in old_layer_names: layers = layers_by_name[layer_name] if len(layers) == 1: old_layers.append(layers[0]) # Prepare few data new_start_position = None new_group_id = None layer_ids_to_remove = set() for layer in old_layers: layer_ids_to_remove.add(layer["layer_id"]) position = layer["position"] group_id = layer["group_id"] if new_start_position is None: new_start_position = position elif new_start_position > position: new_start_position = position if new_group_id is None: new_group_id = group_id elif new_group_id < 0: continue elif new_group_id != group_id: new_group_id = -1 # Remove old container self._remove_container(container) # Remove old layers self._remove_layers(layer_ids=layer_ids_to_remove) # Change `fname` to new representation self.fname = self.filepath_from_context(context) name = container["name"] namespace = container["namespace"] new_container = self.load(context, name, namespace, {}) new_layer_names = self.get_members_from_container(new_container) layers = lib.layers_data() new_layers = [] for layer in layers: if layer["layer_id"] in previous_layer_ids: continue if layer["name"] in new_layer_names: new_layers.append(layer) george_script_lines = [] # Group new layers to same group as previous container layers had # - all old layers must be under same group if new_group_id is not None and new_group_id > 0: for layer in new_layers: line = "tv_layercolor \"set\" {} {}".format( layer["layer_id"], new_group_id) george_script_lines.append(line) # Rename new layer to have same name # - only if both old and new have one layer if len(old_layers) == 1 and len(new_layers) == 1: layer_name = old_layers[0]["name"] george_script_lines.append("tv_layerrename {} \"{}\"".format( new_layers[0]["layer_id"], layer_name)) # Change position of new layer # - this must be done before remove old layers if len(new_layers) == 1 and new_start_position is not None: new_layer = new_layers[0] george_script_lines.extend([ "tv_layerset {}".format(new_layer["layer_id"]), "tv_layermove {}".format(new_start_position) ]) # Execute george scripts if there are any if george_script_lines: george_script = "\n".join(george_script_lines) lib.execute_george_through_file(george_script)
def load(self, context, name, namespace, options): # Create temp file for output output_file = tempfile.NamedTemporaryFile(mode="w", prefix="pype_tvp_", suffix=".txt", delete=False) output_file.close() output_filepath = output_file.name.replace("\\", "/") # Prepare george script import_script = "\n".join(self.import_script_lines) george_script = import_script.format(self.fname.replace("\\", "/"), output_filepath) self.log.info("*** George script:\n{}\n***".format(george_script)) # Execute geoge script lib.execute_george_through_file(george_script) # Read output file lines = [] with open(output_filepath, "r") as file_stream: for line in file_stream: line = line.rstrip() if line: lines.append(line) # Clean up temp file os.remove(output_filepath) output = {} for line in lines: key, value = line.split("|") output[key] = value success = output.get("success") # Successfully loaded sound if success == "0": return if success == "": raise ValueError( "Your TVPaint version does not support loading of" " sound through George script. Please use manual load.") if success is None: raise ValueError("Unknown error happened during load." " Please report and try to use manual load.") # Possible errors by TVPaint documentation # https://www.tvpaint.com/doc/tvpaint-animation-11/george-commands#tv_soundclipnew if success == "-1": raise ValueError( "BUG: George command did not get enough arguments.") if success == "-2": # Who know what does that mean? raise ValueError("No current clip without mixer.") if success == "-3": raise ValueError("TVPaint couldn't read the file.") if success == "-4": raise ValueError("TVPaint couldn't add the track.") raise ValueError("BUG: Unknown success value {}.".format(success))