Why Parsing a Screenplay Is Harder Than It Looks
The opening technical problem: read every screenplay format anyone might hand you, then keep up with a writer in real time.
The script-writing industry is one of the older corners of film software. Final Draft has been around since 1990. Movie Magic Screenwriter is from a similar era. Each of them has its own export format — and several of them have introduced two or three formats over the years, deprecating the old ones at their own pace. PDFs are everywhere too, but a PDF is a layout, not a script: the actual text has to be reconstructed from page coordinates.
What you get, when you sit down to read “a screenplay,” is a small zoo of incompatible files, all containing what’s supposed to be the same thing. There’s no single standard. There hasn’t been one for thirty years.
It started as a supplement
The script side wasn’t meant to be a centerpiece. In our first version it was a supporting panel: you’d import a screenplay, we’d display it next to the shot list, and a director could read along while breaking the scenes down. We weren’t trying to compete with the established screenplay tools. We were trying to make sure the script was always at hand while the real work — the shot list — was happening.
To do that we had to read the formats writers actually use. FDX (Final Draft’s XML export). PDF (the share-it-with-anyone format). RTF (the “I exported from Word” format). Plain TXT for the people who’d given up on proprietary tools entirely. None of those formats was designed for what we wanted to do with them. All of them had quirks we’d discover the hard way.
What we tried first
Each format needs its own parser, and always will. There’s no shortcut around that: FDX is XML and has to be unwrapped from its schema, PDF is page coordinates and has to be reconstructed back into running text, RTF has its own arcane escape rules, plain text is straightforward in theory and rarely plain in practice. Five different ways the same screenplay can land on disk — five different bodies of code to read them.
The format-specific parsers weren’t the problem. What we wrestled with was what came out the other side.
In the first version, every parser made its own decisions about how to represent what it had read. The FDX parser had its own idea of what a “scene heading” looked like in the output. The PDF parser had a slightly different one. The output of two parsers for the same screenplay was close enough to look interchangeable but different in small, annoying ways that downstream code had to special-case. Every consumer of the script data — the display, the shot list integration, the analytics — had to know which parser had produced what it was looking at.
So we kept the per-format parsers and standardized their output. Each parser still decodes its own format on its own terms, but the final step is always the same: emit a flat list of script elements typed as scene heading, action, character, dialogue, parenthetical, or transition. That structure is the lingua franca of how screenplays are organized — the same vocabulary screenwriters and production crews have used for decades — and once every parser converges on it, every other part of the app can read from a single representation regardless of where the file came from.
Adding a new format now means writing a new parser for that format’s quirks and producing the same standard output. The rest of the system never needs to know which file type the script started life as. The display renders an FDX import the same way it renders a Fountain file. The shot list generator reads scene headings the same way regardless of which parser produced them.
Screenwriters also break formatting rules constantly — multi-line parentheticals, unusual character names like ROBOT #1 or JOHN JR., action lines that look like character cues until you read the next line. Each parser had to learn to forgive those gracefully and still hand back a clean structure. The standardized output was the thing that let us actually do that — once you’ve decided what your output is supposed to look like, you can ask each parser, “did you give me that?” and patch the edge cases consistently.
Finding Fountain
Partway through the parser work we ran into a format we hadn’t been planning to support: Fountain.
Fountain is a plain-text screenplay syntax — designed to be readable as-is, parseable cleanly, and editable in any text editor that knows the difference between a paragraph break and a tab. The same way Markdown is to a written document, Fountain is to a screenplay. Open the file in any text editor and it reads as a screenplay even before any parser touches it. Save it and it’s still a plain-text file you can open anywhere.
Coming from formats designed in the 1990s, Fountain felt like a refreshing reset. Modern. Readable. Built for the way writers actually want to work, not for the assumptions of desktop software two decades old.
That changed our plans.
We’d already spent serious time on the parser side, plus a matching PDF export pipeline so imported scripts could be shared with anyone. By the time we had both, we’d accidentally built most of what a real screenplay editor needs. Finding Fountain was the moment we stopped seeing the script side as a “read-only panel next to the shot list” and started seeing it as a feature in its own right.
The work shifted. We’d been writing a screenplay reader. We started writing a screenplay editor.
The real problem started after import
A screenplay parser is one thing. A screenplay editor is another.
We wanted Fountain syntax to apply live as you typed — type INT. KITCHEN - NIGHT and the line becomes a scene heading; press return and you’re in action; type an uppercased name and the next line snaps to dialogue. The first version of this ran the full parser on every keystroke.
It was unusable.
Every character you typed walked the entire document, checked context, and re-classified everything. On a 90-page script, typing felt like dragging. The editor wasn’t slow — it was being asked to do too much work, on the wrong schedule.
The fix was to admit that real-time detection and import-time detection are different problems. Import has full context and can afford to be careful. Real-time only needs to know what this line just became — and 80% of the time it’s continuing dialogue or action, which doesn’t need a full document scan to figure out.
So we split the detector in two:
- Import detector — slow path, full context, runs once when a file is loaded.
- Real-time detector — fast path that looks at the previous few element types and the current line, with a cheap fallback only when those signals are inconclusive.
The fast path handles the vast majority of typing without allocating memory. Real-time formatting feels instant again. The slow path still runs when you paste a block of text or import a file, because that’s the right place to be thorough.
The unglamorous wins
A lot of the work on this parser was unglamorous in a way only people who’ve built one can appreciate:
- Unicode normalization. Scripts copied out of Google Docs or Word arrive full of smart quotes (
'"), em dashes (—), and non-breaking spaces. None of those match the regex you wrote for ASCII. Every parser now normalizes Unicode before doing anything else, with a fast-path check that skips the work if the text is already clean. - Page indicators with periods. PDFs use page numbers like
6.— a digit followed by a dot. Without specific handling, that looks like the start of a forced scene heading. Hours of “why is this dropping my page 6?” later, we have a regex that quietly removes them. - Empty-line restoration. FDX strips whitespace between elements when it exports. A correctly imported FDX with no spacing is technically right and reads terribly. The parser now inserts standard empty lines between elements during a post-processing pass, so imported scripts look the way screenwriters expect.
- Concurrent-editing safety. Two clients editing the same screenplay at once is one of the harder problems in collaborative software. We have script collaboration today, but it isn’t a full CRDT like Google Docs — that’s the right long-term answer, and we’ll get there when we can allocate the resources. Until then, the current model is good enough for the common cases. Where it isn’t, a recovery layer catches the failure, resets state, and keeps you typing instead of forcing a relaunch.
What it looks like today
You drag a .fdx, .fountain, .pdf, .rtf, or .txt into CineLog and the script appears, correctly formatted, with scene headings, character names, dialogue, and transitions in the right places. You start typing and Fountain syntax applies as you go. Smart quotes don’t break anything. Page artifacts from PDF imports disappear.
It feels like nothing happened, which is exactly the point.
What started as a supporting panel next to the shot list ended up as a feature in its own right — a place writers can genuinely work. The script editor isn’t the foundation of CineLog; the shot list still is. But the script side stopped being a passive reader and became one of the most active surfaces in the app, with its own real-time analytics, live preview, and screenplay export pipeline downstream of the same parsed structure.
Next: From Script to Shot List Without Starting From Zero — how we turned the parsed script into something more useful than just text on a screen.