Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 5 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@
- Added clearer Web Sources scan status messages for HTTP 401/403/429 cases.
- Added regression tests for HTTP 403 scan retry + friendly error mapping.
- Rewrote README for clearer product overview, workflow guidance, and release usage.
## 1.1.1 - 2026-03-12
- Replaced README/UI screenshot with a true full-size capture of the running **Sprite Factory Pro** app window.
- Synced repository docs to reflect the current branded UI state.
## 1.1.1 - 2026-03-12
- Replaced README/UI screenshot with a true full-size capture of the running **Sprite Factory Pro** app window.
- Synced repository docs to reflect the current branded UI state.

## 1.1.0 - 2026-03-12
- Rebranded UI and runtime identity to **Sprite Factory Pro**.
- Moved `Encoding Window` access from top toolbar into `Settings > Export Encoding` and kept full encoding controls.
Expand Down Expand Up @@ -63,4 +63,4 @@
- Session/save/export UX fixes.

## 1.0.1 - 2026-03-09
- Initial SpriteFactory baseline import.
- Initial SpriteFactory baseline import.
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ Sprite Factory Pro is a Windows desktop app for cleaning, enhancing, previewing,
- Import local files, folders, ZIP archives, or images found from web pages.
- Preview `Current` and `Final` output side by side before exporting.
- Apply cleanup, color, detail, transparency, GIF, export, and encoding controls.
- Choose real output sizes using sprite-safe 2x/3x/4x/8x scaling or standard 240p-2160p heights while preserving aspect ratio.
- Remove white or black backgrounds when you choose to, without forcing it on import.
- Use presets for faster sprite, photo, GIF, and mixed-format workflows.
- Batch process large queues with presets, background overrides, file naming, and export rules.
- Save presets directly from changed controls, then reuse them for sprite, photo, GIF, and mixed-format workflows.
- Batch process large queues with one clear edit source, background overrides, file naming, and export rules.
- Export to `PNG`, `WEBP`, `JPG`, `GIF`, `ICO`, `TIFF`, and `BMP`.

## Download
Expand All @@ -22,16 +23,16 @@ The easiest way to use the app is the latest Windows release:

