Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
298bdb8690 | ||
|
|
1a58abcc6a | ||
|
|
dbfceba020 | ||
|
|
0faa618c3c | ||
|
|
7035002c03 | ||
|
|
f8fadaef41 | ||
|
|
ee21bf9ef6 | ||
|
|
190ca81951 | ||
|
|
d48254d477 | ||
|
|
1ec2ccca14 |
@@ -68,6 +68,14 @@ OCRmyPDF, use processes.
|
|||||||
not take at least one of these steps, process semantics will prevent
|
not take at least one of these steps, process semantics will prevent
|
||||||
OCRmyPDF from working correctly.
|
OCRmyPDF from working correctly.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
On macOS with Python 3.7, you must call
|
||||||
|
:func:`multiprocessing.set_start_method("spawn")`. Without this, multiprocessing
|
||||||
|
will be unstable. From the command line, OCRmyPDF does this automatically,
|
||||||
|
but as an API user you must do this. See Python bpo-33725 for details.
|
||||||
|
Python 3.8+ also resolve this automatically.
|
||||||
|
|
||||||
Logging
|
Logging
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,23 @@ wish to use some of its features for working with PDFs.
|
|||||||
for Python 3.6 but might release fixes for critical issues if necessary before that
|
for Python 3.6 but might release fixes for critical issues if necessary before that
|
||||||
date.
|
date.
|
||||||
|
|
||||||
|
v13.2.0
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Removed all runtime uses of distutils since it is deprecated in standard library. We
|
||||||
|
previous used ``distutils.version`` to examine version numbers of dependencies
|
||||||
|
at run time, and now use ``packaging.version`` for this. This is a new
|
||||||
|
dependency.
|
||||||
|
- Fixed an error message advising the user that Ghostscript was not installed being
|
||||||
|
suppressed when this condition actually happens.
|
||||||
|
- Fixed an issue with incorrect page number and totals being displayed in the progress
|
||||||
|
bar. This was purely a display/presentation issue. :issue:`876`.
|
||||||
|
|
||||||
|
v13.1.1
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Fixed issue with attempting to deskew a blank page on Tesseract 5. :issue:`868`.
|
||||||
|
|
||||||
v13.1.0
|
v13.1.0
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ install_requires =
|
|||||||
Pillow>=8.2.0
|
Pillow>=8.2.0
|
||||||
coloredlogs>=14.0 # strictly optional
|
coloredlogs>=14.0 # strictly optional
|
||||||
img2pdf>=0.3.0,<0.5 # pure Python
|
img2pdf>=0.3.0,<0.5 # pure Python
|
||||||
|
packaging>=20
|
||||||
pdfminer.six!=20200720,>=20191110,<=20211012
|
pdfminer.six!=20200720,>=20191110,<=20211012
|
||||||
pikepdf>=4.0.0
|
pikepdf>=4.0.0
|
||||||
pluggy>=0.13.0,<2
|
pluggy>=0.13.0,<2
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from distutils.version import StrictVersion
|
|
||||||
from math import pi
|
from math import pi
|
||||||
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 Dict, Iterator, List, Optional
|
from typing import Dict, Iterator, List, Optional
|
||||||
|
|
||||||
|
from packaging.version import Version
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from ocrmypdf.exceptions import (
|
from ocrmypdf.exceptions import (
|
||||||
@@ -60,25 +60,54 @@ class TesseractLoggerAdapter(logging.LoggerAdapter):
|
|||||||
return '[tesseract] %s' % (msg), kwargs
|
return '[tesseract] %s' % (msg), kwargs
|
||||||
|
|
||||||
|
|
||||||
class TesseractVersion(StrictVersion):
|
TESSERACT_VERSION_PATTERN = r"""
|
||||||
|
v?
|
||||||
version_re = re.compile(
|
(?:
|
||||||
r'''
|
(?:(?P<epoch>[0-9]+)!)? # epoch
|
||||||
^(\d+) \. (\d+) (\. (\d+))? # groups: 1/major, 2/minor, 3/[skip], 4/patch
|
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
|
||||||
[-]? # optional hyphen separator
|
(?P<pre> # pre-release
|
||||||
(?: ((?:alpha|beta|rc|dev)\d*)? [.\-\ ]? (\d+)? )? # 5/prerelease, 6/prerelease_num
|
[-_\.]?
|
||||||
(?:(?:-\d+)?-g[0-9a-f]+)? # untagged git version
|
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
|
||||||
$
|
[-_\.]?
|
||||||
''',
|
(?P<pre_n>[0-9]+)?
|
||||||
re.VERBOSE | re.ASCII,
|
)?
|
||||||
|
(?P<post> # post release
|
||||||
|
(?:-(?P<post_n1>[0-9]+))
|
||||||
|
|
|
||||||
|
(?:
|
||||||
|
[-_\.]?
|
||||||
|
(?P<post_l>post|rev|r)
|
||||||
|
[-_\.]?
|
||||||
|
(?P<post_n2>[0-9]+)?
|
||||||
|
)
|
||||||
|
)?
|
||||||
|
(?P<dev> # dev release
|
||||||
|
[-_\.]?
|
||||||
|
(?P<dev_l>dev)
|
||||||
|
[-_\.]?
|
||||||
|
(?P<dev_n>[0-9]+)?
|
||||||
|
)?
|
||||||
|
(?P<date>
|
||||||
|
[-_\.]
|
||||||
|
(?:20[0-9][0-9] [0-1][0-9] [0-3][0-9]) # yyyy mm dd
|
||||||
|
)?
|
||||||
|
(?P<gitcount>
|
||||||
|
[-_\.]?
|
||||||
|
[0-9]+
|
||||||
|
)?
|
||||||
|
(?P<gitcommit>
|
||||||
|
[-_\.]?
|
||||||
|
g[0-9a-f]{2,10}
|
||||||
|
)?
|
||||||
)
|
)
|
||||||
|
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
|
||||||
|
"""
|
||||||
|
|
||||||
def parse(self, vstring):
|
|
||||||
try:
|
class TesseractVersion(Version):
|
||||||
super().parse(vstring)
|
_regex = re.compile(
|
||||||
except TypeError as e:
|
r"^\s*" + TESSERACT_VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE
|
||||||
if 'int() argument must be a string' in str(e):
|
)
|
||||||
super().parse(vstring + '-0')
|
|
||||||
|
|
||||||
|
|
||||||
def version() -> str:
|
def version() -> str:
|
||||||
@@ -200,7 +229,9 @@ def get_deskew(
|
|||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
tesseract_log_output(e.stdout)
|
tesseract_log_output(e.stdout)
|
||||||
tesseract_log_output(e.stderr)
|
tesseract_log_output(e.stderr)
|
||||||
if b'Empty page!!' in e.output: # Not enough info for a skew angle
|
if b'Empty page!!' in e.output or (
|
||||||
|
e.output == b'' and e.returncode == 1
|
||||||
|
): # Not enough info for a skew angle - Tess 4 and 5 return different errors
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
raise SubprocessOutputError() from e
|
raise SubprocessOutputError() from e
|
||||||
|
|||||||
@@ -134,7 +134,6 @@ class StandardExecutor(Executor):
|
|||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
result = future.result()
|
result = future.result()
|
||||||
task_finished(result, pbar)
|
task_finished(result, pbar)
|
||||||
pbar.update()
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
# Terminate pool so we exit instantly
|
# Terminate pool so we exit instantly
|
||||||
executor.shutdown(wait=False, cancel_futures=True)
|
executor.shutdown(wait=False, cancel_futures=True)
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def check_options(options):
|
def check_options(options):
|
||||||
gs_version = ghostscript.version()
|
|
||||||
check_external_program(
|
check_external_program(
|
||||||
program='gs',
|
program='gs',
|
||||||
package='ghostscript',
|
package='ghostscript',
|
||||||
version_checker=gs_version,
|
version_checker=ghostscript.version,
|
||||||
need_version='9.15', # limited by Travis CI / Ubuntu 14.04 backports
|
need_version='9.15', # limited by Travis CI / Ubuntu 14.04 backports
|
||||||
)
|
)
|
||||||
|
gs_version = ghostscript.version()
|
||||||
if gs_version in ('9.24', '9.51'):
|
if gs_version in ('9.24', '9.51'):
|
||||||
raise MissingDependencyError(
|
raise MissingDependencyError(
|
||||||
f"Ghostscript {gs_version} contains serious regressions and is not "
|
f"Ghostscript {gs_version} contains serious regressions and is not "
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from distutils.version import LooseVersion, Version
|
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from subprocess import PIPE, STDOUT, CalledProcessError, CompletedProcess, Popen
|
from subprocess import PIPE, STDOUT, CalledProcessError, CompletedProcess, Popen
|
||||||
from subprocess import run as subprocess_run
|
from subprocess import run as subprocess_run
|
||||||
from typing import Callable, Optional, Type, Union
|
from typing import Callable, Optional, Type, Union
|
||||||
|
|
||||||
|
from packaging.version import Version
|
||||||
|
|
||||||
from ocrmypdf.exceptions import MissingDependencyError
|
from ocrmypdf.exceptions import MissingDependencyError
|
||||||
|
|
||||||
# pylint: disable=logging-format-interpolation
|
# pylint: disable=logging-format-interpolation
|
||||||
@@ -266,11 +267,11 @@ def check_external_program(
|
|||||||
*,
|
*,
|
||||||
program: str,
|
program: str,
|
||||||
package: str,
|
package: str,
|
||||||
version_checker: Union[str, Callable],
|
version_checker: Callable,
|
||||||
need_version: str,
|
need_version: str,
|
||||||
required_for: Optional[str] = None,
|
required_for: Optional[str] = None,
|
||||||
recommended=False,
|
recommended=False,
|
||||||
version_parser: Type[Version] = LooseVersion,
|
version_parser: Type[Version] = Version,
|
||||||
):
|
):
|
||||||
"""Check for required version of external program and raise exception if not.
|
"""Check for required version of external program and raise exception if not.
|
||||||
|
|
||||||
@@ -291,7 +292,7 @@ def check_external_program(
|
|||||||
try:
|
try:
|
||||||
if callable(version_checker):
|
if callable(version_checker):
|
||||||
found_version = version_checker()
|
found_version = version_checker()
|
||||||
else:
|
else: # deprecated
|
||||||
found_version = version_checker
|
found_version = version_checker
|
||||||
except (CalledProcessError, FileNotFoundError, MissingDependencyError):
|
except (CalledProcessError, FileNotFoundError, MissingDependencyError):
|
||||||
_error_missing_program(program, package, required_for, recommended)
|
_error_missing_program(program, package, required_for, recommended)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from distutils.version import LooseVersion
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Iterable, Iterator, Set, Tuple, TypeVar
|
from typing import Any, Callable, Iterable, Iterator, Set, Tuple, TypeVar
|
||||||
@@ -23,6 +22,17 @@ log = logging.getLogger(__name__)
|
|||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
def ghostscript_version_key(s: str) -> Tuple[int, int, int]:
|
||||||
|
"""Compare Ghostscript version numbers."""
|
||||||
|
try:
|
||||||
|
release = [int(elem) for elem in s.split('.', maxsplit=3)]
|
||||||
|
while len(release) < 3:
|
||||||
|
release.append(0)
|
||||||
|
return (release[0], release[1], release[2])
|
||||||
|
except ValueError:
|
||||||
|
return (0, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
def registry_enum(
|
def registry_enum(
|
||||||
key: winreg.HKEYType, enum_fn: Callable[[winreg.HKEYType, int], T]
|
key: winreg.HKEYType, enum_fn: Callable[[winreg.HKEYType, int], T]
|
||||||
) -> Iterator[T]:
|
) -> Iterator[T]:
|
||||||
@@ -51,7 +61,9 @@ def registry_path_ghostscript(env=None) -> Iterator[Path]:
|
|||||||
with winreg.OpenKey(
|
with winreg.OpenKey(
|
||||||
winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Artifex\GPL Ghostscript"
|
winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Artifex\GPL Ghostscript"
|
||||||
) as k:
|
) as k:
|
||||||
latest_gs = max(registry_subkeys(k), key=LooseVersion, default='0')
|
latest_gs = max(
|
||||||
|
registry_subkeys(k), key=ghostscript_version_key, default=(0, 0, 0)
|
||||||
|
)
|
||||||
with winreg.OpenKey(
|
with winreg.OpenKey(
|
||||||
winreg.HKEY_LOCAL_MACHINE, fr"SOFTWARE\Artifex\GPL Ghostscript\{latest_gs}"
|
winreg.HKEY_LOCAL_MACHINE, fr"SOFTWARE\Artifex\GPL Ghostscript\{latest_gs}"
|
||||||
) as k:
|
) as k:
|
||||||
|
|||||||
Vendored
+2
@@ -80,3 +80,5 @@
|
|||||||
{"tesseract_version": "4.1.1", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "4.1.1", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "4.1.1", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "4.1.1", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "4.1.1", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__--oem__1__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "--oem", "1", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "4.1.1", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__--oem__1__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "--oem", "1", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
|
{"tesseract_version": "5.0.0", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__thresholding_method=1__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "-c", "thresholding_method=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
|
{"tesseract_version": "5.0.0", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__thresholding_method=2__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "-c", "thresholding_method=2", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -40,6 +40,11 @@ def test_deskew(resources, outdir):
|
|||||||
assert -0.5 < skew_angle < 0.5, "Deskewing failed"
|
assert -0.5 < skew_angle < 0.5, "Deskewing failed"
|
||||||
|
|
||||||
|
|
||||||
|
def test_deskew_blank_page(resources, outpdf):
|
||||||
|
# Tesseract doesn't like blank pages - make sure we can get through
|
||||||
|
check_ocrmypdf(resources / 'blank.pdf', outpdf, '--deskew')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason="remove background disabled")
|
@pytest.mark.xfail(reason="remove background disabled")
|
||||||
def test_remove_background(resources, outdir):
|
def test_remove_background(resources, outdir):
|
||||||
# Ensure the input image does not contain pure white/black
|
# Ensure the input image does not contain pure white/black
|
||||||
|
|||||||
Reference in New Issue
Block a user