ebook2text 2.1.2__tar.gz → 2.2.0__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.
- {ebook2text-2.1.2 → ebook2text-2.2.0}/PKG-INFO +4 -4
- ebook2text-2.2.0/ebook2text/VERSION.py +1 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/chapter_check.py +10 -4
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/convert_file.py +4 -5
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/docx_conversion/__init__.py +1 -1
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/docx_conversion/docx_converter.py +2 -2
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/docx_conversion/docx_image_extractor.py +3 -5
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/epub_conversion/__init__.py +1 -1
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/epub_conversion/epub_converter.py +3 -3
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/epub_conversion/epub_text_extractor.py +1 -5
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/ocr.py +4 -2
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/pdf_conversion/__init__.py +1 -1
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/pdf_conversion/pdf_converter.py +3 -4
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/pdf_conversion/pdf_image_extractor.py +8 -7
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/pdf_conversion/pdf_text_extractor.py +14 -16
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/text_parser.py +1 -1
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text.egg-info/PKG-INFO +4 -4
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text.egg-info/requires.txt +1 -1
- {ebook2text-2.1.2 → ebook2text-2.2.0}/pyproject.toml +19 -7
- {ebook2text-2.1.2 → ebook2text-2.2.0}/tests/test_pdf_conversion.py +36 -36
- ebook2text-2.1.2/ebook2text/VERSION.py +0 -1
- {ebook2text-2.1.2 → ebook2text-2.2.0}/README.md +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/__init__.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/_exceptions.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/_logger.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/_types.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/docx_conversion/_namespaces.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/docx_conversion/docx_text_extractor.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/pdf_conversion/_enums.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/pdf_conversion/pdf_line_logic.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text/text_utilities.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text.egg-info/SOURCES.txt +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text.egg-info/dependency_links.txt +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/ebook2text.egg-info/top_level.txt +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/setup.cfg +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/setup.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/tests/test_chapter_check.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/tests/test_docx_conversion.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/tests/test_epub_conversion.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/tests/test_ocr.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/tests/test_pdf_image_helpers.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/tests/test_text_conversion.py +0 -0
- {ebook2text-2.1.2 → ebook2text-2.2.0}/tests/test_text_parser.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ebook2text
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Convert common book file types to text for machine learning
|
|
5
5
|
Author: Ashlynn Antrobus
|
|
6
6
|
Author-email: Ashlynn Antrobus <ashlynn@prosepal.io>
|
|
@@ -21,10 +21,10 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
22
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
23
|
Classifier: Topic :: Text Processing
|
|
24
|
-
Requires-Python: >=3.
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
Requires-Dist: beautifulsoup4>=4.12.3
|
|
27
|
-
Requires-Dist: ebooklib-autoupdate>=0.18.
|
|
27
|
+
Requires-Dist: ebooklib-autoupdate>=0.18.3
|
|
28
28
|
Requires-Dist: openai>=1.54.3
|
|
29
29
|
Requires-Dist: pdfminer-six>=20240706
|
|
30
30
|
Requires-Dist: pillow>=10.4.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.2.0"
|
|
@@ -48,9 +48,15 @@ NOT_CHAPTER = {
|
|
|
48
48
|
def roman_to_int(roman_num: str) -> int:
|
|
49
49
|
"""
|
|
50
50
|
Convert a Roman numeral to an integer.
|
|
51
|
+
|
|
51
52
|
Arguments:
|
|
52
53
|
roman_num (str): A string representing the Roman numeral.
|
|
53
|
-
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
int: The integer value of the Roman numeral.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
ValueError if string is not a Roman numeral.
|
|
54
60
|
"""
|
|
55
61
|
if not isinstance(roman_num, str):
|
|
56
62
|
raise TypeError("Input must be a string")
|
|
@@ -61,7 +67,7 @@ def roman_to_int(roman_num: str) -> int:
|
|
|
61
67
|
if roman.count(numeral) > 1:
|
|
62
68
|
raise ValueError("Not roman numeral")
|
|
63
69
|
|
|
64
|
-
|
|
70
|
+
roman_numerals = {
|
|
65
71
|
"I": 1,
|
|
66
72
|
"V": 5,
|
|
67
73
|
"X": 10,
|
|
@@ -77,7 +83,7 @@ def roman_to_int(roman_num: str) -> int:
|
|
|
77
83
|
previous_char: str = ""
|
|
78
84
|
|
|
79
85
|
for char in reversed(roman):
|
|
80
|
-
if char not in
|
|
86
|
+
if char not in roman_numerals:
|
|
81
87
|
raise ValueError("Not roman numeral")
|
|
82
88
|
|
|
83
89
|
consecutive_count = (
|
|
@@ -86,7 +92,7 @@ def roman_to_int(roman_num: str) -> int:
|
|
|
86
92
|
if consecutive_count > 3:
|
|
87
93
|
raise ValueError("Not roman numeral")
|
|
88
94
|
|
|
89
|
-
value =
|
|
95
|
+
value = roman_numerals[char]
|
|
90
96
|
if value >= prev_value:
|
|
91
97
|
total += value
|
|
92
98
|
else:
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import Union
|
|
3
2
|
|
|
4
3
|
from ebook2text.docx_conversion import DocxConverter, initialize_docx_converter
|
|
5
4
|
from ebook2text.epub_conversion import EpubConverter, initialize_epub_converter
|
|
@@ -9,7 +8,7 @@ from ebook2text.text_parser import TextParser
|
|
|
9
8
|
|
|
10
9
|
def _initialize_converter(
|
|
11
10
|
file_path: Path, metadata: dict, extension: str
|
|
12
|
-
) ->
|
|
11
|
+
) -> DocxConverter | EpubConverter | PDFConverter | TextParser:
|
|
13
12
|
"""
|
|
14
13
|
Initialize the appropriate converter based on the file extension.
|
|
15
14
|
|
|
@@ -51,8 +50,8 @@ def convert_file(
|
|
|
51
50
|
metadata: dict,
|
|
52
51
|
*,
|
|
53
52
|
save_file: bool = True,
|
|
54
|
-
save_path:
|
|
55
|
-
) ->
|
|
53
|
+
save_path: Path | None = None,
|
|
54
|
+
) -> str | None:
|
|
56
55
|
"""
|
|
57
56
|
Converts a book to a text file with 3 asterisks for chapter breaks
|
|
58
57
|
Args:
|
|
@@ -77,4 +76,4 @@ def convert_file(
|
|
|
77
76
|
for content in converter.parse_file():
|
|
78
77
|
if content:
|
|
79
78
|
converter.write_text(content, save_path)
|
|
80
|
-
return
|
|
79
|
+
return None
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
1
2
|
from pathlib import Path
|
|
2
|
-
from typing import Generator, Tuple
|
|
3
3
|
|
|
4
4
|
import docx
|
|
5
5
|
from docx.oxml.exceptions import XmlchemyError
|
|
@@ -211,7 +211,7 @@ class DocxConverter:
|
|
|
211
211
|
|
|
212
212
|
def _process_text(
|
|
213
213
|
self, paragraph_text: str, current_para_index: int
|
|
214
|
-
) ->
|
|
214
|
+
) -> tuple[str, int]:
|
|
215
215
|
"""
|
|
216
216
|
Process a paragraph's text to determine if it starts a new chapter and
|
|
217
217
|
format it accordingly.
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
|
|
3
1
|
from ebook2text import logger
|
|
4
2
|
from ebook2text._types import Paragraph
|
|
5
3
|
from ebook2text.docx_conversion._namespaces import docx_ns_map
|
|
@@ -11,7 +9,7 @@ class DocxImageExtractor:
|
|
|
11
9
|
A class dedicated to extracting images from docx Paragraph objects.
|
|
12
10
|
"""
|
|
13
11
|
|
|
14
|
-
def extract_images(self, paragraph: Paragraph) ->
|
|
12
|
+
def extract_images(self, paragraph: Paragraph) -> list[str]:
|
|
15
13
|
"""
|
|
16
14
|
Extracts and converts images found in the paragraph into
|
|
17
15
|
base64-encoded strings.
|
|
@@ -48,8 +46,8 @@ class DocxImageExtractor:
|
|
|
48
46
|
blips = paragraph._p.findall(".//a:blip", namespaces=namespace)
|
|
49
47
|
for blip in blips:
|
|
50
48
|
try:
|
|
51
|
-
|
|
52
|
-
image_part = paragraph.part.related_parts[
|
|
49
|
+
r_id = blip.attrib[f"{{{namespace['r']}}}embed"]
|
|
50
|
+
image_part = paragraph.part.related_parts[r_id]
|
|
53
51
|
blob = image_part.blob
|
|
54
52
|
image_blobs.append(blob)
|
|
55
53
|
except (KeyError, ValueError, AttributeError) as e:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
1
2
|
from pathlib import Path
|
|
2
|
-
from typing import Generator
|
|
3
3
|
|
|
4
4
|
import ebooklib
|
|
5
5
|
from bs4 import BeautifulSoup
|
|
@@ -82,9 +82,9 @@ class EpubConverter:
|
|
|
82
82
|
Returns:
|
|
83
83
|
str: String containing the text of the chapter.
|
|
84
84
|
"""
|
|
85
|
-
|
|
85
|
+
text_elements = ["p", "img", "h1", "h2", "h3", "h4", "h5", "h6"]
|
|
86
86
|
soup = BeautifulSoup(item.content, "html.parser")
|
|
87
|
-
elements: ResultSet[Tag] = soup.find_all(
|
|
87
|
+
elements: ResultSet[Tag] = soup.find_all(text_elements)
|
|
88
88
|
|
|
89
89
|
for i, element in enumerate(elements[: self.max_lines_to_check]):
|
|
90
90
|
text = self.text_extractor.extract_text(element, self.epub_book)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
1
|
from ebook2text._types import EpubBook, Tag
|
|
4
2
|
from ebook2text.ocr import encode_image_bytes, run_ocr
|
|
5
3
|
|
|
@@ -9,9 +7,7 @@ class EpubTextExtractor:
|
|
|
9
7
|
Extracts text from EPUB elements, handling image OCR.
|
|
10
8
|
"""
|
|
11
9
|
|
|
12
|
-
def extract_text(
|
|
13
|
-
self, element: Tag, book: Optional[EpubBook] = None
|
|
14
|
-
) -> str:
|
|
10
|
+
def extract_text(self, element: Tag, book: EpubBook | None = None) -> str:
|
|
15
11
|
"""
|
|
16
12
|
Extracts text from an element, using OCR for images.
|
|
17
13
|
|
|
@@ -9,7 +9,9 @@ from . import logger
|
|
|
9
9
|
from ._exceptions import NoResponseError
|
|
10
10
|
|
|
11
11
|
load_dotenv()
|
|
12
|
-
api_key: str = os.
|
|
12
|
+
api_key: str = os.environ["OPENAI_API_KEY"]
|
|
13
|
+
MODEL: str = os.environ["OPENAI_MODEL"]
|
|
14
|
+
|
|
13
15
|
CLIENT: OpenAI = OpenAI(api_key=api_key)
|
|
14
16
|
|
|
15
17
|
GPT_REFUSALS = [
|
|
@@ -96,7 +98,7 @@ def run_ocr(
|
|
|
96
98
|
|
|
97
99
|
try:
|
|
98
100
|
response: ChatCompletion = client.chat.completions.create(
|
|
99
|
-
model=
|
|
101
|
+
model=MODEL, messages=payload, max_tokens=10
|
|
100
102
|
)
|
|
101
103
|
if response.choices and response.choices[0].message.content:
|
|
102
104
|
answer: str = response.choices[0].message.content
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import re
|
|
2
|
+
from collections.abc import Generator
|
|
2
3
|
from pathlib import Path
|
|
3
|
-
from typing import Generator, List
|
|
4
4
|
|
|
5
5
|
from pdfminer.high_level import extract_pages
|
|
6
6
|
from pdfminer.pdfparser import PDFSyntaxError
|
|
@@ -40,7 +40,7 @@ class PDFConverter:
|
|
|
40
40
|
self._max_lines_to_check = 6
|
|
41
41
|
self._chapter_separator = "***\n"
|
|
42
42
|
|
|
43
|
-
self._page:
|
|
43
|
+
self._page: list[str] = []
|
|
44
44
|
|
|
45
45
|
def _read_file(self, file_path: Path) -> Generator[LTPage, None, None]:
|
|
46
46
|
"""
|
|
@@ -64,8 +64,7 @@ class PDFConverter:
|
|
|
64
64
|
|
|
65
65
|
def _is_title_page(self, page_lines: list[str]) -> bool:
|
|
66
66
|
"""
|
|
67
|
-
Determines if
|
|
68
|
-
the page to a threshold.
|
|
67
|
+
Determines if title page by comparing page length to threshold.
|
|
69
68
|
"""
|
|
70
69
|
return len(page_lines) < self._max_lines_to_check
|
|
71
70
|
|
|
@@ -2,7 +2,6 @@ import base64
|
|
|
2
2
|
import itertools
|
|
3
3
|
from io import BytesIO
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import List, Tuple
|
|
6
5
|
|
|
7
6
|
from pdfminer.pdfdocument import PDFDocument
|
|
8
7
|
from pdfminer.pdfinterp import resolve1
|
|
@@ -83,7 +82,7 @@ class PDFImageExtractor:
|
|
|
83
82
|
def __init__(self, file_path: Path) -> None:
|
|
84
83
|
self.file_path = file_path
|
|
85
84
|
|
|
86
|
-
def extract_images(self, obj_nums:
|
|
85
|
+
def extract_images(self, obj_nums: list[int]) -> list[str]:
|
|
87
86
|
"""
|
|
88
87
|
Processes and extracts images from PDF objects based on the provided
|
|
89
88
|
object numbers.
|
|
@@ -187,14 +186,16 @@ class PDFImageExtractor:
|
|
|
187
186
|
"""
|
|
188
187
|
if attempt > 1:
|
|
189
188
|
logger.warning(
|
|
190
|
-
f"Unable to extract image from object: {obj_num}
|
|
189
|
+
f"Unable to extract image from object: {obj_num} "
|
|
190
|
+
f"or {obj_num - 1}"
|
|
191
191
|
)
|
|
192
192
|
return ""
|
|
193
193
|
try:
|
|
194
194
|
obj = resolve1(self.document.getobj(obj_num))
|
|
195
195
|
if not isinstance(obj, PDFStream):
|
|
196
196
|
raise TypeError(
|
|
197
|
-
|
|
197
|
+
"Invalid object. "
|
|
198
|
+
f"Received {type(obj)} instead of PDFStream"
|
|
198
199
|
)
|
|
199
200
|
if _convert_psliteral_to_str(obj.get("Filter")) == "DCTDecode":
|
|
200
201
|
return self._transcode_to_png(obj.get_data())
|
|
@@ -216,7 +217,7 @@ class PDFImageExtractor:
|
|
|
216
217
|
logger.exception(f"Exception: {e}")
|
|
217
218
|
return ""
|
|
218
219
|
|
|
219
|
-
def _find_next_image(self, obj_num: int, attempt: int) ->
|
|
220
|
+
def _find_next_image(self, obj_num: int, attempt: int) -> tuple[int, int]:
|
|
220
221
|
"""
|
|
221
222
|
Finds the next image object in the PDF document.
|
|
222
223
|
|
|
@@ -238,7 +239,7 @@ class PDFImageExtractor:
|
|
|
238
239
|
|
|
239
240
|
def _parse_image_data(
|
|
240
241
|
self, stream: PDFStream
|
|
241
|
-
) ->
|
|
242
|
+
) -> tuple[int, int, str, bytes]:
|
|
242
243
|
"""
|
|
243
244
|
Parses image data from a PDF object.
|
|
244
245
|
|
|
@@ -261,7 +262,7 @@ class PDFImageExtractor:
|
|
|
261
262
|
stream = _expand_bits(stream.get_data(), bit_depth)
|
|
262
263
|
return width, height, mode, stream
|
|
263
264
|
|
|
264
|
-
def _extract_color_data(self, stream: PDFStream) ->
|
|
265
|
+
def _extract_color_data(self, stream: PDFStream) -> tuple[str, int]:
|
|
265
266
|
"""
|
|
266
267
|
Extracts color data from a PDF object.
|
|
267
268
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import List, Tuple, Union
|
|
2
|
-
|
|
3
1
|
from pdfminer.pdfdocument import PDFSyntaxError
|
|
4
2
|
|
|
5
3
|
from ebook2text import logger
|
|
@@ -24,7 +22,7 @@ class PDFTextExtractor:
|
|
|
24
22
|
self._image_obj_nums: list[int] = []
|
|
25
23
|
self._pdf_text_list: list[str] = []
|
|
26
24
|
|
|
27
|
-
def _match_objects(self, obj_type: str, obj_data:
|
|
25
|
+
def _match_objects(self, obj_type: str, obj_data: int | str) -> None:
|
|
28
26
|
"""
|
|
29
27
|
Matches objects to their respective types.
|
|
30
28
|
|
|
@@ -46,16 +44,17 @@ class PDFTextExtractor:
|
|
|
46
44
|
"""
|
|
47
45
|
Extracts text and image data from a PDF page.
|
|
48
46
|
|
|
49
|
-
This function receives an LTPage object extracted by PDFMiner and
|
|
50
|
-
a tuple containing a list of image object numbers and a list
|
|
51
|
-
containing the text from the page.
|
|
47
|
+
This function receives an LTPage object extracted by PDFMiner and
|
|
48
|
+
returns a tuple containing a list of image object numbers and a list
|
|
49
|
+
of strings containing the text from the page.
|
|
52
50
|
|
|
53
51
|
Args:pdf
|
|
54
52
|
page (LTPage): The page to extract text and image data from.
|
|
55
53
|
|
|
56
54
|
Returns:
|
|
57
|
-
tuple[list[int], list[str]]: A tuple containing a list of image
|
|
58
|
-
numbers and a list of strings containing the text from
|
|
55
|
+
tuple[list[int], list[str]]: A tuple containing a list of image
|
|
56
|
+
object numbers and a list of strings containing the text from
|
|
57
|
+
the page.
|
|
59
58
|
"""
|
|
60
59
|
|
|
61
60
|
for element in page:
|
|
@@ -63,7 +62,7 @@ class PDFTextExtractor:
|
|
|
63
62
|
self._match_objects(obj_type, obj_data)
|
|
64
63
|
return self._image_obj_nums, self._pdf_text_list
|
|
65
64
|
|
|
66
|
-
def extract_text(self, page: LTPage) ->
|
|
65
|
+
def extract_text(self, page: LTPage) -> list[str]:
|
|
67
66
|
"""
|
|
68
67
|
Extracts text from a PDF page, including OCR text from images.
|
|
69
68
|
|
|
@@ -82,8 +81,8 @@ class PDFTextExtractor:
|
|
|
82
81
|
page (LTPage): The PDF page represented as an LTPage object.
|
|
83
82
|
|
|
84
83
|
Returns:
|
|
85
|
-
List[str]: The extracted text content from the PDF page, including
|
|
86
|
-
text from images.
|
|
84
|
+
List[str]: The extracted text content from the PDF page, including
|
|
85
|
+
OCR text from images.
|
|
87
86
|
"""
|
|
88
87
|
image_obj_nums, pdf_text_list = self._extract_element_data(page)
|
|
89
88
|
self._image_obj_nums = []
|
|
@@ -99,7 +98,7 @@ class PDFTextExtractor:
|
|
|
99
98
|
raise PDFConversionError from e
|
|
100
99
|
return [ocr_text] + pdf_text_list
|
|
101
100
|
|
|
102
|
-
def _process_element(self, element: LTItem) ->
|
|
101
|
+
def _process_element(self, element: LTItem) -> tuple[str, int | str]:
|
|
103
102
|
"""
|
|
104
103
|
Processes a PDF layout element to identify its type and extract
|
|
105
104
|
relevant data.
|
|
@@ -128,17 +127,16 @@ class PDFTextExtractor:
|
|
|
128
127
|
return self._process_element(child)
|
|
129
128
|
return "other", ""
|
|
130
129
|
|
|
131
|
-
def _extract_image_text(self, image_obj_nums:
|
|
130
|
+
def _extract_image_text(self, image_obj_nums: list[int]) -> str:
|
|
132
131
|
"""
|
|
133
|
-
Collect list of Base64 encoded images and
|
|
134
|
-
|
|
132
|
+
Collect list of Base64 encoded images and extract text with LLM.
|
|
135
133
|
Args:
|
|
136
134
|
image_obj_nums (List[int]): List of image object numbers.
|
|
137
135
|
|
|
138
136
|
Returns:
|
|
139
137
|
str: Extracted text from images.
|
|
140
138
|
"""
|
|
141
|
-
base64_images:
|
|
139
|
+
base64_images: list[str] = self.image_extractor.extract_images(
|
|
142
140
|
image_obj_nums
|
|
143
141
|
)
|
|
144
142
|
return run_ocr(base64_images)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ebook2text
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Convert common book file types to text for machine learning
|
|
5
5
|
Author: Ashlynn Antrobus
|
|
6
6
|
Author-email: Ashlynn Antrobus <ashlynn@prosepal.io>
|
|
@@ -21,10 +21,10 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
22
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
23
|
Classifier: Topic :: Text Processing
|
|
24
|
-
Requires-Python: >=3.
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
Requires-Dist: beautifulsoup4>=4.12.3
|
|
27
|
-
Requires-Dist: ebooklib-autoupdate>=0.18.
|
|
27
|
+
Requires-Dist: ebooklib-autoupdate>=0.18.3
|
|
28
28
|
Requires-Dist: openai>=1.54.3
|
|
29
29
|
Requires-Dist: pdfminer-six>=20240706
|
|
30
30
|
Requires-Dist: pillow>=10.4.0
|
|
@@ -21,10 +21,10 @@ classifiers = [
|
|
|
21
21
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
22
22
|
"Topic :: Text Processing"
|
|
23
23
|
]
|
|
24
|
-
requires-python = ">=3.
|
|
24
|
+
requires-python = ">=3.10"
|
|
25
25
|
dependencies = [
|
|
26
26
|
"beautifulsoup4>=4.12.3",
|
|
27
|
-
"ebooklib-autoupdate>=0.18.
|
|
27
|
+
"ebooklib-autoupdate>=0.18.3",
|
|
28
28
|
"openai>=1.54.3",
|
|
29
29
|
"pdfminer-six>=20240706",
|
|
30
30
|
"pillow>=10.4.0",
|
|
@@ -42,15 +42,27 @@ build-backend = "setuptools.build_meta"
|
|
|
42
42
|
|
|
43
43
|
[tool.ruff]
|
|
44
44
|
line-length = 79
|
|
45
|
-
|
|
45
|
+
target-version = "py310"
|
|
46
46
|
exclude = [".git", "__pycache__", ".venv", "dist", "build", ".vscode", ".egg-info"]
|
|
47
47
|
|
|
48
|
-
[tool.
|
|
49
|
-
|
|
48
|
+
[tool.ruff.lint]
|
|
49
|
+
select = ["E", "F", "I", "N", "UP"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint.per-file-ignores]
|
|
52
|
+
"**/{tests,docs,tools}/*" = ["F401", "I001", "E501", "N806"]
|
|
53
|
+
"**/VERSION.py" = ["N999"]
|
|
54
|
+
|
|
55
|
+
[tool.ruff.format]
|
|
56
|
+
docstring-code-format = true
|
|
57
|
+
docstring-code-line-length = "dynamic"
|
|
58
|
+
line-ending = "lf"
|
|
50
59
|
|
|
51
60
|
[tool.mypy]
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
mypy_path = "$MYPY_CONFIG_FILE_DIR/ebook2text"
|
|
62
|
+
exclude_gitignore = true
|
|
63
|
+
ignore_missing_imports = true
|
|
64
|
+
follow_imports = "silent"
|
|
65
|
+
follow_untyped_imports = false
|
|
54
66
|
|
|
55
67
|
[tool.pytest.ini_options]
|
|
56
68
|
testpaths = ["tests"]
|
|
@@ -91,21 +91,21 @@ class TestPDFConverter:
|
|
|
91
91
|
def test_parse_file_text_extraction(self, pdf_converter):
|
|
92
92
|
"""Test that parse_file extracts only the main content and excludes unwanted sections."""
|
|
93
93
|
parsed_text = "".join(pdf_converter.parse_file())
|
|
94
|
-
assert (
|
|
95
|
-
"
|
|
96
|
-
)
|
|
97
|
-
assert (
|
|
98
|
-
"Introduction
|
|
99
|
-
)
|
|
100
|
-
assert (
|
|
101
|
-
"Chapter 1
|
|
102
|
-
)
|
|
103
|
-
assert (
|
|
104
|
-
"Chapter 2
|
|
105
|
-
)
|
|
106
|
-
assert (
|
|
107
|
-
"
|
|
108
|
-
)
|
|
94
|
+
assert "Sample Title" not in parsed_text, (
|
|
95
|
+
"Title text should be removed from output"
|
|
96
|
+
)
|
|
97
|
+
assert "Introduction" not in parsed_text, (
|
|
98
|
+
"Introduction section should be removed from output"
|
|
99
|
+
)
|
|
100
|
+
assert "Chapter 1" not in parsed_text, (
|
|
101
|
+
"Chapter 1 header should be removed from output"
|
|
102
|
+
)
|
|
103
|
+
assert "Chapter 2" not in parsed_text, (
|
|
104
|
+
"Chapter 2 header should be removed from output"
|
|
105
|
+
)
|
|
106
|
+
assert "First chapter paragraph text." in parsed_text, (
|
|
107
|
+
"Text after Chapter 1 should be included"
|
|
108
|
+
)
|
|
109
109
|
assert (
|
|
110
110
|
"Lorem ipsum odor amet, consectetuer adipiscing elit."
|
|
111
111
|
in parsed_text
|
|
@@ -113,9 +113,9 @@ class TestPDFConverter:
|
|
|
113
113
|
|
|
114
114
|
def test_chapter_separator_insertion(self, pdf_converter):
|
|
115
115
|
parsed_text = "".join(pdf_converter.parse_file())
|
|
116
|
-
assert (
|
|
117
|
-
|
|
118
|
-
)
|
|
116
|
+
assert pdf_converter._chapter_separator in parsed_text, (
|
|
117
|
+
"Chapter separator should be inserted between chapters"
|
|
118
|
+
)
|
|
119
119
|
|
|
120
120
|
def test_page_combining(self, pdf_converter):
|
|
121
121
|
page_lines = [
|
|
@@ -135,29 +135,29 @@ class TestPDFConverter:
|
|
|
135
135
|
cleaned_text = pdf_converter.remove_extra_whitespace(
|
|
136
136
|
text_with_whitespace
|
|
137
137
|
)
|
|
138
|
-
assert (
|
|
139
|
-
|
|
140
|
-
)
|
|
138
|
+
assert cleaned_text == "Line 1\nLine 2 Line 3", (
|
|
139
|
+
"Extra whitespace should be removed"
|
|
140
|
+
)
|
|
141
141
|
|
|
142
142
|
def test_image_processing(self, pdf_converter_with_images):
|
|
143
143
|
"""Test parsing with an image-based PDF containing 'Chapter 1' as an image."""
|
|
144
144
|
parsed_text = "".join(pdf_converter_with_images.parse_file())
|
|
145
145
|
|
|
146
|
-
assert (
|
|
147
|
-
"
|
|
148
|
-
)
|
|
149
|
-
assert (
|
|
150
|
-
"Introduction
|
|
151
|
-
)
|
|
152
|
-
assert (
|
|
153
|
-
"Chapter 1
|
|
154
|
-
)
|
|
155
|
-
assert (
|
|
156
|
-
"Chapter 2
|
|
157
|
-
)
|
|
158
|
-
assert (
|
|
159
|
-
"
|
|
160
|
-
)
|
|
146
|
+
assert "Sample Title" not in parsed_text, (
|
|
147
|
+
"Title text should be removed from output"
|
|
148
|
+
)
|
|
149
|
+
assert "Introduction" not in parsed_text, (
|
|
150
|
+
"Introduction section should be removed from output"
|
|
151
|
+
)
|
|
152
|
+
assert "Chapter 1" not in parsed_text, (
|
|
153
|
+
"Chapter 1 header should be removed from output"
|
|
154
|
+
)
|
|
155
|
+
assert "Chapter 2" not in parsed_text, (
|
|
156
|
+
"Chapter 2 header should be removed from output"
|
|
157
|
+
)
|
|
158
|
+
assert "First chapter paragraph text." in parsed_text, (
|
|
159
|
+
"Text after Chapter 1 should be included"
|
|
160
|
+
)
|
|
161
161
|
assert (
|
|
162
162
|
"Lorem ipsum odor amet, consectetuer adipiscing elit."
|
|
163
163
|
in parsed_text
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.1.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|