fotolab 0.28.5__py3-none-any.whl → 0.28.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fotolab/__init__.py CHANGED
@@ -17,13 +17,14 @@
17
17
 
18
18
  import argparse
19
19
  import logging
20
+ import os
20
21
  import subprocess
21
22
  import sys
22
23
  from pathlib import Path
23
24
 
24
25
  from PIL import Image
25
26
 
26
- __version__ = "0.28.5"
27
+ __version__ = "0.28.6"
27
28
 
28
29
  log = logging.getLogger(__name__)
29
30
 
@@ -98,10 +99,9 @@ def _open_image(filename):
98
99
  subprocess.call(["xdg-open", filename])
99
100
  elif sys.platform == "darwin":
100
101
  subprocess.call(["open", filename])
101
- elif sys.platform == "windows":
102
- with subprocess.Popen(["open", filename], shell=True) as proc:
103
- proc.wait()
102
+ elif sys.platform == "win32":
103
+ os.startfile(filename)
104
104
  log.info("open image: %s", filename.resolve())
105
105
 
106
- except OSError as error:
106
+ except (OSError, FileNotFoundError) as error:
107
107
  log.error("Error opening image: %s -> %s", filename, error)
fotolab/cli.py CHANGED
@@ -1,17 +1,17 @@
1
1
  # Copyright (C) 2024,2025 Kian-Meng Ang
2
2
 
3
- # This program is free software: you can redistribute it and/or modify
4
- # it under the terms of the GNU General Public License as published by
5
- # the Free Software Foundation, either version 3 of the License, or
6
- # (at your option) any later version.
7
-
8
- # This program is distributed in the hope that it will be useful,
9
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
- # GNU General Public License for more details.
12
-
13
- # You should have received a copy of the GNU General Public License
14
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
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
15
 
16
16
  """A console program to manipulate photos.
17
17
 
@@ -19,23 +19,24 @@ import argparse
19
19
  import logging
20
20
  import math
21
21
  from pathlib import Path
22
+ from typing import Tuple
22
23
 
23
24
  from PIL import Image, ImageColor, ImageDraw, ImageFont, ImageSequence
24
25
 
25
26
  from fotolab import save_image
26
27
  from fotolab.subcommands.info import camera_metadata
27
28
 
28
- log = logging.getLogger(__name__)
29
+ log: logging.Logger = logging.getLogger(__name__)
29
30
 
30
- FONT_SIZE_ASPECT_RATIO = 12 / 600
31
- FONT_PADDING_ASPECT_RATIO = 15 / 600
32
- FONT_OUTLINE_WIDTH_ASPECT_RATIO = 2 / 600
33
- POSITIONS = ["top-left", "top-right", "bottom-left", "bottom-right"]
31
+ FONT_SIZE_ASPECT_RATIO: float = 12 / 600
32
+ FONT_PADDING_ASPECT_RATIO: float = 15 / 600
33
+ FONT_OUTLINE_WIDTH_ASPECT_RATIO: float = 2 / 600
34
+ POSITIONS: list[str] = ["top-left", "top-right", "bottom-left", "bottom-right"]
34
35
 
35
36
 
36
- def build_subparser(subparsers) -> None:
37
+ def build_subparser(subparsers: argparse._SubParsersAction) -> None:
37
38
  """Build the subparser."""
38
- watermark_parser = subparsers.add_parser(
39
+ watermark_parser: argparse.ArgumentParser = subparsers.add_parser(
39
40
  "watermark", help="watermark an image"
40
41
  )
41
42
 
@@ -175,11 +176,13 @@ def run(args: argparse.Namespace) -> None:
175
176
  log.debug(args)
176
177
 
177
178
  for image_filename in args.image_filenames:
178
- image = Image.open(image_filename)
179
+ image: Image.Image = Image.open(image_filename)
179
180
  if image.format == "GIF":
180
181
  watermark_gif_image(image, image_filename, args)
181
182
  else:
182
- watermarked_image = watermark_non_gif_image(image, args)
183
+ watermarked_image: Image.Image = watermark_non_gif_image(
184
+ image, args
185
+ )
183
186
  save_image(args, watermarked_image, image_filename, "watermark")
184
187
 
185
188
 
@@ -196,15 +199,17 @@ def watermark_gif_image(
196
199
  Returns:
197
200
  None
198
201
  """
199
- frames = []
202
+ frames: list[Image.Image] = []
200
203
  for frame in ImageSequence.Iterator(original_image):
