after-effects 0.4.0

High level bindings for the Adobe After Effects® SDK
1
2
3
4
5
6
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
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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
use crate::*;
use crate::aegp::*;
use ae_sys::AEGP_ItemH;

define_suite!(
    /// Accesses and modifies items within a project or composition.
    ///
    /// Anything in the project bin is an `AEGP_Item`. Note that cameras have no source, and thus have no [`ItemHandle`].
    ///
    /// Unless more specificity is required for the function(s) you're using, remain as abstract as possible; AEGP_Comps are passed into and returned from most functions as AEGP_Items.
    ItemSuite,
    AEGP_ItemSuite9,
    kAEGPItemSuite,
    kAEGPItemSuiteVersion9
);

impl ItemSuite {
    /// Acquire this suite from the host. Returns error if the suite is not available.
    /// Suite is released on drop.
    pub fn new() -> Result<Self, Error> {
        crate::Suite::new()
    }

    /// Retrieves the first item in a given project.
    pub fn first_proj_item(&self, project_handle: &ProjectHandle) -> Result<ItemHandle, Error> {
        Ok(ItemHandle::from_raw(
            call_suite_fn_single!(self, AEGP_GetFirstProjItem -> ae_sys::AEGP_ItemH, project_handle.as_ptr())?
        ))
    }

