# FilmOpen Project Specification

**A plain-text format for describing a film.**

| | |
|---|---|
| **Version** | 1.5 — Draft |
| **Date** | 10 September 2026 |
| **Format version** | `"filmopen": 1` |
| **Status** | Public draft. Open for comment. |
| **Licence** | This specification: CC-BY-4.0. The JSON Schemas derived from it: CC0. |
| **Canonical URL** | https://filmopen.ai/spec |
| **Schemas** | https://filmopen.ai/schema/1/ |

---

## Abstract

A FilmOpen project is a folder of plain JSON files that describes a feature film, series or short
completely: its characters, places, props and looks; its script; its shots and sound; and the media
that realises them. The format is designed to be read and written by people, by scripts, and by AI
systems; to reduce file conflicts when many contributors work on one film; and to remain
fully meaningful as a bare folder of files with no software present.

It serves two directions equally. **Forward:** write a film in text and generate it with AI models.
**Reverse:** analyse an existing film and describe it in text, so that it can be studied, adapted,
translated or regenerated.

---

## Changes in 1.5

- **Kind.** `kind` is now one of `short` · `feature` · `miniseries` · `series` (§8.1). Readers read
  the 1.4 words as their nearest value: `film` as `feature`, `franchise` as `series`.
- **Every project declares its epochs, and every library entity has one.** `epochs` is required in
  the project file with at least one entry; the first in order is the project's **default epoch**, the
  one an implicit reference falls back to (§6.1, §8.2). An epoch may carry a `year`. A character may
  carry a `birthYear`; a reader proposes `appearance.age` at each epoch from the two and the author
  overrides it (§9.1). The reserved token `default` remains valid as an epoch name.
- **Version label.** The common header gains an optional `label`: a short word for the version in
  version lists (§7). It does not travel with a fork.
- **The creator directs.** A writer that creates a project lists its creator in `contributors` with
  `role: "director"` (§8.3, §18.3).
- **Pick.** The version an author marks with `prefer` in their commentary is their *pick*; an
  application shows it beside official and lets the author change it (§13.4). Removing an official
  pointer is done by deleting the pointer file (§13.2).

## Changes in 1.4

- **Project manifest.** A project folder is identified by one file with a fixed name at its root,
  `filmopen-project.json` (§4.4). It holds the settings that belong to the folder rather than to any
  author's version of the film: the project tag and where the rendered media lives. It is not versioned,
  not authored and not creative content; it is the one file an application writes when it creates a
  project.
- **Media root.** Rendered and imported media may live in a separate folder from the JSON — a folder
  synced by a different service, or a folder outside version control. The manifest names it with a
  typed locator (§4.4); takes are discovered there (§12.1) and media references resolve against it
  (§6.3). Absent a manifest, the project folder is its own media root, as in 1.3.
- **Shared library location.** Models, platforms and plugins are the application's shared library
  (§14.5), installed and updated independently of any project and of the application itself. A project
  folder need not contain them; a project may still hold its own.

## Changes in 1.3

- **Authored projects.** `pj_<tag>_<author>_v<n>.json` uses the same author, version and
  `forkedFrom` conventions as other spec entities. A remake can change language, title, delivery
  settings and story roots in its own complete project file (§4.1, §8.6).
- **Project selection.** Opening an explicit project file selects it. Folder opening uses the project
  official pointer, a sole author's highest version, or an explicit user selection. The selected
  project supplies the context for reference resolution and export.
- **Portable references.** Grouped reference media, epoch choices, plain paths and URLs remain supported.
  Story-root and child lists accept tags or full entity references.
- **Consistent examples.** The worked example has a resolvable style, a declared import batch, explicit
  ambience trim and frame-aligned timings. Provider definitions are identified as outside that example.
- **Plugin contracts.** Validation inputs and response timing are clarified. Code trust is an application
  decision; an official pointer is a creative selection, not permission to execute changed code.
- **Editor handoff.** Final Cut Pro 7 XML is distinguished from FCPXML; relinking and export guarantees
  are limited to supported, tested behavior.

The format identifier remains `"filmopen": 1` during draft development. This is a revised draft, not a
promise that all previous draft projects validate unchanged. To update a 1.0/1.2 project, give its
`pj_<tag>.json` an author and version in the filename and header, update any explicit references to it,
and add an ordered story-root list (an empty list is valid for a blank project). No history records or
migration database are required. Grouped `refs` and fractional frame rates added in 1.2 remain optional.

---

## Contents

1. Introduction
2. Design principles
3. Concepts and terminology
4. Project structure
5. Naming
6. References and resolution
7. Common header
8. The project file
9. Library entities
10. Story structure and script
11. Shots and cues
12. Takes and render batches
13. Versions, official, compare and commentary
14. Extensibility: hooks, plugins, workspaces, shared library
15. Timeline export and round-trip
16. Interchange with existing standards
17. Editing by hand or by machine
18. Conformance
19. Not in this version

Appendix A — Attribute vocabulary
Appendix B — Worked example
Appendix C — MovieLabs OMC correspondence

---

## 1. Introduction

### 1.1 Purpose

Film has mature interchange standards for the script (Fountain, FDX) and for the finish (ASC CDL, LUTs,
OpenTimelineIO, EDL, FCPXML). It has nothing for the structured middle — the description of characters,
places, props, looks and shots that an AI production pipeline needs, and that a collaborative team needs
to share. This specification fills that gap.

### 1.2 Scope

This document defines:

- the folder and file layout of a project;
- the filename grammar that makes every file self-describing;
- the JSON content of each file type, with a small required header and a large optional vocabulary;
- how files refer to one another and how references resolve;
- how versions, forks, official selections and commentary work;
- how rendered and imported media is recorded;
- how the project maps to a timeline for an editing application;
- how the format extends through hooks and plugins;
- how it maps to and from existing standards.

It does not define any application, user interface, storage service, or rendering model. Application
conveniences — caches, indexes, autosave, job queues — never become mandatory project files.

### 1.3 Audiences

- **Filmmakers**, who may use an application or read and edit the files themselves, and must never be
  required to do the latter.
- **Developers**, who should be able to understand the entire format in an afternoon.
- **Automated systems** — scripts, pipelines and AI agents — that read and write projects directly.

### 1.4 Conventions

The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT** and **MAY** are to be interpreted as
described in RFC 2119. A **reader** is any software or person consuming a project. A **writer** is any
software or person producing or modifying one.

Requirements define syntax and how a reader interprets data. They cannot stop anyone editing a file.
Authorship and review conventions are human governance, not security features (§2, principle 8).

All examples are illustrative; some omit optional content. Field names in `monospace` are normative;
the values shown are not. Model and platform names in examples do not promise any provider's current
capabilities.

---

## 2. Design principles

1. **Plain JSON, complete documents.** Every file fully describes one thing. There are no deltas,
   overlays, inheritance, reserved key prefixes, canonical serialisation rules, or content-hash
   addressing of JSON. Any JSON tool works.
2. **One writer per file.** The author's handle is in the filename, and by convention nobody else edits
   that file. This reduces shared-folder conflicts and makes comparison meaningful. A
   delegated edit to someone else's file is still a valid file; a reader warns, and the maintainer
   decides.
3. **Self-describing filenames.** A filename states what a file is, whose it is, and which version.
   Directories organise; they carry no meaning.
4. **Fork is copy.** Versions are whole files: `v1`, `v2`, `v3`. Forking copies a file into your name.
   Files are edited in place; a new version is a deliberate act, not a side effect of saving.
5. **Official is a pointer.** A small `_official.json` file names the version the project has chosen.
   It neither locks that file nor authenticates its author.
6. **Reference within, copy across.** Inside one author's work, files point at each other. Across
   authors, content is copied. Media is always referenced, never duplicated.
7. **Media sits beside the JSON**, named so that any generated file traces back to its source. Imported
   media may keep its own name and is referenced by path.
8. **Warn, don't enforce.** Every file is editable text. Conventions are checked by readers and
   reported; nothing is locked. Whoever gatekeeps a shared repository makes the final call.

**Git is optional.** A project is a folder — on a disk, on a phone, or in a synced Drive or Dropbox
folder. Principles 2 and 3 reduce conflicts; simultaneous edits can still conflict. Git is layered on when a team wants
history or pull requests; **history belongs to Git**, and the format does not duplicate it.

---

## 3. Concepts and terminology

| Term | Meaning |
|---|---|
| **project** | A folder holding a film, series or franchise and its alternatives. An authored `pj_` file at its root describes one complete project version. |
| **author** | A handle naming the writer of a file. A person (`maria`) or a workspace or plugin run within a person's identity (`maria.locale-th`). |
| **owner** | The person behind an author handle: its first segment. |
| **entity** | Anything described by one JSON file: character, location, scene, shot, model, … |
| **library entity** | A reusable thing with an **epoch** axis: character, outfit, location, prop, style, misc, document. |
| **epoch** | How a library entity appears at a point in the story: `30yo`, `pre-war`, `1990s`, `wrecked`. Declared in the project file, at least one; the first is the default epoch. Each epoch of an entity is a separate, complete file that shares the entity's tag. |
| **story unit** | A level of the script: season or installment, episode or act, sequence, scene. |
| **block** | One unit of script inside a scene: a line of dialogue, an action, a title. |
| **shot** | A visual generation spec covering one or more blocks. |
| **cue** | An audio generation spec: dialogue, narration, music, sound effect, ambience. |
| **take** | One rendered or imported output of a shot, cue or library entity. A media file. |
| **render batch** | The record of how a set of takes was produced, imported, or generated by a plugin. |
| **media reference** | A take stem, a project-relative path, or a URL (§6.3). |
| **version** | A whole file, `v1`, `v2`, … The highest is the author's working copy. |
| **official** | The version the project has chosen for an entity, named by a pointer file. |
| **prefer**, **pick** | A version one author would choose, recorded in their commentary. An opinion, not a decision. |
| **picks** | Inside an entity, which take(s) its author chose. |
| **commentary** | An author's likes, ratings, comments and preferences about an entity. |
| **placement** | Where a shot or cue sits on the timeline: track, anchor, offset, trim. |
| **hook** | A short JavaScript function inside a model, platform, style or project file. |
| **plugin** | A packaged JavaScript extension that transforms, analyses, imports or exports. |
| **workspace** | A derived author handle (`john123.wild`) that keeps a body of work separate from the owner's main versions. Plugin runs are workspaces. A filename convention, nothing more. |

---

## 4. Project structure

### 4.1 The folder

A project folder contains one project tag and one or more `pj_<tag>_<author>_v<n>.json` files at its
root. They are complete alternatives of that project, with an optional `pj_<tag>_official.json` pointer.
Other entity files may be organised in subfolders. External media may be referenced by URL.

Opening an explicit project file selects that version. When opening only a folder, a reader uses its
project official pointer if present; otherwise it uses the highest version if there is exactly one
project author handle. If several handles have project files, the reader asks which one to open (or a
command-line caller supplies the filename), rather than guessing. An invalid official pointer is
reported. The selected project supplies contributors, defaults, language, tracks and story roots for
that view or export. Remembering that selection is an application preference, not a required film file.

A separate film with another project tag belongs in its own folder. A full entity stem is unique within the project;
readers report duplicates rather than choosing the first file found.

Creative content and decisions live in JSON. Images, audio, video, fonts and LUTs are external files
beside it. Plugin code is JavaScript. None of these is embedded into JSON to make the project "text".

### 4.2 Reserved folders

| Folder | Purpose |
|---|---|
| `render/` | Render batch records, and emitted workflows for offline rendering. |
| `conform/` | Stable-name media slots for the editing application (§15). Generated; may be deleted and regenerated. |
| `_inbox/` | Where people put material awaiting assignment. |

All other folder names are free. A reader **MUST NOT** move, rename or delete files it does not
understand; it reports them. Application caches, thumbnails and indexes may live wherever the application
chooses and are never the only copy of creative content.

### 4.3 Storage and collaboration

| Where the folder lives | Offline | Collaboration |
|---|---|---|
| Local disk, desktop or phone | yes | no |
| Drive / Dropbox / iCloud with offline sync | yes | yes — shared folder |
| Local folder that syncs to a cloud folder | yes | yes — also the desktop-to-phone path |
| Git repository | yes | yes — with history and review |

Because every file has one writer, a shared sync folder rarely sees a genuine conflict. The only files
with a shared-write role are `_official.json` files, normally written by directors (§8.3). Project
versions themselves follow the same author convention as other entities.

The remaining case is the same author editing the same file on two devices while both are offline. Sync
clients surface this as a sibling file (`… (conflicted copy)`, `… (1)`). A reader **SHOULD** recognise
such a sibling of its author's own file and present the two for comparison (§13.3); it **MUST NOT**
delete either silently.

Large media may live in the folder, in Git LFS, or in external storage reached by URL (§6.3). A clone
holding only LFS pointers does not yet hold the video; a reader shows unavailable media clearly and lets
the user fetch or relink. Keep API keys and machine-specific paths out of committed project data.

### 4.4 The project manifest and the media root

Every other file in a project is named by its author and version, because it is somebody's creative
work. One file is not: the file that says "this folder is a FilmOpen project" and records the settings
that belong to the folder itself. It has a fixed name so that any reader can recognise a project without
scanning, and so that a project folder — which normally carries the film's short name — is identified
by its content and not by its name:

```
filmopen-project.json
```

```json
{
  "filmopen": 1,
  "tag": "cartographer",
  "data": { "type": "local", "path": "../the-cartographer-data" }
}
```

| Field | Required | Meaning |
|---|---|---|
| `filmopen` | yes | The format version, as in every header (§7). |
| `tag` | yes | The project tag. The folder's `pj_<tag>_…` files carry the same tag; a reader reports a disagreement. |
| `data` | no | Where rendered and imported media lives — the **media root**. Absent, the project folder is its own media root. |
| `library` | no | Reserved: a locator for the shared library the project was seeded from (§14.5). Readers ignore it in this version. |
| `note` | no | Free text. |

The manifest is **not** an entity: it has no author, no version and no official pointer. It is
shared-write in the way `_official.json` files are (§4.3) and changes rarely — normally only when the
project is created or its media is moved. It **MUST NOT** hold credentials, API keys or anything
specific to one machine (§4.3); a locator names *where*, never *how to log in*.

**Locators.** `data` is an object, not a path string, because the media may live somewhere that is not
a folder on this machine:

| `type` | `path` means | Reader support in this version |
|---|---|---|
| `local` | a folder path with `/` separators, relative to the project folder (`../the-cartographer-data`, `media`) or absolute | **MUST** support. Relative paths are portable and are what a writer **SHOULD** write; an absolute path is machine-specific and is reported. |
| `dropbox` | a path inside the user's Dropbox | MAY support; otherwise report the media root as unavailable. |
| `google-drive` | a path inside the user's Google Drive | as above |
| `onedrive` | a path inside the user's OneDrive | as above |
| `icloud` | a path inside iCloud Drive | as above |
| `url` | a base URL; media references are appended to it | MAY support |

A reader that does not support a locator type **MUST** still open the project: the JSON is complete
without the media, and unavailable media is shown as unavailable (§4.3), never as an error that hides
the film. An application MAY remember a per-machine substitute for a locator it cannot reach; that
memory is an application preference, not part of the project.

