fotolab 0.29.2__py3-none-any.whl → 0.30.0__py3-none-any.whl
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.
- fotolab/__init__.py +1 -1
- fotolab/cli.py +4 -4
- fotolab/subcommands/animate.py +89 -27
- fotolab/subcommands/contrast.py +3 -1
- {fotolab-0.29.2.dist-info → fotolab-0.30.0.dist-info}/METADATA +10 -3
- {fotolab-0.29.2.dist-info → fotolab-0.30.0.dist-info}/RECORD +9 -9
- {fotolab-0.29.2.dist-info → fotolab-0.30.0.dist-info}/LICENSE.md +0 -0
- {fotolab-0.29.2.dist-info → fotolab-0.30.0.dist-info}/WHEEL +0 -0
- {fotolab-0.29.2.dist-info → fotolab-0.30.0.dist-info}/entry_points.txt +0 -0
fotolab/__init__.py
CHANGED
fotolab/cli.py
CHANGED
@@ -15,9 +15,9 @@
|
|
15
15
|
|
16
16
|
"""A console program to manipulate photos.
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
website: https://github.com/kianmeng/fotolab
|
19
|
+
changelog: https://github.com/kianmeng/fotolab/blob/master/CHANGELOG.md
|
20
|
+
issues: https://github.com/kianmeng/fotolab/issues
|
21
21
|
"""
|
22
22
|
|
23
23
|
import argparse
|
@@ -143,7 +143,7 @@ def main(args: Optional[Sequence[str]] = None) -> None:
|
|
143
143
|
# correctly
|
144
144
|
log.error(
|
145
145
|
"subcommand '%s' is missing its execution function.",
|
146
|
-
parsed_args.command
|
146
|
+
parsed_args.command,
|
147
147
|
)
|
148
148
|
parser.print_help(sys.stderr)
|
149
149
|
raise SystemExit(1)
|
fotolab/subcommands/animate.py
CHANGED
@@ -26,6 +26,21 @@ from fotolab import _open_image
|
|
26
26
|
log = logging.getLogger(__name__)
|
27
27
|
|
28
28
|
|
29
|
+
def _validate_duration(value: str) -> int:
|
30
|
+
"""Validate that the duration is a positive integer."""
|
31
|
+
try:
|
32
|
+
ivalue = int(value)
|
33
|
+
if ivalue <= 0:
|
34
|
+
raise argparse.ArgumentTypeError(
|
35
|
+
f"duration must be a positive integer, but got {value}"
|
36
|
+
)
|
37
|
+
return ivalue
|
38
|
+
except ValueError as e:
|
39
|
+
raise argparse.ArgumentTypeError(
|
40
|
+
f"duration must be an integer, but got '{value}'"
|
41
|
+
) from e
|
42
|
+
|
43
|
+
|
29
44
|
def build_subparser(subparsers) -> None:
|
30
45
|
"""Build the subparser."""
|
31
46
|
animate_parser = subparsers.add_parser("animate", help="animate an image")
|
@@ -56,9 +71,9 @@ def build_subparser(subparsers) -> None:
|
|
56
71
|
"-d",
|
57
72
|
"--duration",
|
58
73
|
dest="duration",
|
59
|
-
type=
|
74
|
+
type=_validate_duration,
|
60
75
|
default=2500,
|
61
|
-
help="set the duration in milliseconds (default: '%(default)s')",
|
76
|
+
help="set the duration in milliseconds (must be a positive integer, default: '%(default)s')",
|
62
77
|
metavar="DURATION",
|
63
78
|
)
|
64
79
|
|
@@ -81,6 +96,34 @@ def build_subparser(subparsers) -> None:
|
|
81
96
|
help="open the image using default program (default: '%(default)s')",
|
82
97
|
)
|
83
98
|
|
99
|
+
animate_parser.add_argument(
|
100
|
+
"--webp-quality",
|
101
|
+
dest="webp_quality",
|
102
|
+
type=int,
|
103
|
+
default=80,
|
104
|
+
choices=range(0, 101),
|
105
|
+
help="set WEBP quality (0-100, default: '%(default)s')",
|
106
|
+
metavar="QUALITY",
|
107
|
+
)
|
108
|
+
|
109
|
+
animate_parser.add_argument(
|
110
|
+
"--webp-lossless",
|
111
|
+
dest="webp_lossless",
|
112
|
+
default=False,
|
113
|
+
action="store_true",
|
114
|
+
help="enable WEBP lossless compression (default: '%(default)s')",
|
115
|
+
)
|
116
|
+
|
117
|
+
animate_parser.add_argument(
|
118
|
+
"--webp-method",
|
119
|
+
dest="webp_method",
|
120
|
+
type=int,
|
121
|
+
default=4,
|
122
|
+
choices=range(0, 7),
|
123
|
+
help="set WEBP encoding method (0=fast, 6=slow/best, default: '%(default)s')",
|
124
|
+
metavar="METHOD",
|
125
|
+
)
|
126
|
+
|
84
127
|
animate_parser.add_argument(
|
85
128
|
"-od",
|
86
129
|
"--output-dir",
|
@@ -101,31 +144,50 @@ def run(args: argparse.Namespace) -> None:
|
|
101
144
|
"""
|
102
145
|
log.debug(args)
|
103
146
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
147
|
+
first_image_filepath = args.image_filenames[0]
|
148
|
+
main_frame = None
|
149
|
+
other_frames = []
|
150
|
+
|
151
|
+
try:
|
152
|
+
main_frame = Image.open(first_image_filepath)
|
153
|
+
|
154
|
+
for image_filename in args.image_filenames[1:]:
|
155
|
+
img = Image.open(image_filename)
|
156
|
+
other_frames.append(img)
|
157
|
+
|
158
|
+
image_file = Path(first_image_filepath)
|
159
|
+
new_filename = Path(
|
160
|
+
args.output_dir,
|
161
|
+
image_file.with_name(f"animate_{image_file.stem}.{args.format}"),
|
162
|
+
)
|
163
|
+
new_filename.parent.mkdir(parents=True, exist_ok=True)
|
164
|
+
|
165
|
+
log.info("animate image: %s", new_filename)
|
166
|
+
|
167
|
+
save_kwargs = {
|
168
|
+
"format": args.format,
|
169
|
+
"append_images": other_frames,
|
170
|
+
"save_all": True,
|
171
|
+
"duration": args.duration,
|
172
|
+
"loop": args.loop,
|
173
|
+
"optimize": True, # General optimization, good for GIF
|
174
|
+
}
|
175
|
+
|
176
|
+
if args.format == "webp":
|
177
|
+
save_kwargs["quality"] = args.webp_quality
|
178
|
+
save_kwargs["lossless"] = args.webp_lossless
|
179
|
+
save_kwargs["method"] = args.webp_method
|
180
|
+
# Pillow's WEBP save doesn't use a general 'optimize' like GIF.
|
181
|
+
# Specific WEBP params like 'method' and 'quality' control this.
|
182
|
+
# We can remove 'optimize' if it causes issues or is ignored for WEBP.
|
183
|
+
# For now, let's keep it, Pillow might handle it or ignore it.
|
184
|
+
|
185
|
+
main_frame.save(new_filename, **save_kwargs)
|
186
|
+
finally:
|
187
|
+
if main_frame:
|
188
|
+
main_frame.close()
|
189
|
+
for frame in other_frames:
|
190
|
+
frame.close()
|
129
191
|
|
130
192
|
if args.open:
|
131
193
|
_open_image(new_filename)
|
fotolab/subcommands/contrast.py
CHANGED
@@ -31,7 +31,9 @@ def _validate_cutoff(value: str) -> float:
|
|
31
31
|
try:
|
32
32
|
f_value = float(value)
|
33
33
|
except ValueError as e:
|
34
|
-
raise argparse.ArgumentTypeError(
|
34
|
+
raise argparse.ArgumentTypeError(
|
35
|
+
f"invalid float value: '{value}'"
|
36
|
+
) from e
|
35
37
|
if not 0 <= f_value <= 50:
|
36
38
|
raise argparse.ArgumentTypeError(
|
37
39
|
f"cutoff value {f_value} must be between 0 and 50"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fotolab
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.30.0
|
4
4
|
Summary: A console program that manipulate images.
|
5
5
|
Keywords: photography,photo
|
6
6
|
Author-email: Kian-Meng Ang <kianmeng@cpan.org>
|
@@ -106,7 +106,8 @@ fotolab animate -h
|
|
106
106
|
|
107
107
|
```console
|
108
108
|
usage: fotolab animate [-h] [-f FORMAT] [-d DURATION] [-l LOOP] [-op]
|
109
|
-
[-
|
109
|
+
[--webp-quality QUALITY] [--webp-lossless]
|
110
|
+
[--webp-method METHOD] [-od OUTPUT_DIR]
|
110
111
|
IMAGE_FILENAMES [IMAGE_FILENAMES ...]
|
111
112
|
|
112
113
|
positional arguments:
|
@@ -116,10 +117,16 @@ options:
|
|
116
117
|
-h, --help show this help message and exit
|
117
118
|
-f, --format FORMAT set the image format (default: 'gif')
|
118
119
|
-d, --duration DURATION
|
119
|
-
set the duration in milliseconds (
|
120
|
+
set the duration in milliseconds (must be a positive
|
121
|
+
integer, default: '2500')
|
120
122
|
-l, --loop LOOP set the loop cycle (default: '0')
|
121
123
|
-op, --open open the image using default program (default:
|
122
124
|
'False')
|
125
|
+
--webp-quality QUALITY
|
126
|
+
set WEBP quality (0-100, default: '80')
|
127
|
+
--webp-lossless enable WEBP lossless compression (default: 'False')
|
128
|
+
--webp-method METHOD set WEBP encoding method (0=fast, 6=slow/best,
|
129
|
+
default: '4')
|
123
130
|
-od, --output-dir OUTPUT_DIR
|
124
131
|
set default output folder (default: 'output')
|
125
132
|
```
|
@@ -1,11 +1,11 @@
|
|
1
|
-
fotolab/__init__.py,sha256=
|
1
|
+
fotolab/__init__.py,sha256=RNGEVnKSQe-y5yy2fi65zOV5cJGZk9tw9o7id0xBMX4,3262
|
2
2
|
fotolab/__main__.py,sha256=aboOURPs_snOXTEWYR0q8oq1UTY9e-NxCd1j33V0wHI,833
|
3
|
-
fotolab/cli.py,sha256=
|
3
|
+
fotolab/cli.py,sha256=oFiQXmsu3wIsM_DpZnL4B94sAoB62L16Am-cjxGmosY,4406
|
4
4
|
fotolab/subcommands/__init__.py,sha256=l3DlIaJ3u3jGjnC1H1yV8LZ_nPqOLJ6gikD4BCaMAQ0,1129
|
5
|
-
fotolab/subcommands/animate.py,sha256=
|
5
|
+
fotolab/subcommands/animate.py,sha256=vmviz3cLnHfVENxFKiTimhx8nmbGbzumOP6dUd_UiUI,5524
|
6
6
|
fotolab/subcommands/auto.py,sha256=ia-xegV1Z4HvYsbKgmTzf1NfNFdTDPWfZe7vQ1_90Ik,2425
|
7
7
|
fotolab/subcommands/border.py,sha256=BS3BHytdWiNumxdKulKYK-WigbsKtPxECdvInUhUjSQ,4608
|
8
|
-
fotolab/subcommands/contrast.py,sha256=
|
8
|
+
fotolab/subcommands/contrast.py,sha256=fcXmHnxDw74j5ZUDQ5cwWh0N4tpyqqvEjymnpITgrEk,3027
|
9
9
|
fotolab/subcommands/env.py,sha256=QoxRvzZKgmoHTUxDV4QYhdChCpMWs5TbXFY_qIpIQpE,1469
|
10
10
|
fotolab/subcommands/halftone.py,sha256=lt6RV0OuZkGs1LigTC1EcCCY42CocPFHWaJTDjJ5LFM,6693
|
11
11
|
fotolab/subcommands/info.py,sha256=vie578cEOesAyHohm0xCWwZBqXpblYJtdHGSyBgxsoU,3581
|
@@ -14,8 +14,8 @@ fotolab/subcommands/resize.py,sha256=UOb2rg_5ArRj0gxPTDOww_ix3tqJRC0W5b_S-Lt1G4w
|
|
14
14
|
fotolab/subcommands/rotate.py,sha256=uBFjHyjiBSQLtrtH1p9myODIHUDr1gkL4PpU-6Y1Ofo,2575
|
15
15
|
fotolab/subcommands/sharpen.py,sha256=YNho2IPbc-lPvSy3Bsjehc2JOEy27LPqFSGRULs9MyY,3492
|
16
16
|
fotolab/subcommands/watermark.py,sha256=qRGUp1Lc22fZSJDFRqQGPiz8RSB293ebvOVTdsDLUE4,10351
|
17
|
-
fotolab-0.
|
18
|
-
fotolab-0.
|
19
|
-
fotolab-0.
|
20
|
-
fotolab-0.
|
21
|
-
fotolab-0.
|
17
|
+
fotolab-0.30.0.dist-info/entry_points.txt,sha256=mvw7AY_yZkIyjAxPtHNed9X99NZeLnMxEeAfEJUbrCM,44
|
18
|
+
fotolab-0.30.0.dist-info/LICENSE.md,sha256=tGtFDwxWTjuR9syrJoSv1Hiffd2u8Tu8cYClfrXS_YU,31956
|
19
|
+
fotolab-0.30.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
20
|
+
fotolab-0.30.0.dist-info/METADATA,sha256=tPOkSTJz_hy0seegQ60vrMKDyAcbwZWcIBwPl0ZU5Ac,13395
|
21
|
+
fotolab-0.30.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|