Compare commits

..
14 Commits
14 changed files with 158 additions and 19 deletions
+36
View File
@@ -0,0 +1,36 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
What command line or API call were you trying to run?
```bash
ocrmypdf ...arguments... input.pdf output.pdf
```
Run with verbosity or higher `-v1` to see more detailed logging. This information may be helpful.
**Example file**
Please include an example *input* PDF (or image). The input file is more helpful.
If possible, use an input file with no personal or confidential information. At your option you may GPG-encrypt the file for OCRmyPDF's author only.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**System**
- OS: [e.g. Linux, Windows, macOS]
- OCRmyPDF Version: ``ocrmypdf --version``
- How did you install ocrmypdf? Did you use a system package manager, `pip`, or a Docker image?
+17
View File
@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.
+8
View File
@@ -51,6 +51,14 @@ Forking a child process to call ``ocrmypdf.ocr()`` is suggested. That
way your application will survive and remain interactive even if way your application will survive and remain interactive even if
OCRmyPDF does not. OCRmyPDF does not.
.. warning::
On Windows, the script that calls ``ocrmypdf.ocr()`` must be protected
by an "ifmain" guard (``if __name__ == '__main__'``) or you must use
``ocrmypdf.ocr(...use_threads=True)``. If you do not take at least one
of these steps, Windows fork semantics will prevent OCRmyPDF from working
correct.
Logging Logging
------- -------
+7
View File
@@ -202,6 +202,13 @@ Alternatives
- `Watchman <https://facebook.github.io/watchman/>`__ is a more - `Watchman <https://facebook.github.io/watchman/>`__ is a more
powerful alternative to ``watchmedo``. powerful alternative to ``watchmedo``.
AWS Lambda is not viable
------------------------
AWS Lambda and its equivalents have low limits on execution time and payload
size, relative to OCRmyPDF's needs. As of this writing, the request/response
payload for AWS Lambda was 6 MB, which means many PDFs will not fit.
macOS Automator macOS Automator
=============== ===============
+2 -4
View File
@@ -90,10 +90,8 @@ Docker volume:
.. code-block:: bash .. code-block:: bash
docker run --rm -v $(pwd):/data ocrmypdf /data/input.pdf /data/output.pdf alias docker_ocrmypdf='docker run --rm -i --user "$(id -u):$(id -g)" --workdir /data -v "$PWD:/data" ocrmypdf'
docker_ocrmypdf /data/input.pdf /data/output.pdf
(However, when done this way, ``output.pdf`` may be owned by the root
user.)
.. _docker-lang-packs: .. _docker-lang-packs:
+13
View File
@@ -13,6 +13,19 @@ Note that it is licensed under GPLv3, so scripts that
``import ocrmypdf`` and are released publicly should probably also be ``import ocrmypdf`` and are released publicly should probably also be
licensed under GPLv3. licensed under GPLv3.
v9.7.2
======
- Fixed an issue with ``ocrmypdf.ocr(...language=)`` not accepting a list of
languages as documented.
- Updated setup.py to confirm that pdfminer.six version 20200402 is supported.
v9.7.1
======
- Fixed version check failing when used with qpdf 10.0.0.
- Added some missing type annotations.
- Updated documentation to warn about need for "ifmain" guard and Windows.
v9.7.0 v9.7.0
====== ======
+1 -1
View File
@@ -3,7 +3,7 @@
# installation # installation
cffi == 1.14.0 cffi == 1.14.0
img2pdf == 0.3.3 img2pdf == 0.3.3
pdfminer.six == 20200124 pdfminer.six == 20200402
pikepdf == 1.10.2 pikepdf == 1.10.2
Pillow == 7.0.0 Pillow == 7.0.0
reportlab == 3.5.34 reportlab == 3.5.34
+1 -1
View File
@@ -98,7 +98,7 @@ setup(
'chardet >= 3.0.4, < 4', # unlisted requirement of pdfminer.six 20181108 'chardet >= 3.0.4, < 4', # unlisted requirement of pdfminer.six 20181108
'cffi >= 1.9.1', # must be a setup and install requirement 'cffi >= 1.9.1', # must be a setup and install requirement
'img2pdf >= 0.3.0, < 0.4', # pure Python, so track HEAD closely 'img2pdf >= 0.3.0, < 0.4', # pure Python, so track HEAD closely
'pdfminer.six >= 20181108, <= 20200124', 'pdfminer.six >= 20181108, <= 20200402',
'pikepdf >= 1.8.1, < 2', 'pikepdf >= 1.8.1, < 2',
'Pillow >= 6.2.0', 'Pillow >= 6.2.0',
'reportlab >= 3.3.0', # oldest released version with sane image handling 'reportlab >= 3.3.0', # oldest released version with sane image handling
+15 -5
View File
@@ -21,7 +21,7 @@ import sys
from contextlib import suppress from contextlib import suppress
from enum import IntEnum from enum import IntEnum
from pathlib import Path from pathlib import Path
from typing import Dict, List from typing import Dict, Iterable
from tqdm import tqdm from tqdm import tqdm
@@ -67,7 +67,11 @@ class Verbosity(IntEnum):
debug_all = 2 #: More detailed debugging from ocrmypdf and dependent modules debug_all = 2 #: More detailed debugging from ocrmypdf and dependent modules
def configure_logging(verbosity, progress_bar_friendly=True, manage_root_logger=False): def configure_logging(
verbosity: Verbosity,
progress_bar_friendly: bool = True,
manage_root_logger: bool = False,
):
"""Set up logging. """Set up logging.
Library users may wish to use this function if they want their log output to be Library users may wish to use this function if they want their log output to be
@@ -128,7 +132,7 @@ def configure_logging(verbosity, progress_bar_friendly=True, manage_root_logger=
return log return log
def create_options(*, input_file, output_file, **kwargs): def create_options(*, input_file: os.PathLike, output_file: os.PathLike, **kwargs):
cmdline = [] cmdline = []
deferred = [] deferred = []
@@ -150,6 +154,12 @@ def create_options(*, input_file, output_file, **kwargs):
cmdline.append(f"--{cmd_style_arg}") cmdline.append(f"--{cmd_style_arg}")
continue continue
if isinstance(val, Iterable) and not isinstance(val, str):
for elem in val:
cmdline.append(f"--{cmd_style_arg}")
cmdline.append(elem)
continue
# We have a parameter # We have a parameter
cmdline.append(f"--{cmd_style_arg}") cmdline.append(f"--{cmd_style_arg}")
if isinstance(val, (int, float)): if isinstance(val, (int, float)):
@@ -180,7 +190,7 @@ def ocr( # pylint: disable=unused-argument
input_file: os.PathLike, input_file: os.PathLike,
output_file: os.PathLike, output_file: os.PathLike,
*, *,
language: List[str] = None, language: Iterable[str] = None,
image_dpi: int = None, image_dpi: int = None,
output_type=None, output_type=None,
sidecar: os.PathLike = None, sidecar: os.PathLike = None,
@@ -210,7 +220,7 @@ def ocr( # pylint: disable=unused-argument
jbig2_page_group_size: int = None, jbig2_page_group_size: int = None,
pages: str = None, pages: str = None,
max_image_mpixels: float = None, max_image_mpixels: float = None,
tesseract_config: List[str] = None, tesseract_config: Iterable[str] = None,
tesseract_pagesegmode: int = None, tesseract_pagesegmode: int = None,
tesseract_oem: int = None, tesseract_oem: int = None,
pdf_renderer=None, pdf_renderer=None,
+10 -1
View File
@@ -23,6 +23,7 @@ import re
import shutil import shutil
import sys import sys
from collections.abc import Mapping from collections.abc import Mapping
from distutils.version import LooseVersion
from functools import lru_cache from functools import lru_cache
from subprocess import PIPE, STDOUT, CalledProcessError from subprocess import PIPE, STDOUT, CalledProcessError
from subprocess import run as subprocess_run from subprocess import run as subprocess_run
@@ -270,7 +271,15 @@ def check_external_program(
raise MissingDependencyError() raise MissingDependencyError()
return return
if found_version < need_version: def remove_leading_v(s):
if s.startswith('v'):
return s[1:]
return s
found_version = remove_leading_v(found_version)
need_version = remove_leading_v(need_version)
if LooseVersion(found_version) < LooseVersion(need_version):
_error_old_version(program, package, need_version, found_version, required_for) _error_old_version(program, package, need_version, found_version, required_for)
if not recommended: if not recommended:
raise MissingDependencyError() raise MissingDependencyError()
+4 -4
View File
@@ -28,7 +28,7 @@ from pathlib import Path
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def safe_symlink(input_file, soft_link_name, *args, **kwargs): def safe_symlink(input_file: os.PathLike, soft_link_name: os.PathLike, *args, **kwargs):
""" """
Helper function: relinks soft symbolic link if necessary Helper function: relinks soft symbolic link if necessary
""" """
@@ -76,12 +76,12 @@ def is_iterable_notstr(thing):
return isinstance(thing, Iterable) and not isinstance(thing, str) return isinstance(thing, Iterable) and not isinstance(thing, str)
def monotonic(L): def monotonic(L: Iterable):
"""Does list increase monotonically?""" """Does list increase monotonically?"""
return all(b > a for a, b in zip(L, L[1:])) return all(b > a for a, b in zip(L, L[1:]))
def page_number(input_file): def page_number(input_file: os.PathLike):
"""Get one-based page number implied by filename (000002.pdf -> 2)""" """Get one-based page number implied by filename (000002.pdf -> 2)"""
return int(os.path.basename(os.fspath(input_file))[0:6]) return int(os.path.basename(os.fspath(input_file))[0:6])
@@ -97,7 +97,7 @@ def available_cpu_count():
return 1 return 1
def is_file_writable(test_file): def is_file_writable(test_file: os.PathLike):
"""Intentionally racy test if target is writable. """Intentionally racy test if target is writable.
We intend to write to the output file if and only if we succeed and We intend to write to the output file if and only if we succeed and
+3 -3
View File
@@ -22,7 +22,7 @@ from collections import defaultdict, namedtuple
from decimal import Decimal from decimal import Decimal
from enum import Enum from enum import Enum
from math import hypot, isclose from math import hypot, isclose
from os import fspath from os import PathLike, fspath
from pathlib import Path from pathlib import Path
from warnings import warn from warnings import warn
@@ -40,7 +40,7 @@ logger = logging.getLogger()
Colorspace = Enum('Colorspace', 'gray rgb cmyk lab icc index sep devn pattern jpeg2000') Colorspace = Enum('Colorspace', 'gray rgb cmyk lab icc index sep devn pattern jpeg2000')
Encoding = Enum( Encoding = Enum(
'Encoding', 'ccitt jpeg jpeg2000 jbig2 asciihex ascii85 lzw flate ' + 'runlength' 'Encoding', 'ccitt jpeg jpeg2000 jbig2 asciihex ascii85 lzw flate runlength'
) )
FRIENDLY_COLORSPACE = { FRIENDLY_COLORSPACE = {
@@ -558,7 +558,7 @@ def simplify_textboxes(miner, textbox_getter):
yield TextboxInfo(box.bbox, visible, corrupt) yield TextboxInfo(box.bbox, visible, corrupt)
def _pdf_get_pageinfo(pdf, pageno: int, infile, xmltext): def _pdf_get_pageinfo(pdf, pageno: int, infile: PathLike, xmltext: str):
pageinfo = {} pageinfo = {}
pageinfo['pageno'] = pageno pageinfo['pageno'] = pageno
pageinfo['images'] = [] pageinfo['images'] = []
+7
View File
@@ -59,3 +59,10 @@ def test_tqdm_console():
log.info("done") log.info("done")
assert not before_pbar("done") assert not before_pbar("done")
def test_language_list():
with pytest.raises(
(ocrmypdf.exceptions.InputFileError, ocrmypdf.exceptions.MissingDependencyError)
):
ocrmypdf.ocr('doesnotexist.pdf', '_.pdf', language=['eng', 'deu'])
+34
View File
@@ -176,3 +176,37 @@ def test_language_warning(caplog):
vd.check_options_languages(opts) vd.check_options_languages(opts)
assert opts.language == ['eng'] assert opts.language == ['eng']
assert 'assuming --language' in caplog.text assert 'assuming --language' in caplog.text
def test_version_comparison():
vd.check_external_program(
program="dummy_basic",
package="dummy",
version_checker=lambda: '9.0',
need_version='8.0.2',
)
vd.check_external_program(
program="dummy_doubledigit",
package="dummy",
version_checker=lambda: '10.0',
need_version='8.0.2',
)
vd.check_external_program(
program="tesseract",
package="tesseract",
version_checker=lambda: '4.0.0-beta.1',
need_version='4.0.0',
)
vd.check_external_program(
program="tesseract",
package="tesseract",
version_checker=lambda: 'v5.0.0-alpha.20200201',
need_version='4.0.0',
)
with pytest.raises(MissingDependencyError):
vd.check_external_program(
program="dummy_fails",
package="dummy",
version_checker=lambda: '1.0',
need_version='2.0',
)