**A project without a manifest** is still a project (readers **SHOULD** warn, and continue, treating
the folder as its own media root), so that a folder hand-assembled from files, or one written by a 1.3
writer, opens. A writer that creates a project **MUST** write the manifest.

**Why a separate media root.** The JSON of a whole feature is a few megabytes and diffs cleanly; its
renders are gigabytes and do not. Keeping them apart lets the JSON live in Git while the media lives in
a synced folder, and lets a collaborator clone the film's text without its pictures. The media root is a
plain folder: files in it are named exactly as they would be inside the project folder (§5.4), so
moving media between the two changes nothing but the manifest.

---

## 5. Naming

### 5.1 Case and character set

Filenames are **lowercase ASCII**. macOS and Windows filesystems are case-insensitive by default; `r8Af0`
and `r8af0` are one file there and two files on Linux. Lowercase everywhere removes that class of bug.

### 5.2 Tokens

| Token | Rule | Examples |
|---|---|---|
| `type` | two lowercase letters (§5.3) | `ch` `lo` `sc` |
| `segment` | `[a-z0-9][a-z0-9-]{0,19}` — at most 20 characters; readers **SHOULD** warn above 10 | `main-hero` `cafe` `seedance25` |
| `tag` | one or more segments joined by `.`; a dot expresses containment | `main-hero` `main-hero.suit` `5.1` `e2.1` |
| `epoch` | one segment; reserved `default` | `30yo` `pre-war` `1990s` |
| `author` | `<owner>[.<workspace>]`; each a segment; reserved owner `official` | `john123` `maria.locale-th` `paul.wild` |
| `v<n>` | version, integer ≥ 1 | `v1` `v2` |
| `r<id>` | render batch: `r` + a segment, unique **per author**. Generated ids are four lowercase hex digits; people may name batches | `r8af0` `rstage0` |
| `<n>` | take number for one source and deliverable within a batch, integer ≥ 1 | `1` `2` |

The 20-character limit applies to every segment. Readers **SHOULD** report filesystem path-length limits
before writing rather than shorten names. Tags are unique **within a type**; the authors, epochs and
versions of one entity deliberately share its tag.

### 5.3 Entity types

| Prefix | Entity | Grammar | Epoch |
|---|---|---|---|
| `pj` | project | spec; versions at the project root | — |
| `ch` | character | library | yes |
| `of` | outfit | library; tag `<character>.<outfit>` | yes |
| `lo` | location | library; sub-areas as `<location>.<area>` | yes |
| `pr` | prop | library | yes |
| `st` | style | library | yes |
| `mi` | misc — fonts, logos, title cards, licensed audio, reference files | library | yes |
| `dc` | document — treatment, series bible, pitch, research | library | yes |
| `mo` | model — a generative or analysis model | spec | — |
| `pl` | platform — where models run | spec | — |
| `pg` | plugin manifest | spec | — |
| `se` | season / installment | spec | — |
| `ep` | episode / act / arc | spec | — |
| `sq` | sequence | spec | — |
| `sc` | scene — contains the blocks | spec | — |
| `sh` | shot; tag `<scene>.<n>` | spec | — |
| `cu` | cue; tag `<unit>.<n>` where unit is a scene, sequence or episode | spec | — |
| `cm` | commentary | `cm_<type>_<tag>_<author>.json` | — |
| `rd` | render batch | `rd_<id>_<author>.json` | — |

### 5.4 Grammars

```
library entity     <type>_<tag>_<epoch>_<author>_v<n>.json
spec entity        <type>_<tag>_<author>_v<n>.json
official pointer   <type>_<tag>[_<epoch>]_official.json
commentary         cm_<type>_<tag>_<author>.json
render batch       rd_<id>_<author>.json
take (output)      <deliverable>_<source stem>_r<id>_<renderer>_<n>.<ext>
```

A parser splits on `_`, reads the first token, and dispatches. A take's filename embeds the **entire
stem of its source file** and the **batch and renderer that produced it**, so a generated file traces to
its origin and to `rd_<id>_<renderer>.json` with no lookup. Imported media may instead keep its own name
and be referenced by path (§6.3).

### 5.5 Deliverable prefixes

| Prefix | Deliverable | From |
|---|---|---|
| `cs` | concept sheet — character, location, prop, outfit, style board | `ch` `lo` `pr` `of` `st` |
| `sb` | storyboard frame — sketch-quality | `sh` |
| `ff` | first frame | `sh` |
| `lf` | last frame | `sh` |
| `kf` | keyframe — an intermediate frame | `sh` |
| `cl` | clip — video, with or without sound | `sh` |
| `vo` | voice — dialogue, narration | `cu` |
| `mu` | music | `cu` |
| `sx` | sound effect | `cu` |
| `am` | ambience, room tone | `cu` |

### 5.6 Workspaces and derived handles

An author handle may carry a second segment after a dot: `john123.locale-th`. The owner is the first
segment. Derived handles are used for:

- **Plugin runs.** A plugin invoked by `john123` writes under `john123.<plugin>[-<qualifier>]`, e.g.
  `john123.locale-th`. Everything one run produces starts at `v1`; a second run is `v2`. A run is
  therefore one glob: `*_john123.locale-th_v1.*`. The batch record lists the files (§12.4).
- **Personal workspaces.** `john123.wild` keeps an experiment from inflating the owner's main version
  numbers.

A reader treats a derived handle as a distinct author for versioning and as the owner's for warnings.
Derived handles are not listed in the project file.

### 5.7 Examples

```
pj_cartographer_john123_v1.json           John's project version
pj_cartographer_suda_v1.json              Suda's remake; its own language and story roots
pj_cartographer_official.json            → the chosen project version
ch_main-hero_30yo_john123_v2.json          John's second version of the 30-year-old main hero
ch_main-hero_30yo_maria_v1.json            Maria's fork of it
ch_main-hero_30yo_official.json            → names one of the above
ch_main-hero_50yo_maria_v1.json            a different epoch: a distinct file sharing the tag
of_main-hero.suit_30yo_john123_v1.json     an outfit belonging to main-hero
lo_cafe_1990s_john123_v1.json              the cafe as it looked in the 1990s
lo_cafe.kitchen_1990s_john123_v1.json      a sub-area of the cafe
pr_car_wrecked_susan_v1.json               the car, wrecked
st_war-grade_default_maria_v1.json         a look
dc_bible_default_john123_v3.json           the series bible
mo_seedance25_paul_v3.json                 a model, from the shared library
pl_fal_paul_v1.json                        a platform
pg_locale_paul_v2.json                     a plugin manifest; its code is pg_locale_paul_v2.js beside it
se_s1_john123_v1.json                      season 1
ep_e2_john123_v1.json                      episode 2
sq_e2.arrival_john123_v1.json              a sequence within episode 2
sc_5_john123_v1.json                       scene 5 and its blocks
sc_5_john123.locale-th_v1.json             scene 5 as produced by a localisation plugin John ran
sh_5.1_john123_v1.json                     shot 1 of scene 5
cu_5.1_john123_v1.json                     cue 1 of scene 5
cu_e2.1_maria_v1.json                      a cue spanning episode 2 — score
cm_ch_main-hero_maria.json                 Maria's commentary on main-hero
rd_8af0_maria.json                         Maria's render batch 8af0

cs_ch_main-hero_30yo_john123_v2_r8af0_maria_1.png   concept sheet, take 1, Maria's render of John's v2
ff_sh_5.1_john123_v1_r8af0_maria_2.png              first frame, take 2, of shot 5.1
cl_sh_5.1_john123_v1_r8af0_maria_1.mp4              clip, take 1
vo_cu_5.1_john123_v1_r8af0_maria_1.wav              voice, take 1
ff_sh_5.1_john123_v1_r0001_john123_1.png            a frame John extracted from an existing film
references/kira/front.png                           an imported reference, kept under its own name
```

---

## 6. References and resolution

Entities point at one another constantly. Three forms exist: short references and full references name
entities; media references name files.

### 6.1 Short reference

A tag: `"location": "cafe"`. The field determines the type. Resolved by the reader in three steps.

**Step 1 — Epoch.** Use the explicit epoch on the referencing entry if present; else the enclosing
scene's epoch; else the enclosing unit's; else `default`. An outfit takes its character's resolved epoch
unless one is given. If the epoch was chosen **implicitly** and the entity has no file for it, fall back to
`default` and show that fallback. If the epoch was requested **explicitly** and does not exist, the
reference is unresolved: report it; never substitute a different age or state silently.

**Step 2 — Author.** Depends on the *referencing* file's status:

> A file that is **not** named by an `_official.json` resolves **author-first**: the referencing file's
> author's highest version at that epoch; then, for a derived handle, the owner's; then official.
>
> A file that **is** named by an `_official.json` resolves through **official only**.

In plain words: *a proposal sees its author's world; official sees the official world.* Maria's fork of a
scene previews with Maria's fork of the cafe. A localisation plugin's output is self-consistent the
moment it is written. Once the director makes Maria's scene official, it stops picking up her later
experiments.

**Step 3 — Fallback.** If nothing is official and the selected project has a single contributor owner, use that owner's
highest version. Otherwise the reader **MUST** report an unresolved reference rather than guess.

Project-root references use the selected `pj` file as the referencing file, with its own author and
official status. Subsequent references use the file containing them. The selected project remains the
context for defaults and contributors; a full reference bypasses lookup.

This is a lookup over the current folder. Changing an official pointer or editing a referenced file
changes what the project uses; readers **SHOULD** show the resulting selections when previewing a
promotion. Historical states come from Git.

### 6.2 Full reference

A file stem: `"scene": "sc_5_john123_v1"`. Exact; no resolution. It names an editable file, not frozen
content.

### 6.3 Media reference

One of:

- a **take stem** — `cl_sh_5.1_john123_v1_r8af0_maria_1` — resolved to the unique file with that
  basename, or to a batch item naming it;
- a **media path** including extension — `references/kira/front.png` — using `/`, relative to the
  project's media root (§4.4: the folder the manifest's `data` names, else the project folder itself),
  never to the referencing file;
- an **external URL** — `https://…` — naming bytes outside the folder. Expiring signed URLs and
  credentials are not locators.

Generated takes carry their source, batch and renderer in the name. Imported and hand-supplied media keep
their own names and are referenced by path; a batch item (§12) may describe them. A plain reference to a
PNG or WAV is sufficient; no registry entry is required.

### 6.4 Where each is used

- Project story roots and story-unit child lists accept tags or full references; the list key
  determines the type (`seasons` → `se`, `episodes` → `ep`, `sequences` → `sq`, `scenes` → `sc`).
- Story units, scenes, shots and cues use **short references** to library entities. A scene's `props`
  may be tags or `{ "prop": "car", "epoch": "wrecked" }`; a scene may set `locationEpoch`.
- A shot or cue **MAY** carry a full reference to the scene version its block ids belong to. Absent, it
  means the resolved scene of that tag.
- `forkedFrom`, `prefer` and `on` use **full references** or, for `on`, media references.
- `picks`, `refs`, `preview`, `lut`, `file` and deliverable inputs use **media references**.

---

## 7. Common header

Every entity file begins with the same fields. They **repeat the filename** deliberately: the file stays
self-describing if renamed, and a reader can flag disagreement. Project versions use this common header without an epoch. Official pointers, commentary and render
batches use the headers shown in their own sections.

```json
{
  "filmopen": 1,
  "type": "ch",
  "tag": "main-hero",
  "epoch": "30yo",
  "author": "john123",
  "v": 2,
  "name": "Kira Voss",
  "forkedFrom": "ch_main-hero_30yo_john123_v1",
  "created": "2026-09-05T18:04:11Z",
  "updated": "2026-09-06T09:40:00Z"
}
```

| Field | Required | Meaning |
|---|---|---|
| `filmopen` | yes | format version, integer |
| `type` `tag` `author` `v` | yes | as in the filename |
| `epoch` | library entities | as in the filename |
| `name` | yes | display name, any language |
| `label` | no | a short word for this version in version lists — `goth`, `classic`. Not copied by a fork |
| `created` `updated` | no | RFC 3339 UTC. Informational; never used for precedence |
| `forkedFrom` | no | stem this file was copied from. A note; nothing resolves through it |
| `by` | no | plugin stem, when a plugin produced this version |
| `source` | no | origin of imported material: `{ "batch": "rd_0001_john123" }` |
| `license` | no | SPDX identifier or CC URL for this entity's content, if it differs from the project's |
| `notes` | no | free text for people; excluded from prompts unless a user explicitly includes it |
| `x` | no | an object for application-specific data. Readers preserve it; the format ignores it |

---

## 8. The project file

```json
{
  "filmopen": 1, "type": "pj", "tag": "cartographer", "author": "john123", "v": 1,
  "name": "The Cartographer",
  "kind": "series",
  "genre": ["drama", "mystery"],
  "year": 2026,
  "logline": "A mapmaker discovers her charts are rewriting the coastline.",
  "synopsis": "…",
  "lang": "pt-BR",
  "rating": "12",
  "format": { "aspect": "2.39:1", "fps": 24, "resolution": "1920x804", "color": "rec709" },
  "runtimeMin": 45,
  "credits": [
    { "role": "writer", "name": "João Silva", "handle": "john123" },
    { "role": "director", "name": "João Silva", "handle": "john123" }
  ],
  "epochs": {
    "default":  { "label": "Present day",    "order": 1, "year": 2026 },
    "pre-war":  { "label": "Before the war", "order": 2, "year": 1938 },
    "mid-war":  { "label": "During the war", "order": 3, "year": 1943 },
    "post-war": { "label": "After the war",  "order": 4, "year": 1947 }
  },
  "contributors": {
    "john123": { "name": "João Silva", "role": "director" },
    "maria":   { "name": "Maria Costa" },
    "susan":   { "name": "Susan Park" }
  },
  "tracks": [
    { "id": "pic",  "kind": "av",    "name": "Picture" },
    { "id": "pic2", "kind": "av",    "name": "Picture 2" },
    { "id": "dx",   "kind": "audio", "name": "Dialogue" },
    { "id": "vo",   "kind": "audio", "name": "Narration" },
    { "id": "sfx",  "kind": "audio", "name": "SFX" },
    { "id": "amb",  "kind": "audio", "name": "Ambience" },
    { "id": "mx",   "kind": "audio", "name": "Music" }
  ],
  "seasons": ["s1"],
  "license": "CC-BY-SA-4.0",
  "hooks": { "validate": "(ctx) => ctx.entity.type !== 'sc' || ctx.entity.blocks.length > 0 || 'empty scene'" }
}
```

### 8.1 Kind

`kind` ∈ `short` · `feature` · `miniseries` · `series`. It sets the labels of the story levels (§10.1)
— a mini-series and a series have seasons and episodes, a short and a feature have installments and
acts — and the story-root list an application starts a blank project with (`scenes`, `sequences`,
`episodes`, `seasons` respectively). Nothing else depends on it. Readers treat the 1.4 values `film`
as `feature` and `franchise` as `series`.

### 8.2 Epochs

