# LosslessCut Admin & Developer Guide

This guide is the source‑verified architectural and operational reference for **LosslessCut** — the cross‑platform, FFmpeg‑powered lossless video/audio editor by Mikael Finstad. It is written for administrators deploying the app at scale, power users automating it, and developers building, packaging, or contributing to it.

Where the [User Guide](/guides/mifi/lossless-cut/user-guide/) covers *using* the application, this guide covers how it is *built*: the Electron three‑process architecture, the FFmpeg integration, the configuration store, the security model, the packaging matrix, and the module‑level internals.

---

## 1. Executive Summary

LosslessCut is a **desktop application** (not a service or library) built with **Electron 42**, **React 19**, and **TypeScript 6**, bundled by **electron‑vite** (Vite 7). It wraps platform‑specific **FFmpeg/FFprobe** binaries to perform near‑instant, lossless stream‑copy operations on media files.

| Attribute | Value |
|---|---|
| Project | `mifi/lossless-cut` |
| Version analysed | `3.69.0` (commit `260605a`) |
| App ID | `no.mifi.losslesscut` |
| License | GPL‑2.0‑only (FFmpeg bundled under GPL v2+) |
| Runtime | Electron 42 (Chromium 148 renderer, Node 24 main) |
| UI stack | React 19 + Radix UI Themes + SCSS/CSS Modules |
| Build tool | electron‑vite 5 / Vite 7, Yarn 4.11 |
| Languages | TypeScript (strictest) |
| Localisation | i18next, 37 locales |
| Packaging | electron‑builder 26 (mac/win/linux + app stores) |
| Source size | ~768 K chars / 311 files / 170 code files |

**Architectural essence:** three Electron processes (main / preload / renderer) communicate over an RPC bridge. The **renderer** (React app) holds the editing logic and, unusually, has **direct Node and FFmpeg access** via `@electron/remote`. The **main process** owns the native menu, window lifecycle, configuration store, FFmpeg process spawning helpers, the optional HTTP API, update checks, and logging.