    /// Retrieves the next project item; Result will be `None` after the last item.
    pub fn next_proj_item(&self, project_handle: &ProjectHandle, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<Option<ItemHandle>, Error> {
        let next_item = call_suite_fn_single!(self, AEGP_GetNextProjItem -> ae_sys::AEGP_ItemH, project_handle.as_ptr(), item_handle.as_ptr())?;
        if next_item.is_null() {
            Ok(None)
        } else {
            Ok(Some(ItemHandle::from_raw(next_item)))
        }
    }

    /// If the Project window is active, the active item is the selected item (if only one item is selected).
    /// If a Composition, Timeline, or Footage window is active, returns the parent of the layer associated with the front-most tab in the window.
    ///
    /// Returns `None` if no item is active.
    pub fn active_item(&self) -> Result<Option<ItemHandle>, Error> {
        let item_handle = call_suite_fn_single!(self, AEGP_GetActiveItem -> ae_sys::AEGP_ItemH)?;
        if item_handle.is_null() {
            Ok(None)
        } else {
            Ok(Some(ItemHandle::from_raw(item_handle)))
        }
    }

    /// Returns true if the Project window is active and the item is selected.
    pub fn is_item_selected(&self, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<bool, Error> {
        Ok(call_suite_fn_single!(self, AEGP_IsItemSelected -> ae_sys::A_Boolean, item_handle.as_ptr())? != 0)
    }

    /// Toggles the selection state of the item, and (depending on `deselect_others`) can deselect other items.
    /// This call selects items in the Project panel.
    ///
    /// To make selections in the Composition panel, use [`suites::Comp:set_selection()`](aegp::suites::Comp::set_selection).
    pub fn select_item(&self, item_handle: impl AsPtr<AEGP_ItemH>, select: bool, deselect_others: bool) -> Result<(), Error> {
        call_suite_fn!(self, AEGP_SelectItem, item_handle.as_ptr(), select.into(), deselect_others.into())
    }

    /// Gets type of an item. Note: solids don't appear in the project, but can be the source to a layer.
    pub fn item_type(&self, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<ItemType, Error> {
        Ok(call_suite_fn_single!(self, AEGP_GetItemType -> ae_sys::AEGP_ItemType, item_handle.as_ptr())?.into())
    }

    /// Get name of type. (name length up to `32`).
    pub fn type_name(&self, item_type: ItemType) -> Result<String, Error> {
        let mut buffer = [0i8; ae_sys::AEGP_MAX_TYPE_NAME_SIZE as _];
        call_suite_fn!(self, AEGP_GetTypeName, item_type.into(), buffer.as_mut_ptr() as *mut _)?;
        Ok(unsafe { std::ffi::CStr::from_ptr(buffer.as_ptr()) }.to_string_lossy().into_owned())
    }

    /// Get item name.
    pub fn item_name(&self, item_handle: impl AsPtr<AEGP_ItemH>, plugin_id: PluginId) -> Result<String, Error> {
        let mem_handle = call_suite_fn_single!(self, AEGP_GetItemName -> ae_sys::AEGP_MemHandle, plugin_id, item_handle.as_ptr())?;
        // Create a mem handle each and lock it.
        // When the lock goes out of scope it unlocks and when the handle goes out of scope it gives the memory back to Ae.
        Ok(unsafe {
            U16CString::from_ptr_str(
                MemHandle::<u16>::from_raw(mem_handle)?.lock()?.as_ptr(),
            ).to_string_lossy()
        })
    }

    /// Specifies the name of the [`ItemHandle`].
    pub fn set_item_name(&self, item_handle: impl AsPtr<AEGP_ItemH>, name: &str) -> Result<(), Error> {
        let name = U16CString::from_str(name).map_err(|_| Error::InvalidParms)?;
        call_suite_fn!(self, AEGP_SetItemName, item_handle.as_ptr(), name.as_ptr())
    }

    /// Returns the item's unique ID, which persists across saves and loads of the project.
    pub fn item_id(&self, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<ItemId, Error> {
        call_suite_fn_single!(self, AEGP_GetItemID -> ItemId, item_handle.as_ptr())
    }

    /// Get properties of an item.
    ///
    /// Unlike the [`ItemFlags::HAS_AUDIO`] flag, this bit flag will set only if the comp has at least one layer where audio is actually on.
    pub fn item_flags(&self, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<ItemFlags, Error> {
        Ok(ItemFlags::from_bits_truncate(
            call_suite_fn_single!(self, AEGP_GetItemFlags -> ae_sys::A_long, item_handle.as_ptr())? as _
        ))
    }

    /// Toggle item's proxy usage. Undoable.
    pub fn set_item_use_proxy(&self, item_handle: impl AsPtr<AEGP_ItemH>, use_proxy: bool) -> Result<(), Error> {
        call_suite_fn!(self, AEGP_SetItemUseProxy, item_handle.as_ptr(), use_proxy.into())
    }

    /// Get folder containing item.
    pub fn item_parent_folder(&self, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<Option<ItemHandle>, Error> {
        let parent_folder = call_suite_fn_single!(self, AEGP_GetItemParentFolder -> ae_sys::AEGP_ItemH, item_handle.as_ptr())?;
        if parent_folder.is_null() {
            Ok(None)
        } else {
            Ok(Some(ItemHandle::from_raw(parent_folder)))
        }
    }

    /// Sets an item's parent folder. Undoable.
    pub fn set_item_parent_folder(&self, item_handle: impl AsPtr<AEGP_ItemH>, parent_folder: &ItemHandle) -> Result<(), Error> {
        call_suite_fn!(self, AEGP_SetItemParentFolder, item_handle.as_ptr(), parent_folder.as_ptr())
    }

    /// Get duration of item, in seconds.
    pub fn item_duration(&self, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<Time, Error> {
        Ok(call_suite_fn_single!(self, AEGP_GetItemDuration -> ae_sys::A_Time, item_handle.as_ptr())?.into())
    }

    /// Get current time within item. Not updated while rendering.
    pub fn item_current_time(&self, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<Time, Error> {
        Ok(call_suite_fn_single!(self, AEGP_GetItemCurrentTime -> ae_sys::A_Time, item_handle.as_ptr())?.into())
    }

    /// Get width and height of item.
    pub fn item_dimensions(&self, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<(u32, u32), Error> {
        let (width, height) = call_suite_fn_double!(self, AEGP_GetItemDimensions -> ae_sys::A_long, ae_sys:: A_long, item_handle.as_ptr())?;
        Ok((
            width as _,
            height as _
        ))
    }

    /// Get the width of a pixel, assuming its height is 1.0, as numerator over denominator.
    pub fn item_pixel_aspect_ratio(&self, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<Ratio, Error> {
        Ok(call_suite_fn_single!(self, AEGP_GetItemPixelAspectRatio -> ae_sys::A_Ratio, item_handle.as_ptr())?.into())
    }

    /// Removes item from all compositions. Undo-able.
    /// Do not use the [`ItemHandle`] after calling this function.
    pub fn delete_item(&self, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<(), Error> {
        call_suite_fn!(self, AEGP_DeleteItem, item_handle.as_ptr())
    }

    /// Creates a new folder in the project. The newly created folder is allocated and owned by After Effects.
    ///
    /// Passing `None` for `parent_folder` creates the folder at the project's root.
    pub fn create_new_folder(&self, name: &str, parent_folder: Option<ItemHandle>) -> Result<ItemHandle, Error> {
        let name = U16CString::from_str(name).map_err(|_| Error::InvalidParms)?;
        Ok(ItemHandle::from_raw(
            call_suite_fn_single!(self,
                AEGP_CreateNewFolder -> ae_sys::AEGP_ItemH,
                name.as_ptr(),
                parent_folder.as_ref().map_or(std::ptr::null_mut(), |f| f.as_ptr())
            )?
        ))
    }

    /// Sets the current time within a given [`ItemHandle`].
    pub fn set_item_current_time(&self, item_handle: impl AsPtr<AEGP_ItemH>, new_time: Time) -> Result<(), Error> {
        call_suite_fn!(self, AEGP_SetItemCurrentTime, item_handle.as_ptr(), &new_time.into())
    }

    /// Retrieves the [`ItemHandle`]'s comment.
    pub fn item_comment(&self, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<String, Error> {
        let mem_handle = call_suite_fn_single!(self, AEGP_GetItemComment -> ae_sys::AEGP_MemHandle, item_handle.as_ptr())?;
        Ok(unsafe {
            U16CString::from_ptr_str(
                MemHandle::<u16>::from_raw(mem_handle)?.lock()?.as_ptr(),
            ).to_string_lossy()
        })
    }

    /// Sets the [`ItemHandle`]'s comment.
    pub fn set_item_comment(&self, item_handle: impl AsPtr<AEGP_ItemH>, comment: &str) -> Result<(), Error> {
        let comment = U16CString::from_str(comment).map_err(|_| Error::InvalidParms)?;
        call_suite_fn!(self, AEGP_SetItemComment, item_handle.as_ptr(), comment.as_ptr())
    }

    /// Retrieves an item's label.
    pub fn item_label(&self, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<LabelId, Error> {
        Ok(call_suite_fn_single!(self, AEGP_GetItemLabel -> ae_sys::AEGP_LabelID, item_handle.as_ptr())?.into())
    }

    /// Sets an item's label.
    pub fn set_item_label(&self, item_handle: impl AsPtr<AEGP_ItemH>, label: LabelId) -> Result<(), Error> {
        call_suite_fn!(self, AEGP_SetItemLabel, item_handle.as_ptr(), label.into())
    }

    /// Gets an item's most recently used view.
    ///
    /// The view can be used with two calls in the [`suites::ColorSettings`](aegp::suites::ColorSettings), to perform a color transform on a pixel buffer from working to view color space.
    pub fn item_mru_view(&self, item_handle: impl AsPtr<AEGP_ItemH>) -> Result<ae_sys::AEGP_ItemViewP, Error> {
        Ok(call_suite_fn_single!(self, AEGP_GetItemMRUView -> ae_sys::AEGP_ItemViewP, item_handle.as_ptr())?.into())
    }
}

// ――――――――――――――――――――――――――――――――――――――― Types ――――――――――――――――――――――――――――――――――――――――
register_handle!(AEGP_ItemH);
define_handle_wrapper!(ItemHandle, AEGP_ItemH);

define_enum! {
    ae_sys::AEGP_ItemType,
    ItemType {
        None    = ae_sys::AEGP_ItemType_NONE,
        Folder  = ae_sys::AEGP_ItemType_FOLDER,
        Comp    = ae_sys::AEGP_ItemType_COMP,
        /// as of AE6, solids are now just [`ItemType::Footage`] with `AEGP_FootageSignature_SOLID`
        Solid   = ae_sys::AEGP_ItemType_SOLID_defunct,
        Footage = ae_sys::AEGP_ItemType_FOOTAGE,
    }
}

define_enum! {
    ae_sys::AEGP_LabelID,
    LabelId {
        None = ae_sys::AEGP_Label_NONE,
        NoLabel = ae_sys::AEGP_Label_NO_LABEL,
        Label1 = ae_sys::AEGP_Label_1,
        Label2 = ae_sys::AEGP_Label_2,
        Label3 = ae_sys::AEGP_Label_3,
        Label4 = ae_sys::AEGP_Label_4,
        Label5 = ae_sys::AEGP_Label_5,
        Label6 = ae_sys::AEGP_Label_6,
        Label7 = ae_sys::AEGP_Label_7,
        Label8 = ae_sys::AEGP_Label_8,
        Label9 = ae_sys::AEGP_Label_9,
        Label10 = ae_sys::AEGP_Label_10,
        Label11 = ae_sys::AEGP_Label_11,
        Label12 = ae_sys::AEGP_Label_12,
        Label13 = ae_sys::AEGP_Label_13,
        Label14 = ae_sys::AEGP_Label_14,
        Label15 = ae_sys::AEGP_Label_15,
        Label16 = ae_sys::AEGP_Label_16,
    }
}

bitflags::bitflags! {
    pub struct ItemFlags: ae_sys::A_long {
        const MISSING           = ae_sys::AEGP_ItemFlag_MISSING          as ae_sys::A_long;
        const HAS_PROXY         = ae_sys::AEGP_ItemFlag_HAS_PROXY        as ae_sys::A_long;
        const USING_PROXY       = ae_sys::AEGP_ItemFlag_USING_PROXY      as ae_sys::A_long;
        const MISSING_PROXY     = ae_sys::AEGP_ItemFlag_MISSING_PROXY    as ae_sys::A_long;
        const HAS_VIDEO         = ae_sys::AEGP_ItemFlag_HAS_VIDEO        as ae_sys::A_long;
        const HAS_AUDIO         = ae_sys::AEGP_ItemFlag_HAS_AUDIO        as ae_sys::A_long;
        const STILL             = ae_sys::AEGP_ItemFlag_STILL            as ae_sys::A_long;
        const HAS_ACTIVE_AUDIO  = ae_sys::AEGP_ItemFlag_HAS_ACTIVE_AUDIO as ae_sys::A_long;
    }
}

define_suite_item_wrapper!(
    ae_sys::AEGP_ItemH, ItemHandle,
    suite: ItemSuite,
    footage: aegp::suites::Footage,
    comp: aegp::suites::Comp,
    /// Item can be a folder, a composition, or a footage
    Item {
        dispose: ;

        /// Returns true if the Project window is active and the item is selected.
        is_selected() -> bool => suite.is_item_selected,

        /// Toggles the selection state of the item, and (depending on `deselect_others`) can deselect other items.
        /// This call selects items in the Project panel.
        ///
        /// To make selections in the Composition panel, use [`suites::Comp:set_selection()`](aegp::suites::Comp::set_selection).
        select(select: bool, deselect_others: bool) -> () => suite.select_item,

        /// Gets type of an item. Note: solids don't appear in the project, but can be the source to a layer.
        item_type() -> ItemType => suite.item_type,

        /// Get item name.
        name(plugin_id: PluginId) -> String => suite.item_name,

        /// Specifies the name of this item.
        set_name(name: &str) -> () => suite.set_item_name,

        /// Returns the item's unique ID, which persists across saves and loads of the project.
        id() -> ItemId => suite.item_id,

        /// Get properties of an item.
        ///
        /// Unlike the [`ItemFlags::HAS_AUDIO`] flag, this bit flag will set only if the comp has at least one layer where audio is actually on.
        flags() -> ItemFlags => suite.item_flags,

        /// Toggle item's proxy usage. Undoable.
        set_use_proxy(use_proxy: bool) -> () => suite.set_item_use_proxy,

        /// Get folder containing item.
        parent_folder() -> Option<ItemHandle> => suite.item_parent_folder,

        /// Sets an item's parent folder. Undoable.
        set_parent_folder(parent_folder: &ItemHandle) -> () => suite.set_item_parent_folder,

        /// Get duration of item, in seconds.
        duration() -> Time => suite.item_duration,

        /// Get current time within item. Not updated while rendering.
        current_time() -> Time => suite.item_current_time,

        /// Get width and height of item.
        dimensions() -> (u32, u32) => suite.item_dimensions,

        /// Get the width of a pixel, assuming its height is 1.0, as numerator over denominator.
        pixel_aspect_ratio() -> Ratio => suite.item_pixel_aspect_ratio,

        /// Removes item from all compositions. Undo-able.
        /// Do not use the [`ItemHandle`] after calling this function.
        delete() -> () => suite.delete_item,

        /// Sets the current time.
        set_current_time(new_time: Time) -> () => suite.set_item_current_time,

        /// Retrieves the comment.
        comment() -> String => suite.item_comment,

        /// Sets the comment.
        set_comment(comment: &str) -> () => suite.set_item_comment,

        /// Retrieves an item's label.
        label() -> LabelId => suite.item_label,

        /// Sets an item's label.
        set_label(label: LabelId) -> () => suite.set_item_label,

        /// Gets an item's most recently used view.
        ///
        /// The view can be used with two calls in the [`suites::ColorSettings`](aegp::suites::ColorSettings), to perform a color transform on a pixel buffer from working to view color space.
        mru_view() -> ae_sys::AEGP_ItemViewP => suite.item_mru_view,

        // ―――――――――――――――――――――――――――― Footage suite functions ――――――――――――――――――――――――――――

        /// Returns an error if item isn't a footage item.
        /// Used to convert an item handle to a footage handle.
        main_footage() -> Footage => footage.main_footage_from_item,

        /// Returns an error if item has no proxy. Returns the proxy footage handle.
        /// Note: a composition can have a proxy.
        proxy_footage() -> Footage => footage.proxy_footage_from_item,

        /// Sets footage as the proxy for an item. Will be adopted by the project.
        /// This is Undo-able; do not dispose of the returned added item if it's undone.
        set_proxy_footage(footage: impl AsPtr<ae_sys::AEGP_FootageH>) -> () => footage.set_item_proxy_footage,

        /// Replaces footage for an item. The item will replace the main footage for this item.
        /// This is Undo-able; do not dispose of the returned added item if it's undone.
        replace_main_footage(footage: impl AsPtr<ae_sys::AEGP_FootageH>) -> () => footage.replace_item_main_footage,

        /// Populates an AEGP_FootageInterp describing the settings of the [`FootageHandle`].
        /// There is no way to create a valid `AEGP_FootageInterp` other than by using this function.
        /// If `proxy` is `true`, the proxy footage's settings are retrieved.
        footage_interpretation(proxy: bool) -> ae_sys::AEGP_FootageInterp => footage.footage_interpretation,

        /// Apply the settings in the `AEGP_FootageInterp` to the `AEGP_FootageH`. Undo-able.
        /// If `proxy` is `true`, the proxy footage's settings are modified.
        set_footage_interpretation(proxy: bool, interp: &ae_sys::AEGP_FootageInterp) -> () => footage.set_footage_interpretation,

        /// Returns the color of a given solid. Returns an error if the [`ItemHandle`] is not a solid.
        /// If `proxy` is `true`, the proxy solid's color is retrieved.
        solid_footage_color(proxy: bool) -> ae_sys::AEGP_ColorVal => footage.solid_footage_color,

        /// Sets the color of a solid. Undo-able.
        /// If `proxy` is `true`, the proxy solid's color is set.
        set_solid_footage_color(proxy: bool, color: &ae_sys::AEGP_ColorVal) -> () => footage.set_solid_footage_color,

        /// Sets the dimensions of a solid. Undo-able.
        /// If `proxy` is `true`, the proxy solid's dimensions are modified. Returns an error if the item isn't a solid.
        set_solid_footage_dimensions(proxy: bool, width: i32, height: i32) -> () => footage.set_solid_footage_dimensions,
    }
);

impl Item {
    /// Get name of type. (name length up to `32`).
    pub fn type_name(&self) -> Result<String, Error> {
        let Ok(ref suite) = *self.suite else { return Err(Error::MissingSuite); };
        suite.type_name(self.item_type()?)
    }

    /// Creates a new folder in the project, inside the current item.
    /// The newly created folder is allocated and owned by After Effects.
    pub fn create_folder_inside(&self, name: &str) -> Result<Item, Error> {
        let Ok(ref suite) = *self.suite else { return Err(Error::MissingSuite); };
        Ok(Item::from_handle(
            suite.create_new_folder(name, Some(self.handle))?,
            false
        ))
    }

    /// Returns the composition for this item. Returns an error if the item isn't a composition.
    pub fn composition(&self) -> Result<Composition, Error> {
        if self.item_type()? == ItemType::Comp {
            let Ok(ref comp) = *self.comp else { return Err(Error::MissingSuite); };
            Ok(comp.comp_from_item(self.as_ptr())?.unwrap().into())
        } else {
            Err(Error::Parameter)
        }
    }

    /// Creates a new [`aegp::RenderOptions`] from this layer.
    pub fn render_options(&self, plugin_id: PluginId) -> Result<aegp::RenderOptions, Error> {
        aegp::RenderOptions::from_item(self.handle.as_ptr(), plugin_id)
    }
}