Skip to content

load_aftereffects_comp

AECompLoader

Bases: PremiereLoader

Load AfterEffects composition(s) into Premiere as Bins.

Wraps loaded items into Bins for management and stores metadata in a dedicated "AYON Metadata" Bin using Clip.Description field.

Source code in client/ayon_premiere/plugins/load/load_aftereffects_comp.py
 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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
class AECompLoader(api.PremiereLoader):
    """Load AfterEffects composition(s) into Premiere as Bins.

    Wraps loaded items into Bins for management and stores metadata in a
    dedicated "AYON Metadata" Bin using Clip.Description field.
    """
    label = "Load AfterEffects Compositions"
    icon = "image"
    product_types = {"workfile"}
    representations = {"aep"}

    def load(
        self,
        context: Dict[str, Any],
        name: str = None,
        namespace: str = None,
        options: Dict[str, Any] = None
    ) -> None:
        """Main loading method for After Effects compositions."""
        stub = self.get_stub()
        options = options or {}
        repr_entity = context["representation"]
        repre_id = repr_entity["id"]

        # Validate path
        path = self.filepath_from_context(context).replace("\\", "/")
        if not os.path.exists(path):
            raise LoadError(
                f"Invalid path for representation {repre_id}: {path}")

        # Get selected compositions
        selected_compositions = options.get(
            "compositions") or self._get_default_compositions(context)
        if not selected_compositions:
            raise LoadError("No compositions selected for loading")

        # Process each selected composition
        for composition in selected_compositions:
            self._load_single_composition(
                context=context,
                composition=composition,
                stub=stub,
                path=path,
            )

    def update(
        self,
        container: Dict[str, Any],
        context: Dict[str, Any]
    ) -> None:
        """Update container with new version or asset."""
        stub = self.get_stub()
        stored_bin = container.pop("bin")
        old_metadata = stub.get_item_metadata(stored_bin)

        # Get context data
        folder_name = context["folder"]["name"]
        product_name = context["product"]["name"]
        repr_entity = context["representation"]
        repr_data = repr_entity["data"]

        # Handle composition to update
        composition = self._get_composition_to_update(container, repr_data)
        new_bin_name = self._get_updated_bin_name(
            container, context, product_name, stub, composition
        )

        # Validate composition exists in new version
        if composition not in repr_data.get("composition_names_in_workfile",
                                            []):
            raise LoadError(
                f"Composition '{composition}' not found in workfile")

        # Perform update
        path = self.filepath_from_context(context).replace("\\", "/")
        new_bin = stub.replace_ae_comp(
            stored_bin.id,
            path,
            new_bin_name,
            [composition]
        )

        # Update metadata
        updated_metadata = {
            **old_metadata,
            "members": [new_bin.id],
            "representation": repr_entity["id"],
            "name": new_bin_name,
            "namespace": f"{folder_name}_{product_name}"
        }
        stub.imprint(new_bin.id, updated_metadata)

    def remove(self, container: Dict[str, Any]) -> None:
        """Remove container from Premiere project."""
        stub = self.get_stub()
        stored_bin = container.pop("bin")
        stub.imprint(stored_bin.id, {})
        stub.delete_item(stored_bin.id)

    def switch(
        self,
        container: Dict[str, Any],
        context: Dict[str, Any]
    ) -> None:
        """Allows switching folder or product"""
        self.update(container, context)

    @classmethod
    def get_options(cls, contexts: List[Dict[str, Any]]) -> List[EnumDef]:
        """Get composition selection options."""
        repr_entity = contexts[0]["representation"]
        compositions = repr_entity["data"].get("composition_names_in_workfile",
                                               [])

        return [
            EnumDef(
                "compositions",
                label="Available compositions",
                items={comp: comp for comp in compositions},
                default=[compositions[0]] if compositions else [],
                multiselection=True
            )
        ]

    def _load_single_composition(
        self,
        context: Dict[str, Any],
        composition: str,
        stub: Any,
        path: str,
    ) -> None:
        """Handle loading of a single composition."""
        # Generate unique bin name
        new_bin_name = self._generate_bin_name(context, stub, composition)

        # Import composition
        import_element = stub.import_ae_comp(path, new_bin_name, [composition])
        repre_id = context["representation"]["id"]
        if not import_element:
            raise LoadError(
                f"Failed to load composition '{composition}' "
                f"(representation {repre_id}). "
                "Check host app for error details."
            )

        # Create container
        product_name = context["product"]["name"]
        folder_name = context["folder"]["name"]
        namespace = f"{folder_name}_{product_name}"
        api.containerise(
            new_bin_name,
            namespace,
            import_element,
            context,
            self.__class__.__name__,
            composition
        )

    def _generate_bin_name(
        self,
        context: Dict[str, Any],
        stub: Any,
        composition: str
    ) -> str:
        """Generate unique bin name for composition."""
        existing_bins = stub.get_items(bins=True, sequences=False,
                                       footages=False)
        existing_names = [bin_info.name for bin_info in existing_bins]
        folder_name = context["folder"]["name"]
        product_name = context["product"]["name"]
        return get_unique_bin_name(
            existing_names,
            f"{stub.LOADED_ICON}{folder_name}_{product_name}_{composition}"
        )

    def _get_updated_bin_name(
        self,
        container: Dict[str, Any],
        context: Dict[str, Any],
        product_name: str,
        stub: Any,
        composition: str
    ) -> str:
        """Determine appropriate bin name for updated container.

        Returns existing name if namespace matches, generates new unique name if not.
        """
        folder_name = context["folder"]["name"]
        new_namespace = f"{folder_name}_{product_name}"

        if container["namespace"] != new_namespace:
            # Asset switch - need new unique name
            return self._generate_bin_name(context, stub, composition)

        # Version update - keep existing name
        return container["name"]

    def _get_default_compositions(self, context: Dict[str, Any]) -> List[str]:
        """Get default compositions from representation data."""
        return context["representation"]["data"].get(
            "composition_names_in_workfile", [])

    def _get_composition_to_update(
        self,
        container: Dict[str, Any],
        repr_data: Dict[str, Any]
    ) -> str:
        """Get composition name for update operation."""
        if composition := container.get("imported_composition"):
            return composition

        # Backward compatibility for older containers
        if comp_names := repr_data.get("composition_names_in_workfile"):
            return comp_names[0]

        raise LoadError("No composition found for update")

