fotolab 0.22.0__py3-none-any.whl → 0.24.0__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
@@ -21,7 +21,7 @@ import subprocess
21
21
  import sys
22
22
  from pathlib import Path
23
23
 
24
- __version__ = "0.22.0"
24
+ __version__ = "0.24.0"
25
25
 
26
26
  log = logging.getLogger(__name__)
27
27
 
fotolab/cli.py CHANGED
@@ -23,46 +23,40 @@
23
23
  import argparse
24
24
  import logging
25
25
  import sys
26
- from typing import Dict, Optional, Sequence
27
-
28
- import fotolab.animate
29
- import fotolab.auto
30
- import fotolab.border
31
- import fotolab.contrast
32
- import fotolab.env
33
- import fotolab.info
34
- import fotolab.montage
35
- import fotolab.resize
36
- import fotolab.rotate
37
- import fotolab.sharpen
38
- import fotolab.watermark
26
+ from typing import Optional, Sequence
27
+
28
+ import fotolab.subcommands
39
29
  from fotolab import __version__
40
30
 
41
31
  log = logging.getLogger(__name__)
42
32
 
43
33
 
44
34
  def setup_logging(args: argparse.Namespace) -> None:
45
- """Set up logging by level."""
46
- if args.verbose == 0:
47
- logging.getLogger("PIL").setLevel(logging.ERROR)
35
+ """Sets up logging configuration based on command-line arguments.
48
36
 
37
+ Args:
38
+ args (argparse.Namespace): Namespace containing parsed arguments.
39
+ """
49
40
  if args.quiet:
50
41
  logging.disable(logging.NOTSET)
51
- else:
52
- conf: Dict = {
53
- True: {
54
- "level": logging.DEBUG,
55
- "msg": "[%(asctime)s] %(levelname)s: %(name)s: %(message)s",
56
- },
57
- False: {"level": logging.INFO, "msg": "%(message)s"},
58
- }
59
-
60
- logging.basicConfig(
61
- level=conf[args.debug]["level"],
62
- stream=sys.stdout,
63
- format=conf[args.debug]["msg"],
64
- datefmt="%Y-%m-%d %H:%M:%S",
65
- )
42
+ return
43
+
44
+ if args.verbose == 0:
45
+ logging.getLogger("PIL").setLevel(logging.ERROR)
46
+
47
+ level = logging.DEBUG if args.debug else logging.INFO
48
+ format_string = (
49
+ "[%(asctime)s] %(levelname)s: %(name)s: %(message)s"
50
+ if args.debug
51
+ else "%(message)s"
52
+ )
53
+
54
+ logging.basicConfig(
55
+ level=level,
56
+ format=format_string,
57
+ stream=sys.stdout,
58
+ datefmt="%Y-%m-%d %H:%M:%S",
59
+ )
66
60
 
67
61
 
68
62
  def build_parser() -> argparse.ArgumentParser:
@@ -136,17 +130,7 @@ def build_parser() -> argparse.ArgumentParser:
136
130
  )
137
131
 
138
132
  subparsers = parser.add_subparsers(help="sub-command help")
139
- fotolab.animate.build_subparser(subparsers)
140
- fotolab.auto.build_subparser(subparsers)
141
- fotolab.border.build_subparser(subparsers)
142
- fotolab.contrast.build_subparser(subparsers)
143
- fotolab.info.build_subparser(subparsers)
144
- fotolab.resize.build_subparser(subparsers)
145
- fotolab.rotate.build_subparser(subparsers)
146
- fotolab.montage.build_subparser(subparsers)
147
- fotolab.sharpen.build_subparser(subparsers)
148
- fotolab.watermark.build_subparser(subparsers)
149
- fotolab.env.build_subparser(subparsers)
133
+ fotolab.subcommands.build_subparser(subparsers)
150
134
 
151
135
  return parser
152
136
 
