A local desktop and browser app for compressing and converting photos and videos to modern, compact formats (AVIF, JPEG XL, WebP, HEIC, JPEG 2000, JPEG, PNG, AV1, HEVC, H.264, WebM) without modifying the originals. It scans a folder, lets you include or exclude subfolders, previews the output against a storage budget, processes with ImageMagick and FFmpeg, and records every run so it can be undone precisely.
Everything runs on your machine. There is no account, no telemetry, and no upload. Outbound requests are limited to GitHub for toolchain checks and self-updates.
- Features
- Platform support
- Requirements
- Install and run
- Configuration
- Using the app
- Defaults
- ImageMagick
- Safety
- Troubleshooting
- Development
- Project layout
- Local API
- Privacy
- Out of scope
- Contributing
- Releasing
- License
- Local and private. The server binds to
127.0.0.1; photos are read from disk and outputs are written to disk. Nothing is uploaded. - Folder-level control. A cached directory tree with tri-state include/exclude selection. An explicit include overrides an excluded parent.
- A real plan before a long run. Every file is classified as
convert,copy, orskip, with a storage-budget projection and a measured sample estimate. - Seven output formats. JPEG XL, AVIF (default), WebP, HEIC, JPEG 2000, JPEG, and PNG share one format selector. Quality and encoder-speed sliders change meaning per format, and unsupported formats are disabled based on what the detected ImageMagick build can actually write.
- Safe conversion. ImageMagick runs in a worker pool. Pause is graceful, resume continues from the first unfinished file, and cancelling leaves valid completed files on disk.
- Useful audits. Duplicate detection, EXIF date reporting, and a visual quality comparison.
- Restartable runs. Existing outputs are skipped, and a paused job lives in the server rather than the browser tab.
- Precise undo. Each run writes a manifest. Undo deletes only files created by that run and refuses paths outside its destination.
- Managed ImageMagick. The app can download and verify the official portable build on Windows or the official AppImage on Linux.
- First-class Windows and Linux support. Native paths, folder pickers, and launchers on both platforms.
- Optional native window. Electrobun packages the same app as a Windows or Linux desktop app with Bun bundled. The browser launchers remain for source runs.
- Six built-in themes. Midnight, Daylight, Ink, Sepia, Nord, and Rose, plus Auto to follow the system light/dark preference. The choice is remembered in the browser and applied before first paint.
| Windows | Linux | |
|---|---|---|
| Supported versions | Windows 10 or 11 | Linux |
| Desktop app | Native window (WebView2) | Native window (WebKitGTK 4.1) |
| Desktop installer | win-x64-Compakta-Setup.zip |
linux-x64-Compakta-Setup.tar.gz |
| Source launcher | start.bat |
start.sh |
| Folder picker | Native PowerShell dialog | zenity or kdialog; typed-path fallback |
| ImageMagick | Portable Q16 build or system install | System ImageMagick 7 or official AppImage |
| Automatic ImageMagick download | Yes (portable Q16) | Yes on x86_64 (AppImage) |
| Stop the app | Close the window, Quit, or close the minimised server window | Close the window, Quit, or Ctrl+C in the terminal |
| CI | Windows check | Linux check |
Linux on non-x86_64 architectures works when ImageMagick 7 with write support for the formats you use is installed. The automatic AppImage installer is x86_64 only. macOS is not officially supported.
- Source runs: Bun 1.1 or newer. The desktop installer bundles its own Bun, so nothing needs to be installed first.
- Desktop runs: the WebView2 runtime on Windows (included in current Windows 10 and 11) or GTK 3 and WebKitGTK 4.1 on Linux.
- ImageMagick 7 with write support for the format you choose. Linux requires ImageMagick 7; Windows should use ImageMagick 7 as well. AVIF write support is required for the default format.
- A current browser for source runs.
- Enough free disk space for the output tree.
The source app has no separate build step. Bun runs the TypeScript server and serves the browser files directly. The desktop installers are built with Electrobun; see Releasing.
ImageMagick must be able to write the selected format. Check the formats you plan to use with:
magick -list format | grep -E "JXL|AVIF|WEBP|HEIC|JP2|JPEG|PNG" # Linux
magick -list format | findstr "JXL AVIF WEBP HEIC JP2 JPEG PNG" # WindowsA writable format shows w in its mode, usually rw+ or rw-. A build showing
r or r-- can read that format but cannot encode it. The app's Health
panel reports the selected format, and the format dropdown disables anything the
detected build cannot write.
HEIC is the common exception: many ImageMagick builds read HEIC but cannot write
it. HEIC stays in the dropdown but is disabled until a build reports HEIC rw.
The bundled Windows build used for testing (7.1.2-34) has HEIC r--; use a
build with a HEIC encoder, or choose another format, for HEIC output.
If no usable ImageMagick build is found, the app can install a managed build.
Source runs put it in imagemagick/ beside the project; the desktop app uses
its per-user app data folder. On Linux, a build that cannot write AVIF also
triggers the AppImage offer.
Download the installer for your platform from the Releases page:
| Platform | Download | Install |
|---|---|---|
| Windows 10/11 x64 | win-x64-Compakta-Setup.zip |
Unzip, then run Compakta-Setup.exe. It installs for the current user. |
| Linux x64 | linux-x64-Compakta-Setup.tar.gz |
Extract, then run ./installer. |
The desktop app opens its own window and starts the same local server on a free loopback port. Bun is bundled, so neither Bun nor a terminal is required. ImageMagick is still required, and the app offers the same managed download as the browser version.
Linux needs GTK 3, WebKitGTK 4.1, Ayatana AppIndicator, and librsvg. On Ubuntu 24.04 or Debian 13:
sudo apt install libgtk-3-0 libwebkit2gtk-4.1-0 libayatana-appindicator3-1 librsvg2-2Windows uses the WebView2 runtime that ships with current Windows 10 and 11. If the window stays blank, install the WebView2 Runtime.
Compakta includes an integrated self-updater:
- Update Checks: Checks GitHub Releases for new versions without blocking the UI.
- Background Downloads: When a new version is detected, you can choose to download the lean/slim update package (~45 MB) in the background while conversions continue uninterrupted.
- Safe Application: Once downloaded, an "Update Ready" badge appears. Clicking it allows you to restart and apply the update whenever you are ready. Any bundled ImageMagick installation is automatically preserved into persistent user storage so you never have to redownload it.
- Source Users: For git checkout users, the app indicates that a newer version is available and provides instructions (
git pull && bun install).
Clone the repository, then use the Windows or Linux launcher:
git clone https://github.com/esconer/compakta.git
cd compakta-
Install Bun if it is not already available. In PowerShell:
powershell -c "irm bun.sh/install.ps1 | iex"
Open a new terminal afterwards so
bunis onPATH. -
Install the small set of project dependencies:
bun install
-
Start the app by double-clicking
start.bat, or from a terminal:start.bat
start.bat starts the server in a detached, minimised window, waits for it to
answer, and opens http://127.0.0.1:5178. Close that minimised
Compakta window to stop the server, or use Quit in the page header.
-
Install Bun if it is not already available:
curl -fsSL https://bun.sh/install | bashOpen a new terminal afterwards so
bunis onPATH. -
Install dependencies and start the launcher:
bun install chmod +x start.sh ./start.sh
start.sh runs the server in the foreground and opens
http://127.0.0.1:5178 with xdg-open. Use Quit in the page header or
press Ctrl+C in the terminal to stop it. In a headless session, open the URL
manually.
If zenity or kdialog is installed, the folder buttons open a native picker.
Without either, type the path directly into the source or destination field.
bun install # first time, or after dependency changes
bun start # open http://127.0.0.1:5178 yourself
bun start:open # start and open the browser
bun run dev # hot-reload the server while editingTo use a different port:
PORT=6000 bun start$env:PORT=6000; bun startset PORT=6000 && bun startstart.bat assumes the default port 5178. For a custom port on either
platform, start with bun start or bun start:open instead of the launcher
scripts.
The server listens on 127.0.0.1 only. It is not exposed to your network and
has no login because it is designed to run on the same machine as the browser.
The first launch uses these defaults:
| Setting | Default |
|---|---|
| Source folder | ~/Pictures, or the home folder if that does not exist |
| Destination | <source>-avif |
| Output format | AVIF |
| Budget | 10 GB |
| Port | 5178 |
You can change the source and destination in the UI, or set startup defaults with environment variables:
set COMPAKTA_SOURCE=D:\Photos
set COMPAKTA_DESTINATION=D:\Photos-avif
start.bat$env:COMPAKTA_SOURCE = "D:\Photos"
$env:COMPAKTA_DESTINATION = "D:\Photos-avif"
bun start:openexport COMPAKTA_SOURCE="$HOME/Pictures"
export COMPAKTA_DESTINATION="$HOME/Pictures-avif"
bun start:openCOMPAKTA_DESTINATION defaults to the source path with -avif appended. The
legacy PICMAGIK_SOURCE / PICMAGIK_DESTINATION names still work as a fallback.
Source, destination, budget, and conversion policy are kept in server memory
for the current session. Restarting the server restores these defaults; use the
environment variables when you want a persistent starting point.
When the destination is still the default for the previous format, changing the
output format renames it to the new suffix, for example Photos-avif →
Photos-jxl. A destination you typed or picked yourself is never rewritten.
Existing outputs from the old format are not deleted; Sync reports them as
orphans.
-
Choose the source and destination. Type both paths, or use the Browse… buttons where a native picker is available. The destination must be outside the source; the app refuses a destination nested inside the source because the mirror would recurse into its own output.
-
Press Scan. One walk of the library builds the file list and folder tree. Later views reuse that cache.
-
Choose files and folders. Expanding a folder lists the files inside it, each with its own checkbox. Excluding a folder deselects everything beneath it; re-checking one file or one child folder creates an explicit include override. A partly selected folder shows a dash. Filter narrows the tree by file or folder name, and Expand / Collapse control the whole tree. Source-type toggles control which extensions are eligible. RAW is off by default and can be enabled in the settings.
-
Review the plan. Each file ends up in one bucket:
convert— encode to the selected output formatcopy— copy the original bytes unchangedskip— leave it out
Files below the minimum size are copied by default; clear copy under-size files to skip them instead. The budget card shows what fits. Estimate encodes a real sample instead of guessing a compression ratio. Duplicates finds identical content by size and MD5. Dates reports how many files have a real EXIF capture date, a recovered date, or no date. Compare q shows the original beside several quality levels of the selected format.
-
Choose the output format and start the conversion. The format selector sits above the quality slider; AVIF is the default. Quality, encoder speed, and parallel jobs are in the settings pane. The quality and speed labels, ranges, and notes change with the format. Overwrite is opt-in; otherwise existing outputs are skipped. Only new restricts a run to files that sync reports as pending. The format selector locks while a run is in progress so one run cannot mix formats.
-
Pause, resume, or cancel. Pause is graceful: no new work is handed out, but encoders already in flight finish and land on disk. Resume continues the same job from the first unfinished file. Cancelling keeps completed outputs, which are skipped on the next run. A paused job survives closing the browser tab; reopening the page reattaches to it.
-
Verify, sync, and undo. The local API can verify a finished run by decoding outputs and checking dimensions and
DateTimeOriginal. Sync reports new, changed, and up-to-date files, plus orphan outputs without matching sources. Runs lists previous manifests and provides undo.
The Theme picker in the header switches between Midnight (the default), Daylight, Ink, Sepia, Nord, and Rose. Auto follows the operating system's light/dark preference and updates live when the system changes. The choice is stored in browser storage and applied before first paint, so reloading does not flash the default palette. Themes change presentation only; conversion behaviour is unchanged.
The format selector offers seven encoders. AVIF is the default and keeps the pre-format behaviour; the others are opt-in per run.
| Format | Extension | Default quality | Encoder-speed control |
|---|---|---|---|
| JPEG XL | .jxl |
q80 | speed 4–9 → jxl:effort 7–2; 6 = effort 5 |
| AVIF | .avif |
q60 | speed 4–9 → heic:speed; 6 is the default |
| WebP | .webp |
q80 | speed 4–9 → webp:method 6–1; 6 = method 4 |
| HEIC | .heic |
q60 | speed 4–9 → heic:speed; needs a build with a HEIC encoder |
| JPEG 2000 | .jp2 |
q60 = 20:1 | hidden; the encoder has no useful speed knob here |
| JPEG | .jpg |
q85 | 4–5 = 4:4:4 progressive, 6 = 4:2:0 progressive, 7–9 = 4:2:0 baseline |
| PNG | .png |
q90 = zlib 9 | hidden; PNG is lossless |
The quality slider is reused with a format-specific meaning:
- Lossy quality (JXL, AVIF, WebP, HEIC, JPEG) is the encoder's quality value. Higher means better quality and larger files.
- JPEG 2000 maps the slider to a compression ratio. q60 means 20:1; higher
quality means a lower ratio and a larger file. The readout shows both, for
example
q60 (20:1). - PNG is always lossless. The slider maps to the zlib compression level,
floor(q / 10), so q90 is level 9. Higher levels compress more slowly and usually produce smaller files.
Transparency and animation follow the format:
- JXL, AVIF, WebP, HEIC (when the encoder supports alpha), JPEG 2000, and PNG keep alpha. JPEG has no alpha channel, so transparent areas are blended onto white.
- JXL, AVIF, and WebP can keep animation. Flatten GIF still forces frame 0. HEIC, JPEG 2000, JPEG, and PNG write only the first frame.
- Copied under-size files keep their original filename and extension. They are byte-for-byte copies, not re-encoded outputs.
JXL and JPEG 2000 are output formats only in this version. The scanner does not
treat .jxl or .jp2 files as convertible sources, so the app will not
re-convert its own JXL or JPEG 2000 output. JPEG, PNG, WebP, HEIC, and the other
listed source formats can still be read.
One naming collision remains. If a folder contains photo.jpg and an under-size
photo.webp, a WebP run converts photo.jpg to photo.webp and also copies
photo.webp to the same path. The converted file is written first; with
Overwrite off the copy is skipped, and with it on the copy replaces the
converted file. The app does not rename either file.
Old-format outputs are never deleted automatically. After switching from JXL to
AVIF, for example, Sync reports the existing .jxl files as orphans and
leaves them alone. Runs records the format used by each manifest.
The AVIF encoder arguments are unchanged from earlier releases:
magick <source> -auto-orient -colorspace sRGB -quality 60 -define heic:speed=6 AVIF:<output>AVIF is still the default for a fresh session, still uses q60 and speed 6 out of the box, and still writes animated AVIF from animated GIFs unless Flatten GIF is enabled. The regression checks assert the exact AVIF command, output paths, and defaults.
Resume never redoes a file. The server keeps a set of completed relative paths, and the converter also skips outputs that already exist on disk. The second check means a resume still works after a server restart, when the in-memory job list is gone.
A paused job blocks a new run until it is resumed or cancelled. Existing files in the destination are never deleted automatically.
| Setting | Default | Notes |
|---|---|---|
| Output format | AVIF | JXL, WebP, HEIC, JPEG 2000, JPEG, and PNG are selectable |
| Quality | q60 for AVIF |
Per-format default: JXL 80, WebP 80, HEIC 60, JPEG 2000 60, JPEG 85, PNG 90 |
| Resize | none | Full resolution kept for print and crop |
| Metadata | kept | -strip deliberately omitted |
| Encoder speed | 6 for AVIF/HEIC |
JXL 6 = effort 5, WebP 6 = method 4; hidden for JPEG 2000 and PNG |
| RAW | off | Toggle it on per run if you want it |
| Animated GIF | preserved | JXL, AVIF, and WebP keep animation; Flatten GIF is optional. HEIC, JPEG 2000, JPEG, and PNG write frame 0 |
| Under-size threshold | 0 MB | Convert every file; raising it copies or drops smaller files |
| Parallel jobs | 2 | Safe for high-resolution images; raise it for ordinary photos |
| Budget | 10 GB | Preview and planning target; it does not stop a run |
Under-size files. The default threshold is 0, so every file is converted.
Raising it saves time, not space: a file below the threshold is copied at its
original size. Clear Copy under-size files to skip those files entirely.
Budget. The budget panel reports what fits and which folders to drop. It is a planning aid, not a hard cap; a conversion can finish above the target.
For AVIF, the converter runs the equivalent of:
magick <source> -auto-orient -colorspace sRGB -quality 60 -define heic:speed=6 AVIF:<output>The other formats use the same read-only pipeline with their own encoder flags;
see Output formats for the mapping. There is no -strip, no
resize, and no mogrify. Originals are never written to.
aom's AV1 encoder dominates the conversion time. Representative q60 timings for a 24 MP photo:
| Speed | Time | Output | Compared with speed 5 |
|---|---|---|---|
| 4 | 51.2 s | 0.90 MB | Same size, 2.6× slower |
| 5 | 18.2 s | 0.90 MB | Byte-identical to 4 |
| 6 | 4.6 s | 1.03 MB | 4× faster, +12% bytes — default |
| 8 | 2.5 s | 1.05 MB | 7× faster, +14% bytes |
| 10 | 0.4 s | 0 bytes | Broken: exits 0 and writes an empty file |
Speed 6 is the knee of the curve. The UI caps the slider at 9, and the converter treats an empty output file as a failure regardless of the encoder's exit code.
On startup the app searches for a usable ImageMagick build. Builds in
imagemagick/ in the project root are preferred. On Linux, the app only offers
an automatic AppImage download when no system ImageMagick 7 with AVIF write
support is available; it will not replace a working system build. AVIF is the
default format, so AVIF write support remains the discovery criterion. Other
formats are enabled only when the same build reports write support for them.
If no usable build exists, the page shows an ImageMagick prompt. On Linux, a
build that cannot write AVIF also triggers the AppImage offer. Accepting it
downloads a managed build into imagemagick/. The same flow is available from
Health → ImageMagick and the header update pill.
The updater downloads the official portable Q16 archive for the current CPU
from the
ImageMagick releases page.
Windows 10+ includes tar.exe, which can unpack 7z archives; a normal 7-Zip
installation is also supported. The staged magick.exe is run and checked for
the expected version and writable AVIF support before it replaces anything.
A managed install looks like:
imagemagick/
current.txt
ImageMagick-7.1.2-34-portable-Q16-x64/
magick.exe
Unzipping the archive contents directly into imagemagick/ also works:
imagemagick/
magick.exe
...
The updater removes only folders it created (marked with
.picmagik-managed) and stale backups. Hand-placed folders are left alone.
Linux requires ImageMagick 7. The app looks in the project’s imagemagick/
folder, then common locations such as ~/.local/bin, /usr/local/bin,
/usr/bin, /snap/bin, /opt/ImageMagick/bin, and finally PATH.
If the system build is missing or cannot write AVIF, the app can install the
official x86_64 AppImage. The gcc build is preferred because it has the widest
glibc compatibility; the clang build is used when no gcc asset is available.
The download is checked against GitHub’s SHA-256 digest when supplied, checked
for the expected byte count, made executable, and tested for AVIF write support
before it is installed.
A managed AppImage looks like:
imagemagick/
current.txt
.picmagik-appimage.json
ImageMagick-7.1.2-34-gcc-x86_64.AppImage
A manually downloaded AppImage also works. Make it executable, place it in
imagemagick/, and press Check again. The newest AppImage in that folder is
discovered by filename.
AppImages normally need FUSE. If FUSE is unavailable, the app retries the first
run with APPIMAGE_EXTRACT_AND_RUN=1 and keeps that setting for later
conversion workers. For a manual test:
APPIMAGE_EXTRACT_AND_RUN=1 ./imagemagick/ImageMagick-*.AppImage -versionAutomatic AppImage installs are x86_64 only. On other architectures, install ImageMagick 7 with AVIF support from your distribution.
On page load the server asks GitHub for the latest official ImageMagick release and caches the answer for 10 minutes. If a newer managed build exists, the header shows an update pill and a one-time prompt offers the download. Choosing Not now dismisses that version; it can be checked again from Health.
Updates are refused while a conversion is running or paused, and while another ImageMagick download is in progress. If GitHub is unreachable, the app keeps using the installed build and reports the check as offline.
- The source tree is opened read-only and is never written to or deleted from.
- The destination may not be the source folder or sit inside it.
- Existing outputs are skipped unless Overwrite is enabled.
- Undo only removes paths recorded in a manifest and only inside that manifest’s destination.
- Only the temporary scratch folder is removed automatically.
- An empty output file is a failure even when the encoder exits successfully.
- A killed encoder’s partial output is deleted so the next run cannot mistake it for a finished file.
- A paused job blocks a new run until it is resumed or cancelled.
- The original-preview route serves only image extensions and caps files at 64 MB.
- The ImageMagick updater writes only inside
imagemagick/, trusts only the latest official GitHub release asset, and verifies the staged binary before replacing the current install. - Quit is refused while a conversion is running or paused, or while an ImageMagick download is in progress.
- Outputs from a previous format are never deleted automatically. Sync reports them as orphans, and undo only removes files listed in the selected run’s manifest.
Run the format check shown in Requirements. On Linux, the
distribution may still ship ImageMagick 6; use a current ImageMagick 7 package
or the AppImage. On Windows, install the portable build when the page offers
it, or unzip it into imagemagick/ manually.
The format dropdown disables any format the detected build cannot write. Check
the exact mode with magick -list format (see
Requirements). HEIC commonly appears as HEIC r-- on builds
without a HEIC encoder; install a build with HEIC write support or choose a
different format. A format reported as rw+ or rw- is writable.
Switching away from a format does not delete its old outputs. Sync lists them as orphans so you can decide what to keep.
bunis not recognized. Install Bun and open a new terminal.start.batprints the official install command if Bun is missing.- The ImageMagick download cannot extract. Make sure
tar.exeis available (Windows 10 or newer) or install 7-Zip. You can always unzip the portable archive intoimagemagick/yourself. - The folder picker does not open. PowerShell may be restricted. Type the path directly into the field instead.
- The browser does not open. Visit
http://127.0.0.1:5178manually. The server may still be starting; watch the minimised window for errors.
Permission deniedwhen starting. Runchmod +x start.shonce.- No folder picker appears. Install
zenityorkdialog, or type the path directly. The picker also needs a desktop session. - ImageMagick 7 is installed but AVIF is read-only. Install the AVIF/libheif support for your distribution, or use the official AppImage.
- The AppImage fails to mount. The app automatically retries with
APPIMAGE_EXTRACT_AND_RUN=1. You can also install FUSE 2 if your distribution provides it, or run the AppImage manually with that environment variable. - The browser does not open.
xdg-openneeds a desktop session. Openhttp://127.0.0.1:5178manually, or usebun startinstead ofbun start:open. - ARM64 or another non-x86_64 architecture. Automatic AppImage installs are unavailable. Install ImageMagick 7 with AVIF support from your distribution.
- The window is blank on Windows. Install the WebView2 Runtime linked in Desktop app. Current Windows 10 and 11 usually include it already.
- The window does not open on Linux. Install the GTK 3, WebKitGTK 4.1, Ayatana AppIndicator, and librsvg packages listed in the same section. The launcher reports a missing shared library when one is absent.
- The app stops immediately after launch. Run the platform installer rather
than the
build/output. A stable Electrobun bundle is an installer payload; only the installed copy has the metadata the launcher needs. - Where is the managed ImageMagick build? Everything writable (managed ImageMagick and compare previews) lives in the per-user app data folder, not the install folder. Uninstalling can remove it.
- Port 5178 is already in use. Stop the old server, or start on another
port with
PORT=6000 bun start(see Install and run). - The ImageMagick update check is offline. The app can still convert with the installed build. Check your network, proxy, or GitHub access.
- A paused job blocks a new run. Resume or cancel the paused job first.
bun install
bun run devbun run dev starts the server with hot reload. Open
http://127.0.0.1:5178 after it starts.
The desktop app is the same server inside an Electrobun window. src/desktop.ts
is the main-process entrypoint, electrobun.config.ts describes the bundle, and
hutch.config.ts pins the Electrobun/Hutch versions.
bun install
bun run desktop:prepare # fetch the Electrobun SDK once
bun run desktop:dev # build and launch the desktop window with reload
bun run desktop:build # stable installer for the current OS in artifacts/Hutch builds only for the host platform, so a complete release needs one build per operating system; the release workflow does both.
src/electrobun.d.ts is a minimal local declaration for the SDK surface the main
process uses. It keeps bun run typecheck working before .hutch/devkit has
been generated. Re-check it deliberately when the pinned Electrobun version
changes.
Useful checks:
| Command | Purpose |
|---|---|
bun run typecheck |
TypeScript check |
bun run src/client.check.ts |
Undefined identifiers and missing element IDs in browser scripts |
bun run src/platform.check.ts |
Cross-platform path, AppImage, and safety checks |
bun run src/format.check.ts |
AVIF regression contract, format mappings, suffix and manifest checks |
bun run check |
Run all of the above |
bun run release:check |
package.json and electrobun.config.ts versions agree |
CI runs the checks and a desktop build on both Windows and Linux. The release
workflow repeats them, then publishes the installers when a v* tag is pushed.
src/
server.ts routes, static files, SSE, job registry
desktop.ts Electrobun main process: server + native window
appdirs.ts writable data root (project root or per-user app data)
electrobun.d.ts minimal typings for the Electrobun main-process SDK
scan.ts one walk -> cached file list and directory tree
policy.ts selections -> convert | copy | skip
formats.ts output-format registry, encoder args, slider mappings
convert.ts ImageMagick worker pool, cancel, per-file results
jobs.ts run lifecycle and progress events
estimate.ts stratified sample -> measured size and time projection
sync.ts new / changed / up-to-date analysis
dedupe.ts MD5 duplicate detection
verify.ts decode and check dimensions / EXIF after a run
meta.ts identify, EXIF extraction, date recovery
budget.ts what fits under a cap, and what to cut
manifest.ts per-run record and precise undo
health.ts preflight checks
platform.ts OS constants and native path helpers
paths.ts ImageMagick discovery and path safety
imagemagick.ts release checks, portable/AppImage install and update
version.ts version parsing shared by discovery and updates
public/
index.html layout
app.js UI wiring
treelogic.js pure tree logic
formatlogic.js pure destination-suffix logic
style.css styling and theme palettes
assets/
icon.png Linux application icon
icon.ico Windows application icon
imagemagick/ managed ImageMagick (downloaded or placed by hand)
electrobun.config.ts desktop bundle: entrypoint, copied files, icons
hutch.config.ts Electrobun/Hutch version pin and build tasks
start.bat Windows launcher
start.sh Linux launcher
imagemagick/, .hutch/, .cottontail-tmp/, build/, and artifacts/ are
intentionally ignored by Git. The managed ImageMagick build is large and fetched
on demand; the rest are generated by Hutch and Electrobun.
The browser talks to a small JSON API on the same local server. It is internal,
has no authentication, and should only be reachable on 127.0.0.1. POST routes
expect a JSON object; send {} when there are no fields. Conversion routes use
the selected outputFormat; /api/run and /api/compare reject a format the
detected ImageMagick build cannot write.
| Method | Route | Purpose |
|---|---|---|
GET |
/api/status |
Config, scan state, free space, available projects |
POST |
/api/config |
Set source, destination, budget, policy |
POST |
/api/validate |
Check a candidate source/destination pair |
POST |
/api/pick |
Open the native folder picker |
POST |
/api/scan |
Walk the library |
POST |
/api/preview |
Apply policy; return counts, budget, and health |
POST |
/api/estimate |
Measured sample projection |
POST |
/api/sync |
New / changed / up-to-date analysis |
POST |
/api/duplicates |
MD5 duplicate scan |
POST |
/api/dates |
EXIF date audit |
POST |
/api/compare |
Quality ladder for one file |
POST |
/api/run |
Start a conversion |
GET |
/api/stream/:id |
SSE progress stream |
POST |
/api/pause/:id |
Graceful pause |
POST |
/api/resume/:id |
Continue a paused job |
GET |
/api/job/state?id= |
Snapshot for reattaching after a refresh |
POST |
/api/cancel/:id |
Cancel a running or paused job |
POST |
/api/verify |
Verify a finished run |
GET |
/api/manifests |
Previous runs |
POST |
/api/undo |
Remove a run’s output |
GET |
/api/health |
Preflight checks |
POST |
/api/shutdown |
Stop the local server when idle |
GET |
/api/imagemagick |
Installed build and cached release info |
GET |
/api/imagemagick/check |
Check GitHub for a newer build |
GET |
/api/imagemagick/progress |
Download / verify / install progress |
POST |
/api/imagemagick/install |
Download and install the latest managed build |
GET |
/api/source-file?rel= |
Read-only source file for the compare view |
- All scanning, conversion, and verification happen locally.
- There are no analytics, telemetry, accounts, or cloud calls.
- The only outbound requests are the ImageMagick release check and an optional
ImageMagick download, both to GitHub (
api.github.comandgithub.com/ImageMagick/ImageMagick). - The web server binds to
127.0.0.1only.
- Automatic deletion of destination files that have no matching source.
- Watch-folder auto-conversion.
- Automatic ImageMagick downloads on Linux ARM64 or macOS.
- macOS support.
Keep each change focused. Create a branch from main, make the change, run
bun run check, and open a pull request. Changes that affect path handling or
ImageMagick discovery should pass both the Windows and Linux CI jobs.
The desktop installers are built with Electrobun. Each release needs one native build per operating system, so the workflow runs a Windows and an Ubuntu job.
-
Bump the version in
package.json, inapp.versioninelectrobun.config.ts, and insrc/version.ts(APP_VERSION). All three must match the tag. -
Commit, then push a version tag:
git tag vX.Y.Z git push origin main --tags
The Release workflow:
- installs the Linux GTK 3 and WebKitGTK 4.1 build dependencies;
- runs
bun run checkon both platforms; - runs
bun run desktop:build(electrobun build --env=stable) on each; - refuses to continue when the tag,
package.json, andelectrobun.config.tsversions differ; - attaches the installers, update archives, and update metadata to the GitHub release;
- uses the matching
CHANGELOG.mdsection as the start of the release notes, followed by GitHub's generated pull-request summary.
A release contains:
| Platform | Installer | Update artifacts |
|---|---|---|
| Windows x64 | win-x64-Compakta-Setup.zip |
stable-win-x64-Compakta.tar.zst, stable-win-x64-update.json |
| Linux x64 | linux-x64-Compakta-Setup.tar.gz |
stable-linux-x64-Compakta.tar.zst, stable-linux-x64-update.json |
To build for the current OS without publishing:
bun install
bun run desktop:buildArtifacts land in artifacts/; the expanded app bundle is in build/. Both are
ignored by Git. bun run desktop:build:canary produces the canary channel
instead, which installs alongside the stable app.
Electrobun's channel, platform, and filename conventions are fixed. If you later
host updates yourself, set release.baseUrl in electrobun.config.ts to the
folder URL and upload artifacts/ without renaming anything; see the
Electrobun distribution guide.
MIT. See LICENSE.
ImageMagick is distributed under its own license. This project downloads the official build rather than bundling it.