convert-stream 2.3.3__tar.gz → 2.3.5__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.
- {convert_stream-2.3.3 → convert_stream-2.3.5}/PKG-INFO +5 -3
- {convert_stream-2.3.3 → convert_stream-2.3.5}/README.md +4 -2
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/__init__.py +14 -3
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/doc_stream.py +1 -1
- convert_stream-2.3.5/convert_stream/image/__init__.py +7 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/image/img_object.py +87 -22
- convert_stream-2.3.5/convert_stream/mod_types/__init__.py +100 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/mod_types/table_types.py +95 -100
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/pdf/pdf_document.py +113 -118
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/pdf/pdf_page.py +2 -2
- convert_stream-2.3.5/convert_stream/table_files.py +153 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/text/find_text.py +1 -1
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream.egg-info/PKG-INFO +5 -3
- {convert_stream-2.3.3 → convert_stream-2.3.5}/pyproject.toml +1 -1
- convert_stream-2.3.3/convert_stream/image/__init__.py +0 -7
- convert_stream-2.3.3/convert_stream/mod_types/__init__.py +0 -42
- convert_stream-2.3.3/convert_stream/table_files.py +0 -138
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/__main__.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/_version.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/mod_types/enums.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/mod_types/modules.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/pdf/__init__.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/pdf/image_to_pdf.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/pdf/pdf_to_image.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/sheets/__init__.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/sheets/load.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/sheets/parse.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/sheets/save.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/text/__init__.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/text/date.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream/text/terminal.py +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream.egg-info/SOURCES.txt +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream.egg-info/dependency_links.txt +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream.egg-info/requires.txt +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/convert_stream.egg-info/top_level.txt +0 -0
- {convert_stream-2.3.3 → convert_stream-2.3.5}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: convert_stream
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.5
|
|
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
|
|
@@ -21,8 +21,9 @@ pip3 install -r requirements.txt
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
# Uso dividir páginas de um arquivo PDF:
|
|
24
|
-
>>> from
|
|
24
|
+
>>> from soup_files import File, Directory
|
|
25
25
|
>>> from pdflib import PdfStream
|
|
26
|
+
|
|
26
27
|
# instâncie o caminho do arquivo PDF e o diretório para exportar os dados.
|
|
27
28
|
file_pdf = File('path/to/file.pdf')
|
|
28
29
|
output_dir = Directory('path/to/save')
|
|
@@ -32,8 +33,9 @@ pip3 install -r requirements.txt
|
|
|
32
33
|
pdf_stream.to_files_pdf(output_dir) # isso irá converter cada página em um arquivo PDF
|
|
33
34
|
|
|
34
35
|
# Uso para unir vários arquivos PDF.
|
|
35
|
-
>>> from
|
|
36
|
+
>>> from soup_files import File, Directory
|
|
36
37
|
>>> from pdflib import PdfStream
|
|
38
|
+
|
|
37
39
|
# instâncie o caminho dos arquivos PDF que deseja unir.
|
|
38
40
|
f = File('path/to/file.pdf')
|
|
39
41
|
f2 = File('path/to/file2.pdf)
|
|
@@ -3,8 +3,9 @@ pip3 install -r requirements.txt
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
# Uso dividir páginas de um arquivo PDF:
|
|
6
|
-
>>> from
|
|
6
|
+
>>> from soup_files import File, Directory
|
|
7
7
|
>>> from pdflib import PdfStream
|
|
8
|
+
|
|
8
9
|
# instâncie o caminho do arquivo PDF e o diretório para exportar os dados.
|
|
9
10
|
file_pdf = File('path/to/file.pdf')
|
|
10
11
|
output_dir = Directory('path/to/save')
|
|
@@ -14,8 +15,9 @@ pip3 install -r requirements.txt
|
|
|
14
15
|
pdf_stream.to_files_pdf(output_dir) # isso irá converter cada página em um arquivo PDF
|
|
15
16
|
|
|
16
17
|
# Uso para unir vários arquivos PDF.
|
|
17
|
-
>>> from
|
|
18
|
+
>>> from soup_files import File, Directory
|
|
18
19
|
>>> from pdflib import PdfStream
|
|
20
|
+
|
|
19
21
|
# instâncie o caminho dos arquivos PDF que deseja unir.
|
|
20
22
|
f = File('path/to/file.pdf')
|
|
21
23
|
f2 = File('path/to/file2.pdf)
|
|
@@ -2,14 +2,21 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
from io import BytesIO
|
|
4
4
|
from typing import Union
|
|
5
|
-
from soup_files import File
|
|
5
|
+
from soup_files import File, Directory, LibraryDocs, InputFiles
|
|
6
6
|
|
|
7
7
|
from ._version import (
|
|
8
8
|
__module_name__, __author__, __license__, __modify_date__, __version__
|
|
9
9
|
)
|
|
10
|
+
from .mod_types import (
|
|
11
|
+
MetaDataFile, MetaDataItem, create_metadata, ColumnsTable, ListString, ArrayString,
|
|
12
|
+
HeadCell, HeadValues, ColumnBody, TextTable, LibDate, LibPDF, LibImage, LibPdfToImage,
|
|
13
|
+
LibImageToPdf, create_dict_map_from_file, create_void_dict_map, RotationAngle
|
|
14
|
+
)
|
|
10
15
|
from .image import ImageObject, CollectionImages
|
|
11
16
|
from .pdf import DocumentPdf, PageDocumentPdf, CollectionPagePdf
|
|
12
|
-
from .
|
|
17
|
+
from .pdf.pdf_document import SearchableTextPdf
|
|
18
|
+
from .doc_stream import PdfStream, SplitPdf
|
|
19
|
+
from .table_files import PdfFinder
|
|
13
20
|
|
|
14
21
|
|
|
15
22
|
def __create_stream() -> PdfStream:
|
|
@@ -68,6 +75,10 @@ def read_image(img: Union[str, File, bytes, BytesIO]) -> PdfStream:
|
|
|
68
75
|
__all__ = [
|
|
69
76
|
'__modify_date__', '__author__', '__license__', '__module_name__', '__version__',
|
|
70
77
|
'PdfStream', 'DocumentPdf', 'PageDocumentPdf', 'CollectionPagePdf', 'CollectionImages',
|
|
71
|
-
'
|
|
78
|
+
'ImageObject', 'File', 'Directory', 'LibraryDocs', 'InputFiles', 'SearchableTextPdf',
|
|
79
|
+
'PdfFinder', 'SplitPdf', 'read_pdf', 'read_files_pdf', 'read_image',
|
|
80
|
+
'MetaDataFile', 'MetaDataItem', 'create_metadata', 'ColumnsTable', 'ListString', 'ArrayString',
|
|
81
|
+
'HeadCell', 'HeadValues', 'ColumnBody', 'TextTable', 'LibDate', 'LibPDF', 'LibImage', 'LibPdfToImage',
|
|
82
|
+
'LibImageToPdf', 'create_dict_map_from_file', 'create_void_dict_map', 'RotationAngle'
|
|
72
83
|
]
|
|
73
84
|
|
|
@@ -183,7 +183,7 @@ class PdfStream(object):
|
|
|
183
183
|
|
|
184
184
|
class SplitPdf(object):
|
|
185
185
|
|
|
186
|
-
def __init__(self, pages: list[PageDocumentPdf]
|
|
186
|
+
def __init__(self, pages: list[PageDocumentPdf], pbar: ProgressBarAdapter = ProgressBarAdapter()):
|
|
187
187
|
self.pbar: ProgressBarAdapter = pbar
|
|
188
188
|
self.__collection_pages: CollectionPagePdf = CollectionPagePdf(pages)
|
|
189
189
|
self.__collection_pages.set_pbar(self.pbar)
|
|
@@ -10,12 +10,12 @@ from soup_files import File, Directory, InputFiles, LibraryDocs, ProgressBarAdap
|
|
|
10
10
|
from io import BytesIO
|
|
11
11
|
import numpy as np
|
|
12
12
|
from scipy.ndimage import uniform_filter
|
|
13
|
-
from convert_stream.mod_types import
|
|
13
|
+
from convert_stream.mod_types import get_hash_from_bytes, MetaDataFile, MetaDataItem, create_metadata
|
|
14
14
|
from convert_stream.mod_types.enums import LibImage, RotationAngle
|
|
15
15
|
from convert_stream.mod_types.table_types import ListItems
|
|
16
16
|
from convert_stream.mod_types.modules import (
|
|
17
17
|
MOD_IMG_PIL, ModuleImage, DEFAULT_LIB_IMAGE,
|
|
18
|
-
cv2, MatLike, Image, ImageFilter
|
|
18
|
+
cv2, MatLike, Image, ImageFilter, LibImage
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
|
|
@@ -26,11 +26,11 @@ class ABCImageObject(ABC):
|
|
|
26
26
|
self.max_size: tuple[int, int] = (1980, 720)
|
|
27
27
|
self.module_image: ModuleImage = module_image
|
|
28
28
|
self.lib_image: LibImage = LibImage.OPENCV
|
|
29
|
-
self.metadata:
|
|
29
|
+
self.metadata: MetaDataFile = MetaDataFile()
|
|
30
30
|
|
|
31
31
|
@property
|
|
32
32
|
def name(self) -> str | None:
|
|
33
|
-
return self.metadata
|
|
33
|
+
return self.metadata.name if not self.metadata.name.is_empty else None
|
|
34
34
|
|
|
35
35
|
@abstractmethod
|
|
36
36
|
def get_dimensions(self) -> tuple[int, int]:
|
|
@@ -205,11 +205,7 @@ class ImplementOpenCv(ABCImageObject):
|
|
|
205
205
|
if img is None:
|
|
206
206
|
raise ValueError(f"Não foi possível abrir a imagem: {f.absolute()}")
|
|
207
207
|
im_obj = cls(img)
|
|
208
|
-
im_obj.metadata
|
|
209
|
-
im_obj.metadata['md5'] = f.md5()
|
|
210
|
-
im_obj.metadata['name'] = f.absolute()
|
|
211
|
-
im_obj.metadata['size'] = f.size()
|
|
212
|
-
im_obj.metadata['extension'] = f.extension()
|
|
208
|
+
im_obj.metadata = create_metadata(f)
|
|
213
209
|
return im_obj
|
|
214
210
|
|
|
215
211
|
@classmethod
|
|
@@ -221,10 +217,10 @@ class ImplementOpenCv(ABCImageObject):
|
|
|
221
217
|
bytes_io = BytesIO(bt)
|
|
222
218
|
name: str = get_hash_from_bytes(bytes_io)
|
|
223
219
|
bytes_io.close()
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return
|
|
220
|
+
im_obj = cls(img)
|
|
221
|
+
im_obj.metadata.name = MetaDataItem(name)
|
|
222
|
+
im_obj.metadata.md5 = MetaDataItem(name)
|
|
223
|
+
return im_obj
|
|
228
224
|
|
|
229
225
|
|
|
230
226
|
class ImplementPIL(ABCImageObject):
|
|
@@ -352,13 +348,9 @@ class ImplementPIL(ABCImageObject):
|
|
|
352
348
|
|
|
353
349
|
@classmethod
|
|
354
350
|
def create_from_file(cls, f: File) -> ABCImageObject:
|
|
355
|
-
img = Image.open(f.
|
|
351
|
+
img = Image.open(f.absolute())
|
|
356
352
|
im_obj = cls(img)
|
|
357
|
-
im_obj.metadata
|
|
358
|
-
im_obj.metadata['md5'] = f.md5()
|
|
359
|
-
im_obj.metadata['name'] = f.absolute()
|
|
360
|
-
im_obj.metadata['size'] = f.size()
|
|
361
|
-
im_obj.metadata['extension'] = f.extension()
|
|
353
|
+
im_obj.metadata = create_metadata(f)
|
|
362
354
|
return im_obj
|
|
363
355
|
|
|
364
356
|
@classmethod
|
|
@@ -368,8 +360,8 @@ class ImplementPIL(ABCImageObject):
|
|
|
368
360
|
name = get_hash_from_bytes(bytes_io)
|
|
369
361
|
bytes_io.close()
|
|
370
362
|
_obj = cls(img)
|
|
371
|
-
_obj.metadata
|
|
372
|
-
_obj.metadata
|
|
363
|
+
_obj.metadata.name = MetaDataItem(name)
|
|
364
|
+
_obj.metadata.md5 = MetaDataItem(name)
|
|
373
365
|
return _obj
|
|
374
366
|
|
|
375
367
|
|
|
@@ -419,9 +411,13 @@ class ImageObject(object):
|
|
|
419
411
|
return self.img_adapter.name
|
|
420
412
|
|
|
421
413
|
@property
|
|
422
|
-
def metadata(self) ->
|
|
414
|
+
def metadata(self) -> MetaDataFile:
|
|
423
415
|
return self.img_adapter.metadata
|
|
424
416
|
|
|
417
|
+
@metadata.setter
|
|
418
|
+
def metadata(self, metadata: MetaDataFile) -> None:
|
|
419
|
+
self.img_adapter.metadata = metadata
|
|
420
|
+
|
|
425
421
|
def to_pil(self) -> Image.Image:
|
|
426
422
|
"""
|
|
427
423
|
Converte o ImageObject para um objeto PIL.Image.
|
|
@@ -600,3 +596,72 @@ class CollectionImages(ListItems):
|
|
|
600
596
|
def set_pbar(self, p: ProgressBarAdapter) -> None:
|
|
601
597
|
self.pbar = p
|
|
602
598
|
|
|
599
|
+
|
|
600
|
+
class ProcessImageScanner(object):
|
|
601
|
+
|
|
602
|
+
def __init__(self, image: ImageObject):
|
|
603
|
+
self.image: ImageObject = image
|
|
604
|
+
|
|
605
|
+
def select_doc(self) -> ImageObject | None:
|
|
606
|
+
"""
|
|
607
|
+
Detecta e recorta automaticamente a área do documento principal
|
|
608
|
+
(texto ou folha digitalizada) em uma imagem.
|
|
609
|
+
Retorna um novo ImageObject contendo apenas o recorte detectado.
|
|
610
|
+
"""
|
|
611
|
+
# Converte para matriz OpenCV
|
|
612
|
+
img_cv: np.ndarray = cv2.imdecode(self.image.to_numpy(), cv2.IMREAD_COLOR)
|
|
613
|
+
if img_cv is None:
|
|
614
|
+
print(f'[NDARRAY] Falha na imagem: {self.image.metadata.name}')
|
|
615
|
+
return None
|
|
616
|
+
|
|
617
|
+
# Etapa 1: converter para tons de cinza
|
|
618
|
+
gray = cv2.cvtColor(img_cv, cv2.COLOR_BGR2GRAY)
|
|
619
|
+
|
|
620
|
+
# Etapa 2: aplicar blur para suavizar ruídos
|
|
621
|
+
gray_blur = cv2.GaussianBlur(gray, (5, 5), 0)
|
|
622
|
+
|
|
623
|
+
# Etapa 3: detecção de bordas
|
|
624
|
+
edges = cv2.Canny(gray_blur, 50, 150)
|
|
625
|
+
|
|
626
|
+
# Etapa 4: encontrar contornos
|
|
627
|
+
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
628
|
+
if not contours:
|
|
629
|
+
print(f'[CONTOURS] Falha na imagem: {self.image.metadata.name}')
|
|
630
|
+
return None
|
|
631
|
+
|
|
632
|
+
# Etapa 5: selecionar o maior contorno plausível (maior área retangular)
|
|
633
|
+
contours = sorted(contours, key=cv2.contourArea, reverse=True)
|
|
634
|
+
doc_contour = None
|
|
635
|
+
|
|
636
|
+
for c in contours:
|
|
637
|
+
perimeter = cv2.arcLength(c, True)
|
|
638
|
+
approx = cv2.approxPolyDP(c, 0.02 * perimeter, True)
|
|
639
|
+
if len(approx) == 4:
|
|
640
|
+
doc_contour = approx
|
|
641
|
+
break
|
|
642
|
+
|
|
643
|
+
if doc_contour is None:
|
|
644
|
+
# Caso não encontre um quadrilátero, usa o maior contorno
|
|
645
|
+
doc_contour = contours[0]
|
|
646
|
+
|
|
647
|
+
# Etapa 6: criar bounding box e recortar
|
|
648
|
+
x, y, w, h = cv2.boundingRect(doc_contour)
|
|
649
|
+
cropped = img_cv[y:y + h, x:x + w]
|
|
650
|
+
|
|
651
|
+
# Evitar recortes muito pequenos (ruído)
|
|
652
|
+
if w < 50 or h < 50:
|
|
653
|
+
return None
|
|
654
|
+
|
|
655
|
+
# Etapa 7: converter o recorte em ImageObject
|
|
656
|
+
success, buffer = cv2.imencode(".png", cropped)
|
|
657
|
+
if not success:
|
|
658
|
+
print(f'Falha na imagem: {self.image.metadata.name}')
|
|
659
|
+
return None
|
|
660
|
+
|
|
661
|
+
bytes_io = BytesIO(buffer.tobytes())
|
|
662
|
+
new_image = ImageObject(bytes_io, lib_image=LibImage.OPENCV)
|
|
663
|
+
# Herdar metadados originais
|
|
664
|
+
new_image.metadata = self.image.metadata
|
|
665
|
+
new_image.metadata.name = MetaDataItem(f"{self.image.metadata.name}_crop")
|
|
666
|
+
return new_image
|
|
667
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from hashlib import md5 as md5_hash
|
|
5
|
+
from io import BytesIO
|
|
6
|
+
from soup_files import File
|
|
7
|
+
from .enums import (
|
|
8
|
+
RotationAngle, ColumnsTable, LibImage, LibPdfToImage, LibPDF, LibImageToPdf, LibDate
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from .table_types import (
|
|
12
|
+
TextTable, ListString, ArrayString, HeadCell, HeadValues,
|
|
13
|
+
ColumnBody, create_void_dict_map, create_dict_map_values, create_void_df_map,
|
|
14
|
+
create_dict_map_from_file, concat_maps, contains, get_text_from_file
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_hash_from_bytes(bt: BytesIO | bytes) -> str:
|
|
19
|
+
if isinstance(bt, BytesIO):
|
|
20
|
+
return md5_hash(bt.getvalue()).hexdigest().upper()
|
|
21
|
+
elif isinstance(bt, bytes):
|
|
22
|
+
return md5_hash(bt).hexdigest().upper()
|
|
23
|
+
else:
|
|
24
|
+
raise TypeError(f"Unsupported type {type(bt)}")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MetaDataItem(str):
|
|
28
|
+
|
|
29
|
+
def __init__(self, text: str = 'nan'):
|
|
30
|
+
super().__init__()
|
|
31
|
+
self.text: str = text
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def is_empty(self) -> bool:
|
|
35
|
+
return self.text == 'nan'
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class MetaDataFile(object):
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self, *,
|
|
42
|
+
file_path: MetaDataItem = MetaDataItem(),
|
|
43
|
+
dir_path: MetaDataItem = MetaDataItem(),
|
|
44
|
+
name: MetaDataItem = MetaDataItem(),
|
|
45
|
+
md5: MetaDataItem = MetaDataItem(),
|
|
46
|
+
size: MetaDataItem = MetaDataItem(),
|
|
47
|
+
extension: MetaDataItem = MetaDataItem(),
|
|
48
|
+
origin_src: MetaDataItem = MetaDataItem(),
|
|
49
|
+
):
|
|
50
|
+
self.file_path: MetaDataItem = file_path
|
|
51
|
+
self.dir_path: MetaDataItem = dir_path
|
|
52
|
+
self.name: MetaDataItem = name
|
|
53
|
+
self.md5: MetaDataItem = md5
|
|
54
|
+
self.size: MetaDataItem = size
|
|
55
|
+
self.extension: MetaDataItem = extension
|
|
56
|
+
self.origin_src: MetaDataItem = origin_src
|
|
57
|
+
|
|
58
|
+
def to_dict(self) -> dict[str, str | None]:
|
|
59
|
+
return {
|
|
60
|
+
'file_path': self.file_path if not self.file_path.is_empty else None,
|
|
61
|
+
'dir_path': self.dir_path if not self.dir_path.is_empty else None,
|
|
62
|
+
'name': self.name if not self.name.is_empty else None,
|
|
63
|
+
'md5': self.md5 if not self.md5.is_empty else None,
|
|
64
|
+
'size': self.size if not self.size.is_empty else None,
|
|
65
|
+
'extension': self.extension if not self.extension.is_empty else None,
|
|
66
|
+
'origin_src': self.origin_src if not self.origin_src.is_empty else None,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_void_metadata() -> dict[str, str | None]:
|
|
71
|
+
"""
|
|
72
|
+
Salvar os metadados básico de documentos/imagens lidas em disco.
|
|
73
|
+
"""
|
|
74
|
+
return MetaDataFile().to_dict()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def create_metadata(file: str | File) -> MetaDataFile:
|
|
78
|
+
if isinstance(file, str):
|
|
79
|
+
file = File(file)
|
|
80
|
+
mt = MetaDataFile(
|
|
81
|
+
file_path=MetaDataItem(file.absolute()),
|
|
82
|
+
dir_path=MetaDataItem(file.dirname()),
|
|
83
|
+
extension=MetaDataItem(file.extension()),
|
|
84
|
+
origin_src=MetaDataItem('file'),
|
|
85
|
+
name=MetaDataItem(file.name()),
|
|
86
|
+
)
|
|
87
|
+
if file.exists():
|
|
88
|
+
mt.size = MetaDataItem(file.size())
|
|
89
|
+
mt.md5 = MetaDataItem(file.md5())
|
|
90
|
+
return mt
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
__all__ = [
|
|
94
|
+
'get_hash_from_bytes', 'get_void_metadata', 'get_void_metadata', 'contains',
|
|
95
|
+
'concat_maps', 'create_dict_map_from_file', 'create_dict_map_values',
|
|
96
|
+
'create_void_df_map', 'create_void_dict_map', 'ColumnBody', 'ColumnsTable',
|
|
97
|
+
'ListString', 'HeadCell', 'HeadValues', 'TextTable', 'RotationAngle', 'LibPDF',
|
|
98
|
+
'LibImageToPdf', 'LibPdfToImage', 'LibImage', 'LibDate', 'ArrayString',
|
|
99
|
+
'MetaDataFile', 'MetaDataItem', 'create_metadata',
|
|
100
|
+
]
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
from convert_stream.mod_types.enums import ColumnsTable
|
|
5
5
|
import os.path
|
|
6
6
|
import pandas as pd
|
|
7
|
-
from soup_files import File
|
|
7
|
+
from soup_files import File
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def contains(text: str, values: list[str], *, case: bool = True, iqual: bool = False) -> bool:
|
|
@@ -32,6 +32,61 @@ def contains(text: str, values: list[str], *, case: bool = True, iqual: bool = F
|
|
|
32
32
|
return False
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def find_index(text: str, values: list[str], *, case: bool = True, iqual: bool = False) -> int | None:
|
|
36
|
+
"""
|
|
37
|
+
Verificar se um texto existe em lista de ‘strings’ se existir, retorna o índice da posição
|
|
38
|
+
do texto na lista.
|
|
39
|
+
"""
|
|
40
|
+
_idx: int | None = None
|
|
41
|
+
if case:
|
|
42
|
+
if iqual:
|
|
43
|
+
for num, x in enumerate(values):
|
|
44
|
+
if text == x:
|
|
45
|
+
_idx = num
|
|
46
|
+
break
|
|
47
|
+
else:
|
|
48
|
+
for num, x in enumerate(values):
|
|
49
|
+
if text in x:
|
|
50
|
+
_idx = num
|
|
51
|
+
break
|
|
52
|
+
else:
|
|
53
|
+
if iqual:
|
|
54
|
+
for num, x in enumerate(values):
|
|
55
|
+
if text.upper() == x.upper():
|
|
56
|
+
_idx = num
|
|
57
|
+
break
|
|
58
|
+
else:
|
|
59
|
+
for num, x in enumerate(values):
|
|
60
|
+
if text.upper() in x.upper():
|
|
61
|
+
_idx = num
|
|
62
|
+
break
|
|
63
|
+
return _idx
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def find_all_index(text: str, values: list[str], *, case: bool = True, iqual: bool = False) -> list[int]:
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
items: list[int] = ListItems([])
|
|
71
|
+
if iqual:
|
|
72
|
+
for num, i in enumerate(values):
|
|
73
|
+
if case:
|
|
74
|
+
if i == text:
|
|
75
|
+
items.append(num)
|
|
76
|
+
else:
|
|
77
|
+
if text.lower() == i.lower():
|
|
78
|
+
items.append(num)
|
|
79
|
+
else:
|
|
80
|
+
for num, i in enumerate(values):
|
|
81
|
+
if case:
|
|
82
|
+
if text in i:
|
|
83
|
+
items.append(num)
|
|
84
|
+
else:
|
|
85
|
+
if text.lower() in i.lower():
|
|
86
|
+
items.append(num)
|
|
87
|
+
return items
|
|
88
|
+
|
|
89
|
+
|
|
35
90
|
class ListItems(list):
|
|
36
91
|
|
|
37
92
|
def __init__(self, items: list):
|
|
@@ -57,6 +112,9 @@ class ListString(ListItems):
|
|
|
57
112
|
def contains(self, item: str, *, case: bool = True, iqual: bool = False) -> bool:
|
|
58
113
|
return contains(item, self, case=case, iqual=iqual)
|
|
59
114
|
|
|
115
|
+
def find_index(self, item: str, *, case: bool = True, iqual: bool = False) -> int | None:
|
|
116
|
+
return find_index(item, self, case=case, iqual=iqual)
|
|
117
|
+
|
|
60
118
|
def get(self, idx: int) -> str:
|
|
61
119
|
return self[idx]
|
|
62
120
|
|
|
@@ -77,35 +135,23 @@ class ArrayString(ListString):
|
|
|
77
135
|
def __init__(self, items: list[str]):
|
|
78
136
|
super().__init__(items)
|
|
79
137
|
|
|
80
|
-
def
|
|
138
|
+
def get_next_string(self, text: str, *, iqual: bool = False, case: bool = False) -> str | None:
|
|
81
139
|
"""
|
|
82
140
|
Ao encontrar o texto 'text' na lista retorna a próxima string se existir, se não retorna None.
|
|
83
141
|
"""
|
|
84
|
-
|
|
85
|
-
if
|
|
86
|
-
return None
|
|
87
|
-
if _idx < 0:
|
|
88
|
-
return None
|
|
89
|
-
if _idx >= self.length - 1:
|
|
90
|
-
return None
|
|
91
|
-
return self[_idx+1]
|
|
142
|
+
next_idx: int | None = self.get_next_index(text, iqual=iqual, case=case)
|
|
143
|
+
return None if next_idx is None else self[next_idx]
|
|
92
144
|
|
|
93
145
|
def get_next_all(self, text: str, iqual: bool = False, case: bool = False) -> ListString:
|
|
94
|
-
|
|
95
|
-
if
|
|
96
|
-
return ListString([])
|
|
97
|
-
if _idx < 0:
|
|
98
|
-
return ListString([])
|
|
99
|
-
if _idx >= self.length - 1:
|
|
100
|
-
return ListString([])
|
|
101
|
-
return ListString(self[(_idx + 1):])
|
|
146
|
+
next_idx: int | None = self.get_next_index(text, iqual=iqual, case=case)
|
|
147
|
+
return ListString([]) if next_idx is None else ListString(self[next_idx:])
|
|
102
148
|
|
|
103
149
|
def get_next_index(self, text: str, *, iqual: bool = False, case: bool = False) -> int | None:
|
|
104
150
|
"""
|
|
105
151
|
Ao encontrar o texto 'text' na lista retorna o índice da string anterior
|
|
106
152
|
se existir, se não retorna None.
|
|
107
153
|
"""
|
|
108
|
-
_idx: int | None = self.
|
|
154
|
+
_idx: int | None = self.find_index(text, iqual=iqual, case=case)
|
|
109
155
|
if _idx is None:
|
|
110
156
|
return None
|
|
111
157
|
if _idx < 0:
|
|
@@ -126,7 +172,7 @@ class ArrayString(ListString):
|
|
|
126
172
|
return None
|
|
127
173
|
return _final_idx - 1
|
|
128
174
|
|
|
129
|
-
def
|
|
175
|
+
def get_back_string(self, text: str, iqual: bool = False, case: bool = False) -> str | None:
|
|
130
176
|
"""
|
|
131
177
|
Ao encontrar o texto 'text' na lista retorna a string anterior se existir, se não retorna None.
|
|
132
178
|
"""
|
|
@@ -137,79 +183,24 @@ class ArrayString(ListString):
|
|
|
137
183
|
_idx = self.get_back_index(text, iqual=iqual, case=case)
|
|
138
184
|
return ListString([]) if _idx is None else ListString(self[:_idx])
|
|
139
185
|
|
|
140
|
-
def find_index(self, text: str, *, iqual: bool = False, case: bool = True) -> int | None:
|
|
141
|
-
num_idx = None
|
|
142
|
-
if iqual:
|
|
143
|
-
for idx, i in enumerate(self):
|
|
144
|
-
if case:
|
|
145
|
-
if i == text:
|
|
146
|
-
num_idx = idx
|
|
147
|
-
break
|
|
148
|
-
else:
|
|
149
|
-
if text.lower() == i.lower():
|
|
150
|
-
num_idx = idx
|
|
151
|
-
break
|
|
152
|
-
else:
|
|
153
|
-
for idx, i in enumerate(self):
|
|
154
|
-
if case:
|
|
155
|
-
if text in i:
|
|
156
|
-
num_idx = idx
|
|
157
|
-
break
|
|
158
|
-
else:
|
|
159
|
-
if text.lower() in i.lower():
|
|
160
|
-
num_idx = idx
|
|
161
|
-
break
|
|
162
|
-
return num_idx
|
|
163
|
-
|
|
164
186
|
def get(self, idx: int) -> str:
|
|
165
187
|
return self[idx]
|
|
166
188
|
|
|
167
|
-
def
|
|
168
|
-
if text is None:
|
|
189
|
+
def find_text(self, text: str, *, iqual: bool = False, case: bool = False) -> str | None:
|
|
190
|
+
if (text is None) or (text == ""):
|
|
169
191
|
raise ValueError(f'{__class__.__name__}: text is None')
|
|
192
|
+
idx_item = self.find_index(text, iqual=iqual, case=case)
|
|
193
|
+
return None if idx_item is None else self[idx_item]
|
|
170
194
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if text == i:
|
|
176
|
-
element = i
|
|
177
|
-
break
|
|
178
|
-
else:
|
|
179
|
-
if text.lower() == i.lower():
|
|
180
|
-
element = i
|
|
181
|
-
break
|
|
182
|
-
else:
|
|
183
|
-
for i in self:
|
|
184
|
-
if case:
|
|
185
|
-
if text in i:
|
|
186
|
-
element = i
|
|
187
|
-
break
|
|
188
|
-
else:
|
|
189
|
-
if text.lower() in i.lower():
|
|
190
|
-
element = i
|
|
191
|
-
break
|
|
192
|
-
return element
|
|
195
|
+
def find_all(self, text: str, *, iqual: bool = False, case: bool = True) -> ListString:
|
|
196
|
+
list_idx: list[int] = find_all_index(text, self, case=case, iqual=iqual)
|
|
197
|
+
if len(list_idx) == 0:
|
|
198
|
+
return ListString([])
|
|
193
199
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if case:
|
|
199
|
-
if i == text:
|
|
200
|
-
items.append(i)
|
|
201
|
-
else:
|
|
202
|
-
if text.lower() == i.lower():
|
|
203
|
-
items.append(i)
|
|
204
|
-
else:
|
|
205
|
-
for i in self:
|
|
206
|
-
if case:
|
|
207
|
-
if text in i:
|
|
208
|
-
items.append(i)
|
|
209
|
-
else:
|
|
210
|
-
if text.lower() in i.lower():
|
|
211
|
-
items.append(i)
|
|
212
|
-
return items
|
|
200
|
+
new_values = ListString([])
|
|
201
|
+
for idx in list_idx:
|
|
202
|
+
new_values.append(self[idx])
|
|
203
|
+
return new_values
|
|
213
204
|
|
|
214
205
|
def count(self, text: str, *, iqual: bool = False, case: bool = True) -> int:
|
|
215
206
|
count: int = 0
|
|
@@ -257,7 +248,7 @@ class HeadValues(ListString):
|
|
|
257
248
|
self.append(i)
|
|
258
249
|
|
|
259
250
|
|
|
260
|
-
class ColumnBody(
|
|
251
|
+
class ColumnBody(ArrayString):
|
|
261
252
|
"""
|
|
262
253
|
Lista de strings nomeada para representar os dados da coluna de uma tabela.
|
|
263
254
|
"""
|
|
@@ -301,7 +292,7 @@ class TextTable(dict):
|
|
|
301
292
|
self[col.col_name] = col
|
|
302
293
|
|
|
303
294
|
|
|
304
|
-
def
|
|
295
|
+
def create_void_dict_map() -> dict[str, ColumnBody]:
|
|
305
296
|
|
|
306
297
|
return {
|
|
307
298
|
ColumnsTable.KEY.value: ColumnBody(ColumnsTable.KEY.value, ListString([])),
|
|
@@ -315,16 +306,16 @@ def create_void_map() -> dict[str, ColumnBody]:
|
|
|
315
306
|
}
|
|
316
307
|
|
|
317
308
|
|
|
318
|
-
def
|
|
309
|
+
def create_void_df_map() -> pd.DataFrame:
|
|
319
310
|
"""
|
|
320
311
|
Retorna um DataFrame() vazio, apenas com as colunas formatadas
|
|
321
312
|
para tabela de arquivos.
|
|
322
313
|
@rtype: pd.DataFrame
|
|
323
314
|
"""
|
|
324
|
-
return pd.DataFrame.from_dict(
|
|
315
|
+
return pd.DataFrame.from_dict(create_void_dict_map())
|
|
325
316
|
|
|
326
317
|
|
|
327
|
-
def
|
|
318
|
+
def create_dict_map_values(
|
|
328
319
|
values: list[str], *,
|
|
329
320
|
page_num: str = 'nan',
|
|
330
321
|
file_path: str = 'nan',
|
|
@@ -343,9 +334,9 @@ def create_map_from_values(
|
|
|
343
334
|
"""
|
|
344
335
|
max_num = len(values)
|
|
345
336
|
if max_num < 1:
|
|
346
|
-
return
|
|
337
|
+
return create_void_dict_map()
|
|
347
338
|
|
|
348
|
-
_map: dict[str, ColumnBody] =
|
|
339
|
+
_map: dict[str, ColumnBody] = create_void_dict_map()
|
|
349
340
|
_map[ColumnsTable.KEY.value].extend([f'{x}' for x in range(0, max_num)])
|
|
350
341
|
_map[ColumnsTable.NUM_PAGE.value].extend([page_num] * max_num)
|
|
351
342
|
_map[ColumnsTable.NUM_LINE.value].extend([f'{x+1}' for x in range(0, max_num)])
|
|
@@ -357,8 +348,9 @@ def create_map_from_values(
|
|
|
357
348
|
return _map
|
|
358
349
|
|
|
359
350
|
|
|
360
|
-
def
|
|
361
|
-
file: File,
|
|
351
|
+
def create_dict_map_from_file(
|
|
352
|
+
file: File,
|
|
353
|
+
values: list[str], *,
|
|
362
354
|
page_num: str = 'nan'
|
|
363
355
|
) -> dict[str, ColumnBody]:
|
|
364
356
|
"""
|
|
@@ -370,16 +362,19 @@ def create_map_from_file_values(
|
|
|
370
362
|
"""
|
|
371
363
|
max_num = len(values)
|
|
372
364
|
if max_num < 1:
|
|
373
|
-
return
|
|
374
|
-
return
|
|
365
|
+
return create_void_dict_map()
|
|
366
|
+
return create_dict_map_values(
|
|
375
367
|
values, page_num=page_num, file_path=file.absolute(), dir_path=file.dirname(), file_type=file.extension()
|
|
376
368
|
)
|
|
377
369
|
|
|
378
370
|
|
|
379
|
-
def get_text_from_file(file: str) -> ListString:
|
|
371
|
+
def get_text_from_file(file: str | File) -> ListString:
|
|
380
372
|
"""
|
|
381
373
|
Retorna o texto de um arquivo .txt em formato de lista
|
|
382
374
|
"""
|
|
375
|
+
if isinstance(file, File):
|
|
376
|
+
file = file.absolute()
|
|
377
|
+
|
|
383
378
|
try:
|
|
384
379
|
with open(file, 'rt') as f:
|
|
385
380
|
lines = ListString(f.readlines())
|