Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Build

on:
push:
branches: ["main"]
pull_request:
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
# The engine has no UI imports, so it tests without a display.
- run: pip install Pillow piexif
- run: python tests/test_engine.py

smoke:
needs: test
strategy:
fail-fast: false
matrix:
os: [macos-14, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- run: pip install -r requirements.txt
# Proves the GUI actually constructs on this OS — imports, fonts, theme
# tokens, widget options — rather than only that the engine passes.
- run: python main.py --check
- run: python tests/test_gui_boot.py

macos-app:
needs: smoke
strategy:
fail-fast: false
matrix:
include:
# macos-13 is Intel, macos-14 is Apple Silicon. py2app bundles the
# running interpreter, so a single runner produces a single-arch app
# that simply will not launch on the other kind of Mac.
- runner: macos-13
arch: intel
- runner: macos-14
arch: apple-silicon
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- run: |
pip install -r requirements.txt
pip install py2app
# v1's workflow cd'd into a folder that was never in the repo, so this
# job had never once produced a bundle.
- run: python setup.py py2app
- name: Verify the bundle launches
run: |
APP="dist/WebP Studio.app"
test -d "$APP" || { echo "no bundle produced"; exit 1; }
file "$APP/Contents/MacOS/WebP Studio"
# Headless runners have no window server, so a full launch can't be
# tested here — confirm the embedded interpreter starts and imports.
"$APP/Contents/MacOS/WebP Studio" --check
- run: ditto -c -k --keepParent "dist/WebP Studio.app" "WebP-Studio-macOS-${{ matrix.arch }}.zip"
- uses: actions/upload-artifact@v4
with:
name: WebP-Studio-macOS-${{ matrix.arch }}
path: WebP-Studio-macOS-${{ matrix.arch }}.zip
retention-days: 14
43 changes: 0 additions & 43 deletions .github/workflows/build_mac_app.yml

This file was deleted.

36 changes: 1 addition & 35 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,42 +1,8 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Virtual Environment
venv/
env/
ENV/
.env

# PyInstaller/py2app
*.spec
.app/
build/
dist/

# MacOS
.venv/
.DS_Store

# IDEs
.vscode/
.idea/
185 changes: 124 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,100 +1,163 @@
# ConvertImagesToWebP - MacAlpha v0.1
# WebP Studio 2.0

A native macOS GUI application for converting images to WebP format.
Batch image converter for macOS, Windows and Linux. Drop a folder, pick a
preset, get smaller images.

## Features
A rewrite of ConvertImagesToWebP-MacAlpha v0.1 — same idea, working engine.

- 🖱️ **Drag & Drop** - Drop images or folders directly onto the app
- ⚙️ **Customizable Settings** - Quality, resolution, encoding speed
- 📊 **Real-time Progress** - Watch your conversions in progress
- 📈 **Size Savings Stats** - See how much space you saved
- 🌙 **Dark Mode Support** - Native macOS appearance
---

## Supported Formats
## What's new in 2.0

**Things v0.1 advertised but didn't do**

| | v0.1 | 2.0 |
|---|---|---|
| Drag & drop | `tkinterdnd2` in requirements, never imported | works; the app tells you if the package is missing |
| "Preserve EXIF & color profiles" toggle | ignored — metadata dropped from every file | actually written, plus optional GPS-only removal |
| `MAX_THREADS: 8` | unused; conversion ran one file at a time | real thread pool, auto-sized to your CPU |
| Time remaining | permanently "Calculating…" | live ETA and images/sec |
| Cancel | stopped, then reported "Conversion Complete!" | reports what finished and what never started |
| Errors | recorded, never displayed | listed on screen and savable to a log |
| 5,000-image batches | one widget per file, frozen window | single capped log view |
| `python setup.py py2app` | failed — missing `assets/icon.icns` | builds without an icon |
| GitHub Actions build | `cd` into a folder that isn't in the repo | fixed |

**New**

- **Output formats** — WebP, AVIF, JPEG, PNG (only the ones your Pillow build can write are offered)
- **Presets** — Web · Balanced · Archive · Smallest
- **Resize by** longest edge, width, height, or megapixels. Never upscales.
- **Destination control** — subfolder, a folder you choose, or next to each original
- **If a file already exists** — skip, overwrite, or rename
- **Preflight** — "482 images · 3.1 GB → Pictures/Converted" before you commit
- **Lossless mode**, encoder-effort control, worker count
- **System / Light / Dark**, remembered between launches
- **Keyboard** — `Ctrl/⌘O` folder · `Ctrl/⌘⇧O` files · `Return` convert · `Esc` stop or clear

- JPEG (.jpg, .jpeg)
- PNG (.png)
- BMP (.bmp)
- TIFF (.tiff, .tif)
- HEIC (.heic)
- WebP (.webp) - direct copy
---

## Installation
## Install

### 1. Prerequisite: Homebrew
```bash
pip install -r requirements.txt
python main.py
```

If you don't have Homebrew installed, open Terminal and run:
Only `customtkinter` and `Pillow` are required. The rest are optional and the
app degrades cleanly without them — check what you have:

```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
python main.py --check
```

### 2. Setup Python & Dependencies
| Optional | Enables |
|---|---|
| `tkinterdnd2` | drag & drop onto the window |
| `piexif` | removing GPS tags while keeping the rest of the EXIF |
| `pillow-heif` | reading iPhone `.heic` / `.heif` |

## macOS

Run these commands one by one in Terminal:
**Use Homebrew's Python, not Apple's.** macOS ships Tk 8.5.9; CustomTkinter
needs 8.6+ and renders as black rectangles below that. `python main.py --check`
prints your Tk version and says so if it's too old.

```bash
# Install Python and Tkinter via Homebrew
brew install python python-tk
/opt/homebrew/bin/python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
python main.py
```

# Navigate to the app folder
cd ConvertImagesToWebP-MacAlpha
### Build a .app

# Create a virtual environment (fixes 'pip' issues)
python3 -m venv venv
```bash
python setup.py py2app # -> dist/WebP Studio.app
```

# Activate the virtual environment
source venv/bin/activate
Drop an `assets/icon.icns` in first if you want a custom icon — unlike v1, the
build no longer fails without one.

# Install required libraries
pip install -r requirements.txt
**py2app bundles the interpreter it is run with, so the result is single-arch.**
An app built on an M-series Mac will not launch on an Intel Mac and vice versa.
CI therefore builds both (`macos-13` Intel, `macos-14` Apple Silicon) and
uploads them as separate artifacts. To produce one universal binary instead,
build with a universal2 python.org interpreter rather than a Homebrew one.

`LSMinimumSystemVersion` is set to 10.13, but the real floor is whatever the
building Python supports.

### "The app is damaged and can't be opened"

That is Gatekeeper, not a broken build — the bundle is unsigned and
un-notarized, and anything downloaded from a browser or CI artifact gets
quarantined. Either right-click → Open the first time, or:

```bash
xattr -dr com.apple.quarantine "/Applications/WebP Studio.app"
```

### 3. Run the App
Signing and notarizing requires a paid Apple Developer account; that is the
only real fix for distributing to other people.

## Tests

```bash
python main.py
python tests/test_engine.py # no display needed
python tests/test_gui_boot.py # needs a display
```

### Build .app Bundle (via GitHub Actions)
No framework. `test_engine` covers sizing math, alpha flattening, metadata
keep/strip, output-collision handling, the skip/overwrite/rename policies,
error isolation, cancel, and savings accounting. `test_gui_boot` builds the
real window and drives a real conversion through the Tk event loop — it exists
to catch what only breaks on a specific OS (fonts that don't resolve, widget
options a platform's Tk rejects) and runs in CI on macOS and Windows.

**Since you are on Windows**, you cannot build the macOS app directly. Instead:
Both write settings to a temp directory via `WEBP_STUDIO_CONFIG_DIR`, so they
never touch your real config.

1. Push this code to a GitHub repository
2. Go to the **Actions** tab in your repo
3. Select **Build macOS App** workflow
4. Run workflow (or it runs on push)
5. Download the `MacOS-App-Bundle` artifact when done
---

### Build .app Bundle (on macOS)
## How it works

```bash
python setup.py py2app
```
main.py entry point + dependency check
core/
config.py Settings dataclass, presets, JSON persistence
imaging.py one image: open → orient → resize → square → encode
runner.py scan, plan destinations, thread pool, cancel, progress
gui/
theme.py design tokens — every color is a (light, dark) pair
widgets.py Card, StatTile, ProgressRing, SliderRow, LogView
panel.py the settings panel
screens/ home · progress · results
```

This creates `ConvertImagesToWebP.app` in the `dist/` folder.
`core/` has no UI imports, so the engine is usable from a script and testable
without a display.

## Usage
### Notes on behaviour

1. **Drop Zone** - Drag images/folders or click Browse
2. **Settings** - Adjust quality, resolution, encoding options
3. **Progress** - Watch real-time conversion progress
4. **Results** - View stats and open output folder
- **Resizing never upscales.** A limit larger than the source is a no-op.
- **Metadata off converts to sRGB.** An untagged file is read as sRGB by every
viewer, so baking the profile in keeps colors from shifting.
- **The output folder is excluded from scans.** Converting the same folder
twice will not re-convert its own results.
- **Failed files leave nothing behind.** A partial write is deleted, because a
truncated image looks fine in a file manager and fails later.
- **Animated sources take frame one**, and say so in the log.

## Settings
## Verified on

| Setting | Description | Default |
| ----------- | -------------------------- | -------- |
| Quality | WebP quality (1-100) | 90 |
| Resolution | Max megapixels limit | 19 MP |
| Encoding | Speed vs compression (1-6) | 6 (Best) |
| Metadata | Preserve EXIF/ICC profiles | Yes |
| Square Mode | Original, Crop, or Canvas | Original |
| | Status |
|---|---|
| Windows 11 · Python 3.12 · Tk 8.6 · CustomTkinter 6.0 | both test suites pass; app driven end to end |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
| Engine logic (any OS) | 12 checks, no display required |
| macOS | **not yet run** — no Mac available to the author. Push to `main` and the CI matrix will build and smoke-test Intel and Apple Silicon bundles. |
| Linux | should work; `test_gui_boot` needs `xvfb` in CI |

## License

MIT License - Feel free to modify and distribute.

---

Made with ❤️ for macOS
MIT.
2 changes: 1 addition & 1 deletion core/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# Core processing engine
"""Conversion engine: settings, single-image pipeline, batch runner."""
Loading
Loading