Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fed94bb79 | ||
|
|
8c877482bd | ||
|
|
b17d589e84 | ||
|
|
368252a243 | ||
|
|
ccefda1bee | ||
|
|
3d0e8c9629 | ||
|
|
313bbbb94c | ||
|
|
0360f078de | ||
|
|
c8901666c4 | ||
|
|
7430006596 | ||
|
|
f3e06b2dbd | ||
|
|
e97df307ff | ||
|
|
1443354aa2 | ||
|
|
250e68c1cd | ||
|
|
6a380ee99c | ||
|
|
3c90bd96a9 | ||
|
|
06a7ceb25a | ||
|
|
733a8e7d58 | ||
|
|
570bbe9a05 | ||
|
|
5cc3adb39a | ||
|
|
3957a0606c | ||
|
|
11a561dbce | ||
|
|
dad2198394 | ||
|
|
e40fdc502d | ||
|
|
d446fe5922 | ||
|
|
4ca90c106d |
@@ -0,0 +1 @@
|
|||||||
|
ref-names: $Format:%D$
|
||||||
+3
-1
@@ -5,4 +5,6 @@
|
|||||||
# (binary is a macro for -text -diff)
|
# (binary is a macro for -text -diff)
|
||||||
*.jar binary
|
*.jar binary
|
||||||
*.pdf binary
|
*.pdf binary
|
||||||
*.PDF binary
|
*.PDF binary
|
||||||
|
|
||||||
|
.git_archival.txt export-subst
|
||||||
|
|||||||
+9
-5
@@ -23,12 +23,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
|
|
||||||
RUN apt-get install -y --no-install-recommends \
|
RUN apt-get install -y --no-install-recommends \
|
||||||
unpaper \
|
unpaper \
|
||||||
libopenjpeg-dev \
|
|
||||||
ghostscript \
|
|
||||||
qpdf \
|
qpdf \
|
||||||
poppler-utils
|
poppler-utils \
|
||||||
|
|
||||||
RUN apt-get install -y --no-install-recommends \
|
|
||||||
tesseract-ocr \
|
tesseract-ocr \
|
||||||
tesseract-ocr-deu tesseract-ocr-spa tesseract-ocr-eng tesseract-ocr-fra
|
tesseract-ocr-deu tesseract-ocr-spa tesseract-ocr-eng tesseract-ocr-fra
|
||||||
|
|
||||||
@@ -37,6 +33,14 @@ RUN apt-get install -qy --no-install-recommends \
|
|||||||
libpython3-dev \
|
libpython3-dev \
|
||||||
gcc
|
gcc
|
||||||
|
|
||||||
|
# Install Ghostscript from Debian sid to work around JPEG 2000 issue in
|
||||||
|
# Debian stretch libgs9 or gs 9.16~dfsg-2.1
|
||||||
|
|
||||||
|
COPY ./share/etc-apt-sources.list /etc/apt/sources.list
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y ghostscript/sid
|
||||||
|
|
||||||
|
|
||||||
# Enforce UTF-8
|
# Enforce UTF-8
|
||||||
# Borrowed from https://index.docker.io/u/crosbymichael/python/
|
# Borrowed from https://index.docker.io/u/crosbymichael/python/
|
||||||
RUN dpkg-reconfigure locales && \
|
RUN dpkg-reconfigure locales && \
|
||||||
|
|||||||
+4
-4
@@ -116,8 +116,8 @@ Install or upgrade the required Homebrew packages, if any are missing::
|
|||||||
brew install qpdf
|
brew install qpdf
|
||||||
brew install ghostscript
|
brew install ghostscript
|
||||||
brew install python3
|
brew install python3
|
||||||
brew install libxml2
|
brew install libxml2 libffi leptonica
|
||||||
brew install leptonica
|
brew install unpaper # optional
|
||||||
brew install tesseract
|
brew install tesseract
|
||||||
|
|
||||||
Update the homebrew pip and install Pillow::
|
Update the homebrew pip and install Pillow::
|
||||||
@@ -252,11 +252,11 @@ In case you detect an issue, please:
|
|||||||
Press & Media
|
Press & Media
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
- `c't 1-2014, page 59 <http://www.heise.de/ct/inhalt/2014/1/58/>`__:
|
- `c't 1-2014, page 59 <http://heise.de/-2279695>`__:
|
||||||
Detailed presentation of OCRmyPDF v1.0 in the leading German IT
|
Detailed presentation of OCRmyPDF v1.0 in the leading German IT
|
||||||
magazine c't
|
magazine c't
|
||||||
- `heise Open Source, 09/2014: Texterkennung mit
|
- `heise Open Source, 09/2014: Texterkennung mit
|
||||||
OCRmyPDF <http://www.heise.de/-2356670>`__
|
OCRmyPDF <http://heise.de/-2356670>`__
|
||||||
|
|
||||||
Disclaimer
|
Disclaimer
|
||||||
----------
|
----------
|
||||||
|
|||||||
@@ -6,6 +6,54 @@ Please always read this file before installing the package
|
|||||||
Download software here: https://github.com/jbarlow83/OCRmyPDF/tags
|
Download software here: https://github.com/jbarlow83/OCRmyPDF/tags
|
||||||
|
|
||||||
|
|
||||||
|
v4.0.7:
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Minor correction to Ghostscript output settings
|
||||||
|
|
||||||
|
|
||||||
|
v4.0.6:
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Update install instructions
|
||||||
|
- Provide a sRGB profile instead of using Ghostscript's
|
||||||
|
|
||||||
|
|
||||||
|
v4.0.5:
|
||||||
|
=======
|
||||||
|
|
||||||
|
Fixes
|
||||||
|
-----
|
||||||
|
|
||||||
|
- Remove some verbose debug messages from v4.0.4
|
||||||
|
- Fixed temporary that wasn't being deleted
|
||||||
|
- DPI is now calculated correctly for cropped images, along with other image transformations
|
||||||
|
- Inline images are now checked during DPI calculation instead of rejecting the image
|
||||||
|
|
||||||
|
v4.0.4:
|
||||||
|
=======
|
||||||
|
|
||||||
|
Released with verbose debug message turned on. Do not use. Skip to v4.0.5.
|
||||||
|
|
||||||
|
|
||||||
|
v4.0.3:
|
||||||
|
=======
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
- Page orientations detected are now reported in a summary comment
|
||||||
|
|
||||||
|
|
||||||
|
Fixes
|
||||||
|
-----
|
||||||
|
|
||||||
|
- Show stack trace if unexpect errors occur
|
||||||
|
- Treat "too few characters" error message from Tesseract as a reason to skip that page rather than
|
||||||
|
abort the file
|
||||||
|
- Docker: fix blank JPEG2000 issue by insisting on Ghostscript versions that have this fixed
|
||||||
|
|
||||||
|
|
||||||
v4.0.2:
|
v4.0.2:
|
||||||
=======
|
=======
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -5,6 +5,7 @@ from tempfile import NamedTemporaryFile
|
|||||||
from subprocess import Popen, PIPE, check_call
|
from subprocess import Popen, PIPE, check_call
|
||||||
from shutil import copy
|
from shutil import copy
|
||||||
from . import get_program
|
from . import get_program
|
||||||
|
from .pdfa import SRGB_ICC_PROFILE
|
||||||
|
|
||||||
|
|
||||||
def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log,
|
def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log,
|
||||||
@@ -52,7 +53,6 @@ def generate_pdfa(pdf_pages, output_file, threads=1):
|
|||||||
"-dJPEGQ=95",
|
"-dJPEGQ=95",
|
||||||
"-dPDFA=2",
|
"-dPDFA=2",
|
||||||
"-sPDFACompatibilityPolicy=2",
|
"-sPDFACompatibilityPolicy=2",
|
||||||
"-sOutputICCProfile=srgb.icc",
|
|
||||||
"-sOutputFile=" + gs_pdf.name,
|
"-sOutputFile=" + gs_pdf.name,
|
||||||
]
|
]
|
||||||
args_gs.extend(pdf_pages)
|
args_gs.extend(pdf_pages)
|
||||||
|
|||||||
+11
-6
@@ -110,8 +110,8 @@ class Pix:
|
|||||||
return "<leptonica.Pix image NULL>"
|
return "<leptonica.Pix image NULL>"
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
data = ffi.new('l_uint32 *[]', 1)
|
data = ffi.new('l_uint32 **')
|
||||||
size = ffi.new('size_t *', 0)
|
size = ffi.new('size_t *')
|
||||||
|
|
||||||
err = lept.pixSerializeToMemory(self.cpix, data, size)
|
err = lept.pixSerializeToMemory(self.cpix, data, size)
|
||||||
if err != 0:
|
if err != 0:
|
||||||
@@ -195,16 +195,21 @@ class Pix:
|
|||||||
else:
|
else:
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@lru_cache(maxsize=1)
|
||||||
|
def make_pixel_sum_tab8():
|
||||||
|
return lept.makePixelSumTab8()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def correlation_binary(pix1, pix2):
|
def correlation_binary(pix1, pix2):
|
||||||
if get_leptonica_version() < 'leptonica-1.72':
|
if get_leptonica_version() < 'leptonica-1.72':
|
||||||
# Older versions of Leptonica (pre-1.72) have a buggy
|
# Older versions of Leptonica (pre-1.72) have a buggy
|
||||||
# implementation of pixCorrelationBinary that overflows on larger
|
# implementation of pixCorrelationBinary that overflows on larger
|
||||||
# images.
|
# images.
|
||||||
pix1_count = ffi.new('l_int32 *', 0)
|
pix1_count = ffi.new('l_int32 *')
|
||||||
pix2_count = ffi.new('l_int32 *', 0)
|
pix2_count = ffi.new('l_int32 *')
|
||||||
pixn_count = ffi.new('l_int32 *', 0)
|
pixn_count = ffi.new('l_int32 *')
|
||||||
tab8 = lept.makePixelSumTab8() # Small memory leak on each call
|
tab8 = Pix.make_pixel_sum_tab8()
|
||||||
|
|
||||||
lept.pixCountPixels(pix1.cpix, pix1_count, tab8)
|
lept.pixCountPixels(pix1.cpix, pix1_count, tab8)
|
||||||
lept.pixCountPixels(pix2.cpix, pix2_count, tab8)
|
lept.pixCountPixels(pix2.cpix, pix2_count, tab8)
|
||||||
|
|||||||
+23
-1
@@ -378,7 +378,7 @@ def cleanup_working_files(*args):
|
|||||||
@transform(
|
@transform(
|
||||||
input=options.input_file,
|
input=options.input_file,
|
||||||
filter=formatter('(?i)\.pdf'),
|
filter=formatter('(?i)\.pdf'),
|
||||||
output=work_folder + '{basename[0]}.repaired.pdf',
|
output=os.path.join(work_folder, '{basename[0]}.repaired.pdf'),
|
||||||
extras=[_log, _pdfinfo, _pdfinfo_lock])
|
extras=[_log, _pdfinfo, _pdfinfo_lock])
|
||||||
def repair_pdf(
|
def repair_pdf(
|
||||||
input_file,
|
input_file,
|
||||||
@@ -550,6 +550,12 @@ def orient_page(
|
|||||||
with open(output_file, 'wb') as out:
|
with open(output_file, 'wb') as out:
|
||||||
writer.write(out)
|
writer.write(out)
|
||||||
|
|
||||||
|
with pdfinfo_lock:
|
||||||
|
pageno = int(os.path.basename(page_pdf)[0:6]) - 1
|
||||||
|
pageinfo = pdfinfo[pageno].copy()
|
||||||
|
pageinfo['rotated'] = orient_conf.angle
|
||||||
|
pdfinfo[pageno] = pageinfo
|
||||||
|
|
||||||
|
|
||||||
@transform(
|
@transform(
|
||||||
input=orient_page,
|
input=orient_page,
|
||||||
@@ -1045,6 +1051,8 @@ def run_pipeline():
|
|||||||
msg = "Error occurred while running this command:"
|
msg = "Error occurred while running this command:"
|
||||||
_log.error(msg + '\n' + exc_value)
|
_log.error(msg + '\n' + exc_value)
|
||||||
return ExitCode.child_process_error
|
return ExitCode.child_process_error
|
||||||
|
elif not options.verbose:
|
||||||
|
_log.error(e)
|
||||||
|
|
||||||
return ExitCode.other_error
|
return ExitCode.other_error
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1055,6 +1063,20 @@ def run_pipeline():
|
|||||||
_log.warning('Output file: The generated PDF/A file is INVALID')
|
_log.warning('Output file: The generated PDF/A file is INVALID')
|
||||||
return ExitCode.invalid_output_pdfa
|
return ExitCode.invalid_output_pdfa
|
||||||
|
|
||||||
|
with _pdfinfo_lock:
|
||||||
|
_log.debug(_pdfinfo)
|
||||||
|
direction = {0: 'n', 90: 'e',
|
||||||
|
180: 's', 270: 'w'}
|
||||||
|
orientations = []
|
||||||
|
for n, page in enumerate(_pdfinfo):
|
||||||
|
angle = _pdfinfo[n].get('rotated', 0)
|
||||||
|
if angle != 0:
|
||||||
|
orientations.append('{0}{1}'.format(
|
||||||
|
n + 1,
|
||||||
|
direction.get(angle, '')))
|
||||||
|
if orientations:
|
||||||
|
_log.info('Page orientations detected: ' + ' '.join(orientations))
|
||||||
|
|
||||||
return ExitCode.ok
|
return ExitCode.ok
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+195
-33
@@ -6,7 +6,9 @@ from decimal import Decimal, getcontext
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import PyPDF2 as pypdf
|
import PyPDF2 as pypdf
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
matrix_mult = pypdf.pdf.utils.matrixMultiply
|
||||||
|
|
||||||
FRIENDLY_COLORSPACE = {
|
FRIENDLY_COLORSPACE = {
|
||||||
'/DeviceGray': 'gray',
|
'/DeviceGray': 'gray',
|
||||||
@@ -19,7 +21,11 @@ FRIENDLY_COLORSPACE = {
|
|||||||
'/Indexed': 'index',
|
'/Indexed': 'index',
|
||||||
'/Separation': 'sep',
|
'/Separation': 'sep',
|
||||||
'/DeviceN': 'devn',
|
'/DeviceN': 'devn',
|
||||||
'/Pattern': '-'
|
'/Pattern': '-',
|
||||||
|
'/G': 'gray', # Abbreviations permitted in inline images
|
||||||
|
'/RGB': 'rgb',
|
||||||
|
'/CMYK': 'cmyk',
|
||||||
|
'/I': 'index',
|
||||||
}
|
}
|
||||||
|
|
||||||
FRIENDLY_ENCODING = {
|
FRIENDLY_ENCODING = {
|
||||||
@@ -27,6 +33,8 @@ FRIENDLY_ENCODING = {
|
|||||||
'/DCTDecode': 'jpeg',
|
'/DCTDecode': 'jpeg',
|
||||||
'/JPXDecode': 'jpx',
|
'/JPXDecode': 'jpx',
|
||||||
'/JBIG2Decode': 'jbig2',
|
'/JBIG2Decode': 'jbig2',
|
||||||
|
'/CCF': 'ccitt', # Abbreviations permitted in inline images
|
||||||
|
'/DCT': 'jpeg'
|
||||||
}
|
}
|
||||||
|
|
||||||
FRIENDLY_COMP = {
|
FRIENDLY_COMP = {
|
||||||
@@ -38,38 +46,159 @@ FRIENDLY_COMP = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _page_has_inline_images(page):
|
def _matrix_from_shorthand(shorthand):
|
||||||
# PDF always uses \r\n for separator regardless of platform
|
"""Convert from PDF matrix shorthand to full matrix
|
||||||
# Really basic heuristic that might trigger the odd false positive
|
|
||||||
# This is only finds the first image and is not quite spec compliant
|
|
||||||
try:
|
|
||||||
contents = page.getContents()
|
|
||||||
data = contents.getData()
|
|
||||||
except AttributeError:
|
|
||||||
# If we can't access the contents or data (empty page?) then there
|
|
||||||
# are no inline images
|
|
||||||
return False
|
|
||||||
|
|
||||||
begin_image, image_data, end_image = False, False, False
|
PDF 1.7 spec defines a shorthand for describing the entries of a matrix
|
||||||
for data in re.split(b'\s+', data):
|
since the last column is always (0, 0, 1).
|
||||||
if data == b'BI':
|
"""
|
||||||
begin_image = True
|
|
||||||
elif data == b'ID':
|
a, b, c, d, e, f = map(float, shorthand)
|
||||||
image_data = True
|
return ((a, b, 0),
|
||||||
elif data == b'EI':
|
(c, d, 0),
|
||||||
end_image = True
|
(e, f, 1))
|
||||||
if all((begin_image, image_data, end_image)):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _find_page_images(page, pageinfo):
|
def _shorthand_from_matrix(matrix):
|
||||||
|
"""Convert from transformation matrix to PDF shorthand."""
|
||||||
|
a, b = matrix[0][0], matrix[0][1]
|
||||||
|
c, d = matrix[1][0], matrix[1][1]
|
||||||
|
e, f = matrix[2][0], matrix[2][1]
|
||||||
|
return tuple(map(float, (a, b, c, d, e, f)))
|
||||||
|
|
||||||
|
|
||||||
|
def euclidean_distance(rowvec1, rowvec2):
|
||||||
|
return ((rowvec1[0] - rowvec2[0]) ** 2
|
||||||
|
+ (rowvec1[1] - rowvec2[1]) ** 2) ** 0.5
|
||||||
|
|
||||||
|
|
||||||
|
ContentsInfo = namedtuple('ContentsInfo',
|
||||||
|
['raster_settings', 'inline_images'])
|
||||||
|
|
||||||
|
def _interpret_contents(contentstream):
|
||||||
|
operations = contentstream.operations
|
||||||
|
stack = []
|
||||||
|
ctm = _matrix_from_shorthand((1, 0, 0, 1, 0, 0))
|
||||||
|
image_raster_settings = []
|
||||||
|
inline_images = []
|
||||||
|
|
||||||
|
for op in operations:
|
||||||
|
operands, command = op
|
||||||
|
if command == b'q':
|
||||||
|
stack.append(ctm)
|
||||||
|
elif command == b'Q':
|
||||||
|
ctm = stack.pop()
|
||||||
|
elif command == b'cm':
|
||||||
|
ctm = matrix_mult(
|
||||||
|
ctm, _matrix_from_shorthand(operands))
|
||||||
|
elif command == b'Do':
|
||||||
|
image_name = operands[0]
|
||||||
|
image_raster_settings.append(
|
||||||
|
(image_name, _shorthand_from_matrix(ctm)))
|
||||||
|
elif command == b'INLINE IMAGE':
|
||||||
|
settings = operands['settings']
|
||||||
|
inline_images.append(
|
||||||
|
(settings, _shorthand_from_matrix(ctm)))
|
||||||
|
|
||||||
|
return ContentsInfo(
|
||||||
|
raster_settings=image_raster_settings,
|
||||||
|
inline_images=inline_images)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_dpi(ctm_shorthand, image_size):
|
||||||
|
"""Given the transformation matrix and image size, find the image DPI.
|
||||||
|
|
||||||
|
PDFs do not include image resolution information within image data.
|
||||||
|
Instead, the PDF page content stream describes the location where the
|
||||||
|
image will be rasterized, and the effective resolution is the ratio of the
|
||||||
|
pixel size to raster target size.
|
||||||
|
|
||||||
|
Normally a scanned PDF has the paper size set appropriately but this is
|
||||||
|
not guaranteed. The most common case is a cropped image will change the
|
||||||
|
page size (/CropBox) without altering the page content stream. That means
|
||||||
|
it is not sufficient to assume that the image fills the page, even though
|
||||||
|
that is the most common case.
|
||||||
|
|
||||||
|
This code solves the general case where the image may be scaled (always),
|
||||||
|
cropped, translated (often), and rotated in place (occasionally) to an
|
||||||
|
arbitrary angle (rare). It will work as long as the image is a
|
||||||
|
parallelogram from the perspective of a rectilinear coordinate system.
|
||||||
|
It does not work for arbitrarily quadrilaterals that might be produced
|
||||||
|
by shearing, but by that point DPI becomes a linear gradient rather than
|
||||||
|
constant over the image.
|
||||||
|
|
||||||
|
The transformation matrix describes the coordinate system at the time of
|
||||||
|
rendering. We transform the image corner locations into the coordinate
|
||||||
|
system and measure the width and height within the system, expressed in
|
||||||
|
PDF units. From there we can compare to the actual image dimensions.
|
||||||
|
|
||||||
|
pdfimages -list does calculate the DPI in some way that is not completely
|
||||||
|
naive, but it does not the DPI of rotated images right, so cannot be
|
||||||
|
used anymore to validate this. Photoshop works, or using Acrobat to
|
||||||
|
rotate the image back to normal.
|
||||||
|
|
||||||
|
It does not matter if the image is partially cropped, or even out of the
|
||||||
|
/MediaBox.
|
||||||
|
|
||||||
|
"""
|
||||||
|
matrix = _matrix_from_shorthand(ctm_shorthand)
|
||||||
|
|
||||||
|
# Corners of the image in untransformed unit space; last
|
||||||
|
# column is a dummy to assist matrix math
|
||||||
|
corners = [[0, 0, 1],
|
||||||
|
[1, 0, 1],
|
||||||
|
[0, 1, 1],
|
||||||
|
[1, 1, 1]]
|
||||||
|
|
||||||
|
# Rotate/translate/scale the corners into PDF coords (1/72")
|
||||||
|
# ordering of points may change, e.g. if rotation is 180 then
|
||||||
|
# the point (0, 0) may become the top right
|
||||||
|
# The row vectors can all be transformed together here by building
|
||||||
|
# a matrix of them
|
||||||
|
page_unit_corners = matrix_mult(corners, matrix)
|
||||||
|
|
||||||
|
# Calculate the width and height of the rotated image
|
||||||
|
# the transformation matrix so the corner that was originally
|
||||||
|
# (1, 1) can be ignored
|
||||||
|
image_drawn_width = euclidean_distance(
|
||||||
|
page_unit_corners[0], page_unit_corners[1])
|
||||||
|
image_drawn_height = euclidean_distance(
|
||||||
|
page_unit_corners[0], page_unit_corners[2])
|
||||||
|
|
||||||
|
# print((image_drawn_width, image_drawn_height))
|
||||||
|
|
||||||
|
# The scale of the image is pixels per PDF unit (1/72")
|
||||||
|
scale_w = image_size[0] / image_drawn_width
|
||||||
|
scale_h = image_size[1] / image_drawn_height
|
||||||
|
|
||||||
|
# DPI = scale * 72
|
||||||
|
dpi_w = scale_w * 72.0
|
||||||
|
dpi_h = scale_h * 72.0
|
||||||
|
|
||||||
|
return (dpi_w, dpi_h)
|
||||||
|
|
||||||
|
|
||||||
|
def _find_page_images(page, pageinfo, contentsinfo):
|
||||||
|
|
||||||
|
for n, im in enumerate(contentsinfo.inline_images):
|
||||||
|
settings, shorthand = im
|
||||||
|
image = {}
|
||||||
|
image['name'] = str('inline-%02d' % n)
|
||||||
|
image['width'] = settings['/W']
|
||||||
|
image['height'] = settings['/H']
|
||||||
|
image['bpc'] = settings['/BPC']
|
||||||
|
image['color'] = FRIENDLY_COLORSPACE.get(settings['/CS'], '-')
|
||||||
|
image['comp'] = FRIENDLY_COMP.get(image['color'], '?')
|
||||||
|
|
||||||
|
dpi_w, dpi_h = _get_dpi(shorthand, (image['width'], image['height']))
|
||||||
|
image['dpi_w'], image['dpi_h'] = Decimal(dpi_w), Decimal(dpi_h)
|
||||||
|
yield image
|
||||||
|
|
||||||
|
# Look for XObject (out of line images)
|
||||||
try:
|
try:
|
||||||
page['/Resources']['/XObject']
|
page['/Resources']['/XObject']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Look for XObject (out of line images)
|
|
||||||
for xobj in page['/Resources']['/XObject']:
|
for xobj in page['/Resources']['/XObject']:
|
||||||
# PyPDF2 returns the keys as an iterator
|
# PyPDF2 returns the keys as an iterator
|
||||||
pdfimage = page['/Resources']['/XObject'][xobj]
|
pdfimage = page['/Resources']['/XObject'][xobj]
|
||||||
@@ -79,6 +208,7 @@ def _find_page_images(page, pageinfo):
|
|||||||
if pdfimage['/ImageMask']:
|
if pdfimage['/ImageMask']:
|
||||||
continue
|
continue
|
||||||
image = {}
|
image = {}
|
||||||
|
image['name'] = str(xobj)
|
||||||
image['width'] = pdfimage['/Width']
|
image['width'] = pdfimage['/Width']
|
||||||
image['height'] = pdfimage['/Height']
|
image['height'] = pdfimage['/Height']
|
||||||
image['bpc'] = pdfimage['/BitsPerComponent']
|
image['bpc'] = pdfimage['/BitsPerComponent']
|
||||||
@@ -98,8 +228,24 @@ def _find_page_images(page, pageinfo):
|
|||||||
image['color'] = 'jpx' if image['enc'] == 'jpx' else '?'
|
image['color'] = 'jpx' if image['enc'] == 'jpx' else '?'
|
||||||
|
|
||||||
image['comp'] = FRIENDLY_COMP.get(image['color'], '?')
|
image['comp'] = FRIENDLY_COMP.get(image['color'], '?')
|
||||||
image['dpi_w'] = image['width'] / pageinfo['width_inches']
|
image['dpi_w'] = image['dpi_h'] = 0
|
||||||
image['dpi_h'] = image['height'] / pageinfo['height_inches']
|
|
||||||
|
for raster in contentsinfo.raster_settings:
|
||||||
|
# Loop in case the same image is display multiple times on a page
|
||||||
|
if raster[0] != image['name']:
|
||||||
|
continue
|
||||||
|
shorthand = raster[1]
|
||||||
|
|
||||||
|
dpi_w, dpi_h = _get_dpi(
|
||||||
|
shorthand, (image['width'], image['height']))
|
||||||
|
|
||||||
|
# When image is used multiple times take the highest DPI it is
|
||||||
|
# rendered at
|
||||||
|
image['dpi_w'] = max(dpi_w, image.get('dpi_w', 0))
|
||||||
|
image['dpi_h'] = max(dpi_h, image.get('dpi_h', 0))
|
||||||
|
|
||||||
|
image['dpi_w'] = Decimal(image['dpi_w'])
|
||||||
|
image['dpi_h'] = Decimal(image['dpi_h'])
|
||||||
image['dpi'] = (image['dpi_w'] * image['dpi_h']) ** Decimal(0.5)
|
image['dpi'] = (image['dpi_w'] * image['dpi_h']) ** Decimal(0.5)
|
||||||
yield image
|
yield image
|
||||||
|
|
||||||
@@ -141,12 +287,14 @@ def _pdf_get_pageinfo(infile, pageno: int):
|
|||||||
pageinfo['width_inches'] = width_pt / Decimal(72.0)
|
pageinfo['width_inches'] = width_pt / Decimal(72.0)
|
||||||
pageinfo['height_inches'] = height_pt / Decimal(72.0)
|
pageinfo['height_inches'] = height_pt / Decimal(72.0)
|
||||||
|
|
||||||
pageinfo['images'] = [im for im in _find_page_images(page, pageinfo)]
|
try:
|
||||||
|
contentstream = pypdf.pdf.ContentStream(page.getContents(), pdf)
|
||||||
|
except AttributeError as e:
|
||||||
|
return pageinfo
|
||||||
|
|
||||||
# Look for inline images
|
contentsinfo = _interpret_contents(contentstream)
|
||||||
if _page_has_inline_images(page):
|
pageinfo['images'] = [im for im in _find_page_images(
|
||||||
raise NotImplementedError(
|
page, pageinfo, contentsinfo)]
|
||||||
"Warning: input PDF contains inline images - not supported")
|
|
||||||
|
|
||||||
if pageinfo['images']:
|
if pageinfo['images']:
|
||||||
xres = max(image['dpi_w'] for image in pageinfo['images'])
|
xres = max(image['dpi_w'] for image in pageinfo['images'])
|
||||||
@@ -164,3 +312,17 @@ def pdf_get_all_pageinfo(infile):
|
|||||||
pdf = pypdf.PdfFileReader(infile)
|
pdf = pypdf.PdfFileReader(infile)
|
||||||
getcontext().prec = 6
|
getcontext().prec = 6
|
||||||
return [_pdf_get_pageinfo(infile, n) for n in range(pdf.numPages)]
|
return [_pdf_get_pageinfo(infile, n) for n in range(pdf.numPages)]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('infile')
|
||||||
|
args = parser.parse_args()
|
||||||
|
info = pdf_get_all_pageinfo(args.infile)
|
||||||
|
from pprint import pprint
|
||||||
|
pprint(info)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|||||||
+7
-33
@@ -5,10 +5,13 @@
|
|||||||
|
|
||||||
from __future__ import print_function, absolute_import, division
|
from __future__ import print_function, absolute_import, division
|
||||||
from string import Template
|
from string import Template
|
||||||
from subprocess import Popen, PIPE
|
|
||||||
import os
|
|
||||||
import codecs
|
import codecs
|
||||||
from . import get_program
|
import pkg_resources
|
||||||
|
|
||||||
|
ICC_PROFILE_RELPATH = 'data/sRGB_IEC61966-2-1_black_scaled.icc'
|
||||||
|
|
||||||
|
SRGB_ICC_PROFILE = pkg_resources.resource_filename(
|
||||||
|
'ocrmypdf', ICC_PROFILE_RELPATH)
|
||||||
|
|
||||||
|
|
||||||
# This is a template written in PostScript which is needed to create PDF/A
|
# This is a template written in PostScript which is needed to create PDF/A
|
||||||
@@ -93,38 +96,9 @@ def _get_pdfa_def(icc_profile, icc_identifier, pdfmark):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _get_postscript_icc_path():
|
|
||||||
"Parse Ghostscript's help message to find where iccprofiles are stored"
|
|
||||||
|
|
||||||
p_gs = Popen([get_program('gs'), '--help'], close_fds=True,
|
|
||||||
universal_newlines=True,
|
|
||||||
stdout=PIPE, stderr=PIPE)
|
|
||||||
out, _ = p_gs.communicate()
|
|
||||||
lines = out.splitlines()
|
|
||||||
|
|
||||||
def search_paths(lines):
|
|
||||||
seeking = True
|
|
||||||
for line in lines:
|
|
||||||
if seeking:
|
|
||||||
if line.startswith('Search path'):
|
|
||||||
seeking = False
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
if line.strip().startswith('/'):
|
|
||||||
yield from (
|
|
||||||
path.strip() for path in line.split(':')
|
|
||||||
if path.strip() != '')
|
|
||||||
for root in search_paths(lines):
|
|
||||||
path = os.path.realpath(os.path.join(root, '../iccprofiles'))
|
|
||||||
if os.path.exists(path):
|
|
||||||
return path
|
|
||||||
|
|
||||||
raise FileNotFoundError("Could not find Ghostscript's iccprofiles")
|
|
||||||
|
|
||||||
|
|
||||||
def generate_pdfa_def(target_filename, pdfmark, icc='sRGB'):
|
def generate_pdfa_def(target_filename, pdfmark, icc='sRGB'):
|
||||||
if icc == 'sRGB':
|
if icc == 'sRGB':
|
||||||
icc_profile = os.path.join(_get_postscript_icc_path(), 'srgb.icc')
|
icc_profile = SRGB_ICC_PROFILE
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("Only supporting sRGB")
|
raise NotImplementedError("Only supporting sRGB")
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,8 @@ def get_orientation(input_file, language: list, timeout: float, log):
|
|||||||
return OrientationConfidence(angle=0, confidence=0.0)
|
return OrientationConfidence(angle=0, confidence=0.0)
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
tesseract_log_output(log, e.output, input_file)
|
tesseract_log_output(log, e.output, input_file)
|
||||||
if 'Image too large' in e.output:
|
if ('Too few characters. Skipping this page' in e.output or
|
||||||
|
'Image too large' in e.output):
|
||||||
return OrientationConfidence(0, 0)
|
return OrientationConfidence(0, 0)
|
||||||
raise e from e
|
raise e from e
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -209,7 +209,6 @@ setup(
|
|||||||
],
|
],
|
||||||
setup_requires=[
|
setup_requires=[
|
||||||
'setuptools_scm',
|
'setuptools_scm',
|
||||||
'setuptools_scm_git_archive',
|
|
||||||
'cffi>=1.5.0',
|
'cffi>=1.5.0',
|
||||||
'pytest-runner'
|
'pytest-runner'
|
||||||
],
|
],
|
||||||
@@ -231,5 +230,6 @@ setup(
|
|||||||
'ocrmypdf = ocrmypdf.main:run_pipeline'
|
'ocrmypdf = ocrmypdf.main:run_pipeline'
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
package_data={'ocrmypdf': ['data/sRGB_IEC61966-2-1_black_scaled.icc']},
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False)
|
zip_safe=False)
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
deb http://httpredir.debian.org/debian stretch main
|
||||||
|
deb http://httpredir.debian.org/debian stretch-updates main
|
||||||
|
deb http://security.debian.org stretch/updates main
|
||||||
|
deb http://ftp.de.debian.org/debian sid main contrib non-free
|
||||||
Binary file not shown.
@@ -103,8 +103,8 @@ def test_single_page_image():
|
|||||||
assert pdfimage['bpc'] == 8
|
assert pdfimage['bpc'] == 8
|
||||||
|
|
||||||
# DPI in a 1"x1" is the image width
|
# DPI in a 1"x1" is the image width
|
||||||
assert pdfimage['dpi_w'] == 8
|
assert abs(pdfimage['dpi_w'] - 8) < 1e-5
|
||||||
assert pdfimage['dpi_h'] == 8
|
assert abs(pdfimage['dpi_h'] - 8) < 1e-5
|
||||||
|
|
||||||
|
|
||||||
def test_single_page_inline_image():
|
def test_single_page_inline_image():
|
||||||
@@ -120,8 +120,12 @@ def test_single_page_inline_image():
|
|||||||
pdf.showPage()
|
pdf.showPage()
|
||||||
pdf.save()
|
pdf.save()
|
||||||
|
|
||||||
with pytest.raises(NotImplementedError):
|
pdfinfo = pageinfo.pdf_get_all_pageinfo(filename)
|
||||||
pageinfo.pdf_get_all_pageinfo(filename)
|
print(pdfinfo)
|
||||||
|
pdfimage = pdfinfo[0]['images'][0]
|
||||||
|
assert (pdfimage['dpi_w'] - 8) < 1e-5
|
||||||
|
assert pdfimage['color'] != '-'
|
||||||
|
assert pdfimage['width'] == 8
|
||||||
|
|
||||||
|
|
||||||
def test_jpeg():
|
def test_jpeg():
|
||||||
@@ -131,4 +135,5 @@ def test_jpeg():
|
|||||||
|
|
||||||
pdfimage = pdfinfo[0]['images'][0]
|
pdfimage = pdfinfo[0]['images'][0]
|
||||||
assert pdfimage['enc'] == 'jpeg'
|
assert pdfimage['enc'] == 'jpeg'
|
||||||
|
assert (pdfimage['dpi_w'] - 150) < 1e-5
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user