psd2pngs 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- psd2pngs-1.0.0/LICENSE +21 -0
- psd2pngs-1.0.0/PKG-INFO +200 -0
- psd2pngs-1.0.0/README.md +171 -0
- psd2pngs-1.0.0/pyproject.toml +60 -0
- psd2pngs-1.0.0/setup.cfg +4 -0
- psd2pngs-1.0.0/src/psd2pngs/__init__.py +4 -0
- psd2pngs-1.0.0/src/psd2pngs/__main__.py +61 -0
- psd2pngs-1.0.0/src/psd2pngs/conversion.py +140 -0
- psd2pngs-1.0.0/src/psd2pngs/layer_info.py +75 -0
- psd2pngs-1.0.0/src/psd2pngs/layer_save.py +116 -0
- psd2pngs-1.0.0/src/psd2pngs/safe_name.py +16 -0
- psd2pngs-1.0.0/src/psd2pngs/unpack.py +51 -0
- psd2pngs-1.0.0/src/psd2pngs/version.py +3 -0
- psd2pngs-1.0.0/src/psd2pngs.egg-info/PKG-INFO +200 -0
- psd2pngs-1.0.0/src/psd2pngs.egg-info/SOURCES.txt +19 -0
- psd2pngs-1.0.0/src/psd2pngs.egg-info/dependency_links.txt +1 -0
- psd2pngs-1.0.0/src/psd2pngs.egg-info/entry_points.txt +2 -0
- psd2pngs-1.0.0/src/psd2pngs.egg-info/requires.txt +5 -0
- psd2pngs-1.0.0/src/psd2pngs.egg-info/top_level.txt +1 -0
- psd2pngs-1.0.0/tests/test_convert.py +10 -0
- psd2pngs-1.0.0/tests/test_multiprocessing.py +17 -0
psd2pngs-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 34j and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
psd2pngs-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: psd2pngs
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Convert a PSD file to PNG files while maintaining the layer hierarchy.
|
|
5
|
+
Author: 34j
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/34j/psd2pngs/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/34j/psd2pngs/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: documentation, https://psd2pngs.readthedocs.io
|
|
10
|
+
Project-URL: repository, https://github.com/34j/psd2pngs
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: click
|
|
24
|
+
Requires-Dist: pillow
|
|
25
|
+
Requires-Dist: psd-tools
|
|
26
|
+
Requires-Dist: pyhumps
|
|
27
|
+
Requires-Dist: tqdm
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# psd2pngs
|
|
31
|
+
|
|
32
|
+
<p align="center">
|
|
33
|
+
<a href="https://github.com/34j/psd2pngs/actions/workflows/ci.yml?query=branch%3Amain">
|
|
34
|
+
<img src="https://img.shields.io/github/actions/workflow/status/34j/psd2pngs/ci.yml?branch=main&label=CI&logo=github&style=flat-square" alt="CI Status">
|
|
35
|
+
</a>
|
|
36
|
+
<a href="https://psd2pngs.readthedocs.io">
|
|
37
|
+
<img src="https://img.shields.io/readthedocs/psd2pngs.svg?logo=read-the-docs&logoColor=fff&style=flat-square" alt="Documentation Status">
|
|
38
|
+
</a>
|
|
39
|
+
</p>
|
|
40
|
+
<p align="center">
|
|
41
|
+
<a href="https://codecov.io/gh/34j/psd2pngs">
|
|
42
|
+
<img src="https://img.shields.io/codecov/c/github/34j/psd2pngs.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">
|
|
43
|
+
</a>
|
|
44
|
+
<a href="https://github.com/astral-sh/uv">
|
|
45
|
+
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json" alt="uv">
|
|
46
|
+
</a>
|
|
47
|
+
<a href="https://github.com/astral-sh/ruff">
|
|
48
|
+
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff">
|
|
49
|
+
</a>
|
|
50
|
+
<a href="https://github.com/j178/prek">
|
|
51
|
+
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/j178/prek/master/docs/assets/badge-v0.json" alt="prek">
|
|
52
|
+
</a>
|
|
53
|
+
</p>
|
|
54
|
+
<p align="center">
|
|
55
|
+
<a href="https://pypi.org/project/psd2pngs/">
|
|
56
|
+
<img src="https://img.shields.io/pypi/v/psd2pngs.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
|
|
57
|
+
</a>
|
|
58
|
+
<img src="https://img.shields.io/pypi/pyversions/psd2pngs.svg?style=flat-square&logo=python&logoColor=fff" alt="Supported Python versions">
|
|
59
|
+
<img src="https://img.shields.io/pypi/l/psd2pngs.svg?style=flat-square" alt="License">
|
|
60
|
+
</p>
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
**Documentation**: <a href="https://psd2pngs.readthedocs.io" target="_blank">https://psd2pngs.readthedocs.io </a>
|
|
65
|
+
|
|
66
|
+
**Source Code**: <a href="https://github.com/34j/psd2pngs" target="_blank">https://github.com/34j/psd2pngs </a>
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
Convert a PSD file to PNG files while maintaining the layer hierarchy.
|
|
71
|
+
|
|
72
|
+
## Introduction
|
|
73
|
+
|
|
74
|
+
psd2pngs converts a psd file to png files while maintaining the layer hierarchy and performing the appropriate renaming, using multiprocessing.
|
|
75
|
+
You do not have to read this long docs, just download [](https://github.com/34j/psd2pngs/releases) and open a psd file with it!
|
|
76
|
+
|
|
77
|
+
## Example
|
|
78
|
+
|
|
79
|
+

|
|
80
|
+
|
|
81
|
+
Note that this GIF is in fast forward.
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
### Using as an app
|
|
86
|
+
|
|
87
|
+
- Just open `.psd` file with this app. ([Executable version](https://github.com/34j/psd2pngs/releases) only.)
|
|
88
|
+
- Alternatively, this app can also be used with command prompt.
|
|
89
|
+
|
|
90
|
+
```shell
|
|
91
|
+
> psd2pngs -h
|
|
92
|
+
Usage: psd2pngs [OPTIONS] PSD_PATH
|
|
93
|
+
|
|
94
|
+
Options:
|
|
95
|
+
-v, --version Show the version and exit.
|
|
96
|
+
-o, --out PATH Output directory path. If not specified, output
|
|
97
|
+
to the same directory as the PSD file.
|
|
98
|
+
-s, --single-process Force not to use multiprocessing.
|
|
99
|
+
-t, --tasks-count INTEGER Number of tasks. Recommended to be less than or
|
|
100
|
+
equal to the number of CPUs (32) because the
|
|
101
|
+
process maximizes the use of CPUs.
|
|
102
|
+
-j, --json Output JSON file containing layer information in
|
|
103
|
+
snake case.
|
|
104
|
+
-jc, --json-camel-case Output JSON file containing layer information in
|
|
105
|
+
camel case.
|
|
106
|
+
-h, -?, --help Show this message and exit.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The type of content of Output JSON file (snake_case) is the following.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
class LayerInfo(NamedTuple):
|
|
113
|
+
local_path: str
|
|
114
|
+
name: str
|
|
115
|
+
safe_name: str
|
|
116
|
+
is_visible: bool
|
|
117
|
+
is_group: bool
|
|
118
|
+
children: "Iterable[LayerInfo]"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The type of content of Output JSON file (camelCase) is the following.
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
class LayerInfo(NamedTuple):
|
|
125
|
+
localPath: str
|
|
126
|
+
name: str
|
|
127
|
+
safeName: str
|
|
128
|
+
isVisible: bool
|
|
129
|
+
isGroup: bool
|
|
130
|
+
children: "Iterable[LayerInfo]"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Using as a module
|
|
134
|
+
|
|
135
|
+
See the [documentation (readthedocs.io)](https://psd2pngs.readthedocs.io/).
|
|
136
|
+
|
|
137
|
+
## Installation
|
|
138
|
+
|
|
139
|
+
### Option 1. Executable version
|
|
140
|
+
|
|
141
|
+
Download the latest release from [Releases](https://github.com/34j/psd2pngs/releases).
|
|
142
|
+
|
|
143
|
+
### Option 2. Python version using pip install
|
|
144
|
+
|
|
145
|
+
```shell
|
|
146
|
+
py -m venv venv
|
|
147
|
+
"./venv/Scripts/Activate.bat"
|
|
148
|
+
pip install psd2pngs
|
|
149
|
+
psd2pngs from.psd
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Option 3. Python version using git clone
|
|
153
|
+
|
|
154
|
+
```shell
|
|
155
|
+
git clone https://github.com/34j/psd2pngs.git
|
|
156
|
+
cd ./psd2pngs
|
|
157
|
+
py -m venv venv
|
|
158
|
+
"./venv/Scripts/Activate.bat"
|
|
159
|
+
pip install .
|
|
160
|
+
python -m psd2pngs from.psd
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Option 4. Executable version - Compiling yourself using pip
|
|
164
|
+
|
|
165
|
+
```shell
|
|
166
|
+
py -m venv venv
|
|
167
|
+
"./venv/Scripts/Activate.bat"
|
|
168
|
+
pip install .
|
|
169
|
+
pip install pyinstaller
|
|
170
|
+
pyinstaller venv/Lib/site-packages/psd2pngs/__main__.py --onefile -n psd2pngs
|
|
171
|
+
move "./dist/psd2pngs.exe" "./"
|
|
172
|
+
./psd2pngs from.psd
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Option 5. Executable version - Compiling yourself using git
|
|
176
|
+
|
|
177
|
+
```shell
|
|
178
|
+
git clone https://github.com/34j/psd2pngs.git
|
|
179
|
+
cd ./psd2pngs
|
|
180
|
+
py -m venv venv
|
|
181
|
+
"./venv/Scripts/Activate.bat"
|
|
182
|
+
pip install .
|
|
183
|
+
pip install pyinstaller
|
|
184
|
+
pyinstaller src/psd2pngs/__main__.py --onefile -n psd2pngs
|
|
185
|
+
move "./dist/psd2pngs.exe" "./"
|
|
186
|
+
./psd2pngs from.psd
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## 日本語の説明
|
|
190
|
+
|
|
191
|
+
psd2pngsは、psdファイルをレイヤーの階層構造を維持したままpngファイルに変換し、適切なリネームを行うアプリケーションです。
|
|
192
|
+
1ファイルにまとまったWindows用実行ファイル[`psd2pngs.exe`](https://github.com/34j/psd2pngs/releases)も配布しています。
|
|
193
|
+
|
|
194
|
+
### インストール方法
|
|
195
|
+
|
|
196
|
+
- [Releases](https://github.com/34j/psd2pngs/releases)から最新のリリースをダウンロードします。
|
|
197
|
+
|
|
198
|
+
### 使い方
|
|
199
|
+
|
|
200
|
+
- psdファイルを右クリックし、`プログラムから開く`を使ってこのアプリで開きます。
|
psd2pngs-1.0.0/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# psd2pngs
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://github.com/34j/psd2pngs/actions/workflows/ci.yml?query=branch%3Amain">
|
|
5
|
+
<img src="https://img.shields.io/github/actions/workflow/status/34j/psd2pngs/ci.yml?branch=main&label=CI&logo=github&style=flat-square" alt="CI Status">
|
|
6
|
+
</a>
|
|
7
|
+
<a href="https://psd2pngs.readthedocs.io">
|
|
8
|
+
<img src="https://img.shields.io/readthedocs/psd2pngs.svg?logo=read-the-docs&logoColor=fff&style=flat-square" alt="Documentation Status">
|
|
9
|
+
</a>
|
|
10
|
+
</p>
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://codecov.io/gh/34j/psd2pngs">
|
|
13
|
+
<img src="https://img.shields.io/codecov/c/github/34j/psd2pngs.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://github.com/astral-sh/uv">
|
|
16
|
+
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json" alt="uv">
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://github.com/astral-sh/ruff">
|
|
19
|
+
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff">
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://github.com/j178/prek">
|
|
22
|
+
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/j178/prek/master/docs/assets/badge-v0.json" alt="prek">
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
25
|
+
<p align="center">
|
|
26
|
+
<a href="https://pypi.org/project/psd2pngs/">
|
|
27
|
+
<img src="https://img.shields.io/pypi/v/psd2pngs.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
|
|
28
|
+
</a>
|
|
29
|
+
<img src="https://img.shields.io/pypi/pyversions/psd2pngs.svg?style=flat-square&logo=python&logoColor=fff" alt="Supported Python versions">
|
|
30
|
+
<img src="https://img.shields.io/pypi/l/psd2pngs.svg?style=flat-square" alt="License">
|
|
31
|
+
</p>
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
**Documentation**: <a href="https://psd2pngs.readthedocs.io" target="_blank">https://psd2pngs.readthedocs.io </a>
|
|
36
|
+
|
|
37
|
+
**Source Code**: <a href="https://github.com/34j/psd2pngs" target="_blank">https://github.com/34j/psd2pngs </a>
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
Convert a PSD file to PNG files while maintaining the layer hierarchy.
|
|
42
|
+
|
|
43
|
+
## Introduction
|
|
44
|
+
|
|
45
|
+
psd2pngs converts a psd file to png files while maintaining the layer hierarchy and performing the appropriate renaming, using multiprocessing.
|
|
46
|
+
You do not have to read this long docs, just download [](https://github.com/34j/psd2pngs/releases) and open a psd file with it!
|
|
47
|
+
|
|
48
|
+
## Example
|
|
49
|
+
|
|
50
|
+

|
|
51
|
+
|
|
52
|
+
Note that this GIF is in fast forward.
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### Using as an app
|
|
57
|
+
|
|
58
|
+
- Just open `.psd` file with this app. ([Executable version](https://github.com/34j/psd2pngs/releases) only.)
|
|
59
|
+
- Alternatively, this app can also be used with command prompt.
|
|
60
|
+
|
|
61
|
+
```shell
|
|
62
|
+
> psd2pngs -h
|
|
63
|
+
Usage: psd2pngs [OPTIONS] PSD_PATH
|
|
64
|
+
|
|
65
|
+
Options:
|
|
66
|
+
-v, --version Show the version and exit.
|
|
67
|
+
-o, --out PATH Output directory path. If not specified, output
|
|
68
|
+
to the same directory as the PSD file.
|
|
69
|
+
-s, --single-process Force not to use multiprocessing.
|
|
70
|
+
-t, --tasks-count INTEGER Number of tasks. Recommended to be less than or
|
|
71
|
+
equal to the number of CPUs (32) because the
|
|
72
|
+
process maximizes the use of CPUs.
|
|
73
|
+
-j, --json Output JSON file containing layer information in
|
|
74
|
+
snake case.
|
|
75
|
+
-jc, --json-camel-case Output JSON file containing layer information in
|
|
76
|
+
camel case.
|
|
77
|
+
-h, -?, --help Show this message and exit.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The type of content of Output JSON file (snake_case) is the following.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
class LayerInfo(NamedTuple):
|
|
84
|
+
local_path: str
|
|
85
|
+
name: str
|
|
86
|
+
safe_name: str
|
|
87
|
+
is_visible: bool
|
|
88
|
+
is_group: bool
|
|
89
|
+
children: "Iterable[LayerInfo]"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The type of content of Output JSON file (camelCase) is the following.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
class LayerInfo(NamedTuple):
|
|
96
|
+
localPath: str
|
|
97
|
+
name: str
|
|
98
|
+
safeName: str
|
|
99
|
+
isVisible: bool
|
|
100
|
+
isGroup: bool
|
|
101
|
+
children: "Iterable[LayerInfo]"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Using as a module
|
|
105
|
+
|
|
106
|
+
See the [documentation (readthedocs.io)](https://psd2pngs.readthedocs.io/).
|
|
107
|
+
|
|
108
|
+
## Installation
|
|
109
|
+
|
|
110
|
+
### Option 1. Executable version
|
|
111
|
+
|
|
112
|
+
Download the latest release from [Releases](https://github.com/34j/psd2pngs/releases).
|
|
113
|
+
|
|
114
|
+
### Option 2. Python version using pip install
|
|
115
|
+
|
|
116
|
+
```shell
|
|
117
|
+
py -m venv venv
|
|
118
|
+
"./venv/Scripts/Activate.bat"
|
|
119
|
+
pip install psd2pngs
|
|
120
|
+
psd2pngs from.psd
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Option 3. Python version using git clone
|
|
124
|
+
|
|
125
|
+
```shell
|
|
126
|
+
git clone https://github.com/34j/psd2pngs.git
|
|
127
|
+
cd ./psd2pngs
|
|
128
|
+
py -m venv venv
|
|
129
|
+
"./venv/Scripts/Activate.bat"
|
|
130
|
+
pip install .
|
|
131
|
+
python -m psd2pngs from.psd
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Option 4. Executable version - Compiling yourself using pip
|
|
135
|
+
|
|
136
|
+
```shell
|
|
137
|
+
py -m venv venv
|
|
138
|
+
"./venv/Scripts/Activate.bat"
|
|
139
|
+
pip install .
|
|
140
|
+
pip install pyinstaller
|
|
141
|
+
pyinstaller venv/Lib/site-packages/psd2pngs/__main__.py --onefile -n psd2pngs
|
|
142
|
+
move "./dist/psd2pngs.exe" "./"
|
|
143
|
+
./psd2pngs from.psd
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Option 5. Executable version - Compiling yourself using git
|
|
147
|
+
|
|
148
|
+
```shell
|
|
149
|
+
git clone https://github.com/34j/psd2pngs.git
|
|
150
|
+
cd ./psd2pngs
|
|
151
|
+
py -m venv venv
|
|
152
|
+
"./venv/Scripts/Activate.bat"
|
|
153
|
+
pip install .
|
|
154
|
+
pip install pyinstaller
|
|
155
|
+
pyinstaller src/psd2pngs/__main__.py --onefile -n psd2pngs
|
|
156
|
+
move "./dist/psd2pngs.exe" "./"
|
|
157
|
+
./psd2pngs from.psd
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## 日本語の説明
|
|
161
|
+
|
|
162
|
+
psd2pngsは、psdファイルをレイヤーの階層構造を維持したままpngファイルに変換し、適切なリネームを行うアプリケーションです。
|
|
163
|
+
1ファイルにまとまったWindows用実行ファイル[`psd2pngs.exe`](https://github.com/34j/psd2pngs/releases)も配布しています。
|
|
164
|
+
|
|
165
|
+
### インストール方法
|
|
166
|
+
|
|
167
|
+
- [Releases](https://github.com/34j/psd2pngs/releases)から最新のリリースをダウンロードします。
|
|
168
|
+
|
|
169
|
+
### 使い方
|
|
170
|
+
|
|
171
|
+
- psdファイルを右クリックし、`プログラムから開く`を使ってこのアプリで開きます。
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "setuptools.build_meta"
|
|
3
|
+
requires = [ "setuptools>=77.0.3" ]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "psd2pngs"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Convert a PSD file to PNG files while maintaining the layer hierarchy."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "34j" },
|
|
13
|
+
]
|
|
14
|
+
requires-python = ">=3.12"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 5 - Production/Stable",
|
|
17
|
+
"Intended Audience :: End Users/Desktop",
|
|
18
|
+
"Natural Language :: English",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
24
|
+
"Topic :: Multimedia :: Graphics :: Graphics Conversion",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"click",
|
|
28
|
+
"pillow",
|
|
29
|
+
"psd-tools",
|
|
30
|
+
"pyhumps",
|
|
31
|
+
"tqdm",
|
|
32
|
+
]
|
|
33
|
+
urls."Bug Tracker" = "https://github.com/34j/psd2pngs/issues"
|
|
34
|
+
urls.Changelog = "https://github.com/34j/psd2pngs/blob/main/CHANGELOG.md"
|
|
35
|
+
urls.documentation = "https://psd2pngs.readthedocs.io"
|
|
36
|
+
urls.repository = "https://github.com/34j/psd2pngs"
|
|
37
|
+
scripts.psd2pngs = "psd2pngs.__main__:psd2pngs"
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
dev = [
|
|
41
|
+
"coverage",
|
|
42
|
+
"packaging-34j[test]",
|
|
43
|
+
"pytest",
|
|
44
|
+
"pytest-cov",
|
|
45
|
+
"ty",
|
|
46
|
+
]
|
|
47
|
+
docs = [
|
|
48
|
+
"packaging-34j[docs]",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
extend = ".venv/ruff.toml"
|
|
53
|
+
lint.per-file-ignores."src/psd2pngs/**/*.py" = [ "ALL" ]
|
|
54
|
+
lint.per-file-ignores."tests/**/*.py" = [ "ALL" ]
|
|
55
|
+
|
|
56
|
+
[tool.pyproject-fmt]
|
|
57
|
+
max_supported_python = "3.14"
|
|
58
|
+
|
|
59
|
+
[tool.ty]
|
|
60
|
+
terminal.error-on-warning = true
|
psd2pngs-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import multiprocessing
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from psd2pngs.conversion import convert
|
|
6
|
+
from psd2pngs.version import __version__
|
|
7
|
+
|
|
8
|
+
_CONTEXT_SETTINGS = {"help_option_names": ["-?", "-h", "--help"]}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command(context_settings=_CONTEXT_SETTINGS)
|
|
12
|
+
@click.version_option(__version__, "-v", "--version", prog_name="psd2pngs")
|
|
13
|
+
@click.argument("psd_path", type=click.Path(exists=True))
|
|
14
|
+
@click.option(
|
|
15
|
+
"--out",
|
|
16
|
+
"-o",
|
|
17
|
+
"out_dir_path",
|
|
18
|
+
type=click.Path(exists=True),
|
|
19
|
+
default=None,
|
|
20
|
+
help="Output directory path. If not specified, output to the same directory as the PSD file.",
|
|
21
|
+
)
|
|
22
|
+
@click.option("--single-process", "-s", is_flag=True, help="Force not to use multiprocessing.")
|
|
23
|
+
@click.option(
|
|
24
|
+
"--tasks-count",
|
|
25
|
+
"-t",
|
|
26
|
+
"n_tasks",
|
|
27
|
+
type=int,
|
|
28
|
+
default=multiprocessing.cpu_count(),
|
|
29
|
+
help=f"Number of tasks. Recommended to be less than or equal to the number of CPUs ({multiprocessing.cpu_count()}) because the process maximizes the use of CPUs.",
|
|
30
|
+
)
|
|
31
|
+
@click.option(
|
|
32
|
+
"--json",
|
|
33
|
+
"-j",
|
|
34
|
+
"use_json",
|
|
35
|
+
is_flag=True,
|
|
36
|
+
help="Output JSON file containing layer information in snake case.",
|
|
37
|
+
)
|
|
38
|
+
@click.option(
|
|
39
|
+
"--json-camel-case",
|
|
40
|
+
"-jc",
|
|
41
|
+
"use_json_camel_case",
|
|
42
|
+
is_flag=True,
|
|
43
|
+
help="Output JSON file containing layer information in camel case.",
|
|
44
|
+
)
|
|
45
|
+
@click.option(
|
|
46
|
+
"--json-only",
|
|
47
|
+
"-jo",
|
|
48
|
+
"json_only",
|
|
49
|
+
is_flag=True,
|
|
50
|
+
help="Output JSON file only.",
|
|
51
|
+
)
|
|
52
|
+
def psd2pngs(*args, **kwargs) -> None:
|
|
53
|
+
"""This function should be called from the command line."""
|
|
54
|
+
convert(*args, **kwargs)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# This seems to be obvious, but without this, the program will loop infinitely when multiprocessing is used.
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
# For pyinstaller to work when multiprocessing is used. This should be called HERE, not anywhere else.
|
|
60
|
+
multiprocessing.freeze_support()
|
|
61
|
+
psd2pngs()
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import concurrent.futures
|
|
2
|
+
import json
|
|
3
|
+
import multiprocessing
|
|
4
|
+
from logging import DEBUG, StreamHandler, getLogger
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import humps
|
|
8
|
+
import numpy as np
|
|
9
|
+
from psd_tools import PSDImage
|
|
10
|
+
from tqdm import tqdm
|
|
11
|
+
|
|
12
|
+
from psd2pngs.layer_info import get_layer_info
|
|
13
|
+
from psd2pngs.layer_save import save_layer, save_some_layers, search_all_layers
|
|
14
|
+
from psd2pngs.unpack import unpack_nested_namedtuple
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def convert(
|
|
18
|
+
psd_path: str | Path,
|
|
19
|
+
out_dir_path: str | Path | None = None,
|
|
20
|
+
single_process: bool = False,
|
|
21
|
+
n_tasks: int = multiprocessing.cpu_count(),
|
|
22
|
+
use_json: bool = False,
|
|
23
|
+
use_json_camel_case: bool = False,
|
|
24
|
+
json_only: bool = False,
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Convert a PSD file to multiple PNG files.
|
|
27
|
+
When multiprocessing, since pickling Layers are very slow, each process will open the PSD file separately.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
psd_path : str
|
|
32
|
+
Path to the PSD file.
|
|
33
|
+
out_dir_path : Optional[str], optional
|
|
34
|
+
Output directory, by default None
|
|
35
|
+
single_process : bool, optional
|
|
36
|
+
Do not use multiprocessing, by default False
|
|
37
|
+
n_tasks : int, optional
|
|
38
|
+
Number of tasks when multiprocessing is used, by default multiprocessing.cpu_count() (Number of CPU Threads)
|
|
39
|
+
use_json : bool, optional
|
|
40
|
+
Whether to output a json file (snake_case), by default False
|
|
41
|
+
use_json_camel_case : bool, optional
|
|
42
|
+
Whether to output a json file (camelCase), by default False
|
|
43
|
+
json_only : bool, optional
|
|
44
|
+
Only generates a json file and do not convert the file, by default False
|
|
45
|
+
|
|
46
|
+
Raises
|
|
47
|
+
------
|
|
48
|
+
ValueError
|
|
49
|
+
Raises if the suffix of the PSD file is not ".psd".
|
|
50
|
+
ValueError
|
|
51
|
+
Raises if use_json and use_json_camel_case are both True.
|
|
52
|
+
ValueError
|
|
53
|
+
Raises if use_json and use_json_camel_case are both False but json_only is True.
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
# Check paths
|
|
57
|
+
psd_path_ = Path(psd_path).absolute()
|
|
58
|
+
out_dir_path_ = psd_path_.parent
|
|
59
|
+
if out_dir_path is not None:
|
|
60
|
+
out_dir_path_ = Path(out_dir_path).absolute()
|
|
61
|
+
if psd_path_.suffix != ".psd":
|
|
62
|
+
raise ValueError("The suffix of psd_path must be .psd")
|
|
63
|
+
|
|
64
|
+
# Check json options
|
|
65
|
+
if use_json and use_json_camel_case:
|
|
66
|
+
raise ValueError("Cannot use both --json and --json-camel-case.")
|
|
67
|
+
if json_only and not (use_json or use_json_camel_case):
|
|
68
|
+
raise ValueError("Cannot use --json-only without --json or --json-camel-case.")
|
|
69
|
+
|
|
70
|
+
# Open the PSD file
|
|
71
|
+
psd = PSDImage.open(psd_path_)
|
|
72
|
+
|
|
73
|
+
# Create output directory
|
|
74
|
+
out_dir_path_ = out_dir_path_.joinpath(psd_path_.stem)
|
|
75
|
+
out_dir_path_.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
|
|
77
|
+
# Get and configure logger
|
|
78
|
+
logger = getLogger(__name__)
|
|
79
|
+
logger.addHandler(StreamHandler())
|
|
80
|
+
logger.setLevel(DEBUG)
|
|
81
|
+
|
|
82
|
+
# validate values
|
|
83
|
+
if n_tasks > multiprocessing.cpu_count():
|
|
84
|
+
logger.warning("--tasks-count is larger than the number of CPUs.")
|
|
85
|
+
|
|
86
|
+
# search all layers
|
|
87
|
+
logger.info("Searching all layers...")
|
|
88
|
+
all_layers = list(tqdm(search_all_layers(psd, out_dir_path_), unit=" layer(s) found"))
|
|
89
|
+
|
|
90
|
+
# save json files
|
|
91
|
+
if use_json or use_json_camel_case:
|
|
92
|
+
logger.info("Saving JSON file...")
|
|
93
|
+
json_path = out_dir_path_.joinpath(psd_path_.stem + ".json")
|
|
94
|
+
layer_info = unpack_nested_namedtuple(get_layer_info(psd))
|
|
95
|
+
if use_json_camel_case:
|
|
96
|
+
layer_info = humps.camelize(layer_info) # type: ignore
|
|
97
|
+
with Path(json_path).open("w", encoding="utf-8") as f:
|
|
98
|
+
json.dump(layer_info, f, indent=4, ensure_ascii=False)
|
|
99
|
+
|
|
100
|
+
if json_only:
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
# save layers
|
|
104
|
+
logger.info("Saving layers...")
|
|
105
|
+
|
|
106
|
+
if not single_process:
|
|
107
|
+
# Use multiprocessing
|
|
108
|
+
logger.info("Using multiprocessing...")
|
|
109
|
+
with concurrent.futures.ProcessPoolExecutor() as executor:
|
|
110
|
+
tasks = []
|
|
111
|
+
|
|
112
|
+
# Calculate the number of layers that should be processed per task
|
|
113
|
+
n_layers = len(all_layers)
|
|
114
|
+
n_layers_per_task = np.ceil(n_layers / n_tasks).astype(int)
|
|
115
|
+
|
|
116
|
+
for i in range(n_tasks):
|
|
117
|
+
# Calculate the indcies of the layers that should be processed in this task
|
|
118
|
+
indcies = range(
|
|
119
|
+
i * n_layers_per_task,
|
|
120
|
+
np.min([(i + 1) * n_layers_per_task, n_layers]),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Create and append task
|
|
124
|
+
tasks.append(executor.submit(save_some_layers, psd_path_, out_dir_path_, indcies))
|
|
125
|
+
|
|
126
|
+
# Wait for all tasks to finish. As soon as one task finishes, tqdm progress bar will update.
|
|
127
|
+
[
|
|
128
|
+
future.result()
|
|
129
|
+
for future in tqdm(
|
|
130
|
+
concurrent.futures.as_completed(tasks),
|
|
131
|
+
total=len(tasks),
|
|
132
|
+
unit=" process(es)",
|
|
133
|
+
)
|
|
134
|
+
]
|
|
135
|
+
else:
|
|
136
|
+
# Use single process
|
|
137
|
+
logger.info("Using single process...")
|
|
138
|
+
|
|
139
|
+
for layer_info in tqdm(all_layers, unit="file(s)"):
|
|
140
|
+
save_layer(psd.size, layer_info)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import NamedTuple
|
|
4
|
+
|
|
5
|
+
from psd_tools import PSDImage
|
|
6
|
+
|
|
7
|
+
from psd2pngs.safe_name import get_safe_name
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LayerInfo(NamedTuple):
|
|
11
|
+
"""Layer information."""
|
|
12
|
+
|
|
13
|
+
local_path: str
|
|
14
|
+
"""The local path to the layer. The file name is safe_name.png."""
|
|
15
|
+
name: str
|
|
16
|
+
"""The name of the layer."""
|
|
17
|
+
safe_name: str
|
|
18
|
+
"""The safe name (which could be used as a file name) of the layer."""
|
|
19
|
+
is_visible: bool
|
|
20
|
+
"""Whether the layer is visible."""
|
|
21
|
+
is_group: bool
|
|
22
|
+
"""Whether the layer is a group."""
|
|
23
|
+
children: "Iterable[LayerInfo]"
|
|
24
|
+
"""LayerInfos of the child layers. Empty list if not a group.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_layer_info(layer: PSDImage, current_local_path: Path = Path()) -> LayerInfo:
|
|
29
|
+
"""Get LayerInfo for the given layer. Recursively get LayerInfo for all children.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
layer : PSDImage
|
|
34
|
+
The base layer.
|
|
35
|
+
current_local_path : Path, optional
|
|
36
|
+
The base path to use for LayerInfo["local_path"], by default Path("")
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
LayerInfo
|
|
41
|
+
The LayerInfo for the given layer.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
# get names
|
|
45
|
+
name = layer.name
|
|
46
|
+
safe_name = get_safe_name(name)
|
|
47
|
+
|
|
48
|
+
# get visibility
|
|
49
|
+
is_visible = layer.is_visible()
|
|
50
|
+
is_group = layer.is_group()
|
|
51
|
+
|
|
52
|
+
# avoid to create 'Root' folder because it is just disruptive
|
|
53
|
+
if layer.kind == "psdimage":
|
|
54
|
+
local_path = current_local_path
|
|
55
|
+
else:
|
|
56
|
+
local_path = current_local_path.joinpath(get_safe_name(layer.name))
|
|
57
|
+
|
|
58
|
+
# add suffix
|
|
59
|
+
if not is_group:
|
|
60
|
+
local_path = local_path.with_suffix(".png")
|
|
61
|
+
|
|
62
|
+
# recursively get LayerInfo for all children
|
|
63
|
+
children = []
|
|
64
|
+
if is_group:
|
|
65
|
+
children = [get_layer_info(child, local_path) for child in layer] # ty: ignore[invalid-argument-type]
|
|
66
|
+
|
|
67
|
+
# return LayerInfo
|
|
68
|
+
return LayerInfo(
|
|
69
|
+
local_path=str(local_path),
|
|
70
|
+
name=name,
|
|
71
|
+
safe_name=safe_name,
|
|
72
|
+
is_visible=is_visible,
|
|
73
|
+
is_group=is_group,
|
|
74
|
+
children=children,
|
|
75
|
+
)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from collections.abc import Generator, Iterable
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import NamedTuple
|
|
4
|
+
|
|
5
|
+
from PIL import Image
|
|
6
|
+
from psd_tools import PSDImage
|
|
7
|
+
from psd_tools.api.layers import Layer
|
|
8
|
+
|
|
9
|
+
from psd2pngs.safe_name import get_safe_name
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ImageLayerInfo(NamedTuple):
|
|
13
|
+
"""Layer and.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
absolute_path: Path
|
|
18
|
+
The absolute path to output the image.
|
|
19
|
+
layer: Layer
|
|
20
|
+
The layer to output.
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
absolute_path: Path
|
|
25
|
+
"""The absolute path to output the image.
|
|
26
|
+
"""
|
|
27
|
+
layer: Layer
|
|
28
|
+
"""The layer to output.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def search_all_layers(
|
|
33
|
+
layer: Layer | PSDImage, current_absolute_path: Path
|
|
34
|
+
) -> Generator[ImageLayerInfo, None, None]:
|
|
35
|
+
"""Get all ImageLayerInfos under the given layer. Recursively get all ImageLayerInfos for all children.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
layer : Layer
|
|
40
|
+
The base layer.
|
|
41
|
+
current_absolute_path : Path
|
|
42
|
+
The base absolute path to use for ImageLayerInfo["absolute_path"]
|
|
43
|
+
|
|
44
|
+
Yields
|
|
45
|
+
------
|
|
46
|
+
Generator[ImageLayerInfo, None, None]
|
|
47
|
+
ImageLayerInfos for all layers.
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
# avoid to create 'Root' folder because it is just disruptive
|
|
51
|
+
if layer.kind == "psdimage":
|
|
52
|
+
absolute_path = current_absolute_path
|
|
53
|
+
else:
|
|
54
|
+
layer_safe_name = get_safe_name(layer.name)
|
|
55
|
+
absolute_path = current_absolute_path.joinpath(layer_safe_name)
|
|
56
|
+
|
|
57
|
+
if layer.is_group():
|
|
58
|
+
# create folder for group
|
|
59
|
+
absolute_path.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
|
|
61
|
+
# recursively get all ImageLayerInfos for all children
|
|
62
|
+
for child in layer: # type: ignore
|
|
63
|
+
yield from search_all_layers(child, absolute_path)
|
|
64
|
+
else:
|
|
65
|
+
# add suffix
|
|
66
|
+
absolute_path = absolute_path.with_suffix(".png")
|
|
67
|
+
# yield ImageLayerInfo
|
|
68
|
+
yield ImageLayerInfo(absolute_path=absolute_path, layer=layer) # ty: ignore[invalid-argument-type]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def save_layer(image_size: tuple[int, int], layer_info: ImageLayerInfo) -> None:
|
|
72
|
+
"""Save the given layer (layer_info['layer']) to the given path (layer_info['absolute_path']) using PIL.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
image_size : tuple[int, int]
|
|
77
|
+
The size of the root layer (any layer which you want it to be based on). psd.size is Recommended.
|
|
78
|
+
layer_info : ImageLayerInfo
|
|
79
|
+
The layer and absolute path to save the layer to.
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
# make sure that the size and position of the layer is maintained
|
|
83
|
+
with Image.new("RGBA", image_size, (0, 0, 0, 0)) as img:
|
|
84
|
+
# convert to PIL.Image
|
|
85
|
+
img_pil = layer_info.layer.topil()
|
|
86
|
+
if img_pil is not None:
|
|
87
|
+
with img_pil:
|
|
88
|
+
# paste the layer onto the image to maintain the position
|
|
89
|
+
img.paste(img_pil, layer_info.layer.offset)
|
|
90
|
+
img.save(layer_info.absolute_path)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def save_some_layers(psd_path: Path, out_dir_path: Path, layer_indcies: Iterable[int]) -> None:
|
|
94
|
+
"""Open the PSD file and save the given layers to the given path.
|
|
95
|
+
The expected use case of this function is to use as a multiprocessing function.
|
|
96
|
+
(Because heavy layers do not have to be pickled.).
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
psd_path : Path
|
|
101
|
+
The path to the PSD file.
|
|
102
|
+
out_dir_path : Path
|
|
103
|
+
The base absolute path to create a folder in which layers will be saved.
|
|
104
|
+
layer_indcies : Iterable[int]
|
|
105
|
+
Indcies (for search_all_layers()) of the layers to save.
|
|
106
|
+
|
|
107
|
+
"""
|
|
108
|
+
# open the PSD file
|
|
109
|
+
psd = PSDImage.open(psd_path)
|
|
110
|
+
|
|
111
|
+
# search layers
|
|
112
|
+
image_layer_infos = list(search_all_layers(psd, out_dir_path))
|
|
113
|
+
|
|
114
|
+
# save specified layers
|
|
115
|
+
for i in layer_indcies:
|
|
116
|
+
save_layer(psd.size, image_layer_infos[i])
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
def get_safe_name(name: str) -> str:
|
|
2
|
+
"""Get a safe name that could be used as a file name for the given name.
|
|
3
|
+
'*' will be replaced with '-' and the rest will be replaced with '_'. (For better compatibility with PSDToolKit (well-known AviUtl Plugin)).
|
|
4
|
+
|
|
5
|
+
Parameters
|
|
6
|
+
----------
|
|
7
|
+
name : str
|
|
8
|
+
The name to get a safe name for.
|
|
9
|
+
|
|
10
|
+
Returns
|
|
11
|
+
-------
|
|
12
|
+
str
|
|
13
|
+
The safe name.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
return name.translate(str.maketrans('*\\/:?"<>| ', "-_________"))
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from collections.abc import Iterable, Mapping
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def isnamedtupleinstance(obj: object) -> bool:
|
|
5
|
+
"""Check if the given object is a namedtuple.
|
|
6
|
+
|
|
7
|
+
Parameters
|
|
8
|
+
----------
|
|
9
|
+
x : object
|
|
10
|
+
object to check.
|
|
11
|
+
|
|
12
|
+
Returns
|
|
13
|
+
-------
|
|
14
|
+
bool
|
|
15
|
+
True if the given object is a namedtuple.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
type_ = type(obj)
|
|
19
|
+
bases = type_.__bases__
|
|
20
|
+
if len(bases) != 1 or bases[0] != tuple:
|
|
21
|
+
return False
|
|
22
|
+
fields = getattr(type_, "_fields", None)
|
|
23
|
+
if not isinstance(fields, tuple):
|
|
24
|
+
return False
|
|
25
|
+
return all(type(i) == str for i in fields)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def unpack_nested_namedtuple(obj: object) -> object:
|
|
29
|
+
"""Unpack a nested namedtuple to a dict.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
obj : object
|
|
34
|
+
object to unpack.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
object
|
|
39
|
+
Unpacked object.
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
if isnamedtupleinstance(obj):
|
|
43
|
+
assert hasattr(obj, "_asdict")
|
|
44
|
+
return {key: unpack_nested_namedtuple(value) for key, value in obj._asdict().items()} # ty: ignore[call-non-callable]
|
|
45
|
+
if isinstance(obj, Mapping):
|
|
46
|
+
return {key: unpack_nested_namedtuple(value) for key, value in obj.items()}
|
|
47
|
+
if isinstance(obj, Iterable) and not isinstance(obj, str):
|
|
48
|
+
if hasattr(obj, "__init__"):
|
|
49
|
+
return type(obj)([unpack_nested_namedtuple(value) for value in obj]) # ty: ignore[too-many-positional-arguments]
|
|
50
|
+
return [unpack_nested_namedtuple(value) for value in obj]
|
|
51
|
+
return obj
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: psd2pngs
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Convert a PSD file to PNG files while maintaining the layer hierarchy.
|
|
5
|
+
Author: 34j
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/34j/psd2pngs/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/34j/psd2pngs/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: documentation, https://psd2pngs.readthedocs.io
|
|
10
|
+
Project-URL: repository, https://github.com/34j/psd2pngs
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: click
|
|
24
|
+
Requires-Dist: pillow
|
|
25
|
+
Requires-Dist: psd-tools
|
|
26
|
+
Requires-Dist: pyhumps
|
|
27
|
+
Requires-Dist: tqdm
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# psd2pngs
|
|
31
|
+
|
|
32
|
+
<p align="center">
|
|
33
|
+
<a href="https://github.com/34j/psd2pngs/actions/workflows/ci.yml?query=branch%3Amain">
|
|
34
|
+
<img src="https://img.shields.io/github/actions/workflow/status/34j/psd2pngs/ci.yml?branch=main&label=CI&logo=github&style=flat-square" alt="CI Status">
|
|
35
|
+
</a>
|
|
36
|
+
<a href="https://psd2pngs.readthedocs.io">
|
|
37
|
+
<img src="https://img.shields.io/readthedocs/psd2pngs.svg?logo=read-the-docs&logoColor=fff&style=flat-square" alt="Documentation Status">
|
|
38
|
+
</a>
|
|
39
|
+
</p>
|
|
40
|
+
<p align="center">
|
|
41
|
+
<a href="https://codecov.io/gh/34j/psd2pngs">
|
|
42
|
+
<img src="https://img.shields.io/codecov/c/github/34j/psd2pngs.svg?logo=codecov&logoColor=fff&style=flat-square" alt="Test coverage percentage">
|
|
43
|
+
</a>
|
|
44
|
+
<a href="https://github.com/astral-sh/uv">
|
|
45
|
+
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json" alt="uv">
|
|
46
|
+
</a>
|
|
47
|
+
<a href="https://github.com/astral-sh/ruff">
|
|
48
|
+
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff">
|
|
49
|
+
</a>
|
|
50
|
+
<a href="https://github.com/j178/prek">
|
|
51
|
+
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/j178/prek/master/docs/assets/badge-v0.json" alt="prek">
|
|
52
|
+
</a>
|
|
53
|
+
</p>
|
|
54
|
+
<p align="center">
|
|
55
|
+
<a href="https://pypi.org/project/psd2pngs/">
|
|
56
|
+
<img src="https://img.shields.io/pypi/v/psd2pngs.svg?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
|
|
57
|
+
</a>
|
|
58
|
+
<img src="https://img.shields.io/pypi/pyversions/psd2pngs.svg?style=flat-square&logo=python&logoColor=fff" alt="Supported Python versions">
|
|
59
|
+
<img src="https://img.shields.io/pypi/l/psd2pngs.svg?style=flat-square" alt="License">
|
|
60
|
+
</p>
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
**Documentation**: <a href="https://psd2pngs.readthedocs.io" target="_blank">https://psd2pngs.readthedocs.io </a>
|
|
65
|
+
|
|
66
|
+
**Source Code**: <a href="https://github.com/34j/psd2pngs" target="_blank">https://github.com/34j/psd2pngs </a>
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
Convert a PSD file to PNG files while maintaining the layer hierarchy.
|
|
71
|
+
|
|
72
|
+
## Introduction
|
|
73
|
+
|
|
74
|
+
psd2pngs converts a psd file to png files while maintaining the layer hierarchy and performing the appropriate renaming, using multiprocessing.
|
|
75
|
+
You do not have to read this long docs, just download [](https://github.com/34j/psd2pngs/releases) and open a psd file with it!
|
|
76
|
+
|
|
77
|
+
## Example
|
|
78
|
+
|
|
79
|
+

|
|
80
|
+
|
|
81
|
+
Note that this GIF is in fast forward.
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
### Using as an app
|
|
86
|
+
|
|
87
|
+
- Just open `.psd` file with this app. ([Executable version](https://github.com/34j/psd2pngs/releases) only.)
|
|
88
|
+
- Alternatively, this app can also be used with command prompt.
|
|
89
|
+
|
|
90
|
+
```shell
|
|
91
|
+
> psd2pngs -h
|
|
92
|
+
Usage: psd2pngs [OPTIONS] PSD_PATH
|
|
93
|
+
|
|
94
|
+
Options:
|
|
95
|
+
-v, --version Show the version and exit.
|
|
96
|
+
-o, --out PATH Output directory path. If not specified, output
|
|
97
|
+
to the same directory as the PSD file.
|
|
98
|
+
-s, --single-process Force not to use multiprocessing.
|
|
99
|
+
-t, --tasks-count INTEGER Number of tasks. Recommended to be less than or
|
|
100
|
+
equal to the number of CPUs (32) because the
|
|
101
|
+
process maximizes the use of CPUs.
|
|
102
|
+
-j, --json Output JSON file containing layer information in
|
|
103
|
+
snake case.
|
|
104
|
+
-jc, --json-camel-case Output JSON file containing layer information in
|
|
105
|
+
camel case.
|
|
106
|
+
-h, -?, --help Show this message and exit.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The type of content of Output JSON file (snake_case) is the following.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
class LayerInfo(NamedTuple):
|
|
113
|
+
local_path: str
|
|
114
|
+
name: str
|
|
115
|
+
safe_name: str
|
|
116
|
+
is_visible: bool
|
|
117
|
+
is_group: bool
|
|
118
|
+
children: "Iterable[LayerInfo]"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The type of content of Output JSON file (camelCase) is the following.
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
class LayerInfo(NamedTuple):
|
|
125
|
+
localPath: str
|
|
126
|
+
name: str
|
|
127
|
+
safeName: str
|
|
128
|
+
isVisible: bool
|
|
129
|
+
isGroup: bool
|
|
130
|
+
children: "Iterable[LayerInfo]"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Using as a module
|
|
134
|
+
|
|
135
|
+
See the [documentation (readthedocs.io)](https://psd2pngs.readthedocs.io/).
|
|
136
|
+
|
|
137
|
+
## Installation
|
|
138
|
+
|
|
139
|
+
### Option 1. Executable version
|
|
140
|
+
|
|
141
|
+
Download the latest release from [Releases](https://github.com/34j/psd2pngs/releases).
|
|
142
|
+
|
|
143
|
+
### Option 2. Python version using pip install
|
|
144
|
+
|
|
145
|
+
```shell
|
|
146
|
+
py -m venv venv
|
|
147
|
+
"./venv/Scripts/Activate.bat"
|
|
148
|
+
pip install psd2pngs
|
|
149
|
+
psd2pngs from.psd
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Option 3. Python version using git clone
|
|
153
|
+
|
|
154
|
+
```shell
|
|
155
|
+
git clone https://github.com/34j/psd2pngs.git
|
|
156
|
+
cd ./psd2pngs
|
|
157
|
+
py -m venv venv
|
|
158
|
+
"./venv/Scripts/Activate.bat"
|
|
159
|
+
pip install .
|
|
160
|
+
python -m psd2pngs from.psd
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Option 4. Executable version - Compiling yourself using pip
|
|
164
|
+
|
|
165
|
+
```shell
|
|
166
|
+
py -m venv venv
|
|
167
|
+
"./venv/Scripts/Activate.bat"
|
|
168
|
+
pip install .
|
|
169
|
+
pip install pyinstaller
|
|
170
|
+
pyinstaller venv/Lib/site-packages/psd2pngs/__main__.py --onefile -n psd2pngs
|
|
171
|
+
move "./dist/psd2pngs.exe" "./"
|
|
172
|
+
./psd2pngs from.psd
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Option 5. Executable version - Compiling yourself using git
|
|
176
|
+
|
|
177
|
+
```shell
|
|
178
|
+
git clone https://github.com/34j/psd2pngs.git
|
|
179
|
+
cd ./psd2pngs
|
|
180
|
+
py -m venv venv
|
|
181
|
+
"./venv/Scripts/Activate.bat"
|
|
182
|
+
pip install .
|
|
183
|
+
pip install pyinstaller
|
|
184
|
+
pyinstaller src/psd2pngs/__main__.py --onefile -n psd2pngs
|
|
185
|
+
move "./dist/psd2pngs.exe" "./"
|
|
186
|
+
./psd2pngs from.psd
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## 日本語の説明
|
|
190
|
+
|
|
191
|
+
psd2pngsは、psdファイルをレイヤーの階層構造を維持したままpngファイルに変換し、適切なリネームを行うアプリケーションです。
|
|
192
|
+
1ファイルにまとまったWindows用実行ファイル[`psd2pngs.exe`](https://github.com/34j/psd2pngs/releases)も配布しています。
|
|
193
|
+
|
|
194
|
+
### インストール方法
|
|
195
|
+
|
|
196
|
+
- [Releases](https://github.com/34j/psd2pngs/releases)から最新のリリースをダウンロードします。
|
|
197
|
+
|
|
198
|
+
### 使い方
|
|
199
|
+
|
|
200
|
+
- psdファイルを右クリックし、`プログラムから開く`を使ってこのアプリで開きます。
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/psd2pngs/__init__.py
|
|
5
|
+
src/psd2pngs/__main__.py
|
|
6
|
+
src/psd2pngs/conversion.py
|
|
7
|
+
src/psd2pngs/layer_info.py
|
|
8
|
+
src/psd2pngs/layer_save.py
|
|
9
|
+
src/psd2pngs/safe_name.py
|
|
10
|
+
src/psd2pngs/unpack.py
|
|
11
|
+
src/psd2pngs/version.py
|
|
12
|
+
src/psd2pngs.egg-info/PKG-INFO
|
|
13
|
+
src/psd2pngs.egg-info/SOURCES.txt
|
|
14
|
+
src/psd2pngs.egg-info/dependency_links.txt
|
|
15
|
+
src/psd2pngs.egg-info/entry_points.txt
|
|
16
|
+
src/psd2pngs.egg-info/requires.txt
|
|
17
|
+
src/psd2pngs.egg-info/top_level.txt
|
|
18
|
+
tests/test_convert.py
|
|
19
|
+
tests/test_multiprocessing.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
psd2pngs
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import concurrent.futures
|
|
2
|
+
from itertools import repeat
|
|
3
|
+
from unittest import TestCase
|
|
4
|
+
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def multiprocessing_task(pbar):
|
|
9
|
+
for _i in range(200000):
|
|
10
|
+
pbar.update(1) # No errors but does not work
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MultiprocessingTest(TestCase):
|
|
14
|
+
def test_tqdm(self):
|
|
15
|
+
pbar = tqdm(range(1000000))
|
|
16
|
+
with concurrent.futures.ProcessPoolExecutor() as executor:
|
|
17
|
+
executor.map(multiprocessing_task, repeat(pbar), range(5))
|