@@ -0,0 +1,32 @@
1
+ # Copyright (C) 2024 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
+ """Common utils for subcommand."""
17
+
18
+ import importlib
19
+ import pkgutil
20
+
21
+
22
+ def build_subparser(subparsers):
23
+ """Build subparser for each subcommands."""
24
+ iter_namespace = pkgutil.iter_modules(__path__, __name__ + ".")
25
+
26
+ subcommands = {
27
+ name: importlib.import_module(name)
28
+ for finder, name, ispkg in iter_namespace
29
+ }
30
+
31
+ for subcommand in subcommands.values():
32
+ subcommand.build_subparser(subparsers)
@@ -18,10 +18,10 @@
18
18
  import argparse
19
19
  import logging
20
20
 
21
- import fotolab.contrast
22
- import fotolab.resize
23
- import fotolab.sharpen
24
- import fotolab.watermark
21
+ import fotolab.subcommands.contrast
22
+ import fotolab.subcommands.resize
23
+ import fotolab.subcommands.sharpen
24
+ import fotolab.subcommands.watermark
25
25
 
26
26
  log = logging.getLogger(__name__)
27
27
 
@@ -77,7 +77,7 @@ def run(args: argparse.Namespace) -> None:
77
77
  log.debug(args)
78
78
  log.debug(combined_args)
79
79
 
80
- fotolab.resize.run(combined_args)
81
- fotolab.contrast.run(combined_args)
82
- fotolab.sharpen.run(combined_args)
83
- fotolab.watermark.run(combined_args)
80
+ fotolab.subcommands.resize.run(combined_args)
81
+ fotolab.subcommands.contrast.run(combined_args)
82
+ fotolab.subcommands.sharpen.run(combined_args)
83
+ fotolab.subcommands.watermark.run(combined_args)
@@ -44,9 +44,9 @@ def run(_args: argparse.Namespace) -> None:
44
44
  None
45
45
  """
46
46
  sys_version = sys.version.replace("\n", "")
47
- print(
47
+ env = [
48
48
  f"fotolab: {__version__}",
49
49
  f"python: {sys_version}",
50
50
  f"platform: {platform.platform()}",
51
- sep="\n",
52
- )
51
+ ]
52
+ print(*env, sep="\n")
@@ -73,12 +73,20 @@ def run(args: argparse.Namespace) -> None:
73
73
  None
74
74
  """
75
75
  log.debug(args)
76
+
77
+ info = []
78
+ image = Image.open(args.image_filename)
79
+
76
80
  if args.camera:
77
- print(camera_metadata(args.image_filename))
81
+ info.append(camera_metadata(image))
82
+
78
83
  if args.datetime:
79
- print(datetime(args.image_filename))
84
+ info.append(datetime(image))
85
+
86
+ if info:
87
+ print("\n".join(info))
80
88
  else:
81
- exif_tags = extract_exif_tags(args.image_filename)
89
+ exif_tags = extract_exif_tags(image)
82
90
  if exif_tags:
83
91
  tag_name_width = max(map(len, exif_tags))
84
92
  for tag_name, tag_value in exif_tags.items():
@@ -87,9 +95,9 @@ def run(args: argparse.Namespace) -> None:
87
95
  print("No metadata found!")
88
96
 
89
97
 
90
- def extract_exif_tags(image_filename: str, sort: bool = False) -> dict:
98
+ def extract_exif_tags(image: Image.Image, sort: bool = False) -> dict:
91
99
  """Extract Exif metadata from image."""
92
- image = Image.open(image_filename)
100
+ print(type(image))
93
101
  exif = image._getexif()
94
102
  log.debug(exif)
95
103
 
@@ -106,14 +114,14 @@ def extract_exif_tags(image_filename: str, sort: bool = False) -> dict:
106
114
  return filtered_info
107
115
 
108
116
 
109
- def datetime(image_filename):
117
+ def datetime(image: Image.Image):
110
118
  """Extract datetime metadata."""
111
- exif_tags = extract_exif_tags(image_filename)
119
+ exif_tags = extract_exif_tags(image)
112
120
  return exif_tags["DateTime"]
113
121
 
114
122
 
115
- def camera_metadata(image_filename):
123
+ def camera_metadata(image: Image.Image):
116
124
  """Extract camera and model metadata."""
