fotolab 0.31.15__tar.gz → 0.32.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.
Files changed (41) hide show
  1. {fotolab-0.31.15 → fotolab-0.32.0}/PKG-INFO +23 -10
  2. {fotolab-0.31.15 → fotolab-0.32.0}/pyproject.toml +33 -16
  3. fotolab-0.32.0/setup.cfg +4 -0
  4. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/__init__.py +1 -2
  5. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/subcommands/auto.py +18 -3
  6. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/subcommands/watermark.py +5 -3
  7. fotolab-0.32.0/src/fotolab.egg-info/PKG-INFO +487 -0
  8. fotolab-0.32.0/src/fotolab.egg-info/SOURCES.txt +39 -0
  9. fotolab-0.32.0/src/fotolab.egg-info/dependency_links.txt +1 -0
  10. fotolab-0.32.0/src/fotolab.egg-info/entry_points.txt +2 -0
  11. fotolab-0.32.0/src/fotolab.egg-info/requires.txt +18 -0
  12. fotolab-0.32.0/src/fotolab.egg-info/top_level.txt +1 -0
  13. fotolab-0.32.0/tests/test_animate_subcommand.py +21 -0
  14. fotolab-0.32.0/tests/test_auto_subcommand.py +21 -0
  15. fotolab-0.32.0/tests/test_border_subcommand.py +37 -0
  16. fotolab-0.32.0/tests/test_contrast_subcommand.py +20 -0
  17. fotolab-0.32.0/tests/test_env_subcommand.py +36 -0
  18. fotolab-0.32.0/tests/test_halftone_subcommand.py +20 -0
  19. fotolab-0.32.0/tests/test_help_flag.py +22 -0
  20. fotolab-0.32.0/tests/test_info_subcommand.py +20 -0
  21. fotolab-0.32.0/tests/test_montage_subcommand.py +29 -0
  22. fotolab-0.32.0/tests/test_quiet_flag.py +22 -0
  23. fotolab-0.32.0/tests/test_resize_subcommand.py +20 -0
  24. fotolab-0.32.0/tests/test_rotate_subcommand.py +20 -0
  25. fotolab-0.32.0/tests/test_sharpen_subcommand.py +20 -0
  26. fotolab-0.32.0/tests/test_watermark_subcommand.py +20 -0
  27. {fotolab-0.31.15 → fotolab-0.32.0}/LICENSE.md +0 -0
  28. {fotolab-0.31.15 → fotolab-0.32.0}/README.md +0 -0
  29. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/__main__.py +0 -0
  30. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/cli.py +0 -0
  31. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/subcommands/__init__.py +0 -0
  32. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/subcommands/animate.py +0 -0
  33. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/subcommands/border.py +0 -0
  34. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/subcommands/contrast.py +0 -0
  35. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/subcommands/env.py +0 -0
  36. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/subcommands/halftone.py +0 -0
  37. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/subcommands/info.py +0 -0
  38. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/subcommands/montage.py +0 -0
  39. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/subcommands/resize.py +0 -0
  40. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/subcommands/rotate.py +0 -0
  41. {fotolab-0.31.15 → fotolab-0.32.0/src}/fotolab/subcommands/sharpen.py +0 -0
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fotolab
3
- Version: 0.31.15
4
- Summary: A console program that manipulate images.
5
- Keywords: photography,photo
3
+ Version: 0.32.0
6
4
  Author-email: Kian-Meng Ang <kianmeng@cpan.org>
7
- Requires-Python: >=3.9
8
- Description-Content-Type: text/markdown
5
+ License-Expression: AGPL-3.0-or-later
6
+ Project-URL: Changelog, https://github.com/kianmeng/fotolab/blob/master/CHANGELOG.md
7
+ Project-URL: Issues, https://github.com/kianmeng/fotolab/issues
8
+ Project-URL: Source, https://github.com/kianmeng/fotolab
9
+ Keywords: photography,photo
9
10
  Classifier: Development Status :: 3 - Alpha
10
11
  Classifier: Environment :: Console
11
- Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
12
12
  Classifier: Programming Language :: Python :: 3 :: Only
13
13
  Classifier: Programming Language :: Python :: 3.9
14
14
  Classifier: Programming Language :: Python :: 3.10
@@ -16,11 +16,25 @@ Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
19
21
  License-File: LICENSE.md
20
22
  Requires-Dist: pillow
