Skip to content

extract_sources_review

ExtractSourcesReview

Bases: Extractor

Produce a flattened or sequence image files from all 'image' instances.

These files are then used by global ExtractReview and ExtractThumbnail to create reviews with globally controllable configuration.

If no 'image' instance is created, it produces flattened image from all visible layers.

It can also create separate reviews per image instance if necessary. (toggle on an instance in Publisher UI).

'review' family could be used in other steps as a reference, as it contains flattened image by default. (Eg. artist could load this review as a single item and see full image. In most cases 'image' product type is separated by layers to better usage in animation or comp.)

Source code in client/ayon_photoshop/plugins/publish/extract_sources_review.py
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
class ExtractSourcesReview(publish.Extractor):
    """
        Produce a flattened or sequence image files from all 'image' instances.

        These files are then used by global `ExtractReview` and
        `ExtractThumbnail` to create reviews with globally controllable
        configuration.

        If no 'image' instance is created, it produces flattened image from
        all visible layers.

        It can also create separate reviews per `image` instance if necessary.
        (toggle on an instance in Publisher UI).

        'review' family could be used in other steps as a reference, as it
        contains flattened image by default. (Eg. artist could load this
        review as a single item and see full image. In most cases 'image'
        product type is separated by layers to better usage in animation
        or comp.)
    """

    label = "Extract Sources for Review"
    hosts = ["photoshop"]
    families = ["review"]
    settings_category = "photoshop"
    order = publish.Extractor.order - 0.28

    # Extract Options
    make_image_sequence = None

    def process(self, instance):
        staging_dir = self.staging_dir(instance)
        self.log.info("Outputting image to {}".format(staging_dir))

        stub = photoshop.stub()
        self.output_seq_filename = os.path.splitext(
            stub.get_active_document_name())[0] + ".%04d.jpg"

        layers = self._get_layers_from_image_instances(instance)
        self.log.info("Layers image instance found: {}".format(layers))

        additional_repre = {
            "name": "jpg",
            "ext": "jpg",
            "frameStart": instance.data["frameStart"],
            "frameEnd": instance.data["frameEnd"],
            "fps": instance.data["fps"],
            "stagingDir": staging_dir,
            "tags": ["review"],
        }

        if instance.data["productType"] == "image":
            self._attach_review_tag(instance)
        elif self.make_image_sequence and len(layers) > 1:
            self.log.debug("Extract layers to image sequence.")
            img_list = self._save_sequence_images(staging_dir, layers)

            instance.data["frameEnd"] = instance.data["frameStart"] + len(img_list) - 1
            additional_repre["output_name"] = "mov"
            additional_repre["files"] = img_list
            instance.data["representations"].append(additional_repre)

        else:
            self.log.debug("Extract layers to flatten image.")
            review_source_path = self._save_flatten_image(
                staging_dir,
                layers
            )
            additional_repre["files"] = os.path.basename(review_source_path)
            additional_repre["output_name"] = "jpg"
            # just intermediate repre to create a review from
            additional_repre["tags"].append("delete")
            instance.data["representations"].append(additional_repre)

        instance.data["stagingDir"] = staging_dir

        self.log.info(f"Extracted {instance} to {staging_dir}")

    def _attach_review_tag(self, instance):
        """Searches for repre for which jpg review should be created.

        "jpg" representation is preferred.

        """
        jpg_source_repre = None
        for repre in instance.data["representations"]:
            if repre["name"] == "jpg":
                jpg_source_repre = repre
                repre["tags"].append("review")
                break

        if not jpg_source_repre:
            repre = instance.data["representations"][0]
            repre["tags"].append("review")

    def _get_layers_from_image_instances(self, instance):
        """Collect all layers from image instance(s)

        If `instance` is `image` it returns just layers out of it to create
        separate review per instance.

        If `instance` is (most likely) `review`, it collects all layers from
        published instances to create one review from all of them.

        Returns:
            (list) of PSItem
        """
        layers = []
        # creating review for existing 'image' instance
        if (
            instance.data["productType"] == "image"
            and instance.data.get("layer")
        ):
            layers.append(instance.data["layer"])
            return layers

        # collect all layers from published image instances
        for image_instance in instance.context:
            if image_instance.data["productType"] != "image":
                continue
            layer =  image_instance.data.get("layer")
            if layer:
                layers.append(layer)

        return sorted(layers)

    def _save_flatten_image(self, staging_dir, layers):
        """Creates flat image from 'layers' into 'staging_dir'.

        Returns:
            (str): path to new image
        """
        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 output_image_path

    def _save_sequence_images(self, staging_dir, layers):
        """Creates separate images from 'layers' into 'staging_dir'.

        `layers` are actually groups matching instances.

        Used as source for multi frames .mov to review at once.
        Returns:
            (list): paths to new images
        """
        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