convert-stream 2.2.7__tar.gz → 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.
Files changed (32) hide show
  1. {convert_stream-2.2.7 → convert_stream-2.3}/PKG-INFO +1 -1
  2. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/__init__.py +10 -0
  3. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/doc_stream.py +51 -18
  4. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/enum_libs/modules.py +19 -19
  5. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/image/img_object.py +92 -85
  6. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/pdf/pdf_document.py +35 -6
  7. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/pdf/pdf_page.py +1 -1
  8. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream.egg-info/PKG-INFO +1 -1
  9. {convert_stream-2.2.7 → convert_stream-2.3}/pyproject.toml +1 -1
  10. {convert_stream-2.2.7 → convert_stream-2.3}/README.md +0 -0
  11. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/__main__.py +0 -0
  12. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/_version.py +0 -0
  13. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/enum_libs/__init__.py +0 -0
  14. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/enum_libs/enums.py +0 -0
  15. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/image/__init__.py +0 -0
  16. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/pdf/__init__.py +0 -0
  17. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/pdf/image_to_pdf.py +0 -0
  18. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/pdf/pdf_to_image.py +0 -0
  19. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/sheets/__init__.py +0 -0
  20. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/sheets/load.py +0 -0
  21. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/sheets/parse.py +0 -0
  22. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/sheets/save.py +0 -0
  23. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/table_files.py +0 -0
  24. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/text/__init__.py +0 -0
  25. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/text/date.py +0 -0
  26. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/text/string.py +0 -0
  27. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream/text/terminal.py +0 -0
  28. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream.egg-info/SOURCES.txt +0 -0
  29. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream.egg-info/dependency_links.txt +0 -0
  30. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream.egg-info/requires.txt +0 -0
  31. {convert_stream-2.2.7 → convert_stream-2.3}/convert_stream.egg-info/top_level.txt +0 -0
  32. {convert_stream-2.2.7 → convert_stream-2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: convert_stream
3
- Version: 2.2.7
3
+ Version: 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
@@ -88,3 +88,13 @@ def read_image(image: Union[str, File, bytes, BytesIO]) -> PdfStream:
88
88
  return _pdf_stream
89
89
 
90
90
 
91
+ def merge(stream: list[PdfStream]) -> PdfStream:
92
+ if len(stream) == 0:
93
+ raise ValueError()
94
+ if len(stream) == 1:
95
+ return stream[0]
96
+ else:
97
+ s = stream[0]
98
+ for pdf_stream in stream[1:]:
99
+ s.add_pages(pdf_stream.to_document().to_pages())
100
+ return s
@@ -8,7 +8,6 @@ from convert_stream.enum_libs.modules import (
8
8
  DEFAULT_LIB_PDF, DEFAULT_LIB_IMAGE, DEFAULT_LIB_PDF_TO_IMG, DEFAULT_LIB_IMAGE_TO_PDF,
9
9
  )
10
10
  from convert_stream.enum_libs.enums import LibPDF, LibImage, LibImageToPdf, LibPdfToImage
11
- from convert_stream.text.string import FindText
12
11
  from convert_stream.pdf.pdf_page import PageDocumentPdf
13
12
  from convert_stream.image.img_object import ImageObject, CollectionImages
14
13
  from convert_stream.pdf.pdf_document import DocumentPdf, CollectionPagePdf
@@ -108,22 +107,43 @@ class PdfStream(object):
108
107
  def add_directory_image(self, src_dir: Directory, max_files: int = 4000) -> None:
109
108
  self.collection_images.add_directory_images(src_dir, max_files=max_files)
110
109
 
111
- def to_document(self) -> DocumentPdf:
110
+ def to_document(self, *, land_scape: bool = False, a4: bool = False) -> DocumentPdf:
111
+ """
112
+
113
+ Converte os documentos adicionados em PdfStream (imagens/pdfs) em
114
+ objeto do tipo DocumentPdf().
115
+ """
112
116
  if self.collection_pages.is_null and self.collection_images.is_empty:
113
117
  raise ValueError('Adicione imagens e/ou documentos para prosseguir!')
118
+
114
119
  _doc = None
115
- if not self.collection_pages.is_null:
116
- _doc = DocumentPdf.create_from_pages(self.collection_pages.pages)
117
120
  if not self.collection_images.is_empty:
118
- _convert_img_to_pdf = ConvertImageToPdf(self.collection_images)
119
- _pages = _convert_img_to_pdf.to_document().to_pages()
120
- self.collection_images.clear()
121
+ _convert = ConvertImageToPdf(self.collection_images, lib_img_to_pdf=self.lib_img_to_pdf)
122
+ _doc: DocumentPdf = _convert.to_document(land_scape, a4=a4)
123
+ if not self.collection_pages.is_null:
121
124
  if _doc is None:
122
- _doc = DocumentPdf.create_from_pages(_pages)
125
+ _doc: DocumentPdf = self.collection_pages.to_document(self.lib_pdf)
123
126
  else:
124
- _doc.add_pages(_pages)
127
+ _doc.add_pages(self.collection_pages.pages)
125
128
  return _doc
126
129
 
130
+ def thresold(self, *, land_scape: bool = False, a4: bool = False, dpi=300) -> DocumentPdf:
131
+ images: list[ImageObject] = self.collection_images.images
132
+ if not self.collection_pages.is_null:
133
+ tmp_doc = DocumentPdf.create_from_pages(self.collection_pages.pages)
134
+ conv_pdf_to_img = ConvertPdfToImages.create(tmp_doc)
135
+ images.extend(conv_pdf_to_img.to_images(dpi=dpi))
136
+ del tmp_doc
137
+ if len(images) == 0:
138
+ raise ValueError('Adicione imagens para prosseguir!')
139
+ if land_scape:
140
+ for img in images:
141
+ img.set_landscape()
142
+ for i in images:
143
+ i.set_threshold_gray()
144
+ conv_img_to_pdf = ConvertImageToPdf(CollectionImages(images), lib_img_to_pdf=self.lib_img_to_pdf)
145
+ return conv_img_to_pdf.to_document(a4=a4)
146
+
127
147
  def to_files_images(
128
148
  self,
129
149
  output_dir: Directory, *,
@@ -134,16 +154,20 @@ class PdfStream(object):
134
154
  if self.collection_pages.is_null and self.collection_images.is_empty:
135
155
  raise ValueError('Adicione imagens e/ou documentos para prosseguir!')
136
156
  self.progress.start()
157
+
158
+ image_stream = ImageStream()
159
+ if not self.collection_images.is_empty:
160
+ image_stream.add_images(self.collection_images.images)
137
161
  if not self.collection_pages.is_null:
138
162
  self.progress.update_text('Processando Imagens')
139
163
  # Criar um documento a partir das páginas
140
164
  doc_pdf = DocumentPdf.create_from_pages(self.collection_pages.pages)
141
165
  convert_pdf_to_img = ConvertPdfToImages.create(doc_pdf)
142
166
  # Converter as páginas PDF em imagens e adicionar a coleção já existe de imagens.
143
- self.collection_images.add_images(convert_pdf_to_img.to_images(dpi=dpi))
144
- self.collection_pages.clear()
167
+ _pdf_to_images: list[ImageObject] = convert_pdf_to_img.to_images(dpi=dpi)
168
+ image_stream.add_images(_pdf_to_images)
145
169
  # Exportar para arquivos.
146
- self.collection_images.to_files_image(output_dir, replace=replace, land_scape=land_scape)
170
+ image_stream.to_files_image(output_dir, replace=replace, land_scape=land_scape)
147
171
  self.progress.stop()
148
172
 
149
173
  def to_files_pdf(
@@ -151,21 +175,27 @@ class PdfStream(object):
151
175
  output_dir: Directory, *,
152
176
  replace: bool = False,
153
177
  land_scape: bool = False,
178
+ a4: bool = False,
179
+ prefix: str = None,
154
180
  ) -> None:
155
181
  if self.collection_pages.is_null and self.collection_images.is_empty:
156
182
  raise ValueError('Adicione imagens e/ou documentos para prosseguir!')
183
+ pages_document = []
157
184
  if not self.collection_pages.is_null:
158
- convert_img_to_pdf = ConvertImageToPdf(self.collection_images)
159
- self.collection_pages.add_pages(convert_img_to_pdf.to_document().to_pages())
160
- self.collection_images.clear()
185
+ pages_document.extend(self.collection_pages.pages)
186
+ if not self.collection_images.is_empty:
187
+ img_to_pdf = ConvertImageToPdf(self.collection_images, lib_img_to_pdf=self.lib_img_to_pdf)
188
+ tmp_document = img_to_pdf.to_document(land_scape, a4=a4)
189
+ pages_document.extend(tmp_document.to_pages())
190
+ final_collection = CollectionPagePdf(pages_document)
161
191
  if land_scape:
162
- self.collection_pages.set_land_scape()
163
- self.collection_pages.to_files_pdf(output_dir, replace=replace)
192
+ final_collection.set_land_scape()
193
+ final_collection.to_files_pdf(output_dir, replace=replace, prefix=prefix)
164
194
 
165
195
 
166
196
  class SplitPdf(object):
167
197
 
168
- def __init__(self, pages: list[PageDocumentPdf], pbar: ProgressBarAdapter = ProgressBarAdapter()):
198
+ def __init__(self, pages: list[PageDocumentPdf] = [], pbar: ProgressBarAdapter = ProgressBarAdapter()):
169
199
  self.pbar: ProgressBarAdapter = pbar
170
200
  self.__collection_pages: CollectionPagePdf = CollectionPagePdf(pages)
171
201
  self.__collection_pages.set_pbar(self.pbar)
@@ -173,6 +203,9 @@ class SplitPdf(object):
173
203
  def is_empty(self) -> bool:
174
204
  return self.__collection_pages.is_null
175
205
 
206
+ def add_pages_pdf(self, pages: list[PageDocumentPdf]) -> None:
207
+ self.__collection_pages.add_pages(pages)
208
+
176
209
  def export(self, output_dir: Directory, *, replace: bool = False, prefix: str = 'pag') -> None:
177
210
  if self.is_empty():
178
211
  self.pbar.update_text(f'O Documento está vazio!')
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
  from __future__ import annotations
4
- from typing import Union, TypeAlias
4
+ from typing import Union, TypeAlias, Any
5
5
  from convert_stream.enum_libs.enums import *
6
6
 
7
7
  MOD_IMG_PIL: bool = False
@@ -18,16 +18,16 @@ try:
18
18
  from cv2.typing import MatLike
19
19
  MOD_IMG_OPENCV = True
20
20
  except ImportError:
21
- cv2.typing.MatLike = object
22
- MatLike = None
21
+ cv2.typing.MatLike = Any
22
+ MatLike = Any
23
23
 
24
24
  try:
25
25
  from PIL import Image
26
26
  from PIL import ImageOps, ImageFilter
27
27
  MOD_IMG_PIL = True
28
28
  except ImportError:
29
- Image = None
30
- Image.Image = None
29
+ Image = Any
30
+ Image.Image = Any
31
31
 
32
32
  if MOD_IMG_OPENCV and MOD_IMG_PIL:
33
33
  ModuleImage: TypeAlias = Union[MatLike, Image.Image]
@@ -40,7 +40,7 @@ elif MOD_IMG_PIL:
40
40
  DEFAULT_LIB_IMAGE = LibImage.PIL
41
41
  else:
42
42
  DEFAULT_LIB_IMAGE = LibImage.NOT_IMPLEMENTED
43
- ModuleImage = None # fallback para runtime
43
+ ModuleImage = Any # fallback para runtime
44
44
 
45
45
  #=================================================================#
46
46
  # Módulos para PDF fitz e pypdf
@@ -54,17 +54,17 @@ except ImportError:
54
54
  import pymupdf as fitz
55
55
  MOD_FITZ = True
56
56
  except ImportError:
57
- fitz = object
58
- fitz.Page = object
59
- fitz.TextPage = object
57
+ fitz = Any
58
+ fitz.Page = Any
59
+ fitz.TextPage = Any
60
60
 
61
61
  try:
62
62
  from pypdf import PdfWriter, PdfReader, PageObject
63
63
  MOD_PYPDF = True
64
64
  except ImportError:
65
- PageObject = None
66
- PdfReader = None
67
- PdfWriter = None
65
+ PageObject = Any
66
+ PdfReader = Any
67
+ PdfWriter = Any
68
68
 
69
69
 
70
70
  if MOD_FITZ and MOD_PYPDF:
@@ -80,8 +80,8 @@ elif MOD_PYPDF:
80
80
  ModDocPdf = Union[PdfWriter]
81
81
  DEFAULT_LIB_PDF = LibPDF.PYPDF
82
82
  else:
83
- ModPagePdf = None # fallback para runtime
84
- ModDocPdf = None
83
+ ModPagePdf = Any # fallback para runtime
84
+ ModDocPdf = Any
85
85
  DEFAULT_LIB_PDF = LibPDF.NOT_IMPLEMENTED
86
86
 
87
87
  #=================================================================#
@@ -95,10 +95,10 @@ try:
95
95
  from reportlab.lib.utils import ImageReader
96
96
  MOD_CANVAS = True
97
97
  except ImportError:
98
- canvas = None
99
- Canvas = None
98
+ canvas = Any
99
+ Canvas = Any
100
100
  letter = (612, 792)
101
- ImageReader = None
101
+ ImageReader = Any
102
102
 
103
103
  if MOD_CANVAS and MOD_FITZ and MOD_IMG_PIL:
104
104
  ModImageToPdf: TypeAlias = Union[canvas, fitz.Page, Image]
@@ -113,7 +113,7 @@ elif MOD_IMG_PIL:
113
113
  ModImageToPdf: TypeAlias = Union[Image]
114
114
  DEFAULT_LIB_IMAGE_TO_PDF = LibImageToPdf.IMAGE_TO_PDF_PIL
115
115
  else:
116
- ModImageToPdf = None
116
+ ModImageToPdf = Any
117
117
  DEFAULT_LIB_IMAGE_TO_PDF = LibImageToPdf.NOT_IMPLEMENTED
118
118
 
119
119
  #=================================================================#
@@ -125,4 +125,4 @@ if MOD_FITZ:
125
125
  ModPdfToImage: TypeAlias = Union[fitz]
126
126
  else:
127
127
  DEFAULT_LIB_PDF_TO_IMG = LibPdfToImage.NOT_IMPLEMENTED
128
- ModPdfToImage = None
128
+ ModPdfToImage = Any
@@ -4,6 +4,7 @@
4
4
  Módulo para trabalhar com imagens
5
5
  """
6
6
  from __future__ import annotations
7
+ from typing import Union
7
8
  from abc import ABC, abstractmethod
8
9
  from hashlib import md5 as md5_hash
9
10
  from soup_files import File, Directory, InputFiles, LibraryDocs, ProgressBarAdapter
@@ -63,11 +64,11 @@ class ABCImageObject(ABC):
63
64
  pass
64
65
 
65
66
  @abstractmethod
66
- def set_background_black(self):
67
+ def set_threshold_black(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
67
68
  pass
68
69
 
69
70
  @abstractmethod
70
- def set_background_gray(self):
71
+ def set_threshold_gray(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
71
72
  pass
72
73
 
73
74
  @abstractmethod
@@ -114,7 +115,8 @@ class ImplementOpenCv(ABCImageObject):
114
115
 
115
116
  def set_landscape(self):
116
117
  if self.is_vertical():
117
- self.set_rotation(RotationAngle.ROTATION_90)
118
+ #self.set_rotation(RotationAngle.ROTATION_90)
119
+ self.module_image = cv2.rotate(self.module_image, cv2.ROTATE_90_COUNTERCLOCKWISE)
118
120
 
119
121
  def set_vertical(self):
120
122
  if self.is_landscape():
@@ -128,36 +130,10 @@ class ImplementOpenCv(ABCImageObject):
128
130
  elif angle == RotationAngle.ROTATION_270:
129
131
  self.module_image = cv2.rotate(self.module_image, cv2.ROTATE_90_COUNTERCLOCKWISE)
130
132
 
131
- def set_gausian_blur(self, sigma: float = 0.7):
132
- # 1. Converte para escala de cinza
133
- gray = cv2.cvtColor(self.module_image, cv2.COLOR_BGR2GRAY)
134
-
135
- # 2. Detecta o texto com threshold adaptativo
136
- # Texto ficará branco (255) e fundo preto (0)
137
- text_mask = cv2.adaptiveThreshold(
138
- gray, 160,
139
- cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
140
- cv2.THRESH_BINARY_INV,
141
- 25, 15
142
- )
143
-
144
- # 3. Inverte máscara (fundo branco, texto preto)
145
- background_mask = cv2.bitwise_not(text_mask)
146
-
147
- # 4. Borra apenas o fundo
148
- blurred_background = cv2.GaussianBlur(gray, (0, 0), sigma)
149
-
150
- # 5. Combina: fundo borrado + texto nítido
151
- text_part = cv2.bitwise_and(gray, gray, mask=text_mask)
152
- background_part = cv2.bitwise_and(blurred_background, blurred_background, mask=background_mask)
153
- combined = cv2.add(text_part, background_part)
154
-
155
- # 6. Realça contraste (texto mais escuro, fundo mais claro)
156
- combined = cv2.normalize(combined, None, 0, 255, cv2.NORM_MINMAX)
157
- combined = cv2.convertScaleAbs(combined, alpha=1.5, beta=20)
158
-
159
- # 7. Salva de volta em BGR
160
- self.module_image = cv2.cvtColor(combined, cv2.COLOR_GRAY2BGR)
133
+ def set_gausian_blur(self, sigma: float = 0.5):
134
+ # Aplica um filtro Gaussiano para reduzir o ruído
135
+ _blurred: MatLike = cv2.GaussianBlur(self.module_image, (3, 3), sigma)
136
+ self.module_image = _blurred
161
137
 
162
138
  def set_background_blur(self, sigma: float):
163
139
  # 1. Converter para escala de cinza
@@ -175,42 +151,39 @@ class ImplementOpenCv(ABCImageObject):
175
151
  # 5. Converter de volta para BGR
176
152
  self.module_image = cv2.cvtColor(norm, cv2.COLOR_GRAY2BGR)
177
153
 
178
- def set_background_black(self):
179
- mask = cv2.cvtColor(self.module_image, cv2.COLOR_BGR2GRAY)
180
- _, mask = cv2.threshold(mask, 200, 255, cv2.THRESH_BINARY)
181
- mask_inv = cv2.bitwise_not(mask)
182
- self.module_image = cv2.bitwise_and(self.module_image, self.module_image, mask=mask_inv)
183
-
184
- def set_background_gray(self):
185
- # 1. Converte para escala de cinza
186
- gray = cv2.cvtColor(self.module_image, cv2.COLOR_BGR2GRAY)
187
-
188
- # 2. Detecta o texto (texto=branco, fundo=preto)
189
- text_mask = cv2.adaptiveThreshold(
190
- gray, 200,
154
+ def set_threshold_black(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
155
+ nparr = np.frombuffer(self.to_bytes().getvalue(), np.uint8)
156
+ img_opencv: cv2.typing.MatLike = cv2.imdecode(nparr, cv2.IMREAD_GRAYSCALE)
157
+ # Aplica um filtro Gaussiano para reduzir o ruído
158
+ _blurred: cv2.typing.MatLike = cv2.GaussianBlur(img_opencv, (3, 3), sigma)
159
+ # Aplica binarização adaptativa (texto branco, fundo preto)
160
+ binary: cv2.typing.MatLike = cv2.adaptiveThreshold(
161
+ _blurred,
162
+ max_value,
191
163
  cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
192
- cv2.THRESH_BINARY_INV,
193
- 25, 15
164
+ cv2.THRESH_BINARY,
165
+ 11,
166
+ 2
194
167
  )
168
+ self.module_image = cv2.bitwise_not(binary)
195
169
 
196
- # 3. Inverte máscara para pegar fundo
197
- background_mask = cv2.bitwise_not(text_mask)
170
+ def set_threshold_gray(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
171
+ nparr = np.frombuffer(self.to_bytes().getvalue(), np.uint8)
172
+ img_opencv: cv2.typing.MatLike = cv2.imdecode(nparr, cv2.IMREAD_GRAYSCALE)
198
173
 
199
- # 4. Cria fundo uniforme cinza claro
200
- gray_bg_value = 200 # tom claro de cinza (0=preto, 255=branco)
201
- background = np.full_like(gray, gray_bg_value, dtype=np.uint8)
174
+ # Aplica um filtro Gaussiano para reduzir o ruído
175
+ _blurred: cv2.typing.MatLike = cv2.GaussianBlur(img_opencv, (3, 3), sigma)
202
176
 
203
- # 5. Deixa texto bem escuro
204
- text_dark_value = 30
205
- text_layer = np.full_like(gray, text_dark_value, dtype=np.uint8)
206
-
207
- # 6. Combina texto escuro com fundo cinza
208
- text_part = cv2.bitwise_and(text_layer, text_layer, mask=text_mask)
209
- background_part = cv2.bitwise_and(background, background, mask=background_mask)
210
- combined = cv2.add(text_part, background_part)
211
-
212
- # 7. Salva em BGR
213
- self.module_image = cv2.cvtColor(combined, cv2.COLOR_GRAY2BGR)
177
+ # Aplica binarização adaptativa (texto preto, fundo branco)
178
+ binary = cv2.adaptiveThreshold(
179
+ _blurred,
180
+ max_value,
181
+ cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
182
+ cv2.THRESH_BINARY_INV, # Inverte o texto para ser branco inicialmente
183
+ 11,
184
+ 2
185
+ )
186
+ self.module_image = cv2.bitwise_not(binary)
214
187
 
215
188
  def to_file(self, file: File):
216
189
  #print(f'Exportando arquivo: {file.basename()}')
@@ -317,19 +290,18 @@ class ImplementPIL(ABCImageObject):
317
290
  img.paste(blurred, mask=mask)
318
291
  self.module_image = img
319
292
 
320
- def set_background_black(self):
293
+ def set_threshold_black(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
321
294
  img = self.module_image.convert("RGBA")
322
295
  gray = img.convert("L")
323
296
  mask = gray.point(lambda p: 255 if p > 200 else 0).convert("1")
324
- black_bg = Image.new("RGBA", img.size, (0, 0, 0, 255))
297
+ black_bg = Image.new("RGBA", img.size, (0, 0, 0, max_value))
325
298
  img.paste(black_bg, mask=mask)
326
299
  self.module_image = img
327
300
 
328
- def set_background_gray(self):
301
+ def set_threshold_gray(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
329
302
  if not MOD_IMG_PIL:
330
303
  raise RuntimeError("PIL não está disponível.")
331
-
332
- # 1. Converte para escala de cinza
304
+ # 1. Converte para escala de cinza
333
305
  gray_img = self.module_image.convert("L")
334
306
 
335
307
  # 2. Detecta o texto usando um threshold adaptativo (via NumPy)
@@ -343,11 +315,8 @@ class ImplementPIL(ABCImageObject):
343
315
 
344
316
  # 3. Máscara do fundo (inverso)
345
317
  mask_background = 255 - mask_text
346
-
347
318
  # 4. Cria fundo cinza claro
348
- gray_bg_value = 200
349
- background_np = np.full_like(img_np, gray_bg_value, dtype=np.uint8)
350
-
319
+ background_np = np.full_like(img_np, max_value, dtype=np.uint8)
351
320
  # 5. Texto escuro
352
321
  text_dark_value = 30
353
322
  text_np = np.full_like(img_np, text_dark_value, dtype=np.uint8)
@@ -355,7 +324,6 @@ class ImplementPIL(ABCImageObject):
355
324
  # 6. Combina texto escuro com fundo cinza
356
325
  combined_np = ((text_np * (mask_text // 255)) +
357
326
  (background_np * (mask_background // 255))).astype(np.uint8)
358
-
359
327
  # 7. Converte de volta para PIL (BGR → RGB não é necessário, pois estamos em L)
360
328
  self.module_image = Image.fromarray(combined_np).convert("RGB")
361
329
 
@@ -387,9 +355,48 @@ class ImplementPIL(ABCImageObject):
387
355
 
388
356
  class ImageObject(object):
389
357
 
390
- def __init__(self, img_adapter: ABCImageObject) -> None:
391
- self.img_adapter: ABCImageObject = img_adapter
392
- self.name: str = img_adapter.name
358
+ def __init__(
359
+ self, img: Union[ABCImageObject, bytes, BytesIO, str, File],
360
+ *, lib_image: LibImage = DEFAULT_LIB_IMAGE,
361
+ ) -> None:
362
+ #
363
+ if isinstance(img, ABCImageObject):
364
+ self.img_adapter: ABCImageObject = img
365
+ elif isinstance(img, bytes):
366
+ if lib_image == LibImage.OPENCV:
367
+ self.img_adapter = ImplementOpenCv.create_from_bytes(img)
368
+ elif lib_image == LibImage.PIL:
369
+ self.img_adapter = ImplementPIL.create_from_bytes(img)
370
+ else:
371
+ raise ValueError('Use: PIL ou OPENCV')
372
+ elif isinstance(img, BytesIO):
373
+ img.seek(0)
374
+ if lib_image == LibImage.OPENCV:
375
+ self.img_adapter = ImplementOpenCv.create_from_bytes(img.getvalue())
376
+ elif lib_image == LibImage.PIL:
377
+ self.img_adapter = ImplementPIL.create_from_bytes(img.getvalue())
378
+ else:
379
+ raise ValueError('Use: PIL ou OPENCV')
380
+ elif isinstance(img, File):
381
+ if lib_image == LibImage.OPENCV:
382
+ self.img_adapter = ImplementOpenCv.create_from_file(img)
383
+ elif lib_image == LibImage.PIL:
384
+ self.img_adapter = ImplementPIL.create_from_file(img)
385
+ else:
386
+ raise ValueError('Use: PIL ou OPENCV')
387
+ elif isinstance(img, str):
388
+ if lib_image == LibImage.OPENCV:
389
+ self.img_adapter = ImplementOpenCv.create_from_file(File(img))
390
+ elif lib_image == LibImage.PIL:
391
+ self.img_adapter = ImplementPIL.create_from_file(File(img))
392
+ else:
393
+ raise ValueError('Use: PIL ou OPENCV')
394
+ else:
395
+ raise ValueError('Use: str, bytes, BytesIO, File, OPENCV or PIL')
396
+
397
+ @property
398
+ def name(self) -> str:
399
+ return self.img_adapter.name
393
400
 
394
401
  def to_pil(self) -> Image.Image:
395
402
  """
@@ -425,17 +432,17 @@ class ImageObject(object):
425
432
  def set_rotation(self, angle: RotationAngle = RotationAngle.ROTATION_90) -> None:
426
433
  self.img_adapter.set_rotation(angle)
427
434
 
428
- def set_gausian_blur(self, sigma: float = 0.7):
435
+ def set_gausian_blur(self, sigma: float = 0.5):
429
436
  self.img_adapter.set_gausian_blur(sigma)
430
437
 
431
438
  def set_background_blur(self, sigma: float):
432
439
  self.img_adapter.set_background_blur(sigma)
433
440
 
434
- def set_background_black(self):
435
- self.img_adapter.set_background_black()
441
+ def set_threshold_black(self, *, max_value: float = 150, sigma: float = 0.5) -> None:
442
+ self.img_adapter.set_threshold_black(max_value=max_value, sigma=sigma)
436
443
 
437
- def set_background_gray(self):
438
- self.img_adapter.set_background_gray()
444
+ def set_threshold_gray(self, *, max_value: float = 150, sigma: float = 0.5):
445
+ self.img_adapter.set_threshold_gray(max_value=max_value, sigma=sigma)
439
446
 
440
447
  def to_file(self, file: File):
441
448
  self.img_adapter.to_file(file)
@@ -474,7 +481,7 @@ class CollectionImages(object):
474
481
 
475
482
  def __init__(self, images: list[ImageObject] = []) -> None:
476
483
  """
477
-
484
+ Gerir uma lista de Imagens
478
485
  :type images: list[ImageObject]
479
486
  """
480
487
  self.images: list[ImageObject] = images
@@ -550,7 +557,7 @@ class CollectionImages(object):
550
557
  img.set_landscape()
551
558
  if gaussian_filter:
552
559
  img.set_gausian_blur()
553
- img.set_background_gray()
560
+ img.set_threshold_gray()
554
561
  try:
555
562
  img.to_file(file_path)
556
563
  except Exception as e:
@@ -571,7 +578,7 @@ class CollectionImages(object):
571
578
  f'Aplicando GausianBlur: [{_num + 1} de {max_num}]'
572
579
  )
573
580
  im.set_gausian_blur(sigma)
574
- im.set_background_gray()
581
+ im.set_threshold_gray()
575
582
  print()
576
583
  self.pbar.stop()
577
584
 
@@ -27,6 +27,9 @@ class SearchableTextPdf(object):
27
27
  """
28
28
  def __init__(self, silent: bool = False):
29
29
  self.silent = silent
30
+ # Cada elemento encontrado na busca de texto, será guardado nesse objeto para
31
+ # posterior consulta (self.elements), cada chave aqui deve ter uma lista de
32
+ # tamanho igual.
30
33
  self.elements: dict[str, list[str]] = {
31
34
  ColumnsTable.NUM_PAGE.value: [],
32
35
  ColumnsTable.NUM_LINHA.value: [],
@@ -117,7 +120,12 @@ class SearchableTextPdf(object):
117
120
 
118
121
 
119
122
  class ABCDocument(ABC):
120
-
123
+ """
124
+ Classe molde para gerir os documentos, para operações como:
125
+ - leitura e escrita de arquivos pdf
126
+ - exportar páginas ou arquivos pdf
127
+ - permitir a leitura de arquivos ou bytes de pdf.
128
+ """
121
129
  def __init__(self, mod_doc_pdf: ModDocPdf, name: str):
122
130
  self.mod_doc_pdf: ModDocPdf = mod_doc_pdf
123
131
  self.lib_pdf: LibPDF = DEFAULT_LIB_PDF
@@ -169,7 +177,9 @@ class ABCDocument(ABC):
169
177
 
170
178
 
171
179
  class ImplementDocumentFitz(ABCDocument):
172
-
180
+ """
181
+ Implementação usando a biblioteca fitz.
182
+ """
173
183
  def __init__(self, mod_doc_pdf: ModDocPdf, name: str):
174
184
  super().__init__(mod_doc_pdf, name)
175
185
  self.mod_doc_pdf: ModDocPdf = mod_doc_pdf
@@ -255,6 +265,9 @@ class ImplementDocumentFitz(ABCDocument):
255
265
 
256
266
 
257
267
  class ImplementDocumentPyPdf(ABCDocument):
268
+ """
269
+ Implementação usando a biblioteca pypdf
270
+ """
258
271
 
259
272
  def __init__(self, mod_doc_pdf: PdfWriter, name: str):
260
273
  super().__init__(mod_doc_pdf, name)
@@ -353,13 +366,20 @@ class ImplementDocumentPyPdf(ABCDocument):
353
366
 
354
367
 
355
368
  class DocumentPdf(object):
369
+ """
370
+ Gerir um documento PDF com uma implementação de fitz ou pypdf.
371
+ As operações são feitas pela classe encapsulada aqui.
372
+ """
356
373
  def __init__(
357
374
  self,
358
- document: Union[str, File, PdfWriter, fitz.Document, BytesIO],
375
+ document: Union[
376
+ str, File, PdfWriter, fitz.Document, BytesIO,
377
+ ImplementDocumentFitz, ImplementDocumentPyPdf,
378
+ ],
359
379
  *,
360
380
  lib_pdf: LibPDF = LibPDF.FITZ
361
381
  ):
362
- self.doc_pdf = None
382
+ self.doc_pdf: ABCDocument = None
363
383
  self.lib_pdf: LibPDF = lib_pdf
364
384
  if isinstance(document, str):
365
385
  if lib_pdf == LibPDF.PYPDF:
@@ -415,7 +435,6 @@ class DocumentPdf(object):
415
435
  """
416
436
  Retorna a primeira página do documento.
417
437
  """
418
- # O índice da primeira página em PyPDF2 é 0.
419
438
  return self.doc_pdf.get_first_page()
420
439
 
421
440
  def get_last_page(self) -> PageDocumentPdf:
@@ -597,7 +616,9 @@ class DocumentPdf(object):
597
616
 
598
617
 
599
618
  class CollectionPagePdf(object):
600
-
619
+ """
620
+ Gerir uma coleção de páginas PDF.
621
+ """
601
622
  def __init__(self, pages: list[PageDocumentPdf] = list()) -> None:
602
623
  self.pages: list[PageDocumentPdf] = pages
603
624
  self.pbar: ProgressBarAdapter = ProgressBarAdapter()
@@ -622,6 +643,14 @@ class CollectionPagePdf(object):
622
643
  def set_pbar(self, p: ProgressBarAdapter):
623
644
  self.pbar = p
624
645
 
646
+ def set_land_scape(self):
647
+ for page in self.pages:
648
+ page.set_land_scape()
649
+
650
+ def set_rotation(self, rotation: int) -> None:
651
+ for page in self.pages:
652
+ page.set_rotation(rotation)
653
+
625
654
  def add_page(self, page: PageDocumentPdf) -> None:
626
655
  page.number_page = len(self.pages) + 1
627
656
  self.pbar.update_text(f'Adicionando página PDF {page.number_page}')
@@ -210,7 +210,7 @@ class ImplementFitz(ABCPagePdf):
210
210
  return
211
211
  try:
212
212
  # Rotaciona para 90 graus
213
- self.mod_page.set_rotation(90)
213
+ self.mod_page.set_rotation(-90)
214
214
  except Exception:
215
215
  pass
216
216
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: convert_stream
3
- Version: 2.2.7
3
+ Version: 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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "convert_stream"
7
- version = "2.2.7"
7
+ version = "2.3"
8
8
  authors = [
9
9
  { name="Bruno Chaves"},
10
10
  ]
File without changes
File without changes