> [!IMPORTANT]
> LosslessCut deliberately runs the renderer with `nodeIntegration: true` and `contextIsolation: false`. This is a conscious trade‑off, not a vulnerability — the editing logic must call FFmpeg and the Node filesystem directly. See [§5 Security Model](#5-security-model) for the full trust‑boundary analysis.

---

## 2. Architecture

### 2.1 Process model

```text
┌──────────────────────────────────────────────────────────────┐
│ Electron App (no.mifi.losslesscut)                           │
│                                                              │
│  ┌────────────────┐   IPC / @electron/remote   ┌───────────┐ │
│  │  MAIN PROCESS  │ <························>  │ RENDERER  │ │
│  │  src/main/     │                            │ (React)   │ │
│  │                │   preload RPC Proxy        │ src/      │ │
│  │ • BrowserWindow│ <───── window.electron ──  │ renderer/ │ │
│  │ • Native menu  │                            │ src/      │ │
│  │ • configStore  │   webContents.send(chan)   │           │ │
│  │ • FFmpeg spawn │ ─────── menu events ─────> │ Chromium  │ │
│  │ • HTTP API     │                            │ 148       │ │
│  │ • updateCheck  │                            └─────┬─────┘ │
│  │ • winston log  │                                  │       │
│  └───────┬────────┘                                  │       │
│          │ execa spawn                               │ HTML5 │
│          ▼                                           ▼ <video>│
│   ┌─────────────┐                            playback engine  │
│   │ ffmpeg /    │  bundled per-platform                       │
│   │ ffprobe     │  (extraResources)                           │
│   └─────────────┘                                             │
└──────────────────────────────────────────────────────────────┘
```

| Process | Source | Runtime | Responsibility |
|---|---|---|---|
| **Main** | `src/main/` | Node 24 (Electron) | Window/menu lifecycle, config, FFmpeg spawning, HTTP API, updates, logging, i18n backend |
| **Preload** | `src/preload/` | Node (sandbox‑less) | Exposes `window.electron` RPC proxy to the renderer |
| **Renderer** | `src/renderer/src/` | Chromium 148 | React UI, timeline/segments logic, project import/export, FFmpeg arg construction, HTML5 video playback |
| **Common** | `src/common/` | Shared | Constants, shared types, ffprobe types, utilities, i18n maps |

### 2.2 The three IPC surfaces

Communication between renderer and main happens over **three distinct channels** — extraction tools see none of these as "endpoints," so they are documented here from source.

**1. The RPC proxy (`window.electron`).** The preload script installs a `Proxy` that forwards every property access as an `ipcRenderer.invoke('__electron_rpc__', method, args)` call. The main process resolves the method against a `remoteApi` object:

```ts
// src/preload/index.ts
const apiProxy = new Proxy({} as RemoteRpcApi, {
  get(_t, method) {
    return (...args) => ipcRenderer.invoke('__electron_rpc__', method, args);
  },
});
window.electron = apiProxy;
```

The `remoteApi` (main process) exposes: `pathExists`, `downloadMediaUrl`, `fileTypeFromFile`, `focusWindow`, `quitApp`, `setProgressBar`, `sendOsNotification`, `writeClipboardText`, `readClipboardText`, `openExternal`.

**2. `@electron/remote` legacy API.** A richer `remoteApiLegacy` object is exposed via `@electron/remote`, giving the renderer direct in‑process access to main‑side modules: `ffmpeg`, `i18n`, `compatPlayer`, `configStore`, the platform flags (`isLinux`/`isWindows`/`isMac`/`platform`/`arch`), `isDev`, `lossyMode`, `pathToFileURL`, and `hasDisabledNetworking`.

**3. Native‑menu → renderer events.** The native application menu (`src/main/menu.ts`) drives the UI by calling `mainWindow.webContents.send('<channel>')`. These channel names form the **command catalog** — see [§9.2](#92-menu-command-catalog).

### 2.3 FFmpeg integration

FFmpeg is the engine. The main process (`src/main/ffmpeg.ts`) spawns the bundled `ffmpeg`/`ffprobe` binaries via **execa**, tracking running processes in a `Set` so they can be aborted.

Binary resolution order (`getFfPath`):

1. `customFfPath` setting, if configured (the `--settings-json '{customFfPath:...}'` override).
2. `process.resourcesPath` when packaged (binaries shipped as `extraResources`).
3. `ffmpeg/<platform>-<arch>/[lib/]` in local dev.

> [!NOTE]
> On Linux, packaged builds set `LD_LIBRARY_PATH=process.resourcesPath` so the shared‑library FFmpeg build can find its `.so` files. This is skipped when a `customFfPath` is used. Custom FFmpeg paths do **not** work on the Mac App Store build due to sandbox restrictions.

Key FFmpeg‑backed operations in the main process:

| Function | FFmpeg behaviour |
|---|---|
| `runFfmpegProcess` / `runFfmpegWithProgress` | Generic export with progress parsing |
| `runFfmpegConcat` | Stream‑copy concatenation via stdin concat list |
| `runFfprobe` | Metadata probe (`-show_format`, JSON), with timeout |
| `renderWaveformPng` | Two‑stage pipe: extract audio → `showwavespic` filter → PNG |
| `detectSceneChanges` | `select='gt(scene,N)',metadata=print` |
| `blackDetect` / `silenceDetect` | `blackdetect` / `silencedetect` filters, parsed into segments |
| `captureFrames` / `captureFrameToFile` / `captureFrameToClipboard` | Frame export (image2), JPEG quality mapped from 0–1 |
| `createMediaSourceProcess` | Compatibility playback: re‑encode to fragmented MP4 (`libx264 ultrafast`, AAC) for the renderer's MediaSource |
| `downloadMediaUrl` | HTTP/HLS stream copy (gated by networking flag) |

Progress is parsed from FFmpeg's stderr `time=` lines (`parseFfmpegProgressLine`) against the known duration.

### 2.4 Playback architecture

The renderer plays media with the **native Chromium HTML5 `<video>` engine** — the same one in Google Chrome. When Chromium can't decode a file (e.g. some 10‑bit/HEVC content), LosslessCut falls back to **FFmpeg‑assisted software decoding** (`compatPlayer.ts` + `createMediaSourceProcess`), streaming a re‑encoded fragmented MP4 into a MediaSource. This affects preview only — never the lossless export. The `enable-blink-features=AudioVideoTracks` Chromium switch is enabled to expose track selection.

### 2.5 Project/EDL formats

The renderer (`src/renderer/src/edlFormats.ts`, `edlStore.ts`, `cmx3600.ts`) implements bidirectional conversion between the timeline's segment model and ~13 import and 7 export formats (LLC/JSON5, CSV, EDL, FCPXML, XMEML, CUE, PBF, SRT, OTIO, YouTube chapters). The native `.llc` file is JSON5.

---

## 3. System Requirements

### 3.1 End‑user runtime

LosslessCut bundles its own FFmpeg and Electron runtime — there are **no external runtime dependencies** for end users. OS support tracks Electron:

| OS | Minimum (current builds) |
|---|---|
| macOS | macOS 12+ (v3.66 dropped macOS 11) |
| Windows | Windows 10+ 64‑bit (v3.52 dropped 8.1) |
| Linux | 64‑bit; glibc per Electron (see issue #2909) |

> [!WARNING]
> On Windows, the app must run from the `C:` drive, must not be relocated after extraction, and its path must not contain non‑ASCII characters. Violating these causes blank‑window/crash‑on‑launch failures — a frequent support issue.

### 3.2 Developer / build host

| Requirement | Version |
|---|---|
| Node.js | 22+ (toolchain targets `node24.15`) |
| Package manager | Yarn 4.11.0 (`packageManager` field, `node-modules` linker) |
| FFmpeg builds | Downloaded per‑platform via `download-ffmpeg-*` scripts |
| 7‑Zip | Required for Windows FFmpeg extraction |
| Xcode + signing | macOS notarization / MAS provisioning profiles |

---

## 4. Configuration Reference

Configuration is managed by **electron‑store** and persisted to `config.json` inside the OS `appData` (userData) folder. The store is initialised in `src/main/configStore.ts` with a complete `defaults` object — this object is the authoritative configuration schema.

### 4.1 Storage location and precedence

`getConfigPath()` resolves to, in order:

1. `--config-dir <dir>` CLI option (folder containing `config.json`).
2. A neighbour `config.json` next to `LosslessCut.exe` (Windows non‑store builds only) — enables semi‑portable use.
3. The default `app.getPath('userData')/config.json`.

The store is created with up to **5 retries** (2 s apart) to tolerate transient file locks; failure throws `Timed out while creating config store`.

### 4.2 Configuration migrations

On init, `configStore.init()` performs in‑place migrations and **backs up** the config before risky changes (`config.json.backup-v<appVersion>-<oldVersion>-<timestamp>`):

| Migration | Action |
|---|---|
| `enableTransferTimestamps` → `treatOutputFileModifiedTimeAsStart` | Renamed key |
| `cleanupChoices.closeFile` | Defaulted to `true` if missing |
| `customOutDir` → `recentCustomOutDirs` | Converted to list |
| Keybinding format v1 → v2 | `"Ctrl+Shift+S"` strings → `"ControlLeft+ShiftLeft+KeyS"` code form (with backup) |

The config carries a `version` field (currently `2`).

### 4.3 Full settings catalog

Selected from the `defaults` map (grouped by concern). All keys are read/written via `configStore.get/set`.

**Cutting & export**

| Key | Default | Meaning |
|---|---|---|
| `keyframeCut` | `true` | Keyframe (`-ss` before `-i`) cut mode |
| `enableSmartCut` | `false` | Experimental frame‑accurate cut |
| `invertCutSegments` | `false` | Export between segments (Yin‑Yang) |
| `autoMerge` | `false` | Default to merge‑cuts export mode |
| `autoDeleteMergedSegments` | `true` | Delete intermediate files after merge |
| `segmentsToChapters` / `segmentsToChaptersOnly` | `false` | Map segments to chapters |
| `exportConfirmEnabled` | `true` | Show export options dialog |
| `movFastStart` | `true` | MOV faststart |
| `avoidNegativeTs` | `make_zero` | FFmpeg `avoid_negative_ts` |
| `preserveMetadata` | `default` | Metadata retention level |
| `preserveChapters` | `true` | Keep chapters |
| `preserveMetadataOnMerge` | `false` | Keep metadata when merging |
| `preserveMovData` / `preserveChapters` | varies | MOV `udta`/chapters |
| `ffmpegExperimental` | `false` | Pass FFmpeg `-strict experimental` |
| `cutFromAdjustmentFrames` / `cutToAdjustmentFrames` | `0` | Shift start/end by N frames |
| `outFormatLocked` | *(unset)* | Lock output format |
| `safeOutputFileName` | `true` | Sanitize output names |
| `outSegTemplate` / `mergedFileTemplate` / `mergedFilesTemplate` | *(unset)* | Name templates |
| `outputFileNameMinZeroPadding` | `1` | Min zero‑padding for indices |

**Capture (snapshots)**

| Key | Default | Meaning |
|---|---|---|
| `captureFormat` | `jpeg` | `jpeg`/`png`/`webp` |
| `captureFrameMethod` | `videotag` | `videotag` or `ffmpeg` |
| `captureFrameQuality` | `0.95` | 0–1 quality |
| `captureFrameFileNameFormat` | `timestamp` | Naming scheme |

**Playback & decoding**

| Key | Default | Meaning |
|---|---|---|
| `playbackVolume` | `0.3` | Default volume |
| `enableAutoHtml5ify` | `true` | Auto‑convert for preview |
| `enableNativeHevc` | `true` | Hardware HEVC decode |
| `ffmpegHwaccel` | `none` | HW accel for detect/export |
| `waveformHeight` | `40` | Waveform px height |
| `waveformMode` / `thumbnailsEnabled` / `keyframesEnabled` | varies | Timeline visualisations |

**Input/UX**

| Key | Default | Meaning |
|---|---|---|
| `simpleMode` | `true` | Simple vs advanced view |
| `darkMode` | `true` | Dark theme |
| `language` | `en` | UI language |
| `keyBindings` | *(60+ defaults)* | Keyboard bindings |
| `wheelSensitivity` | `0.2` | Mouse‑wheel seek sensitivity |
| `mouseWheelZoomModifierKey` / `...FrameSeek...` / `...KeyframeSeek...` | `ctrl`/`alt`/`shift` | Wheel modifier keys |
| `segmentMouseModifierKey` | `shift` | Drag‑to‑resize modifier |
| `keyboardSeekAccFactor` | `1.03` | Seek acceleration |
| `keyboardNormalSeekSpeed` / `keyboardSeekSpeed2` / `keyboardSeekSpeed3` | `1`/`10`/`60` | Seek speeds |
| `invertTimelineScroll` | *(unset)* | Invert scroll direction |

**Files, instances & updates**

| Key | Default | Meaning |
|---|---|---|
| `enableCustomOutDir` / `recentCustomOutDirs` | `false` / `[]` | Custom output dir |
| `storeProjectInWorkingDir` | `true` | Where `.llc` lives |
| `autoSaveProjectFile` | `true` | Auto‑write `.llc` |
| `enableOverwriteOutput` | `true` | Allow overwriting outputs |
| `cleanupChoices` | `{trashTmpFiles, askForCleanup, closeFile, cleanupAfterExport}` | Cleanup behaviour |
| `askBeforeClose` | `false` | Confirm on quit |
| `allowMultipleInstances` | `false` | Allow multiple instances |
| `enableUpdateCheck` | `true` | GitHub update check |
| `storeWindowBounds` / `windowBounds` | `true` / *(unset)* | Persist window geometry |
| `customFfPath` | *(unset)* | Custom FFmpeg folder |

> [!CAUTION]
> Overriding settings via `--settings-json` writes directly into the store with no validation. Invalid values can corrupt `config.json`. The migration backup mechanism only triggers on keybinding‑format changes, not on arbitrary overrides.

---

## 5. Security Model

### 5.1 The renderer trust boundary (security inversion)

LosslessCut **intentionally disables** Electron's standard renderer hardening:

```ts
// src/main/index.ts — createWindow()
webPreferences: {
  contextIsolation: false,
  nodeIntegration: true,
  webSecurity: !isDev,           // enabled in production
  preload: '../preload/index.cjs',
}
// @electron/remote is initialised and enabled per-window
```

This is the opposite of the hardened `contextBridge` playbook. The reason is structural: the React editing logic calls FFmpeg, the filesystem, and Electron APIs **directly** in the renderer through `@electron/remote`. The honest trust model is therefore:

- The renderer is **fully trusted code** shipped inside the app's ASAR — there is no untrusted web content and no plugin system.
- The mitigating control is that the window only ever loads **local content**: `out/renderer/index.html` via `loadFile` in production (or `http://localhost:3001` in dev). `webSecurity` is on in production.
- There is **no remote code execution surface** by design: no arbitrary URL navigation, no third‑party plugins running in‑process.

> [!IMPORTANT]
> When hardening a managed deployment, do not treat `nodeIntegration:true` as a defect to "fix" — it is required for the app to function. The meaningful controls are OS‑level (sandboxing via the App Store builds) and the `--disable-networking` flag.

### 5.2 Networking controls

All outbound networking funnels through `src/main/networking.ts`. The `--disable-networking` CLI flag sets a global that makes `throwIfDisabledNetworking()` reject:

- `downloadMediaUrl` (HTTP/HLS download) throws `Networking is disabled`.
- `openExternal` is blocked (external links won't open).
- The update check is skipped.

For an air‑gapped or privacy‑sensitive deployment, launch with `--disable-networking`. Store builds additionally never run the update checker (`isStoreBuild`).

### 5.3 HTTP API exposure

When `--http-api` is passed, an Express server (`src/main/httpServer.ts`) binds to **`127.0.0.1`** (forced IPv4) on the chosen port (default `8080`). It exposes `POST /api/action/:action` and `POST /api/await-event/:eventName`.

> [!WARNING]
> The HTTP API has **no authentication**. Any local process that can reach `127.0.0.1:<port>` can drive the app (open files, export, etc.) while it is enabled. Only enable it when needed, and be aware of multi‑user machines. It is off by default.

### 5.4 App Store sandboxing

The Mac App Store (MAS) and Microsoft Store (AppX) builds run inside the OS sandbox. Consequences:

- Custom FFmpeg path (`customFfPath`) does not work on MAS.
- The MAS build cannot open `.vob` files (Apple restriction) and must prompt for an output directory before writing.
- Entitlements are declared in `entitlements.mas.plist` / `entitlements.mas.inherit.plist` / `entitlements.mas.loginhelper.plist`.

### 5.5 Single‑instance enforcement

By default the app enforces a single instance via `requestSingleInstanceLock`. A second launch passes its argv (files or `--keyboard-action`) to the running instance via the `second-instance` event. Setting `allowMultipleInstances: true` disables this (experimental — Electron does not fully support multiple instances).

---

## 6. Operations

### 6.1 Logging

Logging uses **winston** (`src/main/logger.ts`). A rotating file transport writes to `app.log` in the userData folder (or the working dir in dev), capped at **1 MB per file, 100 files**, level `debug`. A console transport mirrors output.

```text
Windows  %APPDATA%\LosslessCut\app.log
macOS    ~/Library/Application Support/LosslessCut/app.log
Linux    ~/.config/LosslessCut/app.log
```

Reach the log quickly via **Help → Log file**, and the config via **Help → Configuration file**.

### 6.2 Update checking

On launch (non‑store, networking enabled, `enableUpdateCheck`), `updateChecker.ts` queries the GitHub API (`GET /repos/mifi/lossless-cut/releases/latest` via Octokit) and uses `semver.lt` to compare. A newer version adds a **"New version!"** menu entry linking to the release. There is no auto‑download/auto‑install — updates are manual.

### 6.3 Window state persistence

Window geometry (position, size, maximized) is debounced (500 ms) and saved to `windowBounds` when `storeWindowBounds` is on. On launch, saved bounds are validated against the current display layout before being applied.

### 6.4 Crash & error handling

`electron-unhandled` is configured with `showDialog: true`, surfacing unhandled main‑process errors to the user and logging them. The renderer wraps the app in an `ErrorBoundary`.

### 6.5 Managed deployment checklist

- [ ] Distribute the appropriate per‑platform artifact (see [§7](#7-build--distribution)).
- [ ] For offline/locked‑down environments, pin launch args to include `--disable-networking`.
- [ ] To centralise settings, ship a `config.json` and point at it with `--config-dir`, or place it next to the EXE (Windows).
- [ ] Confirm the app runs from a local, ASCII‑only path on `C:` (Windows).
- [ ] Do **not** enable `--http-api` on shared/multi‑user hosts unless required.
- [ ] Verify FFmpeg binaries are present in `resources/` (bundled automatically by electron‑builder).

---

## 7. Build & Distribution

### 7.1 Build pipeline

```bash
yarn install            # postinstall runs electron-builder install-app-deps
yarn download-ffmpeg-<platform>-<arch>   # fetch FFmpeg/FFprobe binaries
yarn build              # generate-icon + electron-vite build (main/preload/renderer)
yarn pack-mac | pack-win | pack-linux    # electron-builder packaging
```

`electron-vite` builds three targets from `electron.vite.config.ts`:

| Target | Build target | Notes |
|---|---|---|
| main | `node24.15`, sourcemaps | Dependencies externalized |
| preload | `node24.15`, CJS output | |
| renderer | `chrome148`, React plugin | Dev server on `127.0.0.1:3001` |

The full CI gate is `yarn check`: `tsc` (project references, strictest) → `eslint` → `vitest` → `build` → license check → i18n scan → `dedupe --check` → `generate-docs`.

> [!NOTE]
> FFmpeg binaries are **not** in the repo; they are downloaded from `mifi/ffmpeg-builds` / `mifi/ffmpeg-build-script` (GPL shared builds, FFmpeg 8.0) into `ffmpeg/<platform>-<arch>/` and packaged as electron‑builder `extraResources`.

### 7.2 Distribution matrix (electron‑builder)

App ID `no.mifi.losslesscut`; artifact name `${productName}-${os}-${arch}.${ext}`. ASAR with `smartUnpack: false`.

| Platform | Targets | Arches | Signing |
|---|---|---|---|
| **macOS** | `dmg` + `mas` (universal) | x64, arm64 (dmg); universal (mas) | hardenedRuntime + notarize (dmg); MAS provisioning profile |
| **Windows** | `7z` + `appx` | x64, arm64 | AppX identity `57275mifi.no.LosslessCut` |
| **Linux** | `tar.bz2`, `AppImage`, `snap` | x64 (all), arm64 + armv7l (tar.bz2) | Snap base `core22`, plugs `default`+`removable-media` |

> [!CAUTION]
> The MAS and AppX artifacts are named `*-DONT-USE-THIS-FILE.${ext}` — they are store‑submission artifacts, not direct downloads. The AppX is unsigned and does not work when sideloaded; Windows users should use the `7z` build. macOS users should use the `dmg`.

### 7.3 File associations

Both Windows and macOS builds register **file associations** as a "Viewer" role for ~40 video extensions (mp4, mkv, mov, avi, webm, ts, m2ts, mxf, 3gp, flv, vob, …) and ~40 audio extensions (mp3, aac, flac, wav, m4a, opus, ogg, wma, …). These let users open media in LosslessCut from the OS file manager.

### 7.4 Internationalisation build

Translations live in `locales/<lang>/translation.json` (37 languages) and are packaged as `extraResources`. The i18n keys are extracted from source by `i18next-cli` (`yarn scan-i18n`); strings use the text itself as the key (`keySeparator: false`, `nsSeparator: false`). Both main and renderer initialise i18next; the main process uses `i18next-fs-backend` to load from the resources `locales/` folder (overridable with `--locales-path`).

---

## 8. Repository Structure

```text
lossless-cut/
├── src/
│   ├── main/                 # Electron main process (Node)
│   │   ├── index.ts          # Entry: window, IPC, CLI parse, init
│   │   ├── menu.ts           # Native application menu (command catalog)
│   │   ├── ffmpeg.ts         # FFmpeg/FFprobe spawning + operations
│   │   ├── ffmpegUtil.ts     # JPEG quality mapping, helpers
│   │   ├── compatPlayer.ts   # Software-decode playback stream
│   │   ├── configStore.ts    # electron-store config + defaults + migrations
│   │   ├── httpServer.ts     # Express HTTP API
│   │   ├── networking.ts     # --disable-networking gate
│   │   ├── updateChecker.ts  # GitHub release check (Octokit)
│   │   ├── progress.ts       # FFmpeg progress line parser
│   │   ├── logger.ts         # winston logging
│   │   ├── i18n*.ts          # i18next main-side init + locale paths
│   │   └── util.ts, isDev.ts, isStoreBuild.ts, aboutPanel.ts, contextMenu.ts
│   ├── preload/index.ts      # window.electron RPC Proxy
│   ├── renderer/
│   │   ├── index.html
│   │   └── src/              # React app
│   │       ├── App.tsx       # Root component / action dispatcher
│   │       ├── Timeline*.tsx, SegmentList.tsx, BottomBar.tsx, TopMenu.tsx
│   │       ├── ffmpeg.ts, smartcut.ts, segments.ts, edlFormats.ts, ...
│   │       ├── components/   # ~70 React components (dialogs, buttons, ...)
│   │       ├── hooks/        # ~30 hooks (useSegments, useFfmpegOperations, useKeyboard, ...)
│   │       └── util/         # streams, duration, outputNameTemplate, rate-calculator
│   └── common/               # constants, types, ffprobe, i18n, util (shared)
├── locales/                  # 37 translation.json files
├── script/                   # build/release scripts (TS)
├── docs/                     # authoritative user docs (md)
├── electron.vite.config.ts   # build config
└── package.json              # deps, scripts, electron-builder config
```

> [!NOTE]
> The bulk of application logic is in `src/renderer/src/` (485 K chars). The `components/`, `hooks/`, and `util/` subfolders are where most editing behaviour lives — `useFfmpegOperations`, `useSegments`, `useKeyboard`, `useFrameCapture`, `useWaveform`, and `smartcut.ts` are the highest‑value modules for understanding cut/export behaviour.

### 8.1 Key dependencies

| Dependency | Role |
|---|---|
| `electron` 42 / `@electron/remote` | Desktop runtime + renderer↔main bridge |
| `execa` | Spawning FFmpeg/FFprobe |
| `electron-store` | Persisted config |
| `express` + `morgan` + `express-async-handler` | HTTP API |
| `i18next` + `i18next-fs-backend` + `react-i18next` | Localisation |
| `winston` | Logging |
| `@octokit/core` + `semver` | Update checks |
| `zod` + `json5` + `yargs-parser` | CLI/settings parsing & validation |
| `react` 19 + `@radix-ui/themes` + `sweetalert2` | UI |
| `@dnd-kit/*` | Segment drag‑and‑drop |
| `leaflet` + `react-leaflet` | GPS track map |
| `fast-xml-parser` + `cue-parser` + `csv-parse`/`csv-stringify` | Project format import/export |
| `smpte-timecode` + `luxon` | Timecode handling |

---

## 9. Internals Reference

### 9.1 Keyboard action registry

The action system is the backbone of the UI **and** the automation surface — the same action names drive keyboard shortcuts, `--keyboard-action`, and `POST /api/action/:action`. Default bindings are declared in `configStore.ts`. Representative actions (the full set is in the in‑app Keyboard shortcuts dialog):

`addSegment`, `setCutStart`, `setCutEnd`, `removeCurrentCutpoint`, `splitCurrentSegment`, `labelCurrentSegment`, `export`, `captureSnapshot`, `captureSnapshotToClipboard`, `copySegmentsToClipboard`, `togglePlayResetSpeed`, `togglePlayNoResetSpeed`, `seekBackwards`/`seekForwards` (+`2`/`Percent`/`Keyframe` variants), `seekPreviousFrame`/`seekNextFrame`, `jumpPrevSegment`/`jumpNextSegment`/`jumpFirstSegment`/`jumpLastSegment` (+`Seek` variants), `jumpCutStart`/`jumpCutEnd`, `jumpTimelineStart`/`jumpTimelineEnd`, `timelineZoomIn`/`timelineZoomOut`/`timelineToggleComfortZoom`, `increaseRotation`, `toggleStripAll`/`toggleStripCurrentFilter`, `goToTimecode`/`goToTimecodeDirect`, `undo`/`redo`, `increaseVolume`/`decreaseVolume`/`toggleMuted`, `toggleFullscreenVideo`, `batchPreviousFile`/`batchNextFile`/`batchOpenPreviousFile`/`batchOpenNextFile`/`batchOpenSelectedFile`, `cleanupFilesDialog`, `makeCursorTimeZero`, `toggleKeyboardShortcuts`.

> [!NOTE]
> Unknown actions raise `Action not found: ${action}` (`App.tsx`). Keybindings are stored as `KeyboardEvent.code` tokens joined with `+` (e.g. `ControlLeft+ShiftLeft+KeyZ`), migrated from the legacy human string format.

### 9.2 Menu command catalog

`src/main/menu.ts` maps menu items to renderer `webContents.send` channels. These are the application commands triggered from the native menu:

| Menu | Commands (channel names) |
|---|---|
| **File** | `openFilesDialog`, `openDirDialog`, `promptDownloadMediaUrl`, `closeCurrentFile`, `closeBatch`, `importEdlFile` (csv/csv-frames/cutlist/edl/youtube/xmeml/fcpxml/cue/pbf/srt/dv-analyzer-summary-txt/otio/llc), `exportEdlFile` (csv/csv-human/csv-frames/tsv-human/srt/llc), `exportYouTube`, `html5ify`, `fixInvalidDuration`, `decimate`, `toggleSettings` |
| **Edit** | undo/redo/clipboard roles, `extractAllStreams`, `showStreamsSelector` |
| **Segments** | `createNumSegments`, `createFixedDurationSegments`, `createFixedByteSizedSegments`, `createRandomSegments`, `reorderSegsByStartTime`, `shuffleSegments`, `combineOverlappingSegments`, `combineSelectedSegments`, `splitCurrentSegment`, `invertAllSegments`, `fillSegmentsGaps`, `shiftAllSegmentTimes`, `alignSegmentTimesToKeyframes`, `clearSegments` |
| **Tools** | `concatBatch`, `setStartTimeOffset`, `detectBlackScenes`, `detectSilentScenes`, `detectSceneChanges`, `readAllKeyframes`, `createSegmentsFromKeyframes`, `toggleLastCommands`, dev tools |
| **Help** | `toggleKeyboardShortcuts`, `openSendReportDialog`, external links (usage/FAQ/troubleshooting/feature‑request/licenses) |

### 9.3 CLI argument parsing

`src/main/index.ts` parses argv with `yargs-parser`. Booleans: `disable-networking`. Strings: `settings-json`, `config-dir`, `lossy-mode`. Other recognised: `http-api` (number/boolean), `keyboard-action`, `locales-path`. The experimental `--lossy-mode` accepts a zod‑validated `{ videoEncoder: 'libx264'|'libx265'|'libsvtav1' }`. Positional args are treated as file paths.

### 9.4 Error catalog

Errors surfaced from source (for log triage and support):

| Error message | Source |
|---|---|
| `Unable to determine file format` | `App.tsx` |
| `Action not found: ${action}` | `App.tsx` |
| `Unsupported MIME type or codec: ${mimeCodec}` | `MediaSourcePlayer.tsx` |
| `Media source process did not initialize` | `MediaSourcePlayer.tsx` |
| `Root element <xmeml>/<fcpxml> not found in file` | `edlFormats.ts` |
| `Invalid EDL type` | `edlStore.ts` |
| `ffprobe returned malformed data` | `ffmpeg.ts` |
| `Unsupported file` (`UnsupportedFileError`) | `ffmpeg.ts` |
| `Can only smart cut video with exactly one video stream` | `smartcut.ts` |
| `Smart cut only works on videos` | `smartcut.ts` |
| `Unable to determine codec for smart cut` | `smartcut.ts` |
| `Timed out while creating config store` | `configStore.ts` |
| `Networking is disabled` | `networking.ts` |

---

## 10. Troubleshooting (operational)

### 10.1 App won't launch / blank window (Windows)

Path‑related. Ensure the install path is on `C:`, ASCII‑only, and not relocated. Try `--disable-gpu` / `--disable-features=VizDisplayCompositor`. Whitelist in anti‑virus. Consider removing AppContainer restrictions.

### 10.2 App crashes on startup (Linux)

`setuid_sandbox_host` errors → run with `--no-sandbox`. For Snap/Flatpak issues, fall back to the GitHub `tar.bz2`/`AppImage` build.

### 10.3 FFmpeg not found / custom path ignored

Custom paths don't work on the MAS sandbox build. Confirm `customFfPath` points at the **folder** containing `ffmpeg`(`.exe`) and `ffprobe`(`.exe`). In packaged builds, binaries must be in `resources/`.

### 10.4 Config corruption

A bad `--settings-json` can corrupt `config.json`. Recovery: delete or restore `config.json` from a `*.backup-*` file in the userData folder (backups are written before keybinding migrations).

### 10.5 HTTP API not reachable

Confirm `--http-api` was passed and note the bound port (default 8080). It binds only to `127.0.0.1`, so remote hosts cannot reach it. Check the log for `HTTP API listening on`.

### 10.6 Export produces wrong duration / black frames

Operationally, this is almost always an un‑cuttable track being copied as‑is. Reproduce with only the video track enabled; bisect tracks; switch container to MKV/MOV; adjust `avoidNegativeTs`.

---

## 11. Contributing & Project Status

- **License:** GPL‑2.0‑only. Bundled FFmpeg is GPL v2+. Generated `licenses.txt` aggregates dependency licenses (`yarn generate-licenses`).
- **Maintainer:** single maintainer (Mikael Finstad). The project accepts contributions via the GitHub repo; see `CONTRIBUTING.md`.
- **Tooling discipline:** strictest TypeScript config, ESLint (with `eslint-plugin-unicorn`), Vitest unit tests, i18n extraction gate, license check, and a `generate-docs` step (`docs/generated/types.md` is generated from TS types via `ts-morph`).
- **Docs are first‑class:** the authoritative user docs live in `docs/*.md` in‑repo and are the canonical reference reproduced (and expanded) in the [User Guide](/guides/mifi/lossless-cut/user-guide/).

> [!TIP]
> When extending the app, note the three‑surface action model: a new user command should be wired as a keyboard action (so it's automatically available via `--keyboard-action` and the HTTP API), and optionally added to the native menu as a `webContents.send` channel.

---

## 12. Quick Reference

| Item | Value |
|---|---|
| App ID | `no.mifi.losslesscut` |
| Config file | `<userData>/config.json` (electron‑store) |
| Log file | `<userData>/app.log` (winston, 1 MB ×100) |
| HTTP API | `--http-api`, `127.0.0.1:8080`, no auth |
| Offline mode | `--disable-networking` |
| Custom config dir | `--config-dir <dir>` |
| Override settings | `--settings-json '<json5>'` |
| Run action | `--keyboard-action <name>` or `POST /api/action/<name>` |
| FFmpeg | Bundled (GPL shared 8.0), `resources/ffmpeg[.exe]` |
| Project file | `.llc` (JSON5) |
| Update source | GitHub releases (Octokit) |
| Renderer security | `nodeIntegration:true`, `contextIsolation:false` (by design) |
| License | GPL‑2.0‑only |

---

_Generated June 2026 from mifi/lossless-cut at 260605a. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
