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