convert-stream 1.2.1__tar.gz → 1.2.3__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.

Potentially problematic release.


This version of convert-stream might be problematic. Click here for more details.

Files changed (26) hide show
  1. {convert_stream-1.2.1 → convert_stream-1.2.3}/PKG-INFO +1 -1
  2. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/__main__.py +11 -8
  3. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/_version.py +3 -3
  4. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/convert.py +51 -9
  5. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/imagelib.py +100 -60
  6. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/models/m_text.py +2 -2
  7. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/models/models_image.py +3 -1
  8. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/models/models_pdf.py +96 -105
  9. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/pdf_document.py +20 -13
  10. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/pdf_page.py +74 -57
  11. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/progress/progress_bar.py +8 -7
  12. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream.egg-info/PKG-INFO +1 -1
  13. {convert_stream-1.2.1 → convert_stream-1.2.3}/pyproject.toml +1 -1
  14. {convert_stream-1.2.1 → convert_stream-1.2.3}/README.md +0 -0
  15. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/__init__.py +0 -0
  16. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/image_to_pdf.py +0 -0
  17. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/models/__init__.py +0 -0
  18. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/pdf_compress.py +0 -0
  19. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/pdf_to_images.py +0 -0
  20. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/progress/__init__.py +0 -0
  21. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream/text.py +0 -0
  22. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream.egg-info/SOURCES.txt +0 -0
  23. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream.egg-info/dependency_links.txt +0 -0
  24. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream.egg-info/requires.txt +0 -0
  25. {convert_stream-1.2.1 → convert_stream-1.2.3}/convert_stream.egg-info/top_level.txt +0 -0
  26. {convert_stream-1.2.1 → convert_stream-1.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: convert_stream
3
- Version: 1.2.1
3
+ Version: 1.2.3
4
4
  Summary: Manipulação de documentos PDF e Imagens. Converter, Ler, rotacionar exportar etc.
5
5
  Author: Bruno Chaves
6
6
  Requires-Python: >=3.11
@@ -24,14 +24,17 @@ from convert_stream import (
24
24
 
25
25
 
26
26
  def test():
27
- from convert_stream import FileDocumentPdf, CompressDocument, DocumentPdf
28
- from soup_files import Directory, File, UserFileSystem
29
-
30
- outdir = UserFileSystem().userDownloads.concat('output', create=True)
31
- base = UserFileSystem().userDownloads.join_file('base.pdf')
32
-
33
- comp = CompressDocument.create_from_file(base, dpi=100)
34
- comp.to_file(outdir.join_file('teste.pdf'))
27
+ from convert_stream import (
28
+ FileDocumentPdf, CompressDocument, DocumentPdf, LibraryPDF,
29
+ PdfStream, ImageStream, PageDocumentPdf,
30
+ )
31
+ from soup_files import (
32
+ Directory, File, UserFileSystem, InputFiles
33
+ )
34
+
35
+ outdir: Directory = UserFileSystem().userDownloads.concat('output', create=True)
36
+ indir: Directory = UserFileSystem().userDownloads.concat('TESTE-PDF', create=True)
37
+ base = indir.join_file('base.pdf')
35
38
 
36
39
 
37
40
  def main():
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
- __version__ = '1.2.1'
3
+ __version__ = '1.2.3'
4
4
  __author__ = 'Bruno Chaves'
5
5
  __license__ = 'MIT'
6
- __module_name__ = 'pdflib'
7
- __modify_date__ = '2025-06-21'
6
+ __module_name__ = 'convert_stream'
7
+ __modify_date__ = '2025-06-28'
@@ -81,12 +81,14 @@ class PdfStream(ABCDocumentStream):
81
81
  print(f'{__class__.__name__} Erro: Use lista de PageDocumentPdf, não {type(pages)}')
82
82
  return
83
83
  max_num: int = len(pages)
84
+ self.progress_bar.start()
84
85
  for num, p in enumerate(pages):
85
86
  self.progress_bar.update(
86
87
  ((num+1)/max_num) * 100,
87
88
  f'Adicionando página: [{num+1} de {max_num}]'
88
89
  )
89
90
  self.add_page(p)
91
+ self.progress_bar.stop()
90
92
 
91
93
  def add_page_pdf_bytes(self, bt: bytes):
92
94
  _page = PageDocumentPdf.create_from_page_bytes(bt, library=self.library_pdf)
@@ -107,6 +109,17 @@ class PdfStream(ABCDocumentStream):
107
109
  def add_image(self, image: ImageObject):
108
110
  pg: PageDocumentPdf = self.convert_images_to_pdf.from_image(image)
109
111
  self.add_page(pg)
112
+
113
+ def add_images(self, images: List[ImageObject]):
114
+ maxnum: int = len(images)
115
+ self.progress_bar.start()
116
+ for n, img in enumerate(images):
117
+ self.progress_bar.update(
118
+ ((n+1)/maxnum) * 100,
119
+ f'Adicionando imagens ao documento: [{n+1}] de {maxnum}'
120
+ )
121
+ self.add_image(img)
122
+ self.progress_bar.stop()
110
123
 
111
124
  def to_file_pdf(self, f: File):
112
125
  if self.is_null():
@@ -196,22 +209,25 @@ class PdfStream(ABCDocumentStream):
196
209
  class ImageStream(object):
197
210
  def __init__(
198
211
  self, *,
199
- library_image = LibraryImage.OPENCV,
200
- library_pdf = LibraryPDF.PILPDF,
212
+ library_image: LibraryImage = LibraryImage.OPENCV,
213
+ library_pdf: LibraryPDF = LibraryPDF.PILPDF,
201
214
  progress_bar: ProgressBarAdapter = ProgressBarAdapter(ProgressBarSimple()),
202
215
  ):
203
216
  self.__images: List[ImageObject] = []
204
217
  self.num_images: int = len(self.__images)
205
- self.progress_bar: ProgressBarAdapter = progress_bar
218
+ #self.progress_bar: ProgressBarAdapter = progress_bar
206
219
  self.library_image: LibraryImage = library_image
207
220
  self.library_pdf: LibraryPDF = library_pdf
221
+
208
222
  # Objeto para converter IMAGEM em pdf.
209
223
  if library_pdf == LibraryPDF.FITZ:
210
- self.convert_image_to_pdf:ImageConvertPdf = ImageConvertPdf.create_from_fitz(library_image)
224
+ self.convert_image_to_pdf: ImageConvertPdf = ImageConvertPdf.create_from_fitz(library_image)
211
225
  elif library_pdf == LibraryPDF.CANVAS:
212
226
  self.convert_image_to_pdf: ImageConvertPdf = ImageConvertPdf.create_from_canvas(library_image)
213
227
  elif library_pdf == LibraryPDF.PILPDF:
214
228
  self.convert_image_to_pdf: ImageConvertPdf = ImageConvertPdf.create_from_pil(library_image)
229
+ else:
230
+ raise ValueError(f'PYPDF não pode converter imagens em PDF!!')
215
231
 
216
232
  # Objeto para converter PDF em imagem
217
233
  self.convert_pdf_to_images: ConvertPdfToImage = ConvertPdfToImage(
@@ -220,6 +236,10 @@ class ImageStream(object):
220
236
  dpi=150,
221
237
  progress_bar=progress_bar,
222
238
  )
239
+
240
+ @property
241
+ def progress_bar(self) -> ProgressBarAdapter:
242
+ return self.convert_pdf_to_images.pbar
223
243
 
224
244
  @property
225
245
  def images(self) -> List[ImageObject]:
@@ -237,20 +257,33 @@ class ImageStream(object):
237
257
  def add_image(self, img: ImageObject) -> None:
238
258
  if not isinstance(img, ImageObject):
239
259
  return
240
- print(f'{__class__.__name__} Adicionando imagem {self.num_images+1}')
241
260
  self.__images.append(img)
242
261
  self.num_images += 1
243
262
 
244
263
  def add_images(self, images: List[ImageObject]):
245
- for i in images:
264
+ maxnum: int = len(images)
265
+ self.progress_bar.start()
266
+ for num, i in enumerate(images):
267
+ self.progress_bar.update(
268
+ ((num+1)/maxnum) * 100,
269
+ f'Adicionando imagens: [{num+1} de {maxnum}]'
270
+ )
246
271
  self.add_image(i)
272
+ self.progress_bar.stop()
247
273
 
248
- def add_file_image(self, f:File):
274
+ def add_file_image(self, f: File):
249
275
  img = ImageObject.create_from_file(f, library=self.library_image)
250
276
  self.add_image(img)
251
277
 
252
278
  def add_files_image(self, files: List[File]):
253
- for f in files:
279
+ maxnum: int = len(files)
280
+ for num, f in enumerate(files):
281
+ p = ((num + 1) / maxnum) * 100
282
+ #print(p)
283
+ self.progress_bar.update(
284
+ p,
285
+ f'Adicionando arquivos: [{num + 1} de {maxnum}]'
286
+ )
254
287
  self.add_file_image(f)
255
288
 
256
289
  def is_null(self) -> bool:
@@ -264,6 +297,7 @@ class ImageStream(object):
264
297
 
265
298
  def to_files_image(self, d: Directory, prefix: str = 'imagem'):
266
299
  d.mkdir()
300
+ self.progress_bar.start()
267
301
  for num, image in enumerate(self.images):
268
302
  filename = f'{prefix}-{num+1}.png'
269
303
  self.progress_bar.update(
@@ -271,14 +305,22 @@ class ImageStream(object):
271
305
  f'Exportando imagem: {num+1} de {self.num_images} [{filename}]'
272
306
  )
273
307
  image.to_file(d.join_file(filename))
308
+ self.progress_bar.stop()
274
309
 
275
310
  def to_pages_pdf(self) -> List[PageDocumentPdf]:
276
311
  if self.is_null():
277
312
  print(f'{__class__.__name__} Adicione imagens para prosseguir!')
278
313
  return []
279
314
  new_pages = []
280
- for image in self.images:
315
+ maxnum: int = len(self.images)
316
+ self.progress_bar.start()
317
+ for num, image in enumerate(self.images):
318
+ self.progress_bar.update(
319
+ ((num + 1) / maxnum) * 100,
320
+ f'Adicionando imagens: [{num + 1} de {maxnum}]'
321
+ )
281
322
  new_pages.append(self.convert_image_to_pdf.from_image(image))
323
+ self.progress_bar.stop()
282
324
  return new_pages
283
325
 
284
326
  def set_paisagem(self):
@@ -7,41 +7,48 @@ from PIL import Image, ImageOps, ImageFilter
7
7
  from cv2.typing import MatLike
8
8
  import cv2
9
9
  import numpy as np
10
-
11
10
  from soup_files import File
12
11
  from convert_stream.models.models_image import LibraryImage, ABCImageObject, ABCInvertColor
13
12
 
14
13
  #=============================================================================#
15
14
  # Inverter cores em imagens
16
15
  #=============================================================================#
16
+
17
+
17
18
  class ImplementInvertColorOpenCv(ABCInvertColor):
18
19
  """
19
20
  Escurecer texto em imagens.
20
21
  """
21
- def __init__(self, image_bytes):
22
+ def __init__(self, image_bytes: bytes):
22
23
  super().__init__()
23
24
  if not isinstance(image_bytes, bytes):
24
25
  raise ValueError(f'{__class__.__name__}\nUse: bytes, não {type(image_bytes)}')
25
- self.image_bytes:bytes = image_bytes
26
+ self.image_bytes: bytes = image_bytes
26
27
 
27
- def _image_bytes_to_opencv(self, img_bytes:bytes) -> MatLike:
28
+ def _image_bytes_to_opencv(self, img_bytes: bytes) -> MatLike:
28
29
  """Converte os bytes de uma imagem em objeto opencv MatLike"""
29
30
  nparr = np.frombuffer(img_bytes, np.uint8)
30
31
  return cv2.imdecode(nparr, cv2.IMREAD_GRAYSCALE)
31
32
 
32
33
  def _image_opencv_tobytes(self, img:MatLike) -> bytes:
33
34
  """Convert um objeto opencv MatLike em bytes de imagem"""
34
- _, buffer = cv2.imencode('.png', img) # Codifica como PNG (ou use '.jpg' para JPEG)
35
- return buffer.tobytes() # Obtém os bytes da imagem
35
+ # Codifica como PNG (ou use '.jpg' para JPEG)
36
+ _, buffer = cv2.imencode('.png', img)
37
+ # Obtém os bytes da imagem
38
+ return buffer.tobytes()
36
39
 
37
40
  def set_gaussian_blur(self):
38
41
  # Aplica um filtro Gaussiano para reduzir o ruído
39
- _blurred:MatLike = cv2.GaussianBlur(self._image_bytes_to_opencv(self.image_bytes), (3, 3), 0.5)
42
+ _blurred: MatLike = cv2.GaussianBlur(
43
+ self._image_bytes_to_opencv(self.image_bytes),
44
+ (3, 3),
45
+ 0.5
46
+ )
40
47
  self.image_bytes = self._image_opencv_tobytes(_blurred)
41
48
 
42
- def __set_black(self):
49
+ def __set_background_black(self):
43
50
  # Aplica binarização adaptativa (texto branco, fundo preto)
44
- binary:MatLike = cv2.adaptiveThreshold(
51
+ binary: MatLike = cv2.adaptiveThreshold(
45
52
  self._image_bytes_to_opencv(self.image_bytes),
46
53
  150,
47
54
  cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
@@ -51,7 +58,7 @@ class ImplementInvertColorOpenCv(ABCInvertColor):
51
58
  )
52
59
  self.image_bytes = self._image_opencv_tobytes(binary)
53
60
 
54
- def __set_gray(self):
61
+ def __set_background_gray(self):
55
62
  # Aplica binarização adaptativa (texto preto, fundo branco)
56
63
  _binary = cv2.adaptiveThreshold(
57
64
  self._image_bytes_to_opencv(self.image_bytes),
@@ -65,17 +72,17 @@ class ImplementInvertColorOpenCv(ABCInvertColor):
65
72
 
66
73
  def set_background_black(self):
67
74
  self.set_gaussian_blur()
68
- self.__set_black()
69
- _result:MatLike = cv2.bitwise_not(self._image_bytes_to_opencv(self.image_bytes))
75
+ self.__set_background_black()
76
+ _result: MatLike = cv2.bitwise_not(self._image_bytes_to_opencv(self.image_bytes))
70
77
  self.image_bytes = self._image_opencv_tobytes(_result)
71
78
 
72
79
  def set_background_gray(self):
73
80
  self.set_gaussian_blur()
74
- self.__set_gray()
75
- _result:MatLike = cv2.bitwise_not(self._image_bytes_to_opencv(self.image_bytes))
81
+ self.__set_background_gray()
82
+ _result: MatLike = cv2.bitwise_not(self._image_bytes_to_opencv(self.image_bytes))
76
83
  self.image_bytes = self._image_opencv_tobytes(_result)
77
84
 
78
- def to_file(self, output_path:File):
85
+ def to_file(self, output_path: File):
79
86
  print(f'Salvando: {output_path.basename()}')
80
87
  cv2.imwrite(output_path.absolute(), self._image_bytes_to_opencv(self.image_bytes))
81
88
 
@@ -99,12 +106,12 @@ class ImplementInvertColorPIL(ABCInvertColor):
99
106
  super().__init__()
100
107
  if not isinstance(image_bytes, bytes):
101
108
  raise ValueError(f'{__class__.__name__}\nUse: bytes, não {type(image_bytes)}')
102
- self.image_bytes:bytes = image_bytes
109
+ self.image_bytes: bytes = image_bytes
103
110
 
104
111
  def set_gaussian_blur(self):
105
112
  img_pil = Image.open(BytesIO(self.image_bytes))
106
113
  # Aplicar um desfoque semelhante ao cv2.GaussianBlur
107
- blurred:Image.Image = img_pil.filter(ImageFilter.GaussianBlur(radius=1))
114
+ blurred: Image.Image = img_pil.filter(ImageFilter.GaussianBlur(radius=1))
108
115
  buff = BytesIO()
109
116
  blurred.save(buff, 'PNG')
110
117
  self.image_bytes = buff.getvalue()
@@ -139,7 +146,7 @@ class ImplementInvertColorPIL(ABCInvertColor):
139
146
  buff.close()
140
147
 
141
148
  def to_file(self, output_path: File):
142
- img:Image.Image = Image.open(self.image_bytes)
149
+ img: Image.Image = Image.open(self.image_bytes)
143
150
  img.save(output_path.absolute(), 'PNG')
144
151
 
145
152
  def to_bytes(self) -> bytes:
@@ -157,7 +164,7 @@ class ImageInverColor(ABCInvertColor):
157
164
  """
158
165
  Escurecer texto em imagens.
159
166
  """
160
- def __init__(self, invert_color:ABCInvertColor):
167
+ def __init__(self, invert_color: ABCInvertColor):
161
168
  if not isinstance(invert_color, ABCInvertColor):
162
169
  raise ValueError(f'{__class__.__name__}\nUse: ABCInvertColor(), não {type(invert_color)}')
163
170
  self.invert_color:ABCInvertColor = invert_color
@@ -178,12 +185,15 @@ class ImageInverColor(ABCInvertColor):
178
185
  return self.invert_color.to_bytes()
179
186
 
180
187
  @classmethod
181
- def create_from_file(cls, f:File, *, library:LibraryImage=LibraryImage.OPENCV) -> 'ImageInverColor':
182
- img:ImageObject = ImageObject.create_from_file(f, library=library)
188
+ def create_from_file(
189
+ cls, f: File, *,
190
+ library: LibraryImage = LibraryImage.OPENCV
191
+ ) -> ImageInverColor:
192
+ img: ImageObject = ImageObject.create_from_file(f, library=library)
183
193
  if library == LibraryImage.OPENCV:
184
- inv:ABCInvertColor = ImplementInvertColorOpenCv(f.path.read_bytes())
194
+ inv: ABCInvertColor = ImplementInvertColorOpenCv(f.path.read_bytes())
185
195
  elif library == LibraryImage.PIL:
186
- inv:ABCInvertColor = ImplementInvertColorPIL(f.path.read_bytes())
196
+ inv: ABCInvertColor = ImplementInvertColorPIL(f.path.read_bytes())
187
197
  else:
188
198
  raise NotImplementedError(
189
199
  f'{__class__.__name__}\nUse: LibraryImage.OPENCV, ou LibraryImage.PIL, não {type(library)}'
@@ -191,49 +201,60 @@ class ImageInverColor(ABCInvertColor):
191
201
  return cls(inv)
192
202
 
193
203
  @classmethod
194
- def create_from_bytes(cls, bt:bytes, *, library:LibraryImage=LibraryImage.OPENCV) -> 'ImageInverColor':
204
+ def create_from_bytes(
205
+ cls, bt: bytes, *,
206
+ library: LibraryImage = LibraryImage.OPENCV
207
+ ) -> ImageInverColor:
195
208
  if library == LibraryImage.OPENCV:
196
- invert_color:ABCInvertColor = ImplementInvertColorOpenCv(bt)
209
+ invert_color: ABCInvertColor = ImplementInvertColorOpenCv(bt)
197
210
  elif library == LibraryImage.PIL:
198
- invert_color:ABCInvertColor = ImplementInvertColorPIL(bt)
211
+ invert_color: ABCInvertColor = ImplementInvertColorPIL(bt)
199
212
  else:
200
213
  raise NotImplementedError(
201
214
  f'{__class__.__name__}\nUse: LibraryImage.OPENCV, ou LibraryImage.PIL, não {type(library)}'
202
215
  )
203
216
  return cls(invert_color)
204
217
 
218
+
205
219
  #=============================================================================#
206
220
  # Manipulação de imagens
207
221
  #=============================================================================#
222
+
223
+
208
224
  class ImageObjectPIL(ABCImageObject):
209
225
  """
210
226
  Implementação de ImageObject usando PIL.
211
227
  """
212
- def __init__(self, image_bytes:bytes):
228
+ def __init__(self, image_bytes: bytes):
213
229
  super().__init__()
214
230
  if not isinstance(image_bytes, bytes):
215
231
  raise ValueError(f'{__class__.__name__} Use: bytes, não {type(image_bytes)}')
216
232
  self.current_library = LibraryImage.PIL
217
233
  self.image_bytes = image_bytes
218
- self.max_size:Tuple[int, int] = (1980, 720) # Dimensões máximas, altere se necessário.
234
+ self.max_size: Tuple[int, int] = (1980, 720) # Dimensões máximas, altere se necessário.
219
235
 
220
236
  try:
221
- img = Image.open(BytesIO(image_bytes))
237
+ image_pil = Image.open(BytesIO(image_bytes))
222
238
  except Exception as e:
223
239
  raise ValueError(f"{__class__.__name__}\nPIL: Bytes de imagem inválidos")
224
240
  else:
225
- self.dimensions = img.size
226
-
227
- # Redimensionar, se as dimensões forem maior que self.max_size.
228
- if img.width > self.max_size[0] or img.height > self.max_size[1]:
229
- #img.thumbnail(self.max_size, Image.LANCZOS) # Mantém proporção
230
- buff_image:BytesIO = BytesIO()
231
- img.save(buff_image, format='PNG', optimize=True, quality=80)
241
+ self.dimensions = image_pil.size
242
+
243
+ # Redimensionar, se as dimensões estiverem maior que self.max_size.
244
+ if (image_pil.width > self.max_size[0]) or (image_pil.height > self.max_size[1]):
245
+ #print(f'{__class__.__name__} Redimensionando imagem [{self.dimensions}], aguarde...')
246
+ buff_image: BytesIO = BytesIO()
247
+ image_pil.save(buff_image, format='PNG', optimize=True, quality=80)
248
+ image_pil = Image.open(buff_image)
249
+ self.dimensions = image_pil.size
232
250
  self.image_bytes = buff_image.getvalue()
233
- self.dimensions = img.size
234
251
  buff_image.seek(0)
235
252
  buff_image.close()
236
- del img
253
+ del buff_image
254
+ #print(f'{__class__.__name__} Nova dimensão [{self.dimensions}]')
255
+ self.width = image_pil.width
256
+ self.height = image_pil.height
257
+ del image_pil
237
258
 
238
259
  def is_paisagem(self) -> bool:
239
260
  """
@@ -264,10 +285,10 @@ class ImageObjectPIL(ABCImageObject):
264
285
  img.save(new_bytes, format='PNG')
265
286
  self.image_bytes = new_bytes.getvalue()
266
287
 
267
- def set_optimize(self):
288
+ def set_optimize(self, quality: int = 70):
268
289
  optimized_bytes = BytesIO()
269
290
  img = self.to_image_pil()
270
- img.save(optimized_bytes, format='PNG', optimize=True, quality=70)
291
+ img.save(optimized_bytes, format='PNG', optimize=True, quality=quality)
271
292
  self.image_bytes = optimized_bytes.getvalue()
272
293
 
273
294
  def set_background_black(self):
@@ -306,6 +327,7 @@ class ImageObjectPIL(ABCImageObject):
306
327
  except Exception as e:
307
328
  raise Exception(f"Erro ao salvar imagem PIL: {e}")
308
329
 
330
+
309
331
  class ImageObjectOpenCV(ABCImageObject):
310
332
  """
311
333
  Implementação de ImageObject usando OpenCV.
@@ -316,11 +338,11 @@ class ImageObjectOpenCV(ABCImageObject):
316
338
  raise ValueError(f'{__class__.__name__} Use: bytes, não {type(image_bytes)}')
317
339
  self.current_library = LibraryImage.OPENCV
318
340
  self.image_bytes = image_bytes
319
- self.max_size:Tuple[int, int] = (1980, 720)
341
+ self.max_size: Tuple[int, int] = (1980, 720)
320
342
 
321
343
  try:
322
344
  nparr = np.frombuffer(self.image_bytes, np.uint8)
323
- image_opencv:MatLike = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
345
+ image_opencv: MatLike = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
324
346
  except Exception as e:
325
347
  print('-' * 80)
326
348
  raise ValueError(f"\nCV2: Bytes de imagem OpenCV inválidos")
@@ -328,16 +350,20 @@ class ImageObjectOpenCV(ABCImageObject):
328
350
  self.dimensions = (image_opencv.shape[1], image_opencv.shape[0]) # (largura, altura)
329
351
 
330
352
  # Redimensionar se necessário
331
- if self.dimensions[0] > self.max_size[0] or self.dimensions[1] > self.max_size[1]:
353
+ if (self.dimensions[0] > self.max_size[0]) or (self.dimensions[1] > self.max_size[1]):
354
+ #print(f'{__class__.__name__} Redimensionando imagem [{self.dimensions}], aguarde...')
332
355
  h, w = image_opencv.shape[:2]
333
356
  scale = min(self.max_size[0] / w, self.max_size[1] / h)
334
357
  new_size = (int(w * scale), int(h * scale))
335
- image_opencv = cv2.resize(image_opencv, new_size, interpolation=cv2.INTER_LANCZOS4)
358
+ image_opencv = cv2.resize(image_opencv, new_size, interpolation=cv2.INTER_LANCZOS4)
359
+ self.dimensions = (image_opencv.shape[1], image_opencv.shape[0])
360
+ #print(f'{__class__.__name__} Nova dimensão [{self.dimensions}]')
336
361
  # Converter a imagem de volta para bytes
337
362
  _, encoded_img = cv2.imencode('.png', image_opencv)
338
363
  self.image_bytes = encoded_img.tobytes()
339
- # Atualizar dimensões após redimensionamento
340
- self.dimensions = (image_opencv.shape[1], image_opencv.shape[0])
364
+ # Atualizar dimensões após redução
365
+ self.height = self.dimensions[0]
366
+ self.width = self.dimensions[1]
341
367
 
342
368
  def is_paisagem(self) -> bool:
343
369
  """
@@ -372,12 +398,12 @@ class ImageObjectOpenCV(ABCImageObject):
372
398
  if success:
373
399
  self.image_bytes = encoded_image.tobytes()
374
400
 
375
- def set_optimize(self):
401
+ def set_optimize(self, quality: int = 70):
376
402
  """
377
403
  Reduz o tamanho da imagem, e salva a imagen reduzida na propriedade bytes.
378
404
  """
379
- imagem:MatLike = self.to_image_opencv()
380
- _status, buffer = cv2.imencode(".png", imagem, [cv2.IMWRITE_JPEG_QUALITY, 80])
405
+ imagem: MatLike = self.to_image_opencv()
406
+ _status, buffer = cv2.imencode(".png", imagem, [cv2.IMWRITE_JPEG_QUALITY, quality])
381
407
  if _status:
382
408
  self.image_bytes = buffer.tobytes()
383
409
 
@@ -416,20 +442,29 @@ class ImageObjectOpenCV(ABCImageObject):
416
442
  except Exception as e:
417
443
  raise Exception(f"Erro ao salvar imagem OpenCV: {e}")
418
444
 
419
- class ImageObject(ABCImageObject):
445
+
446
+ class ImageObject(object):
420
447
  """
421
448
  Facade para manipular imagens com PIL ou OPENCV
422
449
  """
423
- def __init__(self, img_obj:ABCImageObject):
450
+ def __init__(self, img_obj: ABCImageObject):
424
451
  super().__init__()
425
- self.img_obj:ABCImageObject = img_obj
426
- self.current_library:LibraryImage = self.img_obj.current_library
427
-
428
- def set_optimize(self):
452
+ self.img_obj: ABCImageObject = img_obj
453
+ self.current_library: LibraryImage = self.img_obj.current_library
454
+
455
+ @property
456
+ def width(self) -> int:
457
+ return self.img_obj.width
458
+
459
+ @property
460
+ def height(self) -> int:
461
+ return self.img_obj.height
462
+
463
+ def set_optimize(self, quality: int = 70):
429
464
  """
430
465
  Reduz o tamanho da imagem, e salva a imagen reduzida na propriedade bytes.
431
466
  """
432
- self.img_obj.set_optimize()
467
+ self.img_obj.set_optimize(quality)
433
468
 
434
469
  def set_rotation(self, rotation: LibraryImage = LibraryImage.ROTATION_180):
435
470
  self.img_obj.set_rotation(rotation)
@@ -452,7 +487,7 @@ class ImageObject(ABCImageObject):
452
487
  def to_image_opencv(self) -> cv2.typing.MatLike:
453
488
  return self.img_obj.to_image_opencv()
454
489
 
455
- def to_file(self, f:File):
490
+ def to_file(self, f: File):
456
491
  self.img_obj.to_file(f.absolute())
457
492
 
458
493
  def is_paisagem(self) -> bool:
@@ -462,7 +497,10 @@ class ImageObject(ABCImageObject):
462
497
  return self.img_obj.to_bytes()
463
498
 
464
499
  @classmethod
465
- def create_from_bytes(cls, image_bytes: bytes, *, library:LibraryImage=LibraryImage.OPENCV) -> 'ImageObject':
500
+ def create_from_bytes(
501
+ cls, image_bytes: bytes, *,
502
+ library: LibraryImage = LibraryImage.OPENCV
503
+ ) -> ImageObject:
466
504
  if library == LibraryImage.PIL:
467
505
  img = ImageObjectPIL(image_bytes)
468
506
  elif library == LibraryImage.OPENCV:
@@ -472,8 +510,10 @@ class ImageObject(ABCImageObject):
472
510
  return cls(img)
473
511
 
474
512
  @classmethod
475
- def create_from_file(cls, filepath: File, *, library:LibraryImage=LibraryImage.OPENCV) -> 'ImageObject':
476
- print(f'Obtendo imagem do arquivo: {filepath.basename()}')
513
+ def create_from_file(
514
+ cls, filepath: File, *,
515
+ library: LibraryImage = LibraryImage.OPENCV
516
+ ) -> ImageObject:
477
517
  try:
478
518
  if library == LibraryImage.PIL:
479
519
  image = ImageObjectPIL(filepath.path.read_bytes())
@@ -371,7 +371,7 @@ class DataString(object):
371
371
 
372
372
 
373
373
  class ArrayString(object):
374
- def __init__(self, values:List[str]):
374
+ def __init__(self, values: List[str]):
375
375
  self.values = values
376
376
  self.length = len(self.values)
377
377
 
@@ -381,7 +381,7 @@ class ArrayString(object):
381
381
  self.values.append(item)
382
382
  self.length += 1
383
383
 
384
- def add_all(self, items:List[str]):
384
+ def add_all(self, items: List[str]):
385
385
  for i in items:
386
386
  self.add(i)
387
387
 
@@ -47,6 +47,8 @@ class ABCImageObject(ABC):
47
47
  def __init__(self):
48
48
  super().__init__()
49
49
  self.current_library: LibraryImage = None
50
+ self.width: int = 0
51
+ self.height: int = 0
50
52
 
51
53
  @abstractmethod
52
54
  def is_paisagem(self) -> bool:
@@ -56,7 +58,7 @@ class ABCImageObject(ABC):
56
58
  pass
57
59
 
58
60
  @abstractmethod
59
- def set_optimize(self):
61
+ def set_optimize(self, quality: int = 70):
60
62
  pass
61
63
 
62
64
  @abstractmethod