Every project declares its epochs here, **at least one**, each with an optional `label`, `order` and
`year`. Readers order them by `order` (an entry without one comes first, in the file's order); a writer
that reorders them renumbers `order` from 1. The **first** epoch is the project's **default epoch**:
the one an implicit reference resolves to (§6.1) and the one a new library entity is created at. The
reserved token `default` may be used as an epoch name and is what a new project starts with.

Every library entity has an epoch — there is no "base" file outside the epoch axis. An epoch variant is a
**separate, complete file** that shares the entity's tag and is treated as a distinct entity: nothing
propagates between epochs. A scene names an epoch and every library entity in it resolves to the
matching file (§6.1). Comparing two epochs side by side and copying attributes across (§13.3) is the
intended way to keep them consistent. `year`, when given, is the calendar year the epoch stands for;
readers use it to propose a character's age at that epoch (§9.1). An epoch token appears in filenames,
so an application does not rename one; it adds a new epoch and the author forks files into it.

Readers **SHOULD** warn when a project file declares no epochs.

### 8.3 Contributors and directors

`contributors` lists the people on the project by owner handle. `role: "director"` designates who
normally writes `_official.json` files. More than one director is allowed. Whoever creates a project
is its first director: a writer that creates a project lists the creator with that role (§18.3); how
a director appoints further directors is the project's own business, edited in this file. Handles are names, not
identities: a file can be created under any handle, and a commit's actor and a filename's author are
different information a maintainer can compare. Readers **SHOULD** warn, and continue, when: a file is
modified by someone other than its author; an `_official.json` is written by a non-director; a commit
or sync introduces a file under a handle other than the current user's. The repository maintainer
decides.

### 8.4 Tracks

The timeline tracks the project uses, in order, each with a kind and a display name. Shots are placed on
`av` tracks; cues on `audio` tracks; a `video` track holds picture with no sound. The list above is the
recommended default; a project may rename, reorder, add or remove. Placement (§11.3) refers to tracks by
`id`; the editing application sees `name`. Track *numbers* are an export artefact (§15.1) and appear
nowhere in a project.

### 8.5 Format and story roots

`format` describes the delivery target. `fps` is an integer for integer rates, or
`{ "num": 24000, "den": 1001 }` for fractional rates — never a rounded decimal. `resolution`, `aspect`
and `color` are strings; a project **MAY** add `width`, `height`, `pixelAspect`, colour primaries and
transfer, audio `sampleRate` and `channels`.

The project lists its **story roots** in order with exactly one of `seasons`, `episodes`, `sequences` or
`scenes`, whichever fits its structure. A short film can start with `"scenes": ["1", "2"]` and needs no
season or episode scaffolding. Entries are tags or full references of the indicated type. An empty list is valid while starting from
scratch. Order comes from these lists, not from sorting filenames.

### 8.6 Project alternatives and remakes

A project file is an ordinary spec entity: copy it into your author handle, set `forkedFrom`, and edit
its complete content. For example, `pj_cartographer_suda_v1.json` can name
`pj_cartographer_john123_v1` in `forkedFrom`, change `lang` to `th-TH`, and change its display name,
credits and story roots. The tag remains `cartographer` so these are alternatives of the same project.
No settings are inherited from `forkedFrom`.

Copying a project file neither copies nor translates all its descendants. Existing media references can
be reused. Full references in the copied root list can keep existing story units selected while the
remake is developed; switching those references or using author-first tags selects new story versions
when desired. A project language is a default, not an instruction to translate existing script text.
Users may copy other entities selectively, run a localisation plugin, or fork the Git repository.

---

## 9. Library entities

A library entity is reusable across the story and has an epoch axis. Every one has the common header
and may carry `refs`, `preview`, `picks` and a `prompt` object. The required content is small; Appendix A
lists the recommended optional vocabulary. An entity with no references yet is valid.

### 9.0 References, picks and previews

`refs` holds the media a model is shown when generating this entity. It is either a plain array of image
references, or an object of **named groups**:

```json
{
  "refs": {
    "sheets":      ["cs_ch_main-hero_30yo_john123_v2_r8af0_maria_1"],
    "turnarounds": ["references/kira/turnaround.png"],
    "expressions": ["references/kira/expressions.png"]
  },
  "voice": {
    "description": "low, unhurried, slight rasp",
    "refs": ["references/kira/voice-calm.wav", "references/kira/voice-raised.wav"]
  },
  "preview": "references/kira/front.png"
}
```

Recommended group names — any may be used on any entity, and others may be added: `images` (generic),
`sheets`, `turnarounds`, `expressions`, `poses`, `details`, `materials`, `floorPlans`, `panoramas`,
`lighting`, `motion`, `sound`. Audio samples for a character's voice live in `voice.refs`; an outfit has
no voice. Every group is an array. Each epoch file has its own groups.

`preview` names the media shown for this entity in a library view. Absent, a reader may use the first
sheet or image, then an image pick, then a placeholder. A preview is not automatically a model input.

`picks` selects generated takes by deliverable; a value is one media reference or an array when several
frames are chosen. A reference used in several files reuses the bytes and nothing else.

### 9.1 Character `ch`

```json
{
  "filmopen": 1, "type": "ch", "tag": "main-hero", "epoch": "30yo", "author": "john123", "v": 2,
  "name": "Kira Voss",
  "kind": "principal",
  "aliases": ["KIRA", "the cartographer"],
  "summary": "A cartographer who discovers her maps are rewriting the coastline.",
  "appearance": {
    "age": 30, "gender": "woman", "build": "lean, athletic", "skin": "olive",
    "hair": { "color": "black", "length": "shoulder", "style": "loose, centre-parted" },
    "eyes": { "color": "dark brown", "notes": "heavy lids" },
    "marks": ["burn scar along the left forearm"],
    "posture": "upright, still; moves only when necessary"
  },
  "personality": {
    "summary": "Guarded, precise, allergic to being managed.",
    "traits": ["methodical", "dry humour", "conflict-avoidant until cornered"],
    "wants": "To prove the coastline is moving.",
    "speech": "Short sentences. Rarely finishes a question."
  },
  "voice": { "description": "low, unhurried, slight rasp", "pitch": "low", "pace": "slow", "accent": "Lisbon", "lang": "pt-BR",
             "refs": ["references/kira/voice-calm.wav"] },
  "relationships": [{ "character": "spouse", "relation": "married to, separating" }],
  "prompt": {
    "positive": "30yo woman, olive skin, black shoulder-length hair, lean athletic build, burn scar left forearm, upright posture",
    "negative": "cartoon, plastic skin, over-smoothed"
  },
  "creatorNotes": "Never smiles with teeth. Keep the scar visible in any shot showing the left arm.",
  "picks": { "sheet": "cs_ch_main-hero_30yo_john123_v2_r8af0_maria_1" },
  "refs": { "sheets": ["cs_ch_main-hero_30yo_john123_v2_r8af0_maria_1"] },
  "preview": "cs_ch_main-hero_30yo_john123_v2_r8af0_maria_1"
}
```

- `kind` ∈ `principal` · `supporting` · `extra` · `group` · `narrator` · `creature` · `animal`. A `group`
  is a crowd described once.
- `birthYear` — the character's year of birth, an integer, the same in every epoch file of the
  character. When the project's epoch has a `year` (§8.2), a reader proposes
  `appearance.age = epoch.year − birthYear` for a new epoch file, and the author may override it: a
  character born in 1950 is 50 at an epoch set in 2000 unless the file says 30.
- `aliases` — every name a script or transcript might use. Essential for import and for Fountain export.
- `appearance`, `personality`, `voice` — **open objects**. Readers display whatever keys are present.
  Appendix A.1 recommends a vocabulary; none of it is required.
- `creatorNotes` — for people; excluded from prompts unless a user explicitly includes it.

### 9.2 Outfit `of`

```json
{
  "filmopen": 1, "type": "of", "tag": "main-hero.suit", "epoch": "30yo", "author": "john123", "v": 1,
  "name": "Charcoal business suit",
  "garments": [
    { "item": "suit jacket", "color": "charcoal", "material": "wool", "fit": "tailored", "condition": "worn at the cuffs" },
    { "item": "shirt", "color": "white", "notes": "open collar, no tie" },
    { "item": "boots", "color": "brown", "material": "leather", "condition": "scuffed" }
  ],
  "accessories": ["steel wristwatch, left wrist"],
  "prompt": { "positive": "charcoal two-button suit, white open-collar shirt, worn brown leather boots, steel wristwatch" },
  "picks": { "sheet": "cs_of_main-hero.suit_30yo_john123_v1_r7c21_john123_1" }
}
```

### 9.3 Location `lo`

```json
{
  "filmopen": 1, "type": "lo", "tag": "cafe", "epoch": "1990s", "author": "john123", "v": 1,
  "name": "Café Atlântico",
  "aliases": ["CAFE", "the café"],
  "kind": "interior",
  "geography": { "city": "Lisbon", "country": "PT" },
  "description": "Tiled floor, ceiling fans, a long zinc bar, harbour visible through steamed windows.",
  "dressing": ["zinc bar", "six marble tables", "espresso machine, chrome", "framed harbour photographs"],
  "lighting": { "default": "late afternoon, warm, low sun through the windows", "practicals": ["pendant lamps over the bar"] },
  "sound": { "roomTone": "tiled, lively reverb", "ambience": "espresso machine, harbour gulls, distant traffic" },
  "areas": ["cafe.bar", "cafe.kitchen"],
  "prompt": { "positive": "1990s Lisbon café interior, tiled floor, zinc bar, ceiling fans, steamed windows, harbour beyond" },
  "picks": { "sheet": "cs_lo_cafe_1990s_john123_v1_r7c21_john123_2" }
}
```

`kind` ∈ `interior` · `exterior` · `both`. Sub-areas are locations with tag `<parent>.<area>` and their
own epoch files.

### 9.4 Prop `pr`

```json
{
  "filmopen": 1, "type": "pr", "tag": "chart", "epoch": "default", "author": "john123", "v": 1,
  "name": "The 1994 harbour chart",
  "kind": "document",
  "hero": true,
  "description": "Hand-drawn nautical chart on yellowed linen-backed paper, folded twice, ink faded to sepia.",
  "scale": "60 × 90 cm",
  "condition": "creased, one corner water-stained",
  "prompt": { "positive": "hand-drawn nautical chart, yellowed linen paper, sepia ink, fold creases, water stain" },
  "picks": { "sheet": "cs_pr_chart_default_john123_v1_r7c21_john123_4" }
}
```

`hero: true` marks a prop the camera will see closely; it needs a stronger reference than background
dressing.

### 9.5 Style `st`

```json
{
  "filmopen": 1, "type": "st", "tag": "war-grade", "epoch": "default", "author": "maria", "v": 1,
  "name": "War — gritty, desaturated",
  "description": "Episode 2 onward. Cold, grey, high contrast, grain.",
  "genre": "drama",
  "references": ["1970s political thrillers", "hand-processed 16mm"],
  "look": { "stock": "16mm colour negative", "grain": "medium", "halation": "slight", "contrast": "high", "saturation": "low" },
  "palette": ["#2b2f33", "#6b7076", "#b9bcb8", "#d9c9a3"],
  "prompt": { "positive": "desaturated, cold grey palette, high contrast, 16mm grain, handheld",
              "negative": "saturated, clean, glossy" },
  "cdl": { "slope": [0.95, 0.98, 1.05], "offset": [-0.02, -0.02, 0.0], "power": [1.1, 1.1, 1.05], "saturation": 0.6 },
  "lut": "style/war-grade/war-grade.cube",
  "refs": { "images": ["cs_st_war-grade_default_maria_v1_r8af0_maria_1"] }
}
```

`cdl` records ASC CDL values; `lut` is a media reference to a `.cube` file. Both record colour intent
and export with the timeline; whether an editing application applies them depends on its importer, and
an exporter reports what it could not carry (§15.3).

### 9.6 Misc `mi` and Document `dc`

```json
{ "filmopen": 1, "type": "mi", "tag": "title-font", "epoch": "default", "author": "john123", "v": 1,
  "name": "Title font", "kind": "font", "file": "misc/atlantico-bold.otf" }
```

```json
{ "filmopen": 1, "type": "dc", "tag": "treatment", "epoch": "default", "author": "john123", "v": 2,
  "name": "Treatment", "kind": "treatment",
  "text": "Kira Voss draws maps for a living and discovers that her maps are redrawing the coast…",
  "file": "docs/treatment-2026-08.docx" }
```

`mi.kind` ∈ `font` · `logo` · `title-card` · `audio` · `image` · `other`. `dc.kind` ∈ `treatment` · `bible`
· `pitch` · `beat-sheet` · `research` · `context` · `other`. For a document, `text` is the editable
FilmOpen content and survives a JSON-only checkout; `file` is an imported source or export. Either may be
present alone.

---

## 10. Story structure and script

### 10.1 Story units `se` `ep` `sq` `sc`

| Level | Series | Film / franchise | File |
|---|---|---|---|
| 1 | season | installment | `se_` |
| 2 | episode | act / arc | `ep_` |
| 3 | sequence *(optional)* | sequence *(optional)* | `sq_` |
| 4 | scene | scene | `sc_` |

Levels 1–3 are optional groupings. Each declares epoch, cast, locations and styles as **defaults for its
children**, and an ordered list of those children. A scene narrows or overrides what it inherits from
its unit; nothing is inherited between library files.

```json
{
  "filmopen": 1, "type": "ep", "tag": "e2", "author": "john123", "v": 1,
  "name": "Episode 2 — Low Tide",
  "synopsis": "Kira finds the first altered chart.",
  "epoch": "mid-war",
  "cast": [
    { "character": "main-hero", "epoch": "30yo", "outfit": "main-hero.suit" },
    { "character": "spouse", "epoch": "28yo" }
  ],
  "locations": ["cafe", "archive"],
  "styles": ["war-grade"],
  "scenes": ["3", "4", "5", "archive-exit"],
  "notes": "Cold open, no score until the reveal."
}
```

`scenes` (or `sequences`, or `episodes` on a season) fixes the order of children. If absent, a reader
**MAY** propose natural (numeric-aware) tag order and **MUST** say so; a reproducible timeline export
needs an explicit list. Cyclic or duplicate containment is reported.

### 10.2 Scene `sc`

A scene is one time and one place. Its file holds the **blocks** — the script — in order.

```json
{
  "filmopen": 1, "type": "sc", "tag": "5", "author": "john123", "v": 1,
  "name": "The archive",
  "location": "archive", "epoch": "mid-war",
  "storyDay": 3, "time": "night", "weather": "rain against the skylight",
  "cast": [{ "character": "main-hero", "epoch": "30yo", "outfit": "main-hero.suit" }],
  "props": [{ "prop": "chart", "epoch": "default" }],
  "style": "war-grade",
  "synopsis": "Kira compares the two charts and realises the coastline moved.",
  "purpose": "First proof. Turns the mystery from doubt to certainty.",
  "targetDurationSec": 165,
  "shots": ["5.1", "5.2"],
  "cues": ["5.1", "5.2", "5.3"],
  "blocks": [
    { "id": "bl_1", "kind": "action",
      "text": "Kira spreads both charts across the reading table." },
    { "id": "bl_2", "kind": "dialogue", "character": "main-hero",
      "text": "The bay was here last spring.",
      "direction": "flat disbelief, does not look up" },
    { "id": "bl_3", "kind": "action",
      "text": "Behind her, the window flashes white. A second later, the glass goes." },
    { "id": "bl_4", "kind": "dialogue", "character": "main-hero", "text": "Not now.", "offscreen": true },
    { "id": "bl_5", "kind": "transition", "text": "CUT TO:" },
    { "id": "bl_6", "kind": "note", "text": "Consider cutting bl_2 if pacing drags." }
  ]
}
```

`storyDay` is the script supervisor's continuity day. `purpose` states what the scene does for the
story, which is the single most useful field for an AI reading the project. `shots` and `cues` fix the
order of the scene's shots and cues; absent, natural tag order applies. `locationEpoch` overrides the
scene epoch for the location alone.

### 10.3 Blocks

| Field | Meaning |
|---|---|
| `id` | `bl_<n>`; stable for the life of the block in this file. **Never renumbered.** |
| `kind` | `dialogue` · `action` · `narration` · `transition` · `title` · `lyric` · `note` |
| `text` | the line, the action, the title text |
| `character` | short reference; dialogue, narration and lyric |
| `direction` | performance note — the screenplay parenthetical |
| `offscreen` `voiceover` | booleans — Fountain's `(O.S.)` and `(V.O.)` |
| `dual` | `true` when spoken simultaneously with the previous dialogue block |
| `lang` | BCP 47 override for this block |
| `startMs` `endMs` | observed timing, typically from import |

Rules: new blocks take the next unused number; order is array order; moving a block to another scene
copies it there with a new id; `note` blocks never render. Because ids are stable, two authors' versions
of a scene align by id with no diff algorithm (§13.3). Two forks that independently invent the same
id are not the same line; a reader shows the text, and the Git base when available, rather than
assuming.

### 10.4 Fountain correspondence

| Fountain | FilmOpen |
|---|---|
| Scene Heading `INT. CAFE – NIGHT` | scene `location` (+ `kind`), `time` |
| Action | block `action` |
| Character + Dialogue | block `dialogue`, `character` via `aliases` |
| Parenthetical | `direction` |
| `(O.S.)` `(V.O.)` | `offscreen` `voiceover` |
| Dual dialogue `^` | `dual` |
| Transition | block `transition` |
| Centered text | block `title` |
| Lyrics `~` | block `lyric` |
| Note `[[ ]]` | block `note` |
| Section `#` | story units |
| Synopsis `=` | `synopsis` |

The screenplay *content* round-trips. Title-page fields, emphasis, forced elements, scene numbers, page
breaks and boneyards are not represented; an importer or exporter reports them, and a `dc` may keep the
original text. Shot and sound information, which Fountain cannot express, lives in shots and cues.

---

## 11. Shots and cues

### 11.1 Shot `sh`

A visual generation spec covering one or more blocks, with its placement on the timeline.

```json
{
  "filmopen": 1, "type": "sh", "tag": "5.2", "author": "john123", "v": 1,
  "name": "Kira reacts; the window goes",
  "scene": "sc_5_john123_v1",
  "blocks": ["bl_3", "bl_4"],
  "characters": ["main-hero"],
  "size": "mcu", "angle": "low", "movement": "static",
  "lens": { "focalMm": 50, "aperture": 2.0, "dof": "shallow" },
  "lighting": { "key": { "direction": "side", "quality": "hard" }, "style": "low-key", "colorTempK": 3200,
                "practicals": ["desk lamp"], "notes": "window flash as a single hard white hit from behind" },
  "composition": { "headroom": "tight", "eyeline": "down-left", "screenDirection": "facing right" },
  "durationSec": 5,
  "continuity": ["chart still unfolded on the table", "scar visible, left forearm"],
  "vfx": "glass shatter, practical-looking, no debris toward lens",
  "deliverables": {
    "firstFrame": { "model": "nanobanana", "prompt": "…", "refs": ["cs_ch_main-hero_30yo_john123_v2_r8af0_maria_1"] },
    "clip":       { "model": "seedance25", "prompt": "…", "from": "firstFrame",
                    "inputs": ["vo_cu_5.3_john123_v1_r8af0_maria_1"] }
  },
  "place": { "track": "pic", "after": "sh_5.1", "offsetMs": 0, "inMs": 0, "outMs": 5000 },
  "picks": { "firstFrame": "ff_sh_5.2_john123_v1_r8af0_maria_1",
             "clip":       "cl_sh_5.2_john123_v1_r8af0_maria_1" }
}
```

`size`, `angle`, `movement`, `lens`, `lighting` and `composition` use the vocabulary in Appendix A.4; all
are optional. `scene` is a full reference to the scene version the block ids belong to (§6.4). In a
deliverable, `from` names a preceding deliverable of the same shot and `inputs` names other media it
depends on — a chosen dialogue take for lip sync, for instance; the batch records what was actually
used. A still image placed as a shot takes its hold duration from `durationSec`.

### 11.2 Cue `cu`

An audio generation spec. A cue attaches to a scene, or to a sequence or episode when it spans scenes.

```json
{
  "filmopen": 1, "type": "cu", "tag": "5.1", "author": "john123", "v": 1,
  "name": "Kira — the bay line",
  "kind": "dialogue",
  "scene": "sc_5_john123_v1",
  "blocks": ["bl_2"],
  "character": "main-hero",
  "spoken": "The bay was here… last spring.",
  "delivery": { "effort": "quiet", "emotion": "flat disbelief", "processing": "none" },
  "model": "elevenlabs",
  "place": { "track": "dx", "with": "sh_5.1", "offsetMs": 400, "gainDb": -3 },
  "picks": { "voice": "vo_cu_5.1_john123_v1_r8af0_maria_1" }
}
```

```json
{
  "filmopen": 1, "type": "cu", "tag": "e2.1", "author": "maria", "v": 1,
  "name": "Low Tide — main theme",
  "kind": "music",
  "unit": "ep_e2_john123_v1",
  "music": { "tempoBpm": 72, "key": "D minor", "instrumentation": ["cello", "prepared piano", "tape hiss"],
             "mood": "unease held under restraint", "structure": "sparse intro, swell at the reveal, cut on the flash" },
  "sync": [{ "with": "sh_5.2", "offsetMs": 0, "event": "cut on the flash" }],
  "anchors": [{ "scene": "sc_5_john123_v1", "blocks": ["bl_3"] }],
  "model": "suno",
  "place": { "track": "mx", "with": "scene:3", "offsetMs": 0, "fadeInMs": 2000, "fadeOutMs": 4000, "gainDb": -12 },
  "picks": { "music": "mu_cu_e2.1_maria_v1_r91c0_maria_2" }
}
```

- `kind` ∈ `dialogue` · `narration` · `music` · `sfx` · `ambience`.
- **Text comes from the referenced blocks**, concatenated in order. **`spoken`, if present, replaces
  it** — for pauses, pronunciation, numbers spelled out, a translated line. Readers surface "spoken
  differs from script" as an intentional state, and warn when the block has changed since.
- `ssml` (W3C SSML) is an alternative to `spoken` for models that accept it; use one or the other.
- A cue attached to a unit that needs specific blocks from specific scenes uses `anchors`, since block
  ids are local to a scene.

### 11.3 Placement

`place` states where a shot or cue sits on the timeline. Every field is optional; a scene with no
placement at all is a straight sequential cut on the first `av` track, in the scene's `shots` order.

| Field | Meaning | Default |
|---|---|---|
| `track` | track `id` from the project (§8.4) | first `av` track for shots; `dx` for dialogue, `vo` narration, `sfx`, `amb`, `mx` music |
| `after` | anchor: start at this item's **end** plus `offsetMs`. A shot tag, or `prev` | `prev` for shots |
| `with` | anchor: start at this item's **start** plus `offsetMs`. A shot tag, `scene`, or `scene:<tag>` | for cues, the first shot covering the cue's first block |
| `offsetMs` | signed milliseconds from the anchor | `0` |
| `inMs` `outMs` | trim within the source take, from its first frame; `outMs` exclusive | whole take |
| `audio` | `false` to place picture only from an `av` clip | `true` |
| `gainDb` `pan` | mix | `0`, `0` |
| `fadeInMs` `fadeOutMs` | audio fades | `0` |
| `transitionIn` | `{ "type": "cut" \| "dissolve" \| "fade", "ms": n }` | cut |

Rules:

- `after` and `with` are the only anchors, and a placement uses **one** of them. `prev` is the previous
  shot in the scene's order, not the previous file on disk. Anchor cycles, missing anchors and negative
  starts are reported.
- **Two items on one track cannot overlap.** A negative `offsetMs` after a picture cut on the same
  track is a collision and is reported. Overlaps go on another track: a second picture track for an
  overlay, or — the common case — the dialogue track for a **J-cut**, where the next line begins before
  the picture cuts: `"with": "sh_5.2", "offsetMs": -1500` on the cue.
- A `dissolve` or `fade` needs **source handles**: media beyond the nominal trims on both sides. The
  exporter checks the picked takes and reports when handles are short; it never shortens the cut.
- Changing a duration ripples downstream through the `after` chain, as a rough assembly should.
- Timings are numbers in milliseconds. An exporter converts to the project frame rate and reports
  any rounding it applies. Equal boundaries must round identically; ties round toward the later frame.
  Source trims must also respect the source media rate and available range.

An observation at 12:04.625 in an imported film belongs in the batch's source timing (`at`).
A placement anchored to `scene` uses an offset from that scene's start, not the source film's start.
The first shot defaults to the scene start when `prev` has no predecessor. Scenes begin sequentially
in the selected story order; a scene's length is the latest end of its placed shots and scene cues.
Unit-level cues do not lengthen individual scenes. Cross-scene anchor cycles are reported.

---

## 12. Takes and render batches

### 12.1 Takes are discovered, never recorded in the entity

A take is a media file whose name embeds its source stem (§5.4), or an imported file named in a batch.
**No entity file lists its takes.** A reader finds them by scanning the media root (§4.4) — and the
project folder itself, so that a project without a manifest keeps working — and by reading batches; the batch
says how they were made; the only thing an entity's author writes about takes is `picks`. This is what
lets one person render another's shot without touching their file, and lets a character page show every
sheet anyone has rendered for it.

### 12.2 Render batch `rd`

```json
{
  "filmopen": 1, "type": "rd", "id": "8af0", "author": "maria",
  "kind": "render", "tier": "final",
  "created": "2026-09-06T15:30:00Z", "status": "done",
  "items": [
    { "n": 1, "source": "sh_5.2_john123_v1", "deliverable": "clip",
      "model": "seedance25", "platform": "fal",
      "prompt": "…the fully resolved prompt as sent…",
      "params": { "seed": 41221, "steps": 30, "durationSec": 5, "fps": 24 },
      "inputs": ["ff_sh_5.2_john123_v1_r8af0_maria_1", "vo_cu_5.3_john123_v1_r8af0_maria_1"],
      "output": "s1/e2/sc5/cl_sh_5.2_john123_v1_r8af0_maria_1.mp4",
      "sha256": "9d02…", "durationMs": 5000, "elapsedMs": 61400, "costUsd": 0.42,
      "thumbnail": "s1/e2/sc5/cl_sh_5.2_john123_v1_r8af0_maria_1.thumb.jpg",
      "human": { "selectedFrom": 4, "promptEditedBy": "maria" } }
  ]
}
```

| Field | Meaning |
|---|---|
| `kind` | `render` · `import` · `plugin` · `manual` |
| `tier` | `preview` · `final` — cheap fast previews versus locked finals |
| `output` | media reference — usually a project-relative path |
| `take` | take number in a generated filename when it is not `1` |
| `sha256` | recorded on ingest. Names carry meaning; hashes carry identity; together they survive renames, moves and duplicates |
| `durationMs` / `elapsedMs` | length of the output media / time the job took |
| `costUsd` | absent when unknown, never zero |
| `human` | the human contribution: candidates reviewed, who edited the prompt, manual edits. The authorship evidence (§16.5) |
| `status` `jobId` `attempts` `error` | job notes for a pending, failed or partial batch; a batch may be updated in place |
| `thumbnail` `poster` `proxy` `waveform` | display derivatives; never model inputs by themselves |
| `mimeType` `bytes` `width` `height` `fps` `frameCount` `sampleRate` `channels` | measured media properties; measured on ingest if absent, never invented |

### 12.3 Import batches — describing a film that exists

`kind: "import"` records material taken from an existing work. Its takes are named like any other; the
renderer is the **importing author**, so the take always finds its batch.

```json
{
  "filmopen": 1, "type": "rd", "id": "0001", "author": "john123",
  "kind": "import",
  "source": { "file": "the-cartographer-2019.mp4", "sha256": "e1a4…", "fps": 24, "durationMs": 5412000 },
  "created": "2026-09-06T09:00:00Z", "status": "done",
  "items": [
    { "n": 1, "source": "sh_5.2_john123_v1", "deliverable": "firstFrame",
      "at": { "ms": 724625 }, "output": "s1/e2/sc5/ff_sh_5.2_john123_v1_r0001_john123_1.png", "sha256": "77b0…" },
    { "n": 2, "source": "cu_5.1_john123_v1", "deliverable": "voice",
      "at": { "startMs": 724625, "endMs": 727200 }, "output": "s1/e2/sc5/vo_cu_5.1_john123_v1_r0001_john123_1.wav", "sha256": "31cd…" },
    { "n": 3, "source": "ch_main-hero_30yo_john123_v1", "deliverable": "baseImage",
      "output": "references/kira/front.png", "width": 2048, "height": 2048 }
  ]
}
```

Reverse-engineering a film produces ordinary FilmOpen files: blocks from a transcript with timing, shots
from detected cuts with `place`, characters with `aliases` from speaker labels and `appearance` from
observed frames, takes extracted at timecodes. Each such entity carries `"source": { "batch": … }`. An
import item for a file that keeps its own name, as in item 3, simply names it.

### 12.4 Plugin batches

`kind: "plugin"` records a plugin run: the plugin stem, the settings used, and the files produced under
the run's derived handle (§14.3).

### 12.5 Offline rendering

A batch may be **emitted instead of submitted**: `render/rd_<id>_<author>/` holds one workflow file per
item — for ComfyUI, a workflow JSON, labelled as UI graph or API payload — each already named for the
output it should produce. The workflows run anywhere; the outputs are dropped into the project; a reader
matches them by name and records hashes.

### 12.6 Ingest

On every scan a reader **SHOULD**: parse every filename; attach takes to sources; record `sha256` and
measured properties for new outputs in their batch; flag outputs whose hash no longer matches their
record; and report unknown files without moving them. On a network timeout a reader checks an existing
provider job before submitting another paid request, and keeps completed items when one fails. A reader
**MUST NOT** assume it is the only writer.

---

## 13. Versions, official, compare and commentary

### 13.1 Versions

A version is a whole file. All are readable; the highest is the author's working copy. Files are edited
in place and nothing is frozen; a filmmaker may edit `sc_5_john123_v1.json` for months. **Fork** on your
own version creates `v(n+1)` as a copy; fork on someone else's creates *your* `v1` (or next) with
`forkedFrom` set. Prior states of a file are Git's business.

### 13.2 Official

```json
{ "filmopen": 1, "type": "ch", "tag": "main-hero", "epoch": "30yo",
  "official": "ch_main-hero_30yo_maria_v1",
  "setBy": "john123", "setAt": "2026-09-06T14:20:00Z", "note": "Maria's. Using hers." }
```

Written by a director. Applies to every versioned type. Everything downstream reads through official
unless a viewer is looking at their own version (§6.1). Editing a version that official names changes
what the whole project sees; readers **SHOULD** say so and offer to fork instead, and then allow it.
A project with no chosen version for an entity simply has no pointer file: to withdraw a choice, a
director deletes the pointer; an application that offers official as a toggle removes the file when
the toggle is cleared and writes it, with `setBy` and `setAt`, when it is set.

### 13.3 Compare and copy-left

The core collaborative act, and the only merge operation. Versions of one entity are shown as columns:

1. the viewer's own working version, if any;
2. the version the viewer has marked `prefer` in their commentary, if it differs from official;
3. official;
4. any others the viewer adds — including other **epochs** of the same entity, plugin output, and a
   sync client's conflicted copy.

Each column offers *copy-left* for the whole entity and beside each attribute. Copying a nested object
copies the subtree. A viewer with no version of their own who copies anything gets a `v1` created —
copy-left is fork when needed. Copying is not confirmed; the re-rendered result is the check.

**Scenes compare by block id.**

| Case | Shown as | Copy-left does |
|---|---|---|
| same id, same text | collapsed: *6 unchanged* | — |
| same id, different text | side by side, changed words marked | replace mine |
| id only in theirs | a gap in my column at their position | insert it |
| id only in mine | greyed: *not in theirs* | delete mine |

"Not in theirs" does not prove they deleted it; when a Git base is available a reader can tell an
addition from a deletion.

### 13.4 Commentary `cm`

One file per author per entity, covering all its epochs, versions and takes.

```json
{
  "filmopen": 1, "type": "cm", "target": "ch_main-hero", "author": "maria",
  "prefer": "ch_main-hero_30yo_susan_v1",
  "entries": [
    { "on": "ch_main-hero_30yo_john123_v2", "at": "2026-09-06T10:02:00Z", "rating": 1,
      "text": "I really like the tattoo." },
    { "on": "cs_ch_main-hero_30yo_john123_v2_r8af0_maria_1", "at": "2026-09-06T11:15:00Z", "rating": 1 },
    { "on": "ch_main-hero_30yo_susan_v1", "at": "2026-09-06T12:40:00Z", "rating": -1,
      "text": "Too young for the mid-war scenes.", "block": "bl_2" }
  ]
}
```

`on` is a version stem or a media reference. `rating` is `1`, `-1`, or absent. An entry may name a
`block`, a `field` or a `gitCommit` for context. `prefer` is the author's **pick** — the version they
would choose; it orders compare columns and an application shows it (a check) beside official (a
star). Changing a pick rewrites only the author's own commentary file; a viewer without one gets an
empty one with `prefer` set. Readers consolidate every
`cm_` file for an entity into one feed. Review can equally happen in pull requests or conversation;
commentary is not required.

---

## 14. Extensibility

### 14.1 Language

Hooks and plugins are **JavaScript, ECMAScript 2020, single file, no `import`/`require`.** Functions
may be `async`. Code receives one argument, `ctx`, and returns a value; it never mutates project state.
Hooks and plugins are data until an application chooses to run them; plain readers never execute code.

### 14.2 Sandbox

Code runs with `JSON`, `Math`, `String`, `Number`, `Array`, `Object`, `RegExp`, `Map`, `Set`, `Promise`
and nothing else: no filesystem, no network, no clock, no randomness, no timers. That list is the API
surface, not the security boundary: an application **MUST** isolate untrusted code — runtime, memory and
output limits, no ambient filesystem or shell, no network except the services in §14.4 — and must protect
its credentials, which never enter project JSON or plugin output. The application decides which code
the user trusts to run and re-evaluates that trust when the code changes. An **official** pointer alone
does not grant execution permission. Trust decisions belong to the application, not to the film files.

### 14.3 Hooks

Short functions inside `mo`, `pl`, `st` and `pj` files, keyed by name.

| Hook | Runs | Receives | Returns |
|---|---|---|---|
| `validate` | before save or render | `{ entity, project, params? }`; resolved generation parameters when rendering | `true` or an error string |
| `buildPrompt` | assembling a prompt | `{ shot \| cue \| entity, cast, location, style, project }` | string, or `{ positive, negative, refs, params }` |
| `buildRequest` | submitting to a platform | `{ model, platform, prompt, inputs, params }` | `{ url, body, headers? }` |
| `parseResponse` | platform replies | raw response | `{ output, durationMs?, elapsedMs?, cost? }` |
| `estimateCost` | before a batch | `{ model, platform, params }` | number, USD, or `null` for unknown |
| `postProcess` | after ingest | `{ item, take }` | modified item |

Prompt precedence: an explicit prompt on the deliverable; else the model's `buildPrompt`; else a simple
documented default. Builders include selected descriptions and references, never every note in a file.
Parameters a provider does not support are reported, not silently dropped.

```json
{
  "filmopen": 1, "type": "mo", "tag": "seedance25", "author": "paul", "v": 3,
  "name": "Seedance 2.5", "vendor": "ByteDance", "kind": "video",
  "capabilities": ["text2video", "image2video"],
  "inputs": { "prompt": "string", "image": "file?", "durationSec": "int 3..15", "seed": "int?" },
  "defaults": { "durationSec": 5, "fps": 24 },
  "platforms": ["fal", "runninghub"],
  "hooks": {
    "buildPrompt": "(ctx) => [ctx.shot.prompt, ctx.style && ctx.style.prompt.positive, ctx.cast.map(c => c.prompt.positive).join(', ')].filter(Boolean).join('. ')",
    "validate":    "(ctx) => !ctx.params || ctx.params.durationSec == null || ctx.params.durationSec <= 15 || 'Seedance 2.5 clips are 15 s max'"
  }
}
```

```json
{
  "filmopen": 1, "type": "pl", "tag": "fal", "author": "paul", "v": 1,
  "name": "fal.ai", "kind": "remote", "baseUrl": "https://fal.run",
  "auth": { "kind": "apiKey", "keychain": "filmopen/fal" },
  "models": { "seedance25": "fal-ai/bytedance/seedance/v2.5" },
  "hooks": {
    "buildRequest":  "(ctx) => ({ url: ctx.platform.baseUrl + '/' + ctx.platform.models[ctx.model.tag], body: { prompt: ctx.prompt, image_url: ctx.inputs.image, duration: ctx.params.durationSec, seed: ctx.params.seed } })",
    "parseResponse": "(res) => ({ output: res.video.url })",
    "estimateCost":  "(ctx) => 0.07 * ctx.params.durationSec"
  }
}
```

`mo.kind` ∈ `image` · `video` · `audio` · `music` · `tts` · `analysis`. Analysis capabilities include
`shot-detect`, `transcribe`, `diarize`, `face-cluster`, `describe-frame`, `describe-video`. Response parsers set `durationMs` only from the output media duration. Inference or request runtime
belongs in `elapsedMs`, converted from the provider's documented units; otherwise omit it.
Secrets never
appear in a project; `auth.keychain` names a key the application holds.

### 14.4 Plugins

A plugin is a manifest and a code file named after it, in `plugin/<tag>/`.

```json
{
  "filmopen": 1, "type": "pg", "tag": "locale", "author": "paul", "v": 2,
  "name": "Localiser", "api": 1,
  "description": "Translates dialogue and relocates settings to a target culture.",
  "entry": "pg_locale_paul_v2.js",
  "applies": ["sc", "cu", "lo"],
  "hooks": ["transform"],
  "capabilities": { "ai": true },
  "settings": { "target": { "type": "string", "default": "th-TH" }, "relocate": { "type": "boolean", "default": true } }
}
```

| Hook | Purpose | Receives | Returns |
|---|---|---|---|
| `transform` | rewrite entities — polish, translate, restyle, relocate | `{ entities, project, settings, ai? }` | the modified entities |
| `analyze` | comment without changing — continuity, pacing, consistency | same | commentary entries |
| `import` | turn a foreign file into FilmOpen entities | `{ file, project, settings, ai? }` | entities and an optional batch |
| `export` | turn entities into a foreign format | `{ entities, project, settings }` | `{ filename, content }` |

Rules:
- `api` names the host interface version; a host reports versions it does not support.
- A plugin has read access to the whole project and operates on the entities the user selected.
- A plugin **never writes files.** It returns entities; the application writes them as **new versions
  under the running user's derived handle** — `john123.locale-th` — all starting at `v1`, and records a
  `kind: "plugin"` batch. A second run is `v2`. If the source files changed while the run was in
  progress, the application shows that rather than overwriting.
- Plugin output is **never merged into the owner's own versions.** Accepting it is pointing official at
  it, entity by entity or as a whole run; taking parts is copy-left; hand-fixing it is forking it.
- `capabilities.ai` grants `await ctx.ai.complete(prompt, options)` → `{ text, usage?, cost? }` and,
  where the host supports vision, `await ctx.ai.describe(media, prompt, options)`, routed through the
  user's configured platform and key. This is the only path from the sandbox to a network. The
  application owns paid-call consent, cancellation and retries within the user's configured limits.

A **localisation** plugin is the canonical example: fork the repository for a new territory, run the
plugin, point every `_official.json` at the run. The original is untouched in the same tree. An
**import** plugin with `ai: true` is the reverse-engineering tool of §12.3.

### 14.5 The shared library

Models, platforms, plugins and optionally styles live in a public repository the format maintainer
controls, using exactly the conventions above: versions, authors, `_official.json`, commentary. *Share*
on a forked entry is a pull request; the maintainer merges and, once tested, moves `_official.json`. No
central service is needed to use a locally installed entry.

The library is installed **beside** projects, not inside them: one copy per machine (or per user), in a
folder the application manages and can update on its own schedule — a `git pull`, a download — without
touching any project and without a new build of the application. A project therefore need not contain
`mo_`, `pl_` or `pg_` files at all; a reader resolves references to models, platforms and plugins
against the project first and then against the library. A project **MAY** hold its own — a fork of a
library entry made inside the project as one would fork a character, or a private model — and those
take part in resolution exactly like any other entity of the project. The same full stem present in both
the project and the library is reported as a duplicate; the project's file is used. Where the library
folder lives is an application preference (§4.2: application caches and indexes live wherever the
application chooses); a project's manifest may later name the library it was seeded from (§4.4,
`library`, reserved).

