Files
flatrender/docs/aep-template-convention.md
soroush.asadi 0a7dd9b84c
Build backend images / build content-svc (push) Failing after 45s
Build backend images / build file-svc (push) Failing after 55s
Build backend images / build gateway (push) Failing after 53s
Build backend images / build identity-svc (push) Failing after 54s
Build backend images / build notification-svc (push) Failing after 53s
Build backend images / build render-svc (push) Failing after 47s
Build backend images / build studio-svc (push) Failing after 51s
feat(nodes): live CPU/RAM/disk monitoring in the node list
- node-agent: internal/metrics — read CPU% (GetSystemTimes), RAM (GlobalMemoryStatusEx),
  disk used%/total (GetDiskFreeSpaceEx) via stdlib kernel32 (no external dep; windows
  build + non-windows stub). Heartbeat now reports cpu_pct/ram_available_mb/disk_used_pct/
  disk_total_gb + ae_running.
- render-svc: heartbeat persists last_disk_pct + disk_total_gb (migration 29); RenderNode
  model + node SELECT/scan carry them.
- admin: rewrite NodesTable to the real RenderNode shape (fixes a pre-existing items/V2Node
  mismatch that left the list empty) + a CPU/RAM/disk bars column + stale-heartbeat flag.
- assets-bundle ingestion: ProjectMediaBundle (jszip) auto-maps project.zip → project/scene
  image/demo/colour + music; PatchProject gains image/full_demo/shared_colors_svg.
- scan: RGBA (4-number) colours recognised + frshare single-int controls detected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:01:18 +03:30

17 KiB
Raw Permalink Blame History

FlatRender — AEP Template Convention & Rule Engine

Single source of truth for how a FlatRender After Effects template is authored, scanned, and rendered (bound). Both the scanner (scan.jsx + Go quick-scan) and the render binder (the JSX generator) must obey this document. Conventions are versioned (convention_version per project) so the rules can evolve without breaking the existing library.


0. Project ↔ AEP relationship

  • One project = one .aep file (in V2 stored at templates/{project_id}/template.aep or bundle.zip). A real project bundle is usually a footage folder + the .aep, zipped, and extracted on the render node so relative footage resolves.
  • The project's human name never appears in AE. Comp/layer names are universal conventions, not per-project. The project is identified purely by which file it owns.
  • AE version: the farm always runs the latest After Effects (a global setting, not per-project) → Master Properties + Media Replacement are always available.

1. Naming conventions

Composition names (universal, identical in every project)

Comp Role
frfinal Final render comp for Fix and MusicVisualizer (pre-built).
flatrender Final render comp for Flexible and Mockupassembled at render time by the binder.
frshare Holds the shared colours (one text layer per colour; layer name = element key, sourceText = colour value).
all Holds the shared-layer definitions.

Layer-name prefixes

Prefix Meaning
frl_<key> Editable visible layer (text / media / audio) — what the user fills in.
frd_<key> Data / direction layer — hidden values, checkbox/dropdown/fill, colour data, RTL companion.
frc_<key> Container comp used for duplication (scenes, repeat boxes).
frs_<key> Shared-ref layer inside an frc_ container.
frd_<key>d Direction companion of an frl_ layer (0=LTR, 1=RTL), produced by FRDMaker(key)+"d".

Key normalization (legacy Helper)

  • FRLMaker(key) → if it already contains frl/frd, keep; else frl_<key>.
  • FRDMaker(key) → strip frl_, ensure frdfrd_<key>.

Naming differs by PROJECT TYPE ⚠️ (scanner must be told the type)

Only two layer kinds: t = text · m = media. In AE image / video / audio are the same footage (AVLayer), so all three are m.

FIX / MusicVisualizer — no per-scene frc_ comps. AE project-panel folders:

Folder Holds
Final/ frfinal — the mother render comp
Edit/ editable comps (any name); layers frl_c(x)t(y) (text) / frl_c(x)m(y) (media). c(x) = scene no., (y) = element index
Share/ frshare comp — frd_<name> layers, distinguished by value: a text layer holding RGBA (4 numbers, e.g. 253,226,228,255) = a shared colour; a layer holding a single integer 03 = a shared control (an expression reads it to switch a design variant — e.g. frd_alladdfill=1 toggles logo = image vs logo = fill-colour overlay). All user-editable; expressions on the visible layers read these.
Other/ footage (video/image) files

