fotolab 0.28.0__tar.gz → 0.28.2__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 (43) hide show
  1. {fotolab-0.28.0 → fotolab-0.28.2}/.gitignore +2 -1
  2. {fotolab-0.28.0 → fotolab-0.28.2}/.pre-commit-config.yaml +1 -1
  3. {fotolab-0.28.0 → fotolab-0.28.2}/CHANGELOG.md +17 -0
  4. {fotolab-0.28.0 → fotolab-0.28.2}/PKG-INFO +1 -1
  5. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/__init__.py +14 -12
  6. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/subcommands/border.py +39 -25
  7. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/subcommands/contrast.py +11 -6
  8. {fotolab-0.28.0 → fotolab-0.28.2}/.coveragerc +0 -0
  9. {fotolab-0.28.0 → fotolab-0.28.2}/.python-version +0 -0
  10. {fotolab-0.28.0 → fotolab-0.28.2}/CONTRIBUTING.md +0 -0
  11. {fotolab-0.28.0 → fotolab-0.28.2}/LICENSE.md +0 -0
  12. {fotolab-0.28.0 → fotolab-0.28.2}/Pipfile +0 -0
  13. {fotolab-0.28.0 → fotolab-0.28.2}/Pipfile.lock +0 -0
  14. {fotolab-0.28.0 → fotolab-0.28.2}/README.md +0 -0
  15. {fotolab-0.28.0 → fotolab-0.28.2}/docs/Makefile +0 -0
  16. {fotolab-0.28.0 → fotolab-0.28.2}/docs/make.bat +0 -0
  17. {fotolab-0.28.0 → fotolab-0.28.2}/docs/source/CHANGELOG.md +0 -0
  18. {fotolab-0.28.0 → fotolab-0.28.2}/docs/source/CONTRIBUTING.md +0 -0
  19. {fotolab-0.28.0 → fotolab-0.28.2}/docs/source/LICENSE.md +0 -0
  20. {fotolab-0.28.0 → fotolab-0.28.2}/docs/source/README.md +0 -0
  21. {fotolab-0.28.0 → fotolab-0.28.2}/docs/source/_static/logo.jpg +0 -0
  22. {fotolab-0.28.0 → fotolab-0.28.2}/docs/source/conf.py +0 -0
  23. {fotolab-0.28.0 → fotolab-0.28.2}/docs/source/index.rst +0 -0
  24. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/__main__.py +0 -0
  25. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/cli.py +0 -0
  26. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/subcommands/__init__.py +0 -0
  27. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/subcommands/animate.py +0 -0
  28. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/subcommands/auto.py +0 -0
  29. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/subcommands/env.py +0 -0
  30. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/subcommands/halftone.py +0 -0
  31. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/subcommands/info.py +0 -0
  32. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/subcommands/montage.py +0 -0
  33. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/subcommands/resize.py +0 -0
  34. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/subcommands/rotate.py +0 -0
  35. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/subcommands/sharpen.py +0 -0
  36. {fotolab-0.28.0 → fotolab-0.28.2}/fotolab/subcommands/watermark.py +0 -0
  37. {fotolab-0.28.0 → fotolab-0.28.2}/noxfile.py +0 -0
  38. {fotolab-0.28.0 → fotolab-0.28.2}/pyproject.toml +0 -0
  39. {fotolab-0.28.0 → fotolab-0.28.2}/tests/__init__.py +0 -0
  40. {fotolab-0.28.0 → fotolab-0.28.2}/tests/conftest.py +0 -0
  41. {fotolab-0.28.0 → fotolab-0.28.2}/tests/test_env_subcommand.py +0 -0
  42. {fotolab-0.28.0 → fotolab-0.28.2}/tests/test_help_flag.py +0 -0
  43. {fotolab-0.28.0 → fotolab-0.28.2}/tests/test_quiet_flag.py +0 -0
@@ -160,6 +160,7 @@ cython_debug/
160
160
  #.idea/
161
161
 
162
162
  # heatmaps
163
- TODO
164
163
  *.png
164
+ .aider*
165
+ TODO
165
166
  output
@@ -15,7 +15,7 @@ repos:
15
15
  - id: trailing-whitespace
16
16
 
17
17
  - repo: https://github.com/abravalheri/validate-pyproject
18
- rev: v0.23
18
+ rev: v0.24
19
19
  hooks:
20
20
  - id: validate-pyproject
21
21
  name: validate-pyproject