---

## 15. Timeline export and round-trip

### 15.1 Track mapping

OpenTimelineIO and every editing application have separate video and audio tracks; there is no
audio-visual track. A FilmOpen `av` track exports as a video track and an audio track that share a name.
The mapping is deterministic:

> Video tracks: each `av` and `video` track in declared order → V1, V2, …
> Audio tracks: the sound of each `av` track in declared order → A1, A2, …; then each `audio` track in
> declared order, continuing the numbering after those `av` audio tracks
> Every exported track carries its FilmOpen **name**.

Nobody in a project ever writes a track number. A cue on `dx` lands on a track labelled *Dialogue*
whether that is A3 or A4 in a given project.

### 15.2 Timing, checks and report

One flat timeline per episode or film. Tracks span the whole timeline; scenes appear in unit order
(§10.1) with a marker at each scene start carrying the scene stem, and a marker on each clip carrying
its shot or cue stem. **No nested compositions** — importers handle them badly.

Timings are computed from `place` by walking `after` chains, in milliseconds, then converted once to the
project rate using the exact rate ratio for fractional rates given as `num`/`den`.
Millisecond inputs may require rounding; conversion does not promise sample-exact interchange. Source in/out are start-inclusive,
end-exclusive. Source timecode and timeline position are distinct.