201
- watermarked_frame = watermark_image(args, frame.convert("RGBA"))
204
+ watermarked_frame: Image.Image = watermark_image(
205
+ args, frame.convert("RGBA")
206
+ )
202
207
  frames.append(watermarked_frame)
203
208
 
204
- image_file = Path(output_filename)
209
+ image_file: Path = Path(output_filename)
205
210
 
206
211
  if args.overwrite:
207
- new_filename = original_image.with_name(image_file.name)
212
+ new_filename: Path = image_file.with_name(image_file.name)
208
213
  else:
209
214
  new_filename = Path(
210
215
  args.output_dir,
@@ -243,16 +248,18 @@ def watermark_image(
243
248
  args: argparse.Namespace, original_image: Image.Image
244
249
  ) -> Image.Image:
245
250
  """Watermark an image."""
246
- watermarked_image = original_image.copy()
247
- draw = ImageDraw.Draw(watermarked_image)
251
+ watermarked_image: Image.Image = original_image.copy()
252
+ draw: ImageDraw.ImageDraw = ImageDraw.Draw(watermarked_image)
248
253
 
249
- font = ImageFont.load_default(calc_font_size(original_image, args))
254
+ font: ImageFont.FreeTypeFont = ImageFont.load_default(
255
+ calc_font_size(original_image, args)
256
+ )
250
257
  log.debug("default font: %s", " ".join(font.getname()))
251
258
 
252
- text = prepare_text(args, original_image)
259
+ text: str = prepare_text(args, original_image)
253
260
  (left, top, right, bottom) = draw.textbbox(xy=(0, 0), text=text, font=font)
254
- text_width = right - left
255
- text_height = bottom - top
261
+ text_width: int = right - left
262
+ text_height: int = bottom - top
256
263
  (position_x, position_y) = calc_position(
257
264
  watermarked_image,
258
265
  text_width,
@@ -272,9 +279,9 @@ def watermark_image(
272
279
  return watermarked_image
273
280
 
274
281
 
275
- def prepare_text(args, image) -> str:
282
+ def prepare_text(args: argparse.Namespace, image: Image.Image) -> str:
276
283
  """Prepare the watermark text."""
277
- text = args.text
284
+ text: str = args.text
278
285
  if args.camera and camera_metadata(image):
279
286
  text = camera_metadata(image)
280
287
 
@@ -284,10 +291,10 @@ def prepare_text(args, image) -> str:
284
291
  return text
285
292
 
286
293
 
287
- def calc_font_size(image, args) -> int:
294
+ def calc_font_size(image: Image.Image, args: argparse.Namespace) -> int:
288
295
  """Calculate the font size based on the width of the image."""
289
296
  width, _height = image.size
290
- new_font_size = args.font_size
297
+ new_font_size: int = args.font_size
291
298
  if width > 600:
292
299
  new_font_size = math.floor(FONT_SIZE_ASPECT_RATIO * width)
293
300
 
@@ -295,10 +302,12 @@ def calc_font_size(image, args) -> int:
295
302
  return new_font_size
296
303
 
297
304
 
298
- def calc_font_outline_width(image, args) -> int:
305
+ def calc_font_outline_width(
306
+ image: Image.Image, args: argparse.Namespace
307
+ ) -> int:
299
308
  """Calculate the font padding based on the width of the image."""
300
309
  width, _height = image.size
301
- new_font_outline_width = args.outline_width
310
+ new_font_outline_width: int = args.outline_width
302
311
  if width > 600:
303
312
  new_font_outline_width = math.floor(
304
313
  FONT_OUTLINE_WIDTH_ASPECT_RATIO * width
@@ -308,10 +317,10 @@ def calc_font_outline_width(image, args) -> int:
308
317
  return new_font_outline_width
309
318
 
310
319
 
311
- def calc_padding(image, args) -> int:
320
+ def calc_padding(image: Image.Image, args: argparse.Namespace) -> int:
312
321
  """Calculate the font padding based on the width of the image."""
313
322
  width, _height = image.size
314
- new_padding = args.padding
323
+ new_padding: int = args.padding
315
324
  if width > 600:
316
325
  new_padding = math.floor(FONT_PADDING_ASPECT_RATIO * width)
317
326
 
@@ -319,9 +328,15 @@ def calc_padding(image, args) -> int:
319
328
  return new_padding
320
329
 
321
330
 
322
- def calc_position(image, text_width, text_height, position, padding) -> tuple:
331
+ def calc_position(
332
+ image: Image.Image,
333
+ text_width: int,
334
+ text_height: int,
335
+ position: str,
336
+ padding: int,
337
+ ) -> Tuple[int, int]:
323
338
  """Calculate the boundary coordinates of the watermark text."""
324
- positions = {
339
+ positions: dict[str, Tuple[int, int]] = {
325
340
  "top-left": (padding, padding),
326
341
  "top-right": (image.width - text_width - padding, padding),
327
342
  "bottom-left": (padding, image.height - text_height - padding),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fotolab
3
- Version: 0.28.5
3
+ Version: 0.28.6
4
4
  Summary: A console program that manipulate images.
5
5
  Keywords: photography,photo
6
6
  Author-email: Kian-Meng Ang <kianmeng@cpan.org>
@@ -1,6 +1,6 @@
1
- fotolab/__init__.py,sha256=qytheZnIjS5g0aUnq6-ChflsFcLasoz97T7GReoaeeQ,3301
1
+ fotolab/__init__.py,sha256=yiRE-ZW8LgYnZAJ7wKNY_XdaaPC9IbHRMnlRKLYjsdA,3262
2
2
  fotolab/__main__.py,sha256=aboOURPs_snOXTEWYR0q8oq1UTY9e-NxCd1j33V0wHI,833
3
- fotolab/cli.py,sha256=fIApD9C4Vkn-u4CejOFTboSV5f-Ks9vBGxeb-_ca-Nw,3935
3
+ fotolab/cli.py,sha256=ffT3CbMN37Qehw4l7YvwUbPFY-SezLLn4Xthi0ejOmk,3956
4
4
  fotolab/subcommands/__init__.py,sha256=l3DlIaJ3u3jGjnC1H1yV8LZ_nPqOLJ6gikD4BCaMAQ0,1129
5
5
  fotolab/subcommands/animate.py,sha256=OcS6Q6JM1TW6HF0dSgBsV9mpEGwDMcJlQssupkf0_ZA,3393
6
6
  fotolab/subcommands/auto.py,sha256=ia-xegV1Z4HvYsbKgmTzf1NfNFdTDPWfZe7vQ1_90Ik,2425
@@ -13,9 +13,9 @@ fotolab/subcommands/montage.py,sha256=d_3EcyRSFS8fKkczlHO8IRP-mrKhQUtkQndjfd0MKs
13
13
  fotolab/subcommands/resize.py,sha256=UOb2rg_5ArRj0gxPTDOww_ix3tqJRC0W5b_S-Lt1G4w,5444
14
14
  fotolab/subcommands/rotate.py,sha256=uBFjHyjiBSQLtrtH1p9myODIHUDr1gkL4PpU-6Y1Ofo,2575
15
15
  fotolab/subcommands/sharpen.py,sha256=YNho2IPbc-lPvSy3Bsjehc2JOEy27LPqFSGRULs9MyY,3492
16
- fotolab/subcommands/watermark.py,sha256=A_dUy_yf9wPHUexB9aIicFIS-BpWrjjfI8Hr4WCCBoc,9449
17
- fotolab-0.28.5.dist-info/entry_points.txt,sha256=mvw7AY_yZkIyjAxPtHNed9X99NZeLnMxEeAfEJUbrCM,44
18
- fotolab-0.28.5.dist-info/LICENSE.md,sha256=tGtFDwxWTjuR9syrJoSv1Hiffd2u8Tu8cYClfrXS_YU,31956
19
- fotolab-0.28.5.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
20
- fotolab-0.28.5.dist-info/METADATA,sha256=0SIqAZceh2lv4iQkVoJu-7nRab99nDecITkquNUXFuU,12970
21
- fotolab-0.28.5.dist-info/RECORD,,
16
+ fotolab/subcommands/watermark.py,sha256=iEuek0BM2PJlGMLq8VZhHxaQ8BInuN2_mvssA7Dhgy8,10032
17
+ fotolab-0.28.6.dist-info/entry_points.txt,sha256=mvw7AY_yZkIyjAxPtHNed9X99NZeLnMxEeAfEJUbrCM,44
18
+ fotolab-0.28.6.dist-info/LICENSE.md,sha256=tGtFDwxWTjuR9syrJoSv1Hiffd2u8Tu8cYClfrXS_YU,31956
19
+ fotolab-0.28.6.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
20
+ fotolab-0.28.6.dist-info/METADATA,sha256=3E5duvvv00UeRQoiWsueL2cdsEj1l17jdkwxY7Nal68,12970
21
+ fotolab-0.28.6.dist-info/RECORD,,