21
- Project-URL: Changelog, https://github.com/kianmeng/fotolab/blob/master/CHANGELOG.md
22
- Project-URL: Issues, https://github.com/kianmeng/fotolab/issues
23
- Project-URL: Source, https://github.com/kianmeng/fotolab
23
+ Provides-Extra: test
24
+ Requires-Dist: pytest; extra == "test"
25
+ Requires-Dist: pytest-cov; extra == "test"
26
+ Requires-Dist: pytest-randomly; extra == "test"
27
+ Requires-Dist: pytest-xdist; extra == "test"
28
+ Requires-Dist: scripttest; extra == "test"
29
+ Provides-Extra: doc
30
+ Requires-Dist: myst-parser; extra == "doc"
31
+ Requires-Dist: sphinx; extra == "doc"
32
+ Requires-Dist: sphinx-autodoc-typehints; extra == "doc"
33
+ Requires-Dist: sphinx-copybutton; extra == "doc"
34
+ Provides-Extra: lint
35
+ Requires-Dist: pre-commit; extra == "lint"
36
+ Requires-Dist: ruff; extra == "lint"
37
+ Dynamic: license-file
24
38
 
25
39
  # fotolab
26
40
 
@@ -471,4 +485,3 @@ The fish logo used in the documentation generated by Sphinx is a public domain
471
485
  drawing of male freshwater phase [Sockeye (red) salmon (Oncorhynchus nerka)]
472
486
  (https://en.wikipedia.org/w/index.php?oldid=1186575702) from
473
487
  <https://commons.wikimedia.org/entity/M2787002>.
474
-
@@ -1,17 +1,21 @@
1
1
  [build-system]
2
- build-backend = "flit_core.buildapi"
3
- requires = ["flit_core"]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.setuptools.packages.find]
6
+ where = ["src"]
4
7
 
5
8
  [project]
6
9
  name = "fotolab"
10
+ version = "0.32.0"
7
11
  authors = [{name = "Kian-Meng Ang", email = "kianmeng@cpan.org"}]
8
12
  requires-python = ">=3.9"
9
13
  readme = "README.md"
