ocr-stream 2.3.2__tar.gz → 2.4.1__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.
- {ocr_stream-2.3.2 → ocr_stream-2.4.1}/PKG-INFO +2 -2
- ocr_stream-2.4.1/ocr_stream/__init__.py +322 -0
- {ocr_stream-2.3.2 → ocr_stream-2.4.1}/ocr_stream/bin_tess/__init__.py +3 -0
- {ocr_stream-2.3.2 → ocr_stream-2.4.1}/ocr_stream/modules/__init__.py +4 -1
- {ocr_stream-2.3.2 → ocr_stream-2.4.1}/ocr_stream/ocr/__init__.py +13 -6
- {ocr_stream-2.3.2 → ocr_stream-2.4.1}/ocr_stream/recognize.py +50 -17
- {ocr_stream-2.3.2 → ocr_stream-2.4.1}/ocr_stream.egg-info/PKG-INFO +2 -2
- {ocr_stream-2.3.2 → ocr_stream-2.4.1}/ocr_stream.egg-info/requires.txt +1 -1
- {ocr_stream-2.3.2 → ocr_stream-2.4.1}/pyproject.toml +2 -2
- ocr_stream-2.3.2/ocr_stream/__init__.py +0 -269
- {ocr_stream-2.3.2 → ocr_stream-2.4.1}/README.md +0 -0
- {ocr_stream-2.3.2 → ocr_stream-2.4.1}/ocr_stream.egg-info/SOURCES.txt +0 -0
- {ocr_stream-2.3.2 → ocr_stream-2.4.1}/ocr_stream.egg-info/dependency_links.txt +0 -0
- {ocr_stream-2.3.2 → ocr_stream-2.4.1}/ocr_stream.egg-info/top_level.txt +0 -0
- {ocr_stream-2.3.2 → ocr_stream-2.4.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ocr_stream
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.1
|
|
4
4
|
Summary: Extrair textos de documentos digitalizados e imagens.
|
|
5
5
|
Author: Bruno Chaves
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -12,7 +12,7 @@ Requires-Dist: pandas
|
|
|
12
12
|
Requires-Dist: requests>=2.32.4
|
|
13
13
|
Requires-Dist: opencv-python
|
|
14
14
|
Requires-Dist: pytesseract
|
|
15
|
-
Requires-Dist: convert_stream>=2.
|
|
15
|
+
Requires-Dist: convert_stream>=2.4.1
|
|
16
16
|
Requires-Dist: soup_files>=1.4
|
|
17
17
|
|
|
18
18
|
# ocrlib
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
from ocr_stream.modules import *
|
|
5
|
+
from ocr_stream.bin_tess import BinTesseract, get_path_tesseract_sys
|
|
6
|
+
from ocr_stream.ocr import TesseractOcr, TextRecognized
|
|
7
|
+
from ocr_stream.recognize import (
|
|
8
|
+
RecognizeImage, RecognizePdf, VoidPbar, ProgressVoid
|
|
9
|
+
)
|
|
10
|
+
import convert_stream as cs
|
|
11
|
+
import pandas as pd
|
|
12
|
+
import soup_files as sp
|
|
13
|
+
|
|
14
|
+
__version__ = '2.4'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def read_image(img: cs.ImageObject, rec: RecognizeImage = RecognizeImage()) -> TextRecognized:
|
|
18
|
+
_txt = rec.image_recognize(img)
|
|
19
|
+
_txt.metadata = img.metadata
|
|
20
|
+
return _txt
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def read_images(
|
|
24
|
+
images: list[cs.ImageObject], *,
|
|
25
|
+
rec: RecognizeImage = RecognizeImage(),
|
|
26
|
+
pbar: sp.ProgressBarAdapter = sp.CreatePbar().get()
|
|
27
|
+
) -> cs.DocumentPdf:
|
|
28
|
+
pbar.start()
|
|
29
|
+
print()
|
|
30
|
+
max_num = len(images)
|
|
31
|
+
pages = []
|
|
32
|
+
|
|
33
|
+
for idx, image in enumerate(images):
|
|
34
|
+
pbar.update(
|
|
35
|
+
((idx + 1) / max_num) * 100,
|
|
36
|
+
f'[OCR IMAGENS] {idx + 1} / {max_num}',
|
|
37
|
+
)
|
|
38
|
+
out = read_image(image, rec=rec).to_page_pdf()
|
|
39
|
+
pages.append(out)
|
|
40
|
+
tmp_doc = cs.DocumentPdf.create_from_pages(pages)
|
|
41
|
+
print()
|
|
42
|
+
pbar.stop()
|
|
43
|
+
return tmp_doc
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def read_document(
|
|
47
|
+
document: cs.DocumentPdf, *,
|
|
48
|
+
dpi: int = 300,
|
|
49
|
+
pbar: sp.ProgressBarAdapter = sp.CreatePbar().get()
|
|
50
|
+
) -> cs.DocumentPdf:
|
|
51
|
+
pbar.start()
|
|
52
|
+
print()
|
|
53
|
+
_metadata: cs.MetaDataFile = document.metadata
|
|
54
|
+
recognized_items: list[TextRecognized] = []
|
|
55
|
+
pages_pdf: list[cs.PageDocumentPdf] = document.to_pages()
|
|
56
|
+
total = len(pages_pdf)
|
|
57
|
+
void_pbar = ProgressVoid()
|
|
58
|
+
for num, page in enumerate(pages_pdf):
|
|
59
|
+
pbar.update(
|
|
60
|
+
((num + 1) / total) * 100,
|
|
61
|
+
f'[OCR PDF] {num + 1}/{total} {page.metadata.name}',
|
|
62
|
+
)
|
|
63
|
+
convert: cs.ConvertPdfToImages = cs.ConvertPdfToImages(cs.DocumentPdf.create_from_pages([page]))
|
|
64
|
+
convert.set_pbar(void_pbar)
|
|
65
|
+
images: list[cs.ImageObject] = convert.to_images(dpi=dpi)
|
|
66
|
+
for im in images:
|
|
67
|
+
recognized_items.append(read_image(im))
|
|
68
|
+
print()
|
|
69
|
+
final_pages: list[cs.PageDocumentPdf] = [x.to_page_pdf() for x in recognized_items]
|
|
70
|
+
final_doc: cs.DocumentPdf = cs.DocumentPdf.create_from_pages(final_pages)
|
|
71
|
+
final_doc.metadata = _metadata
|
|
72
|
+
return final_doc
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class SearchableTextImage(object):
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
cs.ColumnsTable.KEY.value: cs.ColumnBody(cs.ColumnsTable.KEY.value, ListString([])),
|
|
80
|
+
cs.ColumnsTable.NUM_PAGE.value: cs.ColumnBody(cs.ColumnsTable.NUM_PAGE.value, ListString([])),
|
|
81
|
+
cs.ColumnsTable.NUM_LINE.value: cs.ColumnBody(cs.ColumnsTable.NUM_LINE.value, ListString([])),
|
|
82
|
+
cs.ColumnsTable.TEXT.value: cs.ColumnBody(cs.ColumnsTable.TEXT.value, ListString([])),
|
|
83
|
+
cs.ColumnsTable.FILE_NAME.value: cs.ColumnBody(cs.ColumnsTable.FILE_NAME.value, ListString([])),
|
|
84
|
+
cs.ColumnsTable.FILETYPE.value: cs.ColumnBody(cs.ColumnsTable.FILETYPE.value, ListString([])),
|
|
85
|
+
cs.ColumnsTable.FILE_PATH.value: cs.ColumnBody(cs.ColumnsTable.FILE_PATH.value, ListString([])),
|
|
86
|
+
cs.ColumnsTable.DIR.value: cs.ColumnBody(cs.ColumnsTable.DIR.value, ListString([])),
|
|
87
|
+
}
|
|
88
|
+
"""
|
|
89
|
+
def __init__(self, silent: bool = False):
|
|
90
|
+
self.silent = silent
|
|
91
|
+
self.elements: cs.DictTextTable = cs.DictTextTable.create_void_dict()
|
|
92
|
+
|
|
93
|
+
def __repr__(self):
|
|
94
|
+
return f'SearchableTextPdf: {self.elements}'
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def length(self) -> int:
|
|
98
|
+
return len(self.elements[cs.ColumnsTable.TEXT.value])
|
|
99
|
+
|
|
100
|
+
def is_empty(self) -> bool:
|
|
101
|
+
return len(self.elements[cs.ColumnsTable.TEXT.value]) == 0
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def columns(self) -> cs.HeadValues:
|
|
105
|
+
return cs.HeadValues([cs.HeadCell(x) for x in list(self.elements.keys())])
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def first(self) -> dict[str, str]:
|
|
109
|
+
if self.is_empty():
|
|
110
|
+
return {}
|
|
111
|
+
cols = self.columns
|
|
112
|
+
_first = {}
|
|
113
|
+
for c in cols:
|
|
114
|
+
_first[c] = self.elements[c][0]
|
|
115
|
+
return _first
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def last(self) -> dict[str, str]:
|
|
119
|
+
if self.is_empty():
|
|
120
|
+
return {}
|
|
121
|
+
if self.is_empty():
|
|
122
|
+
return {}
|
|
123
|
+
cols = self.columns
|
|
124
|
+
_last = {}
|
|
125
|
+
for c in cols:
|
|
126
|
+
_last[c] = self.elements[c][-1]
|
|
127
|
+
return _last
|
|
128
|
+
|
|
129
|
+
def get_item(self, idx: int) -> dict[str, str]:
|
|
130
|
+
_current_item = {}
|
|
131
|
+
cols = self.columns
|
|
132
|
+
try:
|
|
133
|
+
for col in cols:
|
|
134
|
+
_current_item[col] = self.elements[col][idx]
|
|
135
|
+
except Exception as err:
|
|
136
|
+
if not self.silent:
|
|
137
|
+
print(err)
|
|
138
|
+
return _current_item
|
|
139
|
+
else:
|
|
140
|
+
return _current_item
|
|
141
|
+
|
|
142
|
+
def add_line(self, line: dict[str, str]) -> None:
|
|
143
|
+
cols: cs.HeadValues = self.columns
|
|
144
|
+
cols_line: cs.HeadValues = cs.HeadValues([cs.HeadCell(y) for y in list(line.keys())])
|
|
145
|
+
for col in cols:
|
|
146
|
+
if cols_line.contains(col, case=True, iqual=True):
|
|
147
|
+
self.elements[col].append(line[col])
|
|
148
|
+
|
|
149
|
+
def clear(self) -> None:
|
|
150
|
+
for _k in self.elements.keys():
|
|
151
|
+
self.elements[_k].clear()
|
|
152
|
+
|
|
153
|
+
def to_string(self) -> str:
|
|
154
|
+
"""
|
|
155
|
+
Retorna o texto da coluna TEXT em formato de string
|
|
156
|
+
ou 'nas' em caso de erro nas = Not a String
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
return ' '.join(self.elements[cs.ColumnsTable.TEXT.value])
|
|
160
|
+
except Exception as e:
|
|
161
|
+
print(e)
|
|
162
|
+
return 'nas'
|
|
163
|
+
|
|
164
|
+
def to_data_frame(self) -> pd.DataFrame:
|
|
165
|
+
return pd.DataFrame.from_dict(self.elements)
|
|
166
|
+
|
|
167
|
+
def to_file_json(self, file: sp.File):
|
|
168
|
+
"""Exporta os dados da busca para um arquivo JSON"""
|
|
169
|
+
dt = sp.JsonConvert.from_dict(self.elements).to_json_data()
|
|
170
|
+
dt.to_file(file)
|
|
171
|
+
|
|
172
|
+
def to_file_excel(self, file: sp.File):
|
|
173
|
+
"""Exporta os dados da busca para um arquivo .XLSX"""
|
|
174
|
+
self.to_data_frame().to_excel(file.absolute(), index=False)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class ImageFinder(object):
|
|
178
|
+
"""
|
|
179
|
+
Classe para Filtrar texto em Imagens
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def __init__(self):
|
|
183
|
+
self.docs_collection: dict[str, cs.ImageObject] = {}
|
|
184
|
+
self.recognize: RecognizeImage = RecognizeImage()
|
|
185
|
+
self.pbar: sp.ProgressBarAdapter = sp.ProgressBarAdapter()
|
|
186
|
+
|
|
187
|
+
def is_empty(self) -> bool:
|
|
188
|
+
return len(self.docs_collection) == 0
|
|
189
|
+
|
|
190
|
+
def clear(self) -> None:
|
|
191
|
+
self.docs_collection.clear()
|
|
192
|
+
|
|
193
|
+
def add_image(self, img: sp.File | bytes | BytesIO | str | cs.ImageObject) -> None:
|
|
194
|
+
if isinstance(img, sp.File):
|
|
195
|
+
img = cs.ImageObject(img)
|
|
196
|
+
elif isinstance(img, cs.ImageObject):
|
|
197
|
+
pass
|
|
198
|
+
elif isinstance(img, bytes):
|
|
199
|
+
img = cs.ImageObject(img)
|
|
200
|
+
elif isinstance(img, str):
|
|
201
|
+
img = cs.ImageObject(img)
|
|
202
|
+
elif isinstance(img, BytesIO):
|
|
203
|
+
img = cs.ImageObject(img)
|
|
204
|
+
|
|
205
|
+
if not img.metadata.file_path.is_empty:
|
|
206
|
+
self.docs_collection[img.metadata.file_path] = img
|
|
207
|
+
else:
|
|
208
|
+
self.docs_collection[img.metadata.name] = img
|
|
209
|
+
|
|
210
|
+
def add_images(self, images: list[sp.File] | list[cs.ImageObject] | list[bytes] | list[BytesIO]) -> None:
|
|
211
|
+
for f in images:
|
|
212
|
+
self.add_image(f)
|
|
213
|
+
|
|
214
|
+
def add_directory_images(self, dir_pdf: sp.Directory) -> None:
|
|
215
|
+
files = sp.InputFiles(dir_pdf).get_files(file_type=sp.LibraryDocs.IMAGE)
|
|
216
|
+
if len(files) > 0:
|
|
217
|
+
self.add_images(files)
|
|
218
|
+
|
|
219
|
+
def find(
|
|
220
|
+
self, text: str,
|
|
221
|
+
separator: str = '\n',
|
|
222
|
+
iqual: bool = False,
|
|
223
|
+
case: bool = False,
|
|
224
|
+
silent: bool = False,
|
|
225
|
+
) -> SearchableTextImage:
|
|
226
|
+
"""
|
|
227
|
+
Filtrar texto retornando a primeira ocorrência do Documento PDF.
|
|
228
|
+
"""
|
|
229
|
+
_searchable = SearchableTextImage(silent)
|
|
230
|
+
if self.is_empty():
|
|
231
|
+
return _searchable
|
|
232
|
+
|
|
233
|
+
total_num: int = len(self.docs_collection.keys())
|
|
234
|
+
for n, file_key in enumerate(self.docs_collection.keys()):
|
|
235
|
+
self.pbar.update(
|
|
236
|
+
((n + 1) / total_num) * 100,
|
|
237
|
+
f'[PESQUISANDO]: {n+1}/{total_num}',
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
current_img: cs.ImageObject = self.docs_collection[file_key]
|
|
241
|
+
txt_in_img = self.recognize.image_to_string(current_img)
|
|
242
|
+
if (txt_in_img == '') or (txt_in_img is None):
|
|
243
|
+
self.pbar.update_text(f'ERRO: {n+1} {current_img.metadata.name}')
|
|
244
|
+
continue
|
|
245
|
+
try:
|
|
246
|
+
fd = cs.text.FindText(txt_in_img, separator=separator)
|
|
247
|
+
idx = fd.find_index(text, iqual=iqual, case=case)
|
|
248
|
+
if idx is None:
|
|
249
|
+
continue
|
|
250
|
+
math_text: str | None = fd.get_index(idx)
|
|
251
|
+
except Exception as err:
|
|
252
|
+
print(f'{__class__.__name__} {err}')
|
|
253
|
+
else:
|
|
254
|
+
new_line: dict[str, str] = {
|
|
255
|
+
cs.ColumnsTable.KEY.value: f'{idx}',
|
|
256
|
+
cs.ColumnsTable.NUM_PAGE.value: 'nan',
|
|
257
|
+
cs.ColumnsTable.NUM_LINE.value: f'{idx + 1}',
|
|
258
|
+
cs.ColumnsTable.TEXT.value: math_text,
|
|
259
|
+
cs.ColumnsTable.FILE_NAME.value: current_img.metadata.file_path,
|
|
260
|
+
cs.ColumnsTable.FILETYPE.value: current_img.metadata.extension,
|
|
261
|
+
cs.ColumnsTable.FILE_PATH.value: current_img.metadata.dir_path,
|
|
262
|
+
cs.ColumnsTable.DIR.value: current_img.metadata.dir_path,
|
|
263
|
+
}
|
|
264
|
+
_searchable.add_line(new_line)
|
|
265
|
+
return _searchable
|
|
266
|
+
return _searchable
|
|
267
|
+
|
|
268
|
+
def find_all(
|
|
269
|
+
self, text: str,
|
|
270
|
+
separator: str = '\n',
|
|
271
|
+
iqual: bool = False,
|
|
272
|
+
case: bool = False,
|
|
273
|
+
silent: bool = False,
|
|
274
|
+
) -> SearchableTextImage:
|
|
275
|
+
"""
|
|
276
|
+
Filtrar texto em documento PDF e retorna todas as ocorrências do texto
|
|
277
|
+
encontradas no documento, incluindo o número da linha, página e nome do arquivo
|
|
278
|
+
em cada ocorrência.
|
|
279
|
+
"""
|
|
280
|
+
_searchable = SearchableTextImage(silent)
|
|
281
|
+
if self.is_empty():
|
|
282
|
+
return _searchable
|
|
283
|
+
|
|
284
|
+
total_num: int = len(self.docs_collection.keys())
|
|
285
|
+
for n, file_key in enumerate(self.docs_collection.keys()):
|
|
286
|
+
self.pbar.update(
|
|
287
|
+
((n + 1) / total_num) * 100,
|
|
288
|
+
f'[PESQUISANDO]: {n + 1}/{total_num}',
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
current_img: cs.ImageObject = self.docs_collection[file_key]
|
|
292
|
+
txt_in_img = self.recognize.image_to_string(current_img)
|
|
293
|
+
if (txt_in_img == '') or (txt_in_img is None):
|
|
294
|
+
self.pbar.update_text(f'ERRO: {n + 1} {current_img.metadata.name}')
|
|
295
|
+
continue
|
|
296
|
+
try:
|
|
297
|
+
fd = cs.text.FindText(txt_in_img, separator=separator)
|
|
298
|
+
idx = fd.find_index(text, iqual=iqual, case=case)
|
|
299
|
+
if idx is None:
|
|
300
|
+
continue
|
|
301
|
+
math_text: str | None = fd.get_index(idx)
|
|
302
|
+
except Exception as err:
|
|
303
|
+
print(f'{__class__.__name__} {err}')
|
|
304
|
+
else:
|
|
305
|
+
new_line: dict[str, str] = {
|
|
306
|
+
cs.ColumnsTable.KEY.value: f'{idx}',
|
|
307
|
+
cs.ColumnsTable.NUM_PAGE.value: 'nan',
|
|
308
|
+
cs.ColumnsTable.NUM_LINE.value: f'{idx + 1}',
|
|
309
|
+
cs.ColumnsTable.TEXT.value: math_text,
|
|
310
|
+
cs.ColumnsTable.FILE_NAME.value: current_img.metadata.file_path,
|
|
311
|
+
cs.ColumnsTable.FILETYPE.value: current_img.metadata.extension,
|
|
312
|
+
cs.ColumnsTable.FILE_PATH.value: current_img.metadata.dir_path,
|
|
313
|
+
cs.ColumnsTable.DIR.value: current_img.metadata.dir_path,
|
|
314
|
+
}
|
|
315
|
+
_searchable.add_line(new_line)
|
|
316
|
+
return _searchable
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
__all__ = [
|
|
320
|
+
'ImageFinder', 'SearchableTextImage', 'read_images',
|
|
321
|
+
'read_document', 'RecognizeImage', 'RecognizePdf', 'VoidPbar', 'ProgressVoid'
|
|
322
|
+
]
|
|
@@ -29,7 +29,7 @@ try:
|
|
|
29
29
|
except Exception as err:
|
|
30
30
|
print(err)
|
|
31
31
|
pyocr = object
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
|
|
34
34
|
if MOD_PYTESSERACT and MOD_PYOCR:
|
|
35
35
|
ModuleOcr: TypeAlias = Union[pyocr, pytesseract]
|
|
@@ -43,3 +43,6 @@ elif MOD_PYOCR:
|
|
|
43
43
|
else:
|
|
44
44
|
ModuleOcr: TypeAlias = None
|
|
45
45
|
DEFAULT_LIB_OCR = LibOcr.NOT_IMPLEMENTED
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
__all__ = ['LibOcr', 'DEFAULT_LIB_OCR']
|
|
@@ -7,10 +7,10 @@ from typing import Union
|
|
|
7
7
|
from io import BytesIO
|
|
8
8
|
from pandas import DataFrame
|
|
9
9
|
from soup_files import File
|
|
10
|
-
from convert_stream import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
from convert_stream.mod_types import
|
|
10
|
+
from convert_stream import DocumentPdf, PageDocumentPdf
|
|
11
|
+
from convert_stream.mod_types.modules import DEFAULT_LIB_IMAGE, LibImage
|
|
12
|
+
from convert_stream.image import ImageObject
|
|
13
|
+
from convert_stream.mod_types import MetaDataItem, MetaDataFile
|
|
14
14
|
from convert_stream.mod_types.table_types import ColumnBody
|
|
15
15
|
from ocr_stream.modules import (
|
|
16
16
|
LibOcr, DEFAULT_LIB_OCR, pytesseract, Output, pyocr,
|
|
@@ -31,8 +31,9 @@ class TextRecognized(object):
|
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
33
|
def __init__(self, bytes_recognized: bytes):
|
|
34
|
-
self.metadata:
|
|
34
|
+
self.metadata: MetaDataFile = MetaDataFile()
|
|
35
35
|
self.bytes_recognized: bytes = bytes_recognized
|
|
36
|
+
|
|
36
37
|
self.list_bad_char: list[str] = [
|
|
37
38
|
':', ',', ';', '$', '=',
|
|
38
39
|
'!', '}', '{', '(', ')',
|
|
@@ -51,7 +52,10 @@ class TextRecognized(object):
|
|
|
51
52
|
return doc.to_pages()[0]
|
|
52
53
|
|
|
53
54
|
def to_document(self) -> DocumentPdf:
|
|
54
|
-
|
|
55
|
+
tmp_doc = DocumentPdf(BytesIO(self.bytes_recognized))
|
|
56
|
+
tmp_doc.metadata.file_path = self.metadata.file_path
|
|
57
|
+
tmp_doc.metadata.dir_path = self.metadata.dir_path
|
|
58
|
+
return tmp_doc
|
|
55
59
|
|
|
56
60
|
def to_dict(self, separator: str = '\n') -> dict[str, ColumnBody]:
|
|
57
61
|
return self.to_page_pdf().to_dict(separator=separator)
|
|
@@ -205,3 +209,6 @@ class TesseractOcr(object):
|
|
|
205
209
|
else:
|
|
206
210
|
raise ValueError(f'{lib_ocr} is not supported')
|
|
207
211
|
return cls(md)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
__all__ = ['TesseractOcr', 'TextRecognized']
|
|
@@ -1,11 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
from __future__ import annotations
|
|
3
|
-
from convert_stream import
|
|
3
|
+
from convert_stream import DocumentPdf, PageDocumentPdf
|
|
4
|
+
from convert_stream.pdf import ConvertPdfToImages
|
|
5
|
+
from convert_stream.image import ImageObject
|
|
4
6
|
from ocr_stream.bin_tess import BinTesseract
|
|
5
7
|
from ocr_stream.modules import LibOcr, DEFAULT_LIB_OCR
|
|
6
8
|
from ocr_stream.ocr import TesseractOcr, TextRecognized
|
|
7
9
|
from pandas import DataFrame
|
|
8
|
-
|
|
10
|
+
import soup_files as sp
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class VoidPbar(sp.ABCProgressBar):
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__()
|
|
17
|
+
|
|
18
|
+
def set_percent(self, percent: float):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def set_text(self, text: str):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ProgressVoid(sp.ProgressBarAdapter):
|
|
26
|
+
|
|
27
|
+
def __init__(self, progress_bar: sp.ABCProgressBar = VoidPbar()):
|
|
28
|
+
super().__init__(progress_bar)
|
|
29
|
+
|
|
30
|
+
def update(self, percent: float, status: str = "-"):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def update_percent(self, percent: float = 0):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def update_text(self, text: str = "-"):
|
|
37
|
+
pass
|
|
9
38
|
|
|
10
39
|
|
|
11
40
|
class RecognizeImage(object):
|
|
@@ -30,7 +59,7 @@ class RecognizeImage(object):
|
|
|
30
59
|
def bin_tesseract(self) -> BinTesseract:
|
|
31
60
|
return self.module_ocr.mod_ocr.bin_tess
|
|
32
61
|
|
|
33
|
-
def image_content_data(self, img: ImageObject | File) -> DataFrame:
|
|
62
|
+
def image_content_data(self, img: ImageObject | sp.File) -> DataFrame:
|
|
34
63
|
"""
|
|
35
64
|
Retorna uma tabela estruturada com os dados reconhecidos da imagem.
|
|
36
65
|
|
|
@@ -39,35 +68,35 @@ class RecognizeImage(object):
|
|
|
39
68
|
nível do OCR e confiabilidade, entre outras.
|
|
40
69
|
|
|
41
70
|
Parâmetros:
|
|
42
|
-
img (ImageObject | File): A imagem a ser processada, podendo ser um objeto
|
|
43
|
-
`ImageObject` ou um arquivo `File`.
|
|
71
|
+
img (ImageObject | sp.File): A imagem a ser processada, podendo ser um objeto
|
|
72
|
+
`ImageObject` ou um arquivo `sp.File`.
|
|
44
73
|
|
|
45
74
|
Retorna:
|
|
46
75
|
DataFrame: Uma tabela contendo os dados reconhecidos na imagem.
|
|
47
76
|
|
|
48
77
|
Lança:
|
|
49
|
-
ValueError: Se o tipo do parâmetro `img` não for `ImageObject` ou `File`.
|
|
78
|
+
ValueError: Se o tipo do parâmetro `img` não for `ImageObject` ou `sp.File`.
|
|
50
79
|
|
|
51
80
|
@param img: Imagem a ser processada, podendo ser uma instância de `ImageObject`
|
|
52
|
-
ou um arquivo `File`.
|
|
53
|
-
@type img: ImageObject | File
|
|
81
|
+
ou um arquivo `sp.File`.
|
|
82
|
+
@type img: ImageObject | sp.File
|
|
54
83
|
|
|
55
84
|
@rtype: pandas.DataFrame
|
|
56
85
|
@return: Um DataFrame com os dados reconhecidos na imagem.
|
|
57
86
|
|
|
58
|
-
@raise ValueError: Caso o tipo de `img` não seja `ImageObject` nem `File`.
|
|
87
|
+
@raise ValueError: Caso o tipo de `img` não seja `ImageObject` nem `sp.File`.
|
|
59
88
|
"""
|
|
60
89
|
return self.image_recognize(img).to_dataframe()
|
|
61
90
|
|
|
62
|
-
def image_recognize(self, img: ImageObject | File, *, land_scape: bool = False) -> TextRecognized:
|
|
63
|
-
if isinstance(img, File):
|
|
91
|
+
def image_recognize(self, img: ImageObject | sp.File, *, land_scape: bool = False) -> TextRecognized:
|
|
92
|
+
if isinstance(img, sp.File):
|
|
64
93
|
img = ImageObject(img)
|
|
65
94
|
if land_scape:
|
|
66
95
|
img.set_landscape()
|
|
67
96
|
rec: TextRecognized = self.module_ocr.to_reconize(img)
|
|
68
97
|
return rec
|
|
69
98
|
|
|
70
|
-
def image_to_string(self, img: ImageObject | File) -> str:
|
|
99
|
+
def image_to_string(self, img: ImageObject | sp.File) -> str:
|
|
71
100
|
return self.module_ocr.to_string(img)
|
|
72
101
|
|
|
73
102
|
|
|
@@ -79,9 +108,9 @@ class RecognizePdf(object):
|
|
|
79
108
|
lib_ocr: LibOcr = DEFAULT_LIB_OCR,
|
|
80
109
|
):
|
|
81
110
|
self.recognize_image: RecognizeImage = RecognizeImage(bin_tess, lib_ocr=lib_ocr)
|
|
82
|
-
self.pbar: ProgressBarAdapter = CreatePbar().get()
|
|
111
|
+
self.pbar: sp.ProgressBarAdapter = sp.CreatePbar().get()
|
|
83
112
|
|
|
84
|
-
def set_pbar(self, p: ProgressBarAdapter):
|
|
113
|
+
def set_pbar(self, p: sp.ProgressBarAdapter):
|
|
85
114
|
self.pbar = p
|
|
86
115
|
|
|
87
116
|
def recognize_page_pdf(self, page: PageDocumentPdf, *, dpi: int = 300) -> DocumentPdf:
|
|
@@ -91,34 +120,38 @@ class RecognizePdf(object):
|
|
|
91
120
|
"""
|
|
92
121
|
_doc = DocumentPdf.create_from_pages([page])
|
|
93
122
|
_conv = ConvertPdfToImages.create(_doc)
|
|
123
|
+
_conv.set_pbar(self.pbar)
|
|
94
124
|
img: ImageObject = _conv.to_images(dpi=dpi)[0]
|
|
95
125
|
text_recognized: TextRecognized = self.recognize_image.image_recognize(img)
|
|
96
126
|
del _doc
|
|
97
127
|
del _conv
|
|
98
128
|
del img
|
|
129
|
+
text_recognized.metadata.file_path = page.metadata.file_path
|
|
130
|
+
text_recognized.metadata.dir_path = page.metadata.dir_path
|
|
99
131
|
return text_recognized.to_document()
|
|
100
132
|
|
|
101
133
|
def recognize_document(self, doc: DocumentPdf, *, dpi: int = 300) -> DocumentPdf:
|
|
102
134
|
_pages: list[PageDocumentPdf] = []
|
|
103
135
|
_pages.clear()
|
|
104
136
|
_conv = ConvertPdfToImages.create(doc)
|
|
137
|
+
_conv.set_pbar(self.pbar)
|
|
105
138
|
images: list[ImageObject] = _conv.to_images(dpi=dpi)
|
|
106
139
|
max_num: int = len(images)
|
|
107
140
|
for num, img in enumerate(images):
|
|
108
141
|
self.pbar.update(
|
|
109
142
|
((num + 1) / max_num) * 100,
|
|
110
|
-
f'
|
|
143
|
+
f'OCR Documento [{num+1} de {max_num}]'
|
|
111
144
|
)
|
|
112
145
|
text_recognized = self.recognize_image.image_recognize(img)
|
|
113
146
|
_pages.append(text_recognized.to_page_pdf())
|
|
114
147
|
print()
|
|
115
148
|
self.pbar.stop()
|
|
116
149
|
new_doc = DocumentPdf.create_from_pages(_pages)
|
|
150
|
+
new_doc.metadata = doc.metadata
|
|
117
151
|
return new_doc
|
|
118
152
|
|
|
119
|
-
def recognize_file_pdf(self, file: File, *, dpi: int = 300) -> DocumentPdf:
|
|
153
|
+
def recognize_file_pdf(self, file: sp.File, *, dpi: int = 300) -> DocumentPdf:
|
|
120
154
|
file_doc = DocumentPdf.create_from_file(file)
|
|
121
155
|
recognized_doc = self.recognize_document(file_doc, dpi=dpi)
|
|
122
156
|
del file_doc
|
|
123
157
|
return recognized_doc
|
|
124
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ocr_stream
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.1
|
|
4
4
|
Summary: Extrair textos de documentos digitalizados e imagens.
|
|
5
5
|
Author: Bruno Chaves
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -12,7 +12,7 @@ Requires-Dist: pandas
|
|
|
12
12
|
Requires-Dist: requests>=2.32.4
|
|
13
13
|
Requires-Dist: opencv-python
|
|
14
14
|
Requires-Dist: pytesseract
|
|
15
|
-
Requires-Dist: convert_stream>=2.
|
|
15
|
+
Requires-Dist: convert_stream>=2.4.1
|
|
16
16
|
Requires-Dist: soup_files>=1.4
|
|
17
17
|
|
|
18
18
|
# ocrlib
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ocr_stream"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.4.1"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Bruno Chaves"},
|
|
10
10
|
]
|
|
@@ -21,7 +21,7 @@ dependencies = [
|
|
|
21
21
|
"requests>=2.32.4",
|
|
22
22
|
"opencv-python",
|
|
23
23
|
"pytesseract",
|
|
24
|
-
"convert_stream>=2.
|
|
24
|
+
"convert_stream>=2.4.1",
|
|
25
25
|
"soup_files>=1.4",
|
|
26
26
|
]
|
|
27
27
|
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
from ocr_stream.modules import *
|
|
4
|
-
from ocr_stream.bin_tess import BinTesseract, get_path_tesseract_sys
|
|
5
|
-
from ocr_stream.ocr import TesseractOcr, TextRecognized, DEFAULT_LIB_OCR
|
|
6
|
-
from ocr_stream.recognize import RecognizeImage, RecognizePdf
|
|
7
|
-
from soup_files import File
|
|
8
|
-
from convert_stream import (
|
|
9
|
-
ImageObject, DocumentPdf, ConvertPdfToImages, ColumnsTable, CollectionImages, FindText
|
|
10
|
-
)
|
|
11
|
-
from pandas import DataFrame
|
|
12
|
-
from soup_files import JsonConvert, ProgressBarAdapter, CreatePbar
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def read_images(
|
|
16
|
-
images: list[ImageObject], *,
|
|
17
|
-
pbar: ProgressBarAdapter = CreatePbar().get()
|
|
18
|
-
) -> DocumentPdf:
|
|
19
|
-
pbar.start()
|
|
20
|
-
print()
|
|
21
|
-
max_num = len(images)
|
|
22
|
-
pages = []
|
|
23
|
-
rec = RecognizeImage()
|
|
24
|
-
for idx, image in enumerate(images):
|
|
25
|
-
pbar.update(
|
|
26
|
-
((idx + 1) / max_num) * 100,
|
|
27
|
-
f'[OCR] {idx + 1} / {max_num}',
|
|
28
|
-
)
|
|
29
|
-
out = rec.image_recognize(image).to_page_pdf()
|
|
30
|
-
pages.append(out)
|
|
31
|
-
tmp_doc = DocumentPdf.create_from_pages(pages)
|
|
32
|
-
print()
|
|
33
|
-
pbar.stop()
|
|
34
|
-
return tmp_doc
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def read_document(
|
|
38
|
-
document: DocumentPdf, *,
|
|
39
|
-
dpi: int = 300,
|
|
40
|
-
pbar: ProgressBarAdapter = CreatePbar().get()
|
|
41
|
-
) -> DocumentPdf:
|
|
42
|
-
pbar.start()
|
|
43
|
-
print()
|
|
44
|
-
convert = ConvertPdfToImages.create(document)
|
|
45
|
-
convert.set_pbar(pbar)
|
|
46
|
-
images = convert.to_images(dpi=dpi)
|
|
47
|
-
print()
|
|
48
|
-
return read_images(images, pbar=pbar)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class SearchableTextImage(object):
|
|
52
|
-
"""
|
|
53
|
-
Esta classe serve para armazenar textos filtrados em documentos PDF
|
|
54
|
-
guardando o texto buscado e outros elementos como a página em que o texto foi
|
|
55
|
-
encontrado, linha e o nome do respetivo arquivo.
|
|
56
|
-
"""
|
|
57
|
-
def __init__(self, silent: bool = False):
|
|
58
|
-
self.silent = silent
|
|
59
|
-
self.elements: dict[str, list[str]] = {
|
|
60
|
-
ColumnsTable.NUM_LINE.value: [],
|
|
61
|
-
ColumnsTable.TEXT.value: [],
|
|
62
|
-
ColumnsTable.FILE_PATH.value: [],
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
def __repr__(self):
|
|
66
|
-
return f'{__class__.__name__}: {self.elements}'
|
|
67
|
-
|
|
68
|
-
def is_empty(self) -> bool:
|
|
69
|
-
return len(self.elements[ColumnsTable.TEXT.value]) == 0
|
|
70
|
-
|
|
71
|
-
@property
|
|
72
|
-
def first(self) -> dict[str, str]:
|
|
73
|
-
if self.is_empty():
|
|
74
|
-
return {}
|
|
75
|
-
return {
|
|
76
|
-
ColumnsTable.NUM_LINE.value: self.elements[ColumnsTable.NUM_LINE.value][0],
|
|
77
|
-
ColumnsTable.TEXT.value: self.elements[ColumnsTable.TEXT.value][0],
|
|
78
|
-
ColumnsTable.FILE_PATH.value: self.elements[ColumnsTable.FILE_PATH.value][0],
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
@property
|
|
82
|
-
def last(self) -> dict[str, str]:
|
|
83
|
-
if self.is_empty():
|
|
84
|
-
return {}
|
|
85
|
-
return {
|
|
86
|
-
ColumnsTable.NUM_LINE.value: self.elements[ColumnsTable.NUM_LINE.value][-1],
|
|
87
|
-
ColumnsTable.TEXT.value: self.elements[ColumnsTable.TEXT.value][-1],
|
|
88
|
-
ColumnsTable.FILE_PATH.value: self.elements[ColumnsTable.FILE_PATH.value][-1],
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
@property
|
|
92
|
-
def length(self) -> int:
|
|
93
|
-
return len(self.elements[ColumnsTable.TEXT.value])
|
|
94
|
-
|
|
95
|
-
@property
|
|
96
|
-
def files(self) -> list[str]:
|
|
97
|
-
if self.is_empty():
|
|
98
|
-
return []
|
|
99
|
-
return self.elements[ColumnsTable.FILE_PATH.value]
|
|
100
|
-
|
|
101
|
-
def get_item(self, idx: int) -> dict[str, str]:
|
|
102
|
-
try:
|
|
103
|
-
return {
|
|
104
|
-
ColumnsTable.NUM_LINE.value: self.elements[ColumnsTable.NUM_LINE.value][idx],
|
|
105
|
-
ColumnsTable.TEXT.value: self.elements[ColumnsTable.TEXT.value][idx],
|
|
106
|
-
ColumnsTable.FILE_PATH.value: self.elements[ColumnsTable.FILE_PATH.value][idx],
|
|
107
|
-
}
|
|
108
|
-
except Exception as err:
|
|
109
|
-
if not self.silent:
|
|
110
|
-
print(err)
|
|
111
|
-
return {}
|
|
112
|
-
|
|
113
|
-
def add_line(self, text, *, num_line: str, file: str) -> None:
|
|
114
|
-
if not self.silent:
|
|
115
|
-
print('----------------------')
|
|
116
|
-
print(f'{file} => {text}')
|
|
117
|
-
self.elements[ColumnsTable.NUM_LINE.value].append(num_line)
|
|
118
|
-
self.elements[ColumnsTable.TEXT.value].append(text)
|
|
119
|
-
self.elements[ColumnsTable.FILE_PATH.value].append(file)
|
|
120
|
-
|
|
121
|
-
def clear(self) -> None:
|
|
122
|
-
for _k in self.elements.keys():
|
|
123
|
-
self.elements[_k].clear()
|
|
124
|
-
|
|
125
|
-
def to_string(self) -> str:
|
|
126
|
-
"""
|
|
127
|
-
Retorna o texto da coluna TEXT em formato de string
|
|
128
|
-
ou 'nas' em caso de erro nas = Not a String
|
|
129
|
-
"""
|
|
130
|
-
try:
|
|
131
|
-
return ' '.join(self.elements[ColumnsTable.TEXT.value])
|
|
132
|
-
except Exception as e:
|
|
133
|
-
print(e)
|
|
134
|
-
return 'nas'
|
|
135
|
-
|
|
136
|
-
def to_data_frame(self) -> DataFrame:
|
|
137
|
-
return DataFrame.from_dict(self.elements)
|
|
138
|
-
|
|
139
|
-
def to_file_json(self, file: File):
|
|
140
|
-
"""Exporta os dados da busca para arquivo .JSON"""
|
|
141
|
-
dt = JsonConvert.from_dict(self.elements).to_json_data()
|
|
142
|
-
dt.to_file(file)
|
|
143
|
-
|
|
144
|
-
def to_file_excel(self, file: File):
|
|
145
|
-
"""Exporta os dados da busca para um arquivo .XLSX"""
|
|
146
|
-
try:
|
|
147
|
-
self.to_data_frame().to_excel(file.absolute())
|
|
148
|
-
except Exception as e:
|
|
149
|
-
print(e)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
class FindTextImages(object):
|
|
153
|
-
|
|
154
|
-
def __init__(
|
|
155
|
-
self, collection_images: CollectionImages,
|
|
156
|
-
*,
|
|
157
|
-
rec_img: RecognizeImage = RecognizeImage(),
|
|
158
|
-
pbar: ProgressBarAdapter = CreatePbar().get(),
|
|
159
|
-
):
|
|
160
|
-
self.collection_images: CollectionImages = collection_images
|
|
161
|
-
self._rec_img: RecognizeImage = rec_img
|
|
162
|
-
self.pbar: ProgressBarAdapter = pbar
|
|
163
|
-
|
|
164
|
-
def find(
|
|
165
|
-
self, text: str,
|
|
166
|
-
separator: str = '\n',
|
|
167
|
-
iqual: bool = False,
|
|
168
|
-
case: bool = False,
|
|
169
|
-
silent: bool = False,
|
|
170
|
-
) -> SearchableTextImage:
|
|
171
|
-
"""
|
|
172
|
-
Filtrar texto retornando a primeira ocorrência do Documento PDF.
|
|
173
|
-
"""
|
|
174
|
-
print()
|
|
175
|
-
self.pbar.start()
|
|
176
|
-
_searchable = SearchableTextImage(silent)
|
|
177
|
-
|
|
178
|
-
max_num: int = self.collection_images.length
|
|
179
|
-
for idx, image in enumerate(self.collection_images):
|
|
180
|
-
self.pbar.update(
|
|
181
|
-
((idx + 1) / max_num) * 100,
|
|
182
|
-
f'[Pesquisando]: {idx + 1} / {max_num} Aguarde...',
|
|
183
|
-
)
|
|
184
|
-
text_str_in_page: str = self._rec_img.image_to_string(image)
|
|
185
|
-
|
|
186
|
-
if (text_str_in_page == 'nas') or (text_str_in_page is None):
|
|
187
|
-
continue
|
|
188
|
-
try:
|
|
189
|
-
fd = FindText(text_str_in_page, separator=separator)
|
|
190
|
-
idx = fd.find_index(text, iqual=iqual, case=case)
|
|
191
|
-
if idx is None:
|
|
192
|
-
continue
|
|
193
|
-
math_text = fd.get_index(idx)
|
|
194
|
-
except Exception as err:
|
|
195
|
-
print(f'{__class__.__name__} {err}')
|
|
196
|
-
else:
|
|
197
|
-
_searchable.add_line(
|
|
198
|
-
math_text,
|
|
199
|
-
num_line=str(idx + 1),
|
|
200
|
-
file=image.name,
|
|
201
|
-
)
|
|
202
|
-
return _searchable
|
|
203
|
-
self.pbar.stop()
|
|
204
|
-
print()
|
|
205
|
-
return _searchable
|
|
206
|
-
|
|
207
|
-
def find_all(
|
|
208
|
-
self, text: str,
|
|
209
|
-
separator: str = '\n',
|
|
210
|
-
iqual: bool = False,
|
|
211
|
-
case: bool = False,
|
|
212
|
-
silent: bool = False,
|
|
213
|
-
) -> SearchableTextImage:
|
|
214
|
-
"""
|
|
215
|
-
Filtrar texto em documento PDF e retorna todas as ocorrências do texto
|
|
216
|
-
encontradas no documento, incluindo o número da linha, página e nome do arquivo
|
|
217
|
-
em cada ocorrência.
|
|
218
|
-
"""
|
|
219
|
-
print()
|
|
220
|
-
self.pbar.start()
|
|
221
|
-
_searchable = SearchableTextImage(silent)
|
|
222
|
-
max_num: int = self.collection_images.length
|
|
223
|
-
for idx, image in enumerate(self.collection_images):
|
|
224
|
-
self.pbar.update(
|
|
225
|
-
((idx + 1) / max_num) * 100,
|
|
226
|
-
f'[Pesquisando]: {idx + 1} / {max_num} Aguarde...',
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
text_str_in_page = self._rec_img.image_to_string(image)
|
|
230
|
-
if (text_str_in_page == 'nas') or (text_str_in_page is None):
|
|
231
|
-
continue
|
|
232
|
-
try:
|
|
233
|
-
_values = text_str_in_page.split(separator)
|
|
234
|
-
for num, item in enumerate(_values):
|
|
235
|
-
if case:
|
|
236
|
-
if iqual:
|
|
237
|
-
if text == item:
|
|
238
|
-
_searchable.add_line(
|
|
239
|
-
item,
|
|
240
|
-
num_line=str(num + 1),
|
|
241
|
-
file=image.name,
|
|
242
|
-
)
|
|
243
|
-
else:
|
|
244
|
-
if text in item:
|
|
245
|
-
_searchable.add_line(
|
|
246
|
-
item,
|
|
247
|
-
num_line=str(num + 1),
|
|
248
|
-
file=image.name,
|
|
249
|
-
)
|
|
250
|
-
else:
|
|
251
|
-
if iqual:
|
|
252
|
-
if text.lower() == item.lower():
|
|
253
|
-
_searchable.add_line(
|
|
254
|
-
item,
|
|
255
|
-
num_line=str(num + 1),
|
|
256
|
-
file=image.name,
|
|
257
|
-
)
|
|
258
|
-
else:
|
|
259
|
-
if text.lower() in item.lower():
|
|
260
|
-
_searchable.add_line(
|
|
261
|
-
item,
|
|
262
|
-
num_line=str(num + 1),
|
|
263
|
-
file=image.name,
|
|
264
|
-
)
|
|
265
|
-
except Exception as e:
|
|
266
|
-
print(f'{__class__.__name__} {e}')
|
|
267
|
-
self.pbar.stop()
|
|
268
|
-
print()
|
|
269
|
-
return _searchable
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|