fotolab 0.34.1__py3-none-any.whl → 0.34.3__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 +12 -0
- fotolab/subcommands/__init__.py +1 -0
- fotolab/subcommands/animate.py +6 -30
- fotolab/subcommands/auto.py +3 -10
- fotolab/subcommands/border.py +14 -37
- fotolab/subcommands/common.py +68 -0
- fotolab/subcommands/contrast.py +27 -50
- fotolab/subcommands/halftone.py +18 -41
- fotolab/subcommands/info.py +3 -1
- fotolab/subcommands/montage.py +20 -41
- fotolab/subcommands/resize.py +59 -83
- fotolab/subcommands/rotate.py +12 -35
- fotolab/subcommands/sharpen.py +22 -43
- fotolab/subcommands/watermark.py +17 -38
- {fotolab-0.34.1.dist-info → fotolab-0.34.3.dist-info}/METADATA +1 -1
- fotolab-0.34.3.dist-info/RECORD +23 -0
- fotolab-0.34.1.dist-info/RECORD +0 -22
- {fotolab-0.34.1.dist-info → fotolab-0.34.3.dist-info}/WHEEL +0 -0
- {fotolab-0.34.1.dist-info → fotolab-0.34.3.dist-info}/entry_points.txt +0 -0
- {fotolab-0.34.1.dist-info → fotolab-0.34.3.dist-info}/licenses/LICENSE.md +0 -0
- {fotolab-0.34.1.dist-info → fotolab-0.34.3.dist-info}/top_level.txt +0 -0
fotolab/__init__.py
CHANGED
|
@@ -22,6 +22,8 @@ import logging
|
|
|
22
22
|
import os
|
|
23
23
|
import subprocess
|
|
24
24
|
import sys
|
|
25
|
+
from contextlib import contextmanager
|
|
26
|
+
from typing import Iterator
|
|
25
27
|
|
|
26
28
|
from PIL import Image
|
|
27
29
|
|
|
@@ -98,3 +100,13 @@ def open_image(filename: Path):
|
|
|
98
100
|
|
|
99
101
|
except (OSError, FileNotFoundError) as error:
|
|
100
102
|
log.error("Error opening image: %s -> %s", filename, error)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@contextmanager
|
|
106
|
+
def load_image(filename: Path) -> Iterator[Image.Image]:
|
|
107
|
+
"""Load image using a context manager to ensure file handle is closed."""
|
|
108
|
+
try:
|
|
109
|
+
image = Image.open(filename)
|
|
110
|
+
yield image
|
|
111
|
+
finally:
|
|
112
|
+
image.close()
|
fotolab/subcommands/__init__.py
CHANGED
fotolab/subcommands/animate.py
CHANGED
|
@@ -20,9 +20,9 @@ import logging
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from contextlib import ExitStack
|
|
22
22
|
|
|
23
|
-
from PIL import Image
|
|
24
23
|
|
|
25
|
-
from fotolab import open_image
|
|
24
|
+
from fotolab import load_image, open_image
|
|
25
|
+
from .common import add_common_arguments, log_args_decorator
|
|
26
26
|
|
|
27
27
|
log = logging.getLogger(__name__)
|
|
28
28
|
|
|
@@ -48,14 +48,7 @@ def build_subparser(subparsers) -> None:
|
|
|
48
48
|
|
|
49
49
|
animate_parser.set_defaults(func=run)
|
|
50
50
|
|
|
51
|
-
animate_parser
|
|
52
|
-
dest="image_paths",
|
|
53
|
-
help="set the image filenames",
|
|
54
|
-
nargs="+",
|
|
55
|
-
type=str,
|
|
56
|
-
default=None,
|
|
57
|
-
metavar="IMAGE_PATHS",
|
|
58
|
-
)
|
|
51
|
+
add_common_arguments(animate_parser)
|
|
59
52
|
|
|
60
53
|
animate_parser.add_argument(
|
|
61
54
|
"-f",
|
|
@@ -91,15 +84,6 @@ def build_subparser(subparsers) -> None:
|
|
|
91
84
|
metavar="LOOP",
|
|
92
85
|
)
|
|
93
86
|
|
|
94
|
-
animate_parser.add_argument(
|
|
95
|
-
"-op",
|
|
96
|
-
"--open",
|
|
97
|
-
default=False,
|
|
98
|
-
action="store_true",
|
|
99
|
-
dest="open",
|
|
100
|
-
help="open the image using default program (default: '%(default)s')",
|
|
101
|
-
)
|
|
102
|
-
|
|
103
87
|
animate_parser.add_argument(
|
|
104
88
|
"--webp-quality",
|
|
105
89
|
dest="webp_quality",
|
|
@@ -131,14 +115,6 @@ def build_subparser(subparsers) -> None:
|
|
|
131
115
|
metavar="METHOD",
|
|
132
116
|
)
|
|
133
117
|
|
|
134
|
-
animate_parser.add_argument(
|
|
135
|
-
"-od",
|
|
136
|
-
"--output-dir",
|
|
137
|
-
dest="output_dir",
|
|
138
|
-
default="output",
|
|
139
|
-
help="set default output folder (default: '%(default)s')",
|
|
140
|
-
)
|
|
141
|
-
|
|
142
118
|
animate_parser.add_argument(
|
|
143
119
|
"-of",
|
|
144
120
|
"--output-filename",
|
|
@@ -148,6 +124,7 @@ def build_subparser(subparsers) -> None:
|
|
|
148
124
|
)
|
|
149
125
|
|
|
150
126
|
|
|
127
|
+
@log_args_decorator
|
|
151
128
|
def run(args: argparse.Namespace) -> None:
|
|
152
129
|
"""Run animate subcommand.
|
|
153
130
|
|
|
@@ -157,17 +134,16 @@ def run(args: argparse.Namespace) -> None:
|
|
|
157
134
|
Returns:
|
|
158
135
|
None
|
|
159
136
|
"""
|
|
160
|
-
log.debug(args)
|
|
161
137
|
|
|
162
138
|
image_filepaths = [Path(f) for f in args.image_paths]
|
|
163
139
|
first_image_filepath = image_filepaths[0]
|
|
164
140
|
other_frames = []
|
|
165
141
|
|
|
166
142
|
with ExitStack() as stack:
|
|
167
|
-
main_frame = stack.enter_context(
|
|
143
|
+
main_frame = stack.enter_context(load_image(first_image_filepath))
|
|
168
144
|
|
|
169
145
|
for image_filepath in image_filepaths[1:]:
|
|
170
|
-
img = stack.enter_context(
|
|
146
|
+
img = stack.enter_context(load_image(image_filepath))
|
|
171
147
|
other_frames.append(img)
|
|
172
148
|
|
|
173
149
|
if args.output_filename:
|
fotolab/subcommands/auto.py
CHANGED
|
@@ -23,6 +23,7 @@ import fotolab.subcommands.contrast
|
|
|
23
23
|
import fotolab.subcommands.resize
|
|
24
24
|
import fotolab.subcommands.sharpen
|
|
25
25
|
import fotolab.subcommands.watermark
|
|
26
|
+
from .common import add_common_arguments, log_args_decorator
|
|
26
27
|
|
|
27
28
|
log = logging.getLogger(__name__)
|
|
28
29
|
|
|
@@ -35,14 +36,7 @@ def build_subparser(subparsers) -> None:
|
|
|
35
36
|
|
|
36
37
|
auto_parser.set_defaults(func=run)
|
|
37
38
|
|
|
38
|
-
auto_parser
|
|
39
|
-
dest="image_paths",
|
|
40
|
-
help="set the image filename",
|
|
41
|
-
nargs="+",
|
|
42
|
-
type=str,
|
|
43
|
-
default=None,
|
|
44
|
-
metavar="IMAGE_PATHS",
|
|
45
|
-
)
|
|
39
|
+
add_common_arguments(auto_parser)
|
|
46
40
|
|
|
47
41
|
auto_parser.add_argument(
|
|
48
42
|
"-t",
|
|
@@ -65,6 +59,7 @@ def build_subparser(subparsers) -> None:
|
|
|
65
59
|
)
|
|
66
60
|
|
|
67
61
|
|
|
62
|
+
@log_args_decorator
|
|
68
63
|
def run(args: argparse.Namespace) -> None:
|
|
69
64
|
"""Run auto subcommand.
|
|
70
65
|
|
|
@@ -106,7 +101,6 @@ def run(args: argparse.Namespace) -> None:
|
|
|
106
101
|
combined_args.overwrite = True
|
|
107
102
|
combined_args.open = False
|
|
108
103
|
|
|
109
|
-
log.debug(args)
|
|
110
104
|
log.debug(combined_args)
|
|
111
105
|
|
|
112
106
|
fotolab.subcommands.resize.run(combined_args)
|
|
@@ -118,7 +112,6 @@ def run(args: argparse.Namespace) -> None:
|
|
|
118
112
|
output_filename = (
|
|
119
113
|
args.title.lower().replace(",", "").replace(" ", "_") + ".gif"
|
|
120
114
|
)
|
|
121
|
-
combined_args.output_dir = "output"
|
|
122
115
|
combined_args.format = "gif"
|
|
123
116
|
combined_args.duration = 2500
|
|
124
117
|
combined_args.loop = 0
|
fotolab/subcommands/border.py
CHANGED
|
@@ -20,9 +20,10 @@ import logging
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from typing import Tuple
|
|
22
22
|
|
|
23
|
-
from PIL import
|
|
23
|
+
from PIL import ImageColor, ImageOps
|
|
24
24
|
|
|
25
|
-
from fotolab import save_image
|
|
25
|
+
from fotolab import load_image, save_image
|
|
26
|
+
from .common import add_common_arguments, log_args_decorator
|
|
26
27
|
|
|
27
28
|
log = logging.getLogger(__name__)
|
|
28
29
|
|
|
@@ -33,14 +34,7 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
33
34
|
|
|
34
35
|
border_parser.set_defaults(func=run)
|
|
35
36
|
|
|
36
|
-
border_parser
|
|
37
|
-
dest="image_paths",
|
|
38
|
-
help="set the image filenames",
|
|
39
|
-
nargs="+",
|
|
40
|
-
type=str,
|
|
41
|
-
default=None,
|
|
42
|
-
metavar="IMAGE_PATHS",
|
|
43
|
-
)
|
|
37
|
+
add_common_arguments(border_parser)
|
|
44
38
|
|
|
45
39
|
border_parser.add_argument(
|
|
46
40
|
"-c",
|
|
@@ -106,24 +100,8 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
106
100
|
metavar="WIDTH",
|
|
107
101
|
)
|
|
108
102
|
|
|
109
|
-
border_parser.add_argument(
|
|
110
|
-
"-op",
|
|
111
|
-
"--open",
|
|
112
|
-
default=False,
|
|
113
|
-
action="store_true",
|
|
114
|
-
dest="open",
|
|
115
|
-
help="open the image using default program (default: '%(default)s')",
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
border_parser.add_argument(
|
|
119
|
-
"-od",
|
|
120
|
-
"--output-dir",
|
|
121
|
-
dest="output_dir",
|
|
122
|
-
default="output",
|
|
123
|
-
help="set default output folder (default: '%(default)s')",
|
|
124
|
-
)
|
|
125
|
-
|
|
126
103
|
|
|
104
|
+
@log_args_decorator
|
|
127
105
|
def run(args: argparse.Namespace) -> None:
|
|
128
106
|
"""Run border subcommand.
|
|
129
107
|
|
|
@@ -133,18 +111,17 @@ def run(args: argparse.Namespace) -> None:
|
|
|
133
111
|
Returns:
|
|
134
112
|
None
|
|
135
113
|
"""
|
|
136
|
-
log.debug(args)
|
|
137
114
|
|
|
138
115
|
for image_filepath in [Path(f) for f in args.image_paths]:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
116
|
+
with load_image(image_filepath) as original_image:
|
|
117
|
+
border = get_border(args)
|
|
118
|
+
bordered_image = ImageOps.expand(
|
|
119
|
+
original_image,
|
|
120
|
+
border=border,
|
|
121
|
+
fill=ImageColor.getrgb(args.color),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
save_image(args, bordered_image, image_filepath, "border")
|
|
148
125
|
|
|
149
126
|
|
|
150
127
|
def get_border(
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright (C) 2024,2025 Kian-Meng Ang
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify it under
|
|
4
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
|
5
|
+
#
|
|
6
|
+
# Software Foundation, either version 3 of the License, or (at your option) any
|
|
7
|
+
# later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
10
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
11
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
12
|
+
# details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
"""Common argument parsing for subcommands."""
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import logging
|
|
21
|
+
from functools import wraps
|
|
22
|
+
from typing import Callable
|
|
23
|
+
|
|
24
|
+
log = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def log_args_decorator(func: Callable) -> Callable:
|
|
28
|
+
"""Decorator to log the arguments passed to a function."""
|
|
29
|
+
|
|
30
|
+
@wraps(func)
|
|
31
|
+
def wrapper(args: argparse.Namespace) -> None:
|
|
32
|
+
log.debug(args)
|
|
33
|
+
return func(args)
|
|
34
|
+
|
|
35
|
+
return wrapper
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def add_common_arguments(parser: argparse.ArgumentParser) -> None:
|
|
39
|
+
"""Add common arguments to a subparser.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
parser (argparse.ArgumentParser): The subparser to add arguments to.
|
|
43
|
+
"""
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
dest="image_paths",
|
|
46
|
+
help="set the image filenames",
|
|
47
|
+
nargs="+",
|
|
48
|
+
type=str,
|
|
49
|
+
default=None,
|
|
50
|
+
metavar="IMAGE_PATHS",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
parser.add_argument(
|
|
54
|
+
"-op",
|
|
55
|
+
"--open",
|
|
56
|
+
default=False,
|
|
57
|
+
action="store_true",
|
|
58
|
+
dest="open",
|
|
59
|
+
help="open the image using default program (default: '%(default)s')",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"-od",
|
|
64
|
+
"--output-dir",
|
|
65
|
+
dest="output_dir",
|
|
66
|
+
default="output",
|
|
67
|
+
help="set default output folder (default: '%(default)s')",
|
|
68
|
+
)
|
fotolab/subcommands/contrast.py
CHANGED
|
@@ -19,9 +19,10 @@ import argparse
|
|
|
19
19
|
import logging
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
|
|
22
|
-
from PIL import
|
|
22
|
+
from PIL import ImageOps
|
|
23
23
|
|
|
24
|
-
from fotolab import save_image
|
|
24
|
+
from fotolab import load_image, save_image
|
|
25
|
+
from .common import add_common_arguments, log_args_decorator
|
|
25
26
|
|
|
26
27
|
log = logging.getLogger(__name__)
|
|
27
28
|
|
|
@@ -50,14 +51,7 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
50
51
|
|
|
51
52
|
contrast_parser.set_defaults(func=run)
|
|
52
53
|
|
|
53
|
-
contrast_parser
|
|
54
|
-
dest="image_paths",
|
|
55
|
-
help="set the image filename",
|
|
56
|
-
nargs="+",
|
|
57
|
-
type=str,
|
|
58
|
-
default=None,
|
|
59
|
-
metavar="IMAGE_PATHS",
|
|
60
|
-
)
|
|
54
|
+
add_common_arguments(contrast_parser)
|
|
61
55
|
|
|
62
56
|
contrast_parser.add_argument(
|
|
63
57
|
"-c",
|
|
@@ -73,24 +67,8 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
73
67
|
metavar="CUTOFF",
|
|
74
68
|
)
|
|
75
69
|
|
|
76
|
-
contrast_parser.add_argument(
|
|
77
|
-
"-op",
|
|
78
|
-
"--open",
|
|
79
|
-
default=False,
|
|
80
|
-
action="store_true",
|
|
81
|
-
dest="open",
|
|
82
|
-
help="open the image using default program (default: '%(default)s')",
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
contrast_parser.add_argument(
|
|
86
|
-
"-od",
|
|
87
|
-
"--output-dir",
|
|
88
|
-
dest="output_dir",
|
|
89
|
-
default="output",
|
|
90
|
-
help="set default output folder (default: '%(default)s')",
|
|
91
|
-
)
|
|
92
|
-
|
|
93
70
|
|
|
71
|
+
@log_args_decorator
|
|
94
72
|
def run(args: argparse.Namespace) -> None:
|
|
95
73
|
"""Run contrast subcommand.
|
|
96
74
|
|
|
@@ -100,28 +78,27 @@ def run(args: argparse.Namespace) -> None:
|
|
|
100
78
|
Returns:
|
|
101
79
|
None
|
|
102
80
|
"""
|
|
103
|
-
log.debug(args)
|
|
104
81
|
|
|
105
82
|
for image_path_str in args.image_paths:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
83
|
+
with load_image(Path(image_path_str)) as original_image:
|
|
84
|
+
if original_image.mode == "RGBA":
|
|
85
|
+
# Split the image into RGB and Alpha channels
|
|
86
|
+
rgb_image = original_image.convert("RGB")
|
|
87
|
+
alpha_channel = original_image.getchannel("A")
|
|
88
|
+
|
|
89
|
+
# Apply autocontrast to the RGB part
|
|
90
|
+
contrasted_rgb = ImageOps.autocontrast(
|
|
91
|
+
rgb_image, cutoff=args.cutoff
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Merge the contrasted RGB part with the original Alpha channel
|
|
95
|
+
contrasted_rgb.putalpha(alpha_channel)
|
|
96
|
+
contrast_image = contrasted_rgb
|
|
97
|
+
else:
|
|
98
|
+
# For other modes (like RGB, L, etc.), apply autocontrast
|
|
99
|
+
# directly
|
|
100
|
+
contrast_image = ImageOps.autocontrast(
|
|
101
|
+
original_image, cutoff=args.cutoff
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
save_image(args, contrast_image, Path(image_path_str), "contrast")
|
fotolab/subcommands/halftone.py
CHANGED
|
@@ -23,7 +23,8 @@ from typing import NamedTuple
|
|
|
23
23
|
|
|
24
24
|
from PIL import Image, ImageDraw
|
|
25
25
|
|
|
26
|
-
from fotolab import save_gif_image, save_image
|
|
26
|
+
from fotolab import load_image, save_gif_image, save_image
|
|
27
|
+
from .common import add_common_arguments, log_args_decorator
|
|
27
28
|
|
|
28
29
|
log = logging.getLogger(__name__)
|
|
29
30
|
|
|
@@ -44,14 +45,7 @@ def build_subparser(subparsers) -> None:
|
|
|
44
45
|
|
|
45
46
|
halftone_parser.set_defaults(func=run)
|
|
46
47
|
|
|
47
|
-
halftone_parser
|
|
48
|
-
dest="image_paths",
|
|
49
|
-
help="set the image filename",
|
|
50
|
-
nargs="+",
|
|
51
|
-
type=str,
|
|
52
|
-
default=None,
|
|
53
|
-
metavar="IMAGE_PATHS",
|
|
54
|
-
)
|
|
48
|
+
add_common_arguments(halftone_parser)
|
|
55
49
|
|
|
56
50
|
halftone_parser.add_argument(
|
|
57
51
|
"-ba",
|
|
@@ -62,23 +56,6 @@ def build_subparser(subparsers) -> None:
|
|
|
62
56
|
help="generate a GIF showing before and after changes",
|
|
63
57
|
)
|
|
64
58
|
|
|
65
|
-
halftone_parser.add_argument(
|
|
66
|
-
"-op",
|
|
67
|
-
"--open",
|
|
68
|
-
default=False,
|
|
69
|
-
action="store_true",
|
|
70
|
-
dest="open",
|
|
71
|
-
help="open the image using default program (default: '%(default)s')",
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
halftone_parser.add_argument(
|
|
75
|
-
"-od",
|
|
76
|
-
"--output-dir",
|
|
77
|
-
dest="output_dir",
|
|
78
|
-
default="output",
|
|
79
|
-
help="set default output folder (default: '%(default)s')",
|
|
80
|
-
)
|
|
81
|
-
|
|
82
59
|
halftone_parser.add_argument(
|
|
83
60
|
"-c",
|
|
84
61
|
"--cells",
|
|
@@ -100,6 +77,7 @@ def build_subparser(subparsers) -> None:
|
|
|
100
77
|
)
|
|
101
78
|
|
|
102
79
|
|
|
80
|
+
@log_args_decorator
|
|
103
81
|
def run(args: argparse.Namespace) -> None:
|
|
104
82
|
"""Run halftone subcommand.
|
|
105
83
|
|
|
@@ -109,25 +87,24 @@ def run(args: argparse.Namespace) -> None:
|
|
|
109
87
|
Returns:
|
|
110
88
|
None
|
|
111
89
|
"""
|
|
112
|
-
log.debug(args)
|
|
113
90
|
|
|
114
91
|
for image_path_str in args.image_paths:
|
|
115
92
|
image_filename = Path(image_path_str)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
if args.before_after:
|
|
122
|
-
save_gif_image(
|
|
123
|
-
args,
|
|
124
|
-
image_filename,
|
|
125
|
-
original_image,
|
|
126
|
-
halftone_image,
|
|
127
|
-
"halftone",
|
|
93
|
+
with load_image(image_filename) as original_image:
|
|
94
|
+
halftone_image = create_halftone_image(
|
|
95
|
+
original_image, args.cells, args.grayscale
|
|
128
96
|
)
|
|
129
|
-
|
|
130
|
-
|
|
97
|
+
|
|
98
|
+
if args.before_after:
|
|
99
|
+
save_gif_image(
|
|
100
|
+
args,
|
|
101
|
+
image_filename,
|
|
102
|
+
original_image,
|
|
103
|
+
halftone_image,
|
|
104
|
+
"halftone",
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
save_image(args, halftone_image, image_filename, "halftone")
|
|
131
108
|
|
|
132
109
|
|
|
133
110
|
def _draw_halftone_dot(
|
fotolab/subcommands/info.py
CHANGED
|
@@ -20,6 +20,8 @@ import logging
|
|
|
20
20
|
|
|
21
21
|
from PIL import ExifTags, Image
|
|
22
22
|
|
|
23
|
+
from .common import log_args_decorator
|
|
24
|
+
|
|
23
25
|
log = logging.getLogger(__name__)
|
|
24
26
|
|
|
25
27
|
|
|
@@ -62,6 +64,7 @@ def build_subparser(subparsers) -> None:
|
|
|
62
64
|
)
|
|
63
65
|
|
|
64
66
|
|
|
67
|
+
@log_args_decorator
|
|
65
68
|
def run(args: argparse.Namespace) -> None:
|
|
66
69
|
"""Run info subcommand.
|
|
67
70
|
|
|
@@ -71,7 +74,6 @@ def run(args: argparse.Namespace) -> None:
|
|
|
71
74
|
Returns:
|
|
72
75
|
None
|
|
73
76
|
"""
|
|
74
|
-
log.debug(args)
|
|
75
77
|
|
|
76
78
|
with Image.open(args.image_filename) as image:
|
|
77
79
|
exif_tags = extract_exif_tags(image, args.sort)
|
fotolab/subcommands/montage.py
CHANGED
|
@@ -18,10 +18,12 @@
|
|
|
18
18
|
import argparse
|
|
19
19
|
import logging
|
|
20
20
|
from pathlib import Path
|
|
21
|
+
from contextlib import ExitStack
|
|
21
22
|
|
|
22
23
|
from PIL import Image
|
|
23
24
|
|
|
24
|
-
from fotolab import save_image
|
|
25
|
+
from fotolab import load_image, save_image
|
|
26
|
+
from .common import add_common_arguments, log_args_decorator
|
|
25
27
|
|
|
26
28
|
log = logging.getLogger(__name__)
|
|
27
29
|
|
|
@@ -34,33 +36,10 @@ def build_subparser(subparsers) -> None:
|
|
|
34
36
|
|
|
35
37
|
montage_parser.set_defaults(func=run)
|
|
36
38
|
|
|
37
|
-
montage_parser
|
|
38
|
-
dest="image_paths",
|
|
39
|
-
help="set the image filenames",
|
|
40
|
-
nargs="+",
|
|
41
|
-
type=str,
|
|
42
|
-
default=None,
|
|
43
|
-
metavar="IMAGE_PATHS",
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
montage_parser.add_argument(
|
|
47
|
-
"-op",
|
|
48
|
-
"--open",
|
|
49
|
-
default=False,
|
|
50
|
-
action="store_true",
|
|
51
|
-
dest="open",
|
|
52
|
-
help="open the image using default program (default: '%(default)s')",
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
montage_parser.add_argument(
|
|
56
|
-
"-od",
|
|
57
|
-
"--output-dir",
|
|
58
|
-
dest="output_dir",
|
|
59
|
-
default="output",
|
|
60
|
-
help="set default output folder (default: '%(default)s')",
|
|
61
|
-
)
|
|
39
|
+
add_common_arguments(montage_parser)
|
|
62
40
|
|
|
63
41
|
|
|
42
|
+
@log_args_decorator
|
|
64
43
|
def run(args: argparse.Namespace) -> None:
|
|
65
44
|
"""Run montage subcommand.
|
|
66
45
|
|
|
@@ -70,24 +49,24 @@ def run(args: argparse.Namespace) -> None:
|
|
|
70
49
|
Returns:
|
|
71
50
|
None
|
|
72
51
|
"""
|
|
73
|
-
log.debug(args)
|
|
74
52
|
images = []
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
53
|
+
with ExitStack() as stack:
|
|
54
|
+
for image_path_str in args.image_paths:
|
|
55
|
+
image_filename = Path(image_path_str)
|
|
56
|
+
images.append(stack.enter_context(load_image(image_filename)))
|
|
78
57
|
|
|
79
|
-
|
|
80
|
-
|
|
58
|
+
if len(images) < 2:
|
|
59
|
+
raise ValueError("at least two images is required for montage")
|
|
81
60
|
|
|
82
|
-
|
|
83
|
-
|
|
61
|
+
total_width = sum(img.width for img in images)
|
|
62
|
+
total_height = max(img.height for img in images)
|
|
84
63
|
|
|
85
|
-
|
|
64
|
+
montaged_image = Image.new("RGB", (total_width, total_height))
|
|
86
65
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
66
|
+
x_offset = 0
|
|
67
|
+
for image in images:
|
|
68
|
+
montaged_image.paste(image, (x_offset, 0))
|
|
69
|
+
x_offset += image.width
|
|
91
70
|
|
|
92
|
-
|
|
93
|
-
|
|
71
|
+
output_image_filename = Path(args.image_paths[0])
|
|
72
|
+
save_image(args, montaged_image, output_image_filename, "montage")
|
fotolab/subcommands/resize.py
CHANGED
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
import argparse
|
|
19
19
|
import logging
|
|
20
20
|
import math
|
|
21
|
-
import sys
|
|
22
21
|
from pathlib import Path
|
|
23
22
|
|
|
24
23
|
from PIL import Image, ImageColor
|
|
25
24
|
|
|
26
|
-
from fotolab import save_image
|
|
25
|
+
from fotolab import load_image, save_image
|
|
26
|
+
from .common import add_common_arguments, log_args_decorator
|
|
27
27
|
|
|
28
28
|
log = logging.getLogger(__name__)
|
|
29
29
|
|
|
@@ -37,14 +37,7 @@ def build_subparser(subparsers) -> None:
|
|
|
37
37
|
|
|
38
38
|
resize_parser.set_defaults(func=run)
|
|
39
39
|
|
|
40
|
-
resize_parser
|
|
41
|
-
dest="image_paths",
|
|
42
|
-
help="set the image filename",
|
|
43
|
-
nargs="+",
|
|
44
|
-
type=str,
|
|
45
|
-
default=None,
|
|
46
|
-
metavar="IMAGE_PATHS",
|
|
47
|
-
)
|
|
40
|
+
add_common_arguments(resize_parser)
|
|
48
41
|
|
|
49
42
|
resize_parser.add_argument(
|
|
50
43
|
"-c",
|
|
@@ -65,67 +58,31 @@ def build_subparser(subparsers) -> None:
|
|
|
65
58
|
),
|
|
66
59
|
)
|
|
67
60
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"--width",
|
|
72
|
-
dest="width",
|
|
73
|
-
help="set the width of the image (default: '%(default)s')",
|
|
74
|
-
type=int,
|
|
75
|
-
required=True,
|
|
76
|
-
metavar="WIDTH",
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
resize_parser.add_argument(
|
|
80
|
-
"-H",
|
|
81
|
-
"--height",
|
|
82
|
-
dest="height",
|
|
83
|
-
help="set the height of the image (default: '%(default)s')",
|
|
84
|
-
type=int,
|
|
85
|
-
required=True,
|
|
86
|
-
metavar="HEIGHT",
|
|
87
|
-
)
|
|
88
|
-
else:
|
|
89
|
-
group = resize_parser.add_mutually_exclusive_group(required=False)
|
|
90
|
-
|
|
91
|
-
group.add_argument(
|
|
92
|
-
"-W",
|
|
93
|
-
"--width",
|
|
94
|
-
dest="width",
|
|
95
|
-
help="set the width of the image (default: '%(default)s')",
|
|
96
|
-
type=int,
|
|
97
|
-
default=DEFAULT_WIDTH,
|
|
98
|
-
metavar="WIDTH",
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
group.add_argument(
|
|
102
|
-
"-H",
|
|
103
|
-
"--height",
|
|
104
|
-
dest="height",
|
|
105
|
-
help="set the height of the image (default: '%(default)s')",
|
|
106
|
-
type=int,
|
|
107
|
-
default=DEFAULT_HEIGHT,
|
|
108
|
-
metavar="HEIGHT",
|
|
109
|
-
)
|
|
110
|
-
|
|
61
|
+
# Define width and height arguments as optional with defaults.
|
|
62
|
+
# The conditional logic (required/mutually exclusive) is now handled in the
|
|
63
|
+
# run function.
|
|
111
64
|
resize_parser.add_argument(
|
|
112
|
-
"-
|
|
113
|
-
"--
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
65
|
+
"-W",
|
|
66
|
+
"--width",
|
|
67
|
+
dest="width",
|
|
68
|
+
help="set the width of the image (default: '%(default)s')",
|
|
69
|
+
type=int,
|
|
70
|
+
default=DEFAULT_WIDTH,
|
|
71
|
+
metavar="WIDTH",
|
|
118
72
|
)
|
|
119
73
|
|
|
120
74
|
resize_parser.add_argument(
|
|
121
|
-
"-
|
|
122
|
-
"--
|
|
123
|
-
dest="
|
|
124
|
-
|
|
125
|
-
|
|
75
|
+
"-H",
|
|
76
|
+
"--height",
|
|
77
|
+
dest="height",
|
|
78
|
+
help="set the height of the image (default: '%(default)s')",
|
|
79
|
+
type=int,
|
|
80
|
+
default=DEFAULT_HEIGHT,
|
|
81
|
+
metavar="HEIGHT",
|
|
126
82
|
)
|
|
127
83
|
|
|
128
84
|
|
|
85
|
+
@log_args_decorator
|
|
129
86
|
def run(args: argparse.Namespace) -> None:
|
|
130
87
|
"""Run resize subcommand.
|
|
131
88
|
|
|
@@ -135,15 +92,32 @@ def run(args: argparse.Namespace) -> None:
|
|
|
135
92
|
Returns:
|
|
136
93
|
None
|
|
137
94
|
"""
|
|
138
|
-
|
|
95
|
+
|
|
96
|
+
width_provided = args.width != DEFAULT_WIDTH
|
|
97
|
+
height_provided = args.height != DEFAULT_HEIGHT
|
|
98
|
+
|
|
99
|
+
if args.canvas:
|
|
100
|
+
# Canvas mode: Both width and height are required
|
|
101
|
+
if not (width_provided and height_provided):
|
|
102
|
+
raise SystemExit(
|
|
103
|
+
"error: argument -W/--width and -H/--height are required when "
|
|
104
|
+
"using --canvas"
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
# Resize mode: Width and height are mutually exclusive
|
|
108
|
+
if width_provided and height_provided:
|
|
109
|
+
raise SystemExit(
|
|
110
|
+
"error: argument -W/--width and -H/--height are mutually "
|
|
111
|
+
"exclusive when not using --canvas"
|
|
112
|
+
)
|
|
139
113
|
|
|
140
114
|
for image_filepath in [Path(f) for f in args.image_paths]:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
115
|
+
with load_image(image_filepath) as original_image:
|
|
116
|
+
if args.canvas:
|
|
117
|
+
resized_image = _resize_image_onto_canvas(original_image, args)
|
|
118
|
+
else:
|
|
119
|
+
resized_image = _resize_image(original_image, args)
|
|
120
|
+
save_image(args, resized_image, image_filepath, "resize")
|
|
147
121
|
|
|
148
122
|
|
|
149
123
|
def _resize_image_onto_canvas(original_image, args):
|
|
@@ -176,18 +150,20 @@ def _calc_new_image_dimension(image, args) -> tuple:
|
|
|
176
150
|
|
|
177
151
|
original_aspect_ratio = old_width / old_height
|
|
178
152
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
153
|
+
match (new_width != DEFAULT_WIDTH, new_height != DEFAULT_HEIGHT):
|
|
154
|
+
case (True, False):
|
|
155
|
+
# user provided width, calculate height to maintain aspect ratio
|
|
156
|
+
new_height = math.ceil(new_width / original_aspect_ratio)
|
|
157
|
+
log.debug("new height calculated based on width: %d", new_height)
|
|
158
|
+
case (False, True):
|
|
159
|
+
# user provided height, calculate width to maintain aspect ratio
|
|
160
|
+
new_width = math.ceil(new_height * original_aspect_ratio)
|
|
161
|
+
log.debug("new width calculated based on height: %d", new_width)
|
|
162
|
+
case _:
|
|
163
|
+
# if both are default, no calculation needed, use defaults.
|
|
164
|
+
# The case where both are non-default is disallowed by argparse
|
|
165
|
+
# when --canvas is False, so we do nothing here.
|
|
166
|
+
pass
|
|
191
167
|
|
|
192
168
|
log.debug("new image dimension: %d x %d", new_width, new_height)
|
|
193
169
|
return (new_width, new_height)
|
fotolab/subcommands/rotate.py
CHANGED
|
@@ -21,7 +21,8 @@ from pathlib import Path
|
|
|
21
21
|
|
|
22
22
|
from PIL import Image
|
|
23
23
|
|
|
24
|
-
from fotolab import save_image
|
|
24
|
+
from fotolab import load_image, save_image
|
|
25
|
+
from .common import add_common_arguments, log_args_decorator
|
|
25
26
|
|
|
26
27
|
log = logging.getLogger(__name__)
|
|
27
28
|
|
|
@@ -32,14 +33,7 @@ def build_subparser(subparsers) -> None:
|
|
|
32
33
|
|
|
33
34
|
rotate_parser.set_defaults(func=run)
|
|
34
35
|
|
|
35
|
-
rotate_parser
|
|
36
|
-
dest="image_paths",
|
|
37
|
-
help="set the image filenames",
|
|
38
|
-
nargs="+",
|
|
39
|
-
type=str,
|
|
40
|
-
default=None,
|
|
41
|
-
metavar="IMAGE_PATHS",
|
|
42
|
-
)
|
|
36
|
+
add_common_arguments(rotate_parser)
|
|
43
37
|
|
|
44
38
|
rotate_parser.add_argument(
|
|
45
39
|
"-r",
|
|
@@ -56,24 +50,8 @@ def build_subparser(subparsers) -> None:
|
|
|
56
50
|
help="Rotate clockwise (default: '%(default)s)",
|
|
57
51
|
)
|
|
58
52
|
|
|
59
|
-
rotate_parser.add_argument(
|
|
60
|
-
"-op",
|
|
61
|
-
"--open",
|
|
62
|
-
default=False,
|
|
63
|
-
action="store_true",
|
|
64
|
-
dest="open",
|
|
65
|
-
help="open the image using default program (default: '%(default)s')",
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
rotate_parser.add_argument(
|
|
69
|
-
"-od",
|
|
70
|
-
"--output-dir",
|
|
71
|
-
dest="output_dir",
|
|
72
|
-
default="output",
|
|
73
|
-
help="set default output folder (default: '%(default)s')",
|
|
74
|
-
)
|
|
75
|
-
|
|
76
53
|
|
|
54
|
+
@log_args_decorator
|
|
77
55
|
def run(args: argparse.Namespace) -> None:
|
|
78
56
|
"""Run rotate subcommand.
|
|
79
57
|
|
|
@@ -83,7 +61,6 @@ def run(args: argparse.Namespace) -> None:
|
|
|
83
61
|
Returns:
|
|
84
62
|
None
|
|
85
63
|
"""
|
|
86
|
-
log.debug(args)
|
|
87
64
|
|
|
88
65
|
rotation = -args.rotation if args.clockwise else args.rotation
|
|
89
66
|
log.debug(f"Rotation angle: {rotation} degrees")
|
|
@@ -91,11 +68,11 @@ def run(args: argparse.Namespace) -> None:
|
|
|
91
68
|
for image_path_str in args.image_paths:
|
|
92
69
|
image_filename = Path(image_path_str)
|
|
93
70
|
log.debug(f"Processing image: {image_filename}")
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
71
|
+
with load_image(image_filename) as original_image:
|
|
72
|
+
log.debug(f"Original image size: {original_image.size}")
|
|
73
|
+
rotated_image = original_image.rotate(
|
|
74
|
+
rotation, expand=True, resample=Image.Resampling.BICUBIC
|
|
75
|
+
)
|
|
76
|
+
log.debug(f"Rotated image size: {rotated_image.size}")
|
|
77
|
+
save_image(args, rotated_image, image_filename, "rotate")
|
|
78
|
+
log.debug(f"Image saved: {image_filename}")
|
fotolab/subcommands/sharpen.py
CHANGED
|
@@ -19,9 +19,10 @@ import argparse
|
|
|
19
19
|
import logging
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
|
|
22
|
-
from PIL import
|
|
22
|
+
from PIL import ImageFilter
|
|
23
23
|
|
|
24
|
-
from fotolab import save_gif_image, save_image
|
|
24
|
+
from fotolab import load_image, save_gif_image, save_image
|
|
25
|
+
from .common import add_common_arguments, log_args_decorator
|
|
25
26
|
|
|
26
27
|
log = logging.getLogger(__name__)
|
|
27
28
|
|
|
@@ -32,14 +33,7 @@ def build_subparser(subparsers) -> None:
|
|
|
32
33
|
|
|
33
34
|
sharpen_parser.set_defaults(func=run)
|
|
34
35
|
|
|
35
|
-
sharpen_parser
|
|
36
|
-
dest="image_paths",
|
|
37
|
-
help="set the image filenames",
|
|
38
|
-
nargs="+",
|
|
39
|
-
type=str,
|
|
40
|
-
default=None,
|
|
41
|
-
metavar="IMAGE_PATHS",
|
|
42
|
-
)
|
|
36
|
+
add_common_arguments(sharpen_parser)
|
|
43
37
|
|
|
44
38
|
sharpen_parser.add_argument(
|
|
45
39
|
"-r",
|
|
@@ -86,24 +80,8 @@ def build_subparser(subparsers) -> None:
|
|
|
86
80
|
help="generate a GIF showing before and after changes",
|
|
87
81
|
)
|
|
88
82
|
|
|
89
|
-
sharpen_parser.add_argument(
|
|
90
|
-
"-op",
|
|
91
|
-
"--open",
|
|
92
|
-
default=False,
|
|
93
|
-
action="store_true",
|
|
94
|
-
dest="open",
|
|
95
|
-
help="open the image using default program (default: '%(default)s')",
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
sharpen_parser.add_argument(
|
|
99
|
-
"-od",
|
|
100
|
-
"--output-dir",
|
|
101
|
-
dest="output_dir",
|
|
102
|
-
default="output",
|
|
103
|
-
help="set default output folder (default: '%(default)s')",
|
|
104
|
-
)
|
|
105
|
-
|
|
106
83
|
|
|
84
|
+
@log_args_decorator
|
|
107
85
|
def run(args: argparse.Namespace) -> None:
|
|
108
86
|
"""Run sharpen subcommand.
|
|
109
87
|
|
|
@@ -113,22 +91,23 @@ def run(args: argparse.Namespace) -> None:
|
|
|
113
91
|
Returns:
|
|
114
92
|
None
|
|
115
93
|
"""
|
|
116
|
-
log.debug(args)
|
|
117
94
|
|
|
118
95
|
for image_path_str in args.image_paths:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)
|
|
125
|
-
if args.before_after:
|
|
126
|
-
save_gif_image(
|
|
127
|
-
args,
|
|
128
|
-
Path(image_path_str),
|
|
129
|
-
original_image,
|
|
130
|
-
sharpen_image,
|
|
131
|
-
"sharpen",
|
|
96
|
+
with load_image(Path(image_path_str)) as original_image:
|
|
97
|
+
sharpen_image = original_image.filter(
|
|
98
|
+
ImageFilter.UnsharpMask(
|
|
99
|
+
args.radius, percent=args.percent, threshold=args.threshold
|
|
100
|
+
)
|
|
132
101
|
)
|
|
133
|
-
|
|
134
|
-
|
|
102
|
+
if args.before_after:
|
|
103
|
+
save_gif_image(
|
|
104
|
+
args,
|
|
105
|
+
Path(image_path_str),
|
|
106
|
+
original_image,
|
|
107
|
+
sharpen_image,
|
|
108
|
+
"sharpen",
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
save_image(
|
|
112
|
+
args, sharpen_image, Path(image_path_str), "sharpen"
|
|
113
|
+
)
|
fotolab/subcommands/watermark.py
CHANGED
|
@@ -23,8 +23,9 @@ from typing import Tuple
|
|
|
23
23
|
|
|
24
24
|
from PIL import Image, ImageColor, ImageDraw, ImageFont, ImageSequence
|
|
25
25
|
|
|
26
|
-
from fotolab import save_image
|
|
26
|
+
from fotolab import load_image, save_image
|
|
27
27
|
from fotolab.subcommands.info import get_formatted_camera_info
|
|
28
|
+
from .common import add_common_arguments, log_args_decorator
|
|
28
29
|
|
|
29
30
|
log: logging.Logger = logging.getLogger(__name__)
|
|
30
31
|
|
|
@@ -42,14 +43,7 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
42
43
|
|
|
43
44
|
watermark_parser.set_defaults(func=run)
|
|
44
45
|
|
|
45
|
-
watermark_parser
|
|
46
|
-
dest="image_paths",
|
|
47
|
-
help="set the image filenames",
|
|
48
|
-
nargs="+",
|
|
49
|
-
type=str,
|
|
50
|
-
default=None,
|
|
51
|
-
metavar="IMAGE_PATHS",
|
|
52
|
-
)
|
|
46
|
+
add_common_arguments(watermark_parser)
|
|
53
47
|
|
|
54
48
|
watermark_parser.add_argument(
|
|
55
49
|
"-t",
|
|
@@ -161,24 +155,8 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
161
155
|
help="lowercase the watermark text",
|
|
162
156
|
)
|
|
163
157
|
|
|
164
|
-
watermark_parser.add_argument(
|
|
165
|
-
"-op",
|
|
166
|
-
"--open",
|
|
167
|
-
default=False,
|
|
168
|
-
action="store_true",
|
|
169
|
-
dest="open",
|
|
170
|
-
help="open the image using default program (default: '%(default)s')",
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
watermark_parser.add_argument(
|
|
174
|
-
"-od",
|
|
175
|
-
"--output-dir",
|
|
176
|
-
dest="output_dir",
|
|
177
|
-
default="output",
|
|
178
|
-
help="set default output folder (default: '%(default)s')",
|
|
179
|
-
)
|
|
180
|
-
|
|
181
158
|
|
|
159
|
+
@log_args_decorator
|
|
182
160
|
def run(args: argparse.Namespace) -> None:
|
|
183
161
|
"""Run watermark subcommand.
|
|
184
162
|
|
|
@@ -188,11 +166,22 @@ def run(args: argparse.Namespace) -> None:
|
|
|
188
166
|
Returns:
|
|
189
167
|
None
|
|
190
168
|
"""
|
|
191
|
-
log.debug(args)
|
|
192
169
|
|
|
193
170
|
for image_path_str in args.image_paths:
|
|
194
171
|
try:
|
|
195
|
-
|
|
172
|
+
with load_image(Path(image_path_str)) as image:
|
|
173
|
+
if image.format == "GIF":
|
|
174
|
+
watermark_gif_image(image, image_path_str, args)
|
|
175
|
+
else:
|
|
176
|
+
watermarked_image: Image.Image = watermark_non_gif_image(
|
|
177
|
+
image, args
|
|
178
|
+
)
|
|
179
|
+
save_image(
|
|
180
|
+
args,
|
|
181
|
+
watermarked_image,
|
|
182
|
+
Path(image_path_str),
|
|
183
|
+
"watermark",
|
|
184
|
+
)
|
|
196
185
|
except FileNotFoundError:
|
|
197
186
|
log.error("Image file not found: %s", image_path_str)
|
|
198
187
|
continue
|
|
@@ -200,16 +189,6 @@ def run(args: argparse.Namespace) -> None:
|
|
|
200
189
|
log.error("Could not open image %s: %s", image_path_str, e)
|
|
201
190
|
continue
|
|
202
191
|
|
|
203
|
-
if image.format == "GIF":
|
|
204
|
-
watermark_gif_image(image, image_path_str, args)
|
|
205
|
-
else:
|
|
206
|
-
watermarked_image: Image.Image = watermark_non_gif_image(
|
|
207
|
-
image, args
|
|
208
|
-
)
|
|
209
|
-
save_image(
|
|
210
|
-
args, watermarked_image, Path(image_path_str), "watermark"
|
|
211
|
-
)
|
|
212
|
-
|
|
213
192
|
|
|
214
193
|
def watermark_gif_image(
|
|
215
194
|
original_image: Image.Image, output_filename: str, args: argparse.Namespace
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
fotolab/__init__.py,sha256=xYHsuP-sNzx6xCzWpierNFJfW1qKEzDd54-UYsHgX5o,3481
|
|
2
|
+
fotolab/__main__.py,sha256=Wk11t_zKs3wJ-okM3yv7Mtj1pRwQZu3NCmpteBwn4Hs,810
|
|
3
|
+
fotolab/cli.py,sha256=Ebx6RQM5Dr8PsIqQ4NOySxnGMzGGI0U7CTawYfHROlk,4499
|
|
4
|
+
fotolab/subcommands/__init__.py,sha256=LHKTjyxfB_s5NZwmT9zYuQuP_86rYjcio7FOGdqD1_M,1169
|
|
5
|
+
fotolab/subcommands/animate.py,sha256=JU0eAXyciXUivRJJVq14YuoM_S6h5Yq9RAy0pDUcXMk,5339
|
|
6
|
+
fotolab/subcommands/auto.py,sha256=Lt_htnIYxUavWfd2hAjduGuhG0TxS2ZkCVXW0-gg7Xo,3434
|
|
7
|
+
fotolab/subcommands/border.py,sha256=W31MgIYpLM6PrxN3nVM1U-eqAfh-DHY6iS6ONDKEhNk,4227
|
|
8
|
+
fotolab/subcommands/common.py,sha256=oSoCcL8rWlmV4_rZ-zc3VFISlG1zbC6xkYMKbS7TP9s,1930
|
|
9
|
+
fotolab/subcommands/contrast.py,sha256=kO9t3uThdz225vssPUJ1GpTNtQSa3u5qBGYD9UoAEs4,3283
|
|
10
|
+
fotolab/subcommands/env.py,sha256=QoxRvzZKgmoHTUxDV4QYhdChCpMWs5TbXFY_qIpIQpE,1469
|
|
11
|
+
fotolab/subcommands/halftone.py,sha256=6XVhGvL4sf1rn8V9wv1ZXZwgRc36tIH9V8l4TAPXH1s,6446
|
|
12
|
+
fotolab/subcommands/info.py,sha256=pfCiLYE1zfaMdGtgaGYvwuOkaSDCeTFuvaflp25HS0U,3603
|
|
13
|
+
fotolab/subcommands/montage.py,sha256=QZ-VPVJG29nPGHpiFv5fLpEF4XfBBE3cvQUJgluiKho,2233
|
|
14
|
+
fotolab/subcommands/resize.py,sha256=AoynJy-GwbhFStAFQ_909AOp98usACz6428WvO-zinU,5483
|
|
15
|
+
fotolab/subcommands/rotate.py,sha256=jNcvsuTrktHjf6B2HC7ygHBezykF4YEZ08dqBsLT3oI,2456
|
|
16
|
+
fotolab/subcommands/sharpen.py,sha256=r7bvE6zmoaKsA6LBqLuAqWbSXWeZj8WAojLhQnFVb4g,3180
|
|
17
|
+
fotolab/subcommands/watermark.py,sha256=2wi5fU6Ax2q2B9133J7_smPPSafeWeR0jkA--_oi8Sw,10977
|
|
18
|
+
fotolab-0.34.3.dist-info/licenses/LICENSE.md,sha256=tGtFDwxWTjuR9syrJoSv1Hiffd2u8Tu8cYClfrXS_YU,31956
|
|
19
|
+
fotolab-0.34.3.dist-info/METADATA,sha256=F8bwUkjAIJ1UF-FcEGIuOcvlWx-GM4eAeXz2R7q07Qw,15101
|
|
20
|
+
fotolab-0.34.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
fotolab-0.34.3.dist-info/entry_points.txt,sha256=0e1go9plFpqj5FP-OpV2acxTAx3ViI59PMXuhejvgcQ,45
|
|
22
|
+
fotolab-0.34.3.dist-info/top_level.txt,sha256=XUJ3gdpsbjohoZCLdVlbQrxAUDkbQg7WwGQG2DaN0t4,8
|
|
23
|
+
fotolab-0.34.3.dist-info/RECORD,,
|
fotolab-0.34.1.dist-info/RECORD
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
fotolab/__init__.py,sha256=0UZ6OZFCRkc2Fwc4LT1UtE9JdK3uq0uL8fSSVSOuVyo,3161
|
|
2
|
-
fotolab/__main__.py,sha256=Wk11t_zKs3wJ-okM3yv7Mtj1pRwQZu3NCmpteBwn4Hs,810
|
|
3
|
-
fotolab/cli.py,sha256=Ebx6RQM5Dr8PsIqQ4NOySxnGMzGGI0U7CTawYfHROlk,4499
|
|
4
|
-
fotolab/subcommands/__init__.py,sha256=l3DlIaJ3u3jGjnC1H1yV8LZ_nPqOLJ6gikD4BCaMAQ0,1129
|
|
5
|
-
fotolab/subcommands/animate.py,sha256=3nXWeQ0l6hFMrFfxba9py60b_6Hv2WJwR_VuYzzrexw,5867
|
|
6
|
-
fotolab/subcommands/auto.py,sha256=j34xiZlbl8qFpW8kFr3Z-J0Xk08Qiz8Stn1O9I2y1io,3572
|
|
7
|
-
fotolab/subcommands/border.py,sha256=N_ptzy3vgHcmd7WwGcnSgJ34usNGmlGNT4xJ5D0hD5I,4703
|
|
8
|
-
fotolab/subcommands/contrast.py,sha256=JRnGPSQJYweZkqBIosFcJHYuO3bryC3WuHQStmlbYK0,3699
|
|
9
|
-
fotolab/subcommands/env.py,sha256=QoxRvzZKgmoHTUxDV4QYhdChCpMWs5TbXFY_qIpIQpE,1469
|
|
10
|
-
fotolab/subcommands/halftone.py,sha256=ScJB54gnmUpxh9wYCt_Y7ertZl-gYW_WPAPBHC44ReE,6894
|
|
11
|
-
fotolab/subcommands/info.py,sha256=H3voMi67cKoHT2Mu4RUNQBPdb_MspetPjhOvy-YyNnE,3563
|
|
12
|
-
fotolab/subcommands/montage.py,sha256=bjn-RlbXur38-5YNyyTs0kI2Z-caVDZtBt5EDuH5D1M,2598
|
|
13
|
-
fotolab/subcommands/resize.py,sha256=8JKHLJii6qq9eQ3QvqF56tjriRpebPZewU71xOucdug,5804
|
|
14
|
-
fotolab/subcommands/rotate.py,sha256=M6i34mgD10F9jFlXQGP6-l78BwZY8Nh-eNteAEBOjH4,2925
|
|
15
|
-
fotolab/subcommands/sharpen.py,sha256=Swd2IBO2ENc0g-Ukl-Jtj7OXaytFcTN8qEx8MRchFGE,3582
|
|
16
|
-
fotolab/subcommands/watermark.py,sha256=9mP2PWaXjAyoMsZR4mkemx18gL0pS90Qx9lT5CuOBkA,11343
|
|
17
|
-
fotolab-0.34.1.dist-info/licenses/LICENSE.md,sha256=tGtFDwxWTjuR9syrJoSv1Hiffd2u8Tu8cYClfrXS_YU,31956
|
|
18
|
-
fotolab-0.34.1.dist-info/METADATA,sha256=Nln4axVoaQ3tiFdDwyL0SkJMvpc9N_LixKjSD-Bkv2A,15101
|
|
19
|
-
fotolab-0.34.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
20
|
-
fotolab-0.34.1.dist-info/entry_points.txt,sha256=0e1go9plFpqj5FP-OpV2acxTAx3ViI59PMXuhejvgcQ,45
|
|
21
|
-
fotolab-0.34.1.dist-info/top_level.txt,sha256=XUJ3gdpsbjohoZCLdVlbQrxAUDkbQg7WwGQG2DaN0t4,8
|
|
22
|
-
fotolab-0.34.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|