10
- license = {file = "LICENSE.md"}
14
+ license = "AGPL-3.0-or-later"
15
+ license-files = ["LICENSE.md"]
11
16
  classifiers = [
12
17
  "Development Status :: 3 - Alpha",
13
18
  "Environment :: Console",
14
- "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
15
19
  "Programming Language :: Python :: 3 :: Only",
16
20
  "Programming Language :: Python :: 3.9",
17
21
  "Programming Language :: Python :: 3.10",
@@ -20,8 +24,6 @@ classifiers = [
20
24
  "Programming Language :: Python :: 3.13",
21
25
  "Programming Language :: Python",
22
26
  ]
23
- version = "0.31.15"
24
- dynamic = ["description"]
25
27
  keywords = ["photography", "photo"]
26
28
  dependencies = [
27
29
  "pillow",
@@ -35,26 +37,41 @@ Source = "https://github.com/kianmeng/fotolab"
35
37
  [project.scripts]
36
38
  fotolab = "fotolab.cli:main"
37
39
 
38
- [dependency-groups]
39
- dev = [
40
- "exceptiongroup",
41
- "flit",
42
- "importlib-metadata",
43
- "myst-parser",
44
- "nox",
45
- "pre-commit",
40
+ [project.optional-dependencies]
41
+ test = [
46
42
  "pytest",
47
43
  "pytest-cov",
48
44
  "pytest-randomly",
49
45
  "pytest-xdist",
50
- "ruff",
51
46
  "scripttest",
47
+ ]
48
+
49
+ doc = [
50
+ "myst-parser",
52
51
  "sphinx",
53
52
  "sphinx-autodoc-typehints",
54
53
  "sphinx-copybutton",
55
- "tomli",
54
+ ]
55
+
56
+ lint = [
57
+ "pre-commit",
58
+ "ruff",
56
59
  ]
57
60
 
58
61
  # verify through: uv run ruff check --show-settings
59
62
  [tool.ruff]
60
63
  line-length = 79
64
+ target-version = "py310"
65
+ exclude = [
66
+ "docs/",
67
+ "docs/source/conf.py",
68
+ ]
69
+
70
+ [tool.ruff.lint]
71
+ extend-select = [
72
+ "E",
73
+ "W",
74
+ ]
75
+
76
+ [tool.ruff.lint.pydocstyle]
77
+ convention = "google"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -20,12 +20,11 @@ import logging
20
20
  import os
21
21
  import subprocess
22
22
  import sys
23
- from importlib import metadata
24
23
  from pathlib import Path
25
24
 
26
25
  from PIL import Image
27
26
 
28
- __version__ = metadata.version("fotolab")
27
+ __version__ = "0.31.15"
29
28
 
30
29
  log = logging.getLogger(__name__)
31
30
 
@@ -43,6 +43,16 @@ def build_subparser(subparsers) -> None:
43
43
  metavar="IMAGE_FILENAMES",
44
44
  )
45
45
 
46
+ auto_parser.add_argument(
47
+ "-t",
48
+ "--text",
49
+ dest="text",
50
+ help="set the watermark text (default: '%(default)s')",
51
+ type=str,
52
+ default="kianmeng.org",
53
+ metavar="WATERMARK_TEXT",
54
+ )
55
+
46
56
 
47
57
  def run(args: argparse.Namespace) -> None:
48
58
  """Run auto subcommand.
@@ -60,7 +70,7 @@ def run(args: argparse.Namespace) -> None:
60
70
  "radius": 1,
61
71
  "percent": 100,
62
72
  "threshold": 2,
63
- "text": "kianmeng.org",
73
+ "text": f"{args.text if args.text else 'kianmeng.org'}",
64
74
  "position": "bottom-left",
65
75
  "font_size": 12,
66
76
  "font_color": "white",
@@ -69,13 +79,18 @@ def run(args: argparse.Namespace) -> None:
69
79
  "padding": 15,
70
80
  "camera": False,
71
81
  "canvas": False,
72
- "lowercase": False,
82
+ "lowercase": True,
73
83
  "before_after": False,
74
84
  "alpha": 128,
75
85
  }
76
- combined_args = argparse.Namespace(**vars(args), **extra_args)
86
+
87
+ # resolve error: argparse.Namespace() got multiple values for keyword
88
+ # argument 'text'
89
+ merged_args = {**vars(args), **extra_args}
90
+ combined_args = argparse.Namespace(**merged_args)
77
91
  combined_args.overwrite = True
78
92
  combined_args.open = False
93
+
79
94
  log.debug(args)
80
95
  log.debug(combined_args)
81
96
 
@@ -110,7 +110,8 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
110
110
  type=int,
111
111
  default=2,
112
112
  help=(
113
- "set the outline width of the watermark text (default: '%(default)s')"
113
+ "set the outline width of the watermark text "
114
+ "(default: '%(default)s')"
114
115
  ),
115
116
  metavar="OUTLINE_WIDTH",
116
117
  )
@@ -122,7 +123,8 @@ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
122
123
  type=str,
123
124
  default="black",
124
125
  help=(
125
- "set the outline color of the watermark text (default: '%(default)s')"
126
+ "set the outline color of the watermark text "
127
+ "(default: '%(default)s')"
126
128
  ),
127
129
  metavar="OUTLINE_COLOR",
128
130
  )
@@ -325,7 +327,7 @@ def prepare_text(args: argparse.Namespace, image: Image.Image) -> str:
325
327
  if args.lowercase:
326
328
  text = text.lower()
327
329
 
328
- return text
330
+ return text.replace("\\n", "\n")
329
331
 
330
332
 
331
333
  def calc_font_size(image: Image.Image, args: argparse.Namespace) -> int:
@@ -0,0 +1,487 @@
1
+ Metadata-Version: 2.4
2
+ Name: fotolab
3
+ Version: 0.32.0
4
+ Author-email: Kian-Meng Ang <kianmeng@cpan.org>
5
+ License-Expression: AGPL-3.0-or-later
6
+ Project-URL: Changelog, https://github.com/kianmeng/fotolab/blob/master/CHANGELOG.md
7
+ Project-URL: Issues, https://github.com/kianmeng/fotolab/issues
8
+ Project-URL: Source, https://github.com/kianmeng/fotolab
9
+ Keywords: photography,photo
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE.md
22
+ Requires-Dist: pillow
23
+ Provides-Extra: test
24
+ Requires-Dist: pytest; extra == "test"
25
+ Requires-Dist: pytest-cov; extra == "test"
26
+ Requires-Dist: pytest-randomly; extra == "test"
27
+ Requires-Dist: pytest-xdist; extra == "test"
28
+ Requires-Dist: scripttest; extra == "test"
29
+ Provides-Extra: doc
30
+ Requires-Dist: myst-parser; extra == "doc"
31
+ Requires-Dist: sphinx; extra == "doc"
32
+ Requires-Dist: sphinx-autodoc-typehints; extra == "doc"
33
+ Requires-Dist: sphinx-copybutton; extra == "doc"
34
+ Provides-Extra: lint
35
+ Requires-Dist: pre-commit; extra == "lint"
36
+ Requires-Dist: ruff; extra == "lint"
37
+ Dynamic: license-file
38
+
39
+ # fotolab
40
+
41
+ A console program to manipulate photos.
42
+
43
+ ## Installation
44
+
45
+ Stable version From PyPI using `uv`:
46
+
47
+ ```console
48
+ uv tool install fotolab
49
+ ```
50
+
51
+ Upgrade to latest stable version:
52
+
53
+ ```console
54
+ uv tool upgrade fotolab
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ```console
60
+ fotolab -h
61
+ ```
62
+
63
+ <!--help !-->
64
+
65
+ ```console
66
+ usage: fotolab [-h] [-o] [-q] [-v] [-d] [-V]
67
+ {animate,auto,border,contrast,env,halftone,info,montage,resize,rotate,sharpen,watermark} ...
68
+
69
+ A console program to manipulate photos.
70
+
71
+ website: https://github.com/kianmeng/fotolab
72
+ changelog: https://github.com/kianmeng/fotolab/blob/master/CHANGELOG.md
73
+ issues: https://github.com/kianmeng/fotolab/issues
74
+
75
+ positional arguments:
76
+ {animate,auto,border,contrast,env,halftone,info,montage,resize,rotate,sharpen,watermark}
77
+ sub-command help
78
+ animate animate an image
79
+ auto auto adjust (resize, contrast, and watermark) a photo
80
+ border add border to image
81
+ contrast contrast an image.
82
+ env print environment information for bug reporting
83
+ halftone halftone an image
84
+ info info an image
85
+ montage montage a list of image
86
+ resize resize an image
87
+ rotate rotate an image
88
+ sharpen sharpen an image
89
+ watermark watermark an image
90
+
91
+ options:
92
+ -h, --help show this help message and exit
93
+ -o, --overwrite overwrite existing image
94
+ -q, --quiet suppress all logging
95
+ -v, --verbose show verbosity of debugging log, use -vv, -vvv for more details
96
+ -d, --debug show debugging log and stacktrace
97
+ -V, --version show program's version number and exit
98
+ ```
99
+
100
+ <!--help !-->
101
+
102
+ ### fotolab animate
103
+
104
+ ```console
105
+ fotolab animate -h
106
+ ```
107
+
108
+ <!--help-animate !-->
109
+
110
+ ```console
111
+ usage: fotolab animate [-h] [-f FORMAT] [-d DURATION] [-l LOOP] [-op]
112
+ [--webp-quality QUALITY] [--webp-lossless]
113
+ [--webp-method METHOD] [-od OUTPUT_DIR]
114
+ IMAGE_FILENAMES [IMAGE_FILENAMES ...]
115
+
116
+ positional arguments:
117
+ IMAGE_FILENAMES set the image filenames
118
+
119
+ options:
120
+ -h, --help show this help message and exit
121
+ -f, --format FORMAT set the image format (default: 'gif')
122
+ -d, --duration DURATION
123
+ set the duration in milliseconds (must be a positive
124
+ integer, default: '2500')
125
+ -l, --loop LOOP set the loop cycle (default: '0')
126
+ -op, --open open the image using default program (default:
127
+ 'False')
128
+ --webp-quality QUALITY
129
+ set WEBP quality (0-100, default: '80')
130
+ --webp-lossless enable WEBP lossless compression (default: 'False')
131
+ --webp-method METHOD set WEBP encoding method (0=fast, 6=slow/best,
132
+ default: '4')
133
+ -od, --output-dir OUTPUT_DIR
134
+ set default output folder (default: 'output')
135
+ ```
136
+
137
+ <!--help-animate !-->
138
+
139
+ ### fotolab auto
140
+
141
+ ```console
142
+ fotolab auto -h
143
+ ```
144
+
145
+ <!--help-auto !-->
146
+
147
+ ```console
148
+ usage: fotolab auto [-h] IMAGE_FILENAMES [IMAGE_FILENAMES ...]
149
+
150
+ positional arguments:
151
+ IMAGE_FILENAMES set the image filename
152
+
153
+ options:
154
+ -h, --help show this help message and exit
155
+ ```
156
+
157
+ <!--help-auto !-->
158
+
159
+ ### fotolab border
160
+
161
+ ```console
162
+ fotolab border -h
163
+ ```
164
+
165
+ <!--help-border !-->
166
+
167
+ ```console
168
+ usage: fotolab border [-h] [-c COLOR] [-w WIDTH] [-wt WIDTH] [-wr WIDTH]
169
+ [-wb WIDTH] [-wl WIDTH] [-op] [-od OUTPUT_DIR]
170
+ IMAGE_FILENAMES [IMAGE_FILENAMES ...]
171
+
172
+ positional arguments:
173
+ IMAGE_FILENAMES set the image filenames
174
+
175
+ options:
176
+ -h, --help show this help message and exit
177
+ -c, --color COLOR set the color of border (default: 'black')
178
+ -w, --width WIDTH set the width of border in pixels (default: '10')
179
+ -wt, --width-top WIDTH
180
+ set the width of top border in pixels (default: '0')
181
+ -wr, --width-right WIDTH
182
+ set the width of right border in pixels (default: '0')
183
+ -wb, --width-bottom WIDTH
184
+ set the width of bottom border in pixels (default:
185
+ '0')
186
+ -wl, --width-left WIDTH
187
+ set the width of left border in pixels (default: '0')
188
+ -op, --open open the image using default program (default:
189
+ 'False')
190
+ -od, --output-dir OUTPUT_DIR
191
+ set default output folder (default: 'output')
192
+ ```
193
+
194
+ <!--help-border !-->
195
+
196
+ ### fotolab contrast
197
+
198
+ ```console
199
+ fotolab contrast -h
200
+ ```
201
+
202
+ <!--help-contrast !-->
203
+
204
+ ```console
205
+ usage: fotolab contrast [-h] [-c CUTOFF] [-op] [-od OUTPUT_DIR]
206
+ IMAGE_FILENAMES [IMAGE_FILENAMES ...]
207
+
208
+ positional arguments:
209
+ IMAGE_FILENAMES set the image filename
210
+
211
+ options:
212
+ -h, --help show this help message and exit
213
+ -c, --cutoff CUTOFF set the percentage (0-50) of lightest or darkest
214
+ pixels to discard from histogram (default: '1.0')
215
+ -op, --open open the image using default program (default:
216
+ 'False')
217
+ -od, --output-dir OUTPUT_DIR
218
+ set default output folder (default: 'output')
219
+ ```
220
+
221
+ <!--help-contrast !-->
222
+
223
+ ### fotolab halftone
224
+
225
+ ```console
226
+ fotolab halftone -h
227
+ ```
228
+
229
+ <!--help-halftone !-->
230
+
231
+ ```console
232
+ usage: fotolab halftone [-h] [-ba] [-op] [-od OUTPUT_DIR] [-c CELLS] [-g]
233
+ IMAGE_FILENAMES [IMAGE_FILENAMES ...]
234
+
235
+ positional arguments:
236
+ IMAGE_FILENAMES set the image filename
237
+
238
+ options:
239
+ -h, --help show this help message and exit
240
+ -ba, --before-after generate a GIF showing before and after changes
241
+ -op, --open open the image using default program (default:
242
+ 'False')
243
+ -od, --output-dir OUTPUT_DIR
244
+ set default output folder (default: 'output')
245
+ -c, --cells CELLS set number of cells across the image width (default:
246
+ 50)
247
+ -g, --grayscale convert image to grayscale before applying halftone
248
+ ```
249
+
250
+ <!--help-halftone !-->
251
+
252
+ ### fotolab info
253
+
254
+ ```console
255
+ fotolab info -h
256
+ ```
257
+
258
+ <!--help-info !-->
259
+
260
+ ```console
261
+ usage: fotolab info [-h] [-s] [--camera] [--datetime] IMAGE_FILENAME
262
+
263
+ positional arguments:
264
+ IMAGE_FILENAME set the image filename
265
+
266
+ options:
267
+ -h, --help show this help message and exit
268
+ -s, --sort show image info by sorted field name
269
+ --camera show the camera maker details
270
+ --datetime show the datetime
271
+ ```
272
+
273
+ <!--help-info !-->
274
+
275
+ ### fotolab rotate
276
+
277
+ ```console
278
+ fotolab rotate -h
279
+ ```
280
+
281
+ <!--help-rotate !-->
282
+
283
+ ```console
284
+ usage: fotolab rotate [-h] [-r ROTATION] [-cw] [-op] [-od OUTPUT_DIR]
285
+ IMAGE_FILENAMES [IMAGE_FILENAMES ...]
286
+
287
+ positional arguments:
288
+ IMAGE_FILENAMES set the image filenames
289
+
290
+ options:
291
+ -h, --help show this help message and exit
292
+ -r, --rotation ROTATION
293
+ Rotation angle in degrees (default: '0')
294
+ -cw, --clockwise Rotate clockwise (default: 'False)
295
+ -op, --open open the image using default program (default:
296
+ 'False')
297
+ -od, --output-dir OUTPUT_DIR
298
+ set default output folder (default: 'output')
299
+ ```
300
+
301
+ <!--help-rotate !-->
302
+
303
+ ### fotolab montage
304
+
305
+ ```console
306
+ fotolab montage -h
307
+ ```
308
+
309
+ <!--help-montage !-->
310
+
311
+ ```console
312
+ usage: fotolab montage [-h] [-op] [-od OUTPUT_DIR]
313
+ IMAGE_FILENAMES [IMAGE_FILENAMES ...]
314
+
315
+ positional arguments:
316
+ IMAGE_FILENAMES set the image filenames
317
+
318
+ options:
319
+ -h, --help show this help message and exit
320
+ -op, --open open the image using default program (default:
321
+ 'False')
322
+ -od, --output-dir OUTPUT_DIR
323
+ set default output folder (default: 'output')
324
+ ```
325
+
326
+ <!--help-montage !-->
327
+
328
+ ### fotolab resize
329
+
330
+ ```console
331
+ fotolab resize -h
332
+ ```
333
+
334
+ <!--help-resize !-->
335
+
336
+ ```console
337
+ usage: fotolab resize [-h] [-c] [-l CANVAS_COLOR] [-W WIDTH | -H HEIGHT] [-op]
338
+ [-od OUTPUT_DIR]
339
+ IMAGE_FILENAMES [IMAGE_FILENAMES ...]
340
+
341
+ positional arguments:
342
+ IMAGE_FILENAMES set the image filename
343
+
344
+ options:
345
+ -h, --help show this help message and exit
346
+ -c, --canvas paste image onto a larger canvas
347
+ -l, --canvas-color CANVAS_COLOR
348
+ the color of the extended larger canvas(default:
349
+ 'black')
350
+ -W, --width WIDTH set the width of the image (default: '600')
351
+ -H, --height HEIGHT set the height of the image (default: '277')
352
+ -op, --open open the image using default program (default:
353
+ 'False')
354
+ -od, --output-dir OUTPUT_DIR
355
+ set default output folder (default: 'output')
356
+ ```
357
+
358
+ <!--help-resize !-->
359
+
360
+ ### fotolab sharpen
361
+
362
+ ```console
363
+ fotolab sharpen -h
364
+ ```
365
+
366
+ <!--help-sharpen !-->
367
+
368
+ ```console
369
+ usage: fotolab sharpen [-h] [-r RADIUS] [-p PERCENT] [-t THRESHOLD] [-ba]
370
+ [-op] [-od OUTPUT_DIR]
371
+ IMAGE_FILENAMES [IMAGE_FILENAMES ...]
372
+
373
+ positional arguments:
374
+ IMAGE_FILENAMES set the image filenames
375
+
376
+ options:
377
+ -h, --help show this help message and exit
378
+ -r, --radius RADIUS set the radius or size of edges (default: '1')
379
+ -p, --percent PERCENT
380
+ set the amount of overall strength of sharpening
381
+ effect (default: '100')
382
+ -t, --threshold THRESHOLD
383
+ set the minimum brightness changed to be sharpened
384
+ (default: '3')
385
+ -ba, --before-after generate a GIF showing before and after changes
386
+ -op, --open open the image using default program (default:
387
+ 'False')
388
+ -od, --output-dir OUTPUT_DIR
389
+ set default output folder (default: 'output')
390
+ ```
391
+
392
+ <!--help-sharpen !-->
393
+
394
+ ### fotolab watermark
395
+
396
+ ```console
397
+ fotolab watermark -h
398
+ ```
399
+
400
+ <!--help-watermark !-->
401
+
402
+ ```console
403
+ usage: fotolab watermark [-h] [-t WATERMARK_TEXT]
404
+ [-p {top-left,top-right,bottom-left,bottom-right}]
405
+ [-pd PADDING] [-fs FONT_SIZE] [-fc FONT_COLOR]
406
+ [-ow OUTLINE_WIDTH] [-oc OUTLINE_COLOR]
407
+ [-a ALPHA_VALUE] [--camera]
408
+ [-l | --lowercase | --no-lowercase] [-op]
409
+ [-od OUTPUT_DIR]
410
+ IMAGE_FILENAMES [IMAGE_FILENAMES ...]
411
+
412
+ positional arguments:
413
+ IMAGE_FILENAMES set the image filenames
414
+
415
+ options:
416
+ -h, --help show this help message and exit
417
+ -t, --text WATERMARK_TEXT
418
+ set the watermark text (default: 'kianmeng.org')
419
+ -p, --position {top-left,top-right,bottom-left,bottom-right}
420
+ set position of the watermark text (default: 'bottom-
421
+ left')
422
+ -pd, --padding PADDING
423
+ set the padding of the watermark text relative to the
424
+ image (default: '15')
425
+ -fs, --font-size FONT_SIZE
426
+ set the font size of watermark text (default: '12')
427
+ -fc, --font-color FONT_COLOR
428
+ set the font color of watermark text (default:
429
+ 'white')
430
+ -ow, --outline-width OUTLINE_WIDTH
431
+ set the outline width of the watermark text (default:
432
+ '2')
433
+ -oc, --outline-color OUTLINE_COLOR
434
+ set the outline color of the watermark text (default:
435
+ 'black')
436
+ -a, --alpha ALPHA_VALUE
437
+ set the transparency of the watermark text (0-255,
438
+ where 0 is fully transparent and 255 is fully opaque;
439
+ default: '128')
440
+ --camera use camera metadata as watermark
441
+ -l, --lowercase, --no-lowercase
442
+ lowercase the watermark text
443
+ -op, --open open the image using default program (default:
444
+ 'False')
445
+ -od, --output-dir OUTPUT_DIR
446
+ set default output folder (default: 'output')
447
+ ```
448
+
449
+ <!--help-watermark !-->
450
+
451
+ ### fotolab env
452
+
453
+ ```console
454
+ fotolab env -h
455
+ ```
456
+
457
+ <!--help-env !-->
458
+
459
+ ```console
460
+ usage: fotolab env [-h]
461
+
462
+ options:
463
+ -h, --help show this help message and exit
464
+ ```
465
+
466
+ <!--help-env !-->
467
+
468
+ ## Copyright and License
469
+
470
+ Copyright (C) 2024,2025 Kian-Meng Ang
471
+
472
+ This program is free software: you can redistribute it and/or modify it under
473
+ the terms of the GNU Affero General Public License as published by the Free
474
+ Software Foundation, either version 3 of the License, or (at your option) any
475
+ later version.
476
+
477
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY
478
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
479
+ PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
480
+
481
+ You should have received a copy of the GNU Affero General Public License along
482
+ with this program. If not, see <https://www.gnu.org/licenses/>.
483
+
484
+ The fish logo used in the documentation generated by Sphinx is a public domain
485
+ drawing of male freshwater phase [Sockeye (red) salmon (Oncorhynchus nerka)]
486
+ (https://en.wikipedia.org/w/index.php?oldid=1186575702) from
487
+ <https://commons.wikimedia.org/entity/M2787002>.
@@ -0,0 +1,39 @@
1
+ LICENSE.md
2
+ README.md
3
+ pyproject.toml
4
+ src/fotolab/__init__.py
5
+ src/fotolab/__main__.py
6
+ src/fotolab/cli.py
7
+ src/fotolab.egg-info/PKG-INFO
8
+ src/fotolab.egg-info/SOURCES.txt
9
+ src/fotolab.egg-info/dependency_links.txt
10
+ src/fotolab.egg-info/entry_points.txt
11
+ src/fotolab.egg-info/requires.txt
12
+ src/fotolab.egg-info/top_level.txt
13
+ src/fotolab/subcommands/__init__.py
14
+ src/fotolab/subcommands/animate.py
15
+ src/fotolab/subcommands/auto.py
16
+ src/fotolab/subcommands/border.py
17
+ src/fotolab/subcommands/contrast.py
18
+ src/fotolab/subcommands/env.py
19
+ src/fotolab/subcommands/halftone.py
20
+ src/fotolab/subcommands/info.py
21
+ src/fotolab/subcommands/montage.py
22
+ src/fotolab/subcommands/resize.py
23
+ src/fotolab/subcommands/rotate.py
24
+ src/fotolab/subcommands/sharpen.py
25
+ src/fotolab/subcommands/watermark.py
26
+ tests/test_animate_subcommand.py
27
+ tests/test_auto_subcommand.py
28
+ tests/test_border_subcommand.py
29
+ tests/test_contrast_subcommand.py
30
+ tests/test_env_subcommand.py
31
+ tests/test_halftone_subcommand.py
32
+ tests/test_help_flag.py
33
+ tests/test_info_subcommand.py
34
+ tests/test_montage_subcommand.py
35
+ tests/test_quiet_flag.py
36
+ tests/test_resize_subcommand.py
37
+ tests/test_rotate_subcommand.py
38
+ tests/test_sharpen_subcommand.py
39
+ tests/test_watermark_subcommand.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ fotolab = fotolab.cli:main
@@ -0,0 +1,18 @@
1
+ pillow
2
+
3
+ [doc]
4
+ myst-parser
5
+ sphinx
6
+ sphinx-autodoc-typehints
7
+ sphinx-copybutton
8
+
9
+ [lint]
10
+ pre-commit
11
+ ruff
12
+
13
+ [test]
14
+ pytest
15
+ pytest-cov
16
+ pytest-randomly
17
+ pytest-xdist
18
+ scripttest
@@ -0,0 +1 @@
1
+ fotolab
@@ -0,0 +1,21 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+
17
+ def test_animate_subcommand(cli_runner, image_file):
18
+ img_path1 = image_file("sample.png")
19
+ img_path2 = image_file("sample.png")
20
+ ret = cli_runner("animate", str(img_path1), str(img_path2))
21
+ assert ret.returncode == 0
@@ -0,0 +1,21 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+
17
+ def test_auto_subcommand(cli_runner, image_file):
18
+ """Test auto subcommand."""
19
+ img_path = image_file("sample.png")
20
+ ret = cli_runner("auto", str(img_path))
21
+ assert ret.returncode == 0
@@ -0,0 +1,37 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+
17
+ def test_border_subcommand(cli_runner, image_file):
18
+ img_path = image_file("sample.png")
19
+ ret = cli_runner("border", str(img_path), "--width", "10")
20
+ assert ret.returncode == 0
21
+
22
+
23
+ def test_border_subcommand_with_individual_widths(cli_runner, image_file):
24
+ img_path = image_file("sample.png")
25
+ ret = cli_runner(
26
+ "border",
27
+ str(img_path),
28
+ "--width-top",
29
+ "5",
30
+ "--width-right",
31
+ "10",
32
+ "--width-bottom",
33
+ "15",
34
+ "--width-left",
35
+ "20",
36
+ )
37
+ assert ret.returncode == 0
@@ -0,0 +1,20 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+
17
+ def test_contrast_subcommand(cli_runner, image_file):
18
+ img_path = image_file("sample.png")
19
+ ret = cli_runner("contrast", str(img_path))
20
+ assert ret.returncode == 0
@@ -0,0 +1,36 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ import platform
17
+ import sys
18
+
19
+ from fotolab import __version__
20
+
21
+
22
+ def test_env_output(cli_runner):
23
+ ret = cli_runner("env")
24
+
25
+ actual_sys_version = sys.version.replace("\n", "")
26
+ actual_platform = platform.platform()
27
+
28
+ expected_output = (
29
+ f"fotolab: {__version__}\n"
30
+ f"python: {actual_sys_version}\n"
31
+ f"platform: {actual_platform}\n"
32
+ )
33
+
34
+ assert ret.stdout == expected_output
35
+ assert ret.stderr == ""
36
+ assert ret.returncode == 0
@@ -0,0 +1,20 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+
17
+ def test_halftone_subcommand(cli_runner, image_file):
18
+ img_path = image_file("sample.png")
19
+ ret = cli_runner("halftone", str(img_path))
20
+ assert ret.returncode == 0
@@ -0,0 +1,22 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ import pytest
17
+
18
+
19
+ @pytest.mark.parametrize("option", ["-h", "--help"])
20
+ def test_help_message(cli_runner, option):
21
+ ret = cli_runner(option)
22
+ assert "A console program to manipulate photos." in ret.stdout
@@ -0,0 +1,20 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+
17
+ def test_info_subcommand(cli_runner, image_file):
18
+ img_path = image_file("sample.png")
19
+ ret = cli_runner("info", str(img_path))
20
+ assert ret.returncode == 0
@@ -0,0 +1,29 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+
17
+ def test_montage_subcommand(cli_runner, image_file):
18
+ img_path = image_file("sample.png")
19
+ ret = cli_runner("montage", str(img_path), str(img_path))
20
+ assert ret.returncode == 0
21
+
22
+
23
+ def test_montage_subcommand_with_single_image_raises_error(
24
+ cli_runner, image_file
25
+ ):
26
+ img_path = image_file("sample.png")
27
+ ret = cli_runner("montage", str(img_path))
28
+ assert ret.returncode != 0
29
+ assert "error: at least two images is required for montage" in ret.stdout
@@ -0,0 +1,22 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ import pytest
17
+
18
+
19
+ @pytest.mark.parametrize("option", ["-q", "--quiet"])
20
+ def test_no_debug_logs(cli_runner, option):
21
+ ret = cli_runner(option)
22
+ assert "debug=True" not in ret.stderr
@@ -0,0 +1,20 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+
17
+ def test_resize_subcommand(cli_runner, image_file):
18
+ img_path = image_file("sample.png")
19
+ ret = cli_runner("resize", str(img_path), "--width", "200")
20
+ assert ret.returncode == 0
@@ -0,0 +1,20 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+
17
+ def test_rotate_subcommand(cli_runner, image_file):
18
+ img_path = image_file("sample.png")
19
+ ret = cli_runner("rotate", str(img_path))
20
+ assert ret.returncode == 0
@@ -0,0 +1,20 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+
17
+ def test_sharpen_subcommand(cli_runner, image_file):
18
+ img_path = image_file("sample.png")
19
+ ret = cli_runner("sharpen", str(img_path))
20
+ assert ret.returncode == 0
@@ -0,0 +1,20 @@
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
+ # Software Foundation, either version 3 of the License, or (at your option) any
6
+ # later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ # details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+
17
+ def test_watermark_subcommand(cli_runner, image_file):
18
+ img_path = image_file("sample.png")
19
+ ret = cli_runner("watermark", str(img_path), "--text", "Test")
20
+ assert ret.returncode == 0
File without changes
File without changes
File without changes