→ scanner derives scenes from the distinct c<x> in frl_c(x)t/m(y) layer names; element key = the full layer name; type t→Text, m→Media.

FLEXIBLE / Mockup — each scene is a comp; editable layers frl_<key> inside; story duplicates rename <scene>_d{N}.

→ The scan takes a project-type argument (defaults to the project's ChooseMode) and branches its parsing rule (FR_SCAN_MODEfix parses layer names, flexible parses comps).

🔑 The uniqueness invariant

No two layers ever share a name. Every editable field maps to exactly one independent value.

  • This is why duplication renames (see §4): a shallow duplicate would collide names → values mirror; renaming keeps names unique → values independent.
  • Consequence for the scanner: just enumerate every unique frl_/frd_ layer and emit one content-element per name — no repeat-detection needed.

2. Project modes (5)

Mode Render comp Timeline source Duration Binder generator Output
FIX frfinal fixed (template) = project_duration_sec CreateV2FixJSX video
FLEXIBLE flatrender (assembled) user story (Normal scenes, duplicated) Σ scene + first overlap, clamped to project.max CreateFlexibleJSX video
MockUp flatrender (assembled) one frame per scene Duration = sceneCount (FrameRate=1) CreateMockupJSX images (JPG/scene)
MusicVisualizer frfinal fixed (template) min(audioLen, project.max) CreateV2FixJSX video
VoiceOver (like Fix/MusicViz, timed to a VO track — to confirm) fixed = VO length, capped at project.max tbd video

Differentiators: does the user control the timeline? (Fix=no, Flexible=yes) · content-swap vs design-placement (Fix/Flexible vs Mockup) · audio-driven? (MusicViz/VoiceOver).


2.1 Output spec — aspects · quality · frame rate

Aspects (video) — only these three:

Aspect Dimensions
16:9 1920 × 1080
1:1 1080 × 1080
9:16 1080 × 1920

Render quality tiers (output resolution, chosen per render): 360p · 480p · 720p · 1080p · 4K.

  • This is a render-time output tier (downscales the comp), distinct from the project's native design resolution.
  • The project's Resolution acts as the ceiling; the user picks a tier ≤ ceiling, also bounded by their plan.

Free vs paid:

Free Paid
Max quality 360p up to project ceiling (4K)
Watermark yes no
Frame rate 21 fps (default, per-project configurable) project fps

Frame rate:

  • Free renders use 21 fps by default — stored per project (free_fps), configurable in project settings.
  • Maximum frame rate = 60 (any mode/tier).

3. Scene types & roles

scene_type User-pickable? Role in assembly
Normal yes Story scenes — duplicated into the timeline.
Config no Global shared layers/config — applied across the whole comp (not on the timeline).
DesignStart no BottomDesign — full-duration background, bottom of the layer stack.
DesignEnd no TopDesign — full-duration overlay (logo/frame), top of the layer stack.

Design scenes are authored once in the template and injected at render time (single instance, full duration — no _d{N}).


4. flatrender assembly (Flexible / Mockup)

Layer stack, bottom → top (z-order = depth):

(global)               Config            → shared layers/colours applied across
bottom (behind)        DesignStart       → background, full duration                [auto · 1×]
middle (time-line)     Normal story      → sequenced instances, deep-dup _d{N}      [user-picked]
top (in front)         DesignEnd         → overlay, full duration                   [auto · 1×]

Story sequencing (Normal scenes), in Sort order:

  • Each instance = a deep-duplicated scene comp renamed <sceneKey>_d{N} (N = 1-based duplication index). Inner frl_/frd_ names stay identical (each clone is its own namespace).
  • Instance layer length = SceneLength + OverlapAtEnd; placed at the running time offset; consecutive scenes cross-overlap by OverlapAtEnd.
  • Per-instance inputs are an ordered list of input-sets, one per instance (SceneStory(Name=sceneKey) → Duplications[] → Inputs[]).

5. Duration model (universal across modes)

Per scene: default_duration_sec, min_duration_sec, max_duration_sec, overlap_at_end_sec, can_handle_duration (may the user change it?). Per project: project_duration_sec (default/target), min_duration_sec, max_duration_sec (hard output cap).

Flexible:

each scene:  min ≤ SceneLength ≤ max
total     =  Σ SceneLength (+ first scene overlap)
clamp     =  min(total, project.max_duration_sec)        ; preview hard-capped at 180s

MusicVisualizer / audio modes:

audioLen ≤ max → duration = audioLen          (no min floor; short audio = short video)
audioLen > max → user trims (≤ max, decrease-only)   OR   untrimmed → max + fade-out
  • Trimmer window is capped at project.max (can shrink, never exceed).
  • Cap is enforced in both frontend (UX) and backend (authority — never trust the client).

Fade-out (audio modes): default 1.5 s, admin-configurable, studio-toggleable.

projects.audio_fade_out_sec        default 1.5            (admin default duration)
scenes.audio_fade_out_sec          nullable override      (per-item)
projects.audio_fade_out_enabled    default on             (admin default state)
projects.is_audio_fade_changeable  default true           (may the studio user toggle?)
studio: fade_out_enabled           user choice            (only if changeable)

→ fadeEnabled  = is_audio_fade_changeable ? userChoice : project.audio_fade_out_enabled
→ fadeDuration = item.audio_fade_out_sec ?? project.audio_fade_out_sec ?? 1.5

(These four projects/scenes columns + the studio flag are NOT yet in the schema — added with the audio/binder work.)


6. Responsive fit-box (per element)

Each frl_ element is its own precomp (a box/region) with a fixed area; content auto-fits inside it.

  • Text: single-line or multi-line (TextArea); auto-scaled down if it overflows (is_text_box, max_size); wrapped for multi-line.
  • Alignment in box: justify (LEFT/CENTER/RIGHT/FULL_JUSTIFY) + position_in_container anchors 08 (0 = centre, corners/edges).
  • Media: scale-to-fit the box, keep aspect, centred.

This is why deep-dup is heavy: every element is a nested comp, so a scene is comps-inside-comps; cloning duplicates the whole tree (exactly what v2 avoids).


7. Input types (scanner classification targets)

Input type content_element_type AE detection
image input Media footage/placeholder layer
image / video input Media (video_support) footage layer
text / multi-text input Text / TextArea text layer (single vs multi-line box)
text input Text text layer
number input Number text layer (numeric)
option input (list) DropDown data layer + mapped_list
yes / no input CheckBox / Toggle data layer (0/1)

The detection walks layers top → bottom, classifies each editable layer into one of these, and emits scene content-elements in that order.


8. Expression-driven data flow ⚠️

Visible layers do not hold values — they run AE expressions that read from a central source:

binder writes ─►  frshare colour layers  +  frd_ data layers   ─►  expressions  ─►  visible frl_ layers
                  (one write)                                       (propagate automatically)
  • The binder writes only the data sources (a colour once in frshare, a value in a frd_ layer). Expressions fan it out — it never touches each visible layer.
  • The scanner reads the data sources (frshare layers + frd_ data layers) as the source of truth for colours/values — not the expressions.
  • Colours live in frshare: layer name = element key, sourceText = colour value (hex / r,g,b).

9. Convention versioning + the rule engine

convention_version is an admin-set field on the project (chosen in the add/edit panel, default = latest). The rule engine maps a version → the rules the scanner + binder follow. Both versions are supported (hybrid) so nothing breaks.

Aspect v1 — legacy (current) v2 — Master Properties (proposed)
Per-instance independence deep-dup + rename _d{N} 1 source comp + N layer instances, each with Master/Essential-Property overrides
Scan target layer-name prefixes (frl_/frd_) comp.masterProperties / Essential Graphics (typed + named)
Media per instance duplicated footage Media Replacement essential property
Expressions AE auto-repoints intra-clone expressions global colours stay expression-from-shared; per-instance values flow from the instance's master property into the internal expression/fit
Cost N full comp trees (heavy) tiny project, faster open/render

Shared rules (both versions): §1 naming · §3 scene-type roles · §4 z-order assembly · §5 duration/overlap/fade · §6 fit-box · §7 input types · §8 expression data flow.

v2 golden rule: global → expression-from-shared; per-instance → (v1: dup+rename · v2: master-prop feeding the expression/fit).

⚠️ v2 is "validate-first"

Expressions are the #1 thing that complicate Master Properties (expression-driven prop vs master-prop override; expression scope across the master-property boundary). v1 stays the safe default. Before re-plumbing to v2, spike on one real template to confirm promotion + media replacement + expression scope behave. (Defer to the AE author here.)


10. What this drives in the V2 codebase

  • projects.convention_version (+ admin panel selector) — to add.
  • Scanner (scan.jsx / Go quick-scan) — branch on version: v1 reads names, v2 reads Essential Graphics. Emits the canonical ScanResult the importer consumes.
  • Render bindernot yet built. Hybrid generator: v1 = deep-dup + rename + bind data sources; v2 = layer instances + master-prop overrides. Per-mode (CreateV2FixJSX / CreateFlexibleJSX / CreateMockupJSX equivalents). This is the port of the legacy JSXGenerator.cs.
  • Audio/duration layerprojects/scenes fade + duration columns, trimmer-cap-to-max, MusicViz resolution, fade-out. (One migration, one rebuild.)
  • Dynamic scene generation — the end goal: given business + logic, select/assemble/author scenes against these rules.

11.5 Project import bundles (admin upload)

Adding a project uses two zips with strict conventions:

Zip 1 — render bundle (AE project)

final.aep            ← at the zip root
(Footage)/...        ← footage folder (name may be "(Footage)" with parens), SIBLING of final.aep
(Footage)/LONG VERSION/Items/SFX1..n.mp3   ← SFX are footage the AEP references (NOT separate assets)
  • final.aep + the footage folder must be siblings so AE resolves relative paths. Footage-folder name match is case-insensitive and accepts Footage or (Footage).
  • Stored at templates/{project_id}/bundle.zip; the node extracts it keeping the tree intact and runs aerender from the .aep's folder → footage is beside it → no "missing footage".
  • Validation on upload: the zip must contain final.aep (prefer this name) with a sibling footage folder. Reject otherwise.
  • The scanner reads final.aep from this same bundle.
  • SFX ship inside this footage; they are not in the assets bundle.

Zip 2 — assets bundle (demos / placeholders / colour SVGs / music)

May be wrapped in a top folder (e.g. New folder/) → match by basename, strip leading dirs. s{i} = the i-th scene by sort order.

File Meaning Target
p.jpg project image / thumbnail Project image
p.mp4 full project demo (audio, 1080) Project full demo
p.svg project colour SVG Project colour SVG
demo.mp4 hover preview loop (on the card) Project hover/mini demo
<name>.mp3 the single non-sfx mp3 = default music (arbitrary name, e.g. Playful Ink Reveal.mp3) Project default music
s1.mp4 … s(n).mp4 per-scene loop demos Scene[i].Demo
s1.jpg … s(n).jpg per-scene placeholder images Scene[i].Image
s1.svg … s(n).svg per-scene colour SVG (optional) Scene[i].SceneColorSvg

Music rule: the project's default music is the one .mp3 in the assets bundle whose name is not sfx. (SFX itself comes from the render footage.)

Flow: render bundle → scan → scenes created → assets bundle ingested → each asset uploaded to storage and its field set, mapping s{i} to the i-th scene by sort. (Assets-bundle ingestion is a separate admin feature — TODO.)

11. Open items to confirm / validate

  • VoiceOver mode mechanics (separate mode vs flavor of Fix/Flexible).
  • Exact flatrender timeline offset formula (cumulative Duration overlap).
  • v2 Master-Properties spike on a real template (expressions + media replacement + scope).
  • frfinal vs flatrender — confirm flatrender is always the assembled comp (built by the binder) for Flexible/Mockup.