- Python 99.6%
- Batchfile 0.4%
| kkpy | ||
| .gitignore | ||
| config.yml | ||
| gui_app.py | ||
| LICENSE | ||
| main.py | ||
| pyproject.toml | ||
| README.md | ||
| run-gui.bat | ||
| uv.lock | ||
KKPy
A texture optimizer for Koikatsu game files. Reduces texture sizes by resizing large textures to a maximum dimension and optimizing PNG compression. The GUI can also batch-externalize Material Editor textures to on-disk LOCAL storage.
GUI Application
KKPy includes a graphical user interface for easy texture optimization without command-line usage.
Quick Start
Windows:
- Download the project files
- Double-click
run-gui.bat - The script will:
- Check if UV is installed (and install it if needed)
- Set up the project files
- Launch the GUI application
Linux:
uv run gui_app.py
GUI Features
The GUI provides tabs: Process, Extract Textures, Embed Textures, Texture Index, and Config.
Process Tab
Select files or directories to optimize with the following options:
| Feature | Description |
|---|---|
| Input/Output | Browse for input and output paths, or drag-and-drop files |
| File Type | Auto-detect or manually select (chara, scene, coord, etc.) |
| In-place | Modify files directly without creating copies |
| Recursive | Process subdirectories when input is a directory |
Progress Monitoring:
- Real-time progress bar with file counter
- Elapsed time display
- Summary statistics (textures processed, bytes saved)
- ANSI color support in log output
Extract Textures Tab
Batch-convert Material Editor textures from bundled or deduped storage to LOCAL format so the same texture bytes can be reused across scenes and character cards.
| Feature | Description |
|---|---|
| Input/Output | Same as Process tab — browse, drag-and-drop, in-place, recursive |
| Optimize textures | Optional resize/compress pass using Config tab settings (config.yml) |
| Local texture pool | Writes MaterialEditor/_LocalTextures/ME_LocalTex_{hash}.* to UserData, the batch output directory, or both (see output mode below) |
| Per-file mirror | When output mode is Output directory, also copies required ME_LocalTex_* files beside each output PNG |
| Scene characters | Externalizes ME data on embedded scene characters, not just scene-level mod_data |
| Coordinate files | Skipped with a warning — use the Process tab to compress coordinate PNGs |
| Deduped-only character cards | Skipped with a warning — bytes live in the studio scene DEDUPED_TextureDictionary_DATA pool; extract from the scene PNG |
| Incomplete ME maps | Files fail when deduped/LOCAL id maps reference textures that cannot be loaded (missing pool bytes or ME_LocalTex_* files) |
Output format: Character and scene PNGs store LOCAL_TextureDictionary (id→hash map) with PluginData.version = 2. Texture bytes live on disk, not inside the PNG.
Local texture pool: ME_LocalTex_{hash} files are content-addressed. kkPy follows Material Editor write-if-missing — an existing file at the same hash path is never overwritten. When optimize is enabled, kkPy recomputes expected landed bytes from source data plus config.yml rules and reuses on-disk files when hash and bytes match. The optional com.kkpy.texture_history metadata in output PNGs is a fast-path cache; correctness does not depend on it.
Cancellation: Stopping an Extract batch (GUI Cancel or interrupt) may leave a partial result: some PNGs rewritten to LOCAL, local pool files written, later files unchanged. Re-run Extract on the same folder; already-LOCAL cards are handled idempotently.
In-game setup: Copy ME_LocalTex_* files from the batch output (local texture pool or per-PNG mirror) into your game’s UserData/MaterialEditor/_LocalTextures/. If Material Editor’s Local Texture Path Override is set, use that folder instead.
Embed Textures Tab
Re-bundles LOCAL ME textures from _LocalTextures back into PNG TextureDictionary data (inverse of Extract Textures). Character, scene (including embedded characters), and coordinate PNGs that contain LOCAL_TextureDictionary are supported. Extract Textures skips coordinate files because Material Editor loads coordinate ME textures from bundled data only; Embed restores bundled bytes when LOCAL refs are present.
Embed does not resize or recompress textures.
Texture Index Tab
Read-only diagnostics that map LOCAL Material Editor texture references on character/scene cards against the UserData MaterialEditor/_LocalTextures pool. Use after Extract Textures to find missing pool files (card refs a hash with no on-disk ME_LocalTex_*) and orphan pool files (on disk but not referenced by any indexed card).
| Feature | Description |
|---|---|
| Scan root | Drag-drop a folder or leave empty to scan UserData chara + studio/scene |
| Game scope | Partitioned by active game from the Game menu (KK / KKS; HS2 blocked) |
| Multi-root | Index accumulates across sessions; only the folder scanned this time is pruned on re-index |
| HTML report | Summary, missing/orphan lists, file-type breakdown, ref-count histogram |
| Index workers | Config tab index_workers (1–2) parallelizes KKEx parse on cache misses |
The SQLite database is texture_index.sqlite beside your config file (KKPY_TEXTURE_INDEX_PATH to override). Legacy texture_orphan_index.sqlite is still read if present. Re-scan after Embed Textures or when you change the UserData path (resets that game's partition).
Memory and large batches
Extract Textures builds a cross-file texture pool per chunk (size set by batch_chunk_size). Embed Textures re-reads each PNG per chunk and deduplicates loaded bytes in memory (TextureByteIntern) but does not retain a global extract pool. For very large libraries:
| Guidance | Recommendation |
|---|---|
| Whole collections | Prefer Process (per-file) or split input into subfolders |
batch_chunk_size |
Default 1000 in config.yml — lower (e.g. 200) on RAM-limited machines; reduces the peak memory estimate |
| GUI warning | Appears when estimated peak per-chunk texture data exceeds ~8 GB (total batch volume is shown for context); sampling uses up to 20 files |
| Workers | Automatically reduced above 500 files or ~8 GB estimated peak texture volume per chunk |
| Extract without optimize | Streams unique textures to _LocalTextures during extract to limit peak RAM |
The Process tab is the safest choice for multi-terabyte folders because it does not build a cross-file global texture pool.
Config Tab
Resolution rules fill the tab; optimization settings, paths, and save/load sit in a footer at the bottom.
| Section | Description |
|---|---|
| Optimization | Max texture size, compression, resize filter, workers, index scan workers, always optimize, verbosity |
| UserData folder | Per-game UserData paths (Koikatsu, Sunshine, HS2) — select active game from the Game menu; kkPy loads ME local textures from MaterialEditor/_LocalTextures (does not affect optimization history hashes) |
| Resolution rules | Per-texture rules table — add, edit, reorder, filter |
Rule properties: name, max size (or skip), property/material globs, object type (0–4).
Additional Features
- Dark/Light Mode: Toggle via View menu
- Game selection: Koikatsu / Koikatsu Sunshine / HS2 via Game menu (per-game UserData paths)
- Drag-and-Drop: Drop files directly onto the application window
Requirements
- Python: 3.14 or higher
- Package Manager: uv (recommended) or pip
Game / Material Editor (Extract Textures)
kkPy can read bundled and deduped ME data from older cards offline. Loading Extract Textures output in-game requires a recent Material Editor build:
| Requirement | Version | Notes |
|---|---|---|
| Minimum | Material Editor 3.13.5 (KK_Plugins v268) | First release with LOCAL_TextureDictionary and ME_LocalTex_* disk loading |
| Recommended | Material Editor 4.0+ | Matches PluginData.version = 2 that kkPy writes |
Update KKAPI / Modding API from the same modpack release when upgrading Material Editor.
Installation
# Clone the repository
git clone <repo-url>
cd kkPy
# Install with uv (recommended)
uv sync
# Or install with pip
pip install -e .
Note: For 2-4x faster image operations, install pillow-simd instead of pillow:
pip install pillow-simd
Supported File Types
| Type | Description |
|---|---|
chara / character |
Character files |
scene |
Scene files |
coord / coordinate |
Coordinate/clothes files |
Usage
Process a single file
uv run main.py input.png output.png
Process a directory
uv run main.py input_dir output_dir
Auto-detect file type
uv run main.py input.png output.png
Specify file type explicitly
uv run main.py -t chara input.png output.png
In-place modification
# Single file (will prompt for confirmation)
uv run main.py -i input.png
# Directory (will prompt for confirmation)
uv run main.py -i input_dir
Command-line Options
usage: main.py [-h] [-t {chara,scene,coord,scene_kk,scene_kks}]
[-v] [-j JOBS] [-r] [-i] [--max-size MAX_SIZE]
[--compress-level COMPRESS_LEVEL]
[--resize-filter {nearest,bilinear,bicubic,lanczos}]
[--always-optimize]
[--config CONFIG]
input output
Load KK files, optimize large textures in materialeditor data, and save.
positional arguments:
input Path to input file or directory
output Path to output file or directory (optional with --in-place)
options:
-h, --help show this help message and exit
-t, --type Type of file to process. If not provided, will be
auto-detected from file content.
-v, --verbose Show detailed progress messages
-j, --jobs Number of parallel jobs for directory processing
(default: auto)
-r, --recursive Search subdirectories recursively when processing
a directory
-i, --in-place Modify files in place (output path becomes optional,
will prompt for confirmation)
--max-size Maximum texture dimension in pixels (default: 2048)
--compress-level PNG compression level, 0-9 (default: 6, higher =
smaller but slower)
--resize-filter Resize filter algorithm (default: bilinear)
--always-optimize Optimize textures even if under max resolution
(only if smaller)
--config Path to YAML config file for per-texture resolution
rules (default: config.yml)
Option Details
| Option | Shorthand | Default | Description |
|---|---|---|---|
--type |
-t |
auto-detect | File type (chara, scene, coord, etc.) |
--verbose |
-v |
false | Show detailed progress messages |
--jobs |
-j |
CPU count | Number of parallel workers |
--recursive |
-r |
false | Process subdirectories recursively |
--in-place |
-i |
false | Modify files in place (prompts for confirmation) |
--max-size |
2048 | Maximum texture dimension in pixels | |
--compress-level |
6 | PNG compression level (0-9) | |
--resize-filter |
bilinear | Resize algorithm (nearest, bilinear, bicubic, lanczos) | |
--always-optimize |
-ao |
false | Optimize even if under max size (if smaller) |
--config |
config.yml |
Path to YAML config file for per-texture resolution rules |
Configuration
KKPy can be configured using a YAML file (config.yml by default) for per-texture resolution rules. This allows different maximum sizes for different texture types based on their properties.
Configuration File Format
# Default settings for textures that don't match any rule
default_max_size: 2048 # Maximum dimension in pixels
compress_level: 6 # PNG compression level (0-9)
resize_filter: bilinear # Resize algorithm: nearest, bilinear, bicubic, lanczos
always_optimize: false # Optimize textures even if under max resolution
max_workers: 4 # Number of parallel workers
# Resolution rules - evaluated in order, first match wins
rules:
- name: "Rule name"
max_size: 1024 # Maximum dimension for matching textures
match:
property: "DetailMask" # Glob pattern for texture property (e.g., "MainTex", "Normal*")
material_name: "cf_m_hair_f_*" # Glob pattern for material name (optional)
object_type: 2 # Exact object type ID (optional)
Top-Level Settings
| Setting | Type | Default | Description |
|---|---|---|---|
default_max_size |
int | 2048 | Default maximum texture dimension for textures not matching any rule |
compress_level |
int | 6 | PNG compression level (0-9, higher = smaller but slower) |
resize_filter |
string | bilinear | Resize algorithm: nearest, bilinear, bicubic, or lanczos |
always_optimize |
bool | false | Optimize textures even if under max resolution (if smaller) |
max_workers |
int | 4 | Number of parallel workers for texture processing |
batch_chunk_size |
int | 1000 | Extract/Embed batch chunk size (files per in-memory pool cycle) |
active_game |
string | koikatsu | Active game for UserData resolution: koikatsu, sunshine, or hs2 |
userdata_paths |
map | (unset) | Per-game UserData folders for loading MaterialEditor/_LocalTextures (excluded from optimization hashes). Legacy userdata_path alone is treated as koikatsu on load. |
Resolution Rules
Rules are evaluated in order; the first matching rule determines the max_size for a texture. Each rule supports the following match criteria:
| Match Field | Type | Description |
|---|---|---|
property |
string (glob) | Texture property name pattern (e.g., MainTex, Normal*, Shadow*) |
material_name |
string (glob) | Material name pattern (e.g., Skin*, Cloth*) |
object_type |
int or list[int] | Object type ID(s) to match exactly |
All specified match conditions must be satisfied (AND logic).
Object Types
The object_type field in resolution rules matches against the ObjectType value in the MaterialTexturePropertyList. The following object types are defined in the MaterialEditor plugin:
| Object Type | Description |
|---|---|
0 |
Unknown (reserved) |
1 |
Clothing/Body textures |
2 |
Accessory textures |
3 |
Hair textures |
4 |
Character (face/body parts) |
Example Configuration
# Matcap textures - typically used for lighting effects
- name: "Matcap textures"
max_size: 512
match:
property: "Matcap"
# Shadow textures - don't need high resolution
- name: "Shadow textures"
max_size: 512
match:
property: "Shadow*"
# Normal maps - can be lower resolution
- name: "Normal maps"
max_size: 1024
match:
property: "Normal*"
# Accessory textures (ObjectType 2) - often need higher resolution
- name: "Accessory textures"
max_size: 2048
match:
object_type: 2
Development
Setup
# Install with development dependencies
uv sync --extra dev
# Or with pip
pip install -e ".[dev]"
Running Tests
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=kkpy
# Run specific domain
uv run pytest tests/pipeline
Code Quality
# Run linting
uv run ruff check .
# Run type checking
uv run ty check .
# Format code
uv run ruff format .