[Sprite Factory Pro Releases](https://github.com/Awetspoon/SpriteFactory/releases/latest)

Download the `.exe`, run it, then import files from the top `Import` menu or use the `Web Sources` tab.
Download the `.exe`, run it, then add files from the top `File` menu or use `Web Sources`.

## Main Workflow

1. Open a new session or continue working in the current workspace.
1. Create a workspace or open an existing one from `File`.
2. Import files, folders, ZIPs, or scan web pages for sprite/image links.
3. Select an asset from the workspace.
4. Choose a preset or adjust edit settings manually.
5. Use `Preview` to check the result in the final pane.
6. Use `Apply` when you want to commit the edit to the asset.
4. Start with the controls detected for that asset, then choose a preset or make small adjustments.
5. Watch visual changes update the Final pane automatically, or use `Refresh Final` to rebuild it manually.
6. Use `Run Heavy` only when a selected preset or control requires heavier processing.
7. Export one file, skip to the next asset, or open Batch Manager for queue export.

## Web Sources
Expand All @@ -48,8 +49,9 @@ Download the `.exe`, run it, then import files from the top `Import` menu or use

Batch Manager processes selected workspace assets with a separate batch workflow so normal preview/edit state does not get tangled with queue export.

- Copy current edits when needed.
- Apply a chosen preset or smart preset rules.
- Choose exactly one edit source: keep each asset's controls, apply one preset, copy the active asset, or smart-match each asset.
- Chosen presets start from every asset's detected baseline; smart matching applies at most one preset and never stacks hidden rules.
- Workspace, Preset Studio, and Batch share one merged preset library. Bundled presets live in one engine catalog; user presets are stored separately and override by name.
- Override background-removal behavior for the batch.
- Keep source names or use batch naming rules.
- Save files after processing and review failures clearly.
Expand Down
5 changes: 4 additions & 1 deletion docs/PROJECT_STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ This document describes the intended repository layout for Sprite Factory.

- `image_engine_app/` — current production application code.
- `app/` — startup, controller wiring, settings, persistence, audit.
- `engine/` — ingest, processing, analysis, batch, export, models.
- `assets/` — packaged static assets such as app icons and UI images.
- `engine/` — ingest, processing, analysis, batch, export, models, and the single bundled preset catalog under `engine/presets/`.
- `ui/` — PySide6 windows, coordinators, controls, and dialogs.
- `tests/` — unit and smoke coverage for the active app.
- `docs/` — package-specific reference notes and default data files.
Expand All @@ -23,6 +24,7 @@ This document describes the intended repository layout for Sprite Factory.
- `build_exe.ps1` / `build_exe_onefile.ps1` — Windows packaging helpers.
- `RUN_AUDIT.ps1` — repository audit entry point.
- `BUILD_LOCK.md` — locked build-next checklist that intentionally stays at repo root.
- App icon assets live under `image_engine_app/assets/icons/` and are bundled by the PyInstaller specs.

## Generated or local-only paths

Expand All @@ -42,5 +44,6 @@ These should not be treated as source structure and should stay out of version c
- The active application path is `image_engine_app`, not `image_engine_v3`.
- `image_engine_v3` is still important because the active code and tests depend on it.
- Imports should be package-qualified (`image_engine_app.*`, `image_engine_v3.*`) rather than relying on `PYTHONPATH=image_engine_app`.
- Output-size choices are defined once in `engine/process/output_size.py`; the UI maps them onto the existing resize/width/height controls rather than maintaining duplicate size state.
- `python -m image_engine_app` is the preferred development entrypoint; the repo-root `main.py` wrapper is compatibility sugar only.
- Legacy repo-root `_runtime_data/` may still exist on a local machine, but it is not part of the intended source layout.
6 changes: 4 additions & 2 deletions docs/RELEASE_1.2.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ Sprite Factory Pro 1.2.2 completes the preview-first redesign and focuses on rel
## Highlights

- Added a compact preset selector directly to the editing tools. It only offers compatible presets for the active asset and updates the final preview immediately.
- Kept Preset Manager as the advanced workspace for creating, duplicating, editing, and saving user presets.
- Simplified Batch Manager with compact `Queue` and `Options` menus while retaining isolated batch edits, presets, background rules, naming, and export behavior.
- Rebuilt Preset Manager around `Use Active Controls`; it saves only changes from the active asset's detected baseline and keeps JSON under optional Advanced controls.
- Consolidated bundled presets into one validated engine catalog shared by Workspace, Preset Studio, recommendations, and Batch; removed overlapping starter variants.
- Added real output-size choices for 2x/3x/4x/8x sprite scaling and 240p through 2160p standard heights without duplicating the underlying pixel settings.
- Rebuilt Batch Manager around one explicit edit source so copied controls, chosen presets, and smart matching cannot stack accidentally.
- Reworked Web Sources for saved pages, one-page or multi-page scanning, linked-page discovery, keyword and format filtering, scan limits, retries, and clearer failures.
- Tightened the editor shell, preview controls, workspace paging, export dock, settings cards, labels, and helper guidance.
- Removed obsolete design drafts, superseded UI/backend modules, generated files, and dead tests from the repository.
Expand Down
2 changes: 1 addition & 1 deletion docs/RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Run the commands from the repository root.

## 3) Version + icon
- [ ] Update `pyinstaller_version_info.py` version strings if needed
- [ ] Confirm `spritefactory_pro.ico`, `spritefactory.ico`, and PNG icon assets are present and show the current app icon
- [ ] Confirm `image_engine_app/assets/icons/` contains the current ICO and PNG app icon assets

## 4) Build one-file release (recommended)
- [ ] `powershell -ExecutionPolicy Bypass -File .\build_exe_onefile.ps1`
Expand Down
72 changes: 58 additions & 14 deletions docs/WEB_SOURCES_README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,65 @@
# Sprite Factory Web Sources Scaffold Pack
# Web Sources

This scaffold adds the Web Sources feature flow:
Web Sources finds sprite and image files on public web pages and imports selected files into the Sprite Factory workspace.

`Paste URL or choose a saved page` -> `Scan Page` -> select files -> `Download Selected`
## Workflow

It is designed to be:
- **Beginner-friendly** with a simple UI flow
- **Non-bloated** with optional smart features
- **Patch-safe** because the UI stays thin and the controller owns the logic
All page sources use the same scan request:

## Where to drop these files
`Entered URLs, Saved Pages, or Selected Linked Pages -> Page Scanner -> Found Files -> Download Selected -> Workspace`

Copy the folders inside this pack into the repository root so paths line up, for example:
The page scanner, linked-page discovery, saved-page registry, connection check, and downloader are separate operations. This prevents a menu action from accidentally calling the wrong behavior.

- `image_engine_app/ui/main_window/web_sources_panel.py`
- `image_engine_app/engine/ingest/web_sources_rules.py`
- `image_engine_app/engine/ingest/zip_extract.py`
- `image_engine_app/app/web_sources_models.py`
## 1. Scan Pages

Then follow `image_engine_app/docs/INTEGRATION_CHECKLIST.md`.
- Paste one complete page URL per line.
- URLs can come from different websites.
- **Scan Pages** validates and deduplicates the list before scanning.
- Scans over 100 pages require confirmation and are capped at 100 pages.
- **More** contains Save Entered Pages, Check First URL, Include uncertain image links, and Clear Entered URLs.

## 2. Saved Pages

- **Save Entered Pages** groups exact page URLs under their website host.
- Check one page, several pages, or an entire website row.
- Checked pages from several websites can be scanned together.
- **Scan Selected** uses the same scanner as entered and linked URLs.
- **More** contains only saved-page selection, connection, and removal actions.
- Saved-page changes do not clear Found Files.

## 3. Find Linked Pages

- Use this optional section for an index, category, or directory page.
- **Discover from** explicitly shows which entered or saved page will be inspected.
- **Find Pages** discovers page links but does not scan their files.
- Search and select the discovered pages, then use **Scan Selected**.
- **More** contains only selection and clear actions for linked pages.

## 4. Found Files

- Found Files persists across separate scans.
- New scan results are merged by normalized file URL.
- Failed scans and duplicate links do not remove successful earlier results.
- Search matches filename, URL, and source page.
- Hide words removes matching rows from view without deleting them.
- File Types controls PNG, GIF, WEBP, JPG/JPEG, and ZIP visibility.
- **More** contains Select All Visible Files, Clear File Selection, and Clear Found Files.

## Download

- **Download Options** controls skipping files already downloaded and ZIP extraction.
- **Download Selected** imports selected files into the workspace.
- Smart routing places assets into Main, Shiny, Animated, or Items.
- Only **Clear Found Files** empties the persistent result basket.

## Ownership

- `web_sources_panel.py` renders state and emits typed user requests.
- `web_sources_coordinator.py` validates requests, controls progress, and calls services.
- `web_sources_service.py` scans pages, discovers links, downloads files, and creates assets.
- `web_sources_models.py` defines request contracts and the Found Files store.
- `settings_store.py` persists saved pages, the last selected page, and scan/download options.

## Network Failures

Website failures are reported per page when possible, while successful pages are retained. HTTP 403/429 normally means the remote website blocked or rate-limited the request. HTTP 500/502/503/504 means the remote server failed. Connection checks test one selected URL and never start a scan.
2 changes: 1 addition & 1 deletion image_engine_app/app/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def _check_packaging_files(project_root: Path) -> tuple[bool, dict[str, Any]]:
"onedir_spec": project_root / "spritefactory.spec",
"onefile_spec": project_root / "spritefactory_onefile.spec",
"runtime_hook_dir": project_root / "pyinstaller_rthooks",
"icon": project_root / "spritefactory.ico",
"icon": project_root / "image_engine_app" / "assets" / "icons" / "spritefactory_pro.ico",
"version_info": project_root / "pyinstaller_version_info.py",
}
missing = [k for k, p in paths.items() if not p.exists()]
Expand Down
66 changes: 54 additions & 12 deletions image_engine_app/app/logging_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,49 @@
from __future__ import annotations