117
- exif_tags = extract_exif_tags(image_filename)
125
+ exif_tags = extract_exif_tags(image)
118
126
  metadata = f'{exif_tags["Make"]} {exif_tags["Model"]}'
119
127
  return metadata.strip()
@@ -40,6 +40,21 @@ def build_subparser(subparsers) -> None:
40
40
  metavar="IMAGE_FILENAMES",
41
41
  )
42
42
 
43
+ rotate_parser.add_argument(
44
+ "-r",
45
+ "--rotation",
46
+ type=int,
47
+ default=0,
48
+ help="Rotation angle in degrees (default: '%(default)s')",
49
+ )
50
+
51
+ rotate_parser.add_argument(
52
+ "-cw",
53
+ "--clockwise",
54
+ action="store_true",
55
+ help="Rotate clockwise (default: '%(default)s)",
56
+ )
57
+
43
58
 
44
59
  def run(args: argparse.Namespace) -> None:
45
60
  """Run rotate subcommand.
@@ -52,10 +67,14 @@ def run(args: argparse.Namespace) -> None:
52
67
  """
53
68
  log.debug(args)
54
69
 
70
+ rotation = args.rotation
71
+ if args.clockwise:
72
+ rotation = -rotation
73
+
55
74
  for image_filename in args.image_filenames:
56
75
  original_image = Image.open(image_filename)
57
76
  rotated_image = original_image.rotate(
58
- 180,
77
+ rotation,
59
78
  expand=True,
60
79
  )
61
80
  save_image(args, rotated_image, image_filename, "rotate")
@@ -19,10 +19,10 @@ import argparse
19
19
  import logging
20
20
  import math
21
21
 
22
- from PIL import Image, ImageColor, ImageDraw, ImageFont
22
+ from PIL import Image, ImageColor, ImageDraw, ImageFont, ImageSequence
23
23
 
24
24
  from fotolab import save_image
25
- from fotolab.info import camera_metadata
25
+ from fotolab.subcommands.info import camera_metadata
26
26
 
27
27
  log = logging.getLogger(__name__)
28
28
 
@@ -157,13 +157,72 @@ def run(args: argparse.Namespace) -> None:
157
157
  log.debug(args)
158
158
 
159
159
  for image_filename in args.image_filenames:
160
- watermarked_image = watermark_image(image_filename, args)
161
- save_image(args, watermarked_image, image_filename, "watermark")
160
+ image = Image.open(image_filename)
161
+ if image.format == "GIF":
162
+ watermark_gif_image(image, args)
163
+ else:
164
+ watermarked_image = watermark_non_gif_image(image, args)
165
+ save_image(args, watermarked_image, image_filename, "watermark")
162
166
 
163
167
 
164
- def watermark_image(image_filename, args):
168
+ def watermark_gif_image(original_image: Image.Image, args: argparse.Namespace):
169
+ """Watermark the image."""
170
+ watermarked_image = original_image.copy()
171
+
172
+ frames = []
173
+
174
+ for frame in ImageSequence.Iterator(original_image):
175
+ frame = frame.convert("RGBA")
176
+ draw = ImageDraw.Draw(frame)
177
+
178
+ font = ImageFont.load_default(calc_font_size(original_image, args))
179
+ log.debug("default font: %s", " ".join(font.getname()))
180
+
181
+ text = args.text
182
+ if args.camera and camera_metadata(original_image):
183
+ text = camera_metadata(original_image)
184
+
185
+ if args.lowercase:
186
+ text = text.lower()
187
+
188
+ (left, top, right, bottom) = draw.textbbox(
189
+ xy=(0, 0), text=text, font=font
190
+ )
191
+ text_width = right - left
192
+ text_height = bottom - top
193
+ (position_x, position_y) = calc_position(
194
+ watermarked_image,
195
+ text_width,
196
+ text_height,
197
+ args.position,
198
+ calc_padding(original_image, args),
199
+ )
200
+
201
+ draw.text(
202
+ (position_x, position_y),
203
+ text,
204
+ font=font,
205
+ fill=(*ImageColor.getrgb(args.font_color), 128),
206
+ stroke_width=calc_font_outline_width(original_image, args),
207
+ stroke_fill=(*ImageColor.getrgb(args.outline_color), 128),
208
+ )
209
+ frames.append(frame)
210
+
211
+ frames[0].save(
212
+ "foo.gif",
213
+ format="GIF",
214
+ append_images=frames[1:],
215
+ save_all=True,
216
+ duration=original_image.info.get("duration", 100),
217
+ loop=original_image.info.get("loop", 0),
218
+ disposal=original_image.info.get("disposal", 2),
219
+ )
220
+
221
+
222
+ def watermark_non_gif_image(
223
+ original_image: Image.Image, args: argparse.Namespace
224
+ ):
165
225
  """Watermark the image."""
166
- original_image = Image.open(image_filename)
167
226
  watermarked_image = original_image.copy()
168
227
 
169
228
  draw = ImageDraw.Draw(watermarked_image)
@@ -172,8 +231,8 @@ def watermark_image(image_filename, args):
172
231
  log.debug("default font: %s", " ".join(font.getname()))
173
232
 
174
233
  text = args.text
175
- if args.camera and camera_metadata(image_filename):
176
- text = camera_metadata(image_filename)
234
+ if args.camera and camera_metadata(original_image):
235
+ text = camera_metadata(original_image)
177
236
 
178
237
  if args.lowercase:
179
238
  text = text.lower()
@@ -237,19 +296,14 @@ def calc_padding(image, args) -> int:
237
296
 
238
297
  def calc_position(image, text_width, text_height, position, padding) -> tuple:
239
298
  """Calculate the boundary coordinates of the watermark text."""
240
- (position_x, position_y) = (0, 0)
241
-
242
- if position == "top-left":
243
- position_x = 0 + padding
244
- position_y = 0 + padding
245
- elif position == "top-right":
246
- position_x = image.width - text_width - padding
247
- position_y = 0 + padding
248
- elif position == "bottom-left":
249
- position_x = 0 + padding
250
- position_y = image.height - text_height - padding
251
- elif position == "bottom-right":
252
- position_x = image.width - text_width - padding
253
- position_y = image.height - text_height - padding
254
-
255
- return (position_x, position_y)
299
+ positions = {
300
+ "top-left": (padding, padding),
301
+ "top-right": (image.width - text_width - padding, padding),
302
+ "bottom-left": (padding, image.height - text_height - padding),
303
+ "bottom-right": (
304
+ image.width - text_width - padding,
305
+ image.height - text_height - padding,
306
+ ),
307
+ }
308
+
309
+ return positions.get(position, (0, 0))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fotolab
3
- Version: 0.22.0
3
+ Version: 0.24.0
4
4
  Summary: A console program that manipulate images.
5
5
  Keywords: photography,photo
6
6
  Author-email: Kian-Meng Ang <kianmeng@cpan.org>
@@ -61,7 +61,7 @@ fotolab -h
61
61
 
62
62
  ```console
63
63
  usage: fotolab [-h] [-o] [-op] [-od OUTPUT_DIR] [-q] [-v] [-d] [-V]
64
- {animate,auto,border,contrast,info,resize,rotate,montage,sharpen,watermark,env} ...
64
+ {animate,auto,border,contrast,env,info,montage,resize,rotate,sharpen,watermark} ...
65
65
 
66
66
  A console program to manipulate photos.
67
67
 
@@ -70,19 +70,19 @@ changelog: https://github.com/kianmeng/fotolab/blob/master/CHANGELOG.md
70
70
  issues: https://github.com/kianmeng/fotolab/issues
71
71
 
72
72
  positional arguments:
73
- {animate,auto,border,contrast,info,resize,rotate,montage,sharpen,watermark,env}
73
+ {animate,auto,border,contrast,env,info,montage,resize,rotate,sharpen,watermark}
74
74
  sub-command help
75
75
  animate animate an image
76
76
  auto auto adjust (resize, contrast, and watermark) a photo
77
77
  border add border to image
78
78
  contrast contrast an image
79
+ env print environment information for bug reporting
79
80
  info info an image
81
+ montage montage a list of image
80
82
  resize resize an image
81
83
  rotate rotate an image
82
- montage montage a list of image
83
84
  sharpen sharpen an image
84
85
  watermark watermark an image
85
- env print environment information for bug reporting
86
86
 
87
87
  options:
88
88
  -h, --help show this help message and exit
@@ -206,7 +206,7 @@ fotolab info -h
206
206
  <!--help-info !-->
207
207
 
208
208
  ```console
209
- usage: fotolab info [-h] [-s] [--camera] IMAGE_FILENAME
209
+ usage: fotolab info [-h] [-s] [--camera] [--datetime] IMAGE_FILENAME
210
210
 
211
211
  positional arguments:
212
212
  IMAGE_FILENAME set the image filename
@@ -215,6 +215,7 @@ options:
215
215
  -h, --help show this help message and exit
216
216
  -s, --sort show image info by sorted field name
217
217
  --camera show the camera maker details
218
+ --datetime show the datetime
218
219
  ```
219
220
 
220
221
  <!--help-info !-->
@@ -228,13 +229,17 @@ fotolab rotate -h
228
229
  <!--help-rotate !-->
229
230
 
230
231
  ```console
231
- usage: fotolab rotate [-h] IMAGE_FILENAMES [IMAGE_FILENAMES ...]
232
+ usage: fotolab rotate [-h] [-r ROTATION] [-cw]
233
+ IMAGE_FILENAMES [IMAGE_FILENAMES ...]
232
234
 
233
235
  positional arguments:
234
- IMAGE_FILENAMES set the image filenames
236
+ IMAGE_FILENAMES set the image filenames
235
237
 
236
238
  options:
237
- -h, --help show this help message and exit
239
+ -h, --help show this help message and exit
240
+ -r, --rotation ROTATION
241
+ Rotation angle in degrees (default: '0')
242
+ -cw, --clockwise Rotate clockwise (default: 'False)
238
243
  ```
239
244
 
240
245
  <!--help-rotate !-->
@@ -0,0 +1,20 @@
1
+ fotolab/__init__.py,sha256=xMgr0qY4XC9waGeCumA3xk1SjuwsIC0tuMY3k_u0suU,2061
2
+ fotolab/__main__.py,sha256=aboOURPs_snOXTEWYR0q8oq1UTY9e-NxCd1j33V0wHI,833
3
+ fotolab/cli.py,sha256=vUS7eVcwEy2CWn4g5wTujLXS_GAzug9Np7j8-sM6GHU,4321
4
+ fotolab/subcommands/__init__.py,sha256=5ncuu_LxuWphjNohm32Z_lD0FmJPWrAynBzeza0lyCU,1124
5
+ fotolab/subcommands/animate.py,sha256=ejimhTozo9DN7BbqqcV4x8zLnanZRKq1pxBBFeOdr6Q,2967
6
+ fotolab/subcommands/auto.py,sha256=ljvC0Z88Dva18YHyn3auAjlvVHqGrxsky-HGXwaIMZo,2391
7
+ fotolab/subcommands/border.py,sha256=5ch2d7LVPhB2OFuuXSW5ci6Cn967CPDQu0qSfaO7uMg,3591
8
+ fotolab/subcommands/contrast.py,sha256=l7Bs5p8W8ypN9Cg3fFHnU-A20UwMKtjTiPk6D0PRwpM,2095
9
+ fotolab/subcommands/env.py,sha256=6vQDQ1sunJl2XBaSAHpLZUOiKiHy4STO45CMjXJd_Ow,1466
10
+ fotolab/subcommands/info.py,sha256=vDzomRfHOKCNk9TON5CX9UB_km6GYBJT_nkDcwtARVA,3330
11
+ fotolab/subcommands/montage.py,sha256=lUVY-zDSH7mwH-s34_XefdNp7CoDJHkwpbTUGiyJGgs,2037
12
+ fotolab/subcommands/resize.py,sha256=2bH1Kgoe_DqU8ozJ1E_oA6a9JPtuwIlo5a4sq_4Yles,5018
13
+ fotolab/subcommands/rotate.py,sha256=27Xrc8iV9n8WI6tOE-vBBMUeSYu2CtZAh-PGcF7JL-k,2144
14
+ fotolab/subcommands/sharpen.py,sha256=wUPtJdtB6mCRmcHrA0CoEVO0O0ROBJWhejTvUeL67QU,2655
15
+ fotolab/subcommands/watermark.py,sha256=5vsMDeJZhLT5qmftJQXY4jK2gtPQsbTYKXA6Ye2MqmA,8900
16
+ fotolab-0.24.0.dist-info/entry_points.txt,sha256=mvw7AY_yZkIyjAxPtHNed9X99NZeLnMxEeAfEJUbrCM,44
17
+ fotolab-0.24.0.dist-info/LICENSE.md,sha256=tGtFDwxWTjuR9syrJoSv1Hiffd2u8Tu8cYClfrXS_YU,31956
18
+ fotolab-0.24.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
19
+ fotolab-0.24.0.dist-info/METADATA,sha256=VH-jJMrow1yoLw1XXO4eVgl1putj9tJuLFd93Tg9MSE,10975
20
+ fotolab-0.24.0.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- fotolab/__init__.py,sha256=pw2AneRyAlWbI9FH4Ikuepdam_hEWBbqqmTUXny6WP4,2061
2
- fotolab/__main__.py,sha256=aboOURPs_snOXTEWYR0q8oq1UTY9e-NxCd1j33V0wHI,833
3
- fotolab/animate.py,sha256=ejimhTozo9DN7BbqqcV4x8zLnanZRKq1pxBBFeOdr6Q,2967
4
- fotolab/auto.py,sha256=l_-Kf5V5Anvwz1QV1ET-42YsDWEeHf_okHkXWOycWAI,2295
5
- fotolab/border.py,sha256=5ch2d7LVPhB2OFuuXSW5ci6Cn967CPDQu0qSfaO7uMg,3591
6
- fotolab/cli.py,sha256=FBFSeMNqcOiJ6MuAcy0qUvc9cscdFUG946HlWZXBPtY,4984
7
- fotolab/contrast.py,sha256=l7Bs5p8W8ypN9Cg3fFHnU-A20UwMKtjTiPk6D0PRwpM,2095
8
- fotolab/env.py,sha256=fzUoRWgYEiYJIWYEiiSLEb7dH_xVUOnhMpQgc1yjrTY,1457
9
- fotolab/info.py,sha256=jIWmsDAk8ikKb3uZ568DHu3m21OW2-1Erg9PRYa4giw,3281
10
- fotolab/montage.py,sha256=lUVY-zDSH7mwH-s34_XefdNp7CoDJHkwpbTUGiyJGgs,2037
11
- fotolab/resize.py,sha256=2bH1Kgoe_DqU8ozJ1E_oA6a9JPtuwIlo5a4sq_4Yles,5018
12
- fotolab/rotate.py,sha256=l_vQgf0IcI8AR1TSVsk4PrMZtJ3j_wpU77rKiGJ-KTA,1715
13
- fotolab/sharpen.py,sha256=wUPtJdtB6mCRmcHrA0CoEVO0O0ROBJWhejTvUeL67QU,2655
14
- fotolab/watermark.py,sha256=XuVj-Cg1duNtW1Z9S8Y0AN4Q268lTM3dne5HnzLWgj0,7252
15
- fotolab-0.22.0.dist-info/entry_points.txt,sha256=mvw7AY_yZkIyjAxPtHNed9X99NZeLnMxEeAfEJUbrCM,44
16
- fotolab-0.22.0.dist-info/LICENSE.md,sha256=tGtFDwxWTjuR9syrJoSv1Hiffd2u8Tu8cYClfrXS_YU,31956
17
- fotolab-0.22.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
18
- fotolab-0.22.0.dist-info/METADATA,sha256=4pqzWJZoXD0c7sSRQM5RrSlC6HuEFFf9o2K-Yx6C_m0,10724
19
- fotolab-0.22.0.dist-info/RECORD,,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes