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.