import logging
import os
from pathlib import Path
import tempfile


def _close_handlers(logger: logging.Logger) -> None:
for handler in list(logger.handlers):
logger.removeHandler(handler)
try:
handler.close()
except Exception:
pass


def _file_handler(
path: Path,
*,
level: int,
formatter: logging.Formatter,
) -> logging.FileHandler | None:
try:
path.parent.mkdir(parents=True, exist_ok=True)
handler = logging.FileHandler(path, encoding="utf-8")
except OSError:
return None
handler.setFormatter(formatter)
handler.setLevel(level)
return handler


def _fallback_log_dir() -> Path:
return Path(tempfile.gettempdir()) / "SpriteFactory" / "logs"


def configure_logging(log_dir: str | Path, *, level: int = logging.INFO) -> logging.Logger:
"""Configure app loggers and return the main app logger."""

target_dir = Path(log_dir)
target_dir.mkdir(parents=True, exist_ok=True)
log_file = target_dir / "image_engine_app.log"

logger = logging.getLogger("image_engine_app")
logger.setLevel(level)
logger.handlers.clear()
_close_handlers(logger)

formatter = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")

Expand All @@ -24,21 +54,33 @@ def configure_logging(log_dir: str | Path, *, level: int = logging.INFO) -> logg
stream_handler.setLevel(level)
logger.addHandler(stream_handler)

file_handler = logging.FileHandler(log_file, encoding="utf-8")
file_handler.setFormatter(formatter)
file_handler.setLevel(level)
logger.addHandler(file_handler)
active_log_dir = target_dir
file_handler = _file_handler(log_file, level=level, formatter=formatter)
if file_handler is None:
active_log_dir = _fallback_log_dir()
log_file = active_log_dir / f"image_engine_app_{os.getpid()}.log"
file_handler = _file_handler(log_file, level=level, formatter=formatter)
if file_handler is not None:
logger.addHandler(file_handler)

