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 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: trueandcontextIsolation: 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 for the full trust‑boundary analysis.
2. Architecture
2.1 Process model
┌──────────────────────────────────────────────────────────────┐
│ 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:
// 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.
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):
customFfPathsetting, if configured (the--settings-json '{customFfPath:...}'override).process.resourcesPathwhen packaged (binaries shipped asextraResources).ffmpeg/<platform>-<arch>/[lib/]in local dev.
Note
On Linux, packaged builds set
LD_LIBRARY_PATH=process.resourcesPathso the shared‑library FFmpeg build can find its.sofiles. This is skipped when acustomFfPathis 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:
--config-dir <dir>CLI option (folder containingconfig.json).- A neighbour
config.jsonnext toLosslessCut.exe(Windows non‑store builds only) — enables semi‑portable use. - 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-jsonwrites directly into the store with no validation. Invalid values can corruptconfig.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:
// 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.htmlvialoadFilein production (orhttp://localhost:3001in dev).webSecurityis 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:trueas 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-networkingflag.
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) throwsNetworking is disabled.openExternalis 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
.vobfiles (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.
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).
- [ ] For offline/locked‑down environments, pin launch args to include
--disable-networking. - [ ] To centralise settings, ship a
config.jsonand 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-apion 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
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) intoffmpeg/<platform>-<arch>/and packaged as electron‑builderextraResources.
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 the7zbuild. macOS users should use thedmg.
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
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). Thecomponents/,hooks/, andutil/subfolders are where most editing behaviour lives —useFfmpegOperations,useSegments,useKeyboard,useFrameCapture,useWaveform, andsmartcut.tsare 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 asKeyboardEvent.codetokens 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.txtaggregates 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 agenerate-docsstep (docs/generated/types.mdis generated from TS types viats-morph). - Docs are first‑class: the authoritative user docs live in
docs/*.mdin‑repo and are the canonical reference reproduced (and expanded) in the 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-actionand the HTTP API), and optionally added to the native menu as awebContents.sendchannel.
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 |