@@ -7,6 +7,23 @@ and this project adheres to [0-based versioning](https://0ver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## v0.28.2 (2025-03-23)
11
+
12
+ - Accept width in integer instead of string
13
+ - Bump pre-commit hook for validate-project
14
+ - Handle file not found and other exceptions when opening/processing images
15
+ - Remove all unnecessary exceptions handling
16
+ - Use Union for border width return type to allow int or tuple
17
+
18
+ ## v0.28.1 (2025-03-16)
19
+
20
+ - Add typehint to `_get_output_filename` function
21
+ - Fix incorrect log message formatting in save_gif_image function
22
+ - Handle `FileNotFoundError` and `IOError` exceptions when opening images
23
+ - Handle `OSError` when opening image files.
24
+ - Ignore aider files
25
+ - Revert refactoring on open image using default program
26
+
10
27
  ## v0.28.0 (2025-03-09)
11
28
 
12
29
  - Handle exception of private function with exif info
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fotolab
3
- Version: 0.28.0
3
+ Version: 0.28.2
4
4
  Summary: A console program that manipulate images.
5
5
  Keywords: photography,photo
6
6
  Author-email: Kian-Meng Ang <kianmeng@cpan.org>
@@ -23,7 +23,7 @@ from pathlib import Path
23
23
 
24
24
  from PIL import Image
25
25
 
26
- __version__ = "0.28.0"
26
+ __version__ = "0.28.2"
27
27
 
28
28
  log = logging.getLogger(__name__)
29
29
 
@@ -43,7 +43,7 @@ def save_gif_image(
43
43
  )
44
44
  new_filename.parent.mkdir(parents=True, exist_ok=True)
45
45
 
46
- log.info("f{subcommand} gif image: %s", new_filename)
46
+ log.info("%s gif image: %s", subcommand, new_filename)
47
47
  original_image.save(
48
48
  new_filename,
49
49
  format="gif",
@@ -79,7 +79,9 @@ def save_image(args, new_image, output_filename, subcommand):
79
79
  _open_image(new_filename)
80
80
 
81
81
 
82
- def _get_output_filename(args, image_file, subcommand):
82
+ def _get_output_filename(
83
+ args: argparse.Namespace, image_file: Path, subcommand: str
84
+ ) -> Path:
83
85
  """Build and return output filename based on the command line options."""
84
86
  if args.overwrite:
85
87
  return image_file.with_name(image_file.name)
@@ -91,14 +93,14 @@ def _get_output_filename(args, image_file, subcommand):
91
93
 
92
94
  def _open_image(filename):
93
95
  """Open generated image using default program."""
94
- platform_open_func = {
95
- "linux": subprocess.call(["xdg-open", filename]),
96
- "darwin": subprocess.call(["open", filename]),
97
- "windows": subprocess.call(["start", filename]),
98
- }
99
96
  try:
100
- platform_open_func[sys.platform]
101
- except KeyError:
102
- print(f"Unsupported platform: {sys.platform}")
103
- else:
97
+ if sys.platform == "linux":
98
+ subprocess.call(["xdg-open", filename])
99
+ elif sys.platform == "darwin":
100
+ subprocess.call(["open", filename])
101
+ elif sys.platform == "windows":
102
+ subprocess.Popen(["open", filename])
104
103
  log.info("open image: %s", filename.resolve())
104
+
105
+ except OSError as e:
106
+ log.error("Error opening image: %s -> %s", filename, e)
@@ -17,6 +17,7 @@
17
17
 
18
18
  import argparse
19
19
  import logging
20
+ from typing import Tuple, Union
20
21
 
21
22
  from PIL import Image, ImageColor, ImageOps
22
23
 
@@ -54,9 +55,9 @@ def build_subparser(subparsers) -> None:
54
55
  "-w",
55
56
  "--width",
56
57
  dest="width",
57
- type=str,
58
+ type=int,
58
59
  default=10,
59
- help="set the width of border (default: '%(default)s')",
60
+ help="set the width of border in pixels (default: '%(default)s')",
60
61
  metavar="WIDTH",
61
62
  )
62
63
 
@@ -64,9 +65,9 @@ def build_subparser(subparsers) -> None:
64
65
  "-wt",
65
66
  "--width-top",
66
67
  dest="width_top",
67
- type=str,
68
+ type=int,
68
69
  default=0,
69
- help="set the width of top border (default: '%(default)s')",
70
+ help="set the width of top border in pixels (default: '%(default)s')",
70
71
  metavar="WIDTH",
71
72
  )
72
73
 
@@ -74,9 +75,12 @@ def build_subparser(subparsers) -> None:
74
75
  "-wr",
