Convert Photoshop PSD files to Spine 2D skeletons (JSON + atlas + page PNG).
Project description
pspine
Convert Photoshop PSD files to Spine 2D skeletons (.json + .atlas + .png) — without opening Spine.
A reverse-engineered re-implementation of Spine's PSD importer. Verified against Spine 4.2.43 ground truth: byte-identical skeleton JSON (modulo hash) and pixel-identical per-layer regions; atlas page layout is structurally equivalent (different shelf-packer geometry — Spine doesn't care).
Quick start
uvx pspine my-symbol.psd
Writes my-symbol.json, my-symbol.atlas, my-symbol.png next to the input.
Usage
pspine [OPTIONS] PSD_FILES...
Options
| Flag | Effect |
|---|---|
-o, --output DIR |
Write outputs to DIR instead of next to each PSD. |
-n, --name NAME |
Base filename (no extension) for outputs. Single input only. |
-q, --quiet |
Suppress progress; errors still print to stderr. |
-v, --verbose |
Print extra detail per step (canvas size, every layer name, atlas shape). |
--no-color |
Disable ANSI colors. |
-h, --help |
Show help. |
--version |
Show version. |
Output is colorful by default and downgrades automatically when stdout isn't a TTY.
Examples
# Same folder as PSD (default)
pspine input.psd
# Specific output folder
pspine input.psd -o build/spine/
# Multiple PSDs into one folder
pspine assets/*.psd -o build/spine/
# Custom base name
pspine input.psd -n symbols-blue
# Verbose run
pspine input.psd -v
# Pipeline-friendly (no colors, no progress)
pspine input.psd --quiet --no-color
Layer name conventions
pspine mirrors Spine's PSD importer rules:
- Each visible leaf pixel layer becomes one slot + one attachment with the layer's literal name.
- Layer names containing
[ignore]are skipped. - All slot names within the skeleton must be unique. Duplicates after the first are skipped with a yellow
⚠warning.
Group structure is currently flattened (no
[skin]/[bone]/[slot]group-tag handling). If your PSD has duplicate leaf names across groups (a common pattern in slot-machine sheets), either rename the duplicates or[ignore]the noise layers. Multi-skin support is on the roadmap; seedocs/reverse-engineering.mdfor the full Spine importer spec.
Output
For an input named foo.psd:
| File | Contents |
|---|---|
foo.json |
Spine skeleton: one root bone, one slot per layer, one default skin. Spine version 4.2.43. |
foo.atlas |
libgdx atlas: pma:true, filter:Linear,Linear, one region per slot. |
foo.png |
Packed atlas page (RGBA, premultiplied). |
Each region carries 1 px transparent edge padding so bilinear filtering can't sample neighbours.
Library use
from pspine import convert
result = convert("input.psd", out_dir="build/spine", base_name="symbols")
print(result.json_path, result.atlas_path, result.page_path)
print(f"{len(result.skeleton.slots)} slots")
A progress callback exposes the same events the CLI prints:
def on_event(event: str, payload: dict) -> None:
print(event, payload)
convert("input.psd", on_progress=on_event)
Install
# One-shot run (no install)
uvx pspine ...
# Install globally as a tool
uv tool install pspine
# or
pipx install pspine
# As a library dependency
uv add pspine
# or
pip install pspine
Development
git clone https://github.com/USER/pspine
cd pspine
uv sync
uv run pspine --help
Publishing
The package is published to PyPI with a token. From a clean checkout:
uv build # produces dist/*.whl and dist/*.tar.gz
UV_PUBLISH_TOKEN=pypi-... uv publish # uploads dist/*
To bump the version, edit __version__ in src/pspine/__init__.py; pyproject.toml reads it dynamically.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pspine-0.1.0.tar.gz.
File metadata
- Download URL: pspine-0.1.0.tar.gz
- Upload date:
- Size: 12.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67dbff39e42b34815e4659cc667ae9188cf97108cc6ea9ea8695ed9f56caf52d
|
|
| MD5 |
e763111f353e266a0c30dacff02f8173
|
|
| BLAKE2b-256 |
0d402585182573c2f26b7101d820b4098049faae621f374afce85fa63ea94b11
|
File details
Details for the file pspine-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pspine-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
369b7e30889f95ea4648ed753683abd62c8bb99d1fdc5c1156586fdd4b8ea3f6
|
|
| MD5 |
ac0b063dde1f57c91a7ffb89cce33182
|
|
| BLAKE2b-256 |
a97d02c08c98c4cf1f243f432b4cf8f01b4b7b266f3ac51a26b152d300eb9f10
|