diff --git a/openpype/hosts/photoshop/api/ws_stub.py b/openpype/hosts/photoshop/api/ws_stub.py index fd8377d4e0..d4406d17b9 100644 --- a/openpype/hosts/photoshop/api/ws_stub.py +++ b/openpype/hosts/photoshop/api/ws_stub.py @@ -344,6 +344,28 @@ class PhotoshopServerStub: ) ) + def hide_all_others_layers(self, layers): + """hides all layers that are not part of the list or that are not + children of this list + + Args: + layers (list): list of PSItem - highest hierarchy + """ + extract_ids = set([ll.id for ll in self.get_layers_in_layers(layers)]) + + self.hide_all_others_layers_ids(extract_ids) + + def hide_all_others_layers_ids(self, extract_ids): + """hides all layers that are not part of the list or that are not + children of this list + + Args: + extract_ids (list): list of integer that should be visible + """ + for layer in self.get_layers(): + if layer.visible and layer.id not in extract_ids: + self.set_visible(layer.id, False) + def get_layers_metadata(self): """Reads layers metadata from Headline from active document in PS. (Headline accessible by File > File Info) diff --git a/openpype/hosts/photoshop/plugins/publish/extract_image.py b/openpype/hosts/photoshop/plugins/publish/extract_image.py index beb904215b..04ce77ee34 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_image.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_image.py @@ -26,7 +26,6 @@ class ExtractImage(openpype.api.Extractor): with photoshop.maintained_selection(): self.log.info("Extracting %s" % str(list(instance))) with photoshop.maintained_visibility(): - # Hide all other layers. layer = instance.data.get("layer") ids = set([layer.id]) add_ids = instance.data.pop("ids", None) @@ -34,11 +33,7 @@ class ExtractImage(openpype.api.Extractor): ids.update(set(add_ids)) extract_ids = set([ll.id for ll in stub. get_layers_in_layers_ids(ids)]) - - for layer in stub.get_layers(): - # limit unnecessary calls to client - if layer.visible and layer.id not in extract_ids: - stub.set_visible(layer.id, False) + stub.hide_all_others_layers_ids(extract_ids) file_basename = os.path.splitext( stub.get_active_document_name() diff --git a/openpype/hosts/photoshop/plugins/publish/extract_review.py b/openpype/hosts/photoshop/plugins/publish/extract_review.py index b6c7e2d189..b8f4470c7b 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_review.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_review.py @@ -1,4 +1,5 @@ import os +import shutil import openpype.api import openpype.lib @@ -7,7 +8,7 @@ from openpype.hosts.photoshop import api as photoshop class ExtractReview(openpype.api.Extractor): """ - Produce a flattened image file from all 'image' instances. + Produce a flattened or sequence image file from all 'image' instances. If no 'image' instance is created, it produces flattened image from all visible layers. @@ -20,54 +21,58 @@ class ExtractReview(openpype.api.Extractor): # Extract Options jpg_options = None mov_options = None + make_image_sequence = None def process(self, instance): staging_dir = self.staging_dir(instance) self.log.info("Outputting image to {}".format(staging_dir)) + fps = instance.data.get("fps", 25) stub = photoshop.stub() + self.output_seq_filename = os.path.splitext( + stub.get_active_document_name())[0] + ".%04d.jpg" - layers = [] - for image_instance in instance.context: - if image_instance.data["family"] != "image": - continue - layers.append(image_instance.data.get("layer")) + layers = self._get_layers_from_image_instances(instance) + self.log.info("Layers image instance found: {}".format(layers)) - # Perform extraction - output_image = "{}.jpg".format( - os.path.splitext(stub.get_active_document_name())[0] - ) - output_image_path = os.path.join(staging_dir, output_image) - with photoshop.maintained_visibility(): - if layers: - # Hide all other layers. - extract_ids = set([ll.id for ll in stub. - get_layers_in_layers(layers)]) - self.log.debug("extract_ids {}".format(extract_ids)) - for layer in stub.get_layers(): - # limit unnecessary calls to client - if layer.visible and layer.id not in extract_ids: - stub.set_visible(layer.id, False) + if self.make_image_sequence and len(layers) > 1: + self.log.info("Extract layers to image sequence.") + img_list = self._saves_sequences_layers(staging_dir, layers) - stub.saveAs(output_image_path, 'jpg', True) + instance.data["representations"].append({ + "name": "jpg", + "ext": "jpg", + "files": img_list, + "frameStart": 0, + "frameEnd": len(img_list), + "fps": fps, + "stagingDir": staging_dir, + "tags": self.jpg_options['tags'], + }) + + else: + self.log.info("Extract layers to flatten image.") + img_list = self._saves_flattened_layers(staging_dir, layers) + + instance.data["representations"].append({ + "name": "jpg", + "ext": "jpg", + "files": img_list, + "stagingDir": staging_dir, + "tags": self.jpg_options['tags'] + }) ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") - instance.data["representations"].append({ - "name": "jpg", - "ext": "jpg", - "files": output_image, - "stagingDir": staging_dir, - "tags": self.jpg_options['tags'] - }) instance.data["stagingDir"] = staging_dir # Generate thumbnail. thumbnail_path = os.path.join(staging_dir, "thumbnail.jpg") + self.log.info(f"Generate thumbnail {thumbnail_path}") args = [ ffmpeg_path, "-y", - "-i", output_image_path, + "-i", os.path.join(staging_dir, self.output_seq_filename), "-vf", "scale=300:-1", "-vframes", "1", thumbnail_path @@ -81,14 +86,17 @@ class ExtractReview(openpype.api.Extractor): "stagingDir": staging_dir, "tags": ["thumbnail"] }) + # Generate mov. mov_path = os.path.join(staging_dir, "review.mov") + self.log.info(f"Generate mov review: {mov_path}") + img_number = len(img_list) args = [ ffmpeg_path, "-y", - "-i", output_image_path, + "-i", os.path.join(staging_dir, self.output_seq_filename), "-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2", - "-vframes", "1", + "-vframes", str(img_number), mov_path ] output = openpype.lib.run_subprocess(args) @@ -99,15 +107,86 @@ class ExtractReview(openpype.api.Extractor): "files": os.path.basename(mov_path), "stagingDir": staging_dir, "frameStart": 1, - "frameEnd": 1, - "fps": 25, + "frameEnd": img_number, + "fps": fps, "preview": True, "tags": self.mov_options['tags'] }) # Required for extract_review plugin (L222 onwards). instance.data["frameStart"] = 1 - instance.data["frameEnd"] = 1 + instance.data["frameEnd"] = img_number instance.data["fps"] = 25 self.log.info(f"Extracted {instance} to {staging_dir}") + + def _get_image_path_from_instances(self, instance): + img_list = [] + + for instance in sorted(instance.context): + if instance.data["family"] != "image": + continue + + for rep in instance.data["representations"]: + img_path = os.path.join( + rep["stagingDir"], + rep["files"] + ) + img_list.append(img_path) + + return img_list + + def _copy_image_to_staging_dir(self, staging_dir, img_list): + copy_files = [] + for i, img_src in enumerate(img_list): + img_filename = self.output_seq_filename % i + img_dst = os.path.join(staging_dir, img_filename) + + self.log.debug( + "Copying file .. {} -> {}".format(img_src, img_dst) + ) + shutil.copy(img_src, img_dst) + copy_files.append(img_filename) + + return copy_files + + def _get_layers_from_image_instances(self, instance): + layers = [] + for image_instance in instance.context: + if image_instance.data["family"] != "image": + continue + layers.append(image_instance.data.get("layer")) + + return sorted(layers) + + def _saves_flattened_layers(self, staging_dir, layers): + img_filename = self.output_seq_filename % 0 + output_image_path = os.path.join(staging_dir, img_filename) + stub = photoshop.stub() + + with photoshop.maintained_visibility(): + self.log.info("Extracting {}".format(layers)) + if layers: + stub.hide_all_others_layers(layers) + + stub.saveAs(output_image_path, 'jpg', True) + + return img_filename + + def _saves_sequences_layers(self, staging_dir, layers): + stub = photoshop.stub() + + list_img_filename = [] + with photoshop.maintained_visibility(): + for i, layer in enumerate(layers): + self.log.info("Extracting {}".format(layer)) + + img_filename = self.output_seq_filename % i + output_image_path = os.path.join(staging_dir, img_filename) + list_img_filename.append(img_filename) + + with photoshop.maintained_visibility(): + stub.hide_all_others_layers([layer]) + stub.saveAs(output_image_path, 'jpg', True) + + return list_img_filename diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index f095986ee6..118b9c721e 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -28,6 +28,7 @@ ] }, "ExtractReview": { + "make_image_sequence": false, "jpg_options": { "tags": [] }, @@ -43,4 +44,4 @@ "create_first_version": false, "custom_templates": [] } -} \ No newline at end of file +} diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index f54aa847b5..b499ccc4be 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -164,6 +164,11 @@ "key": "ExtractReview", "label": "Extract Review", "children": [ + { + "type": "boolean", + "key": "make_image_sequence", + "label": "Makes an image sequence instead of a flatten image" + }, { "type": "dict", "collapsible": false,