75
76
  "--width-right",
76
77
  dest="width_right",
77
- type=str,
78
+ type=int,
78
79
  default=0,
79
- help="set the width of right border (default: '%(default)s')",
80
+ help=(
81
+ "set the width of right border in pixels"
82
+ " (default: '%(default)s')"
83
+ ),
80
84
  metavar="WIDTH",
81
85
  )
82
86
 
@@ -84,9 +88,12 @@ def build_subparser(subparsers) -> None:
84
88
  "-wb",
85
89
  "--width-bottom",
86
90
  dest="width_bottom",
87
- type=str,
91
+ type=int,
88
92
  default=0,
89
- help="set the width of bottom border (default: '%(default)s')",
93
+ help=(
94
+ "set the width of bottom border in pixels"
95
+ " (default: '%(default)s')"
96
+ ),
90
97
  metavar="WIDTH",
91
98
  )
92
99
 
@@ -94,9 +101,9 @@ def build_subparser(subparsers) -> None:
94
101
  "-wl",
95
102
  "--width-left",
96
103
  dest="width_left",
97
- type=str,
104
+ type=int,
98
105
  default=0,
99
- help="set the width of left border (default: '%(default)s')",
106
+ help="set the width of left border in pixels (default: '%(default)s')",
100
107
  metavar="WIDTH",
101
108
  )
102
109
 
@@ -130,27 +137,34 @@ def run(args: argparse.Namespace) -> None:
130
137
  log.debug(args)
131
138
 
132
139
  for image_filename in args.image_filenames:
133
- original_image = Image.open(image_filename)
134
-
135
- border = get_border(args)
136
- bordered_image = ImageOps.expand(
137
- original_image,
138
- border=border,
139
- fill=ImageColor.getrgb(args.color),
140
- )
140
+ try:
141
+ original_image = Image.open(image_filename)
142
+ border = get_border(args)
143
+ bordered_image = ImageOps.expand(
144
+ original_image,
145
+ border=border,
146
+ fill=ImageColor.getrgb(args.color),
147
+ )
148
+ except Exception as e:
149
+ log.error("Error adding border to %s: %s", image_filename, e)
150
+ continue
141
151
 
142
152
  save_image(args, bordered_image, image_filename, "border")
143
153
 
144
154
 
145
- def get_border(args: argparse.Namespace) -> tuple[int, int, int, int]:
155
+ def get_border(
156
+ args: argparse.Namespace,
157
+ ) -> Union[Tuple[int, int, int, int], int]:
146
158
  """Calculate the border dimensions.
147
159
 
148
160
  Args:
149
161
  args (argparse.Namespace): Command line arguments
150
162
 
151
163
  Returns:
152
- tuple[int, int, int, int]: Border dimensions in pixesl. If individual
153
- widths are not specified, returns a uniform width for all sides.
164
+ Union[Tuple[int, int, int, int], int]: Border dimensions in pixels.
165
+ If individual widths are specified, returns a tuple of (left, top,
166
+ right, bottom) widths. Otherwise, returns a uniform width for all
167
+ sides.
154
168
  """
155
169
  if any(
156
170
  [
@@ -161,9 +175,9 @@ def get_border(args: argparse.Namespace) -> tuple[int, int, int, int]:
161
175
  ]
162
176
  ):
163
177
  return (
164
- int(args.width_left),
165
- int(args.width_top),
166
- int(args.width_right),
167
- int(args.width_bottom),
178
+ args.width_left,
179
+ args.width_top,
180
+ args.width_right,
181
+ args.width_bottom,
168
182
  )
169
183
  return args.width
@@ -86,9 +86,14 @@ def run(args: argparse.Namespace) -> None:
86
86
  log.debug(args)
87
87
 
88
88
  for image_filename in args.image_filenames:
89
- original_image = Image.open(image_filename)
90
- contrast_image = ImageOps.autocontrast(
91
- original_image, cutoff=args.cutoff
92
- )
93
-
94
- save_image(args, contrast_image, image_filename, "contrast")
89
+ try:
90
+ original_image = Image.open(image_filename)
91
+ contrast_image = ImageOps.autocontrast(
92
+ original_image, cutoff=args.cutoff
93
+ )
94
+
95
+ save_image(args, contrast_image, image_filename, "contrast")
96
+ except FileNotFoundError:
97
+ log.error(f"Image file not found: {image_filename}")
98
+ except IOError:
99
+ log.error(f"Cannot open or read image file: {image_filename}")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes