EZLaybacks desktop — onboarding
One sentence: an Electron app where the renderer previews music against a video in real time, and a Python worker renders the real export with ffmpeg.
The three processes
- Renderer (
src/renderer.js+renderer.html) — the whole UI. Vanilla JS, one big file. Web Audio API for preview: each track is an<audio>element feeding a GainNode; sliders move gains live. Nothing here touches ffmpeg. - Main (
src/main.js) — file I/O, window, menus, and the bridge. Spawns the Python worker once at launch and speaks newline-delimited JSON to it over stdin/stdout. Also owns auth (src/auth.js) — tokens never reach the renderer. - Python worker (
../worker/, not in this site) — loudness analysis (pyloudnorm) and export (ffmpeg). The renderer's gain values are snapshotted and sent over at Save time.
Preview vs export
Preview = Web Audio gains in the renderer, real time, no files written. Export = same gain numbers applied by ffmpeg in the worker. Not bit-identical, by design. What you hear is what gets written, but the signal path differs.
The timeline mental model
Two clocks. The video element is the master; every track position is derived from it. Two coordinate spaces: video time and track time. src/timeline.js is the tiny pure module that maps between them — trackTimeFor (video→track) and videoTimeForTrack (track→video), both anchored on the Music Start marker (mStart), which falls back to the video In point when unset.
Key modules
src/renderer.js— UI, playback, seeking, trim handles, Music Start marker, save flow. Start reading at the top-of-file overview block.src/timeline.js— video↔track time math. Read this before touching anything playback-related.src/automix.js— the gain math behind AUTO-MIX (LUFS-based, pure functions).src/track-fade.js— the scheduled tail fade when a track outlasts the video; gain changes must reschedule it.src/main.js— IPC bridge, worker spawn, dialogs, protocol handlers.src/auth.js— sign-in, refresh, Ed25519 entitlement verification, safeStorage. Main-process only.src/preload.js— the contextBridge surface; the only door between renderer and main.src/settings.js/src/settings-store.js— persisted prefs (plain JSON, atomic writes).src/secrets.js— encrypted-at-rest secrets via safeStorage; refuses plaintext.src/entitlement-verify.js— signature check for the entitlement payload.src/updater.js— electron-updater wiring against updates.ezlaybacks.com.src/project.js— .ezlproj save/restore of a working session.
Rules that bite
- One active track at a time; all playback flows through Preview.
- Master volume is monitor-only — never exported.
- No DAW jargon in any user-facing string (no dB, LUFS, normalize, limiter).
- "Original Audio", never "VOFX".
Running it
Backend: cd customer-app && docker compose --profile stack up -d. App: cd desktop && npm start (venv active so the worker finds its deps). Tests: cd desktop && npm test — pure-math and scheduling tests, no Electron needed.
Use the sidebar to browse modules; every function is JSDoc'd.