# Dedicated debug trace for batch workflows (UI + runner) to diagnose mid-run failures.
batch_logger = logging.getLogger("image_engine_app.batch")
batch_logger.setLevel(logging.DEBUG)
batch_logger.handlers.clear()
batch_file_handler = logging.FileHandler(target_dir / "batch_debug.log", encoding="utf-8")
batch_file_handler.setFormatter(formatter)
batch_file_handler.setLevel(logging.DEBUG)
batch_logger.addHandler(batch_file_handler)
_close_handlers(batch_logger)
batch_file_handler = _file_handler(
active_log_dir / "batch_debug.log",
level=logging.DEBUG,
formatter=formatter,
)
if batch_file_handler is not None:
batch_logger.addHandler(batch_file_handler)
batch_logger.propagate = True

logger.propagate = False
logger.debug("Logging configured at %s", log_file)
if file_handler is None:
logger.warning("File logging unavailable; continuing with console logging")
elif active_log_dir != target_dir:
logger.warning("Primary log unavailable; using fallback log: %s", log_file)
else:
logger.debug("Logging configured at %s", log_file)
return logger
42 changes: 26 additions & 16 deletions image_engine_app/app/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""App entrypoint (UI launch) for the Prompt 16 Qt shell."""
"""Application entrypoint for the Sprite Factory Qt desktop UI."""

from __future__ import annotations

Expand All @@ -17,6 +17,9 @@
from image_engine_app.ui.common.shell_theme import build_app_stylesheet


APP_ICON_NAMES = ("spritefactory_pro.ico", "spritefactory.ico", "spritefactory_pro.png", "spritefactory.png")


def build_startup_session() -> SessionState:
"""Create a clean empty startup session (no demo assets)."""

Expand Down Expand Up @@ -56,19 +59,19 @@ def _resolve_runtime_icon_candidates() -> list[Path]:
meipass = getattr(sys, "_MEIPASS", None)
if meipass:
base = Path(str(meipass))
candidates.append(base / "spritefactory_pro.ico")
candidates.append(base / "spritefactory.ico")
candidates.append(base / "spritefactory_pro.png")
candidates.append(base / "spritefactory.png")
for icon_root in (base / "image_engine_app" / "assets" / "icons", base):
for icon_name in APP_ICON_NAMES:
candidates.append(icon_root / icon_name)

exe_path = Path(sys.executable)
candidates.append(exe_path)
else:
root = Path(__file__).resolve().parents[2]
candidates.append(root / "spritefactory_pro.ico")
candidates.append(root / "spritefactory.ico")
candidates.append(root / "spritefactory_pro.png")
candidates.append(root / "spritefactory.png")
icon_root = root / "image_engine_app" / "assets" / "icons"
for icon_name in APP_ICON_NAMES:
candidates.append(icon_root / icon_name)
for icon_name in APP_ICON_NAMES:
candidates.append(root / icon_name)
except Exception:
return []

Expand Down Expand Up @@ -104,12 +107,19 @@ def _apply_clean_pro_theme(app) -> None: # noqa: ANN001 - QApplication imported
app.setStyleSheet(build_app_stylesheet())


def main(argv: list[str] | None = None) -> int:
"""Launch the Sprite Factory UI shell."""
def _parse_startup_args(argv: list[str] | None) -> tuple[argparse.Namespace, list[str]]:
"""Parse Sprite Factory options and leave unrelated options for Qt."""

parser = argparse.ArgumentParser(description="Launch the Sprite Factory UI shell")
parser.add_argument("--app-data-dir", default=None, help="Override app data directory (for local testing)")
args = parser.parse_args(argv)
raw_args = list(sys.argv[1:] if argv is None else argv)
return parser.parse_known_args(raw_args)


def main(argv: list[str] | None = None) -> int:
"""Launch the Sprite Factory UI shell."""

args, qt_args = _parse_startup_args(argv)

paths = ensure_app_paths(base_dir=args.app_data_dir)
logger = configure_logging(paths.logs)
Expand All @@ -126,7 +136,7 @@ def main(argv: list[str] | None = None) -> int:

_set_windows_app_user_model_id("Marcus.SpriteFactory.Windows.PythonV2", logger)

app = QApplication(argv or sys.argv)
app = QApplication([sys.argv[0], *qt_args])

# Prefer native Windows style so min/max/titlebar behavior matches user expectations.
try:
Expand Down Expand Up @@ -193,6 +203,6 @@ def _on_about_to_quit() -> None:


if __name__ == "__main__":
raise SystemExit(main())
raise SystemExit(main())


Loading