Build Frame.io
KINDAreplaces $25/mosaves $300/yrback to the verdict
A watch folder that transcodes a review proxy, an unlisted page where reviewers scrub and comment on an exact frame, versions stacked so a client can compare, and an export of every note as timecode your editing application understands.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the watcher, the server and the share pages.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
Why Probing for the exact frame rate and transcoding proxies both run through them.
Get it brew install ffmpeg or your package manager. open ↗
Verify
ffprobe reports a rational frame rate for a test file - have readyfree
Why The timecode work is only testable against a real 29.97 file, and rounding errors only become visible at length.
Get it Use your own footage. If you have a drop-frame file, use that; otherwise generate one at 30000 over 1001.
Verify
Both are available and one reports a non-integer rational rate - have ready10 to 40 US dollars a month depending on storage and transfer
Why Proxies are large and clients stream them. A small instance will transcode slowly and serve worse.
Get it A virtual server with enough disk for your active projects and bandwidth you have checked the price of.
Verify
A proxy transcodes and streams smoothly to another network - installfree if you already have one
Why Phase 5's export is only correct if the markers land where the comments were, and only the editing application can tell you.
Get it Whichever you already use. The marker import format differs, so target the one you actually work in.
Verify
Exported markers import and land on the intended frames
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- assets(id, source_path, title, frame_rate_num, frame_rate_den, drop_frame, duration_frames, timecode_start_frames, proxy_key, state) - versions(id, asset_group_id, asset_id, ordinal, label, uploaded_at) · a group is one shot reviewed repeatedly - shares(id, asset_group_id, token, password_hash, expires_at, allow_download, created_at) - comments(id, version_id, frame, author_name, body, created_at, resolved_at, parent_id) - annotations(comment_id, shape_json) · a drawing over the frame, stored in normalised coordinates - The frame rate is stored as a numerator and a denominator. Storing it as a decimal is the bug that produces off-by-one frames an hour into a long file.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
WATCH_DIR | required | ./incoming | Folder watched for new exports. Files here are never modified. |
PROXY_DIR | required | ./proxies | Where review proxies are written. Treat as a cache. |
PROXY_KEYFRAME_FRAMES | required | 12 | Keyframe interval in frames. Short for scrubbing, at the cost of file size. |
PROXY_CRF | required | 23 | Quality setting for the proxy. Lower is better and larger. |
SIGNED_MEDIA_TTL_SECONDS | required | 7200 | How long a signed media URL lasts. Long enough for a review session. |
PROXY_RETENTION_DAYS | required | 120 | Age at which an inactive project's proxies are removed, after a warning. |
PUBLIC_URL | required | https://review.example.com | Used in share links. Must be HTTPS. |
The build, in order
Frame rates and timecode, before anything else
You can convert between frame number and timecode correctly for a non-integer rate.
Read the rational frame rate rather than the rounded one, plus duration and start timecode.
Files
src/probe.jsterminalffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate,nb_frames -show_entries format_tags=timecode -of json input.mov
Frame to timecode and back, handling drop-frame and a non-zero start.
Files
src/timecode.jsEvery frame of a one hour file, both directions. This is cheap and catches every rounding mistake.
Files
test/timecode.test.js
done when · tick each as it passeswatch out- Storing 29.97 rather than 30000 over 1001 accumulates error, and after an hour the computed frame is wrong. Keep the rate rational throughout.
- Drop-frame timecode skips frame numbers rather than frames: it omits numbers 0 and 1 at the start of each minute except every tenth minute, so wall clock and timecode stay close. Converting a drop-frame timecode with non-drop arithmetic is wrong by several seconds an hour, and the error grows.
A proxy that scrubs well
A reviewer can drag the playhead and see frames update immediately.
Size stable across two checks before touching a file. An export in progress is the common case here.
Files
src/watch.jsShort interval for scrubbing, matching the source frame rate exactly rather than normalising it.
terminalffmpeg -i source.mov -c:v libx264 -g 12 -keyint_min 12 -sc_threshold 0 -crf 23 -c:a aac proxy.mp4
Source and proxy must have the same duration in frames, or every comment is offset.
Files
src/verify.js
done when · tick each as it passeswatch out- A proxy with a long keyframe interval forces the player to decode from the last keyframe on every seek, which makes scrubbing feel broken even though playback is fine. Use a short interval, accept the larger file, and remember that reviewers judge this tool on scrubbing rather than on playback.
A review link that plays for a client
Someone with the link can watch and scrub without an account.
Unguessable token, optional password, expiry. No public listing anywhere.
Files
src/share.jsThe proxy is private and reachable only through a short-lived signature issued after the gate.
Files
src/media.jsArrow keys step one frame, the display shows timecode, and every seek is computed from a frame number.
Files
public/player.js
done when · tick each as it passeswatch out- An unguessable page URL with a predictable video file URL means the video circulates without the page, which for unreleased client work is exactly what the review link was supposed to prevent. Sign the media requests and keep the file private.
Comments pinned to a frame
A comment lands on the exact frame the reviewer was looking at.
Not seconds. The frame is what the reviewer saw and what the editor needs.
Files
src/comments.jsRelative to frame dimensions, so drawings scale with the display.
Files
public/annotate.jsOpen the same link on a phone and a desktop. The drawing must land in the same place.
done when · tick each as it passeswatch out- Storing an annotation in pixel coordinates makes it land in the wrong place on any other display size or if the proxy resolution changes. Normalise to the frame's dimensions, so the drawing scales with whatever the reviewer is watching on.
Versions and the notes going back
A client can compare versions, and you get every note as timecode.
One share, several versions, comments per version, with a visible current version.
Files
src/versions.jsRational rate and drop-frame flag applied. Verify a note near the end rather than the start.
Files
src/export.jsImport the markers and confirm placement. This is the step that reveals a rounding error.
done when · tick each as it passeswatch out- Exporting notes as timecode computed with a rounded frame rate produces markers that drift from the comment position, and the drift grows through the file so the last notes are the most wrong. Use the stored rational rate and the drop-frame flag for every conversion in the export.
Storage, bandwidth and cleanupproduct builder
Proxies do not fill the disk and a finished project can be archived.
Proxies are a cache. Regenerate from source if a project reopens.
Files
src/retention.jsPer project. Client review of a long cut moves real bandwidth.
Files
src/usage.jsNotes plus a manifest, readable without this application.
Files
scripts/backup.shsrc/archive.js
done when · tick each as it passeswatch out- Proxies are regenerable from the source and comments are not, so the backup priority is inverted from what the file sizes suggest. Back up the small database religiously and treat the large proxies as a cache.
That is the whole plan for Frame.io. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Camera to cloud ingest.
- Review panels inside editing applications.
- A transcode farm and a global delivery network.
- The reliability clients already expect from a service.
- Camera to Cloud ingest straight off set
- Premiere, After Effects, and Resolve review panels that sync comments to timeline markers
- a transcode farm and global CDN, so client playback is only as good as your box
- forensic watermarking, SSO, and the security posture studios require
- the iPad and mobile review apps clients actually use
- storage, transfer tooling, and uptime bundled into the seat price
- Side by side version comparison with synchronised playheads
- Comment status and approval, so a version can be signed off
- Automatic proxy regeneration when an archived project is reopened
Need the files? The project pack on the verdict page hands your agent the whole brief · more audio & video.