Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d32324859c | ||
|
|
48222b87b5 | ||
|
|
62e5edc72b | ||
|
|
2846d46bb8 | ||
|
|
47ef1914d4 | ||
|
|
df157552f3 | ||
|
|
0b3a526049 | ||
|
|
1e80d412fa | ||
|
|
df6e106203 | ||
|
|
bd0f005861 | ||
|
|
6ba4b7b3f3 | ||
|
|
2c11349ee8 | ||
|
|
b0afef09ef | ||
|
|
72fa347c38 | ||
|
|
96d68c2413 | ||
|
|
babc76fa74 | ||
|
|
dc06990e5d | ||
|
|
0ff0d2f8d1 | ||
|
|
81602cf420 |
-24
@@ -1,24 +0,0 @@
|
|||||||
[paths]
|
|
||||||
source =
|
|
||||||
src
|
|
||||||
*/site-packages
|
|
||||||
|
|
||||||
[run]
|
|
||||||
branch = true
|
|
||||||
parallel = true
|
|
||||||
concurrency =
|
|
||||||
thread
|
|
||||||
multiprocessing
|
|
||||||
source =
|
|
||||||
src/ocrmypdf
|
|
||||||
|
|
||||||
[report]
|
|
||||||
exclude_lines =
|
|
||||||
pragma: no cover
|
|
||||||
def __repr__
|
|
||||||
raise AssertionError
|
|
||||||
raise NotImplementedError
|
|
||||||
if 0:
|
|
||||||
if False:
|
|
||||||
if __name__ == .__main__.:
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
+1
-1
@@ -32,7 +32,7 @@ stages:
|
|||||||
choco install --yes --no-progress --pre tesseract
|
choco install --yes --no-progress --pre tesseract
|
||||||
choco install --yes --no-progress python3
|
choco install --yes --no-progress python3
|
||||||
choco install --yes --no-progress ghostscript
|
choco install --yes --no-progress ghostscript
|
||||||
choco install --yes --no-progress pngquant
|
# choco install --yes --no-progress pngquant
|
||||||
displayName: "Install system packages"
|
displayName: "Install system packages"
|
||||||
- pwsh: |
|
- pwsh: |
|
||||||
refreshenv
|
refreshenv
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -637,7 +637,7 @@ Installing with Python pip
|
|||||||
OCRmyPDF is delivered by PyPI because it is a convenient way to install
|
OCRmyPDF is delivered by PyPI because it is a convenient way to install
|
||||||
the latest version. However, PyPI and ``pip`` cannot address the fact
|
the latest version. However, PyPI and ``pip`` cannot address the fact
|
||||||
that ``ocrmypdf`` depends on certain non-Python system libraries and
|
that ``ocrmypdf`` depends on certain non-Python system libraries and
|
||||||
programs being instsalled.
|
programs being installed.
|
||||||
|
|
||||||
For best results, first install `your platform's
|
For best results, first install `your platform's
|
||||||
version <https://repology.org/metapackage/ocrmypdf/versions>`__ of
|
version <https://repology.org/metapackage/ocrmypdf/versions>`__ of
|
||||||
|
|||||||
@@ -12,6 +12,28 @@ 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.5
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Fixed an issue where files may not be closed when the API is used.
|
||||||
|
- Improved ``setup.cfg`` with better settings for test coverage.
|
||||||
|
|
||||||
|
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
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Removed a redundant debug message.
|
||||||
|
- Test suite now asserts that most patched functions are called when they should be.
|
||||||
|
- Test suite now skips a test that fails on two particular versions of piekpdf.
|
||||||
|
|
||||||
v11.4.2
|
v11.4.2
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ filterwarnings =
|
|||||||
ignore:.*XMLParser.*:DeprecationWarning
|
ignore:.*XMLParser.*:DeprecationWarning
|
||||||
markers =
|
markers =
|
||||||
slow
|
slow
|
||||||
|
addopts =
|
||||||
|
-n auto
|
||||||
|
|
||||||
[isort]
|
[isort]
|
||||||
multi_line_output=3
|
multi_line_output=3
|
||||||
@@ -27,3 +29,30 @@ known_third_party = PIL,_cffi_backend,cffi,flask,img2pdf,pdfminer,pikepdf,pkg_re
|
|||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
license_file = LICENSE
|
license_file = LICENSE
|
||||||
|
|
||||||
|
[coverage:paths]
|
||||||
|
source =
|
||||||
|
src/
|
||||||
|
|
||||||
|
[coverage:run]
|
||||||
|
branch = true
|
||||||
|
parallel = true
|
||||||
|
concurrency = multiprocessing
|
||||||
|
source =
|
||||||
|
src/ocrmypdf
|
||||||
|
|
||||||
|
[coverage:report]
|
||||||
|
# Regexes for lines to exclude from consideration
|
||||||
|
exclude_lines =
|
||||||
|
# Have to re-enable the standard pragma
|
||||||
|
pragma: no cover
|
||||||
|
|
||||||
|
# Don't complain if tests don't hit defensive assertion code:
|
||||||
|
raise AssertionError
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# Don't complain if non-runnable code isn't run:
|
||||||
|
if 0:
|
||||||
|
if False:
|
||||||
|
if __name__ == .__main__.:
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -254,6 +254,8 @@ def generate_pdfa(
|
|||||||
raise SubprocessOutputError('Ghostscript PDF/A rendering failed') from e
|
raise SubprocessOutputError('Ghostscript PDF/A rendering failed') from e
|
||||||
else:
|
else:
|
||||||
stderr = p.stderr
|
stderr = p.stderr
|
||||||
|
# If there is an error we log the whole stderr, except for filtering
|
||||||
|
# duplicates.
|
||||||
if _gs_error_reported(stderr):
|
if _gs_error_reported(stderr):
|
||||||
last_part = None
|
last_part = None
|
||||||
repcount = 0
|
repcount = 0
|
||||||
@@ -266,11 +268,3 @@ def generate_pdfa(
|
|||||||
else:
|
else:
|
||||||
repcount += 1
|
repcount += 1
|
||||||
last_part = part
|
last_part = part
|
||||||
elif 'overprint mode not set' in stderr:
|
|
||||||
# Unless someone is going to print PDF/A documents on a
|
|
||||||
# magical sRGB printer I can't see the removal of overprinting
|
|
||||||
# being a problem....
|
|
||||||
log.debug(
|
|
||||||
"Ghostscript had to remove PDF 'overprinting' from the "
|
|
||||||
"input file to complete PDF/A conversion. "
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
+10
-12
@@ -756,19 +756,17 @@ def metadata_fixup(working_file: Path, context: PdfContext):
|
|||||||
if 'xmp:CreateDate' not in meta:
|
if 'xmp:CreateDate' not in meta:
|
||||||
meta['xmp:CreateDate'] = meta.get('xmp:ModifyDate', '')
|
meta['xmp:CreateDate'] = meta.get('xmp:ModifyDate', '')
|
||||||
|
|
||||||
# Ghostscript likes to set title to Untitled if omitted from input.
|
with original.open_metadata(
|
||||||
# Reverse this, because PDF/A TechNote 0003:Metadata in PDF/A-1
|
set_pikepdf_as_editor=False, update_docinfo=False, strict=False
|
||||||
# and the XMP Spec do not make this recommendation.
|
) as meta_original:
|
||||||
if meta.get('dc:title') == 'Untitled':
|
if meta.get('dc:title') == 'Untitled':
|
||||||
with original.open_metadata(
|
# Ghostscript likes to set title to Untitled if omitted from input.
|
||||||
set_pikepdf_as_editor=False, update_docinfo=False
|
# Reverse this, because PDF/A TechNote 0003:Metadata in PDF/A-1
|
||||||
) as original_meta:
|
# and the XMP Spec do not make this recommendation.
|
||||||
if 'dc:title' not in original_meta:
|
if 'dc:title' not in meta_original:
|
||||||
del meta['dc:title']
|
del meta['dc:title']
|
||||||
|
missing = set(meta_original.keys()) - set(meta.keys())
|
||||||
meta_original = original.open_metadata()
|
report_on_metadata(missing)
|
||||||
missing = set(meta_original.keys()) - set(meta.keys())
|
|
||||||
report_on_metadata(missing)
|
|
||||||
|
|
||||||
pdf.save(
|
pdf.save(
|
||||||
output_file,
|
output_file,
|
||||||
|
|||||||
@@ -338,6 +338,8 @@ def run_pipeline(options, *, plugin_manager, api=False):
|
|||||||
and not api
|
and not api
|
||||||
):
|
):
|
||||||
# Debug log for command line interface only with verbose output
|
# Debug log for command line interface only with verbose output
|
||||||
|
# See https://github.com/pytest-dev/pytest/issues/5502 for why we skip this
|
||||||
|
# when pytest is running
|
||||||
debug_log_handler = configure_debug_logging(Path(work_folder) / "debug.log")
|
debug_log_handler = configure_debug_logging(Path(work_folder) / "debug.log")
|
||||||
|
|
||||||
pikepdf_enable_mmap()
|
pikepdf_enable_mmap()
|
||||||
|
|||||||
+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)
|
||||||
|
|||||||
@@ -498,7 +498,7 @@ def transcode_pngs(
|
|||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
def rewrite_png_as_g4(pike: Pdf, im_obj: Object, compdata) -> None:
|
def rewrite_png_as_g4(pike: Pdf, im_obj: Object, compdata) -> None: # pragma: no cover
|
||||||
im_obj.BitsPerComponent = 1
|
im_obj.BitsPerComponent = 1
|
||||||
im_obj.Width = compdata.w
|
im_obj.Width = compdata.w
|
||||||
im_obj.Height = compdata.h
|
im_obj.Height = compdata.h
|
||||||
@@ -519,7 +519,7 @@ def rewrite_png_as_g4(pike: Pdf, im_obj: Object, compdata) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
def rewrite_png(pike: Pdf, im_obj: Object, compdata) -> None:
|
def rewrite_png(pike: Pdf, im_obj: Object, compdata) -> None: # pragma: no cover
|
||||||
# When a PNG is inserted into a PDF, we more or less copy the IDAT section from
|
# When a PNG is inserted into a PDF, we more or less copy the IDAT section from
|
||||||
# the PDF and transfer the rest of the PNG headers to PDF image metadata.
|
# the PDF and transfer the rest of the PNG headers to PDF image metadata.
|
||||||
# One thing we have to do is tell the PDF reader whether a predictor was used
|
# One thing we have to do is tell the PDF reader whether a predictor was used
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -77,20 +77,19 @@ def run_polling_stderr(args, *, callback, check=False, env=None, **kwargs):
|
|||||||
args, env, process_log, text = _fix_process_args(args, env, kwargs)
|
args, env, process_log, text = _fix_process_args(args, env, kwargs)
|
||||||
assert text, "Must use text=True"
|
assert text, "Must use text=True"
|
||||||
|
|
||||||
proc = Popen(args, env=env, **kwargs)
|
with Popen(args, env=env, **kwargs) as proc:
|
||||||
|
lines = []
|
||||||
|
while proc.poll() is None:
|
||||||
|
for msg in iter(proc.stderr.readline, ''):
|
||||||
|
if process_log.isEnabledFor(logging.DEBUG):
|
||||||
|
process_log.debug(msg.strip())
|
||||||
|
callback(msg)
|
||||||
|
lines.append(msg)
|
||||||
|
stderr = ''.join(lines)
|
||||||
|
|
||||||
lines = []
|
if check and proc.returncode != 0:
|
||||||
while proc.poll() is None:
|
raise CalledProcessError(proc.returncode, args, output=None, stderr=stderr)
|
||||||
for msg in iter(proc.stderr.readline, ''):
|
return CompletedProcess(args, proc.returncode, None, stderr=stderr)
|
||||||
if process_log.isEnabledFor(logging.DEBUG):
|
|
||||||
process_log.debug(msg.strip())
|
|
||||||
callback(msg)
|
|
||||||
lines.append(msg)
|
|
||||||
stderr = ''.join(lines)
|
|
||||||
|
|
||||||
if check and proc.returncode != 0:
|
|
||||||
raise CalledProcessError(proc.returncode, args, output=None, stderr=stderr)
|
|
||||||
return CompletedProcess(args, proc.returncode, None, stderr=stderr)
|
|
||||||
|
|
||||||
|
|
||||||
def _fix_process_args(args, env, kwargs):
|
def _fix_process_args(args, env, kwargs):
|
||||||
|
|||||||
@@ -137,16 +137,7 @@ def run_ocrmypdf(input_file, output_file, *args, text=True):
|
|||||||
+ [str(input_file), str(output_file)]
|
+ [str(input_file), str(output_file)]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Tell subprocess where to find coverage.py configuration
|
|
||||||
# This has no unless except when coverage is running
|
|
||||||
# Details: https://coverage.readthedocs.io/en/coverage-5.0/subprocess.html
|
|
||||||
coverage_rc = Path(__file__).parent.parent / '.coveragerc'
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
if coverage_rc.exists():
|
|
||||||
env['COVERAGE_PROCESS_START'] = os.fspath(coverage_rc)
|
|
||||||
elif not running_in_docker():
|
|
||||||
assert False, "could not find .coveragerc"
|
|
||||||
|
|
||||||
p = run(
|
p = run(
|
||||||
p_args,
|
p_args,
|
||||||
stdout=PIPE,
|
stdout=PIPE,
|
||||||
|
|||||||
@@ -23,21 +23,22 @@ from unittest.mock import patch
|
|||||||
|
|
||||||
from ocrmypdf import hookimpl
|
from ocrmypdf import hookimpl
|
||||||
from ocrmypdf.builtin_plugins import ghostscript
|
from ocrmypdf.builtin_plugins import ghostscript
|
||||||
from ocrmypdf.subprocess import run
|
from ocrmypdf.subprocess import run_polling_stderr
|
||||||
|
|
||||||
elision_warning = """GPL Ghostscript 9.20: Setting Overprint Mode to 1
|
elision_warning = """GPL Ghostscript 9.20: Setting Overprint Mode to 1
|
||||||
not permitted in PDF/A-2, overprint mode not set"""
|
not permitted in PDF/A-2, overprint mode not set"""
|
||||||
|
|
||||||
|
|
||||||
def run_append_stderr(*args, **kwargs):
|
def run_append_stderr(*args, **kwargs):
|
||||||
proc = run(*args, **kwargs)
|
proc = run_polling_stderr(*args, **kwargs)
|
||||||
proc.stderr = b'\n'.join([proc.stderr, elision_warning.encode('utf-8')])
|
proc.stderr += '\n' + elision_warning + '\n'
|
||||||
return proc
|
return proc
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdfa_part):
|
def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdfa_part):
|
||||||
with patch('ocrmypdf._exec.ghostscript.run', new=run_append_stderr):
|
with patch('ocrmypdf._exec.ghostscript.run_polling_stderr') as mock:
|
||||||
|
mock.side_effect = run_append_stderr
|
||||||
ghostscript.generate_pdfa(
|
ghostscript.generate_pdfa(
|
||||||
pdf_pages=pdf_pages,
|
pdf_pages=pdf_pages,
|
||||||
pdfmark=pdfmark,
|
pdfmark=pdfmark,
|
||||||
@@ -47,4 +48,5 @@ def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdf
|
|||||||
pdfa_part=pdfa_part,
|
pdfa_part=pdfa_part,
|
||||||
progressbar_class=None,
|
progressbar_class=None,
|
||||||
)
|
)
|
||||||
return output_file
|
mock.assert_called_once()
|
||||||
|
return output_file
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ def run_rig_args(args, **kwargs):
|
|||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdfa_part):
|
def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdfa_part):
|
||||||
with patch('ocrmypdf._exec.ghostscript.run_polling_stderr', new=run_rig_args):
|
with patch('ocrmypdf._exec.ghostscript.run_polling_stderr') as mock:
|
||||||
|
mock.side_effect = run_rig_args
|
||||||
ghostscript.generate_pdfa(
|
ghostscript.generate_pdfa(
|
||||||
pdf_pages=pdf_pages,
|
pdf_pages=pdf_pages,
|
||||||
pdfmark=pdfmark,
|
pdfmark=pdfmark,
|
||||||
@@ -49,4 +50,5 @@ def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdf
|
|||||||
pdfa_part=pdfa_part,
|
pdfa_part=pdfa_part,
|
||||||
progressbar_class=None,
|
progressbar_class=None,
|
||||||
)
|
)
|
||||||
|
mock.assert_called()
|
||||||
return output_file
|
return output_file
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ def rasterize_pdf_page(
|
|||||||
rotation=None,
|
rotation=None,
|
||||||
filter_vector=False,
|
filter_vector=False,
|
||||||
) -> Path:
|
) -> Path:
|
||||||
with patch('ocrmypdf._exec.ghostscript.run', new=raise_gs_fail):
|
with patch('ocrmypdf._exec.ghostscript.run') as mock:
|
||||||
|
mock.side_effect = raise_gs_fail
|
||||||
ghostscript.rasterize_pdf_page(
|
ghostscript.rasterize_pdf_page(
|
||||||
input_file=input_file,
|
input_file=input_file,
|
||||||
output_file=output_file,
|
output_file=output_file,
|
||||||
@@ -55,4 +56,5 @@ def rasterize_pdf_page(
|
|||||||
rotation=rotation,
|
rotation=rotation,
|
||||||
filter_vector=filter_vector,
|
filter_vector=filter_vector,
|
||||||
)
|
)
|
||||||
|
mock.assert_called()
|
||||||
return output_file
|
return output_file
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ def raise_gs_fail(*args, **kwargs):
|
|||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdfa_part):
|
def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdfa_part):
|
||||||
with patch('ocrmypdf._exec.ghostscript.run_polling_stderr', new=raise_gs_fail):
|
with patch('ocrmypdf._exec.ghostscript.run_polling_stderr') as mock:
|
||||||
|
mock.side_effect = raise_gs_fail
|
||||||
ghostscript.generate_pdfa(
|
ghostscript.generate_pdfa(
|
||||||
pdf_pages=pdf_pages,
|
pdf_pages=pdf_pages,
|
||||||
pdfmark=pdfmark,
|
pdfmark=pdfmark,
|
||||||
@@ -44,4 +45,5 @@ def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdf
|
|||||||
pdfa_part=pdfa_part,
|
pdfa_part=pdfa_part,
|
||||||
progressbar_class=None,
|
progressbar_class=None,
|
||||||
)
|
)
|
||||||
|
mock.assert_called()
|
||||||
return output_file
|
return output_file
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ that is not UTF-8 compatible, so we are forced to check that we can convert it
|
|||||||
and present it to the user.
|
and present it to the user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
from subprocess import CalledProcessError
|
from subprocess import CalledProcessError
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@@ -42,17 +43,25 @@ def bad_utf8(*args, **kwargs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def patch_tesseract_run():
|
||||||
|
with patch('ocrmypdf._exec.tesseract.run') as mock:
|
||||||
|
mock.side_effect = bad_utf8
|
||||||
|
yield
|
||||||
|
mock.assert_called()
|
||||||
|
|
||||||
|
|
||||||
class BadUtf8OcrEngine(TesseractOcrEngine):
|
class BadUtf8OcrEngine(TesseractOcrEngine):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_hocr(input_file, output_hocr, output_text, options):
|
def generate_hocr(input_file, output_hocr, output_text, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=bad_utf8):
|
with patch_tesseract_run():
|
||||||
TesseractOcrEngine.generate_hocr(
|
TesseractOcrEngine.generate_hocr(
|
||||||
input_file, output_hocr, output_text, options
|
input_file, output_hocr, output_text, options
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_pdf(input_file, output_pdf, output_text, options):
|
def generate_pdf(input_file, output_pdf, output_text, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=bad_utf8):
|
with patch_tesseract_run():
|
||||||
TesseractOcrEngine.generate_pdf(
|
TesseractOcrEngine.generate_pdf(
|
||||||
input_file, output_pdf, output_text, options
|
input_file, output_pdf, output_text, options
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
from subprocess import CalledProcessError
|
from subprocess import CalledProcessError
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@@ -35,22 +36,30 @@ def raise_size_exception(*args, **kwargs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def patch_tesseract_run():
|
||||||
|
with patch('ocrmypdf._exec.tesseract.run') as mock:
|
||||||
|
mock.side_effect = raise_size_exception
|
||||||
|
yield
|
||||||
|
mock.assert_called()
|
||||||
|
|
||||||
|
|
||||||
class BigImageErrorOcrEngine(TesseractOcrEngine):
|
class BigImageErrorOcrEngine(TesseractOcrEngine):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_orientation(input_file, options):
|
def get_orientation(input_file, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=raise_size_exception):
|
with patch_tesseract_run():
|
||||||
return TesseractOcrEngine.get_orientation(input_file, options)
|
return TesseractOcrEngine.get_orientation(input_file, options)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_hocr(input_file, output_hocr, output_text, options):
|
def generate_hocr(input_file, output_hocr, output_text, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=raise_size_exception):
|
with patch_tesseract_run():
|
||||||
TesseractOcrEngine.generate_hocr(
|
TesseractOcrEngine.generate_hocr(
|
||||||
input_file, output_hocr, output_text, options
|
input_file, output_hocr, output_text, options
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_pdf(input_file, output_pdf, output_text, options):
|
def generate_pdf(input_file, output_pdf, output_text, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=raise_size_exception):
|
with patch_tesseract_run():
|
||||||
TesseractOcrEngine.generate_pdf(
|
TesseractOcrEngine.generate_pdf(
|
||||||
input_file, output_pdf, output_text, options
|
input_file, output_pdf, output_text, options
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
import signal
|
import signal
|
||||||
|
from contextlib import contextmanager
|
||||||
from subprocess import CalledProcessError
|
from subprocess import CalledProcessError
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@@ -37,22 +38,30 @@ def raise_crash(*args, **kwargs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def patch_tesseract_run():
|
||||||
|
with patch('ocrmypdf._exec.tesseract.run') as mock:
|
||||||
|
mock.side_effect = raise_crash
|
||||||
|
yield
|
||||||
|
mock.assert_called()
|
||||||
|
|
||||||
|
|
||||||
class CrashOcrEngine(TesseractOcrEngine):
|
class CrashOcrEngine(TesseractOcrEngine):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_orientation(input_file, options):
|
def get_orientation(input_file, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=raise_crash):
|
with patch_tesseract_run():
|
||||||
return TesseractOcrEngine.get_orientation(input_file, options)
|
return TesseractOcrEngine.get_orientation(input_file, options)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_hocr(input_file, output_hocr, output_text, options):
|
def generate_hocr(input_file, output_hocr, output_text, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=raise_crash):
|
with patch_tesseract_run():
|
||||||
TesseractOcrEngine.generate_hocr(
|
TesseractOcrEngine.generate_hocr(
|
||||||
input_file, output_hocr, output_text, options
|
input_file, output_hocr, output_text, options
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_pdf(input_file, output_pdf, output_text, options):
|
def generate_pdf(input_file, output_pdf, output_text, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=raise_crash):
|
with patch_tesseract_run():
|
||||||
TesseractOcrEngine.generate_pdf(
|
TesseractOcrEngine.generate_pdf(
|
||||||
input_file, output_pdf, output_text, options
|
input_file, output_pdf, output_text, options
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -36,12 +36,17 @@ class TestSafeSymlink:
|
|||||||
|
|
||||||
|
|
||||||
def test_no_cpu_count(monkeypatch):
|
def test_no_cpu_count(monkeypatch):
|
||||||
|
invoked = False
|
||||||
|
|
||||||
def cpu_count_raises():
|
def cpu_count_raises():
|
||||||
|
nonlocal invoked
|
||||||
|
invoked = True
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
monkeypatch.setattr(multiprocessing, 'cpu_count', cpu_count_raises)
|
monkeypatch.setattr(multiprocessing, 'cpu_count', cpu_count_raises)
|
||||||
with pytest.warns(expected_warning=UserWarning):
|
with pytest.warns(expected_warning=UserWarning):
|
||||||
assert helpers.available_cpu_count() == 1
|
assert helpers.available_cpu_count() == 1
|
||||||
|
assert invoked, "Patched function called during test"
|
||||||
|
|
||||||
|
|
||||||
def test_deprecated():
|
def test_deprecated():
|
||||||
|
|||||||
@@ -65,11 +65,12 @@ def test_cmyk_no_icc(caplog, resources, no_outpdf):
|
|||||||
def test_img2pdf_fails(resources, no_outpdf):
|
def test_img2pdf_fails(resources, no_outpdf):
|
||||||
with patch(
|
with patch(
|
||||||
'ocrmypdf._pipeline.img2pdf.convert', side_effect=img2pdf.ImageOpenError()
|
'ocrmypdf._pipeline.img2pdf.convert', side_effect=img2pdf.ImageOpenError()
|
||||||
):
|
) as mock:
|
||||||
rc = run_ocrmypdf_api(
|
rc = run_ocrmypdf_api(
|
||||||
resources / 'baiona_gray.png', no_outpdf, '--image-dpi', '200'
|
resources / 'baiona_gray.png', no_outpdf, '--image-dpi', '200'
|
||||||
)
|
)
|
||||||
assert rc == ocrmypdf.ExitCode.input_file
|
assert rc == ocrmypdf.ExitCode.input_file
|
||||||
|
mock.assert_called()
|
||||||
|
|
||||||
|
|
||||||
def test_jpeg_in_jpeg_out(resources, outpdf):
|
def test_jpeg_in_jpeg_out(resources, outpdf):
|
||||||
|
|||||||
@@ -306,6 +306,9 @@ def test_kodak_toc(resources, outpdf):
|
|||||||
assert isinstance(p.Root.Outlines.First, pikepdf.Dictionary)
|
assert isinstance(p.Root.Outlines.First, pikepdf.Dictionary)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
pikepdf.__version__ in ('2.2.2', '2.2.3'), reason="Raises wrong warning"
|
||||||
|
)
|
||||||
def test_metadata_fixup_warning(resources, outdir, caplog):
|
def test_metadata_fixup_warning(resources, outdir, caplog):
|
||||||
options = get_parser().parse_args(
|
options = get_parser().parse_args(
|
||||||
args=['--output-type', 'pdfa-2', 'graph.pdf', 'out.pdf']
|
args=['--output-type', 'pdfa-2', 'graph.pdf', 'out.pdf']
|
||||||
@@ -318,7 +321,7 @@ def test_metadata_fixup_warning(resources, outdir, caplog):
|
|||||||
)
|
)
|
||||||
metadata_fixup(working_file=outdir / 'graph.pdf', context=context)
|
metadata_fixup(working_file=outdir / 'graph.pdf', context=context)
|
||||||
for record in caplog.records:
|
for record in caplog.records:
|
||||||
assert record.levelname != 'WARNING'
|
assert record.levelname != 'WARNING', "Unexpected warning"
|
||||||
|
|
||||||
# Now add some metadata that will not be copyable
|
# Now add some metadata that will not be copyable
|
||||||
graph = pikepdf.open(outdir / 'graph.pdf')
|
graph = pikepdf.open(outdir / 'graph.pdf')
|
||||||
|
|||||||
+17
-7
@@ -21,7 +21,15 @@ from ocrmypdf.helpers import Resolution
|
|||||||
|
|
||||||
check_ocrmypdf = pytest.helpers.check_ocrmypdf # pylint: disable=e1101
|
check_ocrmypdf = pytest.helpers.check_ocrmypdf # pylint: disable=e1101
|
||||||
|
|
||||||
|
needs_pngquant = pytest.mark.skipif(
|
||||||
|
not pngquant.available(), reason="pngquant not installed"
|
||||||
|
)
|
||||||
|
needs_jbig2enc = pytest.mark.skipif(
|
||||||
|
not jbig2enc.available(), reason="jbig2enc not installed"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@needs_pngquant
|
||||||
@pytest.mark.parametrize('pdf', ['multipage.pdf', 'palette.pdf'])
|
@pytest.mark.parametrize('pdf', ['multipage.pdf', 'palette.pdf'])
|
||||||
def test_basic(resources, pdf, outpdf):
|
def test_basic(resources, pdf, outpdf):
|
||||||
infile = resources / pdf
|
infile = resources / pdf
|
||||||
@@ -30,6 +38,7 @@ def test_basic(resources, pdf, outpdf):
|
|||||||
assert 0.98 * Path(outpdf).stat().st_size <= Path(infile).stat().st_size
|
assert 0.98 * Path(outpdf).stat().st_size <= Path(infile).stat().st_size
|
||||||
|
|
||||||
|
|
||||||
|
@needs_pngquant
|
||||||
def test_mono_not_inverted(resources, outdir):
|
def test_mono_not_inverted(resources, outdir):
|
||||||
infile = resources / '2400dpi.pdf'
|
infile = resources / '2400dpi.pdf'
|
||||||
opt.main(infile, outdir / 'out.pdf', level=3)
|
opt.main(infile, outdir / 'out.pdf', level=3)
|
||||||
@@ -45,7 +54,7 @@ def test_mono_not_inverted(resources, outdir):
|
|||||||
assert im.getpixel((0, 0)) == 255, "Expected white background"
|
assert im.getpixel((0, 0)) == 255, "Expected white background"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not pngquant.available(), reason='need pngquant')
|
@needs_pngquant
|
||||||
def test_jpg_png_params(resources, outpdf):
|
def test_jpg_png_params(resources, outpdf):
|
||||||
check_ocrmypdf(
|
check_ocrmypdf(
|
||||||
resources / 'crom.png',
|
resources / 'crom.png',
|
||||||
@@ -63,7 +72,7 @@ def test_jpg_png_params(resources, outpdf):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not jbig2enc.available(), reason='need jbig2enc')
|
@needs_jbig2enc
|
||||||
@pytest.mark.parametrize('lossy', [False, True])
|
@pytest.mark.parametrize('lossy', [False, True])
|
||||||
def test_jbig2_lossy(lossy, resources, outpdf):
|
def test_jbig2_lossy(lossy, resources, outpdf):
|
||||||
args = [
|
args = [
|
||||||
@@ -95,10 +104,8 @@ def test_jbig2_lossy(lossy, resources, outpdf):
|
|||||||
assert len(pim.decode_parms) == 0
|
assert len(pim.decode_parms) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@needs_pngquant
|
||||||
not jbig2enc.available() or not pngquant.available(),
|
@needs_jbig2enc
|
||||||
reason='need jbig2enc and pngquant',
|
|
||||||
)
|
|
||||||
def test_flate_to_jbig2(resources, outdir):
|
def test_flate_to_jbig2(resources, outdir):
|
||||||
# This test requires an image that pngquant is capable of converting to
|
# This test requires an image that pngquant is capable of converting to
|
||||||
# to 1bpp - so use an existing 1bpp image, convert up, confirm it can
|
# to 1bpp - so use an existing 1bpp image, convert up, confirm it can
|
||||||
@@ -126,6 +133,7 @@ def test_flate_to_jbig2(resources, outdir):
|
|||||||
assert pim.filters[0] == '/JBIG2Decode'
|
assert pim.filters[0] == '/JBIG2Decode'
|
||||||
|
|
||||||
|
|
||||||
|
@needs_pngquant
|
||||||
def test_multiple_pngs(resources, outdir):
|
def test_multiple_pngs(resources, outdir):
|
||||||
with Path.open(outdir / 'in.pdf', 'wb') as inpdf:
|
with Path.open(outdir / 'in.pdf', 'wb') as inpdf:
|
||||||
img2pdf.convert(
|
img2pdf.convert(
|
||||||
@@ -141,7 +149,8 @@ def test_multiple_pngs(resources, outdir):
|
|||||||
draw.rectangle((0, 0, im.width, im.height), fill=128)
|
draw.rectangle((0, 0, im.width, im.height), fill=128)
|
||||||
im.save(output_file)
|
im.save(output_file)
|
||||||
|
|
||||||
with patch('ocrmypdf.optimize.pngquant.quantize', new=mockquant):
|
with patch('ocrmypdf.optimize.pngquant.quantize') as mock:
|
||||||
|
mock.side_effect = mockquant
|
||||||
check_ocrmypdf(
|
check_ocrmypdf(
|
||||||
outdir / 'in.pdf',
|
outdir / 'in.pdf',
|
||||||
outdir / 'out.pdf',
|
outdir / 'out.pdf',
|
||||||
@@ -155,6 +164,7 @@ def test_multiple_pngs(resources, outdir):
|
|||||||
'--plugin',
|
'--plugin',
|
||||||
'tests/plugins/tesseract_noop.py',
|
'tests/plugins/tesseract_noop.py',
|
||||||
)
|
)
|
||||||
|
mock.assert_called()
|
||||||
|
|
||||||
with pikepdf.open(outdir / 'in.pdf') as inpdf, pikepdf.open(
|
with pikepdf.open(outdir / 'in.pdf') as inpdf, pikepdf.open(
|
||||||
outdir / 'out.pdf'
|
outdir / 'out.pdf'
|
||||||
|
|||||||
@@ -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,
|
||||||
|
)
|
||||||
|
|||||||
@@ -28,11 +28,12 @@ def test_no_unpaper(resources, no_outpdf):
|
|||||||
output = fspath(no_outpdf)
|
output = fspath(no_outpdf)
|
||||||
|
|
||||||
_parser, options, pm = get_parser_options_plugins(["--clean", input_, output])
|
_parser, options, pm = get_parser_options_plugins(["--clean", input_, output])
|
||||||
with patch("ocrmypdf._exec.unpaper.version") as mock_unpaper_version:
|
with patch("ocrmypdf._exec.unpaper.version") as mock:
|
||||||
mock_unpaper_version.side_effect = FileNotFoundError("unpaper")
|
mock.side_effect = FileNotFoundError("unpaper")
|
||||||
|
|
||||||
with pytest.raises(MissingDependencyError):
|
with pytest.raises(MissingDependencyError):
|
||||||
check_options(options, pm)
|
check_options(options, pm)
|
||||||
|
mock.assert_called()
|
||||||
|
|
||||||
|
|
||||||
def test_old_unpaper(resources, no_outpdf):
|
def test_old_unpaper(resources, no_outpdf):
|
||||||
@@ -40,11 +41,12 @@ def test_old_unpaper(resources, no_outpdf):
|
|||||||
output = fspath(no_outpdf)
|
output = fspath(no_outpdf)
|
||||||
|
|
||||||
_parser, options, pm = get_parser_options_plugins(["--clean", input_, output])
|
_parser, options, pm = get_parser_options_plugins(["--clean", input_, output])
|
||||||
with patch("ocrmypdf._exec.unpaper.version") as mock_unpaper_version:
|
with patch("ocrmypdf._exec.unpaper.version") as mock:
|
||||||
mock_unpaper_version.return_value = '0.5'
|
mock.return_value = '0.5'
|
||||||
|
|
||||||
with pytest.raises(MissingDependencyError):
|
with pytest.raises(MissingDependencyError):
|
||||||
check_options(options, pm)
|
check_options(options, pm)
|
||||||
|
mock.assert_called()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not have_unpaper(), reason="requires unpaper")
|
@pytest.mark.skipif(not have_unpaper(), reason="requires unpaper")
|
||||||
|
|||||||
@@ -188,18 +188,20 @@ def test_language_warning(caplog):
|
|||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
with patch(
|
with patch(
|
||||||
'ocrmypdf._validation.locale.getlocale', return_value=('en_US', 'UTF-8')
|
'ocrmypdf._validation.locale.getlocale', return_value=('en_US', 'UTF-8')
|
||||||
):
|
) as mock:
|
||||||
vd.check_options_languages(opts, {'eng'})
|
vd.check_options_languages(opts, {'eng'})
|
||||||
assert opts.languages == {'eng'}
|
assert opts.languages == {'eng'}
|
||||||
assert '' in caplog.text
|
assert '' in caplog.text
|
||||||
|
mock.assert_called_once()
|
||||||
|
|
||||||
opts = make_opts(language=None)
|
opts = make_opts(language=None)
|
||||||
with patch(
|
with patch(
|
||||||
'ocrmypdf._validation.locale.getlocale', return_value=('fr_FR', 'UTF-8')
|
'ocrmypdf._validation.locale.getlocale', return_value=('fr_FR', 'UTF-8')
|
||||||
):
|
) as mock:
|
||||||
vd.check_options_languages(opts, {'eng'})
|
vd.check_options_languages(opts, {'eng'})
|
||||||
assert opts.languages == {'eng'}
|
assert opts.languages == {'eng'}
|
||||||
assert 'assuming --language' in caplog.text
|
assert 'assuming --language' in caplog.text
|
||||||
|
mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
def test_version_comparison():
|
def test_version_comparison():
|
||||||
@@ -265,7 +267,8 @@ def test_pagesegmode_warning(caplog):
|
|||||||
|
|
||||||
|
|
||||||
def test_two_languages():
|
def test_two_languages():
|
||||||
with patch('ocrmypdf._exec.tesseract.has_textonly_pdf', return_value=True):
|
with patch('ocrmypdf._exec.tesseract.has_textonly_pdf', return_value=True) as mock:
|
||||||
vd._check_options(
|
vd._check_options(
|
||||||
*make_opts_pm(language='fakelang1+fakelang2'), {'fakelang1', 'fakelang2'}
|
*make_opts_pm(language='fakelang1+fakelang2'), {'fakelang1', 'fakelang2'}
|
||||||
)
|
)
|
||||||
|
mock.assert_called()
|
||||||
|
|||||||
Reference in New Issue
Block a user