A fast, focused broadcast camera & lens planning tool for multicam setups. Calculate FOV/DoF, plan camera positions in 2D & 3D, and preview live camera views. Available as a web app and Windows desktop application.
Every push to the default branch builds this repo's page from
.github/workflows/pages.yml and publishes it:
https://larszu.github.io/multicam-planner/
The workflow asks the Pages API before it configures anything. With no Pages site it still builds β that is a real check β and skips only the publishing step, with a warning and the one missing step in the run summary. A run that must stay red for a click nobody made teaches people to ignore red.
Measured 2026-09-09: published β the deploy job ran and succeeded.
LZ Multicam Planner is designed for quick, intuitive camera planning with essential features for real-world broadcast productions. Its streamlined workflow and simple interface make it perfect for fast setups and clear projectsβwithout the complexity and feature overload of traditional CAD or architecture applications.
- 377 cameras from 36 brands (Sony, Canon, Panasonic, Blackmagic, ARRI, RED, Grass Valley, Hitachi, Ikegami, JVC, Nikon, Kinefinity, Z CAM, PTZOptics, Marshall, AIDA, Avonic, BirdDog, Lumens, Vaddio, DJI, GoPro, Insta360 β¦) β 372 with a manufacturer datasheet link
- 835 lenses from 37 brands (Fujinon, AngΓ©nieux, Cooke, ARRI, Zeiss, Leitz, Canon, Sony, Sigma, Tamron, Atlas, Hawk, Panavision, DZOFilm, Laowa β¦) β 798 with a datasheet link
- 49 camera rigs with real dimensions (Jimmy Jib, Technocrane, SuperTechno, Spidercam, Panther, J.L. Fisher, Sachtler, Vinten, Newton, Slidekamera)
- 24 mounts: B4, EF, E, PL, MFT, RF, FZ, L, LPL, XPL, M12, C/CS, Z, X, K, G, XCD, integrated β¦
- Adapter system: automatic adapter detection with T-stop light loss, sensor crop info, Speed Booster support (e.g., EFβMFT)
- Custom lens support: create and save your own lenses
- Custom cameras and lenses travel with the project: the ones the placed
cameras use are written into the
.mcplan(and the.avplancameras slot) and added to the local library when the project is opened on another machine. An entry that already exists there with the same id but different data is never overwritten β the local one is kept, and a notice names it. - Favorites: star cameras and lenses for quick access
- Connectors in the Cable Planner: a camera whose manufacturer and model
match an entry of the Cable Planner's camera catalog exactly (case, spaces
and dashes aside) carries that entry's device-type GUID, and the Cable
Planner resolves it to the real connector panel. Similar names do not count β
a wrong GUID would be trusted blindly over there. Four Blackmagic bodies
whose names differ only by wording (Pocket Cinema 6K G2 / Pocket Cinema
Camera 6K G2 and the like) are assigned by hand, with the reason next to
them in
cameras.ts. Today 16 of 377 cameras (the catalog lists 20 devices). A custom camera can pick a catalog device as its port template. The catalog identities are a frozen snapshot insrc/data/cableCameraCatalogIds.ts;npm run katalog:cable-idsrefreshes it from acable-plannercheckout next to this repo, andnpm testthen names every camera whose GUID has to be added or removed.
- Settings β Device library: server address (default
https://devices.zumpelars.de, changeable, Reset returns to it), sign-in with email or username and password, a second step for the two-factor code, sign-out, and links to Create account / Forgot password on the library's website (registration happens there, not in the planner). Every build talks to the default server unless the address is changed. - Sync: on start (while signed in) and on Sync now β first up (own
entries, see below), then down. Down is incremental: only what changed
since the last
latestSeq. Library cameras appear in the camera list under Device library, library lenses in the lens list with Β· library; both are read-only (editing creates a local modified copy, like a built-in). A device markedremovedleaves the catalog. Every entry goes through the same check as cameras/lenses carried in a project file; one that fails is skipped and counted in the sync line. The cache lives in local storage and survives sign-out, so placed library cameras keep working offline. - Offline contract (shared by every planner,
syncFromin the client): the cache changes only on a successful response β offline, a timeout (15 s per request, 120 s per upload batch), a server error, an expired sign-in or signing out leave the last synced devices usable. Every server address has its own cache slot under the same storage key; a single cache from an older version is read as the slot of its server. When the server reports a lowerlatestSeqthan remembered (set up anew, restored from a backup), the whole stand is fetched again and replaces the cache β unless it is empty: then the sync fails with server was set up anew β¦ devices were kept, and nothing is deleted. Under the selector a library entry shows its status, its number of confirmations and a link to its page. - Library entries travel in the project file: the ones placed cameras use
are written into the
.mcplan(and the.avplancameras slot) aslibraryCameras/libraryLenses. On a machine without them β no account, another server, empty cache β the project calculates with the file's copy; the sync cache is never written from a file, and where the cache has the same id, the cache wins. Such an entry is marked carried in the project file under the selector. - Upload of own devices: every custom camera or lens β including a
modified copy of a built-in or a library entry β goes to the library
(
POST /api/upload) in the facet format below. The library matches by manufacturer and model: an existing device gets this planner's data as its next version instead of a second device. Upload own devices automatically (Settings, on by default) uploads on start and a few seconds after a change; Sync now uploads everything. Unchanged entries (by hash of what was sent last) are not re-sent automatically β except those still waiting for moderation: the server reportsmoderation: pending | approvedwith every result (alsoin-sync), so the status turns live once a moderator approves. Under the selector every own entry shows its last result β waiting for moderation, live, blocked (with the reason, e.g. datasheet link missing) or failed β and Uploadβ¦, which asks for the datasheet link, stores it on the entry (manufacturerUrl) and uploads at once. Not signed in, it leads to the sign-in. Changed community guidelines (guidelines-outdated) have to be accepted again on the website β the message links to<server>/guidelines. - Built-in catalog:
npm run library:publishuploadssrc/data/cameras.tsandsrc/data/lenses.tswith an admin API key (DEVICE_LIBRARY_KEY=dlk_β¦, optionalDEVICE_LIBRARY_URL); admin uploads go live at once. Entries without a datasheet link are listed and left out.--dry-runsends nothing..github/workflows/library-publish.ymlruns it after every push tomainthat touches the catalog (and by hand); without the repository secretDEVICE_LIBRARY_KEYit ends green with a notice. CI runs the dry run, so the script stays loadable (Node reads the.tsfiles directly β value imports in the modules it loads carry the.tsextension). - Facet format (the
multicampart of a library device, identical for upload and import):{ kind: 'camera', version: 1, camera: <Camera without id> }or{ kind: 'lens', version: 1, lens: <Lens without id and isCustom> }β the planner's native catalog entry, includingdeviceTypeId(the device-type GUID the Cable Planner resolves to ports),manufacturerUrlandspecSource(datasheet evidence per field). Nested, because the server strips top-level private keys such asidandnotesfrom every facet. Imported entries get the iddevlib-<slug>; the core'scategoryisCameraorLens. Mapping:src/library/facet.ts. - Token storage: the desktop app keeps the sign-in token in the system
keychain (
safeStorage, viaelectron/preload.cjs); without a keychain it is kept for the session only, never in plain text. The web build uses local storage β a browser offers a page nothing safer; signing out revokes the token on the server. The token is never written to a project file, the autosave or a log. - Another server: the content security policy (
index.html,electron/main.cjs) allows onlyhttps://devices.zumpelars.de. A different address must be added there, otherwise every request is blocked and the settings report the server as unreachable. Changing the address signs out at the old server, forgets the token (it must never reach another server) and switches to that server's cache slot and a fresh upload record; the previous server's cache stays stored and is back when you switch back. Onlyhttps://is accepted (http://for localhost). - Client:
src/utils/deviceLibraryClient.ts, an unchanged copy oflarszu/av-device-libraryclients/deviceLibraryClient.tsβ changes go there first. - Every catalogue entry also carries a DERIVED device-type GUID
(
src/data/geraetetypIds.ts, generated). The bullet above describes the hand-set and exactly-name-matched ones: 16 of 377. The other 361 went across with no identity at all, so the cable planner fell back to comparing manufacturer and model as strings β the very thing the GUID was meant to abolish. The derived id closes that gap for all of them; a hand-set one still wins, because it is older and saved plans point at it. It is derived, not invented: UUIDv5 overavplan:camera:<id>, so the same catalogue entry yields the same id in every app, and it says this catalogue entry, never these ports. Regenerate from the cable planner withnpm run katalog:uebernahme.
- Top-down drag & drop camera placement with real-time FOV cones
- Zoom, pan, snap-to-grid, background floor plan import (image & PDF, first page) β via the upload button or by dragging the file onto the 2D plan or the sidebar's Floor Plan section. Images are capped at 3000 px on the long edge, PDFs are rendered to at most 2000 px, so a phone photo no longer bloats the project file
- Two-point calibration tool for scaling imported floor plans (per axis, X or Y)
- Draw walls with 45Β° shift-snapping
- Place stage objects (person, guitarist, drums, keys, mic stand, custom)
- Interactive 3D venue visualization with FOV pyramids
- FPS-style controls (WASD + mouse look, Space/Shift up/down, Ctrl sprint)
- Touch: one finger looks around, two fingers pinch to move forward/back and drag to slide sideways and up/down β no keyboard needed on a phone or tablet
- Drag cameras in space, visualize stage meshes & venue boundaries
- Background floor plan projection, floor grid with metric labels
- Live viewfinder simulation with accurate perspective
- Ground grid, sky/horizon, stage outlines, reference silhouettes
- Overlays: rule of thirds, safe areas, crosshair, data HUD
- Pan/tilt by dragging β with the mouse or one finger; pinch with two fingers to zoom the lens
- On a narrow window the data readout moves below the image instead of taking a fixed column beside it: the picture gets the full width
- 9 sensor sizes (Full Frame, Super 35, APS-C, MFT, 1", 2/3", 1/2", 1/3", 1/2.3")
- Controls for focal length, aperture, distance, extenders
- Outputs: horizontal/vertical/diagonal FOV, image dimensions at distance, 35mm equivalent, DoF near/far/total, hyperfocal distance, person height in frame
- Depth of field in the floor plan β the sharp zone drawn as a band inside the FOV cone, so "is the band inside camera 3's focus range?" is a look instead of a calculation. Toggled next to the FOV eye in the camera list. The band deliberately reaches past the cone: the cone ends at the focus distance because it shows the frame width there β sharpness does not. A dashed outer edge means the band continues beyond the drawn area, which is the normal case once focus sits at or past the hyperfocal distance.
- Save/load projects as JSON with version tracking and unsaved changes detection
- Autosave: the open project is kept in the browser's local storage one second after the last change (and at once when the page is left) and comes back on the next start β including whether it has unsaved changes. The start screen then offers Continue last project; opening a file or starting a new project replaces it, and asks first if it has unsaved changes. If the storage is full, the status bar says so; the project stays open and can still be saved as a file.
- Stable project id: every project gets a UUID when it is created and keeps it through every save. An older file without one gets an id derived from the file (save time and venue name), so opening the same file twice gives the same id. The Cable Planner uses it to recognise a project it has seen before.
- Camera list for the Cable Planner (
*.cameras.json, formatcamera-listv3): every placed camera with manufacturer, model, device-type GUID, position and height, pan and tilt, the active mount, the set focal length, an engaged extender, the lens (manufacturer, model, zoom range, mount) and its saved PTZ presets (number, shot, segment, pan, tilt, focal length, focus, saved at). A field MultiCam does not know stays out β no default that would read like a measurement over there. v1 and v2 files are still read; a reader that only knows v2 refuses a v3 file by name instead of silently dropping the presets. The.avplanexport carries the same list inside MultiCam's own slot (domains.cameras.cameraList), so the Cable Planner does not need to know MultiCam's project format; it is rebuilt on every export and dropped on import. - Dockable panel system (FlexLayout): drag, split, tab, resize
- Layout modes: Focus (single tab) and Grid (2Γ2)
- Customizable layout presets (save/load/delete)
- Venue templates: sport, concert, church, conference, custom (save your own)
- Export: Combined 1920px PNG containing 2D plan, 3D view, camera preview, and technical data sheet
- Windows installer (NSIS) and portable build with Electron
- Native window controls, external links open in system browser
- App data stays in the
MultiCam Plannerfolder (the product name before the rename to LZ Multicam Planner);electron/main.cjspins it before first use - Header shows the Lars Zumpe Medienproduktion signet, Settings β About the main logo
(
src/assets/brand/, original contour files, Navy/Off-White by theme)
| Technology | Version |
|---|---|
| React | 18.3 |
| TypeScript | 5.7 |
| Vite | 6.0 |
| Zustand | 5.0 |
| React-Konva | 18.2 |
| Three.js / React Three Fiber | 0.170 / 8.17 |
| Tailwind CSS | 3.4 |
| FlexLayout-React | 0.8 |
| Electron | 41.2 |
| electron-builder | 25.1 |
Source language: en. When this planner gets its translation layer, the
English string in t('ns.key', 'English source') is the source text β the one
that appears when a key has no translation β and German lives in an override
dictionary. That is how the copy inside av-planner-suite is already built
(482 German override keys in 14 files); turning the direction around would mean
touching ~500 strings again for no visible gain.
This is a property of this repository, decided on 2026-09-08 (E-17/E-20):
sony-camera-bridge is English-source as well, while cable-planner and
light-planner are German-source. Upstream here still has no i18n at all and
a hard-coded German UI β that gap is tracked as B-25. npm run lang:check
holds the declaration today and starts measuring by itself as soon as the first
fallback string appears. The machine-readable copy sits in package.json under
avplan.sourceLanguage.
npm install
npm run dev
# Open http://localhost:4182# Run in dev mode
npm run desktop
# Build Windows installer & portable
npm run dist:win
# Build macOS DMG + ZIP (x64 + arm64, must run on macOS)
npm run dist:macApp icons come from build/icon.svg and build/favicon.svg;
node scripts/gen-icon.mjs renders build/icon.png, build/icon.ico and the
web icons in public/ (needs npm install --no-save sharp png-to-ico).
A GitHub Actions workflow (.github/workflows/release-build.yml) automatically
builds Windows and macOS artifacts whenever a release is published and attaches
the binaries (NSIS installer, portable .exe, DMG, ZIP) directly to the release
page. It can also be triggered manually via the Actions tab for testing.
| Script | Description |
|---|---|
| npm run dev | Start local Vite dev server |
| npm run build | TypeScript check + production build |
| npm run desktop | Launch Electron in dev mode |
| npm run dist:win | Build Windows installer & portable |
| npm run dist:mac | Build macOS DMG + ZIP (x64 + arm64, host = macOS) |
| npm run preview | Preview production build |
| npm run lint | Run ESLint linter |
| npm run ci:complete | Assert every *:check script is actually run by CI |
| npm run katalog:cable-ids | Refresh the snapshot of the Cable Planner camera catalog (GUIDs) |
| npm run library:publish | Upload the built-in catalog to the device library (DEVICE_LIBRARY_KEY, --dry-run) |
docs/MERGE_INTO_CABLE_PLANNER.mdβ superseded, kept as analysis. It describes folding MulticamPlanner intocable-planner; the suite went the other way (ADR-006: crowded areas move out into their own repos and the shell integrates them). Its list of dependency-free modules and its risk section still hold.docs/venue-suite-architecture.mdβ extends that guide withlight-plannerand a shared venue data model. The shared-model part is built (@avplan/*); the merge part is superseded too.
src/avplan/<name>/ holds byte-identical copies of packages from
av-planner-suite/packages/<name>/src, each with a MANIFEST.json (SHA-256
per file). Today that is @avplan/floorplan: the floor-plan loader (image/PDF,
drag & drop handlers) and the venue-exchange schema, which
src/utils/venueExchange.ts re-exports next to MultiCam's own conversion.
Never edit these folders here β change the package in the suite and run
npm run pakete:verteilen there. src/__tests__/avplanKopien.test.ts hashes
every file against its manifest and fails on changed or unlisted files.
npm run docs:reachable fails the build if a document under docs/ is not
reachable by links from an entry page. Both were orphaned until 2026-09-04.
If LZ Multicam Planner saves you time on your next show, consider buying me a coffee:
Donations are completely optional β the app stays free to use. It is proprietary software, not open source. π
ProprietΓ€r β Β© 2026 Lars Zumpe, alle Rechte vorbehalten. Nutzung der verΓΆffentlichten Builds ist kostenlos; Weiterverbreitung und abgeleitete Werke sind es nicht. Siehe LICENSE.
Feedback, issues, feature requests, and contributions are welcome!
Demo & contact: larszu.github.io
