Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2847ea4c3 | ||
|
|
19e35db2b7 | ||
|
|
df688742d5 | ||
|
|
42c2925f9d | ||
|
|
933f0b8f9b | ||
|
|
03ab5a8ee2 | ||
|
|
4f06920224 | ||
|
|
a733b09623 | ||
|
|
5483dacf52 | ||
|
|
ae7844ad88 | ||
|
|
a6e7485da6 | ||
|
|
3bcc6d6121 | ||
|
|
9fe067bbd9 | ||
|
|
f095e91cb4 | ||
|
|
66c8d4b47a | ||
|
|
721489a06c | ||
|
|
9a4493f211 | ||
|
|
edb4d6c586 |
@@ -16,4 +16,9 @@ COPY .docker/webservice.py /application
|
||||
|
||||
USER docker
|
||||
|
||||
VOLUME ["/config"]
|
||||
|
||||
# This config file is optional
|
||||
ENV OCRMYPDF_WEBSERVICE_SETTINGS "/config/config.py"
|
||||
|
||||
ENTRYPOINT ["python3", "/application/webservice.py"]
|
||||
|
||||
@@ -32,11 +32,9 @@ import shlex
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = "secret"
|
||||
app.config['MAX_CONTENT_LENGTH'] = 50_000_000
|
||||
app.config.from_envvar("OCRMYPDF_WEBSERVICE_SETTINGS", silent=True)
|
||||
|
||||
uploaddir = TemporaryDirectory(prefix="ocrmypdf-upload")
|
||||
downloaddir = TemporaryDirectory(prefix="ocrmypdf-download")
|
||||
|
||||
app.config["UPLOAD_FOLDER"] = uploaddir
|
||||
ALLOWED_EXTENSIONS = set(["pdf"])
|
||||
|
||||
|
||||
@@ -45,9 +43,13 @@ def allowed_file(filename):
|
||||
|
||||
|
||||
def do_ocrmypdf(file):
|
||||
uploaddir = TemporaryDirectory(prefix="ocrmypdf-upload")
|
||||
downloaddir = TemporaryDirectory(prefix="ocrmypdf-download")
|
||||
|
||||
filename = secure_filename(file.filename)
|
||||
up_file = os.path.join(uploaddir.name, filename)
|
||||
file.save(up_file)
|
||||
|
||||
down_file = os.path.join(downloaddir.name, filename)
|
||||
|
||||
cmd_args = [arg for arg in shlex.split(request.form["params"])]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: stable
|
||||
hooks:
|
||||
- id: black
|
||||
language_version: python3.6
|
||||
+28
-1
@@ -1,6 +1,34 @@
|
||||
Advanced features
|
||||
=================
|
||||
|
||||
Control of unpaper
|
||||
------------------
|
||||
|
||||
OCRmyPDF uses ``unpaper`` to provide the implementation of the ``--clean`` and ``--clean-final`` arguments. `unpaper <https://github.com/Flameeyes/unpaper/blob/master/doc/basic-concepts.md>`_ provides a variety of image processing filters to improve images.
|
||||
|
||||
By default, OCRmyPDF uses only ``unpaper`` arguments that were found to be safe to use on almost all files without having to inspect every page of the file afterwards. This is particularly true when only ``--clean`` is used, since that instructs OCRmyPDF to only clean the image before OCR and not the final image.
|
||||
|
||||
However, if you wish to use the more aggressive options in ``unpaper``, you may use ``--unpaper-args '...'`` to override the OCRmyPDF's defaults and forward other arguments to unpaper. This option will forward arguments to ``unpaper`` without any knowledge of what that program considers to be valid arguments. The string of arguments must be quoted as shown in the examples below. No filename arguments may be included. OCRmyPDF will assume it can append input and output filename of intermediate images to the ``--unpaper-args`` string.
|
||||
|
||||
In this example, we tell ``unpaper`` to expect two pages of text on a sheet (image), such as occurs when two facing pages of a book are scanned. ``unpaper`` uses this information to deskew each independently and clean up the margins of both.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
ocrmypdf --clean --clean-final --unpaper-args '--layout double' input.pdf output.pdf
|
||||
ocrmypdf --clean --clean-final --unpaper-args '--layout double --no-noisefilter' input.pdf output.pdf
|
||||
|
||||
.. warning::
|
||||
|
||||
Some ``unpaper`` features will reposition text within the image. ``--clean-final`` is recommended to avoid this issue.
|
||||
|
||||
.. warning::
|
||||
|
||||
Some ``unpaper`` features cause multiple input or output files to be consumed or produced. OCRmyPDF requires ``unpaper`` to consume one file and produce one file. An deviation from that condition will result in errors.
|
||||
|
||||
.. note::
|
||||
|
||||
``unpaper`` uses uncompressed PBM/PGM/PPM files for its intermediate files. For large images or documents, it can take a lot of temporary disk space.
|
||||
|
||||
Control of OCR options
|
||||
----------------------
|
||||
|
||||
@@ -17,7 +45,6 @@ If ``--redo-ocr`` is issued, then a detailed text analysis is performed. Text is
|
||||
|
||||
If ``--force-ocr`` is issued, then all pages will be rasterized to images, discarding any hidden OCR text, and rasterizing any printable text. This is useful for redoing OCR, for fixing OCR text with a damaged character map (text is selectable but not searchable), and destroying redacted information. Any forms and vector graphics will be rasterized as well.
|
||||
|
||||
|
||||
Time and image size limits
|
||||
""""""""""""""""""""""""""
|
||||
|
||||
|
||||
+5
-7
@@ -18,12 +18,12 @@ The ``--tag`` argument tells parallel to print the filename as a prefix whenever
|
||||
|
||||
parallel --tag -j 2 ocrmypdf '{}' 'output/{}' ::: *.pdf
|
||||
|
||||
OCRmyPDF automatically repairs PDFs before parsing and gathering information from them. If you are already repairing PDFs with ``qpdf`` prior to attempting OCR, or you can use ``--skip-repair`` to skip this step. It may improve performance for large files, since repairing PDFs is single-threaded.
|
||||
OCRmyPDF automatically repairs PDFs before parsing and gathering information from them.
|
||||
|
||||
Directory trees
|
||||
---------------
|
||||
|
||||
This will walk through a directory tree and run OCR on all files in place, printing the output in a way that makes
|
||||
This will walk through a directory tree and run OCR on all files in place, printing the output in a way that makes
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -66,7 +66,7 @@ This user contributed script also provides an example of batch processing.
|
||||
log_file = script_dir + '/ocr-tree.log'
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format='%(asctime)s %(message)s',
|
||||
level=logging.INFO, format='%(asctime)s %(message)s',
|
||||
filename=log_file, filemode='w')
|
||||
|
||||
for dir_name, subdirs, file_list in os.walk(start_dir):
|
||||
@@ -144,11 +144,11 @@ This is only possible for x86-based Synology products. Some Synology products us
|
||||
timestamp_OCR = time.strftime("%Y-%m-%d-%H%M_OCR_")
|
||||
filename_OCR = timestamp_OCR + file_noext + '.pdf'
|
||||
docker_mount = dir_name + ':/home/docker'
|
||||
# create string for pdf processing
|
||||
# create string for pdf processing
|
||||
# diskstation needs a user:group docker:docker. find uid:gid of your diskstation docker:docker with id docker.
|
||||
# use this uid:gid in -u flag
|
||||
# rw rights for docker:docker at source dir are also necessary
|
||||
# the script is processed as root user via chron
|
||||
# the script is processed as root user via chron
|
||||
cmd = ['docker', 'run', '--rm', '-v', docker_mount, '-u=1030:65538', 'jbarlow83/ocrmypdf', , '--deskew' , filename, filename_OCR]
|
||||
logging.info(cmd)
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
@@ -210,5 +210,3 @@ Alternatives
|
||||
""""""""""""
|
||||
|
||||
* `Watchman <https://facebook.github.io/watchman/>`_ is a more powerful alternative to ``watchmedo``.
|
||||
|
||||
|
||||
|
||||
@@ -260,14 +260,14 @@ These installation instructions omit the optional dependency ``unpaper``, which
|
||||
|
||||
To add JBIG2 encoding, see :ref:`jbig2`.
|
||||
|
||||
ArchLinux
|
||||
^^^^^^^^^
|
||||
ArchLinux (AUR)
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
.. image:: https://repology.org/badge/version-for-repo/aur/ocrmypdf.svg
|
||||
:alt: ArchLinux
|
||||
:target: https://repology.org/metapackage/ocrmypdf
|
||||
|
||||
The author is aware of an `ArchLinux User Repository package for ocrmypdf <https://aur.archlinux.org/packages/ocrmypdf/>`_. You can use the following command.
|
||||
There is an `ArchLinux User Repository package for ocrmypdf <https://aur.archlinux.org/packages/ocrmypdf/>`_. You can use the following command.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -399,7 +399,7 @@ Assuming you have a Docker engine running, you can download one of the three ava
|
||||
- ``docker pull jbarlow83/ocrmypdf-polyglot``
|
||||
- As above, with all available language packs.
|
||||
* - ocrmypdf-webservice
|
||||
- ``docker pull jbarlow83/ocrmypdf-polyglot``
|
||||
- ``docker pull jbarlow83/ocrmypdf-webservice``
|
||||
- All language packs, and a simple HTTP wrapper allowing OCRmyPDF to be used as a web service. Note that this component is licensed under AGPLv3.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -13,6 +13,18 @@ Note that it is licensed under GPLv3, so scripts that ``import ocrmypdf`` and ar
|
||||
find: [^`]\#([0-9]{1,3})[^0-9]
|
||||
replace: `#$1 <https://github.com/jbarlow83/OCRmyPDF/issues/$1>`_
|
||||
|
||||
v8.1.0
|
||||
------
|
||||
|
||||
- Added a feature, ``--unpaper-args``, which allows passing arbitrary arguments to ``unpaper`` when using ``--clean`` or ``--clean-final``. The default, very conservative unpaper settings are suppressed.
|
||||
|
||||
- The argument ``--clean-final`` now implies ``--clean``. It was possible to issue ``--clean-final`` on its before this, but it would have no useful effect.
|
||||
|
||||
- Fixed an exception on traversing corrupt table of contents entries (specifically, those with invalid destination objects)
|
||||
|
||||
- Fixed an issue when using ``--tesseract-timeout`` and image processing features on a file with more than 100 pages. `#347 <https://github.com/jbarlow83/OCRmyPDF/issues/347>`_
|
||||
|
||||
- OCRmyPDF now always calls ``os.nice(5)`` to signal to operating systems that it is a background process.
|
||||
|
||||
v8.0.1
|
||||
------
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
py36 = true
|
||||
skip-string-normalization = true
|
||||
include = '\.pyi?$'
|
||||
exclude = '''
|
||||
/(
|
||||
\.eggs
|
||||
| \.git
|
||||
| \.hg
|
||||
| \.mypy_cache
|
||||
| \.tox
|
||||
| \.venv
|
||||
| _build
|
||||
| buck-out
|
||||
| build
|
||||
| dist
|
||||
| docs
|
||||
| misc
|
||||
| \.egg-info
|
||||
)/
|
||||
'''
|
||||
@@ -278,6 +278,13 @@ preprocessing.add_argument(
|
||||
help="Clean page as above, and incorporate the cleaned image in the final "
|
||||
"PDF. Might remove desired content.",
|
||||
)
|
||||
preprocessing.add_argument(
|
||||
'--unpaper-args',
|
||||
type=str,
|
||||
default=None,
|
||||
help="A quoted string of arguments to pass to unpaper. Requires --clean. "
|
||||
"Example: --unpaper-args '--layout double'.",
|
||||
)
|
||||
preprocessing.add_argument(
|
||||
'--oversample',
|
||||
metavar='DPI',
|
||||
@@ -623,12 +630,25 @@ def _optional_program_recommended(name, version_fn, min_version, for_argument):
|
||||
|
||||
|
||||
def check_options_preprocessing(options, log):
|
||||
if options.clean_final:
|
||||
options.clean = True
|
||||
if options.unpaper_args and not options.clean:
|
||||
raise argparse.ArgumentError(
|
||||
None, "--clean is required for --unpaper-args"
|
||||
)
|
||||
if any((options.clean, options.clean_final)):
|
||||
from .exec import unpaper
|
||||
|
||||
_optional_program_required(
|
||||
'unpaper', unpaper.version, '6.1', '--clean, --clean-final'
|
||||
)
|
||||
try:
|
||||
if options.unpaper_args:
|
||||
options.unpaper_args = unpaper.validate_custom_args(
|
||||
options.unpaper_args
|
||||
)
|
||||
except Exception as e:
|
||||
raise argparse.ArgumentError(None, str(e))
|
||||
|
||||
|
||||
def check_options_ocr_behavior(options, log):
|
||||
@@ -1081,6 +1101,8 @@ def run_pipeline(args=None):
|
||||
|
||||
build_pipeline(options, work_folder, _log, context)
|
||||
atexit.register(cleanup_working_files, work_folder, options)
|
||||
if hasattr(os, 'nice'):
|
||||
os.nice(5)
|
||||
cmdline.run(options)
|
||||
except ruffus_exceptions.RethrownJobError as e:
|
||||
if options.verbose:
|
||||
|
||||
@@ -566,7 +566,7 @@ def preprocess_clean(input_file, output_file, log, context):
|
||||
pageinfo = get_pageinfo(input_file, context)
|
||||
dpi = get_page_square_dpi(pageinfo, options)
|
||||
|
||||
unpaper.clean(input_file, output_file, dpi, log)
|
||||
unpaper.clean(input_file, output_file, dpi, log, options.unpaper_args)
|
||||
|
||||
|
||||
def select_ocr_image(infiles, output_file, log, context):
|
||||
@@ -648,7 +648,7 @@ def ocr_tesseract_hocr(input_file, output_files, log, context):
|
||||
|
||||
|
||||
def select_visible_page_image(infiles, output_file, log, context):
|
||||
"Selects a whole page image that we can show the user (if necessary)"
|
||||
"""Selects a whole page image that we can show the user (if necessary)"""
|
||||
|
||||
options = context.get_options()
|
||||
if options.clean_final:
|
||||
|
||||
+19
-11
@@ -17,6 +17,7 @@
|
||||
|
||||
from itertools import groupby
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
import pikepdf
|
||||
|
||||
@@ -24,8 +25,11 @@ from .exec import tesseract
|
||||
from .helpers import flatten_groups, page_number
|
||||
|
||||
|
||||
MAX_OPEN_PAGE_PDFS = int(os.environ.get('_OCRMYPDF_MAX_OPEN_PAGE_PDFS', 100))
|
||||
|
||||
|
||||
def _update_page_resources(*, page, font, font_key, procset):
|
||||
"Update this page's fonts with a reference to the Glyphless font"
|
||||
"""Update this page's fonts with a reference to the Glyphless font"""
|
||||
|
||||
if '/Resources' not in page:
|
||||
page['/Resources'] = pikepdf.Dictionary({})
|
||||
@@ -34,7 +38,7 @@ def _update_page_resources(*, page, font, font_key, procset):
|
||||
fonts = resources['/Font']
|
||||
except KeyError:
|
||||
fonts = pikepdf.Dictionary({})
|
||||
if font_key not in fonts:
|
||||
if font_key is not None and font_key not in fonts:
|
||||
fonts[font_key] = font
|
||||
resources['/Font'] = fonts
|
||||
|
||||
@@ -160,7 +164,7 @@ def _weave_layers_graft(
|
||||
|
||||
|
||||
def _find_font(text, pdf_base):
|
||||
"Copy a font from the filename text into pdf_base"
|
||||
"""Copy a font from the filename text into pdf_base"""
|
||||
|
||||
font, font_key = None, None
|
||||
possible_font_names = ('/f-0-0', '/F1')
|
||||
@@ -177,6 +181,8 @@ def _find_font(text, pdf_base):
|
||||
break
|
||||
if pdf_text_font:
|
||||
font = pdf_base.copy_foreign(pdf_text_font)
|
||||
if font_key is None:
|
||||
print('font_key is None')
|
||||
return font, font_key
|
||||
|
||||
|
||||
@@ -245,12 +251,14 @@ def _fix_toc(pdf_base, pageref_remap, log):
|
||||
Inner helper function: change the objgen for any page from the old we
|
||||
invalidated to its new one.
|
||||
"""
|
||||
if not isinstance(dest_node, pikepdf.Array):
|
||||
return
|
||||
pageref = dest_node[0]
|
||||
if pageref['/Type'] == '/Page' and pageref.objgen in pageref_remap:
|
||||
new_objgen = pageref_remap[pageref.objgen]
|
||||
dest_node[0] = pdf_base.get_object(new_objgen)
|
||||
try:
|
||||
pageref = dest_node[0]
|
||||
if pageref['/Type'] == '/Page' and pageref.objgen in pageref_remap:
|
||||
new_objgen = pageref_remap[pageref.objgen]
|
||||
dest_node[0] = pdf_base.get_object(new_objgen)
|
||||
except (IndexError, TypeError) as e:
|
||||
log.warning("This file may contain invalid table of contents entries")
|
||||
log.debug(e)
|
||||
|
||||
def visit_remap_dest(pdf_base, node, log):
|
||||
"""
|
||||
@@ -390,7 +398,7 @@ def weave_layers(infiles, output_file, log, context):
|
||||
content_rotation - autorotate_correction
|
||||
) % 360
|
||||
|
||||
if len(keep_open) > 100:
|
||||
if len(keep_open) > MAX_OPEN_PAGE_PDFS:
|
||||
# qpdf limitations require us to keep files open when we intend
|
||||
# to copy content from them before saving. However, we want to keep
|
||||
# a lid on file handles and memory usage, so for big files we're
|
||||
@@ -407,7 +415,7 @@ def weave_layers(infiles, output_file, log, context):
|
||||
|
||||
pdf_base = pikepdf.open(interim)
|
||||
procset = pdf_base.pages[0].Resources.ProcSet
|
||||
font = pdf_base.pages[0].Resources.Font.get(font_key)
|
||||
font, font_key = None, None # Reacquire this information
|
||||
|
||||
_fix_toc(pdf_base, pagerefs, log)
|
||||
pdf_base.save(output_file)
|
||||
|
||||
@@ -200,7 +200,9 @@ def tesseract_log_output(log, stdout, input_file):
|
||||
log.info(prefix + line.strip())
|
||||
|
||||
|
||||
def page_timedout(log, input_file):
|
||||
def page_timedout(log, input_file, timeout):
|
||||
if timeout == 0:
|
||||
return
|
||||
prefix = f"{(page_number(input_file)):4d}: [tesseract] "
|
||||
log.warning(prefix + " took too long to OCR - skipping")
|
||||
|
||||
@@ -257,7 +259,7 @@ def generate_hocr(
|
||||
# Generate a HOCR file with no recognized text if tesseract times out
|
||||
# Temporary workaround to hocrTransform not being able to function if
|
||||
# it does not have a valid hOCR file.
|
||||
page_timedout(log, input_file)
|
||||
page_timedout(log, input_file, timeout)
|
||||
_generate_null_hocr(output_hocr, output_sidecar, input_file)
|
||||
except CalledProcessError as e:
|
||||
tesseract_log_output(log, e.output, input_file)
|
||||
@@ -347,7 +349,7 @@ def generate_pdf(
|
||||
if os.path.exists(prefix + '.txt'):
|
||||
shutil.move(prefix + '.txt', output_text)
|
||||
except TimeoutExpired:
|
||||
page_timedout(log, input_image)
|
||||
page_timedout(log, input_image, timeout)
|
||||
use_skip_page(text_only, skip_pdf, output_pdf, output_text)
|
||||
except CalledProcessError as e:
|
||||
tesseract_log_output(log, e.output, input_image)
|
||||
|
||||
@@ -19,13 +19,15 @@
|
||||
# https://github.com/Flameeyes/unpaper/blob/master/doc/basic-concepts.md
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from subprocess import STDOUT, CalledProcessError, check_output
|
||||
from tempfile import NamedTemporaryFile
|
||||
from subprocess import PIPE, STDOUT, CalledProcessError
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from . import get_version
|
||||
from ..exceptions import MissingDependencyError
|
||||
from ..exceptions import MissingDependencyError, SubprocessOutputError
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
@@ -64,43 +66,64 @@ def run(input_file, output_file, dpi, log, mode_args):
|
||||
im.close()
|
||||
raise MissingDependencyError() from e
|
||||
|
||||
with NamedTemporaryFile(suffix=suffix) as input_pnm, NamedTemporaryFile(
|
||||
suffix=suffix, mode="r+b"
|
||||
) as output_pnm:
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
input_pnm = os.path.join(tmpdir, f'input{suffix}')
|
||||
output_pnm = os.path.join(tmpdir, f'output{suffix}')
|
||||
im.save(input_pnm, format='PPM')
|
||||
im.close()
|
||||
|
||||
os.unlink(output_pnm.name)
|
||||
|
||||
args_unpaper.extend([input_pnm.name, output_pnm.name])
|
||||
# To prevent any shenanigans from accepting arbitrary parameters in
|
||||
# --unpaper-args, we:
|
||||
# 1) run with cwd set to a tmpdir with only unpaper's files
|
||||
# 2) forbid the use of '/' in arguments, to prevent changing paths
|
||||
# 3) append absolute paths for the input and output file
|
||||
# This should ensure that a user cannot clobber some other file with
|
||||
# their unpaper arguments (whether intentionally or otherwise)
|
||||
args_unpaper.extend([input_pnm, output_pnm])
|
||||
try:
|
||||
stdout = check_output(
|
||||
args_unpaper, close_fds=True, universal_newlines=True, stderr=STDOUT
|
||||
proc = subprocess.run(
|
||||
args_unpaper,
|
||||
check=True,
|
||||
close_fds=True,
|
||||
universal_newlines=True,
|
||||
stderr=STDOUT,
|
||||
cwd=tmpdir,
|
||||
stdout=PIPE,
|
||||
)
|
||||
except CalledProcessError as e:
|
||||
log.debug(e.output)
|
||||
raise e from e
|
||||
else:
|
||||
log.debug(stdout)
|
||||
# unpaper sets dpi to 72
|
||||
Image.open(output_pnm.name).save(output_file, dpi=(dpi, dpi))
|
||||
log.debug(proc.stdout)
|
||||
# unpaper sets dpi to 72; fix this
|
||||
try:
|
||||
Image.open(output_pnm).save(output_file, dpi=(dpi, dpi))
|
||||
except (FileNotFoundError, OSError):
|
||||
raise SubprocessOutputError(
|
||||
"unpaper: failed to produce the expected output file. Called with: "
|
||||
+ str(args_unpaper)
|
||||
) from None
|
||||
|
||||
|
||||
def clean(input_file, output_file, dpi, log):
|
||||
run(
|
||||
input_file,
|
||||
output_file,
|
||||
dpi,
|
||||
log,
|
||||
[
|
||||
'--layout',
|
||||
'none',
|
||||
'--mask-scan-size',
|
||||
'100', # don't blank out narrow columns
|
||||
'--no-border-align', # don't align visible content to borders
|
||||
'--no-mask-center', # don't center visible content within page
|
||||
'--no-grayfilter', # don't remove light gray areas
|
||||
'--no-blackfilter', # don't remove solid black areas
|
||||
'--no-deskew', # don't deskew
|
||||
],
|
||||
)
|
||||
def validate_custom_args(args: str):
|
||||
unpaper_args = shlex.split(args)
|
||||
if any('/' in arg for arg in unpaper_args):
|
||||
raise ValueError('No filenames allowed in --unpaper-args')
|
||||
return unpaper_args
|
||||
|
||||
|
||||
def clean(input_file, output_file, dpi, log, unpaper_args=None):
|
||||
default_args = [
|
||||
'--layout',
|
||||
'none',
|
||||
'--mask-scan-size',
|
||||
'100', # don't blank out narrow columns
|
||||
'--no-border-align', # don't align visible content to borders
|
||||
'--no-mask-center', # don't center visible content within page
|
||||
'--no-grayfilter', # don't remove light gray areas
|
||||
'--no-blackfilter', # don't remove solid black areas
|
||||
'--no-deskew', # don't deskew
|
||||
]
|
||||
if not unpaper_args:
|
||||
unpaper_args = default_args
|
||||
run(input_file, output_file, dpi, log, unpaper_args)
|
||||
|
||||
+1
-1
@@ -147,7 +147,7 @@ def no_outpdf(tmpdir):
|
||||
|
||||
@pytest.helpers.register
|
||||
def check_ocrmypdf(input_file, output_file, *args, env=None):
|
||||
"Run ocrmypdf and confirmed that a valid file was created"
|
||||
"""Run ocrmypdf and confirmed that a valid file was created"""
|
||||
|
||||
p, out, err = run_ocrmypdf(input_file, output_file, *args, env=env)
|
||||
# ensure py.test collects the output, use -s to view
|
||||
|
||||
+68
-10
@@ -15,11 +15,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import argparse
|
||||
from os import fspath
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from ocrmypdf import __main__ as main
|
||||
from ocrmypdf.exceptions import ExitCode
|
||||
from ocrmypdf.exec import unpaper
|
||||
|
||||
# pytest.helpers is dynamic
|
||||
# pylint: disable=no-member
|
||||
@@ -30,26 +35,79 @@ run_ocrmypdf = pytest.helpers.run_ocrmypdf
|
||||
spoof = pytest.helpers.spoof
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def have_unpaper():
|
||||
try:
|
||||
unpaper.version()
|
||||
except Exception:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def spoof_unpaper_oldversion(tmpdir_factory):
|
||||
return spoof(tmpdir_factory, unpaper='unpaper_oldversion.py')
|
||||
return spoof(tmpdir_factory, unpaper="unpaper_oldversion.py")
|
||||
|
||||
|
||||
@pytest.mark.skipif(True, reason="needs new fixture implementation")
|
||||
def test_no_unpaper(resources, no_outpdf):
|
||||
# <disable unpaper here>
|
||||
p, out, err = run_ocrmypdf(
|
||||
resources / 'c02-22.pdf', no_outpdf, '--clean', env=os.environ
|
||||
)
|
||||
assert p.returncode == ExitCode.missing_dependency
|
||||
input_ = fspath(resources / "c02-22.pdf")
|
||||
output = fspath(no_outpdf)
|
||||
options = main.parser.parse_args(args=["--clean", input_, output])
|
||||
|
||||
with patch("ocrmypdf.exec.unpaper.version") as mock_unpaper_version:
|
||||
mock_unpaper_version.side_effect = FileNotFoundError("unpaper")
|
||||
with pytest.raises(SystemExit):
|
||||
main.check_options(options, log=MagicMock())
|
||||
|
||||
|
||||
def test_old_unpaper(spoof_unpaper_oldversion, resources, no_outpdf):
|
||||
p, out, err = run_ocrmypdf(
|
||||
resources / 'c02-22.pdf', no_outpdf, '--clean', env=spoof_unpaper_oldversion
|
||||
resources / "c02-22.pdf", no_outpdf, "--clean", env=spoof_unpaper_oldversion
|
||||
)
|
||||
assert p.returncode == ExitCode.missing_dependency
|
||||
|
||||
|
||||
@pytest.mark.skipif(not have_unpaper(), reason="requires unpaper")
|
||||
def test_clean(spoof_tesseract_noop, resources, outpdf):
|
||||
check_ocrmypdf(resources / 'skew.pdf', outpdf, '-c', env=spoof_tesseract_noop)
|
||||
check_ocrmypdf(resources / "skew.pdf", outpdf, "-c", env=spoof_tesseract_noop)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not have_unpaper(), reason="requires unpaper")
|
||||
def test_unpaper_args_valid(spoof_tesseract_noop, resources, outpdf):
|
||||
check_ocrmypdf(
|
||||
resources / "skew.pdf",
|
||||
outpdf,
|
||||
"-c",
|
||||
"--unpaper-args",
|
||||
"--layout double", # Spaces required here
|
||||
env=spoof_tesseract_noop,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not have_unpaper(), reason="requires unpaper")
|
||||
def test_unpaper_args_invalid_filename(spoof_tesseract_noop, resources, outpdf):
|
||||
p, out, err = run_ocrmypdf(
|
||||
resources / "skew.pdf",
|
||||
outpdf,
|
||||
"-c",
|
||||
"--unpaper-args",
|
||||
"/etc/passwd",
|
||||
env=spoof_tesseract_noop,
|
||||
)
|
||||
assert "No filenames allowed" in err
|
||||
assert p.returncode == ExitCode.bad_args
|
||||
|
||||
|
||||
@pytest.mark.skipif(not have_unpaper(), reason="requires unpaper")
|
||||
def test_unpaper_args_invalid(spoof_tesseract_noop, resources, outpdf):
|
||||
p, out, err = run_ocrmypdf(
|
||||
resources / "skew.pdf",
|
||||
outpdf,
|
||||
"-c",
|
||||
"--unpaper-args",
|
||||
"unpaper is not going to like these arguments",
|
||||
env=spoof_tesseract_noop,
|
||||
)
|
||||
# Can't tell difference between unpaper choking on bad arguments or some
|
||||
# other unpaper failure
|
||||
assert p.returncode == ExitCode.child_process_error
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# © 2019 James R. Barlow: github.com/jbarlow83
|
||||
#
|
||||
# This file is part of OCRmyPDF.
|
||||
#
|
||||
# OCRmyPDF is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# OCRmyPDF is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
import logging
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
import pikepdf
|
||||
from ocrmypdf._weave import _fix_toc, _update_page_resources
|
||||
|
||||
check_ocrmypdf = pytest.helpers.check_ocrmypdf
|
||||
|
||||
|
||||
def test_invalid_toc(resources, outdir, caplog):
|
||||
pdf = pikepdf.open(resources / 'toc.pdf')
|
||||
|
||||
# Corrupt a TOC entry
|
||||
pdf.Root.Outlines.Last.Dest = pikepdf.Array([None, 0.0, 0.1, 0.2])
|
||||
pdf.save(outdir / 'test.pdf')
|
||||
|
||||
pdf = pikepdf.open(outdir / 'test.pdf')
|
||||
remap = {}
|
||||
remap[pdf.pages[0].objgen] = pdf.pages[0].objgen # Dummy remap
|
||||
|
||||
# Confirm we complain about the TOC and don't throw an exception
|
||||
log = logging.getLogger()
|
||||
_fix_toc(pdf, remap, log)
|
||||
assert 'invalid table of contents entries' in caplog.text
|
||||
|
||||
|
||||
def test_no_glyphless_weave(resources, outdir):
|
||||
pdf = pikepdf.open(resources / 'francais.pdf')
|
||||
pdf_aspect = pikepdf.open(resources / 'aspect.pdf')
|
||||
pdf_cmyk = pikepdf.open(resources / 'cmyk.pdf')
|
||||
pdf.pages.extend(pdf_aspect.pages)
|
||||
pdf.pages.extend(pdf_cmyk.pages)
|
||||
pdf.save(outdir / 'test.pdf')
|
||||
|
||||
env = os.environ.copy()
|
||||
env['_OCRMYPDF_MAX_OPEN_PAGE_PDFS'] = '2'
|
||||
check_ocrmypdf(
|
||||
outdir / 'test.pdf',
|
||||
outdir / 'out.pdf',
|
||||
'--deskew',
|
||||
'--tesseract-timeout',
|
||||
'0',
|
||||
env=env,
|
||||
)
|
||||
Reference in New Issue
Block a user