Skip to content
Aphelion

Authoring editor nodes

Plugins run inside Aphelion Editor. Import public classes and helpers from aphelion_sdk.editor; use NumPy for data processing and PyQt6 for native UI. You do not need imports from the editor's core, effects, render or ui packages.

Local development

For a checkout with aphelion-sdk and aphelion-editor beside each other, activate the editor's environment. From the parent directory, in PowerShell:

python -m pip install -e ./aphelion-sdk -e ./aphelion-styling
$env:PYTHONPATH = (Resolve-Path ./aphelion-editor/src).Path
python -c "from aphelion_sdk.editor import VideoEffectPlugin; print(VideoEffectPlugin)"

The environment must also contain the editor's dependencies. Setting PYTHONPATH here makes the editor runtime available to standalone development scripts; normal editor startup already configures its runtime. Installing only the SDK does not supply the renderer or make node classes standalone.

Copy your plugin to the user directory opened by Preferences > Plugins > Open user folder, then enable user plugins and reload. See the first-plugin walkthrough.

Video effects

A complete effect with one parameter:

from aphelion_sdk.editor import VideoEffectPlugin, slider_property, register_plugin


@register_plugin
class Grayscale(VideoEffectPlugin):
    plugin_name = "Tutorial Grayscale"
    plugin_category = "My Plugins"
    plugin_color = (140, 140, 140)

    def setup_effect_properties(self):
        self.set_property("amount", slider_property(100, 0, 100, label="Amount", suffix="%"))

    def process_frame(self, frame, frame_num):
        amount = self.float_value("amount", 100.0) / 100.0
        luma = frame[..., 0] * 0.2126 + frame[..., 1] * 0.7152 + frame[..., 2] * 0.0722
        gray = luma[..., None].repeat(3, axis=2)
        return frame * (1.0 - amount) + gray * amount

The default base supplies frame input/output sockets, enabled, and a mix slider from 0 to 100. It preserves accompanying audio. With no frame input it returns a blank frame. Implement process_frame for your operation and optionally setup_effect_properties for parameters; the default processor passes through.

Frames are NumPy arrays with shape (height, width, 3) and dtype float32, with nominal values from 0 to 1. There is no alpha channel. Return the same shape for ordinary unary effects. Do not modify the input in place: other branches may share it. For example, use frame * gain, not frame *= gain.

Arbitrary sockets and multiple outputs

Use NodePlugin for generators, compositors, masks, utilities, logic or mixed-media nodes. Define sockets in setup_input_outputs and calculate outputs in evaluate. The editor calls setup_effect_properties after socket setup.

from aphelion_sdk.editor import NodePlugin, NodeSocketType, register_plugin


@register_plugin
class AnalyzeFrame(NodePlugin):
    plugin_name = "Analyze Frame"
    plugin_category = "My Plugins"

    def setup_input_outputs(self):
        self.add_input("image", NodeSocketType.Frame)
        self.add_output("image", NodeSocketType.Frame)
        self.add_output("mask", NodeSocketType.Mask)
        self.add_output("average", NodeSocketType.Number)

    def evaluate(self, frame_num):
        frame = self.input_frame("image")
        if frame is None:
            frame = self.blank_frame()
        mask = frame.mean(axis=2)
        return {"image": frame, "mask": mask, "average": float(mask.mean())}

For a single output, return its payload directly. For multiple outputs, return a dictionary whose keys exactly match output names. You can omit inputs to make a generator or declare multiple inputs for a compositor. Available socket types are Frame, Mask, Number, Color, Audio, Any, and legacy Node.

Read connections using input_frame(slot), input_audio(slot), input_number(slot, default=0.0) or get_input_value(slot). Missing frame/audio inputs return None. input_frame_with_audio(slot="frame") returns the combined container when present. Handle missing inputs explicitly.

NodePlugin has no automatic bypass/mix controls. For an existing custom VideoEffectPlugin, overriding both setup_input_outputs and evaluate remains supported; this bypasses the default unary effect behavior.

Declare and read properties

from enum import Enum
from aphelion_sdk.editor import NodePlugin, NodeSocketType, choice_property, number_property, register_plugin


class Operation(Enum):
    Multiply = "multiply"
    Add = "add"


@register_plugin
class AdjustNumber(NodePlugin):
    plugin_name = "Adjust Number"
    plugin_category = "My Plugins"

    def setup_input_outputs(self):
        self.add_input("value", NodeSocketType.Number)
        self.add_output("value", NodeSocketType.Number)

    def setup_effect_properties(self):
        self.set_property("amount", number_property(2.0, -100.0, 100.0, label="Amount"))
        self.set_property("operation", choice_property(Operation.Multiply, label="Operation"))
        self.expose_modulation_input("amount")

    def evaluate(self, frame_num):
        value = self.input_number("value")
        amount = self.float_value("amount", 2.0)
        operation = self.enum_value("operation", Operation, Operation.Multiply)
        return value * amount if operation == Operation.Multiply else value + amount

expose_modulation_input("amount") creates a Number socket named in_amount. A connection there overrides the stored numeric value during evaluation. Property Drive can also supply numeric overrides. Use the typed readers instead of reading raw property storage when calculating outputs:

ReaderValue
float_value(key, default) / int_value(key, default)Numeric parameter
bool_value(key, default)Toggle
string_value(key, default="")Text
color_value(key, default=(128, 128, 128))RGB channels clamped to 0 through 255
enum_value(key, enum_type, default)Enum with fallback

Builders also support description, group and priority (lower first). See property signatures. Keep property keys stable for saved projects. UI edits should use the widget host setters to participate in undo.

Identity, evaluation and persistence

Class attributeMeaning
plugin_nameDisplay name and saved node type
plugin_categoryAdd Node category; defaults to Plugins
plugin_descriptionDescription/search text
plugin_colorHeader RGB tuple
plugin_authorCredit
widgetsTuple of attached inspector, dialog and panel classes
plugin_productDefaults to editor
plugin_api_versionDefaults to 1; host API compatibility, not package version
is_temporalSet True if output depends on frame/time independently of inputs

For example, a procedural flicker effect should set is_temporal = True and use frame_num in its processor. Evaluation can revisit frames out of order after scrubbing; do not assume sequential calls or exactly one call per frame. Keep UI work out of evaluation and avoid relying on mutable global state for outputs.

The editor persists declared properties and graph connections. It does not serialize arbitrary Python instance fields. Renaming a plugin's category/name, sockets or property keys may break saved graphs. After a code reload, existing node instances retain their class until recreated or the project is reopened.

Next: audio processors, custom properties UI, or editor-wide extensions.

Authoring | Aphelion SDK Documentation