Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47ef1914d4 | ||
|
|
df157552f3 | ||
|
|
0b3a526049 | ||
|
|
1e80d412fa | ||
|
|
df6e106203 |
@@ -56,6 +56,11 @@ Programs that call ``ocrmypdf.ocr()`` should also install a SIGBUS signal
|
|||||||
handler (except on Windows), to raise an exception if access to a memory
|
handler (except on Windows), to raise an exception if access to a memory
|
||||||
mapped file fails. OCRmyPDF may use memory mapping.
|
mapped file fails. OCRmyPDF may use memory mapping.
|
||||||
|
|
||||||
|
``ocrmypdf.ocr()`` will take a threading lock to prevent multiple runs of itself
|
||||||
|
in the same Python interpreter process. This is not thread-safe, because of how
|
||||||
|
OCRmyPDF's plugins and Python's library import system work. If you need to parallelize
|
||||||
|
OCRmyPDF, use processes.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
On Windows and macOS, the script that calls ``ocrmypdf.ocr()`` must be
|
On Windows and macOS, the script that calls ``ocrmypdf.ocr()`` must be
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ may be unreliable. Use the API to depend on precise behavior.
|
|||||||
The public API may be useful in scripts that launch OCRmyPDF processes or that
|
The public API may be useful in scripts that launch OCRmyPDF processes or that
|
||||||
wish to use some of its features for working with PDFs.
|
wish to use some of its features for working with PDFs.
|
||||||
|
|
||||||
|
v11.4.4
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Fixed ``AttributeError: 'NoneType' object has no attribute 'userunit'``, issue #700,
|
||||||
|
related to OCRmyPDF not properly forwarded an error message from pdfminer.six.
|
||||||
|
- Adjusted typing of some arguments.
|
||||||
|
- ``ocrmypdf.ocr`` now takes a ``threading.Lock`` for reasons outlined in the
|
||||||
|
documentation.
|
||||||
|
|
||||||
v11.4.3
|
v11.4.3
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
|||||||
@@ -109,15 +109,11 @@ def exec_progress_pool(
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
results = pool.imap_unordered(task, task_arguments)
|
results = pool.imap_unordered(task, task_arguments)
|
||||||
while True:
|
for result in results:
|
||||||
try:
|
if task_finished:
|
||||||
result = results.next()
|
task_finished(result, pbar)
|
||||||
if task_finished:
|
else:
|
||||||
task_finished(result, pbar)
|
pbar.update()
|
||||||
else:
|
|
||||||
pbar.update()
|
|
||||||
except StopIteration:
|
|
||||||
break
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
# Terminate pool so we exit instantly
|
# Terminate pool so we exit instantly
|
||||||
pool.terminate()
|
pool.terminate()
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from collections import namedtuple
|
|||||||
from os import fspath
|
from os import fspath
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import PIPE, STDOUT, CalledProcessError, TimeoutExpired
|
from subprocess import PIPE, STDOUT, CalledProcessError, TimeoutExpired
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ def get_languages():
|
|||||||
return set(lang.strip() for lang in rest)
|
return set(lang.strip() for lang in rest)
|
||||||
|
|
||||||
|
|
||||||
def tess_base_args(langs: List[str], engine_mode: int) -> List[str]:
|
def tess_base_args(langs: List[str], engine_mode: Optional[int]) -> List[str]:
|
||||||
args = ['tesseract']
|
args = ['tesseract']
|
||||||
if langs:
|
if langs:
|
||||||
args.extend(['-l', '+'.join(langs)])
|
args.extend(['-l', '+'.join(langs)])
|
||||||
@@ -127,7 +127,7 @@ def tess_base_args(langs: List[str], engine_mode: int) -> List[str]:
|
|||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def get_orientation(input_file: Path, engine_mode: int, timeout: float):
|
def get_orientation(input_file: Path, engine_mode: Optional[int], timeout: float):
|
||||||
args_tesseract = tess_base_args(['osd'], engine_mode) + [
|
args_tesseract = tess_base_args(['osd'], engine_mode) + [
|
||||||
'--psm',
|
'--psm',
|
||||||
'0',
|
'0',
|
||||||
|
|||||||
+16
-7
@@ -8,6 +8,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from io import IOBase
|
from io import IOBase
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -30,6 +31,8 @@ except ModuleNotFoundError:
|
|||||||
StrPath = Union[os.PathLike, AnyStr]
|
StrPath = Union[os.PathLike, AnyStr]
|
||||||
PathOrIO = Union[BinaryIO, StrPath]
|
PathOrIO = Union[BinaryIO, StrPath]
|
||||||
|
|
||||||
|
_api_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
class Verbosity(IntEnum):
|
class Verbosity(IntEnum):
|
||||||
"""Verbosity level for configure_logging."""
|
"""Verbosity level for configure_logging."""
|
||||||
@@ -306,12 +309,18 @@ def ocr( # pylint: disable=unused-argument
|
|||||||
|
|
||||||
parser = get_parser()
|
parser = get_parser()
|
||||||
create_options_kwargs['parser'] = parser
|
create_options_kwargs['parser'] = parser
|
||||||
plugin_manager = get_plugin_manager(plugins)
|
|
||||||
plugin_manager.hook.add_options(parser=parser) # pylint: disable=no-member
|
|
||||||
|
|
||||||
if 'verbose' in kwargs:
|
with _api_lock:
|
||||||
warn("ocrmypdf.ocr(verbose=) is ignored. Use ocrmypdf.configure_logging().")
|
# We can't allow multiple ocrmypdf.ocr() threads to run in parallel, because
|
||||||
|
# they might install different plugins, and generally speaking we have areas
|
||||||
|
# of code that use global state.
|
||||||
|
|
||||||
options = create_options(**create_options_kwargs)
|
plugin_manager = get_plugin_manager(plugins)
|
||||||
check_options(options, plugin_manager)
|
plugin_manager.hook.add_options(parser=parser) # pylint: disable=no-member
|
||||||
return run_pipeline(options=options, plugin_manager=plugin_manager, api=True)
|
|
||||||
|
if 'verbose' in kwargs:
|
||||||
|
warn("ocrmypdf.ocr(verbose=) is ignored. Use ocrmypdf.configure_logging().")
|
||||||
|
|
||||||
|
options = create_options(**create_options_kwargs)
|
||||||
|
check_options(options, plugin_manager)
|
||||||
|
return run_pipeline(options=options, plugin_manager=plugin_manager, api=True)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import pikepdf
|
|||||||
from pikepdf import Object, Pdf, PdfMatrix
|
from pikepdf import Object, Pdf, PdfMatrix
|
||||||
|
|
||||||
from ocrmypdf._concurrent import exec_progress_pool
|
from ocrmypdf._concurrent import exec_progress_pool
|
||||||
from ocrmypdf.exceptions import EncryptedPdfError
|
from ocrmypdf.exceptions import EncryptedPdfError, InputFileError
|
||||||
from ocrmypdf.helpers import Resolution, available_cpu_count, pikepdf_enable_mmap
|
from ocrmypdf.helpers import Resolution, available_cpu_count, pikepdf_enable_mmap
|
||||||
from ocrmypdf.pdfinfo.layout import get_page_analysis, get_text_boxes
|
from ocrmypdf.pdfinfo.layout import get_page_analysis, get_text_boxes
|
||||||
|
|
||||||
@@ -598,6 +598,8 @@ def _pdf_pageinfo_concurrent(
|
|||||||
|
|
||||||
def update_pageinfo(result, pbar):
|
def update_pageinfo(result, pbar):
|
||||||
page = result
|
page = result
|
||||||
|
if not page:
|
||||||
|
raise InputFileError("Could read a page in the PDF")
|
||||||
pages[page.pageno] = page
|
pages[page.pageno] = page
|
||||||
pbar.update()
|
pbar.update()
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from pdfminer.pdffont import PDFSimpleFont, PDFUnicodeNotDefined
|
|||||||
from pdfminer.pdfpage import PDFPage
|
from pdfminer.pdfpage import PDFPage
|
||||||
from pdfminer.utils import bbox2str, matrix2str
|
from pdfminer.utils import bbox2str, matrix2str
|
||||||
|
|
||||||
from ocrmypdf.exceptions import EncryptedPdfError
|
from ocrmypdf.exceptions import EncryptedPdfError, InputFileError
|
||||||
|
|
||||||
STRIP_NAME = re.compile(r'[0-9]+')
|
STRIP_NAME = re.compile(r'[0-9]+')
|
||||||
|
|
||||||
@@ -236,8 +236,13 @@ def get_page_analysis(infile, pageno, pscript5_mode):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
with Path(infile).open('rb') as f:
|
with Path(infile).open('rb') as f:
|
||||||
page = PDFPage.get_pages(f, pagenos=[pageno], maxpages=0)
|
page_iter = PDFPage.get_pages(f, pagenos=[pageno], maxpages=0)
|
||||||
interp.process_page(next(page))
|
page = next(page_iter, None)
|
||||||
|
if page is None:
|
||||||
|
raise InputFileError(
|
||||||
|
f"pdfminer could not process page {pageno} (counting from 0)."
|
||||||
|
)
|
||||||
|
interp.process_page(page)
|
||||||
except PDFTextExtractionNotAllowed as e:
|
except PDFTextExtractionNotAllowed as e:
|
||||||
raise EncryptedPdfError() from e
|
raise EncryptedPdfError() from e
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ from PIL import Image
|
|||||||
from reportlab.pdfgen.canvas import Canvas
|
from reportlab.pdfgen.canvas import Canvas
|
||||||
|
|
||||||
from ocrmypdf import pdfinfo
|
from ocrmypdf import pdfinfo
|
||||||
|
from ocrmypdf.exceptions import InputFileError
|
||||||
from ocrmypdf.pdfinfo import Colorspace, Encoding
|
from ocrmypdf.pdfinfo import Colorspace, Encoding
|
||||||
|
from ocrmypdf.pdfinfo.layout import PDFPage
|
||||||
|
|
||||||
|
run_ocrmypdf_api = pytest.helpers.run_ocrmypdf_api
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
|
|
||||||
@@ -179,3 +183,18 @@ def test_stack_abuse():
|
|||||||
with pytest.warns(None):
|
with pytest.warns(None):
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
pdfinfo.info._interpret_contents(stream)
|
pdfinfo.info._interpret_contents(stream)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pages_issue700(monkeypatch, resources):
|
||||||
|
def get_no_pages(*args, **kwargs):
|
||||||
|
return iter([])
|
||||||
|
|
||||||
|
monkeypatch.setattr(PDFPage, 'get_pages', get_no_pages)
|
||||||
|
|
||||||
|
with pytest.raises(InputFileError, match="pdfminer"):
|
||||||
|
pdfinfo.PdfInfo(
|
||||||
|
resources / 'cardinal.pdf',
|
||||||
|
detailed_analysis=True,
|
||||||
|
progbar=False,
|
||||||
|
max_workers=1,
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user