Differences from ExtendScript¶
This page documents the intentional design differences between py_aep and the After Effects ExtendScript API. These are not bugs - they are choices made to provide a more Pythonic, convenient, or complete interface.
Naming Conventions¶
ExtendScript uses camelCase for attributes and methods. py_aep uses
snake_case following Python conventions:
| ExtendScript | py_aep |
|---|---|
blendingMode |
blending_mode |
frameRate |
frame_rate |
isTimeVarying |
is_time_varying |
Indexing¶
ExtendScript collections are 1-based. Python lists and py_aep are 0-based:
Layer.index and PropertyBase.property_index also use 0-based numbering,
so that comp.layers[layer.index] and group.properties[prop.property_index]
work directly without offset arithmetic.
Collections and Iterators¶
ExtendScript uses indexed accessor methods (item(index), layer(index)) on
custom collection objects (ItemCollection, LayerCollection,
OMCollection, RQItemCollection). py_aep uses standard Python lists:
Keyframes¶
ExtendScript accesses keyframe data through Property.key*() methods that take
a 1-based key index. py_aep exposes keyframes as a list of Keyframe objects on
Property.keyframes:
The Keyframe object bundles all keyframe attributes together, so you don't
need separate method calls for each attribute.
The keyframe mutation methods mirror ExtendScript but use 0-based key indices:
These work on every property kind, including the complex ones (mask paths, source text, markers, orientation, gradients). Adding the first keyframe converts a static property to an animated one, and removing the last keyframe reverts it to a static value holding the removed keyframe's value, matching After Effects' on-disk forms. Marker properties accept a plain string as a comment shorthand:
Feather Points¶
ExtendScript exposes mask feather data as parallel arrays on Shape
(featherSegLocs, featherRadii, etc.). py_aep exposes a list of
FeatherPoint objects on Shape.feather_points:
Each FeatherPoint bundles segment location, radius, interpolation,
tension, and corner angle together.
Markers¶
In ExtendScript, CompItem.markerProperty and Layer.marker are
Property objects accessed via keyValue(). In py_aep,
CompItem.marker_property and Layer.marker expose the underlying
Property (with keyframes holding marker times), while
CompItem.markers and Layer.markers provide a convenient flat
list[MarkerValue]:
Frame-Based Time Attributes¶
ExtendScript expresses all times in seconds (floating-point). py_aep adds integer frame-based equivalents for convenience:
| ExtendScript (seconds) | py_aep (seconds) | py_aep (frames) |
|---|---|---|
Layer.inPoint |
layer.in_point |
layer.frame_in_point |
Layer.outPoint |
layer.out_point |
layer.frame_out_point |
Layer.startTime |
layer.start_time |
layer.frame_start_time |
Item.time |
item.time |
item.frame_time |
Layer.time |
layer.time |
layer.frame_time |
| - | keyframe.time |
keyframe.frame_time |
MarkerValue.duration |
marker.duration |
marker.frame_duration |
Warning:
AVItem.frame_duration is the total duration in frames (an integer),
not the duration of a single frame in seconds (which is
1/frame_rate). This differs from ExtendScript's AVItem.frameDuration
which is the duration of one frame in seconds.
Convenience Access Properties¶
Project¶
Project provides filtered views of items that ExtendScript requires manual
filtering for:
project.compositions # list[CompItem] - all compositions
project.folders # list[FolderItem] - all folders
project.footages # list[FootageItem] - all footages
FolderItem¶
FolderItem provides filtered item lists:
folder.compositions # list[CompItem] - compositions in the folder
folder.folders # list[FolderItem] - subfolders
folder.footages # list[FootageItem] - footages in the folder
CompItem¶
CompItem provides filtered layer lists:
comp.text_layers # list[TextLayer]
comp.shape_layers # list[ShapeLayer]
comp.camera_layers # list[CameraLayer]
comp.light_layers # list[LightLayer]
comp.parametric_mesh_layers # list[ParametricMeshLayer]
comp.null_layers # list[Layer]
comp.solid_layers # list[AVLayer]
comp.adjustment_layers # list[AVLayer]
comp.three_d_layers # list[AVLayer]
comp.guide_layers # list[AVLayer]
comp.solo_layers # list[Layer]
comp.composition_layers # list[AVLayer] - layers sourced from comps
comp.footage_layers # list[AVLayer] - layers sourced from footages
comp.file_layers # list[AVLayer] - layers sourced from files
comp.placeholder_layers # list[AVLayer]
comp.av_layers # list[AVLayer] - all AV layers
Extra Attributes¶
py_aep exposes additional attributes parsed from the binary format that are not available in ExtendScript:
Property¶
| Attribute | Description |
|---|---|
dimensions |
Number of dimensions (1, 2, or 3) |
locked_ratio |
True if X/Y ratio is locked |
default_value |
The default value of the property |
last_value |
The last value before animation |
nb_options |
Number of options in a dropdown property |
FootageItem¶
| Attribute | Description |
|---|---|
asset_type |
The footage type ("placeholder", "solid", "file") |
start_frame |
The footage start frame |
end_frame |
The footage end frame |
CompItem¶
| Attribute | Description |
|---|---|
time_scale |
Internal time scale divisor for keyframe times |
essential_graphics_controllers |
List of Essential Graphics controllers in the comp |
guides |
List of Guide objects (ruler guides for alignment) |
renderer_options |
The active 3D renderer's options |
Layer¶
| Attribute | Description |
|---|---|
layer_type |
The layer type ("AVLayer", "Layer", "CameraLayer", "LightLayer" "ParametricMeshLayer") |
RenderQueueItem¶
| Attribute | Description |
|---|---|
comment |
A comment describing the render queue item |
name |
The render settings template name |
settings |
Full render settings as a dict |
OutputModule¶
| Attribute | Description |
|---|---|
file_template |
Raw file path template with variables like [compName] |
format_options |
Format-specific options (JPEG quality, EXR compression, etc.) |
settings |
Full output module settings as a dict |
Enums¶
py_aep provides many enum classes across 8 modules, covering values that ExtendScript exposes as plain integers or doesn't expose at all:
- Format options enums:
VideoCodec,AudioCodec,OpenExrCompression, etc. - Render settings enums:
FieldRender,MotionBlurSetting,DiskCacheSetting, etc. - Output module enums:
OutputChannels,OutputColorDepth,ResizeQuality, etc. - Font enums:
CTFontTechnology,CTFontType,CTScript - Text enums:
ComposerEngine,BoxAutoFitPolicy,LineOrientation, etc.
Gradient Colors¶
ExtendScript reports gradient color properties (ADBE Vector Grad Colors)
with propertyValueType = NO_VALUE and provides no .value accessor.
py_aep parses the underlying XML stored in the binary and exposes it as a
Gradient object on Property.value:
gfill = contents.property("ADBE Vector Graphic - G-Fill")
colors = gfill.property("ADBE Vector Grad Colors")
gradient = colors.value # Gradient instance
for stop in gradient.color_stops:
print(stop.offset, stop.color) # (red, green, blue) tuple
for stop in gradient.alpha_stops:
print(stop.offset, stop.alpha)
Output Module Format Options¶
ExtendScript provides no access to format-specific render settings. py_aep parses these from the binary and exposes them:
CineonFormatOptions- black/white points, gamma, bit depthJpegFormatOptions- quality, format type, scansOpenExrFormatOptions- compression, luminance/chroma, bit depthPngFormatOptions- bit depth, compression, HDR10 metadataTargaFormatOptions- bits per pixel, RLE compressionTiffFormatOptions- LZW compression, byte orderXmlFormatOptions- video/audio codec, frame rate, MPEG settings
3D Renderer Options¶
CompItem.renderer names the active 3D renderer, but ExtendScript exposes
nothing about that renderer's own settings — the Options dialog beside the
3D Renderer dropdown in Composition Settings. py_aep parses them from the
binary and exposes them on CompItem.renderer_options:
The options available depend on the active renderer:
ClassicRendererOptions— shadow map resolutionAdvancedRendererOptions— quality, environment light shadow resolution and smoothness, casting box size and centreCinema4DRendererOptions— qualityRayTracedRendererOptions— none; see Known Limitations
Every option py_aep exposes is stored in the .aep and survives an After
Effects preferences reset.
Advanced 3D's casting box values are stored as fractions of the composition's raw pixel dimensions and are exposed here as the pixel values the dialog shows. Pixel aspect ratio is not applied, and resizing a composition rescales these options in pixel terms because the stored fraction does not move — both are After Effects' own behaviour.
Stricter Write Validation¶
py_aep validates values like After Effects' dialogs, which is sometimes stricter than ExtendScript's own setters. Where AE scripting accepts a degenerate value and silently misbehaves, py_aep raises instead:
- Render time spans: AE scripting accepts a
timeSpanStartbefore 0 or past the span end, then silently renders garbage (a span starting at -5 renders 5 seconds of void lead-in; an end before the start renders a single frame, both with aDONEstatus - probed in AE 2026). py_aep rejects a negative start, a start at or past the end, and a duration below one frame. The semantics match ExtendScript: setting the start keeps the span end fixed (the duration is recomputed), setting the duration keeps the start. - Booleans: AE coerces any truthy value, so
"no"becomesTrue. py_aep boolean attributes and settings accept onlyTrue/False.
Approximated Runtime Behaviors¶
Some ExtendScript methods gate their behavior on runtime state that only a running After Effects has. py_aep implements the closest file-level equivalent and documents the divergence:
Project.auto_fix_expressions(): After Effects only rewrites expressions that are currently erroring - runtime state that is not stored in the project file (verified: broken and working expressions are chunk-identical on disk). py_aep instead replaces the quoted forms"old_text"/'old_text'in every enabled expression. Everything else matches AE (probed in AE 2026): both quote styles are fixed, disabled expressions are never touched, unquoted mentions are ignored, and quoted occurrences inside comments of a rewritten expression are replaced too. The one divergent case: an expression that evaluates cleanly but contains the quoted text is rewritten by py_aep, while AE leaves it alone.AVLayergeometry methods (source_point_to_comp(),comp_point_to_source(),source_rect_at_time()): After Effects evaluates these at the current playhead position, which py_aep reads as the comp's storedtimeattribute - deterministic per file, but it reflects wherever the playhead sat when the project was last saved. The two point conversions accept an optionaltimekeyword (py_aep extension) to evaluate at an explicit time instead. Layers whose parent chain uses auto-orientation raiseNotImplementedError(the transform math does not model it), as do text and shape layers forsource_rect_at_time()(content bounds need glyph extents / shape geometry evaluation).calculate_transform_from_points()names its third parameterpoint_bottom_left: the AE guide calls itpointBottomRight, but After Effects treats it as the bottom-left corner (probed AE 2026; the guide's own example passesbl).TextDocument.reset_char_style()/reset_paragraph_style(): After Effects restores the Character / Paragraph panel defaults, which are application state rather than project data - they live in the AE preferences (["Text Style Sheet"]/["Text Paragraph Sheet"]inPrefs-text.txt), not in the.aep. py_aep reads those same sections from the preferences directory the project was parsed with (seeparse(..., ae_preferences_dir=...)), so a reset restores your panel defaults exactly as AE would. Parsed without a preferences directory, it falls back to AE's factory values. Only the attributes present in those panel sheets are restored - which is why, like After Effects, a paragraph reset leavesauto_hyphenateuntouched.TextDocument.baseline_locs: reports the layout After Effects persisted. The per-line pen origins and glyph advances exist only in that cache, so - unlikecomposed_line_count- this never recomposes; after a layout-affecting py-side write the values stay at the persisted layout (seecomposition_stale).Project.replace_font():no_font_lockingis accepted for parity and ignored. After Effects uses it to suppress the fallback font it picks when the target font lacks glyphs for the text; that is a runtime font-engine decision py_aep does not make, so py_aep always performs the direct replacement (it behaves as ifno_font_lockingwereTrue). Like every py_aep text write, the layer's layout cache is left as-is (AE recomputes it on open), so glyph advances cached for the old font stay until then.