Before writing, the exporter checks and **reports**: unresolved or ambiguous references; missing or
unreadable media; trims beyond the take's measured duration; anchor cycles; same-track collisions;
transitions without sufficient handles; a cue's embedded audio duplicated on two tracks; any timing rounding applied. An incomplete project is still editable and still exports as a labelled
preview if asked; a final conform stops on the first impossibility and names the field.

Every export ships a **manifest** — source stem, media path, hash, target path, source and timeline
ranges — and the **report** above.

### 15.3 What travels and what does not

OTIO represents tracks and names, clips, source ranges, gaps, markers, transitions and linear speed.
Support in each editing application or adapter must be tested. Metadata can preserve other intent, but
does not guarantee that the editor applies grades, effects, audio automation or titles. Consequently:

- Export is a **conform of raw footage**: picked takes placed as specified, nothing more. The editor
  finishes in the editing application. CDL and LUT values ship as sidecars.
- Additional export formats are optional. Final Cut Pro 7 XML (`xmeml`, `.xml`) and Final Cut Pro X
  XML (`.fcpxml`) are different formats. Premiere supports the former; direct FCPXML import requires
  conversion. Choose a tested target profile rather than promising generic round-trip fidelity.
  CMX3600 EDL is a limited fallback, with unsupported structure reported.
  [Adobe interchange documentation](https://helpx.adobe.com/premiere/desktop/organize-media/import-files/migrate-from-final-cut-pro-x.html)
- An editor's export back to FilmOpen is **lossy by nature**. The format does not attempt to hold what
  OTIO cannot carry.

### 15.4 Never overwrite the editor's timeline

Two things change after the editor has started: takes get re-rendered, and the editor changes timing.
They are handled by different mechanisms, and neither one emits a new timeline over the editor's.

**Re-rendered takes → relink.** Export media into `conform/` under **stable slot names** that do not
change when a pick changes — `sh_5.2_clip.mp4`, `cu_5.1_voice.wav`. Picking a new take and re-exporting
overwrites the slot file as a deliberate act. The editor's timeline references the same path; the
editor can refresh or relink the media while retaining timeline edits. Preservation of grades, effects
and transitions depends on the editor and compatible replacement media; verify it in the named target
version rather than guaranteeing it. A new take of a
different duration no longer fits the editor's in/out — exactly as when a live-action take is swapped —
and the report **MUST** say so.

**Editor's timing → import.** When the editor exports OTIO, a reader **MAY** read it and propose changes
to `place` — track by name, anchor as `with: scene`, offset, in/out. Clip identity comes from the media
filename or slot name and from the markers. Split clips, repeated occurrences and structure the format
cannot express are reported, not compressed into one wrong placement. FilmOpen's representation stays in
step with the editor's decisions without ever emitting over them.

**New material → additive.** New scenes and shots export as a separate timeline in the same project, or
as a bin of slot media. The editor drags them in.

Under this model the editor can begin finishing as soon as an assembly exists. What still has to wait is
structural change — reordering shots after cutting has begun — which no format can make free.

---

## 16. Interchange with existing standards

### 16.1 Summary

| Domain | Standard | Status |
|---|---|---|
| Screenplay | **Fountain** | adopted, import and export of screenplay content (§10.4) |
| Screenplay | FDX | import via Fountain, with a loss report |
| Subtitles and timing | **SRT**, **WebVTT** | adopted; import produces timed blocks, export from dialogue cues |
| Production entities | **MovieLabs OMC v2.8** / OMC-JSON | concepts aligned; export mapping (Appendix C) |
| Colour | **ASC CDL**, **`.cube`** | adopted in styles; exported as sidecars |
| Edit | **OpenTimelineIO**; optional **EDL CMX3600**, **FCP7 XML**, **FCPXML** | export as conform; OTIO import proposes timing |
| Speech | **SSML** | optional on cues |
| Provenance | **C2PA**, **XMP** | embedded on export (§16.5) |
| Workflows | **ComfyUI workflow JSON** | native local workflow and offline batches |
| AI character definition | Character Card V2 | precedent; `creatorNotes` and PNG-embedded JSON borrowed |
| Location, prop, shot, cue, render batch | — | none exists; defined here |

### 16.2 MovieLabs OMC

The Ontology for Media Creation, published by MovieLabs and governed with the major studios, defines
Character, Location, Prop, Costume, Scene, Shot, Sequence, Asset and **Depiction** as first-class
entities. FilmOpen's entity list is nearly OMC's; `picks` is OMC's Depiction — the link from a narrative
object to the asset that depicts it. FilmOpen keeps its own readable field names and maps on export
(Appendix C). FilmOpen is not an OMC implementation. The OMC-JSON schema repository and the ontology
documentation carry their own licences; confirm them before reusing artefacts.

### 16.3 Character Card V2

The AI-conversation community's character format embeds its JSON inside the PNG that pictures the
character. FilmOpen readers **MAY** embed the entity JSON in a concept-sheet PNG's `tEXt` chunk under
the key `filmopen`, so a shared image carries its own definition. The JSON file remains authoritative;
embedding changes the image's bytes and hash.

### 16.4 ComfyUI

ComfyUI embeds the workflow that produced an image in the image's metadata. FilmOpen readers **SHOULD**
do the same for takes produced through ComfyUI, and **SHOULD** preserve any embedded workflow found on
ingest.

### 16.5 Provenance

Each batch item is the provenance of its take. On export a reader **SHOULD** write it into the output's
XMP and, where tooling allows, emit a C2PA manifest: `trainedAlgorithmicMedia` for rendered takes, and
for imported takes whatever the batch's `source` states — imported media may itself be generated or
composite and is never labelled camera capture by default. The `human` block records the human
contribution to each output; it is evidence, not a legal determination. A reader **MAY** produce an
authorship report walking batches, picks and commentary.

---

## 17. Editing by hand or by machine

The format is meant to be edited without any FilmOpen application. For a person or an agent:

1. **Never rename an entity file.** Create a new version.
2. **Edit only files whose author is you.** To change someone else's, fork it: copy, rename to your
   handle and `v1`, set `forkedFrom`. A delegated edit to another's file is still valid data; the
   maintainer decides whether to accept it.
3. **Prefer not to edit a version named by `_official.json`.** Fork instead. If you must, you are changing
   what everyone sees.
4. **Keep the header in step with the filename.**
5. **Never renumber block ids.** Add with the next unused number; reorder by moving array elements.
6. **Short references** in story files; **full references** in `forkedFrom`, `prefer`, `on`; **media
   references** in `picks`, `refs`, `preview`, `file`.
7. **Do not write takes into entities.** Drop conforming media beside the JSON, or name it in a batch;
   a reader will find it. If you cannot name it, use `_inbox/`.
8. **No secrets in the project.**
9. **Preserve fields you do not understand.**
10. What the format cannot say goes in `notes`, or in `x` for structured application data.

A machine generating a project from a film should produce: `pj_`; one `ch_` per identified speaker with
`aliases`; `lo_` per setting; `sc_` files with timed blocks; `sh_` per detected cut with `place`; `cu_`
per dialogue line; an import batch; extracted takes — and nothing else.

---

## 18. Conformance

### 18.1 Files

A conforming file is UTF-8 JSON with no duplicate keys, named by the grammar for its category, with the
required header for that category. Writers **SHOULD** pretty-print with two-space indentation for
readable diffs; whitespace is not semantic.

### 18.2 Readers

A conforming reader:

- **MUST** parse every filename by the grammars in §5.4 and dispatch on type;
- **MUST** read `filmopen-project.json` when present (§4.4) and use the media root it names; **MUST
  NOT** refuse a folder that lacks it;
- **MUST** resolve references by §6;
- **MUST** discover takes by scanning and by batches, and **MUST NOT** require entities to list them;
- **MUST** preserve unknown fields and the `x` object when rewriting a file it authors;
- **MUST NOT** assume it is the only writer, and **MUST NOT** move, rename or delete files it does not
  understand;
- **SHOULD** warn, and continue, on: a header that disagrees with its filename; a file whose author is
  not the current user being modified; an `_official.json` written by a non-director; a version named by
  `_official.json` being edited; a segment longer than 10 characters; a reference that resolves
  ambiguously; an explicit epoch that does not exist; a project file that declares no epochs.

Authorship warnings are not validity tests. A file edited by someone other than its named author is
still a conforming file; whether it is accepted is the maintainer's decision.

### 18.3 Writers

A conforming writer:

- **MUST** write lowercase filenames matching §5;
- **MUST** write `filmopen-project.json` when creating a project folder (§4.4), and **SHOULD** write the
  creator's first project version with the creator as director and at least one epoch (§8.2, §8.3);
- **MUST** write the required header for the file's category;
- **SHOULD** write only under the current user's owner handle or a workspace of it;
- **MUST NOT** delete another author's files.

### 18.4 Schemas and tests

JSON Schemas for every file type are intended to be published at `https://filmopen.ai/schema/1/<type>.json`, CC0, and
usable offline. Until released and tested, the URLs are publication targets. Schemas mark the header
and category-specific required fields (including one project story-root list) as required, and enumerate the
vocabularies in Appendix A without restricting free-form objects to them. Cross-file resolution and
timeline arithmetic are reader and exporter work beyond schema validation.

A first implementation should demonstrate at least: a blank project written, rendered and exported with
no account and no Git; the same project opened in a text editor and a third-party reader; a delegated
edit shown clearly to a maintainer; base and two epochs of one character used in different scenes with
independent references; another contributor's shot rendered and discovered without changing their file;
an imported file with a non-conforming name referenced through a batch; a plugin run compared and partly
accepted; an interrupted provider job recovered without a second paid request; and an integer-rate and
a fractional-rate export opened in a named editing application.

---

## 19. Not in this version

- **Overlays or inheritance.** Every file is complete.
- **Reserved key prefixes, canonical serialisation, hash-addressed JSON, UUIDs.** Any editor works.
- **Per-field attribution, immutable revisions, history files.** Credit lives in `forkedFrom`, `by`,
  batches and commentary; history lives in Git.
- **Frame- or sample-unit timing fields.** Milliseconds only; exporters convert and report rounding.
- **Cast instances.** A scene selects one appearance per character; a story needing two at once uses two
  tags.
- **A full edit model** — L-cut audio splits beyond cue placement, parameterised transitions, effects.
  That is the editing application's job; the format hands it a conform with timing.
- **Alternate cuts as first-class objects.** An alternate assembly is a set of shot versions with
  different `place`. An explicit assembly entity may be added in a later version.
- **Identity or authentication.** Handles are names. Trust is the repository gatekeeper's.

---

## Appendix A — Attribute vocabulary

Everything in this appendix is **optional**. It exists so that writers — human, scripted or AI — share a
vocabulary, so that comparisons line up attribute by attribute, and so that a reverse-engineering tool
has a checklist of what to look for. Free-form objects (`appearance`, `personality`, `lighting`, …) may
carry any additional keys.

Enumerated values are lowercase strings. Where a field accepts free text, the enumeration is a suggested
vocabulary, not a constraint. "media ref" means a media reference as defined in §6.3.

### A.1 Character `ch`

| Key | Type | Values / notes |
|---|---|---|
| `kind` | enum | `principal` `supporting` `extra` `group` `narrator` `creature` `animal` |
| `aliases` | string[] | script cue names, nicknames, transcript labels |
| `summary` | string | one or two sentences |
| `arc` | string | how the character changes across the story |
| `birthYear` | integer | year of birth, the same in every epoch file; with the epoch's `year` a reader proposes `appearance.age` (§9.1) |
| `appearance.age` | number or string | `30`, `"late thirties"`; proposed from `birthYear` and the epoch's `year`, overridable |
| `appearance.gender` | string | free |
| `appearance.ethnicity` | string | free; optional; for continuity only |
| `appearance.species` | string | `human` default |
| `appearance.heightCm` `appearance.weightKg` | number | |
| `appearance.build` | string | `slight` `lean` `athletic` `average` `stocky` `heavy` … |
| `appearance.skin` | string | tone, texture, notable features |
| `appearance.hair.color` `.length` `.style` `.texture` | string | length: `bald` `buzz` `short` `chin` `shoulder` `long`; texture: `straight` `wavy` `curly` `coily` |
| `appearance.facialHair` | string | |
| `appearance.eyes.color` `.shape` `.notes` | string | |
| `appearance.face` | string | shape, notable features |
| `appearance.marks` | string[] | scars, tattoos, birthmarks, with location |
| `appearance.glasses` `appearance.jewellery` | string | worn habitually |
| `appearance.posture` `appearance.gait` | string | how they stand and move |
| `appearance.hands` `appearance.teeth` | string | often visible in close-ups |
| `appearance.notes` | string | |
| `personality.summary` | string | |
| `personality.traits` | string[] | |
| `personality.wants` `.needs` `.fears` | string | |
| `personality.speech` | string | rhythm, vocabulary, verbal habits |
| `personality.mannerisms` | string[] | physical habits |
| `voice.description` | string | |
| `voice.pitch` | enum | `very-low` `low` `mid` `high` `very-high` |
| `voice.pace` | enum | `slow` `measured` `normal` `quick` `rapid` |
| `voice.timbre` | string | `breathy` `rasp` `nasal` `resonant` `thin` … |
| `voice.accent` `voice.lang` | string | free; BCP 47 |
| `voice.refs` | media ref[] | reference voice samples for this character and epoch |
| `voice.providerBindings` | object | platform tag → provider voice id; samples and text remain the portable truth |
| `relationships[]` | object | `{ character, relation }` |
| `defaultOutfit` | tag | outfit used when a scene does not say; resolved at the character's epoch |
| `prompt.positive` `prompt.negative` | string | |
| `creatorNotes` | string | for people; excluded from prompts unless explicitly included |
| `picks.sheet` `picks.turnaround` `picks.expressions` | media ref or media ref[] | chosen concept sheets |
| `refs` | media ref[] or grouped object | §9.0 |
| `preview` | media ref | shown in library views |

### A.2 Outfit `of`

| Key | Type | Values / notes |
|---|---|---|
| `garments[]` | object | `{ item, color, material, fit, pattern, condition, notes }` |
| `accessories` | string[] | |
| `footwear` | string | |
| `palette` | string[] | hex or names |
| `era` | string | |
| `condition` | enum | `new` `worn` `distressed` `damaged` `bloodied` `wet` `muddy` |
| `prompt` `creatorNotes` `picks.sheet` `refs` `preview` | | as for character; no `voice` |

### A.3 Location `lo` and Prop `pr`

**Location**

| Key | Type | Values / notes |
|---|---|---|
| `kind` | enum | `interior` `exterior` `both` |
| `aliases` | string[] | slugline names |
| `geography` | object | `{ city, region, country, coordinates }` |
| `era` | string | period of the architecture and dressing |
| `architecture` | string | style, materials, scale |
| `description` | string | |
| `dressing` | string[] | props and set dressing habitually present |
| `areas` | tag[] | sub-locations `<parent>.<area>` |
| `lighting.default` | string | |
| `lighting.practicals` | string[] | visible light sources |
| `lighting.windows` | string | direction, size, coverings |
| `sound.roomTone` `sound.ambience` | string | |
| `weather` | string | default, exteriors |
| `timeOfDay` | enum | default; see A.5 |
| `access` | string | entrances, exits, sightlines |
| `palette` | string[] | |
| `prompt` `creatorNotes` `picks.sheet` `refs` `preview` | | groups often used: `floorPlans`, `panoramas`, `lighting` |

**Prop**

| Key | Type | Values / notes |
|---|---|---|
| `kind` | enum | `vehicle` `weapon` `document` `device` `furniture` `food` `tool` `clothing-item` `creature` `other` |
| `hero` | boolean | seen close; needs a strong reference |
| `description` `scale` `material` `condition` | string | |
| `interaction` | string | how characters handle it |
| `sound` | string | what it sounds like |
| `prompt` `creatorNotes` `picks.sheet` `refs` `preview` | | |

### A.4 Shot `sh`

| Key | Type | Values |
|---|---|---|
| `size` | enum | `ecu` extreme close-up · `cu` close-up · `mcu` medium close-up · `ms` medium · `mls` medium long / cowboy · `ls` long / wide · `els` extreme long · `ots` over-the-shoulder · `pov` · `two-shot` · `group` · `insert` · `establishing` · `master` |
| `angle` | enum | `eye` `low` `high` `dutch` `overhead` `birds-eye` `worms-eye` `profile` `three-quarter` `frontal` `reverse` |
| `movement` | enum | `static` `pan` `tilt` `push-in` `pull-out` `truck` `pedestal` `crane` `boom` `handheld` `steadicam` `tracking` `arc` `orbit` `zoom-in` `zoom-out` `rack-focus` `whip-pan` `drone` |
| `movementNotes` | string | speed, start and end framing |
| `lens.focalMm` | number | |
| `lens.aperture` | number | f-stop |
| `lens.dof` | enum | `shallow` `moderate` `deep` |
| `lens.anamorphic` | boolean | |
| `lens.focus` | string | what is sharp; rack targets |
| `speed` | enum | `normal` `slow` `fast` `timelapse` `freeze` |
| `speedFactor` | number | `0.5` = half speed |
| `lighting.key.direction` | enum | `front` `side` `three-quarter` `back` `rim` `top` `under` |
| `lighting.key.quality` | enum | `hard` `soft` |
| `lighting.style` | enum | `high-key` `low-key` `natural` `practical` `silhouette` `neon` `firelight` `mixed` |
| `lighting.colorTempK` | number | |
| `lighting.contrast` | enum | `flat` `normal` `high` `extreme` |
| `lighting.practicals` | string[] | |
| `lighting.notes` | string | |
| `composition.headroom` | enum | `tight` `normal` `loose` `none` |
| `composition.leadRoom` | string | |
| `composition.eyeline` | string | |
| `composition.screenDirection` | string | `facing left` `moving right` … |
| `composition.symmetry` | enum | `centred` `thirds` `asymmetric` |
| `composition.depth` | object | `{ foreground, midground, background }` |
| `composition.blocking` | string | where characters are and move |
| `characters` | tag[] | visible in frame; appearance and outfit come from the scene cast |
| `props` | tag[] | visible in frame |
| `action` | string | what happens within the shot, in order |
| `continuity` | string[] | states that must match adjacent shots |
| `vfx` | string | |
| `aspect` | string | override of project aspect |
| `durationSec` | number | target; the hold for a still |
| `deliverables` | object | keys `storyboard` `firstFrame` `lastFrame` `keyframes` `clip`; each `{ model, platform?, prompt, negative?, refs?, from?, inputs?, params? }` |
| `place` | object | §11.3 |
| `picks` | object | one media ref, or several frames, per deliverable |
| `coverage` | string | notes on what else was shot for this beat |

### A.5 Scene `sc` and story units

| Key | Type | Values |
|---|---|---|
| `location` | tag | `locationEpoch` overrides the scene epoch for the location |
| `epoch` | epoch | |
| `storyDay` | number | continuity day |
| `time` | enum | `dawn` `morning` `midday` `afternoon` `golden-hour` `dusk` `blue-hour` `night` `late-night` |
| `weather` | string | `clear` `overcast` `rain` `storm` `fog` `snow` `wind` `heat-haze` … free |
| `cast[]` | object | `{ character, epoch?, outfit?, notes? }` — one selection per character |
| `props` | array | tags, or `{ prop, epoch }` for an explicit state |
| `style` | tag | |
| `synopsis` | string | |
| `purpose` | string | what the scene does for the story |
| `mood` | string | |
| `pacing` | enum | `slow` `measured` `brisk` `frantic` |
| `targetDurationSec` | number | |
| `sound` | object | `{ ambience, notes }` |
| `continuity` | string[] | |
| `vfx` | string | |
| `shots` `cues` | tag[] | order of the scene's shots and cues |
| `blocks` | array | §10.3 |

Units add `scenes` / `sequences` / `episodes` for order, and cascade `epoch`, `cast`, `locations`,
`styles`.

### A.6 Cue `cu`

| Key | Type | Values |
|---|---|---|
| `kind` | enum | `dialogue` `narration` `music` `sfx` `ambience` |
| `scene` or `unit` | full ref | what the blocks belong to |
| `blocks` | id[] | span, within `scene` |
| `anchors[]` | object | `{ scene, blocks }` when a unit-level cue cites specific scenes' blocks |
| `character` | tag | dialogue and narration |
| `spoken` | string | replaces block text |
| `ssml` | string | alternative to `spoken` |
| `lang` | string | BCP 47 |
| `delivery.effort` | enum | `whisper` `quiet` `normal` `raised` `shout` |
| `delivery.emotion` | string | |
| `delivery.processing` | enum | `none` `phone` `radio` `pa` `distant` `underwater` |
| `delivery.adr` | boolean | replaces on-camera sound |
| `music.tempoBpm` `music.key` | | |
| `music.instrumentation` | string[] | |
| `music.mood` `music.genre` `music.structure` | string | |
| `music.reference` | string | a description, never a copyrighted recording as a target |
| `music.lyrics` | string | |
| `music.stems` | object | named stem → media ref, when a delivered piece is split |
| `sfx.description` | string | |
| `sync[]` | object | `{ with, offsetMs, event }` — hit points |
| `model` `platform` | tag | |
| `place` | object | §11.3 incl. `gainDb` `pan` `fadeInMs` `fadeOutMs` |
| `picks` | object | `voice` `music` `sfx` `ambience` |

### A.7 Style `st`

| Key | Type | Values |
|---|---|---|
| `genre` | string | |
| `references` | string[] | descriptive — eras, movements, techniques |
| `look.stock` | string | film stock or digital emulation |
| `look.grain` | enum | `none` `fine` `medium` `heavy` |
| `look.halation` | enum | `none` `slight` `strong` |
| `look.contrast` | enum | `flat` `normal` `high` `extreme` |
| `look.saturation` | enum | `mono` `low` `normal` `high` |
| `look.sharpness` | enum | `soft` `normal` `crisp` |
| `look.vignette` | enum | `none` `slight` `strong` |
| `palette` | string[] | hex |
| `colorTempBias` | enum | `cool` `neutral` `warm` |
| `aspect` `letterbox` | string / boolean | |
| `lensCharacter` | string | flare, breathing, distortion |
| `cdl` | object | `{ slope[3], offset[3], power[3], saturation }` |
| `lut` | media ref | `.cube` |
| `prompt` `refs` `preview` | | groups often used: `images`, `lighting`, `sound` |

### A.8 Block kinds and per-kind fields

| Kind | Fields |
|---|---|
| `dialogue` | `character` `text` `direction` `offscreen` `voiceover` `dual` `lang` |
| `action` | `text` `characters?` |
| `narration` | `character` `text` `lang` |
| `transition` | `text` — `CUT TO:` `DISSOLVE TO:` `FADE OUT.` … |
| `title` | `text` `font?` (misc tag) `position?` `durationMs?` |
| `lyric` | `character` `text` |
| `note` | `text` |

All kinds: `id`; optional `startMs`, `endMs`.

### A.9 Project `pj`

`author` `v` `forkedFrom?` `label?` `name` `kind` (`short` `feature` `miniseries` `series`) `genre[]` `year`
`logline` `synopsis` `lang` `rating` `format{aspect,fps,resolution,color,…}` `runtimeMin`
`credits[{role,name,handle}]` `epochs{<token>: {label?, order?, year?}}` (at least one; the first is the
default) `contributors` `tracks` `license` `hooks`, and exactly one story root list: `seasons`,
`episodes`, `sequences` or `scenes`.

Recommended `genre` tokens: `action` `adventure` `animation` `biography` `comedy` `crime` `documentary`
`drama` `family` `fantasy` `history` `horror` `musical` `mystery` `noir` `romance` `science-fiction`
`thriller` `war` `western`. A project may name others.

### A.10 Render batch item

`n` `source` `deliverable` `take` `model` `platform` `prompt` `negative` `params` `inputs[]` `output`
`sha256` `costUsd` `durationMs` `elapsedMs` `at{ms|startMs,endMs}` `human{selectedFrom,promptEditedBy,manualEdits[]}`
`status` `jobId` `attempts` `error` `thumbnail` `poster` `proxy` `waveform` `mimeType` `bytes` `width`
`height` `fps` `frameCount` `sampleRate` `channels` `workflow`.

---

## Appendix B — Worked example

One scene of *The Cartographer* as a story and conform example. The story and selected media are
listed below; provider/model definitions for regeneration are intentionally omitted. Model/platform
labels in the historical batch are illustrative. The supplied media has the durations recorded in the
batches. Shortened hashes are illustrative; real files use complete hashes. B.10 follows the placements.

### B.1 Files

```
the-cartographer/
  filmopen-project.json
  pj_cartographer_john123_v1.json
  character/main-hero/
    ch_main-hero_30yo_john123_v1.json
    ch_main-hero_30yo_maria_v1.json
    ch_main-hero_30yo_official.json
    cm_ch_main-hero_maria.json
    cs_ch_main-hero_30yo_maria_v1_r8af0_maria_1.png
  references/kira/
    voice-calm.wav
  location/archive/
    lo_archive_mid-war_john123_v1.json
    cs_lo_archive_mid-war_john123_v1_r7c21_john123_1.png
  prop/chart/
    pr_chart_default_john123_v1.json
  style/war-grade/
    st_war-grade_default_maria_v1.json
    st_war-grade_default_official.json
  s1/
    se_s1_john123_v1.json
    e2/
      ep_e2_john123_v1.json
      sc5/
        sc_5_john123_v1.json
        sh_5.1_john123_v1.json
        sh_5.2_john123_v1.json
        cu_5.1_john123_v1.json
        cu_5.2_john123_v1.json
        cu_5.3_john123_v1.json
        ff_sh_5.1_john123_v1_r8af0_maria_1.png
        cl_sh_5.1_john123_v1_r8af0_maria_1.mp4
        ff_sh_5.2_john123_v1_r8af0_maria_1.png
        cl_sh_5.2_john123_v1_r8af0_maria_1.mp4
        vo_cu_5.1_john123_v1_r8af0_maria_1.wav
        am_cu_5.2_john123_v1_r8af0_maria_1.wav
        vo_cu_5.3_john123_v1_r8af0_maria_1.wav
  render/
    rd_7c21_john123.json
    rd_8af0_maria.json
```

Here the media sits beside the JSON, so the manifest names no `data` locator and the project folder is
its own media root:

```json
{ "filmopen": 1, "tag": "cartographer" }
```

Had the renders been kept in a synced folder next to the project, the manifest would say
`"data": { "type": "local", "path": "../the-cartographer-data" }` and every `.png`, `.mp4` and `.wav`
above would live there instead, under the same names (§4.4).

### B.2 Project

```json
{
  "filmopen": 1, "type": "pj", "tag": "cartographer", "author": "john123", "v": 1,
  "name": "The Cartographer", "kind": "series", "genre": ["drama", "mystery"], "year": 2026,
  "logline": "A mapmaker discovers her charts are rewriting the coastline.",
  "lang": "pt-BR",
  "format": { "aspect": "2.39:1", "fps": 24, "resolution": "1920x804", "color": "rec709" },
  "epochs": { "default": { "label": "Present day", "order": 1, "year": 2026 }, "mid-war": { "label": "During the war", "order": 2, "year": 1943 } },
  "contributors": { "john123": { "name": "João Silva", "role": "director" }, "maria": { "name": "Maria Costa" } },
  "tracks": [
    { "id": "pic", "kind": "av", "name": "Picture" },
    { "id": "dx", "kind": "audio", "name": "Dialogue" },
    { "id": "amb", "kind": "audio", "name": "Ambience" },
    { "id": "mx", "kind": "audio", "name": "Music" }
  ],
  "seasons": ["s1"],
  "license": "CC-BY-SA-4.0",
  "created": "2026-09-05T18:00:00Z", "updated": "2026-09-06T16:00:00Z"
}
```

### B.3 Character — two versions and the official pointer

```json
{
  "filmopen": 1, "type": "ch", "tag": "main-hero", "epoch": "30yo", "author": "john123", "v": 1,
  "name": "Kira Voss", "kind": "principal", "aliases": ["KIRA"],
  "summary": "A cartographer who discovers her maps are rewriting the coastline.",
  "appearance": { "age": 30, "gender": "woman", "build": "lean", "skin": "olive",
                  "hair": { "color": "black", "length": "shoulder", "style": "loose" },
                  "eyes": { "color": "dark brown" }, "marks": ["burn scar, left forearm"] },
  "personality": { "summary": "Guarded, precise.", "speech": "Short sentences." },
  "voice": { "description": "low, unhurried, slight rasp", "pitch": "low", "pace": "slow", "lang": "pt-BR",
             "refs": ["references/kira/voice-calm.wav"] },
  "prompt": { "positive": "30yo woman, olive skin, black shoulder-length hair, lean build, burn scar left forearm",
              "negative": "cartoon, plastic skin" },
  "created": "2026-09-05T18:04:11Z", "updated": "2026-09-05T19:22:40Z"
}
```

```json
{
  "filmopen": 1, "type": "ch", "tag": "main-hero", "epoch": "30yo", "author": "maria", "v": 1,
  "name": "Kira Voss", "kind": "principal", "aliases": ["KIRA"],
  "forkedFrom": "ch_main-hero_30yo_john123_v1",
  "summary": "A cartographer who discovers her maps are rewriting the coastline.",
  "appearance": { "age": 30, "gender": "woman", "build": "lean", "skin": "olive",
                  "hair": { "color": "copper red", "length": "short", "style": "cropped, undercut" },
                  "eyes": { "color": "dark brown" }, "marks": ["burn scar, left forearm", "small compass-rose tattoo, right wrist"] },
  "personality": { "summary": "Guarded, precise.", "speech": "Short sentences." },
  "voice": { "description": "low, unhurried, slight rasp", "pitch": "low", "pace": "slow", "lang": "pt-BR",
             "refs": ["references/kira/voice-calm.wav"] },
  "prompt": { "positive": "30yo woman, olive skin, cropped copper-red hair with undercut, lean build, burn scar left forearm, small compass tattoo right wrist",
              "negative": "cartoon, plastic skin" },
  "picks": { "sheet": "cs_ch_main-hero_30yo_maria_v1_r8af0_maria_1" },
  "refs": { "sheets": ["cs_ch_main-hero_30yo_maria_v1_r8af0_maria_1"] },
  "preview": "cs_ch_main-hero_30yo_maria_v1_r8af0_maria_1",
  "created": "2026-09-06T09:12:00Z", "updated": "2026-09-06T10:31:02Z"
}
```

```json
{ "filmopen": 1, "type": "ch", "tag": "main-hero", "epoch": "30yo",
  "official": "ch_main-hero_30yo_maria_v1",
  "setBy": "john123", "setAt": "2026-09-06T14:20:00Z", "note": "The red hair reads better against the archive." }
```

### B.4 Location, prop, style

```json
{
  "filmopen": 1, "type": "lo", "tag": "archive", "epoch": "mid-war", "author": "john123", "v": 1,
  "name": "The municipal archive", "aliases": ["ARCHIVE"], "kind": "interior",
  "geography": { "city": "Lisbon", "country": "PT" }, "era": "1930s building, wartime neglect",
  "description": "Long reading room, iron shelving to the ceiling, one skylight, dust in the air.",
  "dressing": ["oak reading table", "green-shade lamps", "map drawers", "sandbags at the windows"],
  "lighting": { "default": "single skylight, cold; lamps warm and low", "practicals": ["green-shade desk lamps"] },
  "sound": { "roomTone": "large, dead, high ceiling", "ambience": "rain on skylight, distant sirens" },
  "prompt": { "positive": "1930s municipal archive reading room, iron shelving, skylight, green desk lamps, sandbagged windows, wartime, dust" },
  "picks": { "sheet": "cs_lo_archive_mid-war_john123_v1_r7c21_john123_1" },
  "refs": { "sheets": ["cs_lo_archive_mid-war_john123_v1_r7c21_john123_1"] },
  "created": "2026-09-05T18:30:00Z", "updated": "2026-09-05T18:30:00Z"
}
```

```json
{
  "filmopen": 1, "type": "pr", "tag": "chart", "epoch": "default", "author": "john123", "v": 1,
  "name": "The 1994 harbour chart", "kind": "document", "hero": true,
  "description": "Hand-drawn nautical chart, yellowed linen-backed paper, sepia ink, folded twice.",
  "prompt": { "positive": "hand-drawn nautical chart, yellowed linen paper, sepia ink, fold creases" },
  "created": "2026-09-05T18:40:00Z", "updated": "2026-09-05T18:40:00Z"
}
```

```json
{
  "filmopen": 1, "type": "st", "tag": "war-grade", "epoch": "default", "author": "maria", "v": 1,
  "name": "War — gritty, desaturated",
  "look": { "stock": "16mm colour negative", "grain": "medium", "contrast": "high", "saturation": "low" },
  "palette": ["#2b2f33", "#6b7076", "#b9bcb8", "#d9c9a3"],
  "prompt": { "positive": "desaturated, cold grey palette, high contrast, 16mm grain, handheld", "negative": "saturated, glossy" },
  "cdl": { "slope": [0.95, 0.98, 1.05], "offset": [-0.02, -0.02, 0.0], "power": [1.1, 1.1, 1.05], "saturation": 0.6 },
  "created": "2026-09-06T08:00:00Z", "updated": "2026-09-06T08:00:00Z"
}
```

```json
{ "filmopen": 1, "type": "st", "tag": "war-grade", "epoch": "default",
  "official": "st_war-grade_default_maria_v1", "setBy": "john123" }
```

### B.5 Story units

```json
{ "filmopen": 1, "type": "se", "tag": "s1", "author": "john123", "v": 1,
  "name": "Season 1", "episodes": ["e2"],
  "created": "2026-09-05T18:00:00Z", "updated": "2026-09-05T18:00:00Z" }
```

```json
{
  "filmopen": 1, "type": "ep", "tag": "e2", "author": "john123", "v": 1,
  "name": "Episode 2 — Low Tide", "synopsis": "Kira finds the first altered chart.",
  "epoch": "mid-war",
  "cast": [{ "character": "main-hero", "epoch": "30yo" }],
  "locations": ["archive"], "styles": ["war-grade"],
  "scenes": ["5"],
  "created": "2026-09-05T18:00:00Z", "updated": "2026-09-06T12:00:00Z"
}
```

### B.6 Scene

```json
{
  "filmopen": 1, "type": "sc", "tag": "5", "author": "john123", "v": 1,
  "name": "The archive",
  "location": "archive", "epoch": "mid-war", "storyDay": 3, "time": "night", "weather": "rain",
  "cast": [{ "character": "main-hero", "epoch": "30yo" }],
  "props": ["chart"], "style": "war-grade",
  "synopsis": "Kira compares the two charts and realises the coastline moved.",
  "purpose": "First proof. Turns the mystery from doubt to certainty.",
  "targetDurationSec": 40,
  "shots": ["5.1", "5.2"],
  "cues": ["5.1", "5.2", "5.3"],
  "blocks": [
    { "id": "bl_1", "kind": "action", "text": "Kira spreads both charts across the reading table." },
    { "id": "bl_2", "kind": "dialogue", "character": "main-hero", "text": "The bay was here last spring.",
      "direction": "flat disbelief, does not look up" },
    { "id": "bl_3", "kind": "action", "text": "Behind her, the window flashes white. A second later, the glass goes." },
    { "id": "bl_4", "kind": "dialogue", "character": "main-hero", "text": "Not now.", "offscreen": true }
  ],
  "created": "2026-09-06T11:58:02Z", "updated": "2026-09-06T12:02:11Z"
}
```

### B.7 Shots — eight seconds, then a straight cut to five seconds

```json
{
  "filmopen": 1, "type": "sh", "tag": "5.1", "author": "john123", "v": 1,
  "name": "Kira at the table", "scene": "sc_5_john123_v1", "blocks": ["bl_1", "bl_2"],
  "characters": ["main-hero"], "props": ["chart"],
  "size": "mcu", "angle": "eye", "movement": "push-in", "movementNotes": "slow, ends tight on her hands",
  "lens": { "focalMm": 50, "dof": "shallow" },
  "lighting": { "key": { "direction": "side", "quality": "soft" }, "style": "low-key", "colorTempK": 3200, "practicals": ["green desk lamp"] },
  "durationSec": 8,
  "deliverables": {
    "firstFrame": { "model": "nanobanana", "prompt": "Kira leaning over two nautical charts on an oak table, green desk lamp, archive shelving behind, 16mm grain",
                    "refs": ["cs_ch_main-hero_30yo_maria_v1_r8af0_maria_1", "cs_lo_archive_mid-war_john123_v1_r7c21_john123_1"] },
    "clip": { "model": "seedance25", "prompt": "slow push in as she traces the coastline with a finger and speaks without looking up", "from": "firstFrame" }
  },
  "place": { "track": "pic", "with": "scene", "offsetMs": 0 },
  "picks": { "firstFrame": "ff_sh_5.1_john123_v1_r8af0_maria_1", "clip": "cl_sh_5.1_john123_v1_r8af0_maria_1" },
  "created": "2026-09-06T12:10:00Z", "updated": "2026-09-06T16:00:00Z"
}
```

```json
{
  "filmopen": 1, "type": "sh", "tag": "5.2", "author": "john123", "v": 1,
  "name": "The window goes", "scene": "sc_5_john123_v1", "blocks": ["bl_3", "bl_4"],
  "characters": ["main-hero"],
  "size": "ms", "angle": "low", "movement": "static",
  "lens": { "focalMm": 35, "dof": "moderate" },
  "lighting": { "key": { "direction": "back", "quality": "hard" }, "style": "low-key", "notes": "single white flash from behind, then darkness with lamp only" },
  "continuity": ["charts still on the table", "scar visible, left forearm"],
  "vfx": "glass shatter, no debris toward lens",
  "durationSec": 5,
  "deliverables": {
    "firstFrame": { "model": "nanobanana", "prompt": "low angle, Kira in medium shot at the archive table, the window behind her blown white", "refs": ["cs_ch_main-hero_30yo_maria_v1_r8af0_maria_1"] },
    "clip": { "model": "seedance25", "prompt": "the window flashes white, glass falls, she does not turn", "from": "firstFrame" }
  },
  "place": { "track": "pic", "after": "sh_5.1", "offsetMs": 0, "transitionIn": { "type": "cut" } },
  "picks": { "firstFrame": "ff_sh_5.2_john123_v1_r8af0_maria_1", "clip": "cl_sh_5.2_john123_v1_r8af0_maria_1" },
  "created": "2026-09-06T12:12:00Z", "updated": "2026-09-06T16:00:00Z"
}
```

### B.8 Cues — dialogue riding with picture, a J-cut leading the second shot, ambience under the scene

```json
{
  "filmopen": 1, "type": "cu", "tag": "5.1", "author": "john123", "v": 1,
  "name": "Kira — the bay line", "kind": "dialogue", "scene": "sc_5_john123_v1", "blocks": ["bl_2"],
  "character": "main-hero",
  "delivery": { "effort": "quiet", "emotion": "flat disbelief" },
  "model": "elevenlabs",
  "place": { "track": "dx", "with": "sh_5.1", "offsetMs": 2750, "gainDb": -3 },
  "picks": { "voice": "vo_cu_5.1_john123_v1_r8af0_maria_1" },
  "created": "2026-09-06T12:20:00Z", "updated": "2026-09-06T16:00:00Z"
}
```

```json
{
  "filmopen": 1, "type": "cu", "tag": "5.3", "author": "john123", "v": 1,
  "name": "Kira — not now (J-cut)", "kind": "dialogue", "scene": "sc_5_john123_v1", "blocks": ["bl_4"],
  "character": "main-hero",
  "delivery": { "effort": "quiet", "emotion": "clipped" },
  "model": "elevenlabs",
  "place": { "track": "dx", "with": "sh_5.2", "offsetMs": -1500, "gainDb": -3 },
  "picks": { "voice": "vo_cu_5.3_john123_v1_r8af0_maria_1" },
  "notes": "Her line starts 1.5 s before the picture cuts to 5.2 — we hear her before we see the window go.",
  "created": "2026-09-06T12:22:00Z", "updated": "2026-09-06T16:00:00Z"
}
```

```json
{
  "filmopen": 1, "type": "cu", "tag": "5.2", "author": "john123", "v": 1,
  "name": "Archive — rain and sirens", "kind": "ambience", "scene": "sc_5_john123_v1", "blocks": ["bl_1", "bl_2", "bl_3", "bl_4"],
  "sfx": { "description": "rain on a skylight, large dead room, one distant siren rising and falling" },
  "model": "elevenlabs-sfx",
  "place": { "track": "amb", "with": "scene", "offsetMs": 0, "inMs": 0, "outMs": 13000, "fadeInMs": 1000, "fadeOutMs": 1500, "gainDb": -18 },
  "picks": { "ambience": "am_cu_5.2_john123_v1_r8af0_maria_1" },
  "created": "2026-09-06T12:25:00Z", "updated": "2026-09-06T16:00:00Z"
}
```

### B.9 Render batch and commentary

```json
{ "filmopen": 1, "type": "rd", "id": "7c21", "author": "john123", "kind": "manual",
  "status": "done", "items": [
    { "n": 1, "source": "lo_archive_mid-war_john123_v1", "deliverable": "sheet",
      "output": "location/archive/cs_lo_archive_mid-war_john123_v1_r7c21_john123_1.png" }
  ] }
```

```json
{
  "filmopen": 1, "type": "rd", "id": "8af0", "author": "maria", "kind": "render", "tier": "final",
  "created": "2026-09-06T15:30:00Z", "status": "done",
  "items": [
    { "n": 1, "source": "ch_main-hero_30yo_maria_v1", "deliverable": "sheet", "model": "midjourney", "platform": "midjourney",
      "prompt": "character sheet, 30yo woman, olive skin, cropped copper-red hair with undercut, lean build, burn scar left forearm, compass tattoo right wrist, front three-quarter profile, neutral grey",
      "output": "character/main-hero/cs_ch_main-hero_30yo_maria_v1_r8af0_maria_1.png", "sha256": "a4f1…", "costUsd": 0.08,
      "width": 2048, "height": 2048, "human": { "selectedFrom": 6 } },
    { "n": 2, "source": "sh_5.1_john123_v1", "deliverable": "firstFrame", "model": "nanobanana", "platform": "fal",
      "inputs": ["character/main-hero/cs_ch_main-hero_30yo_maria_v1_r8af0_maria_1.png", "location/archive/cs_lo_archive_mid-war_john123_v1_r7c21_john123_1.png"],
      "output": "s1/e2/sc5/ff_sh_5.1_john123_v1_r8af0_maria_1.png", "sha256": "0c9e…", "costUsd": 0.04, "human": { "selectedFrom": 4 } },
    { "n": 3, "source": "sh_5.1_john123_v1", "deliverable": "clip", "model": "seedance25", "platform": "fal",
      "params": { "seed": 41221, "durationSec": 8, "fps": 24 }, "inputs": ["s1/e2/sc5/ff_sh_5.1_john123_v1_r8af0_maria_1.png"],
      "output": "s1/e2/sc5/cl_sh_5.1_john123_v1_r8af0_maria_1.mp4", "sha256": "9d02…", "durationMs": 8000, "elapsedMs": 64200, "costUsd": 0.56, "human": { "selectedFrom": 3 } },
    { "n": 4, "source": "sh_5.2_john123_v1", "deliverable": "firstFrame", "model": "nanobanana", "platform": "fal",
      "output": "s1/e2/sc5/ff_sh_5.2_john123_v1_r8af0_maria_1.png", "sha256": "5b77…", "costUsd": 0.04 },
    { "n": 5, "source": "sh_5.2_john123_v1", "deliverable": "clip", "model": "seedance25", "platform": "fal",
      "params": { "seed": 41222, "durationSec": 5, "fps": 24 }, "inputs": ["s1/e2/sc5/ff_sh_5.2_john123_v1_r8af0_maria_1.png"],
      "output": "s1/e2/sc5/cl_sh_5.2_john123_v1_r8af0_maria_1.mp4", "sha256": "e310…", "durationMs": 5000, "elapsedMs": 41800, "costUsd": 0.35 },
    { "n": 6, "source": "cu_5.1_john123_v1", "deliverable": "voice", "model": "elevenlabs", "platform": "elevenlabs",
      "output": "s1/e2/sc5/vo_cu_5.1_john123_v1_r8af0_maria_1.wav", "sha256": "31cd…", "durationMs": 2500, "sampleRate": 48000, "channels": 1, "costUsd": 0.01 },
    { "n": 7, "source": "cu_5.3_john123_v1", "deliverable": "voice", "model": "elevenlabs", "platform": "elevenlabs",
      "output": "s1/e2/sc5/vo_cu_5.3_john123_v1_r8af0_maria_1.wav", "sha256": "c0de…", "durationMs": 1000, "sampleRate": 48000, "channels": 1, "costUsd": 0.01 },
    { "n": 8, "source": "cu_5.2_john123_v1", "deliverable": "ambience", "model": "elevenlabs-sfx", "platform": "elevenlabs",
      "output": "s1/e2/sc5/am_cu_5.2_john123_v1_r8af0_maria_1.wav", "sha256": "88a2…", "durationMs": 14000, "costUsd": 0.02 }
  ]
}
```

```json
{
  "filmopen": 1, "type": "cm", "target": "ch_main-hero", "author": "maria",
  "entries": [
    { "on": "ch_main-hero_30yo_john123_v1", "at": "2026-09-06T09:05:00Z", "rating": 1, "text": "Good bones. Hair is too safe for the archive — trying red." },
    { "on": "cs_ch_main-hero_30yo_maria_v1_r8af0_maria_1", "at": "2026-09-06T15:40:00Z", "rating": 1 }
  ]
}
```

### B.10 What the export produces

One timeline, *Episode 2*, 24 fps, 13 seconds (312 frames). All boundaries in this example
are frame-aligned, so no timing rounding is needed:

| Track | Clip | Start | End |
|---|---|---|---|
| V1 *Picture* | `cl_sh_5.1…` | 0:00.000 | 0:08.000 |
| V1 *Picture* | `cl_sh_5.2…` | 0:08.000 | 0:13.000 |
| A1 *Picture* | the two clips' embedded sound | as above | |
| A2 *Dialogue* | `vo_cu_5.1…` | 0:02.750 | 0:05.250 |
| A2 *Dialogue* | `vo_cu_5.3…` | 0:06.500 | 0:07.500 — leads the cut by 1.5 s |
| A3 *Ambience* | `am_cu_5.2…` | 0:00.000 | 0:13.000, 1 s in, 1.5 s out |

A marker *sc_5* at 0:00.000; a marker on each clip carrying its stem; `conform/` holding
`sh_5.1_clip.mp4`, `sh_5.2_clip.mp4`, `cu_5.1_voice.wav`, `cu_5.3_voice.wav`, `cu_5.2_ambience.wav`; a
manifest; and a report noting that the ambience take is 14 s and is trimmed to 13.

---

## Appendix C — MovieLabs OMC correspondence

Concept-level. Property names must be confirmed against the OMC-JSON v2.8 schema before an exporter is
written.

| FilmOpen | OMC concept | Notes |
|---|---|---|
| `pj` | Creative Work | title, synopsis; `contributors` and `credits` → Participants |
| `ch` | Character | `name`; `appearance` → profile / physical characteristics; `aliases` → alternate names |
| `of` | Narrative Wardrobe / Costume | linked to Character |
| `lo` | Narrative Location | `areas` → contained Locations |
| `pr` | Narrative Prop | `hero` → notable |
| `st` | — | no OMC equivalent; export as Asset with CDL sidecar |
| `se` `ep` `sq` | Creative Work structure / Sequence | nested by `scenes`, `sequences`, `episodes` |
| `sc` | Narrative Scene | blocks → scene description and dialogue; `storyDay` → Context |
| `sh` | Shot | `place` → editorial timing; with Slate where an import batch supplies one |
| take | Asset | media file with identifiers; `sha256` → identifier |
| `picks` | **Depiction** | the narrative-object-to-asset link |
| `rd` | Task and Asset provenance | `human` → participant contribution |
| `author` | Participant | owner segment |
| epoch | Context | "in which circumstances" |

---

*FilmOpen Project Specification 1.3 Draft · filmopen.ai · CC-BY-4.0*
