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 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.
@@ -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&amp;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 [![Executable](https://img.shields.io/badge/psd2pngs.exe-Click-darkblue)](https://github.com/34j/psd2pngs/releases) and open a psd file with it!
76
+
77
+ ## Example
78
+
79
+ ![Screenshot GIF](Example.gif)
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,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&amp;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 [![Executable](https://img.shields.io/badge/psd2pngs.exe-Click-darkblue)](https://github.com/34j/psd2pngs/releases) and open a psd file with it!
47
+
48
+ ## Example
49
+
50
+ ![Screenshot GIF](Example.gif)
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
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from .conversion import convert
2
+ from .version import __version__
3
+
4
+ __all__ = ["__version__", "convert"]
@@ -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,3 @@
1
+ """Version of psd2pngs."""
2
+
3
+ __version__ = "1.2.4"
@@ -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&amp;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 [![Executable](https://img.shields.io/badge/psd2pngs.exe-Click-darkblue)](https://github.com/34j/psd2pngs/releases) and open a psd file with it!
76
+
77
+ ## Example
78
+
79
+ ![Screenshot GIF](Example.gif)
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,2 @@
1
+ [console_scripts]
2
+ psd2pngs = psd2pngs.__main__:psd2pngs
@@ -0,0 +1,5 @@
1
+ click
2
+ pillow
3
+ psd-tools
4
+ pyhumps
5
+ tqdm
@@ -0,0 +1 @@
1
+ psd2pngs
@@ -0,0 +1,10 @@
1
+ from unittest import TestCase
2
+
3
+ from psd2pngs import convert
4
+
5
+
6
+ class TestConvert(TestCase):
7
+ def test_convert(self):
8
+ convert(
9
+ r"tests/cc0-psds/1.psd", use_json_camel_case=True, out_dir_path="tests/.test_results"
10
+ )
@@ -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))