# 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 **Mockup** — **assembled 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_` | Editable **visible** layer (text / media / audio) — what the user fills in. | | `frd_` | **Data / direction** layer — hidden values, checkbox/dropdown/fill, colour data, RTL companion. | | `frc_` | **Container comp** used for duplication (scenes, repeat boxes). | | `frs_` | **Shared-ref** layer inside an `frc_` container. | | `frd_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_`. - `FRDMaker(key)` → strip `frl_`, ensure `frd` → `frd_`. ### 🔑 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 **`_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 `0–8` (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 binder** — *not 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 layer** — `projects`/`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. 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.