get_options(contexts) classmethod

Get composition selection options.

Source code in client/ayon_premiere/plugins/load/load_aftereffects_comp.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
@classmethod
def get_options(cls, contexts: List[Dict[str, Any]]) -> List[EnumDef]:
    """Get composition selection options."""
    repr_entity = contexts[0]["representation"]
    compositions = repr_entity["data"].get("composition_names_in_workfile",
                                           [])

    return [
        EnumDef(
            "compositions",
            label="Available compositions",
            items={comp: comp for comp in compositions},
            default=[compositions[0]] if compositions else [],
            multiselection=True
        )
    ]

load(context, name=None, namespace=None, options=None)

Main loading method for After Effects compositions.

Source code in client/ayon_premiere/plugins/load/load_aftereffects_comp.py
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
def load(
    self,
    context: Dict[str, Any],
    name: str = None,
    namespace: str = None,
    options: Dict[str, Any] = None
) -> None:
    """Main loading method for After Effects compositions."""
    stub = self.get_stub()
    options = options or {}
    repr_entity = context["representation"]
    repre_id = repr_entity["id"]

    # Validate path
    path = self.filepath_from_context(context).replace("\\", "/")
    if not os.path.exists(path):
        raise LoadError(
            f"Invalid path for representation {repre_id}: {path}")

    # Get selected compositions
    selected_compositions = options.get(
        "compositions") or self._get_default_compositions(context)
    if not selected_compositions:
        raise LoadError("No compositions selected for loading")

    # Process each selected composition
    for composition in selected_compositions:
        self._load_single_composition(
            context=context,
            composition=composition,
            stub=stub,
            path=path,
        )

remove(container)

Remove container from Premiere project.

Source code in client/ayon_premiere/plugins/load/load_aftereffects_comp.py
102
103
104
105
106
107
def remove(self, container: Dict[str, Any]) -> None:
    """Remove container from Premiere project."""
    stub = self.get_stub()
    stored_bin = container.pop("bin")
    stub.imprint(stored_bin.id, {})
    stub.delete_item(stored_bin.id)

switch(container, context)

Allows switching folder or product

Source code in client/ayon_premiere/plugins/load/load_aftereffects_comp.py
109
110
111
112
113
114
115
def switch(
    self,
    container: Dict[str, Any],
    context: Dict[str, Any]
) -> None:
    """Allows switching folder or product"""
    self.update(container, context)

update(container, context)

Update container with new version or asset.

Source code in client/ayon_premiere/plugins/load/load_aftereffects_comp.py
 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
def update(
    self,
    container: Dict[str, Any],
    context: Dict[str, Any]
) -> None:
    """Update container with new version or asset."""
    stub = self.get_stub()
    stored_bin = container.pop("bin")
    old_metadata = stub.get_item_metadata(stored_bin)

    # Get context data
    folder_name = context["folder"]["name"]
    product_name = context["product"]["name"]
    repr_entity = context["representation"]
    repr_data = repr_entity["data"]

    # Handle composition to update
    composition = self._get_composition_to_update(container, repr_data)
    new_bin_name = self._get_updated_bin_name(
        container, context, product_name, stub, composition
    )

    # Validate composition exists in new version
    if composition not in repr_data.get("composition_names_in_workfile",
                                        []):
        raise LoadError(
            f"Composition '{composition}' not found in workfile")

    # Perform update
    path = self.filepath_from_context(context).replace("\\", "/")
    new_bin = stub.replace_ae_comp(
        stored_bin.id,
        path,
        new_bin_name,
        [composition]
    )

    # Update metadata
    updated_metadata = {
        **old_metadata,
        "members": [new_bin.id],
        "representation": repr_entity["id"],
        "name": new_bin_name,
        "namespace": f"{folder_name}_{product_name}"
    }
    stub.imprint(new_bin.id, updated_metadata)