Compare commits

..
3 Commits
Author SHA1 Message Date
fritz-hh d55e36267a Release notes updated for v1.1-stable 2014-01-06 19:45:59 +01:00
fritz-hh 6ea29cc892 fixes #42 (bashism removed) 2014-01-06 19:36:07 +01:00
fritz-hh 0245ce4385 typo corrected 2014-01-06 19:34:53 +01:00
118 changed files with 40048 additions and 18240 deletions
-31
View File
@@ -1,31 +0,0 @@
*.ipynb
*.pdf
*.pyc
*.rst
*.sublime*
*/*.pyc
*/*/*.pyc
*/*/*/*.pyc
*/*/*/*/*.pyc
*/*/*/*/*/*.pyc
*/*/*/*/*/*/*.pyc
*/*/*/*/*/*/*/*.pyc
.cache/
.git/
.ipynb_checkpoints/
.ruffus_history.sqlite
bin/
build/
dist/
htmlcov/
include/
lib/
MANIFEST.in
ocrmypdf.egg-info/
staging/
tests/cache/
tests/output/
tests/resources/private/
tmp/
venv-3.4/
venv-3.5/
-1
View File
@@ -1 +0,0 @@
ref-names: $Format:%D$
+1 -3
View File
@@ -5,6 +5,4 @@
# (binary is a macro for -text -diff)
*.jar binary
*.pdf binary
*.PDF binary
.git_archival.txt export-subst
*.PDF binary
-3
View File
@@ -1,3 +0,0 @@
Please include the command line and (if needed) a test file with your issue report.
If possible, please use a test file that we can include in future test cases (no personal information, no copyrighted material).
+1 -33
View File
@@ -1,34 +1,2 @@
# Development environment
*.pyc
*.sublime-*
venv-*/
pyvenv.cfg
tasks.py
# Package building
*.egg-info/
.cache/
.eggs/
build/
dist/
wheelhouse/
# Automatically generated files
ocrmypdf/lib/_*.py
ocrmypdf/version.py
docs/_build/
docs/_static/
docs/_templates/
# Code coverage
.coverage
htmlcov/
# Testing
log/
/*.pdf
.ipynb_checkpoints/
tests/cache/
tests/output/
tests/resources/private/
tmp/
log/
-45
View File
@@ -1,45 +0,0 @@
sudo: required
dist: trusty
language: python
cache: pip
cache:
directories:
- $HOME/.ccache
- packages
- tests/cache
python:
- 3.4
- 3.5
before_cache:
- rm -f $HOME/.cache/pip/log/debug.log
before_install:
# Ubuntu packages
- sudo add-apt-repository ppa:vshn/ghostscript -y # for ghostscript 9.16 (trusty has 9.10)
- sudo add-apt-repository ppa:heyarje/libav-11 -y # for libav11, which is for unpaper
- sudo apt-get update -qq # must go after all add-apt-repo
- sudo apt-get install -y ghostscript tesseract-ocr tesseract-ocr-deu tesseract-ocr-eng tesseract-ocr-fra qpdf poppler-utils libavformat56 libavcodec56 libavutil54 libffi-dev
# pip
- pip install --upgrade pip
# Download, make and install unpaper (using ccache)
- mkdir -p packages
- "[ -f packages/unpaper_6.1-1.deb ] || wget -q https://dl.dropboxusercontent.com/u/28971240/unpaper_6.1-1.deb -O packages/unpaper_6.1-1.deb"
- sudo dpkg -i packages/unpaper_6.1-1.deb
install:
- pip install -r requirements.txt
- pip install -r test_requirements.txt
script:
- python setup.py clean
- python setup.py install
- python setup.py test
os:
- linux
+17
View File
@@ -0,0 +1,17 @@
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-91
View File
@@ -1,91 +0,0 @@
# OCRmyPDF
#
# VERSION 3.2
FROM debian:stretch
MAINTAINER James R. Barlow <jim@purplerock.ca>
# Add unprivileged user
RUN useradd docker \
&& mkdir /home/docker \
&& chown docker:docker /home/docker
# Update system and install our dependencies
# If this command takes too Docker hub's automated build will timeout,
# so try it in portions
RUN apt-get update && apt-get install -y --no-install-recommends \
locales \
python3 \
python3-pip \
python3-venv \
python3-reportlab \
python3-pil \
python3-wheel
RUN apt-get install -y --no-install-recommends \
unpaper \
qpdf \
poppler-utils \
tesseract-ocr \
tesseract-ocr-deu tesseract-ocr-spa tesseract-ocr-eng tesseract-ocr-fra
RUN apt-get install -qy --no-install-recommends \
libffi-dev \
libpython3-dev \
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
# Borrowed from https://index.docker.io/u/crosbymichael/python/
RUN dpkg-reconfigure locales && \
locale-gen C.UTF-8 && \
/usr/sbin/update-locale LANG=C.UTF-8
ENV LC_ALL C.UTF-8
# Set up a Python virtualenv and take all of the system packages, so we can
# rely on the platform packages rather than importing GCC and compiling them
RUN pyvenv /appenv \
&& pyvenv --system-site-packages /appenv
COPY . /application/
# Replace stock Tesseract 3.04.00 font with improved sharp2.ttf that resolves
# issues in many PDF viewers.
# Discussion is in https://github.com/tesseract-ocr/tesseract/issues/182
COPY ./share/sharp2.ttf /usr/share/tesseract-ocr/tessdata/pdf.ttf
RUN chmod 644 /usr/share/tesseract-ocr/tessdata/pdf.ttf
# Set this here to force a docker version, allowing non-tagged versions to
# be built
# ENV SETUPTOOLS_SCM_PRETEND_VERSION=v3.3.0
# Install application and dependencies
# In this arrangement Pillow and reportlab will be provided by the system
# Even though ocrmypdf is locally present, pull from PyPI because
# Dockerhub and setuptools_scm clash
RUN . /appenv/bin/activate; \
pip install --upgrade pip \
&& pip install ocrmypdf \
&& pip install --no-cache-dir -r /application/test_requirements.txt
# Remove the junk
RUN apt-get remove -qy gcc
RUN apt-get autoremove -y && apt-get clean -y
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /root/*
USER docker
WORKDIR /home/docker
ENV OCRMYPDF_TEST_OUTPUT=/tmp/test-output
ENV OCRMYPDF_SHARP_TTF=1
# Must use array form of ENTRYPOINT
# Non-array form does not append other arguments, because that is "intuitive"
ENTRYPOINT ["/application/docker-wrapper.sh"]
-16
View File
@@ -1,16 +0,0 @@
# OCRmyPDF polyglot
#
# VERSION 3.2
FROM jbarlow83/ocrmypdf:latest
MAINTAINER James R. Barlow <jim@purplerock.ca>
# Update system and install our dependencies
USER root
RUN apt-get update && apt-get install -y --no-install-recommends \
tesseract-ocr-all
USER docker
# Must use array form of ENTRYPOINT
# Non-array form does not append other arguments, because that is "intuitive"
ENTRYPOINT ["/application/docker-wrapper.sh"]
-20
View File
@@ -1,20 +0,0 @@
Copyright (c) 2013-2016, The OCRmyPDF Authors
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-48
View File
@@ -1,48 +0,0 @@
# requirements
include requirements.txt
include test_requirements.txt
include dev_requirements.txt
# git
include .git_archival.txt
# docker
include Dockerfile
include Dockerfile.polyglot
include .dockerignore
# tests
include pytest.ini
recursive-include tests *.jpg
recursive-include tests *.pdf
recursive-include tests *.py
recursive-include tests *.rst
recursive-include tests *.txt
recursive-exclude tests/output *
recursive-exclude tests/output_pageinfo *
recursive-exclude tests/resources/private *
recursive-exclude tests/cache *
# documentation
include *.rst
include pipeline.svg
recursive-exclude .github *
recursive-include docs *.py
recursive-include docs *.rst
recursive-include docs *.svg
recursive-exclude docs/_build *
# support files
recursive-include ocrmypdf/data *
recursive-include share *
include *.py
exclude tasks.py
# code
recursive-include ocrmypdf *.py
exclude ocrmypdf/lib/_leptonica.py
include OCRmyPDF.sh
include docker-wrapper.sh
+358 -3
View File
@@ -1,8 +1,363 @@
#!/bin/sh
##############################################################################
# Copyright (c) 2013-14: fritz-hh from Github (https://github.com/fritz-hh)
# Copyright (c) 2013: fritz-hh from Github (https://github.com/fritz-hh)
##############################################################################
echo "This script is deprecated. Use 'ocrmypdf' instead."
TOOLNAME="OCRmyPDF"
VERSION="v1.1-stable"
python3 -m ocrmypdf.main "$@"
START=`date +%s`
usage() {
cat << EOF
--------------------------------------------------------------------------------------
Script aimed at generating a searchable PDF file from a PDF file containing only images.
(The script performs optical character recognition of each respective page using the
tesseract engine)
Copyright: fritz from NAS4Free forum
Version: $VERSION
Usage: OCRmyPDF.sh [-h] [-v] [-g] [-k] [-d] [-c] [-i] [-l language] [-C filename] inputfile outputfile
-h : Display this help message
-v : Increase the verbosity (this option can be used more than once)
-k : Do not delete the temporary files
-g : Activate debug mode:
- Generates a PDF file containing each page twice (once with the image, once without the image
but with the OCRed text as well as the detected bounding boxes)
- Set the verbosity to the highest possible
- Do not delete the temporary files
-d : Deskew each page before performing OCR
-c : Clean each page before performing OCR
-i : Incorporate the cleaned image in the final PDF file (by default the original image
image, or the deskewed image if the -d option is set, is incorporated)
-l : Set the language of the PDF file in order to improve OCR results (default "eng")
Any language supported by tesseract is supported.
-C : Pass an additional configuration file to the tesseract OCR engine.
(this option can be used more than once)
Note: The configuration file must be available in the "tessdata/configs" folder
of your tesseract installation
inputfile : PDF file to be OCRed
outputfile : The PDF/A file to be generated
--------------------------------------------------------------------------------------
EOF
}
#################################################
# Get an absolute path from a relative path to a file
#
# Param1 : Relative path
# Returns: 1 if the folder in which the file is located does not exist
# 0 otherwise
#################################################
absolutePath() {
local wdsave absolutepath
wdsave="$(pwd)"
! cd "$(dirname "$1")" 1> /dev/null 2> /dev/null && return 1
absolutepath="$(pwd)/$(basename "$1")"
cd "$wdsave"
echo "$absolutepath"
return 0
}
# Initialization of constants
EXIT_BAD_ARGS="1" # possible exit codes
EXIT_BAD_INPUT_FILE="2"
EXIT_MISSING_DEPENDENCY="3"
EXIT_INVALID_OUPUT_PDFA="4"
EXIT_OTHER_ERROR="5"
LOG_ERR="0" # 0=only error messages
LOG_INFO="1" # 1=error messages and some infos
LOG_DEBUG="2" # 2=debug level logging
SRC="./src" # location of the source folder (except source of external tools like jhove)
JHOVE="./jhove/bin/JhoveApp.jar" # java SW for validating the final PDF/A
JHOVE_CFG="./jhove/conf/jhove.conf" # location of the jhove config file
# Initialization the configuration parameters with default values
VERBOSITY="$LOG_ERR" # default verbosity level
LAN="eng" # default language of the PDF file (required to get good OCR results)
KEEP_TMP="0" # do not delete the temporary files (default)
PREPROCESS_DESKEW="0" # 0=no, 1=yes (deskew image)
PREPROCESS_CLEAN="0" # 0=no, 1=yes (clean image to improve OCR)
PREPROCESS_CLEANTOPDF="0" # 0=no, 1=yes (put cleaned image in final PDF)
PDF_NOIMG="0" # 0=no, 1=yes (generates each PDF page twice, with and without image)
TESS_CFG_FILES="" # list of additional configuration files to be used by tesseract
# Parse optional command line arguments
while getopts ":hvgkdcil:C:" opt; do
case $opt in
h) usage ; exit 0 ;;
v) VERBOSITY=$(($VERBOSITY+1)) ;;
k) KEEP_TMP="1" ;;
g) PDF_NOIMG="1"; VERBOSITY="10"; KEEP_TMP="1" ;;
d) PREPROCESS_DESKEW="1" ;;
c) PREPROCESS_CLEAN="1" ;;
i) PREPROCESS_CLEANTOPDF="1" ;;
l) LAN="$OPTARG" ;;
C) TESS_CFG_FILES="$OPTARG $TESS_CFG_FILES" ;;
\?)
echo "Invalid option: -$OPTARG" >&2
usage
exit $EXIT_BAD_ARGS ;;
:)
echo "Option -$OPTARG requires an argument" >&2
usage
exit $EXIT_BAD_ARGS ;;
esac
done
# Remove the optional arguments parsed above.
shift $((OPTIND-1))
# Check if the number of mandatory parameters
# provided is as expected
if [ "$#" -ne "2" ]; then
echo "Exactly two mandatory argument shall be provided ($# arguments provided)" >&2
usage
exit $EXIT_BAD_ARGS
fi
! absolutePath "$1" > /dev/null && echo "The folder in which the input file should be located does not exist. Exiting..." >&2 && exit $EXIT_BAD_ARGS
FILE_INPUT_PDF="`absolutePath "$1"`"
! absolutePath "$2" > /dev/null && echo "The folder in which the output file should be generated does not exist. Exiting..." >&2 && exit $EXIT_BAD_ARGS
FILE_OUTPUT_PDFA="`absolutePath "$2"`"
# set script path as working directory
cd "`dirname $0`"
[ $VERBOSITY -ge $LOG_INFO ] && echo "$TOOLNAME version: $VERSION"
# check if the required utilities are installed
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Checking if all dependencies are installed"
! command -v identify > /dev/null && echo "Please install ImageMagick. Exiting..." >&2 && exit $EXIT_MISSING_DEPENDENCY
! command -v pdfimages > /dev/null && echo "Please install poppler-utils. Exiting..." >&2 && exit $EXIT_MISSING_DEPENDENCY
! command -v pdftoppm > /dev/null && echo "Please install poppler-utils. Exiting..." >&2 && exit $EXIT_MISSING_DEPENDENCY
! command -v pdftk > /dev/null && echo "Please install pdftk. Exiting..." >&2 && exit $EXIT_MISSING_DEPENDENCY
[ $PREPROCESS_CLEAN -eq 1 ] && ! command -v unpaper > /dev/null && echo "Please install unpaper. Exiting..." >&2 && exit $EXIT_MISSING_DEPENDENCY
! command -v tesseract > /dev/null && echo "Please install tesseract and tesseract-data. Exiting..." >&2 && exit $EXIT_MISSING_DEPENDENCY
! command -v python > /dev/null && echo "Please install python, and the python libraries: reportlab, lxml. Exiting..." >&2 && exit $EXIT_MISSING_DEPENDENCY
! command -v gs > /dev/null && echo "Please install ghostcript. Exiting..." >&2 && exit $EXIT_MISSING_DEPENDENCY
! command -v java > /dev/null && echo "Please install java. Exiting..." >&2 && exit $EXIT_MISSING_DEPENDENCY
# Initialize path to temporary files
today=$(date +"%Y%m%d_%H%M")
fld=$(basename "$FILE_INPUT_PDF" | sed 's/[.][^.]*//')
TMP_FLD="./tmp/$today.filename.$fld"
FILE_TMP="$TMP_FLD/tmp.txt" # temporary file with a very short lifetime (may be used for several things)
FILE_SIZE_PAGES="$TMP_FLD/page-sizes.txt" # size in pt of the respective page of the input PDF file
FILE_OUTPUT_PDF_CAT="${TMP_FLD}/ocred.pdf" # concatenated OCRed PDF files
FILE_OUTPUT_PDFA_WO_META="${TMP_FLD}/ocred-pdfa-wo-metadata.pdf" # PDFA file before appending metadata
FILE_VALIDATION_LOG="${TMP_FLD}/pdf_validation.log" # log file containing the results of the validation of the PDF/A file
# Create tmp folder
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Creating temporary folder: \"$TMP_FLD\""
rm -r -f "${TMP_FLD}"
mkdir -p "${TMP_FLD}"
# get the size of each pdf page (width / height) in pt (inch*72)
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Input file: Extracting size of each page (in pt)"
! identify -format "%w %h\n" "$FILE_INPUT_PDF" > "$FILE_TMP" \
&& echo "Could not get size of PDF pages. Exiting..." >&2 && exit $EXIT_BAD_INPUT_FILE
# removing empty lines (last one should be) and prepend page # before each line
sed '/^$/d' "$FILE_TMP" | awk '{printf "%04d %s\n", NR, $0}' > "$FILE_SIZE_PAGES"
numpages=`tail -n 1 "$FILE_SIZE_PAGES" | cut -f1 -d" "`
# Itterate the pages of the input pdf file
while read pageSize ; do
page=`echo $pageSize | cut -f1 -d" "`
[ $VERBOSITY -ge $LOG_INFO ] && echo "Processing page $page / $numpages"
# create the name of the required file
curOrigImg="$TMP_FLD/${page}_Image" # original image available in the current PDF page
# (the image file may have a different orientation than in the pdf file)
curHocr="$TMP_FLD/$page.hocr" # hocr file to be generated by the OCR SW for the current page
curOCRedPDF="$TMP_FLD/${page}-ocred.pdf" # PDF file containing the image + the OCRed text for the current page
curOCRedPDFDebug="$TMP_FLD/${page}-debug-ocred.pdf" # PDF file containing data required to find out if OCR worked correctly
# get width / height of PDF page (in pt)
widthPDF=`echo $pageSize | cut -f2 -d" "`
heightPDF=`echo $pageSize | cut -f3 -d" "`
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Page $page: size ${heightPDF}x${widthPDF} (h*w in pt)"
# extract raw image from pdf file to compute resolution
# unfortunatelly this image can have another orientation than in the pdf...
# so we will have to extract it again later using pdftoppm
pdfimages -f $page -l $page -j "$FILE_INPUT_PDF" "$curOrigImg" 1>&2
# count number of extracted images
nbImg=`ls -1 "$curOrigImg"* | wc -l`
[ $nbImg -ne "1" ] && echo "Expecting exactly 1 image on page $page (found $nbImg). Exiting..." >&2 && exit $EXIT_BAD_INPUT_FILE
# Get characteristics of the extracted image
curImg=`ls -1 "$curOrigImg"*`
propCurImg=`identify -format "%w %h %[colorspace]" "$curImg"`
widthCurImg=`echo "$propCurImg" | cut -f1 -d" "`
heightCurImg=`echo "$propCurImg" | cut -f2 -d" "`
colorspaceCurImg=`echo "$propCurImg" | cut -f3 -d" "`
# switch height/width values if the image has not the right orientation
# we make here the assumption that vertical/horizontal dpi are equal
# we will check that later
if [ $((($heightPDF-$widthPDF)*($heightCurImg-$widthCurImg))) -lt 0 ]; then
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Page $page: Extracted image has wrong orientation. Inverting image height/width values"
tmpval=$heightCurImg
heightCurImg=$widthCurImg
widthCurImg=$tmpval
fi
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Page $page: size ${heightCurImg}x${widthCurImg} (h*w pixel)"
# compute the resolution of the image
dpi_x=`echo "scale=5;$widthCurImg*72/$widthPDF" | bc`
dpi_y=`echo "scale=5;$heightCurImg*72/$heightPDF" | bc`
# compute the maximum allowed resolution difference that can be cause by:
# - the truncated PDF with/height in pt
# - the precision of dpi value
epsilon=`echo "scale=5;($widthCurImg*72/$widthPDF^2)+($heightCurImg*72/$heightPDF^2)+0.00002" | bc` # max inaccuracy due to truncation of PDF size in pt
[ `echo "($dpi_x - $dpi_y) < $epsilon " | bc` -eq 0 -o `echo "($dpi_y - $dpi_x) < $epsilon " | bc` -eq 0 ] \
&& echo "Resolutions difference ($dpi_x/$dpi_y) higher than expected ($epsilon). Exiting..." >&2 && exit $EXIT_BAD_INPUT_FILE
dpi=`echo "scale=5;($dpi_x+$dpi_y)/2+0.5" | bc` # adding 0.5 is required for rounding
dpi=`echo "scale=0;$dpi/1" | bc` # round to the nearest integer
# Identify if page image should be saved as ppm (color) or pgm (gray)
ext="ppm"
opt=""
if [ $colorspaceCurImg = "Gray" ]; then
ext="pgm"
opt="-gray"
fi
curImgPixmap="$TMP_FLD/$page.$ext"
curImgPixmapDeskewed="$TMP_FLD/$page.deskewed.$ext"
curImgPixmapClean="$TMP_FLD/$page.cleaned.$ext"
# extract current page as image with right orientation and resoltution
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Page $page: Extracting image as $ext file (${dpi} dpi)"
! pdftoppm -f $page -l $page -r $dpi $opt "$FILE_INPUT_PDF" > "$curImgPixmap" \
&& echo "Could not extract page $page as $ext from \"$FILE_INPUT_PDF\". Exiting..." >&2 && exit $EXIT_OTHER_ERROR
# if requested deskew image (without changing its size in pixel)
if [ "$PREPROCESS_DESKEW" -eq "1" ]; then
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Page $page: Deskewing image"
! convert "$curImgPixmap" -deskew 40% -gravity center -extent ${widthCurImg}x${heightCurImg} "$curImgPixmapDeskewed" \
&& echo "Could not deskew \"$curImgPixmap\". Exiting..." >&2 && exit $EXIT_OTHER_ERROR
else
cp "$curImgPixmap" "$curImgPixmapDeskewed"
fi
# if requested clean image with unpaper to get better OCR results
if [ "$PREPROCESS_CLEAN" -eq "1" ]; then
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Page $page: Cleaning image with unpaper"
! unpaper --dpi $dpi --mask-scan-size 100 \
--no-deskew --no-grayfilter --no-blackfilter --no-mask-center --no-border-align \
"$curImgPixmapDeskewed" "$curImgPixmapClean" 1> /dev/null \
&& echo "Could not clean \"$curImgPixmapDeskewed\". Exiting..." >&2 && exit $EXIT_OTHER_ERROR
else
cp "$curImgPixmapDeskewed" "$curImgPixmapClean"
fi
# perform OCR
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Page $page: Performing OCR"
! tesseract -l "$LAN" "$curImgPixmapClean" "$curHocr" hocr $TESS_CFG_FILES 1> /dev/null 2> /dev/null \
&& echo "Could not OCR file \"$curImgPixmapClean\". Exiting..." >&2 && exit $EXIT_OTHER_ERROR
mv "$curHocr.html" "$curHocr"
# embed text and image to new pdf file
if [ "$PREPROCESS_CLEANTOPDF" -eq "1" ]; then
image4finalPDF="$curImgPixmapClean"
else
image4finalPDF="$curImgPixmapDeskewed"
fi
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Page $page: Embedding text in PDF"
! python $SRC/hocrTransform.py -r $dpi -i "$image4finalPDF" "$curHocr" "$curOCRedPDF" \
&& echo "Could not create PDF file from \"$curHocr\". Exiting..." >&2 && exit $EXIT_OTHER_ERROR
# if requested generate special debug PDF page with visible OCR text
if [ $PDF_NOIMG -eq "1" ] ; then
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Page $page: Embedding text in PDF (debug page)"
! python $SRC/hocrTransform.py -b -r $dpi "$curHocr" "$curOCRedPDFDebug" \
&& echo "Could not create PDF file from \"$curHocr\". Exiting..." >&2 && exit $EXIT_OTHER_ERROR
fi
# delete temporary files created for the current page
# to avoid using to much disk space in case of PDF files having many pages
if [ $KEEP_TMP -eq 0 ]; then
rm "$curOrigImg"*.*
rm "$curHocr"
rm "$curImgPixmap"
rm "$curImgPixmapDeskewed"
rm "$curImgPixmapClean"
fi
done < "$FILE_SIZE_PAGES"
# concatenate all pages
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Output file: Concatenating all pages"
! pdftk "${TMP_FLD}/"*-ocred.pdf cat output "$FILE_OUTPUT_PDF_CAT" \
&& echo "Could not concatenate individual PDF pages (\"${TMP_FLD}/*-ocred.pdf\") to one file. Exiting..." >&2 && exit $EXIT_OTHER_ERROR
# convert the pdf file to match PDF/A format
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Output file: Converting to PDF/A"
! gs -dQUIET -dPDFA -dBATCH -dNOPAUSE -dUseCIEColor \
-sProcessColorModel=DeviceCMYK -sDEVICE=pdfwrite -sPDFACompatibilityPolicy=2 \
-sOutputFile="$FILE_OUTPUT_PDFA" "$FILE_OUTPUT_PDF_CAT" 1> /dev/null 2> /dev/null \
&& echo "Could not convert PDF file \"$FILE_OUTPUT_PDF_CAT\" to PDF/A. Exiting..." >&2 && exit $EXIT_OTHER_ERROR
# # Write metadata
# # Needs to be done after converting to PDF/A, as gs does not preserve metadata
# [ $VERBOSITY -ge $LOG_DEBUG ] && echo "Output file: Update metadata (creator, producer, and title)"
# title=`basename "$FILE_INPUT_PDF" | sed 's/[.][^.]*//' | \
# sed 's/_/ /g' | sed 's/-/ /g' | \
# sed 's/\([[:lower:]]\)\([[:upper:]]\)/\1 \2/g' | \
# sed 's/\([[:alpha:]]\)\([[:digit:]]\)/\1 \2/g' | \
# sed 's/\([[:digit:]]\)\([[:alpha:]]\)/\1 \2/g'` # transform the file name (with extension) into distinct words
# pdftk "$FILE_OUTPUT_PDFA_WO_META" update_info_utf8 - output "$FILE_OUTPUT_PDFA" << EOF
# InfoBegin
# InfoKey: Title
# InfoValue: $title
# InfoBegin
# InfoKey: Creator
# InfoValue: $TOOLNAME $VERSION
# InfoBegin
# InfoKey: Producer
# InfoValue: ghostcript `gs --version`, pdftk
# EOF
# validate generated pdf file (compliance to PDF/A)
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Output file: Checking compliance to PDF/A standard"
java -jar "$JHOVE" -c "$JHOVE_CFG" -m PDF-hul "$FILE_OUTPUT_PDFA" > "$FILE_VALIDATION_LOG"
grep -i "Status|Message" "$FILE_VALIDATION_LOG" # summary of the validation
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "The full validation log is available here: \"$FILE_VALIDATION_LOG\""
# check the validation results
pdf_valid=1
grep -i 'ErrorMessage' "$FILE_VALIDATION_LOG" >&2 && pdf_valid=0
grep -i 'Status.*not valid' "$FILE_VALIDATION_LOG" >&2 && pdf_valid=0
grep -i 'Status.*Not well-formed' "$FILE_VALIDATION_LOG" >&2 && pdf_valid=0
! grep -i 'Profile:.*PDF/A-1' "$FILE_VALIDATION_LOG" > /dev/null && echo "PDF file profile is not PDF/A-1" >&2 && pdf_valid=0
[ $pdf_valid -ne 1 ] && echo "Output file: The generated PDF/A file is INVALID" >&2
[ $pdf_valid -ne 0 ] && [ $VERBOSITY -ge $LOG_INFO ] && echo "Output file: The generated PDF/A file is VALID"
# delete temporary files
if [ $KEEP_TMP -eq 0 ]; then
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Deleting temporary files"
rm -r -f "${TMP_FLD}"
fi
END=`date +%s`
[ $VERBOSITY -ge $LOG_DEBUG ] && echo "Script took $(($END-$START)) seconds"
[ $pdf_valid -ne 1 ] && exit $EXIT_INVALID_OUPUT_PDFA || exit 0
+42
View File
@@ -0,0 +1,42 @@
OCRmyPDF
========
OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to be searched
To get the script usage, call: sh ./OCRmyPDF.sh -h
Features
--------
- Generate a searchable PDF/A file from a PDF file containing only images
- Place OCRed text accurately below the image to easy copy / paste
- Keep the exact resolution of the original embedded images
- If requested deskew and / or clean the image before performing OCR
- Validate the generated file against the PDF/A specification using jhove
- Provides debug mode to enable easy verification of the OCR results
Motivation
----------
I searched the web for a free command line tool to OCR PDF files on linux/unix:
I found many, but none of them were really satisfying.
- Either they produced PDF files with misplaced text under the image (making copy/paste impossible)
- Or they did not display correctly some escaped html characters located in the hocr file produced by the OCR engine
- Or they changed the resolution of the embedded images
- Or they generated PDF file having a ridiculous big size
- Or they crashed when trying to OCR some of my PDF files
- Or they did not produce valid PDF files (even though they were readable with my current PDF reader)
- On top of that none of them produced PDF/A files (format dedicated for long time storage / archiving)
... so I decided to develop my own tool (using various existing scripts as an inspiration)
Install
--------
Download OCRmyPDF here: https://github.com/fritz-hh/OCRmyPDF/tags
Copy the file in onto your linux/unix machine and extract it.
Run: "sh ./OCRmyPDF.sh -h" to get the script usage
If not yet installed, the script will notify you about dependencies that need to be installed
-119
View File
@@ -1,119 +0,0 @@
OCRmyPDF
========
OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to
be searched or copy-pasted.
.. code-block:: bash
ocrmypdf # it's a scriptable command line program
-l eng+fra # it supports multiple languages
--rotate-pages # it can fix pages that are misrotated
--deskew # it can deskew crooked PDFs!
--title "My PDF" # it can change output metadata
--jobs 4 # it uses multiple cores by default
--output-type pdfa # it produces PDF/A by default
input_scanned.pdf # takes PDF input (or images)
output_searchable.pdf # produces validated PDF output
Main features
-------------
- Generates a searchable
`PDF/A <https://en.wikipedia.org/?title=PDF/A>`_ file from a regular PDF
- Places OCR text accurately below the image to ease copy / paste
- Keeps the exact resolution of the original embedded images
- When possible, inserts OCR information as a "lossless" operation without rendering vector information
- Keeps file size about the same
- If requested deskews and/or cleans the image before performing OCR
- Validates input and output files
- Provides debug mode to enable easy verification of the OCR results
- Processes pages in parallel when more than one CPU core is
available
- Uses `Tesseract OCR <https://github.com/tesseract-ocr/tesseract>`_ engine
- Supports more than `100 languages <https://github.com/tesseract-ocr/tessdata>`_ recognized by Tesseract
- Battle-tested on thousands of PDFs, a test suite and continuous integration
For details: please consult the `release notes <RELEASE_NOTES.rst>`_.
Motivation
----------
I searched the web for a free command line tool to OCR PDF files on
Linux/UNIX: I found many, but none of them were really satisfying.
- Either they produced PDF files with misplaced text under the image (making copy/paste impossible)
- Or they did not handle accents and multilingual characters
- Or they changed the resolution of the embedded images
- Or they generated ridiculously large PDF files
- Or they crashed when trying to OCR some of my PDF files
- Or they did not produce valid PDF files (even though they were readable with my current PDF reader)
- On top of that none of them produced PDF/A files (format dedicated for long time storage)
...so I decided to develop my own tool (using various existing scripts
as an inspiration).
Installation
------------
Linux, UNIX, and macOS are supported. Windows is not directly supported but there is a Docker image available that runs on Windows.
Users of Debian 9 or later or Ubuntu 16.10 or later may simply
``apt-get install ocrmypdf``.
For everyone else, `see our documentation <https://ocrmypdf.readthedocs.io/en/latest/installation.html>`_ for installation steps.
Languages
---------
OCRmyPDF uses Tesseract for OCR, and relies on its language packs. For Linux users,
you can often find packages that provide language packs:
.. code-block:: bash
# Display a list of all Tesseract language packs
apt-cache search tesseract-ocr
# Debian/Ubuntu users
apt-get install tesseract-ocr-chi-sim # Example: Install Chinese Simplified language back
You can then pass the ``-l LANG`` argument to OCRmyPDF to give a hint as to what languages it should search for. Multiple
languages can be requested.
Documentation and support
-------------------------
Once ocrmypdf is installed, the built-in help which explains the command syntax and options can be accessed via:
.. code-block:: bash
ocrmypdf --help
Our `documentation is served on Read the Docs <https://ocrmypdf.readthedocs.io/en/latest/index.html>`_.
If you detect an issue, please:
- Check whether your issue is already known
- If no problem report exists on github, please create one here:
https://github.com/jbarlow83/OCRmyPDF/issues
- Describe your problem thoroughly
- Append the console output of the script when running the debug mode
(``-v 1`` option)
- If possible provide your input PDF file as well as the content of the
temporary folder (using a file sharing service like Dropbox)
Press & Media
-------------
- `c't 1-2014, page 59 <http://heise.de/-2279695>`_:
Detailed presentation of OCRmyPDF v1.0 in the leading German IT
magazine c't
- `heise Open Source, 09/2014: Texterkennung mit
OCRmyPDF <http://heise.de/-2356670>`_
Disclaimer
----------
The software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
+143
View File
@@ -0,0 +1,143 @@
RELEASE NOTES
=============
Please always read this file before installing the package
Download software here: https://github.com/fritz-hh/OCRmyPDF/tags
v1.1-stable (2014-01-06):
====
New features
------------
- N/A
Changes
-------
- N/A
Fixes
-----
- Fixed syntax error (bashism) leading to an error message on certain systems (fixes #42)
Tested with
-----------
- Operating system: FreeBSD 9.1
- Dependencies:
- poppler-utils 0.22.2
- ImageMagick 6.8.0-7 2013-03-30
- Unpaper 0.3
- tesseract 3.02.02
- Python 2.7.3
- pdftk 1.45
- ghoscript (gs): 9.06
- java: openjdk version "1.7.0_17"
v1.0-stable (2013-05-06):
====
New features
------------
- In debug mode: compute and echo time required for processing (fixes #26)
Changes
-------
- Removed feature to add metadata in final pdf file (because it lead to to final PDF file that does not comply to the PDF/A-1 format)
- Removed feature to set same owner & permissions in final PDF file than in input file
- Removed many unused jhove files (e.g. documentation, *.java and *.class files)
Fixes
-----
- Correction to handle correctly path and input PDF files having spaces (fixes #31)
- Resolutions (x/y) that are nearly equal are now supported (fixes #25)
- Fix compatibility issue with Ubuntu server 12.04 / Ubuntu server 10.04 / Linux Mint 13 Maya and probably other Linux distributions (fixes #27)
- Commit missing jhove files (*.jar mainly) due to wrong .gitignore
Tested with
-----------
- Operating system: FreeBSD 9.1
- Dependencies:
- poppler-utils 0.22.2
- ImageMagick 6.8.0-7 2013-03-30
- Unpaper 0.3
- tesseract 3.02.02
- Python 2.7.3
- pdftk 1.45
- ghoscript (gs): 9.06
- java: openjdk version "1.7.0_17"
v1.0-rc2 (2013-04-29):
====
New features
------------
- Keep temporary files if debug mode is set (fixes #22)
- Set same owner & permissions in final PDF file than in input file (fixes #9)
- Added metadata in final pdf file (fixes #4)
Changes
-------
- N/A
Fixes
-----
- Fixed wrong image cropping when deskew option is activated
- Exit with error message if page size is not found in hocr file (fixes #21)
- Various minor fixes in log messages
Tested with
-----------
- Operating system: FreeBSD 9.1
- Dependencies:
- poppler-utils 0.22.2
- ImageMagick 6.8.0-7 2013-03-30
- Unpaper 0.3
- tesseract 3.02.02
- Python 2.7.3
- pdftk 1.45
- ghoscript (gs): 9.06
- java: openjdk version "1.7.0_17"
v1.0-rc1 (2013-04-26):
====
New features
------------
- First release candidate
Changes
-------
- N/A
Fixes
-----
- N/A
Tested with
-----------
- Operating system: FreeBSD 9.1
- Dependencies:
- poppler-utils 0.22.2
- ImageMagick 6.8.0-7 2013-03-30
- Unpaper 0.3
- tesseract 3.02.02
- Python 2.7.3
- pdftk 1.45
- ghoscript (gs): 9.06
- java: openjdk version "1.7.0_17"
-435
View File
@@ -1,435 +0,0 @@
RELEASE NOTES
=============
OCRmyPDF uses `semantic versioning <http://semver.org/>`_.
v4.3:
=====
- New feature ``--remove-background`` to detect and erase the background of color and grayscale images
- Better documentation
- Fixed an issue with PDFs that draw images when the raster stack depth is zero
- ocrmypdf can now redirect its output to stdout for use in a shell pipeline
+ This does not improve performance since temporary files are still used for buffering
+ Some output validation is disabled in this mode
v4.2.5:
=======
- Fixed an issue (#100) with PDFs that omit the optional /BitsPerComponent parameter on images
- Removed non-free file milk.pdf
v4.2.4:
=======
- Fixed an error (#90) caused by PDFs that use stencil masks properly
- Fixed handling of PDFs that try to draw images or stencil masks without properly setting up the graphics state (such images are now ignored for the purposes of calculating DPI)
v4.2.3:
=======
- Fixed an issue with PDFs that store page rotation (/Rotate) in an indirect object
- Integrated a few fixes to simplify downstream packaging (Debian)
+ The test suite no longer assumes it is installed
+ If running Linux, skip a test that passes Unicode on the command line
- Added a test case to check explicit masks and stencil masks
- Added a test case for indirect objects and linearized PDFs
- Deprecated the OCRmyPDF.sh shell script
v4.2.2:
=======
- Improvements to documentation
v4.2.1:
=======
- Fixed an issue where PDF pages that contained stencil masks would report an incorrect DPI and cause Ghostscript to abort
- Implemented stdin streaming
v4.2:
=====
- ocrmypdf will now try to convert single image files to PDFs if they are provided as input (#15)
+ This is a basic convenience feature. It only supports a single image and always makes the image fill the whole page.
+ For better control over image to PDF conversion, use ``img2pdf`` (one of ocrmypdf's dependencies)
- New argument ``--output-type {pdf|pdfa}`` allows disabling Ghostscript PDF/A generation
+ ``pdfa`` is the default, consistent with past behavior
+ ``pdf`` provides a workaround for users concerned about the increase in file size from Ghostscript forcing JBIG2 images to CCITT and transcoding JPEGs
+ ``pdf`` preserves as much as it can about the original file, including problems that PDF/A conversion fixes
- PDFs containing images with "non-square" pixel aspect ratios, such as 200x100 DPI, are now handled and converted properly (fixing a bug that caused to be cropped)
- ``--force-ocr`` rasterizes pages even if they contain no images
+ supports users who want to use OCRmyPDF to reconstruct text information in PDFs with damaged Unicode maps (copy and paste text does not match displayed text)
+ supports reinterpreting PDFs where text was rendered as curves for printing, and text needs to be recovered
+ fixes issue #82
- Fixes an issue where, with certain settings, monochrome images in PDFs would be converted to 8-bit grayscale, increasing file size (#79)
- Support for Ubuntu 12.04 LTS "precise" has been dropped in favor of (roughly) Ubuntu 14.04 LTS "trusty"
+ Some Ubuntu "PPAs" (backports) are needed to make it work
- Support for some older dependencies dropped
+ Ghostscript 9.15 or later is now required (available in Ubuntu trusty with backports)
+ Tesseract 3.03 or later is now required (available in Ubuntu trusty)
- Ghostscript now runs in "safer" mode where possible
v4.1.4:
=======
- Bug fix: monochrome images with an ICC profile attached were incorrectly converted to full color images if lossless reconstruction was not possible due to other settings; consequence was increased file size for these images
v4.1.3:
=======
- More helpful error message for PDFs with version 4 security handler
- Update usage instructions for Windows/Docker users
- Fix order of operations for matrix multiplication (no effect on most users)
- Add a few leptonica wrapper functions (no effect on most users)
v4.1.2:
=======
- Replace IEC sRGB ICC profile with Debian's sRGB (from icc-profiles-free) which is more compatible with the MIT license
- More helpful error message for an error related to certain types of malformed PDFs
v4.1:
=====
- ``--rotate-pages`` now only rotates pages when reasonably confidence in the orientation. This behavior can be adjusted with the new argument ``--rotate-pages-threshold``
- Fixed problems in error checking if ``unpaper`` is uninstalled or missing at run-time
- Fixed problems with "RethrownJobError" errors during error handling that suppressed the useful error messages
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:
=======
- 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 unexpected 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:
=======
Fixes
-----
- Fixed compatibility with Tesseract 3.04.01 release, particularly its different way of outputting
orientation information
- Improved handling of Tesseract errors and crashes
- Fixed use of chmod on Docker that broke most test cases
v4.0.1:
=======
Fixes
-----
- Fixed a KeyError if tesseract fails to find page orientation information
v4.0:
=====
New features
------------
- Automatic page rotation (``-r``) is now available. It uses ignores any prior rotation information
on PDFs and sets rotation based on the dominant orientation of detectable text. This feature is
fairly reliable but some false positives occur especially if there is not much text to work with. (#4)
- Deskewing is now performed using Leptonica instead of unpaper. Leptonica is faster and more reliable
at image deskewing than unpaper.
Fixes
-----
- Fixed an issue where lossless reconstruction could cause some pages to be appear incorrectly
if the page was rotated by the user in Acrobat after being scanned (specifically if it a /Rotate tag)
- Fixed an issue where lossless reconstruction could misalign the graphics layer with respect to
text layer if the page had been cropped such that its origin is not (0, 0) (#49)
Changes
-------
- Logging output is now much easier to read
- ``--deskew`` is now performed by Leptonica instead of unpaper (#25)
- libffi is now required
- Some changes were made to the Docker and Travis build environments to support libffi
- ``--pdf-renderer=tesseract`` now displays a warning if the Tesseract version is less than 3.04.01,
the planned release that will include fixes to an important OCR text rendering bug in Tesseract 3.04.00.
You can also manually install ./share/sharp2.ttf on top of pdf.ttf in your Tesseract tessdata folder
to correct the problem.
v3.2.1:
=======
Changes
-------
- Fixed issue #47 "convert() got and unexpected keyword argument 'dpi'" by upgrading to img2pdf 0.2
- Tweaked the Dockerfiles
v3.2:
=====
New features
------------
- Lossless reconstruction: when possible, OCRmyPDF will inject text layers without
otherwise manipulating the content and layout of a PDF page. For example, a PDF containing a mix
of vector and raster content would see the vector content preserved. Images may still be transcoded
during PDF/A conversion. (``--deskew`` and ``--clean-final`` disable this mode, necessarily.)
- New argument ``--tesseract-pagesegmode`` allows you to pass page segmentation arguments to Tesseract OCR.
This helps for two column text and other situations that confuse Tesseract.
- Added a new "polyglot" version of the Docker image, that generates Tesseract with all languages packs installed,
for the polyglots among us. It is much larger.
Changes
-------
- JPEG transcoding quality is now 95 instead of the default 75. Bigger file sizes for less degradation.
v3.1.1:
=======
Changes
-------
- Fixed bug that caused incorrect page size and DPI calculations on documents with mixed page sizes
v3.1:
=====
Changes
-------
- Default output format is now PDF/A-2b instead of PDF/A-1b
- Python 3.5 and OS X El Capitan are now supported platforms - no changes were
needed to implement support
- Improved some error messages related to missing input files
- Fixed issue #20 - uppercase .PDF extension not accepted
- Fixed an issue where OCRmyPDF failed to text that certain pages contained previously OCR'ed text,
such as OCR text produced by Tesseract 3.04
- Inserts /Creator tag into PDFs so that errors can be traced back to this project
- Added new option ``--pdf-renderer=auto``, to let OCRmyPDF pick the best PDF renderer.
Currently it always chooses the 'hocrtransform' renderer but that behavior may change.
- Set up Travis CI automatic integration testing
v3.0:
=====
New features
------------
- Easier installation with a Docker container or Python's ``pip`` package manager
- Eliminated many external dependencies, so it's easier to setup
- Now installs ``ocrmypdf`` to ``/usr/local/bin`` or equivalent for system-wide
access and easier typing
- Improved command line syntax and usage help (``--help``)
- Tesseract 3.03+ PDF page rendering can be used instead for better positioning
of recognized text (``--pdf-renderer tesseract``)
- PDF metadata (title, author, keywords) are now transferred to the
output PDF
- PDF metadata can also be set from the command line (``--title``, etc.)
- Automatic repairs malformed input PDFs if possible
- Added test cases to confirm everything is working
- Added option to skip extremely large pages that take too long to OCR and are
often not OCRable (e.g. large scanned maps or diagrams); other pages are still
processed (``--skip-big``)
- Added option to kill Tesseract OCR process if it seems to be taking too long on
a page, while still processing other pages (``--tesseract-timeout``)
- Less common colorspaces (CMYK, palette) are now supported by conversion to RGB
- Multiple images on the same PDF page are now supported
Changes
-------
- New, robust rewrite in Python 3.4+ with ruffus_ pipelines
- Now uses Ghostscript 9.14's improved color conversion model to preserve PDF colors
- OCR text is now rendered in the PDF as invisible text. Previous versions of OCRmyPDF
incorrectly rendered visible text with an image on top.
- All "tasks" in the pipeline can be executed in parallel on any
available CPUs, increasing performance
- The ``-o DPI`` argument has been phased out, in favor of ``--oversample DPI``, in
case we need ``-o OUTPUTFILE`` in the future
- Removed several dependencies, so it's easier to install. We no
longer use:
- GNU parallel_
- ImageMagick_
- Python 2.7
- Poppler
- MuPDF_ tools
- shell scripts
- Java and JHOVE_
- libxml2
- Some new external dependencies are required or optional, compared to v2.x:
- Ghostscript 9.14+
- qpdf_ 5.0.0+
- Unpaper_ 6.1 (optional)
- some automatically managed Python packages
.. _ruffus: http://www.ruffus.org.uk/index.html
.. _parallel: https://www.gnu.org/software/parallel/
.. _ImageMagick: http://www.imagemagick.org/script/index.php
.. _MuPDF: http://mupdf.com/docs/
.. _qpdf: http://qpdf.sourceforge.net/
.. _Unpaper: https://github.com/Flameeyes/unpaper
.. _JHOVE: http://jhove.sourceforge.net/
Release candidates
------------------
- rc9:
- fix issue #118: report error if ghostscript iccprofiles are missing
- fixed another issue related to #111: PDF rasterized to palette file
- add support image files with a palette
- don't try to validate PDF file after an exception occurs
- rc8:
- fix issue #111: exception thrown if PDF is missing DocumentInfo dictionary
- rc7:
- fix error when installing direct from pip, "no such file 'requirements.txt'"
- rc6:
- dropped libxml2 (Python lxml) since Python 3's internal XML parser is sufficient
- set up Docker container
- fix Unicode errors if recognized text contains Unicode characters and system locale is not UTF-8
- rc5:
- dropped Java and JHOVE in favour of qpdf
- improved command line error output
- additional tests and bug fixes
- tested on Ubuntu 14.04 LTS
- rc4:
- dropped MuPDF in favour of qpdf
- fixed some installer issues and errors in installation instructions
- improve performance: run Ghostscript with multithreaded rendering
- improve performance: use multiple cores by default
- bug fix: checking for wrong exception on process timeout
- rc3: skipping version number intentionally to avoid confusion with Tesseract
- rc2: first release for public testing to test-PyPI, Github
- rc1: testing release process
Compatibility notes
-------------------
- ``./OCRmyPDF.sh`` script is still available for now
- Stacking the verbosity option like ``-vvv`` is no longer supported
- The configuration file ``config.sh`` has been removed. Instead, you can
feed a file to the arguments for common settings:
::
ocrmypdf input.pdf output.pdf @settings.txt
where ``settings.txt`` contains *one argument per line*, for example:
::
-l
deu
--author
A. Merkel
--pdf-renderer
tesseract
Fixes
-----
- Handling of filenames containing spaces: fixed
Notes and known issues
----------------------
- Some dependencies may work with lower versions than tested, so try
overriding dependencies if they are "in the way" to see if they work.
- ``--pdf-renderer tesseract`` will output files with an incorrect page size in Tesseract 3.03,
due to a bug in Tesseract.
- PDF files containing "inline images" are not supported and won't be for the 3.0 release. Scanned
images almost never contain inline images.
v2.2-stable (2014-09-29):
=========================
OCRmyPDF versions 1 and 2 were implemented as shell scripts. OCRmyPDF 3.0+ is a fork that gradually replaced all shell scripts with Python while maintaining the existing command line arguments. No one is maintaining old versions.
For details on older versions, see the `final version of its release notes <https://github.com/fritz-hh/OCRmyPDF/blob/7fd3dbdf42ca53a619412ce8add7532c5e81a9d1/RELEASE_NOTES.md>`_.
-4
View File
@@ -1,4 +0,0 @@
check-manifest>=0.33
setuptools-scm>=1.11.1
twine>=1.8.1
coverage>=4.2
-5
View File
@@ -1,5 +0,0 @@
#!/bin/bash
. /appenv/bin/activate
cd /home/docker
exec ocrmypdf "$@"
-35
View File
@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 915 585" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="b" y2="445" gradientUnits="userSpaceOnUse" y1="179" gradientTransform="translate(0 -2.06)" x2="-29.7" x1="322">
<stop stop-color="#333" offset="0"/>
<stop stop-color="#fff" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient id="a" y2="414" gradientUnits="userSpaceOnUse" y1="159" x2="490" x1="815">
<stop stop-color="#33f" offset="0"/>
<stop stop-color="#3f3fff" stop-opacity="0" offset="1"/>
</linearGradient>
</defs>
<path fill="url(#b)" d="m403 247c-12 115-135 122-368 123-4.3-1.07-7.32-7.33-6-41 76-37 151-124 167-236 93 123 201 40.9 207 154z"/>
<g stroke-width="3.45" fill="none">
<path stroke="#000" d="m11.8 11.8h411v411l-411 0.01v-411z"/>
<path stroke="#448" d="m489 11.7h415v411h-415v-411z"/>
</g>
<path d="m876 244c-12 115-133 120-366 121-6-14-10-40-3-43 76-37.3 136-106 152-218 38 48 209 101 217 140z" fill="url(#a)"/>
<g id="RasterLarge" transform="matrix(1.36 0 0 1.28 -161 -636)">
<path fill="#999" d="m287 730h-30v-60h20v20h10v10h10v10h20v10h40v-10h20v-20h10v-20h-10v-10h-10v-10h-40v-10h-30v-10h-20v-10h-10v-10h-10v-60h10v-10h10v-10h10v-10h90v10h30v60h-20v-10h-10v-20h-10v-10h-20v-10h-30v10h-20v10h-10v20h10v20h30v10h30v10h30v10h10v10h10v10h10v60h-10v10h-10v10h-10v10h-100v-10z"/>
<path fill="#555" d="m297 730h-30v-10h-10v-50h20v20h10v20h10 10v10h60v-10h10v-10h10v-40h-20v-10h-20v-10h-40v-10h-20v-10h-20v-20h-10v-50h10v-10h10v-10h20v-10h70v10h30v10h10v50h-20v-20h-10v-20h-20v-10h-50v10h-10v10h-10v30h10v10h20v10h30v10h30v10h20v10h10v10h10v50h-10v20h-10v10h-20v10h-80z"/>
<path d="m307 730h-30v-10h-20v-50h20v30h10v10h10v10h70v-10h20v-50h-10v-10h-20v-10h-40v-10h-30v-10h-10v-10h-10v-20h-10v-30h10v-20h20v-10h20v-10h50v10h30v10h20v50h-20v-30h-10v-10h-10v-10h-70v10h-10v40h10v10h10v10h30v10h40v10h20v10h10v20h10v40h-10v20h-20v10h-30v10h-50v-10z"/>
</g>
<g font-size="40" font-family="sans-serif" text-anchor="middle">
<g font-size="100">
<text y="518" x="210">Raster</text>
<text y="518" x="695" fill="#338">Vector</text>
</g>
<text y="563" x="210">.jpeg .gif .png</text>
<text y="563" x="696" fill="#338">.svg</text>
</g>
<path id="VectorLarge" fill="#005" d="m661 294v-62.5l23.4 0.184c0.678 20.8 7.32 36.3 19.9 46.4 12.7 9.93 32.1 14.9 57.9 14.9 24.1 0 42.5-4.29 55.1-12.9 12.7-8.71 19.1-21.3 19.1-37.9 0-13.2-3.86-23.4-11.6-30.5-7.59-7.11-23.7-14-48.4-20.8l-40.1-10.9c-29-7.97-49.5-17.9-61.4-29.8-11.8-11.9-17.7-28.2-17.7-48.9 0-23.3 9.15-41.4 27.4-54.3 18.3-12.9 44-19.3 77.1-19.3 14.1 0 29.6 1.41 46.4 4.23 16.8 2.7 34.7 6.68 53.7 12v58.5h-23c-2.3-19.4-9.49-33.4-21.6-41.9-11.9-8.71-30.2-13.1-54.7-13.1-21.4 0-37.7 3.99-49 12-11.1 7.85-16.7 19.3-16.7 34.4 0 13.1 4.2 23.4 12.6 30.9 8.4 7.48 26.2 14.9 53.5 22.3l37.6 10.1c27.5 7.48 47.1 17 58.8 28.7 11.8 11.5 17.7 27 17.7 46.5 0 26.6-9.42 46.7-28.3 60.2s-46.9 20.2-84.2 20.2c-16.7 0-33.7-1.53-51-4.6-18-3-35-7-53-13z"/>
<use xlink:href="#VectorLarge" transform="matrix(.17 0 0 .17 392 313)"/>
<use xlink:href="#RasterLarge" transform="matrix(.173 0 0 .173 -8.25 314)" height="100%" width="100%" y="0" x="0"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

-350
View File
@@ -1,350 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# ocrmypdf documentation build configuration file, created by
# sphinx-quickstart on Sun Sep 4 14:29:43 2016.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.mathjax',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'ocrmypdf'
copyright = '2016, James R. Barlow'
author = 'James R. Barlow'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
from ocrmypdf import VERSION as OCRMYPDF_VERSION
_version_parts = OCRMYPDF_VERSION.split('.')
version = '.'.join(_version_parts[0:2])
# The full version, including alpha/beta/rc tags.
release = OCRMYPDF_VERSION
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#
# today = ''
#
# Else, today_fmt is used as the format for a strftime call.
#
today_fmt = '%Y-%m-%d'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
import sphinx_rtd_theme
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {
'display_version': False
}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents.
# "<project> v<release> documentation" by default.
#
# html_title = 'ocrmypdf v4.2'
# A shorter title for the navigation bar. Default is the same as html_title.
#
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#
# html_logo = None
# The name of an image file (relative to this directory) to use as a favicon of
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#
# html_extra_path = []
# If not None, a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
# The empty string is equivalent to '%b %d, %Y'.
#
# html_last_updated_fmt = None
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#
# html_additional_pages = {}
# If false, no module index is generated.
#
# html_domain_indices = True
# If false, no index is generated.
#
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh'
#
# html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# 'ja' uses this config value.
# 'zh' user can custom change `jieba` dictionary path.
#
# html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#
# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'ocrmypdfdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'ocrmypdf.tex', 'ocrmypdf Documentation',
'James R. Barlow', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#
# latex_use_parts = False
# If true, show page references after internal links.
#
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#
# latex_appendices = []
# It false, will not define \strong, \code, itleref, \crossref ... but only
# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added
# packages.
#
# latex_keep_old_macro_names = True
# If false, no module index is generated.
#
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'ocrmypdf', 'ocrmypdf Documentation',
[author], 1)
]
# If true, show URL addresses after external links.
#
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'ocrmypdf', 'ocrmypdf Documentation',
author, 'ocrmypdf', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#
# texinfo_appendices = []
# If false, no module index is generated.
#
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#
# texinfo_no_detailmenu = False
-139
View File
@@ -1,139 +0,0 @@
Cookbook
========
Basic examples
--------------
Help!
"""""
ocrmypdf has built-in help.
.. code-block:: bash
ocrmypdf --help
Add an OCR layer and convert to PDF/A
"""""""""""""""""""""""""""""""""""""
.. code-block:: bash
ocrmypdf input.pdf output.pdf
Add an OCR layer and output a standard PDF
""""""""""""""""""""""""""""""""""""""""""
.. code-block:: bash
ocrmypdf --output-type pdf input.pdf output.pdf
Modify a file in place
""""""""""""""""""""""
The file will only be overwritten if OCRmyPDF is successful.
.. code-block:: bash
ocrmypdf myfile.pdf myfile.pdf
Correct page rotation
"""""""""""""""""""""
OCR will attempt to automatic correct the rotation of each page. This can help fix a scanning job that contains a mix of landscape and portrait pages.
.. code-block:: bash
ocrmypdf --rotate-pages myfile.pdf myfile.pdf
You can increase (decrease) the parameter ``--rotate-pages-threshold`` to make page rotation more (less) aggressive.
OCR images, not PDFs
--------------------
Use a program like `img2pdf <https://gitlab.mister-muffin.de/josch/img2pdf>`_ to convert your images to PDFs, and then pipe the resutls to run ocrmypdf:
.. code-block:: bash
img2pdf my-images*.jpg | ocrmypdf - myfile.pdf
If given a single image as input, OCRmyPDF will try converting it to a PDF on its own. This feature may be removed at some point, because OCRmyPDF does not specialize in converting images to PDFs.
You can also use Tesseract 3.04+ directly to convert single page images or multi-page TIFFs to PDF:
.. code-block:: bash
tesseract my-image.jpg output-prefix pdf
Image processing
----------------
OCRmyPDF perform some image processing on each page of a PDF, if desired. The same processing is applied to each page. It is suggested that the user review files after image processing as these commands might remove desirable content, especially from poor quality scans.
* ``--rotate-pages`` attempts to determine the correct orientation for each page and rotates the page if necessary.
* ``--remove-background`` attempts to detect and remove a noisy background from grayscale or color images. Monochrome images are ignored. This should not be used on documents that contain color photos as it may remove them.
* ``--deskew`` will correct pages were scanned at a skewed angle by rotating them back into place. Skew determination and correction is performed using `Postl's variance of line sums <http://www.leptonica.com/skew-measurement.html>`_ algorithm as implemented in `Leptonica <http://www.leptonica.com/index.html>`_.
* ``--clean`` uses `unpaper <https://www.flameeyes.eu/projects/unpaper>`_ to clean up pages before OCR, but does not alter the final output. This makes it less likely that OCR will try to find text in background noise.
* ``--clean-final`` uses unpaper to clean up pages before OCR and inserts the page into the final output. You will want to review each page to ensure that unpaper did not remove something important.
OCR and correct document skew (crooked scan)
""""""""""""""""""""""""""""""""""""""""""""
.. code-block:: bash
ocrmypdf --deskew input.pdf output.pdf
Hot (watched) folders
---------------------
To set up a "hot folder" that will trigger an OCR operation for every file inserted, use a program like Python `watchdog <https://pypi.python.org/pypi/watchdog>`_ (supports all major OS).
.. code-block:: bash
pip install watchdog
watchdog installs the command line program ``watchmedo``, which can be told to run ``ocrmypdf`` on any .pdf added to the current directory (``.``) and place the result in the previously created ``out/`` folder.
.. code-block:: bash
cd hot-folder
mkdir out
watchmedo shell-command \
--patterns="*.pdf" \
--command='ocrmypdf "${watch_src_path}" "out/${watch_src_path}" ' \
. # don't forget the final dot
For more complex behavior you can write a Python script around to use the watchdog API.
On file servers, you could configure watchmedo as a system service so it will run all the time.
Caveats
"""""""
* ``watchmedo`` may not work properly on a networked file system, depending on the capabilities of the file system client and server.
* This simple recipe does not filter for the type of file system event, so file copies, deletes and moves, and directory operations, will all be sent to ocrmypdf, producing errors in several cases. Disable your watched folder if you are doing anything other than copying files to it.
Batch jobs
----------
Consider using the excellent `GNU Parallel <https://www.gnu.org/software/parallel/>`_ to apply OCRmyPDF to multiple files at once.
Both ``parallel`` and ``ocrmypdf`` will try to use all available processors. To maximize parallelism without overloading your system with processes, consider using ``parallel -j 2`` to limit parallel to running two jobs at once.
This command will run all ocrmypdf all files named ``*.pdf`` in the current directory and write them to the previous created ``output/`` folder.
.. code-block:: bash
parallel -j 2 ocrmypdf '{}' 'output/{}' ::: *.pdf
If you have thousands of files to work with, contact the author.
-33
View File
@@ -1,33 +0,0 @@
Common error messages
=====================
Page already has text
---------------------
.. code::
ERROR - 1: page already has text! aborting (use --force-ocr to force OCR)
You ran ocrmypdf on a file that already contains printable text or a hidden OCR text layer (it can't quite tell the difference). You probably don't want to do this, because the file is already searchable.
As the error message suggests, your options are:
- ``ocrmypdf --force-ocr`` to :ref:`rasterize <raster-vector>` all vector content and run OCR on the images. This is useful if a previous OCR program failed, or if the document contains a text watermark.
- ``ocrmypdf --skip-text`` to skip OCR and other processing on any pages that contain text. Text pages will be copied into the output PDF without modification.
Input file 'filename' is not a valid PDF
----------------------------------------
OCRmyPDF passes files through qpdf, a program that fixes errors in PDFs, before it tries to work on them. In most cases this happens because the PDF is corrupt and
truncated (incomplete file copying) and not much can be done.
You can try rewriting the file with Ghostscript or pdftk:
- ``gs -o output.pdf -dSAFER -sDEVICE=pdfwrite input.pdf``
- ``pdftk input.pdf cat output output.pdf``
Sometimes Acrobat can repair PDFs with its `Preflight tool <https://helpx.adobe.com/acrobat/using/correcting-problem-areas-preflight-tool.html>`_.
-33
View File
@@ -1,33 +0,0 @@
.. ocrmypdf documentation master file, created by
sphinx-quickstart on Sun Sep 4 14:29:43 2016.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
OCRmyPDF documentation
======================
OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to
be searched.
PDFs are the best format for scanned documents. Unfortunately, PDFs can be difficult to work with. OCRmyPDF makes it easy to apply image processing and OCR to existing PDFs.
Contents:
.. toctree::
:maxdepth: 2
introduction
installation
languages
cookbook
errors
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
-269
View File
@@ -1,269 +0,0 @@
Installation
============
Installing on Debian and Ubuntu
-------------------------------
Users of Debian 9 ("stretch") or later or Ubuntu 16.10 or later may simply
.. code-block:: bash
apt-get install ocrmypdf
.. _Docker:
Installing the Docker image
---------------------------
For many users, installing the Docker image will be easier than installing all of OCRmyPDF's dependencies. For Windows, it is the only option.
If you have `Docker <https://docs.docker.com/>`_ installed on your system, you can install
a Docker image of the latest release.
Follow the Docker installation instructions for your platform. If you can run this command
successfully, your system is ready to download and execute the image:
.. code-block:: bash
docker run hello-world
OCRmyPDF will use all available CPU cores. By default, the VirtualBox machine instance on Windows and OS X has only a single CPU core enabled. Use the VirtualBox Manager to determine the name of your Docker engine host, and then follow these optional steps to enable multiple CPUs:
.. code-block:: bash
# Optional step for Mac OS X users
docker-machine stop "yourVM"
VBoxManage modifyvm "yourVM" --cpus 2 # or whatever number of core is desired
docker-machine start "yourVM"
eval $(docker-machine env "yourVM")
Assuming you have a Docker engine running somewhere, you can run these commands to download
the image:
.. code-block:: bash
docker pull jbarlow83/ocrmypdf
Then tag it to give a more convenient name, just ocrmypdf:
.. code-block:: bash
docker tag jbarlow83/ocrmypdf ocrmypdf
.. _docker-polyglot:
This image contains language packs for English, French, Spanish and German. The alternative "polyglot" image provides `all available language packs <https://github.com/tesseract-ocr/tesseract/blob/master/doc/tesseract.1.asc#languages>`_:
.. code-block:: bash
# Alternative step: If you need all language packs
docker pull jbarlow83/ocrmypdf-polyglot
docker tag jbarlow83/ocrmypdf-polyglot ocrmypdf
You can then run ocrmypdf using the command:
.. code-block:: bash
docker run --rm ocrmypdf --help
To execute the OCRmyPDF on a local file, you must `provide a writable volume to the Docker image <https://docs.docker.com/userguide/dockervolumes/>`_, and both the input and output file must be inside the writable volume. This example command uses the current working directory as the writable volume:
.. code-block:: bash
docker run --rm -v "$(pwd):/home/docker" <other docker arguments> ocrmypdf <your arguments to ocrmypdf>
In this worked example, the current working directory contains an input file called ``test.pdf`` and the output will go to ``output.pdf``:
.. code-block:: bash
docker run --rm -v "$(pwd):/home/docker" ocrmypdf --skip-text test.pdf output.pdf
Note that ``ocrmypdf`` has its own separate ``-v VERBOSITYLEVEL`` argument to control debug verbosity. All Docker arguments should before the ``ocrmypdf`` image name and all arguments to ``ocrmypdf`` should be listed after.
Installing on macOS (formerly Mac OS X)
---------------------------------------
These instructions probably work on all macOS supported by Homebrew. OCRmyPDF is known to work on Yosemite and El Capitan, and regularly tested on El Capitan.
If it's not already present, `install Homebrew <http://brew.sh/>`_.
Update Homebrew:
.. code-block:: bash
brew update
Install or upgrade the required Homebrew packages, if any are missing:
.. code-block:: bash
brew install libpng openjpeg jbig2dec libtiff # image libraries
brew install qpdf
brew install ghostscript
brew install python3
brew install libxml2 libffi leptonica
brew install unpaper # optional
Install the required Tesseract OCR engine with the language packs you plan to use:
.. code-block:: bash
brew install tesseract # Option 1: for English, French, German, Spanish
.. _macos-all-languages:
.. code-block:: bash
brew install tesseract --with-all-languages # Option 2: for all language packs
Update the homebrew pip and install Pillow:
.. code-block:: bash
pip3 install --upgrade pip
pip3 install --upgrade pillow
You can then install OCRmyPDF from PyPI:
.. code-block:: bash
pip3 install ocrmypdf
The command line program should now be available:
.. code-block:: bash
ocrmypdf --help
Installing on Ubuntu 14.04 LTS
------------------------------
Installing on Ubuntu 14.04 LTS (trusty) is more difficult than some other options, because of bugs in Python package installation.
Add new "apt" repositories needed for backports of Ghostscript 9.16 and libav-11, which supports unpaper 6.1. This will replace Ghostscript on your system.
.. code-block:: bash
sudo add-apt-repository ppa:vshn/ghostscript -y
sudo add-apt-repository ppa:heyarje/libav-11 -y
Update apt-get:
.. code-block:: bash
sudo apt-get update
sudo apt-get upgrade
Install system dependencies:
.. code-block:: bash
sudo apt-get install \
zlib1g-dev \
libjpeg-dev \
libffi-dev \
libavformat56 libavcodec56 libavutil54 \
ghostscript \
tesseract-ocr \
qpdf \
python3-pip \
python3-pil \
python3-pytest \
python3-reportlab
If you wish install OCRmyPDF to the system Python, then install as follows (note this installs new packages
into your system Python, which could interfere with other programs):
.. code-block:: bash
sudo pip3 install ocrmypdf
If you wish to install OCRmyPDF to a virtual environment to isolate the system Python, you can
follow these steps. This includes a workaround `for a known, unresolved issue in Ubuntu 14.04's ensurepip
package <http://www.thefourtheye.in/2014/12/Python-venv-problem-with-ensurepip-in-Ubuntu.html>`_:
.. code-block:: bash
sudo apt-get install python3-venv
python3 -m venv venv-ocrmypdf --without-pip
source venv-ocrmypdf/bin/activate
wget -O - -o /dev/null https://bootstrap.pypa.io/get-pip.py | python
deactivate
python3 -m venv --system-site-packages venv-ocrmypdf
source venv-ocrmypdf/bin/activate
pip install ocrmypdf
These installation instructions omit the optional dependency ``unpaper``, which is only available at version 0.4.2 in Ubuntu 14.04. The author could not find a backport of ``unpaper``, and created a .deb package to do the job of installing unpaper 6.1 (for x86 64-bit only):
.. code-block:: bash
wget -q https://dl.dropboxusercontent.com/u/28971240/unpaper_6.1-1.deb -O unpaper_6.1-1.deb
sudo dpkg -i unpaper_6.1-1.deb
Installing on Windows
---------------------
Direct installation on Windows is not possible. Install the _`Docker` container as described above. Ensure that your command prompt can run the docker "hello world" container.
Running on Windows
~~~~~~~~~~~~~~~~~~
The command line syntax to run ocrmypdf from a command prompt will resemble:
.. code-block:: bat
docker run -v /c/Users/sampleuser:/home/docker ocrmypdf --skip-text test.pdf output.pdf
where /c/Users/sampleuser is a Unix representation of the Windows path C:\\Users\\sampleuser, assuming a user named "sampleuser" is running ocrmypdf on a file in their home directory, and the files "test.pdf" and "output.pdf" are in the sampleuser folder. The Windows user must have read and write permissions.
Installing HEAD revision from sources
-------------------------------------
If you have ``git`` and ``python3.4`` or ``python3.5`` installed, you can install from source. When the ``pip`` installer runs,
it will alert you if dependencies are missing.
To install the HEAD revision from sources in the current Python 3 environment:
.. code-block:: bash
pip3 install git+https://github.com/jbarlow83/OCRmyPDF.git
Or, to install in `development mode <https://pythonhosted.org/setuptools/setuptools.html#development-mode>`_, allowing customization of OCRmyPDF, use the ``-e`` flag:
.. code-block:: bash
pip3 install -e git+https://github.com/jbarlow83/OCRmyPDF.git
On certain Linux distributions such as Ubuntu, you may need to use
run the install command as superuser:
.. code-block:: bash
sudo pip3 install [-e] git+https://github.com/jbarlow83/OCRmyPDF.git
Note that this will alter your system's Python distribution. If you prefer
to not install as superuser, you can install the package in a Python virtual environment:
.. code-block:: bash
git clone -b master https://github.com/jbarlow83/OCRmyPDF.git
python3 -m venv
source venv/bin/activate
cd OCRmyPDF
pip3 install .
However, ``ocrmypdf`` will only be accessible on the system PATH after
you activate the virtual environment.
To run the program:
.. code-block:: bash
ocrmypdf --help
If not yet installed, the script will notify you about dependencies that
need to be installed. The script requires specific versions of the
dependencies. Older version than the ones mentioned in the release notes
are likely not to be compatible to OCRmyPDF.
-71
View File
@@ -1,71 +0,0 @@
Introduction
============
OCRmyPDF is a Python 3 package that adds OCR layers to PDFs.
About OCR
---------
`Optical character recognition <https://en.wikipedia.org/wiki/Optical_character_recognition>`_ is technology that converts images of typed or handwritten text, such as in a scanned document, to computer text that can be searched and copied.
OCRmyPDF uses `Tesseract <https://github.com/tesseract-ocr/tesseract>`_, the best available open source OCR engine, to perform OCR.
.. _raster-vector:
About PDFs
----------
PDFs are page description files that attempts to preserve a layout exactly. They can contain `vector graphic files <http://vector-conversions.com/vectorizing/raster_vs_vector.html>`_ that can contain raster objects such as scanned images. Because PDFs can contain multiple pages (unlike many image formats) and can contain fonts and text, it is a good formats for exchanging scanned documents.
.. image:: bitmap_vs_svg.svg
A PDF page might contain multiple images, even if it only appears to have one image. Some scanners or scanning software will segment pages into monochromatic text and color regions for example, to improve the compression ratio and appearance of the page.
Rasterizing a PDF is the process of generating an image suitable for display or analyzing with an OCR engine. OCR engines like Tesseract work with images, not vector objects.
About PDF/A
-----------
`PDF/A <https://en.wikipedia.org/wiki/PDF/A>`_ is a standardized subset of the full PDF specification that is designed for archiving. PDF/A differs from PDF primarily by omitting features that would make it difficult to read the file in the future, such as embedded Javascript or references to external fonts. All fonts and resources needed to interpret the PDF must be contained within it. Generally speaking, scanned documents should be converted to PDF/A. There are various conformance levels and versions, such as "PDF/A-2b".
Since most people who scan documents are interested in reading them in the future, OCRmyPDF generates PDF/A-2b by default.
PDF/A has a few drawbacks. Some PDF viewers include an alert that the file is a PDF/A, which may confuse some users. It also tends to produce larger files than PDF, because it embeds certain resources even if they are commonly available.
What OCRmyPDF does
------------------
OCRmyPDF analyzes each page of a PDF to determine the colorspace and resolution (DPI) needed to capture all of the information on that page without losing content. It uses `Ghostscript <http://ghostscript.com/>`_ to rasterize the page, and then performs on OCR on the rasterized image. It is not enough to simply extract the images from each page and run OCR on them individually. Of course one could use Ghostscript or another PDF rasterizer and then pass the image to Tesseract. OCRmyPDF automates this process and produces a minimally changed output file that contains the same information, colorspace and resolution.
The Tesseract OCR engine can output 'hOCR' files, which are XML files that contain a description of the text it found on the page. OCRmyPDF will render a new PDF that contains only the hidden text layer, and merge this with the original page.
Alternately, OCRmyPDF can use the Tesseract OCR engine to directly output PDFs for each page, then merge them.
By default, OCRmyPDF will convert the file to a PDF/A. This behavior can be disabled with the ``--output-type pdf`` argument.
Depending on the settings selected, OCRmyPDF may "graft" the OCR layer into the existing PDF, or reconstruct a visually equivalent new PDF.
Limitations
-----------
OCRmyPDF is limited by the Tesseract OCR engine. As such it experiences these limitations, as do any other programs that rely on Tesseract:
* The OCR is not as accurate as commercial solutions such as Abbyy.
* It is not capable of recognizing handwriting.
* It may find gibberish and report this as OCR output.
* If a document contains languages outside of those given in the ``-l LANG`` arguments, results may be poor.
* It is not always good at analyzing the natural reading order of documents. For example, it may fail to recognize that a document contains two columns and join text across the columns.
* Poor quality scans may produce poor quality OCR. Garbage in, garbage out.
OCRmyPDF is also limited by the PDF specification:
* PDF encodes the position of text glyphs but does not encode document structure. There is no markup that divides a document in sections, paragraphs, sentences, or even words (since blank spaces are not represented). As such all elements of document structure including the spaces between words must be derived heuristically. Some PDF viewers do a better job of this than others.
Ghostscript also imposes some limitations:
* PDFs containing JBIG2-encoded content will be converted to CCITT Group4 encoding, which has lower compression ratios, if Ghostscript PDF/A is enabled.
OCRmyPDF is currently not designed to be used as a Python API; it is designed to be run as a command line tool. ``import ocrmypf`` currently attempts to process the command line on ``sys.argv`` at import time so it has side effects that will interfere with its use as a package. The API it presents should not be considered stable.
-46
View File
@@ -1,46 +0,0 @@
Installing additional language packs
====================================
OCRmyPDF uses Tesseract for OCR, and relies on its language packs for languages other than English.
Tesseract supports `most languages <https://github.com/tesseract-ocr/tesseract/blob/master/doc/tesseract.1.asc#languages>`_.
For Linux users, you can often find packages that provide language packs:
Debian and Ubuntu users
-----------------------
.. code-block:: bash
# Display a list of all Tesseract language packs
apt-cache search tesseract-ocr
# Debian/Ubuntu users
apt-get install tesseract-ocr-chi-sim # Example: Install Chinese Simplified language back
You can then pass the ``-l LANG`` argument to OCRmyPDF to give a hint as to what languages it should search for. Multiple
languages can be requested.
Mac OS X (macOS) users
----------------------
You can install additional language packs by :ref:`installing Tesseract using Homebrew with all language packs <macos-all-languages>`.
Docker users
------------
Users of the Docker image may use the alternative :ref:`"polyglot" container <docker-polyglot>` which includes all languages.
Known limitations
-----------------
As of v4.2, users of ocrmypdf working languages outside the Latin alphabet should use the following syntax:
.. code-block:: bash
ocrmypdf --output-type pdf --pdf-renderer tesseract
The reasons for this are:
* The latest version of Ghostscript (9.19 as of this writing) has unfixed bugs in Unicode handling that generate invalid character maps, so Ghostscript cannot be used for PDF/A conversion
* The default "hocr" PDF renderer does not handle Asian fonts properly
+502
View File
@@ -0,0 +1,502 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!
+17
View File
@@ -0,0 +1,17 @@
JHOVE - JSTOR/Harvard Object Validation Environment
Copyright 2003-2008 by JSTOR and the President and Fellows of Harvard College
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lessor General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This program 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
+227
View File
@@ -0,0 +1,227 @@
JHOVE - JSTOR/Harvard Object Validation Environment
Copyright 2003-2012 by JSTOR and the President and Fellows of Harvard College
JHOVE is made available under the GNU Lesser General Public License (LGPL;
see the file LICENSE for details)
Rev. 1.9, 2012-12-17
JHOVE (the JSTOR/Harvard Object Validation Environment, pronounced "jhove")
is an extensible software framework for performing format identification,
validation, and characterization of digital objects.
o Format identification is the process of determining the format to which a
digital object conforms: "I have a digital object; what format is it?"
o Format validation is the process of determining the level of compliance of a
digital object to the specification for its purported format: "I have an
object purportedly of format F; is it?"
o Format characterization is the process of determing the format-specific
significant properties of an object of a given format: "I have an object of
format F; what are its salient properties?"
These actions are frequently necessary during routine operation of digital
repositories and for digital preservation activities.
The output from JHOVE is controlled by output handlers. JHOVE uses an
extensible plug-in architecture; it can be configured at the time of its
invocation to include whatever specific format modules and output handlers
that are desired. The initial release of JHOVE includes modules for
arbitrary byte streams, ASCII and UTF-8 encoded text, AIFF and WAVE audio,
GIF, JPEG, JPEG 2000, TIFF, and PDF; and text and XML output handlers.
The JHOVE project is a collaboration of JSTOR and the Harvard University
Library. Development of JHOVE was funded in part by the Andrew W. Mellon
Foundation. JHOVE is made available under the GNU Lesser General Public
License (LGPL; see the file LICENSE for details).
JHOVE is currently being maintained by indpendent developers.
REQUIREMENTS
1. Java J2SE 1.5
(JHOVE was originally implemented using the Sun J2SE SDK 1.4.1 and has
been tested to work with 1.5)
2. If you would like to compile the JHOVE source code, then
Apache Ant, a Java-based build tool <http://ant.apache.org/> is necessary.
Note that the JAVA_HOME environment variable must be set appropriately for
Ant to work properly.
(JHOVE was implemented and tested using Ant 1.5.1.)
DISTRIBUTION
The JHOVE distribution package includes:
jhove/ # JHOVE home directory
COPYING # GNU Lesser General Public License
LICENSE # JHOVE license information
README
RELEASENOTES # JHOVE release notes
bin/
jhove.jar # JHOVE API package
jhove-handler.jar # Standard output handler package
jhove-module.jar # Standard module package
JhoveApp.jar # JHOVE command line application
JhoveView.jar # JHOVE with Swing GUI front-end
build.xml # Ant configuration file
classes/
build.xml # Ant configuration file
edu/ ... # JHOVE API packages
ADump.* # AIFF dump utility class
GDump.* # GIF dump utility class
Jhove.* # JHOVE main class
JDump.* # JPEG dump utility class
J2Dump.* # JPEG 2000 dump utility class
PDump.* # PDF dump utility class
TDump.* # TIFF dump utility class
UserHome.* # user.home property utility class
WDump.* # WAVE dump utility class
conf/
jhove.conf # JHOVE configuration file
jhove.xsd # JHOVE output schema
jhoveConfig.xsd # JHOVE configuration file schema
doc/
*.html # API documentation
...
examples/ # Sample files
ascii/ ...
gif/ ...
jpeg/ ...
jpeg2000/ ...
pdf/ ...
tiff/ ...
utf-8/ ...
adump* # AIFF dump Bourne shell driver
adump.bat* # AIFF dump DOS shell driver script
gdump* # GIF dump Bourne shell driver
gdump.bat* # GIF dump DOS shell driver script
jdump* # JPEG dump Bourne shell driver
jdump.bat* # JPEG dump DOS shell driver script
j2dump* # JPEG 2000 dump Bourne shell driver
j2dump.bat* # JPEG 2000 dump DOS shell driver
jhove.tmpl* # Template for JHOVE Bourne shell driver script
jhove_bat.tmpl* # Template for JHOVE DOS shell driver script
pdump* # PDF dump Bourne shell driver
pdump.bat* # PDF dump DOS shell driver script
tdump* # TIFF dump Bourne shell driver
tdump.bat* # TIFF dump DOS shell driver script
userhome* # user.home Bourne shell driver
userhome.bat* # user.home DOS shell driver script
wdump* # WAVE dump Bourne shell driver
wdump.bat* # WAVE dump DOS shell driver script
INSTALLATION
Edit the configuration file, jhove/conf/jhove.conf, and set the absolute
pathname of the JHOVE home directory and the temporary directory (in which
temporary files are created):
<jhoveHome>jhove-home-directory</jhoveHome>
<tempDirectory>temporary-directory</tempDirectory>
The JHOVE home directory is the top-most directory in the distribution TAR
or ZIP file. On Unix systems, "/var/tmp" is an appropriate temporary
directory; on Windows, "C:\Temp". For example, if the distribution TAR
file is disaggregated on a Unix system in the directory "/users/stephen/
projects", then the configuration file should read:
<jhoveHome>/users/stephen/projects/jhove</jhoveHome>
<tempDirectory>/var/tmp</jhoveHome>
In the JHOVE home directory, copy the JHOVE Bourne shell driver script
template, "jhove.tmpl", to "jhove" (or the equivalent Windows shell
script, "jhove_bat.tmpl" to "jhove.bat"), and set the
JHOVE home directory, Java home directory, and Java interpreter:
JHOVE_HOME=jhove-home-directory
JAVA_HOME=java-home-directory
JAVA=java-interpreter
The JAVA_HOME property should provide the absolute pathname of the Java
runtime or SDK installation; JAVA should provide the absolute pathname of the
Java interpreter. For example:
JHOVE_HOME=/users/stephen/projects/jhove
JAVA_HOME=/usr/local/j2re1.4.1_02
JAVA=$JAVA_HOME/bin/java
In the DOS shell driver script, jhove.bat, the equivalent three
variables are:
SET JHOVE_HOME=jhove-home-directory
SET JAVA_HOME=java-home-directory
SET JAVA=%JAVA_HOME%\bin\java
For example:
SET JHOVE_HOME="C:\Program Files\jhove"
SET JAVA_HOME="C:\Program Files\java\j2re1.4.1_02"
SET JAVA=%JAVA_HOME%\bin\java
The quotation marks are necessary because of the embedded space characters.
On Windows platforms it may also be necessary to add the Java bin subdirectory
to the System PATH environment variable:
PATH=C:\Program Files\java\j2re1.4.1_02\bin;...
(For information on setting a Windows environment variable, consult your local
documentation or system administrator.)
USAGE
java Jhove [-c config] [-m module] [-h handler] [-e encoding] [-H handler]
[-o output] [-x saxclass] [-t tempdir] [-b bufsize]
[-l loglevel] [[-krs] dir-file-or-uri [...]]
where -c config Configuration file pathname
-m module Module name
-h handler Output handler name (defaults to TEXT)
-e encoding Character encoding used by output handler (defaults to UTF-8)
-H handler About handler name
-o output Output file pathname (defaults to standard output)
-x saxclass SAX parser class (defaults to J2SE default)
-t tempdir Temporary directory in which to create temporary files
-b bufsize Buffer size for buffered I/O (defaults to J2SE 1.4 default)
-l loglevel Logging level
-k Calculate CRC32, MD5, and SHA-1 checksums
-r Display raw data flags, not textual equivalents
-s Format identification based on internal signatures only
dir-file-or-uri Directory or file pathname or URI of formated content
stream
All named modules and output handlers must be found on the Java CLASSPATH at
the time of invocation. The JHOVE driver script, jhove/jhove, automatically
sets the CLASSPATH and invokes the Jhove main class:
jhove [-c config] [-m module] [-h handler] [-e encoding] [-H handler]
[-o output] [-x saxclass] [-t tempdir] [-b bufsize] [-l loglevel]
[[-krs] dir-file-or-uri [...]]
The following additional programs are available, primarily for testing
and debugging purposes. They display a minimally processed, human-readable
version of the contents of AIFF, GIF, JPEG, JPEG 2000, PDF, TIFF, and WAVE
files:
java ADump aiff-file
java GDump gif-file
java JDump jpeg-file
java J2Dump jpeg2000-file
java PDump pdf-file
java TDump tiff-file
java WDump wave-file
For convenience, the following driver scripts are also available:
adump aiff-file
gdump gif-file
jdump jpeg-file
j2dump jpeg2000-file
pdump pdf-file
tdump tiff-file
wdump wave-file
The JHOVE Swing-based GUI interface can be invoked from a command shell from
the jhove/bin sub-directory:
java -jar JhoveView.jar -c <configFile>
where <configFile> is the pathname of the JHOVE configuration file.
+1676
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
+25
View File
@@ -0,0 +1,25 @@
JHOVE - JSTOR/Harvard Object Validation Environment
Copyright 2003 by JSTOR and the President and Fellows of Harvard College
JHOVE is made available under the GNU General Public License (see the file
LICENSE for details)
Rev. 2003-11-25
The following jar files are meant to be used for embedding JHOVE functionality
into new applications or systems.
jhove.jar Contains the JHOVE API interfaces and classes
jhove-module.jar Contains the standard JHOVE modules ()
jhove-handler.jar Contains the standard JHOVE output handlers (TEXT and XML)
The following jar file is meant to be used with the stand-alone JHOVE
application using a command-line interface. It contains the main Jhove class
and the contents of jhove.jar, jhove-module.jar, and jhove-handler.jar.
JhoveApp.jar
The following jar file is meant to be used with the stand-alone JHOVE
application using a Swing GUI interface. It contains the main JhoveView class
and the contents of jhove.jar, jhove-module.jar, and jhove-handler.jar.
JhoveView.jar
Binary file not shown.
Binary file not shown.
Binary file not shown.
+78
View File
@@ -0,0 +1,78 @@
<project name="Jhove" default="dist" basedir=".">
<description>Project build file
Jhove - JSTOR/Harvard Object Validation Environment
Version 1.0 2004-09-10
Copyright 2004 by JSTOR and the President and Fellows of Harvard College
</description>
<!-- ant (or ant dist) Build everything
ant debug Build everything with debug enabled
ant clean Delete backup files
ant cleanclass Delete backup and class files
ant cleandist Delete backup, class, and jar files
ant javadoc Build javadocs
-->
<!-- set global properties for this build -->
<property name="bin" location="bin"/>
<property name="classes" location="classes"/>
<property name="doc" location="doc"/>
<target name="dist" description="Create distribution">
<ant dir="${classes}" inheritAll="false">
<property name="dbg" value="off"/>
</ant>
<chmod file="jhove" perm="ugo+x"/>
<chmod file="${bin}/JhoveView.jar" perm="ugo+x"/>
</target>
<target name="debug" description="Create distribution with debug enabled">
<ant dir="${classes}" inheritAll="false">
<property name="dbg" value="on"/>
</ant>
</target>
<target name="view" description="Create JhoveView application">
<ant dir="${classes}" target="view" inheritAll="false">
<property name="dbs" value="on"/>
</ant>
</target>
<target name="clean" description="Delete backup files">
<ant dir="${classes}" target="main-clean" inheritAll="false"/>
</target>
<target name="cleanclass" depends="clean">
<ant dir="${classes}" target="main-cleanclass" inheritAll="false"/>
</target>
<target name="cleandist" depends="cleanclass">
<delete file="${bin}/jhove.jar"/>
<delete file="${bin}/jhove-handler.jar"/>
<delete file="${bin}/jhove-module.jar"/>
<delete file="${bin}/JhoveApp.jar"/>
<delete file="${bin}/JhoveView.jar"/>
</target>
<target name="javadoc">
<javadoc sourcepath="${classes}" destdir="${doc}"
windowtitle="JHOVE Documentation"
Overview="${classes}/overview.html">
<package name="edu.harvard.hul.ois.jhove"/>
<package name="edu.harvard.hul.ois.jhove.handler"/>
<package name="edu.harvard.hul.ois.jhove.handler.audit"/>
<package name="edu.harvard.hul.ois.jhove.module"/>
<package name="edu.harvard.hul.ois.jhove.module.aiff"/>
<package name="edu.harvard.hul.ois.jhove.module.gif"/>
<package name="edu.harvard.hul.ois.jhove.module.html"/>
<package name="edu.harvard.hul.ois.jhove.module.iff"/>
<package name="edu.harvard.hul.ois.jhove.module.jpeg"/>
<package name="edu.harvard.hul.ois.jhove.module.jpeg2000"/>
<package name="edu.harvard.hul.ois.jhove.module.pdf"/>
<package name="edu.harvard.hul.ois.jhove.module.tiff"/>
<package name="edu.harvard.hul.ois.jhove.module.wave"/>
<package name="edu.harvard.hul.ois.jhove.module.xml"/>
<package name="edu.harvard.hul.ois.jhove.viewer"/>
</javadoc>
</target>
</project>
+63
View File
@@ -0,0 +1,63 @@
JHOVE - JSTOR/Harvard Object Validation Environment
Copyright 2003-2007 by JSTOR and the President and Fellows of Harvard College
JHOVE is made available under the GNU General Public License (see the file
LICENSE for details)
Rev. 2007-08-30
Edit the configuration file, jhove.conf, and set the JHOVE home
directory:
<jhoveHome>jhove-home-directory</jhoveHome>
and temporary directory:
<tempDirectory>temporary-directory</tempDirectory>
On most Unix systems, a reasonable temporary directory is "/var/tmp";
on Windows, "C:\temp".
The optional
<bufferSize>buffer-size</bufferSize>
element defines the buffer size used for buffer I/O operations.
The optional
<mixVersion>1.0</mixVersion>
element specifies that the XML output handler should conform to the
MIX 1.0 schema. The default behavior is for handler output to conform
to the MIX 0.2 schema.
The optional
<sigBytes>n</sigBytes>
element specifies that JHOVE modules will look for format signatures
in the first <n> bytes of the file. The default value is 1024.
All class names must be fully qualified with their package name:
<module>
<class>fully-package-qualified-class-name</class>
<init>optional-initialization-argument</init>
<param>optional-invocation-argument</param>
</module>
The optional <init> argument is passed to the module once at the time
its class is instantiated. See module-specific documentation for a
description of any initialization options.
The optional <param> argument is passed to the module every time it is
invoked. See module-specific documentation for a description of any
invocation options.
The order in which format modules are defined is important; when
performing a format identification operation, JHOVE will search for a
matching module in the order in which the modules are defined in the
configuration file. In general, the modules for more generic formats
should come later in the list. For example, the standard module ASCII
should be defined before the UTF-8 module, since all ASCII objects
are, by definition, UTF-8 objects, but not vice versa.
+47
View File
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<jhoveConfig version="1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://hul.harvard.edu/ois/xml/ns/jhove/jhoveConfig"
xsi:schemaLocation="http://hul.harvard.edu/ois/xml/ns/jhove/jhoveConfig
http://hul.harvard.edu/ois/xml/xsd/jhove/1.4/jhoveConfig.xsd">
<jhoveHome>/users/stephen/projects/jhove</jhoveHome>
<defaultEncoding>utf-8</defaultEncoding>
<tempDirectory>/var/tmp</tempDirectory>
<bufferSize>131072</bufferSize>
<mixVersion>1.0</mixVersion>
<sigBytes>1024</sigBytes>
<module>
<class>edu.harvard.hul.ois.jhove.module.AiffModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.WaveModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.PdfModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.Jpeg2000Module</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.JpegModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.GifModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.TiffModule</class>
<param>byteoffset=true</param>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.XmlModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.HtmlModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.AsciiModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.Utf8Module</class>
</module>
</jhoveConfig>
+51
View File
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<jhoveConfig version="1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://hul.harvard.edu/ois/xml/ns/jhove/jhoveConfig"
xsi:schemaLocation="http://hul.harvard.edu/ois/xml/ns/jhove/jhoveConfig
http://hul.harvard.edu/ois/xml/xsd/jhove/1.4/jhoveConfig.xsd">
<jhoveHome>/users/stephen/projects/jhove</jhoveHome>
<defaultEncoding>utf-8</defaultEncoding>
<tempDirectory>/var/tmp</tempDirectory>
<bufferSize>131072</bufferSize>
<mixVersion>1.0</mixVersion>
<sigBytes>1024</sigBytes>
<module>
<class>edu.harvard.hul.ois.jhove.module.AiffModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.WaveModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.PdfModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.Jpeg2000Module</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.JpegModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.GifModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.TiffModule</class>
<param>byteoffset=true</param>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.XmlModule</class>
<param>withTextMD=true</param>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.HtmlModule</class>
<param>withTextMD=true</param>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.AsciiModule</class>
<param>withTextMD=true</param>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.Utf8Module</class>
<param>withTextMD=true</param>
</module>
</jhoveConfig>
+45
View File
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<jhoveConfig version="1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://hul.harvard.edu/ois/xml/ns/jhove/jhoveConfig"
xsi:schemaLocation="http://hul.harvard.edu/ois/xml/ns/jhove/jhoveConfig
http://hul.harvard.edu/ois/xml/xsd/jhove/1.4/jhoveConfig.xsd">
<jhoveHome>./jhove/</jhoveHome>
<defaultEncoding>utf-8</defaultEncoding>
<tempDirectory>/var/tmp</tempDirectory>
<bufferSize>131072</bufferSize>
<module>
<class>edu.harvard.hul.ois.jhove.module.AiffModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.WaveModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.PdfModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.Jpeg2000Module</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.JpegModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.GifModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.TiffModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.XmlModule</class>
<param>schema=http://www.example.com/schema;/home/schemas/exampleschema.xsd</param>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.HtmlModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.AsciiModule</class>
</module>
<module>
<class>edu.harvard.hul.ois.jhove.module.Utf8Module</class>
</module>
</jhoveConfig>
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/perl
########################################################################
# Jhove - JSTOR/Harvard Object Validation Environment
# Copyright 2004 by JSTOR and the President and Fellows of Harvard College
#
# A Perl script for plugging local path information into the
# various script files of JHOVE, as well as conf/jhove.conf.
#
# This is configured only for Unix (including OS X).
#
# Usage: configure.pl jhove_home_directory [java_home_directory [java_runtime_directory]]
#
# If invoked with no arguments, it will output a usage message.
#
########################################################################
use File::Copy;
sub mung {
my $f = $_[0];
my $bak = $f . "~";
#If there is no backup file, copy the file to the
#backup. Otherwise work from the backup.
if (!(-e $bak)) {
rename ($f, $bak);
}
open (INFILE, $bak);
open (OUTFILE, ">" . $f);
#Walks through each line of file, making substitutions.
#Remember that the JAVA_HOME and JAVA arguments are optional.
while (<INFILE>) {
s/^JHOVE_HOME=.*/JHOVE_HOME=$ARGV[0]/;
if ($narg >= 2) {
s/^JAVA_HOME=.*/JAVA_HOME=$ARGV[1]/;
}
if ($narg >= 3) {
s/^JAVA=.*/JAVA=$ARGV[2]/;
}
print OUTFILE;
}
close (INFILE);
close (OUTFILE);
if (-e $f) {
print ("Fixed " . $f . "\n");
}
}
$narg = $#ARGV + 1;
if ($narg <= 0) {
print "Usage: configure.pl jhove_home_directory [java_home_directory [java_runtime_directory]]\n";
exit;
}
print "JHOVE_HOME will be set to " . $ARGV[0] . "\n";
if ($narg >= 2) {
print "JAVA_HOME will be set to " . $ARGV[1] . "\n";
}
if ($narg >= 3) {
print "JAVA will be set to " . $ARGV[2] . "\n";
}
mung ("jhove");
mung ("adump");
mung ("gdump");
mung ("jdump");
mung ("j2dump");
mung ("pdump");
mung ("tdump");
mung ("wdump");
#Fix up the config file. We assume that the <jhoveHome>
#element is all on one line.
if (!(-e "conf/jhove.conf~")) {
rename ("conf/jhove.conf", "conf/jhove.conf~");
}
open (INFILE, "conf/jhove.conf~");
open (OUTFILE, ">conf/jhove.conf");
while (<INFILE>) {
s!<jhoveHome>.*</jhoveHome>!<jhoveHome>$ARGV[0]</jhoveHome>!;
print OUTFILE;
}
close (INFILE);
close (OUTFILE);
if (-e "conf/jhove.conf") {
print "Fixed conf/jhove.conf\n";
}
exit;
+37
View File
@@ -0,0 +1,37 @@
#!/bin/sh
########################################################################
# gdump - JSTOR/Harvard Object Validation Environment
# Copyright 2004-2005 by the President and Fellows of Harvard College
# JHOVE is made available under the GNU General Public License (see the
# file LICENSE for details)
#
# Driver script for the GIF dump utility
#
# Usage: gdump file
#
# where file is a GIF file
#
# Configuration constants:
JHOVE_HOME=/users/stephen/projects/jhove
JAVA_HOME=/usr/java # Java JRE directory
JAVA=$JAVA_HOME/bin/java # Java interpreter
EXTRA_JARS= # Extra .jar files to add to CLASSPATH
# NOTE: Nothing below this line should be edited
########################################################################
CP=${JHOVE_HOME}/bin/JhoveApp.jar:${EXTRA_JARS}
# Retrieve a copy of all command line arguments to pass to the application.
ARGS=""
for ARG do
ARGS="$ARGS $ARG"
done
# Set the CLASSPATH and invoke the Java loader.
${JAVA} -classpath $CP GDump $ARGS
+37
View File
@@ -0,0 +1,37 @@
#!/bin/sh
########################################################################
# j2dump - JSTOR/Harvard Object Validation Environment
# Copyright 2004-2005 by the President and Fellows of Harvard College
# JHOVE is made available under the GNU General Public License (see the
# file LICENSE for details)
#
# Driver script for the JPEG 2000 dump utility
#
# Usage: j2dump file
#
# where file is a JPEG file
#
# Configuration constants:
JHOVE_HOME=/users/stephen/projects/jhove
JAVA_HOME=/usr/java # Java JRE directory
JAVA=$JAVA_HOME/bin/java # Java interpreter
EXTRA_JARS= # Extra .jar files to add to CLASSPATH
# NOTE: Nothing below this line should be edited
########################################################################
CP=${JHOVE_HOME}/bin/JhoveApp.jar:${EXTRA_JARS}
# Retrieve a copy of all command line arguments to pass to the application.
ARGS=""
for ARG do
ARGS="$ARGS $ARG"
done
# Set the CLASSPATH and invoke the Java loader.
${JAVA} -classpath $CP J2Dump $ARGS
+37
View File
@@ -0,0 +1,37 @@
#!/bin/sh
########################################################################
# jdump - JSTOR/Harvard Object Validation Environment
# Copyright 2004-2005 by the President and Fellows of Harvard College
# JHOVE is made available under the GNU General Public License (see the
# file LICENSE for details)
#
# Driver script for the JPEG dump utility
#
# Usage: jdump file
#
# where file is a JPEG file
#
# Configuration constants:
JHOVE_HOME=/users/stephen/projects/jhove
JAVA_HOME=/usr/java # Java JRE directory
JAVA=$JAVA_HOME/bin/java # Java interpreter
EXTRA_JARS= # Extra .jar files to add to CLASSPATH
# NOTE: Nothing below this line should be edited
########################################################################
CP=${JHOVE_HOME}/bin/JhoveApp.jar:${EXTRA_JARS}
# Retrieve a copy of all command line arguments to pass to the application.
ARGS=""
for ARG do
ARGS="$ARGS $ARG"
done
# Set the CLASSPATH and invoke the Java loader.
${JAVA} -classpath $CP JDump $ARGS
+58
View File
@@ -0,0 +1,58 @@
#!/bin/sh
########################################################################
# JHOVE - JSTOR/Harvard Object Validation Environment
# Copyright 2003-2005 by JSTOR and the President and Fellows of Harvard College
# JHOVE is made available under the GNU General Public License (see the
# file LICENSE for details)
#
# Usage: jhove [-c config] [-m module] [-h handler] [-e encoding] [-H handler]
# [-o output] [-x saxclass] [-t tempdir] [-b bufsize]
# [-l loglevel] [[-krs] dir-file-or-uri [...]]
#
# where -c config Configuration file pathname
# -m module Module name
# -h handler Output handler name (defaults to TEXT)
# -e encoding Character encoding of output handler (defaults to UTF-8)
# -H handler About handler name
# -o output Output file pathname (defaults to standard output)
# -x saxclass SAX parser class (defaults to J2SE 1.4 default)
# -t tempdir Temporary directory in which to create temporary files
# -b bufsize Buffer size for buffered I/O (defaults to J2SE 1.4 default)
# -k Calculate CRC32, MD5, and SHA-1 checksums
# -r Display raw data flags, not textual equivalents
# -s Format identification based on internal signatures only
# dir-file-or-uri Directory, file pathname or URI of formatted content
#
# CHANGE for JHOVE 1.8:
# You no longer have to figure out where JAVA_HOME is; that's the
# operating system's job. If the OS tells you it can't find Java,
# adjust your shell's path or revert to the old way (commented out).
# Configuration constants:
#JHOVE_HOME=/users/gary/dev/jhove
JHOVE_HOME=[fill in path to jhove directory]
JAVA_HOME=/usr/java # Java JRE directory -- change to your local java home
JAVA=$JAVA_HOME/bin/java # Java interpreter -- usually won't need change
#XTRA_JARS=/users/stephen/xercesImpl.jar
EXTRA_JARS= # Extra .jar files to add to CLASSPATH
# NOTE: Nothing below this line should be edited
########################################################################
CP=${JHOVE_HOME}/bin/JhoveApp.jar:${EXTRA_JARS}
# Retrieve a copy of all command line arguments to pass to the application.
ARGS=""
for ARG do
ARGS="$ARGS $ARG"
done
# Set the CLASSPATH and invoke the Java loader.
#{JAVA} -classpath $CP Jhove $ARGS -x org.apache.xerces.parsers.SAXParser
#${JAVA} -classpath $CP Jhove $ARGS
# New way, doesn't require you to use JAVA_HOME.
java -classpath $CP Jhove $ARGS
+54
View File
@@ -0,0 +1,54 @@
#!/bin/sh
########################################################################
# JHOVE - JSTOR/Harvard Object Validation Environment
# Copyright 2003-2004 by JSTOR and the President and Fellows of Harvard College
# JHOVE is made available under the GNU General Public License (see the
# file LICENSE for details)
#
# Copy jhove.tmpl to jhove, and replace the value of JHOVE_HOME with
# the path to your jhove directory.
#
# Usage: jhove [-c config] [-m module [-p param]] [-h handler [-P param]]
# [-e encoding] [-H handler] [-o output] [-x saxclass]
# [-t tempdir] [-b bufsize] [[-krs] dir-file-or-uri [...]]
#
# where -c config Configuration file pathname
# -m module Module name
# -p param Module-specific parameter
# -h handler Output handler name (defaults to TEXT)
# -P param Handler-specific parameter
# -o output Output file pathname (defaults to standard output)
# -x saxclass SAX parser class (defaults to J2SE 1.4 default)
# -t tempdir Temporary directory in which to create temporary files
# -b bufsize Buffer size for buffered I/O (defaults to J2SE 1.4 default)
# -k Calculate CRC32, MD5, and SHA-1 checksums
# -r Display raw data flags, not textual equivalents
# -s Format identification based on internal signatures only
# dir-file-or-uri Directory, file pathname or URI of formatted content
#
# Configuration constants:
JHOVE_HOME=[your directory path]/jhove
JAVA_HOME=/usr/java
JAVA=/usr/bin/java
#XTRA_JARS=/users/stephen/xercesImpl.jar
EXTRA_JARS= # Extra .jar files to add to CLASSPATH
# NOTE: Nothing below this line should be edited
########################################################################
CP=${JHOVE_HOME}/bin/JhoveApp.jar:${EXTRA_JARS}
# Retrieve a copy of all command line arguments to pass to the application.
ARGS=""
for ARG do
ARGS="$ARGS $ARG"
done
# Set the CLASSPATH and invoke the Java loader.
#{JAVA} -classpath $CP Jhove $ARGS -x org.apache.xerces.parsers.SAXParser
${JAVA} -classpath $CP Jhove $ARGS
+63
View File
@@ -0,0 +1,63 @@
@ECHO OFF
REM JHOVE - JSTOR/Harvard Object Validation Environment
REM Copyright 2003-2005 by JSTOR and the President and Fellows of Harvard College
REM JHOVE is made available under the GNU General Public License (see the
REM file LICENSE for details)
REM
REM Usage: jhove [-c config] [-m module] [-h handler] [-e encoding]
REM [-H handler] [-o output] [-x saxclass] [-t tempdir]
REM [-b bufsize] [-l loglevel] [[-krs] dir-file-or-uri [...]]
REM
REM For Windows systems, copy jhove_bat.tmpl to jhove.bat and change
REM the value of JHOVE_HOME to the path to your jhove directory.
REM
REM where -c config Configuration file pathname
REM -m module Module name
REM -h handler Output handler name (defaults to TEXT)
REM -e encoding Character encoding of output handler (defaults to UTF-8)
REM -H handler About handler name
REM -o output Output file pathname (defaults to standard output)
REM -x saxclass SAX parser class (defaults to J2SE 1.4 default)
REM -t tempdir Temporary directory in which to create temporary files
REM -b bufsize Buffer size for buffered I/O (defaults to J2SE default)
REM -l loglevel Logging level
REM -k Calculate CRC32, MD5, and SHA-1 checksums
REM -r Display raw data flags, not textual equivalents
REM -s Format identification based on internal signatures only
REM dir-file-or-uri Directory, file pathname, or URI of formatted content
REM
REM Configuration constants:
REM JHOVE_HOME Jhove installation directory
REM JAVA_HOME Java JRE directory
REM JAVA Java interpreter
REM EXTRA_JARS Extra jar files to add to CLASSPATH
REM SET JHOVE_HOME="C:\Program Files\jhove"
SET JHOVE_HOME="[your directory path]\jhove"
SET JAVA_HOME="C:\Program Files\java\j2re1.4.1_02"
SET JAVA=%JAVA_HOME%\bin\java
SET EXTRA_JARS=
REM NOTE: Nothing below this line should be edited
REM #########################################################################
SET CP=%JHOVE_HOME%\bin\JhoveApp.jar
IF "%EXTRA_JARS%"=="" GOTO FI
SET CP=%CP%:%EXTRA_JARS
:FI
REM Retrieve a copy of all command line arguments to pass to the application
SET ARGS=
:WHILE
IF "%1"=="" GOTO LOOP
SET ARGS=%ARGS% %1
SHIFT
GOTO WHILE
:LOOP
REM Set the CLASSPATH and invoke the Java loader
%JAVA% -classpath %CP% Jhove %ARGS%
Binary file not shown.
Binary file not shown.
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/perl -w
# Generate MD5 checksum
use Digest::MD5;
die "usage: md5.pl file\n" if $#ARGV < 0;
open (FILE, "<$ARGV[0]") or die "can't open file\"$ARGV[0]\"!\n";
$ctx = Digest::MD5->new->addfile (*FILE);
close (FILE);
$digest = $ctx->hexdigest;
print "$digest\n";
+44
View File
@@ -0,0 +1,44 @@
#!/bin/sh
#DO NOT RUN THIS ON A DEVELOPEMENT DIRECTORY, ONLY ON A
#CHECKED-OUT COPY TO BE PACKAGED!
if [ "$1" = "" ]; then
echo "Usage: packagejhove.sh [version]"
echo "e.g., packagejhove.sh 1_8"
exit 1
fi
echo "This script will prepare your directory for uploading."
echo "DO NOT RUN IT unless you're a developer and know what"
echo "you are doing. "
echo "Start in the top-level directory of the JHOVE checkout."
echo "Run ant and ant javadoc and do any necessary testing and"
echo "committing before running this script."
echo
echo
echo "To continue, enter the secret phrase."
read OATH
if [ "$OATH" != "I solemnly swear that I am up to no good" ]; then
exit 1
fi
cd ..
cat >>CVSS << EOF
CVS
.cvsignore
EOF
tar cvfX jhove-$1.tar CVSS jhove
gzip jhove-$1.tar
cp -r jhove jhove-zip
cd jhove-zip
find . \( -name CVS -o -name .cvsignore \) -exec rm -r {} \;
cd ..
mv jhove jhove-ok
mv jhove-zip jhove
zip -r jhove-$1.zip jhove
rm -r jhove
mv jhove-ok jhove
jhove/md5.pl jhove-$1.tar.gz >jhove-$1.tar.gz.md5
jhove/md5.pl jhove-$1.zip >jhove-$1.zip.md5
+37
View File
@@ -0,0 +1,37 @@
#!/bin/sh
########################################################################
# pdump - JSTOR/Harvard Object Validation Environment
# Copyright 2003-2005 by JSTOR and the President and Fellows of Harvard College
# JHOVE is made available under the GNU General Public License (see the
# file LICENSE for details)
#
# Driver script for the PDF dump utility
#
# Usage: pdump file
#
# where file is a PDF file
#
# Configuration constants:
JHOVE_HOME=/users/stephen/projects/jhove
JAVA_HOME=/usr/java # Java JRE directory
JAVA=$JAVA_HOME/bin/java # Java interpreter
EXTRA_JARS= # Extra .jar files to add to CLASSPATH
# NOTE: Nothing below this line should be edited
########################################################################
CP=${JHOVE_HOME}/bin/JhoveApp.jar:${EXTRA_JARS}
# Retrieve a copy of all command line arguments to pass to the application.
ARGS=""
for ARG do
ARGS="$ARGS $ARG"
done
# Set the CLASSPATH and invoke the Java loader.
${JAVA} -classpath $CP PDump $ARGS
+24
View File
@@ -0,0 +1,24 @@
#!/bin/sh
########################################################################
# userhome - JSTOR/Harvard Object Validation Environment
# Copyright 2004-2006 by the President and Fellows of Harvard College
# JHOVE is made available under the GNU General Public License (see the
# file LICENSE for details)
#
# Driver script to display the default Java user.home property
#
# Usage: userhome
#
# Configuration constants:
JHOVE_HOME=/users/stephen/projects/jhove
JAVA_HOME=/usr/java # Java JRE directory
JAVA=$JAVA_HOME/bin/java # Java interpreter
# NOTE: Nothing below this line should be edited
########################################################################
# Set the CLASSPATH and invoke the Java loader.
${JAVA} -classpath ${JHOVE_HOME}/classes UserHome
-34
View File
@@ -1,34 +0,0 @@
from enum import IntEnum
import os
from collections.abc import Iterable
import pkg_resources
VERSION = pkg_resources.get_distribution('ocrmypdf').version
class ExitCode(IntEnum):
ok = 0
bad_args = 1
input_file = 2
missing_dependency = 3
invalid_output_pdf = 4
file_access_error = 5
already_done_ocr = 6
child_process_error = 7
encrypted_pdf = 8
other_error = 15
ctrl_c = 130
def get_program(name):
envvar = 'OCRMYPDF_' + name.upper()
return os.environ.get(envvar, name)
def page_number(input_file):
return int(os.path.basename(input_file)[0:6])
def is_iterable_notstr(thing):
return isinstance(thing, Iterable) and not isinstance(thing, str)
-1521
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
from tempfile import NamedTemporaryFile
from subprocess import Popen, PIPE, check_call
from shutil import copy
from . import get_program
from .pdfa import SRGB_ICC_PROFILE
def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log,
pageno=1):
with NamedTemporaryFile(delete=True) as tmp:
args_gs = [
get_program('gs'),
'-dQUIET',
'-dSAFER',
'-dBATCH',
'-dNOPAUSE',
'-sDEVICE=%s' % raster_device,
'-dFirstPage=%i' % pageno,
'-dLastPage=%i' % pageno,
'-o', tmp.name,
'-r{0}x{1}'.format(str(round(xres)), str(round(yres))),
input_file
]
p = Popen(args_gs, close_fds=True, stdout=PIPE, stderr=PIPE,
universal_newlines=True)
stdout, stderr = p.communicate()
if stdout:
if 'error' in stdout:
log.error(stdout) # Ghostscript puts errors in stdout
else:
log.debug(stdout)
if stderr:
log.error(stderr)
if p.returncode == 0:
copy(tmp.name, output_file)
else:
log.error('Ghostscript rendering failed')
def generate_pdfa(pdf_pages, output_file, threads=1):
with NamedTemporaryFile(delete=True) as gs_pdf:
args_gs = [
get_program("gs"),
"-dQUIET",
"-dBATCH",
"-dNOPAUSE",
'-dNumRenderingThreads=' + str(threads),
"-sDEVICE=pdfwrite",
"-dAutoRotatePages=/None",
"-sColorConversionStrategy=/RGB",
"-sProcessColorModel=DeviceRGB",
"-dJPEGQ=95",
"-dPDFA=2",
"-sPDFACompatibilityPolicy=2",
"-sOutputFile=" + gs_pdf.name,
]
args_gs.extend(pdf_pages)
check_call(args_gs)
copy(gs_pdf.name, output_file)
-230
View File
@@ -1,230 +0,0 @@
#!/usr/bin/env python3
##############################################################################
# Copyright (c) 2013-14: fritz-hh from Github
# (https://github.com/fritz-hh)
#
# Copyright (c) 2010: Jonathan Brinley from Github
# (https://github.com/jbrinley/HocrConverter)
# Initial version by Jonathan Brinley, jonathanbrinley@gmail.com
##############################################################################
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.units import inch
from xml.etree import ElementTree
from PIL import Image
from collections import namedtuple
import re
import argparse
Rect = namedtuple('Rect', ['x1', 'y1', 'x2', 'y2'])
class HocrTransformError(Exception):
pass
class HocrTransform():
"""
A class for converting documents from the hOCR format.
For details of the hOCR format, see:
http://docs.google.com/View?docid=dfxcv4vc_67g844kf
"""
def __init__(self, hocrFileName, dpi):
self.dpi = dpi
self.boxPattern = re.compile(r'bbox((\s+\d+){4})')
self.hocr = ElementTree.parse(hocrFileName)
# if the hOCR file has a namespace, ElementTree requires its use to
# find elements
matches = re.match(r'({.*})html', self.hocr.getroot().tag)
self.xmlns = ''
if matches:
self.xmlns = matches.group(1)
# get dimension in pt (not pixel!!!!) of the OCRed image
self.width, self.height = None, None
for div in self.hocr.findall(
".//%sdiv[@class='ocr_page']" % (self.xmlns)):
coords = self.element_coordinates(div)
pt_coords = self.pt_from_pixel(coords)
self.width = pt_coords.x2 - pt_coords.x1
self.height = pt_coords.y2 - pt_coords.y1
# there shouldn't be more than one, and if there is, we don't want
# it
break
if self.width is None or self.height is None:
raise HocrTransformError("hocr file is missing page dimensions")
def __str__(self):
"""
Return the textual content of the HTML body
"""
if self.hocr is None:
return ''
body = self.hocr.find(".//%sbody" % (self.xmlns))
if body:
return self._get_element_text(body)
else:
return ''
def _get_element_text(self, element):
"""
Return the textual content of the element and its children
"""
text = ''
if element.text is not None:
text += element.text
for child in element.getchildren():
text += self._get_element_text(child)
if element.tail is not None:
text += element.tail
return text
def element_coordinates(self, element):
"""
Returns a tuple containing the coordinates of the bounding box around
an element
"""
out = (0, 0, 0, 0)
if 'title' in element.attrib:
matches = self.boxPattern.search(element.attrib['title'])
if matches:
coords = matches.group(1).split()
out = Rect._make(int(coords[n]) for n in range(4))
return out
def pt_from_pixel(self, pxl):
"""
Returns the quantity in PDF units (pt) given quantity in pixels
"""
return Rect._make(
(c / self.dpi * inch) for c in pxl)
def replace_unsupported_chars(self, s):
"""
Given an input string, returns the corresponding string that:
- is available in the helvetica facetype
- does not contain any ligature (to allow easy search in the PDF file)
"""
# The 'u' before the character to replace indicates that it is a
# unicode character
s = s.replace(u"", "fl")
s = s.replace(u"", "fi")
return s
def to_pdf(self, outFileName, imageFileName=None, showBoundingboxes=False,
fontname="Helvetica", invisibleText=False):
"""
Creates a PDF file with an image superimposed on top of the text.
Text is positioned according to the bounding box of the lines in
the hOCR file.
The image need not be identical to the image used to create the hOCR
file.
It can have a lower resolution, different color mode, etc.
"""
# create the PDF file
# page size in points (1/72 in.)
pdf = Canvas(
outFileName, pagesize=(self.width, self.height), pageCompression=1)
# draw bounding box for each paragraph
# light blue for bounding box of paragraph
pdf.setStrokeColorRGB(0, 1, 1)
# light blue for bounding box of paragraph
pdf.setFillColorRGB(0, 1, 1)
pdf.setLineWidth(0) # no line for bounding box
for elem in self.hocr.findall(
".//%sp[@class='%s']" % (self.xmlns, "ocr_par")):
elemtxt = self._get_element_text(elem).rstrip()
if len(elemtxt) == 0:
continue
pxl_coords = self.element_coordinates(elem)
pt = self.pt_from_pixel(pxl_coords)
# draw the bbox border
if showBoundingboxes:
pdf.rect(
pt.x1, self.height - pt.y2, pt.x2 - pt.x1, pt.y2 - pt.y1,
fill=1)
# check if element with class 'ocrx_word' are available
# otherwise use 'ocr_line' as fallback
elemclass = "ocr_line"
if self.hocr.find(
".//%sspan[@class='ocrx_word']" % (self.xmlns)) is not None:
elemclass = "ocrx_word"
# itterate all text elements
# light green for bounding box of word/line
pdf.setStrokeColorRGB(1, 0, 0)
pdf.setLineWidth(0.5) # bounding box line width
pdf.setDash(6, 3) # bounding box is dashed
pdf.setFillColorRGB(0, 0, 0) # text in black
for elem in self.hocr.findall(
".//%sspan[@class='%s']" % (self.xmlns, elemclass)):
elemtxt = self._get_element_text(elem).rstrip()
elemtxt = self.replace_unsupported_chars(elemtxt)
if len(elemtxt) == 0:
continue
pxl_coords = self.element_coordinates(elem)
pt = self.pt_from_pixel(pxl_coords)
# draw the bbox border
if showBoundingboxes:
pdf.rect(
pt.x1, self.height - pt.y2, pt.x2 - pt.x1, pt.y2 - pt.y1,
fill=0)
text = pdf.beginText()
fontsize = pt.y2 - pt.y1
text.setFont(fontname, fontsize)
if invisibleText:
text.setTextRenderMode(3) # Invisible (indicates OCR text)
# set cursor to bottom left corner of bbox (adjust for dpi)
text.setTextOrigin(pt.x1, self.height - pt.y2)
# scale the width of the text to fill the width of the bbox
text.setHorizScale(
100 * (pt.x2 - pt.x1) / pdf.stringWidth(
elemtxt, fontname, fontsize))
# write the text to the page
text.textLine(elemtxt)
pdf.drawText(text)
# put the image on the page, scaled to fill the page
if imageFileName is not None:
pdf.drawImage(imageFileName, 0, 0,
width=self.width, height=self.height)
# finish up the page and save it
pdf.showPage()
pdf.save()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Convert hocr file to PDF')
parser.add_argument('-b', '--boundingboxes', action="store_true",
default=False, help='Show bounding boxes borders')
parser.add_argument('-r', '--resolution', type=int,
default=300,
help='Resolution of the image that was OCRed')
parser.add_argument('-i', '--image', default=None,
help='Path to the image to be placed above the text')
parser.add_argument('hocrfile', help='Path to the hocr file to be parsed')
parser.add_argument(
'outputfile', help='Path to the PDF file to be generated')
args = parser.parse_args()
hocr = HocrTransform(args.hocrfile, args.resolution)
hocr.to_pdf(args.outputfile, args.image, args.boundingboxes)
-592
View File
@@ -1,592 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# © 2013-16: jbarlow83 from Github (https://github.com/jbarlow83)
#
# Python FFI wrapper for Leptonica library
from __future__ import print_function, absolute_import, division
import argparse
import sys
import os
import logging
from tempfile import TemporaryFile
from ctypes.util import find_library
from .lib._leptonica import ffi
from functools import lru_cache
from enum import Enum
lept = ffi.dlopen(find_library('lept'))
logger = logging.getLogger(__name__)
def stderr(*objs):
"""Python 2/3 compatible print to stderr.
"""
print("leptonica.py:", *objs, file=sys.stderr)
class LeptonicaErrorTrap(object):
"""Context manager to trap errors reported by Leptonica.
Leptonica's error return codes are unreliable to the point of being
almost useless. It does, however, write errors to stderr provided that is
not disabled at its compile time. Fortunately this is done using error
macros so it is very self-consistent.
This context manager redirects stderr to a temporary file which is then
read and parsed for error messages. As a side benefit, debug messages
from Leptonica are also suppressed.
"""
def __enter__(self):
from io import UnsupportedOperation
self.tmpfile = TemporaryFile()
# Save the old stderr, and redirect stderr to temporary file
try:
self.old_stderr_fileno = os.dup(sys.stderr.fileno())
os.dup2(self.tmpfile.fileno(), sys.stderr.fileno())
except UnsupportedOperation:
self.old_stderr_fileno = None
return
def __exit__(self, exc_type, exc_value, traceback):
# Restore old stderr
if self.old_stderr_fileno is not None:
os.dup2(self.old_stderr_fileno, sys.stderr.fileno())
# Get data from tmpfile (in with block to ensure it is closed)
with self.tmpfile as tmpfile:
tmpfile.seek(0) # Cursor will be at end, so move back to beginning
leptonica_output = tmpfile.read().decode(errors='replace')
# If there are Python errors, let them bubble up
if exc_type:
logger.warning(leptonica_output)
return False
# If there are Leptonica errors, wrap them in Python excpetions
if 'Error' in leptonica_output:
if 'image file not found' in leptonica_output:
raise FileNotFoundError()
if 'pixWrite: stream not opened' in leptonica_output:
raise LeptonicaIOError()
raise LeptonicaError(leptonica_output)
return False
class LeptonicaError(Exception):
pass
class LeptonicaIOError(LeptonicaError):
pass
class RemoveColormap(Enum):
to_binary = 0
to_grayscale = 1
to_full_color = 2
based_on_src = 3
class Pix:
"""Wrapper around leptonica's PIX object.
Leptonica uses referencing counting on PIX objects. Also, many Leptonica
functions return the original object with an increased reference count
if the operation had no effect (for example, image skew was found to be 0).
This has complications for memory management in Python. Whenever Leptonica
returns a PIX object (new or old), we wrap it in this class, which
registers it with the FFI garbage collector. pixDestroy() decrements the
reference count and only destroys when the last reference is removed.
Leptonica's reference counting is not threadsafe. This class can be used
in a threadsafe manner if a Python threading.Lock protects the data.
This class treats Pix objects as immutable. All methods return new
modified objects. This allows convenient chaining:
>>> Pix.read('filename.jpg').scale((0.5, 0.5)).deskew().show()
"""
def __init__(self, pix):
self._pix = ffi.gc(pix, Pix._pix_destroy)
def __repr__(self):
if self._pix:
s = "<leptonica.Pix image size={0}x{1} depth={2} at 0x{3:x}>"
return s.format(self._pix.w, self._pix.h, self._pix.d,
int(ffi.cast("intptr_t", self._pix)))
else:
return "<leptonica.Pix image NULL>"
def _repr_png_(self):
"""iPython display hook
returns png version of image
"""
data = ffi.new('l_uint8 **')
size = ffi.new('size_t *')
err = lept.pixWriteMemPng(data, size, self._pix, 0)
if err != 0:
raise LeptonicaIOError("pixWriteMemPng")
char_data = ffi.cast('char *', data[0])
return ffi.buffer(char_data, size[0])[:]
def __getstate__(self):
data = ffi.new('l_uint32 **')
size = ffi.new('size_t *')
err = lept.pixSerializeToMemory(self._pix, data, size)
if err != 0:
raise LeptonicaIOError("pixSerializeToMemory")
char_data = ffi.cast('char *', data[0])
# Copy from C bytes to python bytes()
data_bytes = ffi.buffer(char_data, size[0])[:]
# Can now free C bytes
lept.lept_free(char_data)
return dict(data=data_bytes)
def __setstate__(self, state):
cdata_bytes = ffi.new('char[]', state['data'])
cdata_uint32 = ffi.cast('l_uint32 *', cdata_bytes)
pix = lept.pixDeserializeFromMemory(
cdata_uint32, len(state['data']))
Pix.__init__(self, pix)
def __eq__(self, other):
return self.__getstate__() == other.__getstate__()
@property
def width(self):
return self._pix.w
@property
def height(self):
return self._pix.h
@property
def depth(self):
return self._pix.d
@property
def size(self):
return (self._pix.w, self._pix.h)
@property
def info(self):
return {'dpi': (self._pix.xres, self._pix.yres)}
@property
def mode(self):
"Return mode like PIL.Image"
if self.depth == 1:
return '1'
elif self.depth >= 16:
return 'RGB'
elif not self._pix.colormap:
return 'L'
else:
return 'P'
@classmethod
def read(cls, filename):
"""Load an image file into a PIX object.
Leptonica can load TIFF, PNM (PBM, PGM, PPM), PNG, and JPEG. If
loading fails then the object will wrap a C null pointer.
"""
with LeptonicaErrorTrap():
return cls(lept.pixRead(
filename.encode(sys.getfilesystemencoding())))
def write_implied_format(
self, filename, jpeg_quality=0, jpeg_progressive=0):
"""Write pix to the filename, with the extension indicating format.
jpeg_quality -- quality (iff JPEG; 1 - 100, 0 for default)
jpeg_progressive -- (iff JPEG; 0 for baseline seq., 1 for progressive)
"""
with LeptonicaErrorTrap():
lept.pixWriteImpliedFormat(
filename.encode(sys.getfilesystemencoding()),
self._pix, jpeg_quality, jpeg_progressive)
def topil(self):
"Returns a PIL.Image version of this Pix"
from PIL import Image
# Leptonica manages data in words, so it implicitly does an endian
# swap. Tell Pillow about this when it reads the data.
pix = self
if sys.byteorder == 'little':
if self.mode == 'RGB':
raw_mode = 'XBGR'
elif self.mode == 'RGBA':
raw_mode = 'ABGR'
elif self.mode == '1':
raw_mode = '1;I'
pix = Pix(lept.pixEndianByteSwapNew(pix._pix))
else:
raw_mode = self.mode
pix = Pix(lept.pixEndianByteSwapNew(pix._pix))
else:
raw_mode = self.mode # no endian swap needed
size = (pix._pix.w, pix._pix.h)
bytecount = pix._pix.wpl * 4 * pix._pix.h
buf = ffi.buffer(pix._pix.data, bytecount)
stride = pix._pix.wpl * 4
im = Image.frombytes(self.mode, size, buf, 'raw', raw_mode, stride)
return im
def show(self):
return self.topil().show()
def deskew(self, reduction_factor=0):
"""Returns the deskewed pix object.
A clone of the original is returned when the algorithm cannot find a
skew angle with sufficient confidence.
reduction_factor -- amount to downsample (0 for default) when searching
for skew angle
"""
with LeptonicaErrorTrap():
return Pix(lept.pixDeskew(self._pix, reduction_factor))
def scale(self, scale_xy):
"Returns the pix object rescaled according to the proportions given."
with LeptonicaErrorTrap():
return Pix(lept.pixScale(self._pix, scale_xy[0], scale_xy[1]))
def rotate180(self):
with LeptonicaErrorTrap():
return Pix(lept.pixRotate180(ffi.NULL, self._pix))
def rotate_orth(self, quads):
"Orthographic rotation, quads: 0-3, number of clockwise rotations"
with LeptonicaErrorTrap():
return Pix(lept.pixRotateOrth(self._pix, quads))
def find_skew(self):
"""Returns a tuple (deskew angle in degrees, confidence value).
Returns (None, None) if no angle is available.
"""
with LeptonicaErrorTrap():
angle = ffi.new('float *', 0.0)
confidence = ffi.new('float *', 0.0)
result = lept.pixFindSkew(self._pix, angle, confidence)
if result == 0:
return (angle[0], confidence[0])
else:
return (None, None)
def convert_rgb_to_luminance(self):
with LeptonicaErrorTrap():
gray_pix = lept.pixConvertRGBToLuminance(self._pix)
if gray_pix:
return Pix(gray_pix)
return None
def remove_colormap(self, removal_type):
"""Remove a palette
removal_type - RemovalColormap()
"""
with LeptonicaErrorTrap():
return Pix(lept.pixRemoveColormap(self._pix, removal_type))
def otsu_adaptive_threshold(
self, tile_size=(300, 300), kernel_size=(4, 4), scorefract=0.1):
with LeptonicaErrorTrap():
sx, sy = tile_size
smoothx, smoothy = kernel_size
p_pix = ffi.new('PIX **')
result = lept.pixOtsuAdaptiveThreshold(
self._pix,
sx, sy,
smoothx, smoothy,
scorefract,
ffi.NULL,
p_pix)
if result == 0:
return Pix(p_pix[0])
else:
return None
def otsu_threshold_on_background_norm(
self, mask=None, tile_size=(10, 15), thresh=100, mincount=50,
bgval=255, kernel_size=(2, 2), scorefract=0.1):
with LeptonicaErrorTrap():
sx, sy = tile_size
smoothx, smoothy = kernel_size
if mask is None:
mask = ffi.NULL
if isinstance(mask, Pix):
mask = mask._pix
thresh_pix = lept.pixOtsuThreshOnBackgroundNorm(
self._pix,
mask,
sx, sy,
thresh, mincount, bgval,
smoothx, smoothy,
scorefract,
ffi.NULL
)
if thresh_pix == ffi.NULL:
return None
return Pix(thresh_pix)
def crop_to_foreground(
self, threshold=128, mindist=70, erasedist=30, pagenum=0,
showmorph=0, display=0, pdfdir=ffi.NULL):
with LeptonicaErrorTrap():
cropbox = Box(lept.pixFindPageForeground(
self._pix,
threshold,
mindist,
erasedist,
pagenum,
showmorph,
display,
pdfdir))
print(repr(cropbox))
cropped_pix = lept.pixClipRectangle(
self._pix,
cropbox._box,
ffi.NULL)
return Pix(cropped_pix)
def clean_background_to_white(
self, mask=None, grayscale=None, gamma=1.0, black=0, white=255):
with LeptonicaErrorTrap():
return Pix(lept.pixCleanBackgroundToWhite(
self._pix,
mask or ffi.NULL,
grayscale or ffi.NULL,
gamma,
black,
white))
def gamma_trc(self, gamma=1.0, minval=0, maxval=255):
with LeptonicaErrorTrap():
return Pix(lept.pixGammaTRC(
ffi.NULL,
self._pix,
gamma,
minval,
maxval
))
def background_norm(
self, mask=None, grayscale=None, tile_size=(10, 15), fg_threshold=60,
min_count=40, bg_val=200, smooth_kernel=(2, 1)):
with LeptonicaErrorTrap():
return Pix(lept.pixBackgroundNorm(
self._pix,
mask or ffi.NULL,
grayscale or ffi.NULL,
tile_size[0],
tile_size[1],
fg_threshold,
min_count,
bg_val,
smooth_kernel[0],
smooth_kernel[1]
))
@staticmethod
@lru_cache(maxsize=1)
def make_pixel_sum_tab8():
return lept.makePixelSumTab8()
@staticmethod
def correlation_binary(pix1, pix2):
if get_leptonica_version() < 'leptonica-1.72':
# Older versions of Leptonica (pre-1.72) have a buggy
# implementation of pixCorrelationBinary that overflows on larger
# images. Ubuntu trusty has 1.70. Ubuntu PPA
# ppa:rebuntu16/avidemux+unofficial has "leptonlib" 1.73.
pix1_count = ffi.new('l_int32 *')
pix2_count = ffi.new('l_int32 *')
pixn_count = ffi.new('l_int32 *')
tab8 = Pix.make_pixel_sum_tab8()
lept.pixCountPixels(pix1._pix, pix1_count, tab8)
lept.pixCountPixels(pix2._pix, pix2_count, tab8)
pixn = Pix(lept.pixAnd(ffi.NULL, pix1._pix, pix2._pix))
lept.pixCountPixels(pixn._pix, pixn_count, tab8)
# Python converts these int32s to larger units as needed
# to avoid overflow. Overflow happens easily here.
correlation = (
(pixn_count[0] * pixn_count[0]) /
(pix1_count[0] * pix2_count[0])
)
return correlation
else:
correlation = ffi.new('float *', 0.0)
result = lept.pixCorrelationBinary(pix1._pix, pix2._pix,
correlation)
if result != 0:
raise LeptonicaError("Correlation failed")
return correlation[0]
@staticmethod
def _pix_destroy(pix):
p_pix = ffi.new('PIX **', pix)
lept.pixDestroy(p_pix)
# print('pix destroy ' + repr(pix))
class Box:
"""Wrapper around Leptonica's BOX objects.
See class Pix for notes about reference counting.
"""
def __init__(self, box):
self._box = ffi.gc(box, Box._box_destroy)
def __repr__(self):
if self._box:
return '<leptonica.Box x={0} y={1} w={2} h={3}>'.format(
self.x, self.y, self.w, self.h)
return '<leptonica.Box NULL>'
@property
def x(self):
return self._box.x
@property
def y(self):
return self._box.y
@property
def w(self):
return self._box.w
@property
def h(self):
return self._box.h
@staticmethod
def _box_destroy(box):
p_box = ffi.new('BOX **', box)
lept.boxDestroy(p_box)
@lru_cache(maxsize=1)
def get_leptonica_version():
"""Get Leptonica version string.
Caveat: Leptonica expects the caller to free this memory. We don't,
since that would involve binding to libc to access libc.free(),
a pointless effort to reclaim 100 bytes of memory.
"""
return ffi.string(lept.getLeptonicaVersion()).decode()
def deskew(infile, outfile, dpi):
try:
pix_source = Pix.read(infile)
except LeptonicaIOError:
raise LeptonicaIOError("Failed to open file: %s" % infile)
if dpi < 150:
reduction_factor = 1 # Don't downsample too much if DPI is already low
else:
reduction_factor = 0 # Use default
pix_deskewed = pix_source.deskew(reduction_factor)
try:
pix_deskewed.write_implied_format(outfile)
except LeptonicaIOError:
raise LeptonicaIOError("Failed to open destination file: %s" % outfile)
def remove_background(infile, outfile, tile_size=(40, 60), gamma=1.0,
black_threshold=70, white_threshold=190):
try:
pix = Pix.read(infile)
except LeptonicaIOError:
raise LeptonicaIOError("Failed to open file: %s" % infile)
pix = pix.background_norm(tile_size=tile_size).gamma_trc(
gamma, black_threshold, white_threshold)
try:
pix.write_implied_format(outfile)
except LeptonicaIOError:
raise LeptonicaIOError("Failed to open destination file: %s" % outfile)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description="Python wrapper to access Leptonica")
subparsers = parser.add_subparsers(title='commands',
description='supported operations')
parser_deskew = subparsers.add_parser('deskew')
parser_deskew.add_argument('-r', '--dpi', dest='dpi', action='store',
type=int, default=300, help='input resolution')
parser_deskew.add_argument('infile', help='image to deskew')
parser_deskew.add_argument('outfile', help='deskewed output image')
parser_deskew.set_defaults(func=deskew)
args = parser.parse_args()
if get_leptonica_version() != u'leptonica-1.69':
print("Unexpected leptonica version: %s" % getLeptonicaVersion())
args.func(args)
def test_skew_angle():
from PIL import Image, ImageDraw
from tempfile import NamedTemporaryFile
im = Image.new(mode='1', size=(1000, 1000), color=1)
draw = ImageDraw.Draw(im)
for n in range(20):
draw.line([(50, 25 + 50*n), (950, 25 + 50*n)], width=1)
del draw
test_angles = [0.1 * ang for ang in range(1, 10)] + \
[float(ang) for ang in range(1, 7)]
test_angles += [-ang for ang in test_angles]
test_angles = sorted(test_angles)
for rotate_angle in test_angles:
rotated_im = im.rotate(rotate_angle)
with NamedTemporaryFile(prefix='lept-skew', suffix='.png', delete=True) as tmpfile:
rotated_im.save(tmpfile)
pix = pixRead(tmpfile.name)
angle, confidence = pixFindSkew(pix)
print('{0} {1} {2}'.format(rotate_angle, angle, confidence), file=sys.stderr)
View File
-192
View File
@@ -1,192 +0,0 @@
from cffi import FFI
ffi = FFI()
ffi.set_source("ocrmypdf.lib._leptonica", None)
ffi.cdef("""
typedef signed char l_int8;
typedef unsigned char l_uint8;
typedef short l_int16;
typedef unsigned short l_uint16;
typedef int l_int32;
typedef unsigned int l_uint32;
typedef float l_float32;
typedef double l_float64;
typedef long long l_int64;
typedef unsigned long long l_uint64;
struct Pix
{
l_uint32 w; /* width in pixels */
l_uint32 h; /* height in pixels */
l_uint32 d; /* depth in bits (bpp) */
l_uint32 spp; /* number of samples per pixel */
l_uint32 wpl; /* 32-bit words/line */
l_uint32 refcount; /* reference count (1 if no clones) */
l_int32 xres; /* image res (ppi) in x direction */
/* (use 0 if unknown) */
l_int32 yres; /* image res (ppi) in y direction */
/* (use 0 if unknown) */
l_int32 informat; /* input file format, IFF_* */
l_int32 special; /* special instructions for I/O, etc */
char *text; /* text string associated with pix */
struct PixColormap *colormap; /* colormap (may be null) */
l_uint32 *data; /* the image data */
};
typedef struct Pix PIX;
struct PixColormap
{
void *array; /* colormap table (array of RGBA_QUAD) */
l_int32 depth; /* of pix (1, 2, 4 or 8 bpp) */
l_int32 nalloc; /* number of color entries allocated */
l_int32 n; /* number of color entries used */
};
typedef struct PixColormap PIXCMAP;
struct Box
{
l_int32 x;
l_int32 y;
l_int32 w;
l_int32 h;
l_uint32 refcount; /* reference count (1 if no clones) */
};
typedef struct Box BOX;
""")
ffi.cdef("""
PIX * pixRead ( const char *filename );
PIX * pixScale ( PIX *pixs, l_float32 scalex, l_float32 scaley );
l_int32 pixFindSkew ( PIX *pixs, l_float32 *pangle, l_float32 *pconf );
l_int32 pixWriteImpliedFormat ( const char *filename, PIX *pix, l_int32 quality, l_int32 progressive );
l_int32
pixWriteMemPng(l_uint8 **pdata,
size_t *psize,
PIX *pix,
l_float32 gamma);
void pixDestroy ( PIX **ppix );
PIX *
pixEndianByteSwapNew(PIX *pixs);
PIX * pixDeskew ( PIX *pixs, l_int32 redsearch );
char * getLeptonicaVersion ( );
l_int32 pixCorrelationBinary(PIX *pix1, PIX *pix2, l_float32 *pval);
PIX *pixRotate180(PIX *pixd, PIX *pixs);
PIX *
pixRotateOrth(PIX *pixs,
l_int32 quads);
l_int32 pixCountPixels ( PIX *pix, l_int32 *pcount, l_int32 *tab8 );
PIX * pixAnd ( PIX *pixd, PIX *pixs1, PIX *pixs2 );
l_int32 * makePixelSumTab8 ( void );
PIX * pixDeserializeFromMemory ( const l_uint32 *data, size_t nbytes );
l_int32 pixSerializeToMemory ( PIX *pixs, l_uint32 **pdata, size_t *pnbytes );
PIX * pixConvertRGBToLuminance(PIX *pixs);
PIX * pixRemoveColormap(PIX *pixs, l_int32 type);
l_int32
pixOtsuAdaptiveThreshold(PIX *pixs,
l_int32 sx,
l_int32 sy,
l_int32 smoothx,
l_int32 smoothy,
l_float32 scorefract,
PIX **ppixth,
PIX **ppixd);
PIX *
pixOtsuThreshOnBackgroundNorm(PIX *pixs,
PIX *pixim,
l_int32 sx,
l_int32 sy,
l_int32 thresh,
l_int32 mincount,
l_int32 bgval,
l_int32 smoothx,
l_int32 smoothy,
l_float32 scorefract,
l_int32 *pthresh);
PIX *
pixCleanBackgroundToWhite(PIX *pixs,
PIX *pixim,
PIX *pixg,
l_float32 gamma,
l_int32 blackval,
l_int32 whiteval);
BOX *
pixFindPageForeground(PIX *pixs,
l_int32 threshold,
l_int32 mindist,
l_int32 erasedist,
l_int32 pagenum,
l_int32 showmorph,
l_int32 display,
const char *pdfdir);
PIX *
pixClipRectangle(PIX *pixs,
BOX *box,
BOX **pboxc);
PIX *
pixBackgroundNorm(PIX *pixs,
PIX *pixim,
PIX *pixg,
l_int32 sx,
l_int32 sy,
l_int32 thresh,
l_int32 mincount,
l_int32 bgval,
l_int32 smoothx,
l_int32 smoothy);
PIX *
pixGammaTRC(PIX *pixd,
PIX *pixs,
l_float32 gamma,
l_int32 minval,
l_int32 maxval);
l_int32
pixNumSignificantGrayColors(PIX *pixs,
l_int32 darkthresh,
l_int32 lightthresh,
l_float32 minfract,
l_int32 factor,
l_int32 *pncolors);
l_int32
pixColorFraction(PIX *pixs,
l_int32 darkthresh,
l_int32 lightthresh,
l_int32 diffthresh,
l_int32 factor,
l_float32 *ppixfract,
l_float32 *pcolorfract);
PIX *
pixColorMagnitude(PIX *pixs,
l_int32 rwhite,
l_int32 gwhite,
l_int32 bwhite,
l_int32 type);
void
boxDestroy(BOX **pbox);
void lept_free(void *ptr);
""")
if __name__ == '__main__':
ffi.compile()
-7
View File
@@ -1,7 +0,0 @@
#!/usr/bin/env python3
# © 2015-16 James R. Barlow: github.com/jbarlow83
# This file is now an alias for __main__
# Consider removing in future releases
from ocrmypdf.__main__ import *
-415
View File
@@ -1,415 +0,0 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
from subprocess import Popen, PIPE
from decimal import Decimal, getcontext
from math import hypot
import re
import sys
import PyPDF2 as pypdf
from collections import namedtuple
try:
from math import isclose
except ImportError:
def isclose(a, b, rel_tol=1e-9):
"Python 3.4 does not have math.isclose()"
diff = abs(b - a)
return diff <= abs(rel_tol * b) or diff <= abs(rel_tol * a)
matrix_mult = pypdf.pdf.utils.matrixMultiply
FRIENDLY_COLORSPACE = {
'/DeviceGray': 'gray',
'/CalGray': 'gray',
'/DeviceRGB': 'rgb',
'/CalRGB': 'rgb',
'/DeviceCMYK': 'cmyk',
'/Lab': 'lab',
'/ICCBased': 'icc',
'/Indexed': 'index',
'/Separation': 'sep',
'/DeviceN': 'devn',
'/Pattern': '-',
'/G': 'gray', # Abbreviations permitted in inline images
'/RGB': 'rgb',
'/CMYK': 'cmyk',
'/I': 'index',
}
FRIENDLY_ENCODING = {
'/CCITTFaxDecode': 'ccitt',
'/DCTDecode': 'jpeg',
'/JPXDecode': 'jpx',
'/JBIG2Decode': 'jbig2',
'/CCF': 'ccitt', # Abbreviations permitted in inline images
'/DCT': 'jpeg',
'/AHx': 'asciihex',
'/A85': 'ascii85',
'/LZW': 'lzw',
'/Fl': 'flate',
'/RL': 'runlength'
}
FRIENDLY_COMP = {
'gray': 1,
'rgb': 3,
'cmyk': 4,
'lab': 3,
'index': 1
}
UNIT_SQUARE = (1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
def _matrix_from_shorthand(shorthand):
"""Convert from PDF matrix shorthand to full matrix
PDF 1.7 spec defines a shorthand for describing the entries of a matrix
since the last column is always (0, 0, 1).
"""
a, b, c, d, e, f = map(float, shorthand)
return ((a, b, 0),
(c, d, 0),
(e, f, 1))
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 _is_unit_square(shorthand):
values = map(float, shorthand)
pairwise = zip(values, UNIT_SQUARE)
return all([isclose(a, b, rel_tol=1e-3) for a, b in pairwise])
RasterSettings = namedtuple('RasterSettings',
['name', 'shorthand', 'stack_depth'])
InlineSettings = namedtuple('InlineSettings',
['settings', 'shorthand', 'stack_depth'])
ContentsInfo = namedtuple('ContentsInfo', ['raster_settings', 'inline_images'])
def _interpret_contents(contentstream):
"""Interpret the PDF content stream
The stack represents the state of the PDF graphics stack. We are only
interested in the current transformation matrix (CTM) so we only track
this object; a full implementation would need to track many other items.
The CTM is initialized to the mapping from user space to device space.
PDF units are 1/72". In a PDF viewer or printer this matrix is initialized
to the transformation to device space. For example if set to
(1/72, 0, 0, 1/72, 0, 0) then all units would be calculated in inches.
Images are always considered to be (0, 0) -> (1, 1). Before drawing an
image there should be a 'cm' that sets up an image coordinate system
where drawing from (0, 0) -> (1, 1) will draw on the desired area of the
page.
PDF units suit our needs so we initialize ctm to the identity matrix.
PyPDF2 replaces inline images with a fake "INLINE IMAGE" operator.
"""
operations = contentstream.operations
stack = []
ctm = _matrix_from_shorthand(UNIT_SQUARE)
image_raster_settings = []
inline_images = []
for op in operations:
operands, command = op
if command == b'q':
stack.append(ctm)
if len(stack) > 32:
raise RuntimeError("PDF graphics stack overflow")
elif command == b'Q':
ctm = stack.pop()
elif command == b'cm':
ctm = matrix_mult(
_matrix_from_shorthand(operands), ctm)
elif command == b'Do':
image_name = operands[0]
raster = RasterSettings(
name=image_name, shorthand=_shorthand_from_matrix(ctm),
stack_depth=len(stack))
image_raster_settings.append(raster)
elif command == b'INLINE IMAGE':
settings = operands['settings']
inline = InlineSettings(
settings=settings, shorthand=_shorthand_from_matrix(ctm),
stack_depth=len(stack))
inline_images.append(inline)
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.
A PDF image may be scaled (always), cropped, translated, rotated in place
to an arbitrary angle (rarely) and skewed. Only equal area mappings can
be expressed, that is, it is not necessary to consider distortions where
the effective DPI varies with position.
To determine the image scale, transform an offset axis vector v0 (0, 0),
width-axis vector v0 (1, 0), height-axis vector vh (0, 1) with the matrix,
which gives the dimensions of the image in PDF units. From there we can
compare to actual image dimensions. PDF uses
row vector * matrix_tranposed unlike the traditional
matrix * column vector.
The offset, width and height vectors can be combined in a matrix and
multiplied by the transform matrix. Then we want to calculated
magnitude(width_vector - offset_vector)
and
magnitude(height_vector - offset_vector)
When the above is worked out algebraically, the effect of translation
cancels out, and the vector magnitudes become functions of the nonzero
transformation matrix indices. The results of the derivation are used
in this code.
pdfimages -list does calculate the DPI in some way that is not completely
naive, but it does not get 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.
"""
a, b, c, d, _, _ = ctm_shorthand
# Calculate the width and height of the image in PDF units
image_drawn_width = hypot(a, b)
image_drawn_height = hypot(c, d)
# 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_inline_images(page, pageinfo, contentsinfo):
"Find inline images on the page"
for n, inline in enumerate(contentsinfo.inline_images):
image = {}
image['name'] = str('inline-%02d' % n)
image['width'] = inline.settings['/W']
image['height'] = inline.settings['/H']
if '/BPC' in inline.settings:
image['bpc'] = inline.settings['/BPC']
else:
image['bpc'] = 8
image['color'] = FRIENDLY_COLORSPACE.get(inline.settings['/CS'], '-')
image['comp'] = FRIENDLY_COMP.get(image['color'], '?')
if '/F' in inline.settings:
filter_ = inline.settings['/F']
if isinstance(filter_, pypdf.generic.ArrayObject):
filter_ = filter_[0]
image['enc'] = FRIENDLY_ENCODING.get(filter_, 'image')
else:
image['enc'] = 'image'
dpi_w, dpi_h = _get_dpi(
inline.shorthand, (image['width'], image['height']))
image['dpi_w'], image['dpi_h'] = Decimal(dpi_w), Decimal(dpi_h)
yield image
def _find_page_regular_images(page, pageinfo, contentsinfo):
"Find images stored in XObject resources"
try:
page['/Resources']['/XObject']
except KeyError:
return
for xobj in page['/Resources']['/XObject']:
# PyPDF2 returns the keys as an iterator
pdfimage = page['/Resources']['/XObject'][xobj]
if pdfimage['/Subtype'] != '/Image':
continue
image = {}
image['name'] = str(xobj)
image['width'] = pdfimage['/Width']
image['height'] = pdfimage['/Height']
if '/BitsPerComponent' in pdfimage:
image['bpc'] = pdfimage['/BitsPerComponent']
else:
image['bpc'] = 8
# Fixme: this is incorrectly treats explicit masks as stencil masks,
# but good enough for now. Explicit masks have /ImageMask true but are
# never called for in content stream, instead are drawn as a /Mask on
# other images. For our purposes finding out the details of /Mask
# will seldom matter.
if '/ImageMask' in pdfimage:
image['type'] = 'stencil' if pdfimage['/ImageMask'].value \
else 'image'
else:
image['type'] = 'image'
if '/Filter' in pdfimage:
filter_ = pdfimage['/Filter']
if isinstance(filter_, pypdf.generic.ArrayObject):
filter_ = filter_[0]
image['enc'] = FRIENDLY_ENCODING.get(filter_, 'image')
else:
image['enc'] = 'image'
if '/ColorSpace' in pdfimage:
cs = pdfimage['/ColorSpace']
if isinstance(cs, pypdf.generic.ArrayObject):
cs = cs[0]
image['color'] = FRIENDLY_COLORSPACE.get(cs, '-')
else:
image['color'] = 'jpx' if image['enc'] == 'jpx' else '?'
image['comp'] = FRIENDLY_COMP.get(image['color'], '?')
# Bit of a hack... infer grayscale if component count is uncertain
# but encoding must be monochrome. This happens if a monochrome image
# has an ICC profile attached. Better solution would be to examine
# the ICC profile.
if image['comp'] == '?' and image['enc'] in ('ccitt', 'jbig2'):
image['comp'] = FRIENDLY_COMP['gray']
image['dpi_w'] = image['dpi_h'] = 0
for raster in contentsinfo.raster_settings:
# Loop in case the same image is display multiple times on a page
if raster.name != image['name']:
continue
if raster.stack_depth == 0 and _is_unit_square(raster.shorthand):
# At least one PDF in the wild (and test suite) draws an image
# when the graphics stack depth is 0, meaning that the image
# gets drawn into a square of 1x1 PDF units (or 1/72",
# or 0.35 mm). The equivalent DPI will be >100,000. Exclude
# these from our DPI calculation for the page.
continue
dpi_w, dpi_h = _get_dpi(
raster.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)
yield image
def _find_page_images(page, pageinfo, contentsinfo):
yield from _find_page_inline_images(page, pageinfo, contentsinfo)
yield from _find_page_regular_images(page, pageinfo, contentsinfo)
def _page_has_text(pdf, page):
# Simple test
text = page.extractText()
if text.strip() != '':
return True
# More nuanced test to deal with quirks of Tesseract PDF generation
# Check if there's a Glyphless font
try:
font = page['/Resources']['/Font']
except KeyError:
pass
else:
font_objects = list(font.keys())
for font_object in font_objects:
basefont = font[font_object]['/BaseFont']
if basefont.endswith('GlyphLessFont'):
return True
return False
def _pdf_get_pageinfo(infile, pageno: int):
pageinfo = {}
pageinfo['pageno'] = pageno
pageinfo['images'] = []
pdf = pypdf.PdfFileReader(infile)
page = pdf.pages[pageno]
pageinfo['has_text'] = _page_has_text(pdf, page)
width_pt = page.mediaBox.getWidth()
height_pt = page.mediaBox.getHeight()
pageinfo['width_inches'] = width_pt / Decimal(72.0)
pageinfo['height_inches'] = height_pt / Decimal(72.0)
try:
contentstream = pypdf.pdf.ContentStream(page.getContents(), pdf)
except AttributeError as e:
return pageinfo
contentsinfo = _interpret_contents(contentstream)
pageinfo['images'] = [im for im in _find_page_images(
page, pageinfo, contentsinfo)]
if pageinfo['images']:
xres = max(image['dpi_w'] for image in pageinfo['images'])
yres = max(image['dpi_h'] for image in pageinfo['images'])
pageinfo['xres'], pageinfo['yres'] = xres, yres
pageinfo['width_pixels'] = \
int(round(xres * pageinfo['width_inches']))
pageinfo['height_pixels'] = \
int(round(yres * pageinfo['height_inches']))
return pageinfo
def pdf_get_all_pageinfo(infile):
pdf = pypdf.PdfFileReader(infile)
getcontext().prec = 6
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()
-160
View File
@@ -1,160 +0,0 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
#
# Generate a PDFA_def.ps file for Ghostscript >= 9.14
from __future__ import print_function, absolute_import, division
from string import Template
import codecs
import pkg_resources
import PyPDF2 as pypdf
ICC_PROFILE_RELPATH = 'data/sRGB.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
# files, from the Ghostscript documentation. Lines beginning with % are
# comments. Python substitution variables have a '$' prefix.
pdfa_def_template = u"""%!
% This is a sample prefix file for creating a PDF/A document.
% Feel free to modify entries marked with "Customize".
% This assumes an ICC profile to reside in the file (ISO Coated sb.icc),
% unless the user modifies the corresponding line below.
% Define entries in the document Info dictionary :
/ICCProfile ($icc_profile)
def
[ /Title <$title>
/Author <$author>
/Subject <$subject>
/Keywords <$keywords>
/Creator <$creator>
/DOCINFO pdfmark
% Define an ICC profile :
[/_objdef {icc_PDFA} /type /stream /OBJ pdfmark
[{icc_PDFA}
<<
/N currentpagedevice /ProcessColorModel known {
currentpagedevice /ProcessColorModel get dup /DeviceGray eq
{pop 1} {
/DeviceRGB eq
{3}{4} ifelse
} ifelse
} {
(ERROR, unable to determine ProcessColorModel) == flush
} ifelse
>> /PUT pdfmark
[{icc_PDFA} ICCProfile (r) file /PUT pdfmark
% Define the output intent dictionary :
[/_objdef {OutputIntent_PDFA} /type /dict /OBJ pdfmark
[{OutputIntent_PDFA} <<
/Type /OutputIntent % Must be so (the standard requires).
/S /GTS_PDFA1 % Must be so (the standard requires).
/DestOutputProfile {icc_PDFA} % Must be so (see above).
/OutputConditionIdentifier ($icc_identifier)
>> /PUT pdfmark
[{Catalog} <</OutputIntents [ {OutputIntent_PDFA} ]>> /PUT pdfmark
"""
def encode_text_string(s: str) -> str:
'''Encode text string to hex string for use in a PDF
From PDF 32000-1:2008 a string object may be included in hexademical form
if it is enclosed in angle brackets. For general Unicode the string should
be UTF-16 (big endian) with byte order marks. Many strings including all
ASCII strings fall could be encoded as PdfDocEncoding literals provided
that certain Postscript sequences are escaped. But it's far simpler to
encode everything as UTF-16.
'''
# Sometimes lazy C programmers leave their NULs at the end of strings they
# insert into PDFs
# tests/resources/aspect.pdf is one example (created by ImageMagick)
s = s.replace('\x00', '')
if s == '':
return ''
utf16_bytes = s.encode('utf-16be')
ascii_hex_bytes = codecs.encode(b'\xfe\xff' + utf16_bytes, 'hex')
ascii_hex_str = ascii_hex_bytes.decode('ascii').lower()
return ascii_hex_str
def _get_pdfa_def(icc_profile, icc_identifier, pdfmark):
pdfmark_utf16 = {k: encode_text_string(v) for k, v in pdfmark.items()}
t = Template(pdfa_def_template)
result = t.substitute(icc_profile=icc_profile,
icc_identifier=icc_identifier,
title=pdfmark_utf16.get('/Title', ''),
author=pdfmark_utf16.get('/Author', ''),
subject=pdfmark_utf16.get('/Subject', ''),
creator=pdfmark_utf16.get('/Creator', ''),
keywords=pdfmark_utf16.get('/Keywords', ''))
return result
def generate_pdfa_def(target_filename, pdfmark, icc='sRGB'):
if icc == 'sRGB':
icc_profile = SRGB_ICC_PROFILE
else:
raise NotImplementedError("Only supporting sRGB")
ps = _get_pdfa_def(icc_profile, icc, pdfmark)
# We should have encoded everything to pure ASCII by this point, and
# to be safe, only allow ASCII in PostScript
with open(target_filename, 'w', encoding='ascii') as f:
f.write(ps)
def file_claims_pdfa(filename):
"""Determines if the file claims to be PDF/A compliant
Checking if a file is a truly compliant PDF/A is a massive undertaking
that no open source tool does properly. Some commercial tools are
generally reliable (Acrobat).
This checks if the XMP metadata contains a PDF/A marker.
"""
pdf = pypdf.PdfFileReader(filename)
xmp = pdf.getXmpMetadata()
try:
pdfa_nodes = xmp.getNodesInNamespace(
aboutUri='',
namespace='http://www.aiim.org/pdfa/ns/id/')
except AttributeError:
return {'pass': False, 'output': 'pdf',
'conformance': 'No XMP metadata'}
pdfa_dict = {attr.localName: attr.value for attr in pdfa_nodes}
pdfa_dict['pass'] = False
pdfa_dict['output'] = 'pdf'
if pdfa_dict:
part_conformance = pdfa_dict['part'] + pdfa_dict['conformance']
valid_part_conforms = {'1A', '1B', '2A', '2B', '2U', '3A', '3B', '3U'}
conformance = 'PDF/A-{}'.format(
part_conformance)
if part_conformance in valid_part_conforms:
pdfa_dict['pass'] = True
pdfa_dict['output'] = 'pdfa'
pdfa_dict['conformance'] = conformance
else:
pdfa_dict['conformance'] = 'PDF'
return pdfa_dict
-119
View File
@@ -1,119 +0,0 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
from subprocess import CalledProcessError, check_output, STDOUT, check_call
from functools import lru_cache
import sys
import os
import re
from . import ExitCode, get_program
@lru_cache(maxsize=1)
def version():
args_qpdf = [
get_program('qpdf'),
'--version'
]
try:
versions = check_output(
args_qpdf, close_fds=True, universal_newlines=True,
stderr=STDOUT)
except CalledProcessError:
print("Could not find qpdf executable on system PATH.")
sys.exit(ExitCode.missing_dependency)
qpdf_version = re.match(r'qpdf version (.+)', versions).group(1)
return qpdf_version
def check(input_file, log):
args_qpdf = [
get_program('qpdf'),
'--check',
input_file
]
try:
check_output(args_qpdf, stderr=STDOUT, universal_newlines=True)
except CalledProcessError as e:
if e.returncode == 2:
log.error("{0}: not a valid PDF, and could not repair it.".format(
input_file))
log.error("Details:")
log.error(e.output)
elif e.returncode == 3:
log.info("qpdf --check returned warnings:")
log.info(e.output)
else:
log.warning(e.output)
return False
return True
def repair(input_file, output_file, log):
args_qpdf = [
get_program('qpdf'), input_file, output_file
]
try:
check_output(args_qpdf, stderr=STDOUT, universal_newlines=True)
except CalledProcessError as e:
if e.returncode == 3 and e.output.find("operation succeeded"):
log.debug('qpdf found and fixed errors: ' + e.output)
log.debug(e.output)
return
if e.returncode == 2 and e.output.find("invalid password"):
log.error("{0}: this PDF is password-protected - password must "
"be removed for OCR".format(input_file))
sys.exit(ExitCode.input_file)
elif e.returncode == 2:
log.error("{0}: not a valid PDF, and could not repair it.".format(
input_file))
log.error("Details: " + e.output)
sys.exit(ExitCode.input_file)
else:
log.error("{0}: unknown error".format(
input_file))
log.error(e.output)
sys.exit(ExitCode.unknown)
def get_npages(input_file, log):
try:
pages = check_output(
[get_program('qpdf'), '--show-npages', input_file],
universal_newlines=True, close_fds=True)
except CalledProcessError as e:
if e.returncode == 2 and e.output.find('No such file'):
log.error(e.output)
sys.exit(ExitCode.input_file)
return int(pages)
def split_pages(input_file, work_folder, npages):
"""Split multipage PDF into individual pages.
Incredibly enough, this multiple process approach is about 70 times
faster than using Ghostscript.
"""
for n in range(int(npages)):
args_qpdf = [
get_program('qpdf'), input_file,
'--pages', input_file, '{0}'.format(n + 1), '--',
os.path.join(work_folder, '{0:06d}.page.pdf'.format(n + 1))
]
check_call(args_qpdf)
def merge(input_files, output_file):
"""Merge the list of input files (all filenames) into the output file.
The input files may contain one or more pages.
"""
args_qpdf = [
get_program('qpdf'), input_files[0], '--pages'
] + input_files + ['--', output_file]
check_call(args_qpdf)
-256
View File
@@ -1,256 +0,0 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
import sys
import os
import re
import shutil
from functools import lru_cache
from . import ExitCode, get_program, page_number
from collections import namedtuple
from subprocess import Popen, PIPE, CalledProcessError, \
TimeoutExpired, check_output, STDOUT, DEVNULL
OrientationConfidence = namedtuple(
'OrientationConfidence',
('angle', 'confidence'))
HOCR_TEMPLATE = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name='ocr-system' content='tesseract 3.02.02' />
<meta name='ocr-capabilities' content='ocr_page ocr_carea ocr_par ocr_line ocrx_word'/>
</head>
<body>
<div class='ocr_page' id='page_1' title='image "x.tif"; bbox 0 0 {0} {1}; ppageno 0'>
<div class='ocr_carea' id='block_1_1' title="bbox 0 1 {0} {1}">
<p class='ocr_par' dir='ltr' id='par_1' title="bbox 0 1 {0} {1}">
<span class='ocr_line' id='line_1' title="bbox 0 1 {0} {1}"><span class='ocrx_word' id='word_1' title="bbox 0 1 {0} {1}"> </span>
</span>
</p>
</div>
</div>
</body>
</html>'''
@lru_cache(maxsize=1)
def version():
args_tess = [
get_program('tesseract'),
'--version'
]
try:
versions = check_output(
args_tess, close_fds=True, universal_newlines=True,
stderr=STDOUT)
except CalledProcessError:
print("Could not find Tesseract executable on system PATH.")
sys.exit(ExitCode.missing_dependency)
tesseract_version = re.match(r'tesseract\s(.+)', versions).group(1)
return tesseract_version
@lru_cache(maxsize=1)
def languages():
args_tess = [
get_program('tesseract'),
'--list-langs'
]
try:
langs = check_output(
args_tess, close_fds=True, universal_newlines=True,
stderr=STDOUT)
except CalledProcessError as e:
print("Tesseract failed to report available languages.")
print("Output from Tesseract:")
print("-" * 40)
print(e.output)
sys.exit(ExitCode.missing_dependency)
return set(lang.strip() for lang in langs.splitlines()[1:])
def get_orientation(input_file, language: list, timeout: float, log):
args_tesseract = [
get_program('tesseract'),
'-l', '+'.join(language),
'-psm', '0',
input_file,
'stdout'
]
try:
stdout = check_output(
args_tesseract, close_fds=True, stderr=STDOUT,
universal_newlines=True, timeout=timeout)
except TimeoutExpired:
return OrientationConfidence(angle=0, confidence=0.0)
except CalledProcessError as e:
tesseract_log_output(log, e.output, input_file)
if ('Too few characters. Skipping this page' in e.output or
'Image too large' in e.output):
return OrientationConfidence(0, 0)
raise e from e
else:
osd = {}
for line in stdout.splitlines():
line = line.strip()
parts = line.split(':', maxsplit=2)
if len(parts) == 2:
osd[parts[0].strip()] = parts[1].strip()
angle = int(osd.get('Orientation in degrees', 0))
if 'Orientation' in osd:
# Tesseract < 3.04.01
# reports "Orientation in degrees" as a counterclockwise angle
# We keep it clockwise
assert 'Rotate' not in osd
angle = -angle % 360
else:
# Tesseract == 3.04.01, hopefully also Tesseract > 3.04.01
# reports "Orientation in degrees" as a clockwise angle
assert 'Rotate' in osd
oc = OrientationConfidence(
angle=angle,
confidence=float(osd.get('Orientation confidence', 0)))
return oc
def tesseract_log_output(log, stdout, input_file):
lines = stdout.splitlines()
prefix = "{0:4d}: [tesseract] ".format(page_number(input_file))
for line in lines:
if line.startswith("Tesseract Open Source"):
continue
elif line.startswith("Warning in pixReadMem"):
continue
elif 'diacritics' in line:
log.warning(prefix + "lots of diacritics - possibly poor OCR")
elif line.startswith('OSD: Weak margin'):
log.warning(prefix + "unsure about page orientation")
elif 'error' in line.lower() or 'exception' in line.lower():
log.error(prefix + line.strip())
else:
log.info(prefix + line.strip())
def page_timedout(log, input_file):
prefix = "{0:4d}: [tesseract] ".format(page_number(input_file))
log.warning(prefix + " took too long to OCR - skipping")
def _generate_null_hocr(output_hocr, pageinfo):
with open(output_hocr, 'w', encoding="utf-8") as f:
f.write(HOCR_TEMPLATE.format(
pageinfo['width_pixels'],
pageinfo['height_pixels']))
def generate_hocr(input_file, output_hocr, language: list, tessconfig: list,
timeout: float, pageinfo_getter, pagesegmode: int, log):
badxml = os.path.splitext(output_hocr)[0] + '.badxml'
args_tesseract = [
get_program('tesseract'),
'-l', '+'.join(language)
]
if pagesegmode is not None:
args_tesseract.extend(['-psm', str(pagesegmode)])
args_tesseract.extend([
input_file,
badxml,
'hocr'
] + tessconfig)
try:
stdout = check_output(
args_tesseract, close_fds=True, stderr=STDOUT,
universal_newlines=True, timeout=timeout)
except TimeoutExpired:
# 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)
_generate_null_hocr(output_hocr, pageinfo_getter())
except CalledProcessError as e:
tesseract_log_output(log, e.output, input_file)
if 'Image too large' in e.output:
_generate_null_hocr(output_hocr, pageinfo_getter())
return
raise e from e
else:
tesseract_log_output(log, stdout, input_file)
if os.path.exists(badxml + '.html'):
# Tesseract 3.02 appends suffix ".html" on its own (.badxml.html)
shutil.move(badxml + '.html', badxml)
elif os.path.exists(badxml + '.hocr'):
# Tesseract 3.03 appends suffix ".hocr" on its own (.badxml.hocr)
shutil.move(badxml + '.hocr', badxml)
# Tesseract 3.03 inserts source filename into hocr file without
# escaping it, creating invalid XML and breaking the parser.
# As a workaround, rewrite the hocr file, replacing the filename
# with a space. Don't know if Tesseract 3.02 does the same.
regex_nested_single_quotes = re.compile(
r"""title='image "([^"]*)";""")
with open(badxml, mode='r', encoding='utf-8') as f_in, \
open(output_hocr, mode='w', encoding='utf-8') as f_out:
for line in f_in:
line = regex_nested_single_quotes.sub(
r"""title='image " ";""", line)
f_out.write(line)
def generate_pdf(input_image, skip_pdf, output_pdf, language: list,
tessconfig: list, timeout: float, pagesegmode: int, log):
'''Use Tesseract to render a PDF.
input_image -- image to analyze
skip_pdf -- if we time out, use this file as output
language -- list of languages to consider
tessconfig -- tesseract configuration
timeout -- timeout (seconds)
log -- logger object
'''
args_tesseract = [
get_program('tesseract'),
'-l', '+'.join(language)
]
if pagesegmode is not None:
args_tesseract.extend(['-psm', str(pagesegmode)])
args_tesseract.extend([
input_image,
os.path.splitext(output_pdf)[0], # Tesseract appends suffix
'pdf'
] + tessconfig)
try:
stdout = check_output(
args_tesseract, close_fds=True, stderr=STDOUT,
universal_newlines=True, timeout=timeout)
except TimeoutExpired:
page_timedout(log, input_image)
shutil.copy(skip_pdf, output_pdf)
except CalledProcessError as e:
tesseract_log_output(log, e.output, input_image)
if 'Image too large' in e.output:
shutil.copy(skip_pdf, output_pdf)
return
raise e from e
else:
tesseract_log_output(log, stdout, input_image)
-103
View File
@@ -1,103 +0,0 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
# unpaper documentation:
# https://github.com/Flameeyes/unpaper/blob/master/doc/basic-concepts.md
from subprocess import CalledProcessError, STDOUT, check_output, check_call
from tempfile import NamedTemporaryFile
import sys
import os
from functools import lru_cache
from . import ExitCode, get_program
@lru_cache(maxsize=1)
def version():
args_unpaper = [
get_program('unpaper'),
'--version'
]
version = check_output(
args_unpaper, close_fds=True, universal_newlines=True,
stderr=STDOUT, timeout=5)
return version.strip()
try:
from PIL import Image
except ImportError:
print("Could not find Python3 imaging library", file=sys.stderr)
raise
def run(input_file, output_file, dpi, log, mode_args):
args_unpaper = [
get_program('unpaper'),
'-v',
'--dpi', str(dpi)
] + mode_args
SUFFIXES = {'1': '.pbm', 'L': '.pgm', 'RGB': '.ppm'}
im = Image.open(input_file)
if im.mode not in SUFFIXES.keys():
log.info("Converting image to other colorspace")
try:
if im.mode == 'P' and len(im.getcolors()) == 2:
im = im.convert(mode='1')
else:
im = im.convert(mode='RGB')
except IOError:
log.error(
"Could not convert image with type " + im.mode)
sys.exit(ExitCode.missing_dependency)
try:
suffix = SUFFIXES[im.mode]
except KeyError:
log.error(
"Failed to convert image to a supported format.")
sys.exit(ExitCode.missing_dependency)
with NamedTemporaryFile(suffix=suffix) as input_pnm, \
NamedTemporaryFile(suffix=suffix, mode="r+b") as output_pnm:
im.save(input_pnm, format='PPM')
im.close()
os.unlink(output_pnm.name)
args_unpaper.extend([input_pnm.name, output_pnm.name])
try:
stdout = check_output(
args_unpaper, close_fds=True,
universal_newlines=True, stderr=STDOUT,
)
except CalledProcessError as e:
log.debug(e.output)
raise e from e
else:
log.debug(stdout)
Image.open(output_pnm.name).save(output_file)
def deskew(input_file, output_file, dpi, log):
run(input_file, output_file, dpi, log, [
'--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-noisefilter', # don't remove salt and pepper noise
'--no-blurfilter' # don't remove blurry objects/debris
])
def clean(input_file, output_file, dpi, log):
run(input_file, output_file, dpi, log, [
'--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
])
-338
View File
@@ -1,338 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
-->
<!-- Title: Pipeline: Pages: 1 -->
<svg width="1444pt" height="973pt"
viewBox="0.00 0.00 1444.00 973.18" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 969.181)">
<title>Pipeline:</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-969.181 1440,-969.181 1440,4 -4,4"/>
<g id="clust1" class="cluster"><title>clustertasks</title>
<polygon fill="none" stroke="black" points="8,-8 8,-957.181 1428,-957.181 1428,-8 8,-8"/>
<text text-anchor="middle" x="718" y="-929.181" font-family="Times,serif" font-size="30.00" fill="#ff3232">Pipeline:</text>
</g>
<!-- t0 -->
<g id="node1" class="node"><title>t0</title>
<polygon fill="#efa03b" stroke="black" points="1219.77,-911.181 1034.23,-911.181 1030.23,-907.181 1030.23,-875.181 1215.77,-875.181 1219.77,-879.181 1219.77,-911.181"/>
<polyline fill="none" stroke="black" points="1215.77,-907.181 1030.23,-907.181 "/>
<polyline fill="none" stroke="black" points="1215.77,-907.181 1215.77,-875.181 "/>
<polyline fill="none" stroke="black" points="1215.77,-907.181 1219.77,-911.181 "/>
<text text-anchor="middle" x="1125" y="-887.181" font-family="Times,serif" font-size="20.00">triage</text>
</g>
<!-- t1 -->
<g id="node2" class="node"><title>t1</title>
<polygon fill="#efa03b" stroke="black" points="1238.54,-853.181 1015.46,-853.181 1011.46,-849.181 1011.46,-817.181 1234.54,-817.181 1238.54,-821.181 1238.54,-853.181"/>
<polyline fill="none" stroke="black" points="1234.54,-849.181 1011.46,-849.181 "/>
<polyline fill="none" stroke="black" points="1234.54,-849.181 1234.54,-817.181 "/>
<polyline fill="none" stroke="black" points="1234.54,-849.181 1238.54,-853.181 "/>
<text text-anchor="middle" x="1125" y="-829.181" font-family="Times,serif" font-size="20.00">repair_pdf</text>
</g>
<!-- t0&#45;&gt;t1 -->
<g id="edge1" class="edge"><title>t0&#45;&gt;t1</title>
<path fill="none" stroke="#0044a0" d="M1125,-875.075C1125,-871.384 1125,-867.394 1125,-863.424"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1128.5,-863.182 1125,-853.182 1121.5,-863.182 1128.5,-863.182"/>
</g>
<!-- t2 -->
<g id="node3" class="node"><title>t2</title>
<polygon fill="#efa03b" stroke="black" points="1329.11,-774.253 1125,-791.156 920.888,-774.253 921.078,-746.905 1328.92,-746.905 1329.11,-774.253"/>
<polygon fill="none" stroke="black" points="1333.13,-777.932 1125,-795.167 916.866,-777.932 917.11,-742.906 1332.89,-742.906 1333.13,-777.932"/>
<text text-anchor="middle" x="1125" y="-760.694" font-family="Times,serif" font-size="20.00">split_pages</text>
</g>
<!-- t1&#45;&gt;t2 -->
<g id="edge2" class="edge"><title>t1&#45;&gt;t2</title>
<path fill="none" stroke="#0044a0" d="M1125,-817.059C1125,-813.44 1125,-809.484 1125,-805.438"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1128.5,-805.334 1125,-795.334 1121.5,-805.334 1128.5,-805.334"/>
</g>
<!-- t15 -->
<g id="node17" class="node"><title>t15</title>
<polygon fill="#efa03b" stroke="black" points="632.082,-716.208 293.918,-716.208 289.918,-712.208 289.918,-680.208 628.082,-680.208 632.082,-684.208 632.082,-716.208"/>
<polyline fill="none" stroke="black" points="628.082,-712.208 289.918,-712.208 "/>
<polyline fill="none" stroke="black" points="628.082,-712.208 628.082,-680.208 "/>
<polyline fill="none" stroke="black" points="628.082,-712.208 632.082,-716.208 "/>
<text text-anchor="middle" x="461" y="-692.208" font-family="Times,serif" font-size="20.00">generate_postscript_stub</text>
</g>
<!-- t1&#45;&gt;t15 -->
<g id="edge23" class="edge"><title>t1&#45;&gt;t15</title>
<path fill="none" stroke="#0044a0" d="M1026.3,-817.165C989.157,-810.572 946.587,-802.788 908,-795.181 781.899,-770.319 636.479,-738.51 546.633,-718.49"/>
<polygon fill="#0044a0" stroke="#0044a0" points="547.289,-715.05 536.767,-716.289 545.765,-721.882 547.289,-715.05"/>
</g>
<!-- t18 -->
<g id="node19" class="node"><title>t18</title>
<polygon fill="#efa03b" stroke="black" points="1025.79,-156.452 1221,-129.134 1416.21,-156.452 1416.03,-200.654 1025.97,-200.654 1025.79,-156.452"/>
<polygon fill="none" stroke="black" points="1021.78,-152.971 1221,-125.091 1420.22,-152.971 1420.01,-204.657 1021.99,-204.657 1021.78,-152.971"/>
<text text-anchor="middle" x="1221" y="-162.669" font-family="Times,serif" font-size="20.00">merge_pages_qpdf</text>
</g>
<!-- t1&#45;&gt;t18 -->
<g id="edge33" class="edge"><title>t1&#45;&gt;t18</title>
<path fill="none" stroke="#0044a0" d="M1238.67,-826.639C1321.4,-814.236 1419,-782.049 1419,-699.208 1419,-699.208 1419,-699.208 1419,-319.208 1419,-280.294 1424,-263.161 1398,-234.208 1389.64,-224.894 1379.82,-216.923 1369.17,-210.101"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1370.83,-207.019 1360.46,-204.872 1367.23,-213.02 1370.83,-207.019"/>
</g>
<!-- t3 -->
<g id="node4" class="node"><title>t3</title>
<polygon fill="#efa03b" stroke="black" points="1186.88,-716.208 905.117,-716.208 901.117,-712.208 901.117,-680.208 1182.88,-680.208 1186.88,-684.208 1186.88,-716.208"/>
<polyline fill="none" stroke="black" points="1182.88,-712.208 901.117,-712.208 "/>
<polyline fill="none" stroke="black" points="1182.88,-712.208 1182.88,-680.208 "/>
<polyline fill="none" stroke="black" points="1182.88,-712.208 1186.88,-716.208 "/>
<text text-anchor="middle" x="1044" y="-692.208" font-family="Times,serif" font-size="20.00">rasterize_preview</text>
</g>
<!-- t2&#45;&gt;t3 -->
<g id="edge3" class="edge"><title>t2&#45;&gt;t3</title>
<path fill="none" stroke="#0044a0" d="M1097.12,-742.813C1089.24,-736.345 1080.66,-729.296 1072.73,-722.788"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1074.71,-719.888 1064.76,-716.249 1070.27,-725.299 1074.71,-719.888"/>
</g>
<!-- t4 -->
<g id="node5" class="node"><title>t4</title>
<polygon fill="#efa03b" stroke="black" points="1258.15,-658.208 1023.85,-658.208 1019.85,-654.208 1019.85,-622.208 1254.15,-622.208 1258.15,-626.208 1258.15,-658.208"/>
<polyline fill="none" stroke="black" points="1254.15,-654.208 1019.85,-654.208 "/>
<polyline fill="none" stroke="black" points="1254.15,-654.208 1254.15,-622.208 "/>
<polyline fill="none" stroke="black" points="1254.15,-654.208 1258.15,-658.208 "/>
<text text-anchor="middle" x="1139" y="-634.208" font-family="Times,serif" font-size="20.00">orient_page</text>
</g>
<!-- t2&#45;&gt;t4 -->
<g id="edge5" class="edge"><title>t2&#45;&gt;t4</title>
<path fill="none" stroke="#0044a0" d="M1171.65,-742.795C1181.42,-735.692 1190.44,-726.864 1196,-716.208 1203.4,-702.023 1203.46,-694.36 1196,-680.208 1192.84,-674.222 1188.31,-668.98 1183.15,-664.445"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1185.3,-661.676 1175.25,-658.302 1181,-667.203 1185.3,-661.676"/>
</g>
<!-- t3&#45;&gt;t4 -->
<g id="edge4" class="edge"><title>t3&#45;&gt;t4</title>
<path fill="none" stroke="#0044a0" d="M1073.02,-680.102C1081.95,-674.836 1091.92,-668.963 1101.34,-663.407"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1103.32,-666.302 1110.16,-658.209 1099.77,-660.272 1103.32,-666.302"/>
</g>
<!-- t5 -->
<g id="node6" class="node"><title>t5</title>
<polygon fill="#efa03b" stroke="black" points="1271.3,-600.208 920.701,-600.208 916.701,-596.208 916.701,-564.208 1267.3,-564.208 1271.3,-568.208 1271.3,-600.208"/>
<polyline fill="none" stroke="black" points="1267.3,-596.208 916.701,-596.208 "/>
<polyline fill="none" stroke="black" points="1267.3,-596.208 1267.3,-564.208 "/>
<polyline fill="none" stroke="black" points="1267.3,-596.208 1271.3,-600.208 "/>
<text text-anchor="middle" x="1094" y="-576.208" font-family="Times,serif" font-size="20.00">rasterize_with_ghostscript</text>
</g>
<!-- t4&#45;&gt;t5 -->
<g id="edge6" class="edge"><title>t4&#45;&gt;t5</title>
<path fill="none" stroke="#0044a0" d="M1125.25,-622.102C1121.76,-617.759 1117.94,-613.003 1114.21,-608.353"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1116.65,-605.812 1107.66,-600.209 1111.2,-610.197 1116.65,-605.812"/>
</g>
<!-- t10 -->
<g id="node12" class="node"><title>t10</title>
<polygon fill="#00cc66" stroke="black" points="334,-348.208 35.7812,-320.208 334,-292.208 632.219,-320.208 334,-348.208"/>
<text text-anchor="middle" x="334" y="-314.208" font-family="Times,serif" font-size="20.00">select_image_layer</text>
</g>
<!-- t4&#45;&gt;t10 -->
<g id="edge15" class="edge"><title>t4&#45;&gt;t10</title>
<path fill="none" stroke="#0044a0" d="M1019.87,-634.085C799.18,-622.991 349,-592.156 349,-525.208 349,-525.208 349,-525.208 349,-465.208 349,-428.606 344.147,-387.052 339.961,-358.022"/>
<polygon fill="#0044a0" stroke="#0044a0" points="343.393,-357.303 338.46,-347.926 336.469,-358.332 343.393,-357.303"/>
</g>
<!-- t16 -->
<g id="node15" class="node"><title>t16</title>
<polygon fill="#efa03b" stroke="black" points="1380.34,-270.208 1159.66,-270.208 1155.66,-266.208 1155.66,-234.208 1376.34,-234.208 1380.34,-238.208 1380.34,-270.208"/>
<polyline fill="none" stroke="black" points="1376.34,-266.208 1155.66,-266.208 "/>
<polyline fill="none" stroke="black" points="1376.34,-266.208 1376.34,-234.208 "/>
<polyline fill="none" stroke="black" points="1376.34,-266.208 1380.34,-270.208 "/>
<text text-anchor="middle" x="1268" y="-246.208" font-family="Times,serif" font-size="20.00">skip_page</text>
</g>
<!-- t4&#45;&gt;t16 -->
<g id="edge20" class="edge"><title>t4&#45;&gt;t16</title>
<path fill="none" stroke="#0044a0" d="M1258.35,-631.473C1326.7,-619.809 1399,-591.745 1399,-525.208 1399,-525.208 1399,-525.208 1399,-397.208 1399,-350.33 1418.37,-328.748 1389,-292.208 1383.58,-285.469 1376.99,-279.879 1369.71,-275.244"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1371.3,-272.123 1360.88,-270.231 1367.85,-278.211 1371.3,-272.123"/>
</g>
<!-- t14 -->
<g id="node16" class="node"><title>t14</title>
<polygon fill="#66ccff" stroke="black" points="1380.24,-338.208 999.76,-338.208 995.76,-334.208 995.76,-302.208 1376.24,-302.208 1380.24,-306.208 1380.24,-338.208"/>
<polyline fill="none" stroke="black" points="1376.24,-334.208 995.76,-334.208 "/>
<polyline fill="none" stroke="black" points="1376.24,-334.208 1376.24,-302.208 "/>
<polyline fill="none" stroke="black" points="1376.24,-334.208 1380.24,-338.208 "/>
<text text-anchor="middle" x="1188" y="-314.208" font-family="Times,serif" font-size="20.00">tesseract_ocr_and_render_pdf</text>
</g>
<!-- t4&#45;&gt;t14 -->
<g id="edge22" class="edge"><title>t4&#45;&gt;t14</title>
<path fill="none" stroke="#0044a0" d="M1244.5,-622.179C1297.25,-607.003 1350,-578.503 1350,-525.208 1350,-525.208 1350,-525.208 1350,-465.208 1350,-422.752 1366.76,-403.169 1340,-370.208 1330.4,-358.377 1317.91,-349.368 1304.22,-342.513"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1305.62,-339.304 1295.07,-338.313 1302.7,-345.666 1305.62,-339.304"/>
</g>
<!-- t6 -->
<g id="node7" class="node"><title>t6</title>
<polygon fill="#efa03b" stroke="black" points="1074.74,-542.208 779.258,-542.208 775.258,-538.208 775.258,-506.208 1070.74,-506.208 1074.74,-510.208 1074.74,-542.208"/>
<polyline fill="none" stroke="black" points="1070.74,-538.208 775.258,-538.208 "/>
<polyline fill="none" stroke="black" points="1070.74,-538.208 1070.74,-506.208 "/>
<polyline fill="none" stroke="black" points="1070.74,-538.208 1074.74,-542.208 "/>
<text text-anchor="middle" x="925" y="-518.208" font-family="Times,serif" font-size="20.00">preprocess_deskew</text>
</g>
<!-- t5&#45;&gt;t6 -->
<g id="edge7" class="edge"><title>t5&#45;&gt;t6</title>
<path fill="none" stroke="#0044a0" d="M1042.38,-564.102C1024.52,-558.185 1004.35,-551.5 985.823,-545.362"/>
<polygon fill="#0044a0" stroke="#0044a0" points="986.901,-542.032 976.308,-542.209 984.699,-548.677 986.901,-542.032"/>
</g>
<!-- t9 -->
<g id="node11" class="node"><title>t9</title>
<polygon fill="#efa03b" stroke="black" points="1013,-426.208 694.762,-398.208 1013,-370.208 1331.24,-398.208 1013,-426.208"/>
<text text-anchor="middle" x="1013" y="-392.208" font-family="Times,serif" font-size="20.00">select_image_for_pdf</text>
</g>
<!-- t5&#45;&gt;t9 -->
<g id="edge13" class="edge"><title>t5&#45;&gt;t9</title>
<path fill="none" stroke="#0044a0" d="M1093.89,-563.996C1093.29,-548.44 1091.11,-525.146 1084,-506.208 1073.94,-479.426 1056.13,-452.609 1040.86,-432.521"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1043.62,-430.369 1034.72,-424.619 1038.09,-434.663 1043.62,-430.369"/>
</g>
<!-- t7 -->
<g id="node8" class="node"><title>t7</title>
<polygon fill="#efa03b" stroke="black" points="993.95,-484.208 716.05,-484.208 712.05,-480.208 712.05,-448.208 989.95,-448.208 993.95,-452.208 993.95,-484.208"/>
<polyline fill="none" stroke="black" points="989.95,-480.208 712.05,-480.208 "/>
<polyline fill="none" stroke="black" points="989.95,-480.208 989.95,-448.208 "/>
<polyline fill="none" stroke="black" points="989.95,-480.208 993.95,-484.208 "/>
<text text-anchor="middle" x="853" y="-460.208" font-family="Times,serif" font-size="20.00">preprocess_clean</text>
</g>
<!-- t6&#45;&gt;t7 -->
<g id="edge8" class="edge"><title>t6&#45;&gt;t7</title>
<path fill="none" stroke="#0044a0" d="M903.006,-506.102C896.726,-501.216 889.771,-495.807 883.095,-490.615"/>
<polygon fill="#0044a0" stroke="#0044a0" points="884.901,-487.586 874.859,-484.209 880.604,-493.111 884.901,-487.586"/>
</g>
<!-- t6&#45;&gt;t9 -->
<g id="edge12" class="edge"><title>t6&#45;&gt;t9</title>
<path fill="none" stroke="#0044a0" d="M977.454,-506.003C987.329,-500.504 996.562,-493.377 1003,-484.208 1012.67,-470.441 1015.74,-452.229 1016.16,-436.195"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1019.66,-436.018 1016.09,-426.041 1012.66,-436.063 1019.66,-436.018"/>
</g>
<!-- t8 -->
<g id="node9" class="node"><title>t8</title>
<polygon fill="#00cc66" stroke="black" points="676.666,-416.208 387.334,-416.208 383.334,-412.208 383.334,-380.208 672.666,-380.208 676.666,-384.208 676.666,-416.208"/>
<polyline fill="none" stroke="black" points="672.666,-412.208 383.334,-412.208 "/>
<polyline fill="none" stroke="black" points="672.666,-412.208 672.666,-380.208 "/>
<polyline fill="none" stroke="black" points="672.666,-412.208 676.666,-416.208 "/>
<text text-anchor="middle" x="530" y="-392.208" font-family="Times,serif" font-size="20.00">ocr_tesseract_hocr</text>
</g>
<!-- t7&#45;&gt;t8 -->
<g id="edge9" class="edge"><title>t7&#45;&gt;t8</title>
<path fill="none" stroke="#0044a0" d="M769.399,-448.125C724.645,-438.98 669.394,-427.691 623.252,-418.262"/>
<polygon fill="#0044a0" stroke="#0044a0" points="623.748,-414.791 613.25,-416.219 622.347,-421.65 623.748,-414.791"/>
</g>
<!-- t7&#45;&gt;t9 -->
<g id="edge11" class="edge"><title>t7&#45;&gt;t9</title>
<path fill="none" stroke="#0044a0" d="M894.62,-448.039C911.413,-441.112 931.162,-432.966 949.666,-425.333"/>
<polygon fill="#0044a0" stroke="#0044a0" points="951.009,-428.565 958.918,-421.516 948.339,-422.094 951.009,-428.565"/>
</g>
<!-- t11 -->
<g id="node10" class="node"><title>t11</title>
<polygon fill="#00cc66" stroke="black" points="938.109,-338.208 653.891,-338.208 649.891,-334.208 649.891,-302.208 934.109,-302.208 938.109,-306.208 938.109,-338.208"/>
<polyline fill="none" stroke="black" points="934.109,-334.208 649.891,-334.208 "/>
<polyline fill="none" stroke="black" points="934.109,-334.208 934.109,-302.208 "/>
<polyline fill="none" stroke="black" points="934.109,-334.208 938.109,-338.208 "/>
<text text-anchor="middle" x="794" y="-314.208" font-family="Times,serif" font-size="20.00">render_hocr_page</text>
</g>
<!-- t8&#45;&gt;t11 -->
<g id="edge10" class="edge"><title>t8&#45;&gt;t11</title>
<path fill="none" stroke="#0044a0" d="M589.247,-380.152C629.442,-368.58 682.727,-353.241 724.863,-341.111"/>
<polygon fill="#0044a0" stroke="#0044a0" points="725.853,-344.468 734.495,-338.338 723.917,-337.741 725.853,-344.468"/>
</g>
<!-- t12 -->
<g id="node14" class="node"><title>t12</title>
<polygon fill="#00cc66" stroke="black" points="1118.49,-270.208 775.514,-270.208 771.514,-266.208 771.514,-234.208 1114.49,-234.208 1118.49,-238.208 1118.49,-270.208"/>
<polyline fill="none" stroke="black" points="1114.49,-266.208 771.514,-266.208 "/>
<polyline fill="none" stroke="black" points="1114.49,-266.208 1114.49,-234.208 "/>
<polyline fill="none" stroke="black" points="1114.49,-266.208 1118.49,-270.208 "/>
<text text-anchor="middle" x="945" y="-246.208" font-family="Times,serif" font-size="20.00">render_hocr_debug_page</text>
</g>
<!-- t8&#45;&gt;t12 -->
<g id="edge19" class="edge"><title>t8&#45;&gt;t12</title>
<path fill="none" stroke="#0044a0" d="M620.537,-380.161C641.947,-376.512 664.736,-372.939 686,-370.208 714.866,-366.5 927.304,-369.633 947,-348.208 963.502,-330.256 960.281,-301.363 954.611,-280.149"/>
<polygon fill="#0044a0" stroke="#0044a0" points="957.925,-279.012 951.707,-270.433 951.218,-281.017 957.925,-279.012"/>
</g>
<!-- t13 -->
<g id="node13" class="node"><title>t13</title>
<polygon fill="#00cc66" stroke="black" points="753.082,-270.208 494.918,-270.208 490.918,-266.208 490.918,-234.208 749.082,-234.208 753.082,-238.208 753.082,-270.208"/>
<polyline fill="none" stroke="black" points="749.082,-266.208 490.918,-266.208 "/>
<polyline fill="none" stroke="black" points="749.082,-266.208 749.082,-234.208 "/>
<polyline fill="none" stroke="black" points="749.082,-266.208 753.082,-270.208 "/>
<text text-anchor="middle" x="622" y="-246.208" font-family="Times,serif" font-size="20.00">add_text_layer</text>
</g>
<!-- t11&#45;&gt;t13 -->
<g id="edge16" class="edge"><title>t11&#45;&gt;t13</title>
<path fill="none" stroke="#0044a0" d="M749.258,-302.039C726.862,-293.446 699.574,-282.975 676.019,-273.936"/>
<polygon fill="#0044a0" stroke="#0044a0" points="677.108,-270.605 666.518,-270.29 674.6,-277.14 677.108,-270.605"/>
</g>
<!-- t9&#45;&gt;t10 -->
<g id="edge14" class="edge"><title>t9&#45;&gt;t10</title>
<path fill="none" stroke="#0044a0" d="M877.633,-382.056C761.19,-369.023 593.788,-350.286 475.578,-337.054"/>
<polygon fill="#0044a0" stroke="#0044a0" points="475.697,-333.546 465.37,-335.912 474.919,-340.503 475.697,-333.546"/>
</g>
<!-- t9&#45;&gt;t12 -->
<g id="edge18" class="edge"><title>t9&#45;&gt;t12</title>
<path fill="none" stroke="#0044a0" d="M998.887,-371.447C994.931,-364.003 990.702,-355.831 987,-348.208 983.822,-341.663 967.96,-305.616 956.669,-279.865"/>
<polygon fill="#0044a0" stroke="#0044a0" points="959.82,-278.337 952.601,-270.582 953.409,-281.146 959.82,-278.337"/>
</g>
<!-- t9&#45;&gt;t14 -->
<g id="edge21" class="edge"><title>t9&#45;&gt;t14</title>
<path fill="none" stroke="#0044a0" d="M1065.03,-374.611C1088.59,-364.381 1116.27,-352.359 1139.4,-342.314"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1140.9,-345.479 1148.68,-338.285 1138.11,-339.058 1140.9,-345.479"/>
</g>
<!-- t10&#45;&gt;t13 -->
<g id="edge17" class="edge"><title>t10&#45;&gt;t13</title>
<path fill="none" stroke="#0044a0" d="M417.688,-300.029C455.345,-291.399 499.718,-281.231 537.473,-272.578"/>
<polygon fill="#0044a0" stroke="#0044a0" points="538.461,-275.943 547.427,-270.297 536.898,-269.12 538.461,-275.943"/>
</g>
<!-- t17 -->
<g id="node18" class="node"><title>t17</title>
<polygon fill="#efa03b" stroke="black" points="540.724,-156.455 770,-129.143 999.276,-156.455 999.062,-200.646 540.938,-200.646 540.724,-156.455"/>
<polygon fill="none" stroke="black" points="536.704,-152.903 770,-125.112 1003.3,-152.903 1003.05,-204.648 536.954,-204.648 536.704,-152.903"/>
<text text-anchor="middle" x="770" y="-162.669" font-family="Times,serif" font-size="20.00">merge_pages_ghostscript</text>
</g>
<!-- t13&#45;&gt;t17 -->
<g id="edge27" class="edge"><title>t13&#45;&gt;t17</title>
<path fill="none" stroke="#0044a0" d="M653.026,-234.114C665.992,-226.971 681.653,-218.343 697.24,-209.755"/>
<polygon fill="#0044a0" stroke="#0044a0" points="699.319,-212.606 706.388,-204.715 695.941,-206.475 699.319,-212.606"/>
</g>
<!-- t13&#45;&gt;t18 -->
<g id="edge31" class="edge"><title>t13&#45;&gt;t18</title>
<path fill="none" stroke="#0044a0" d="M753.329,-235.235C756.584,-234.884 759.812,-234.541 763,-234.208 873.497,-222.668 902.059,-228.201 1012,-212.208 1023.74,-210.5 1035.81,-208.554 1047.95,-206.456"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1048.91,-209.84 1058.16,-204.659 1047.7,-202.946 1048.91,-209.84"/>
</g>
<!-- t12&#45;&gt;t17 -->
<g id="edge28" class="edge"><title>t12&#45;&gt;t17</title>
<path fill="none" stroke="#0044a0" d="M908.314,-234.114C892.552,-226.77 873.424,-217.858 854.484,-209.033"/>
<polygon fill="#0044a0" stroke="#0044a0" points="855.759,-205.766 845.216,-204.715 852.803,-212.111 855.759,-205.766"/>
</g>
<!-- t12&#45;&gt;t18 -->
<g id="edge32" class="edge"><title>t12&#45;&gt;t18</title>
<path fill="none" stroke="#0044a0" d="M1002.86,-234.114C1029.07,-226.37 1061.19,-216.881 1092.64,-207.59"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1093.77,-210.905 1102.37,-204.715 1091.79,-204.192 1093.77,-210.905"/>
</g>
<!-- t16&#45;&gt;t17 -->
<g id="edge24" class="edge"><title>t16&#45;&gt;t17</title>
<path fill="none" stroke="#0044a0" d="M1155.57,-234.546C1111.13,-227.821 1059.63,-219.853 1013,-212.208 1001.53,-210.326 989.752,-208.365 977.872,-206.363"/>
<polygon fill="#0044a0" stroke="#0044a0" points="978.316,-202.888 967.872,-204.672 977.149,-209.79 978.316,-202.888"/>
</g>
<!-- t16&#45;&gt;t18 -->
<g id="edge29" class="edge"><title>t16&#45;&gt;t18</title>
<path fill="none" stroke="#0044a0" d="M1258.03,-233.915C1254.58,-227.933 1250.55,-220.928 1246.43,-213.785"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1249.24,-211.645 1241.21,-204.729 1243.17,-215.141 1249.24,-211.645"/>
</g>
<!-- t14&#45;&gt;t17 -->
<g id="edge26" class="edge"><title>t14&#45;&gt;t17</title>
<path fill="none" stroke="#0044a0" d="M1172.36,-302.154C1164.33,-293.013 1154.66,-281.362 1147,-270.208 1136.64,-255.121 1141.88,-244.862 1127,-234.208 1125.19,-232.915 1055.67,-220.232 978.45,-206.446"/>
<polygon fill="#0044a0" stroke="#0044a0" points="978.964,-202.982 968.505,-204.672 977.735,-209.874 978.964,-202.982"/>
</g>
<!-- t14&#45;&gt;t18 -->
<g id="edge30" class="edge"><title>t14&#45;&gt;t18</title>
<path fill="none" stroke="#0044a0" d="M1310.22,-302.184C1345.8,-294.579 1378.07,-284.119 1389,-270.208 1408.78,-245.028 1391.25,-224.765 1361.2,-209.211"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1362.48,-205.944 1351.96,-204.742 1359.44,-212.246 1362.48,-205.944"/>
</g>
<!-- t15&#45;&gt;t17 -->
<g id="edge25" class="edge"><title>t15&#45;&gt;t17</title>
<path fill="none" stroke="#0044a0" d="M289.656,-684.797C164.646,-670.853 17,-641.95 17,-583.208 17,-583.208 17,-583.208 17,-319.208 17,-213.417 306.554,-181.327 526.553,-172.213"/>
<polygon fill="#0044a0" stroke="#0044a0" points="526.794,-175.706 536.646,-171.808 526.514,-168.711 526.794,-175.706"/>
</g>
<!-- t19 -->
<g id="node20" class="node"><title>t19</title>
<polygon fill="#efa03b" stroke="black" points="844.361,-47.3406 995,-20.006 1145.64,-47.3406 1145.5,-91.5689 844.501,-91.5689 844.361,-47.3406"/>
<polygon fill="none" stroke="black" points="840.355,-43.9965 995,-15.9349 1149.65,-43.9965 1149.48,-95.5749 840.519,-95.5749 840.355,-43.9965"/>
<text text-anchor="middle" x="995" y="-53.565" font-family="Times,serif" font-size="20.00">copy_final</text>
</g>
<!-- t17&#45;&gt;t19 -->
<g id="edge35" class="edge"><title>t17&#45;&gt;t19</title>
<path fill="none" stroke="#0044a0" d="M841.81,-133.486C864.177,-122.839 889.024,-111.011 912.085,-100.034"/>
<polygon fill="#0044a0" stroke="#0044a0" points="913.64,-103.17 921.165,-95.7118 910.631,-96.8497 913.64,-103.17"/>
</g>
<!-- t18&#45;&gt;t19 -->
<g id="edge34" class="edge"><title>t18&#45;&gt;t19</title>
<path fill="none" stroke="#0044a0" d="M1151.35,-134.66C1128.23,-123.705 1102.26,-111.399 1078.23,-100.009"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1079.66,-96.8131 1069.12,-95.6933 1076.66,-103.139 1079.66,-96.8131"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 23 KiB

-2
View File
@@ -1,2 +0,0 @@
[pytest]
norecursedirs = lib .pc .git
-9
View File
@@ -1,9 +0,0 @@
# requirements.txt can be used to replicate the developer's build environment
# setup.py lists a separate set of requirements that are looser to simplify
# installation
ruffus==2.6.3
Pillow==3.3.0
reportlab==3.2.0
PyPDF2==1.26
img2pdf==0.2.1
cffi==1.5.2
-9
View File
@@ -1,9 +0,0 @@
[bdist_wheel]
python-tag = py34
[aliases]
test=pytest
[check-manifest]
ignore =
.github
-238
View File
@@ -1,238 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# © 2015 James R. Barlow: github.com/jbarlow83
from __future__ import print_function, unicode_literals
import sys
if sys.version_info < (3, 4):
print("Python 3.4 or newer is required", file=sys.stderr)
sys.exit(1)
from setuptools import setup # nopep8
from subprocess import STDOUT, check_output, CalledProcessError # nopep8
from collections.abc import Mapping # nopep8
import re # nopep8
missing_program = '''
The program '{program}' could not be executed or was not found on your
system PATH.
'''
unknown_version = '''
OCRmyPDF requires '{program}' {need_version} or higher. Your system has
'{program}' but we cannot tell what version is installed. Contact the
package maintainer.
'''
old_version = '''
OCRmyPDF requires '{program}' {need_version} or higher. Your system appears
to have {found_version}. Please update this program.
'''
okay_its_optional = '''
This program is OPTIONAL, so installation of OCRmyPDF can proceed, but
some functionality may be missing.
'''
not_okay_its_required = '''
This program is REQUIRED for OCRmyPDF to work. Installation will abort.
'''
osx_install_advice = '''
If you have homebrew installed, try these command to install the missing
packages:
brew update
brew upgrade
brew install {package}
'''
linux_install_advice = '''
On systems with the aptitude package manager (Debian, Ubuntu), try these
commands:
sudo apt-get update
sudo apt-get install {package}
On RPM-based systems (Red Hat, Fedora), search for instructions on
installing the RPM for {program}.
'''
def get_platform():
if sys.platform.startswith('freebsd'):
return 'freebsd'
elif sys.platform.startswith('linux'):
return 'linux'
return sys.platform
def _error_trailer(program, package, optional, **kwargs):
if optional:
print(okay_its_optional.format(**locals()), file=sys.stderr)
else:
print(not_okay_its_required.format(**locals()), file=sys.stderr)
if isinstance(package, Mapping):
package = package[get_platform()]
if get_platform() == 'darwin':
print(osx_install_advice.format(**locals()), file=sys.stderr)
elif get_platform() == 'linux':
print(linux_install_advice.format(**locals()), file=sys.stderr)
def error_missing_program(
program,
package,
optional
):
print(missing_program.format(**locals()), file=sys.stderr)
_error_trailer(**locals())
def error_unknown_version(
program,
package,
optional,
need_version
):
print(unknown_version.format(**locals()), file=sys.stderr)
_error_trailer(**locals())
def error_old_version(
program,
package,
optional,
need_version,
found_version
):
print(old_version.format(**locals()), file=sys.stderr)
_error_trailer(**locals())
def check_external_program(
program,
need_version,
package,
version_check_args=['--version'],
version_scrape_regex=re.compile(r'(\d+\.\d+(?:\.\d+)?)'),
optional=False):
print('Checking for {program} >= {need_version}...'.format(
program=program, need_version=need_version))
try:
result = check_output(
[program] + version_check_args,
universal_newlines=True, stderr=STDOUT)
except (CalledProcessError, FileNotFoundError):
error_missing_program(program, package, optional)
if not optional:
sys.exit(1)
print('Continuing install without {program}'.format(program=program))
return
try:
found_version = version_scrape_regex.search(result).group(1)
except AttributeError:
error_unknown_version(program, package, optional, need_version)
sys.exit(1)
if found_version < need_version:
error_old_version(program, package, optional, need_version,
found_version)
print('Found {program} {found_version}'.format(
program=program, found_version=found_version))
command = next((arg for arg in sys.argv[1:] if not arg.startswith('-')), '')
forced = ('--force' in sys.argv)
if not forced and command.startswith('install') or \
command in ['check', 'test', 'nosetests', 'easy_install']:
check_external_program(
program='tesseract',
need_version='3.03', # limited by Travis CI / Ubuntu 12.04 backports
package={'darwin': 'tesseract', 'linux': 'tesseract-ocr'}
)
check_external_program(
program='gs',
need_version='9.15', # limited by Travis CI / Ubuntu 12.04 backports
package='ghostscript'
)
check_external_program(
program='unpaper',
need_version='6.1', # latest sane version
package='unpaper',
optional=True
)
check_external_program(
program='qpdf',
need_version='5.0.0', # limited by Travis CI / Ubuntu 12.04 backports
package='qpdf',
version_check_args=['--version']
)
else:
print('Skipping external program tests because of --force')
if 'upload' in sys.argv[1:]:
print('Use twine to upload the package - setup.py upload is insecure')
sys.exit(1)
tests_require = open('test_requirements.txt').read().splitlines()
setup(
name='ocrmypdf',
description='OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to be searched',
url='https://github.com/jbarlow83/OCRmyPDF',
author='James R. Barlow',
author_email='jim@purplerock.ca',
license='Public Domain',
packages=['ocrmypdf'],
keywords=['PDF', 'OCR', 'optical character recognition', 'PDF/A', 'scanning'],
classifiers=[
"Programming Language :: Python :: 3",
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: End Users/Desktop",
"Intended Audience :: Science/Research",
"Intended Audience :: System Administrators",
"License :: OSI Approved :: MIT License",
"Operating System :: MacOS :: MacOS X",
"Operating System :: POSIX",
"Operating System :: POSIX :: BSD",
"Operating System :: POSIX :: Linux",
"Topic :: Scientific/Engineering :: Image Recognition",
"Topic :: Text Processing :: Indexing",
"Topic :: Text Processing :: Linguistic",
],
setup_requires=[
'setuptools_scm',
'cffi>=1.5.0',
'pytest-runner'
],
use_scm_version={'version_scheme': 'post-release'},
cffi_modules=[
'ocrmypdf/lib/compile_leptonica.py:ffi'
],
install_requires=[
'ruffus==2.6.3', # pinned - ocrmypdf implements a 2.6.3 workaround
'Pillow>=3.1.0', # Pillow is pretty stable
'reportlab>=3.2.0', # oldest released version with sane image handling
'PyPDF2>=1.26', # pure Python, so track HEAD closely
'img2pdf>=0.2.1', # pure Python, so track HEAD closely
'cffi>=1.5.0' # oldest version ever tested
],
tests_require=tests_require,
entry_points={
'console_scripts': [
'ocrmypdf = ocrmypdf.__main__:run_pipeline'
],
},
package_data={'ocrmypdf': ['data/sRGB.icc']},
include_package_data=True,
zip_safe=False)
-4
View File
@@ -1,4 +0,0 @@
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
BIN
View File
Binary file not shown.
+183
View File
@@ -0,0 +1,183 @@
##############################################################################
# Copyright (c) 2013: fritz-hh from Github (https://github.com/fritz-hh)
#
# Copyright (c) 2010: Jonathan Brinley from Github (https://github.com/jbrinley/HocrConverter)
# Initial version by Jonathan Brinley, jonathanbrinley@gmail.com
##############################################################################
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.units import inch
from lxml import etree as ElementTree
import Image, re, sys
import argparse
class hocrTransform():
"""
A class for converting documents from the hOCR format.
For details of the hOCR format, see:
http://docs.google.com/View?docid=dfxcv4vc_67g844kf
"""
def __init__(self, hocrFileName, dpi):
self.dpi = dpi
self.boxPattern = re.compile('bbox((\s+\d+){4})')
self.hocr = ElementTree.ElementTree()
self.hocr.parse(hocrFileName)
# if the hOCR file has a namespace, ElementTree requires its use to find elements
matches = re.match('({.*})html', self.hocr.getroot().tag)
self.xmlns = ''
if matches:
self.xmlns = matches.group(1)
# get dimension in pt (not pixel!!!!) of the OCRed image
for div in self.hocr.findall(".//%sdiv[@class='ocr_page']"%(self.xmlns)):
coords = self.element_coordinates(div)
self.width = self.px2pt(coords[2]-coords[0])
self.height = self.px2pt(coords[3]-coords[1])
break # there shouldn't be more than one, and if there is, we don't want it
# no width and heigh definition in the ocr_image element of the hocr file
if self.width is None:
print "No page dimension found in the hocr file"
sys.exit(1)
def __str__(self):
"""
Return the textual content of the HTML body
"""
if self.hocr is None:
return ''
body = self.hocr.find(".//%sbody"%(self.xmlns))
if body:
return self._get_element_text(body).encode('utf-8') # XML gives unicode
else:
return ''
def _get_element_text(self, element):
"""
Return the textual content of the element and its children
"""
text = ''
if element.text is not None:
text = text + element.text
for child in element.getchildren():
text = text + self._get_element_text(child)
if element.tail is not None:
text = text + element.tail
return text
def element_coordinates(self, element):
"""
Returns a tuple containing the coordinates of the bounding box around
an element
"""
out = (0,0,0,0)
if 'title' in element.attrib:
matches = self.boxPattern.search(element.attrib['title'])
if matches:
coords = matches.group(1).split()
out = (int(coords[0]),int(coords[1]),int(coords[2]),int(coords[3]))
return out
def px2pt(self, pxl):
"""
Returns the length in pt given length in pxl
"""
return float(pxl)/self.dpi*inch
def to_pdf(self, outFileName, imageFileName, showBoundingboxes, fontname="Helvetica"):
"""
Creates a PDF file with an image superimposed on top of the text.
Text is positioned according to the bounding box of the lines in
the hOCR file.
The image need not be identical to the image used to create the hOCR file.
It can have a lower resolution, different color mode, etc.
"""
# create the PDF file
pdf = Canvas(outFileName, pagesize=(self.width, self.height), pageCompression=1) # page size in points (1/72 in.)
# draw bounding box for each paragraph
pdf.setStrokeColorRGB(0,1,1) # light blue for bounding box of paragraph
pdf.setFillColorRGB(0,1,1) # light blue for bounding box of paragraph
pdf.setLineWidth(0) # no line for bounding box
for elem in self.hocr.findall(".//%sp[@class='%s']" % (self.xmlns, "ocr_par")):
elemtxt=self._get_element_text(elem).rstrip()
if len(elemtxt) == 0:
continue
coords = self.element_coordinates(elem)
x1=self.px2pt(coords[0])
y1=self.px2pt(coords[1])
x2=self.px2pt(coords[2])
y2=self.px2pt(coords[3])
# draw the bbox border
if showBoundingboxes == True:
pdf.rect(x1, self.height-y2, x2-x1, y2-y1, fill=1)
# check if element with class 'ocrx_word' are available
# otherwise use 'ocr_line' as fallback
elemclass="ocr_line"
if self.hocr.find(".//%sspan[@class='ocrx_word']" %(self.xmlns)) is not None:
elemclass="ocrx_word"
# itterate all text elements
pdf.setStrokeColorRGB(1,0,0) # light green for bounding box of word/line
pdf.setLineWidth(0.5) # bounding box line width
pdf.setDash(6,3) # bounding box is dashed
pdf.setFillColorRGB(0,0,0) # text in black
for elem in self.hocr.findall(".//%sspan[@class='%s']" % (self.xmlns, elemclass)):
elemtxt=self._get_element_text(elem).rstrip()
if len(elemtxt) == 0:
continue
coords = self.element_coordinates(elem)
x1=self.px2pt(coords[0])
y1=self.px2pt(coords[1])
x2=self.px2pt(coords[2])
y2=self.px2pt(coords[3])
# draw the bbox border
if showBoundingboxes == True:
pdf.rect(x1, self.height-y2, x2-x1, y2-y1, fill=0)
text = pdf.beginText()
fontsize=self.px2pt(coords[3]-coords[1])
text.setFont(fontname, fontsize)
# set cursor to bottom left corner of bbox (adjust for dpi)
text.setTextOrigin(x1, self.height-y2)
# scale the width of the text to fill the width of the bbox
text.setHorizScale(100*(x2-x1)/pdf.stringWidth(elemtxt, fontname, fontsize))
# write the text to the page
text.textLine(elemtxt)
pdf.drawText(text)
# put the image on the page, scaled to fill the page
if imageFileName != None:
im = Image.open(imageFileName)
pdf.drawInlineImage(im, 0, 0, width=self.width, height=self.height)
# finish up the page and save it
pdf.showPage()
pdf.save()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Convert hocr file to PDF')
parser.add_argument('-b', '--boundingboxes', action="store_true", default=False, help='Show bounding boxes borders')
parser.add_argument('-r', '--resolution', type=int, default=300, help='Resolution of the image that was OCRed')
parser.add_argument('-i', '--image', default=None, help='Path to the image to be placed above the text')
parser.add_argument('hocrfile', help='Path to the hocr file to be parsed')
parser.add_argument('outputfile', help='Path to the PDF file to be generated')
args = parser.parse_args()
hocr = hocrTransform(args.hocrfile, args.resolution)
hocr.to_pdf(args.outputfile, args.image, args.boundingboxes)
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
#
# Perform OCR several times in order to find how quicly the quality decreases
cpt=1
while [ $cpt -le 10 ] ; do
echo "------- Itteration $cpt ---------"
! ../../OCRmyPDF.sh -vv -l deu -k ../../tmp/ocred-$(($cpt-1)).pdf ../../tmp/ocred-$cpt.pdf && exit 1
cpt=$(($cpt+1))
done
+4
View File
@@ -0,0 +1,4 @@
Note
====
The file(s) located in this folder are aimed at testing the OCRmyPDF script
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
Note
====
The file(s) located in this folder are tesseract configuration files.
(Tesseract configuration files enable to tune the behaviour of tesseract)
If needed, these files should be copied to the "tessdata/configs" folder of your tesseract installation.
To request OCRmyPDF.sh to use a configuration file, please use the -C option
+1
View File
@@ -0,0 +1 @@
tessedit_char_blacklist fifl
-1
View File
@@ -1 +0,0 @@
pytest>=2.7.2
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

-106
View File
@@ -1,106 +0,0 @@
These test files are used in OCRmyPDF's test suite. They do not necessarily produce OCR results
at all and are not meant as examples of OCR output. Some are even invalid PDFs that might
crash certain PDF viewers.
Files derived from free sources
===============================
These test resources come from free sources, under either public domain or Creative Commons licenses.
In some cases they were converted from one image format to another without other changes.
.. list-table::
:widths: 20 50 30
:header-rows: 1
* - File
- Source
- License
* - c02-22.pdf
- `Project Gutenberg`_, Adventures of Huckleberry Finn, page 22
- Public Domain
* - congress.jpg
- `US Congressional Records`_
- Public Domain
* - graph.pdf
- `Wikimedia: Pandas text analysis.png`_
- Public Domain
* - lichtenstein.pdf
- `Wikimedia: JPEG2000 Lichtenstein`_
- Creative Commons BY-SA 3.0
* - LinnSequencer.jpg, linn.pdf, linn.txt
- `Wikimedia: LinnSequencer`_
- Creative Commons BY-SA 3.0
Files generated for this project
================================
The following test resources were crafted specifically for this project, and can be used
under the terms of the license in LICENSE.rst.
.. list-table::
:widths: 20 20 60
:header-rows: 1
* - File
- Contributor
- Purpose
* - aspect.pdf
- @jbarlow83
- test image with 200 x 100 DPI resolution
* - blank.pdf
- @jbarlow83
- blank PDF
* - cmyk.pdf
- @jbarlow83
- a CMYK image created in Photoshop
* - enormous.pdf
- @jbarlow83
- very large PDF page
* - epson.pdf
- @lowesjam
- a linearized PDF containing some unusual indirect objects, created by an Epson printer; printout of a Wikipedia article (CC BY-SA)
* - francais.pdf
- @jbarlow83
- a page containing French accents (diacritics)
* - hugemono.pdf
- @jbarlow83
- large monochrome 35000x35000 image in JBIG2 encoding
* - invalid.pdf
- @jbarlow83
- a PDF file header followed by EOF marker
* - masks.pdf
- @supergrobi
- file containing explicit masks and a stencil mask drawn without a proper transformation matrix; printout of a German Wikipedia article (CC BY-SA)
* - missing_docinfo.pdf
- @jbarlow83
- PDF file with no /DocumentInfo section
Assemblies
==========
These test resources are assemblies from other previously mentioned files, released under the same license terms as their input files.
- cardinal.pdf (four cardinal directions, rotated copies of LinnSequencer.jpg)
- ccitt.pdf (LinnSequencer.jpg, converted to CCITT encoding)
- encrypted_algo4.pdf (congress.jpg, encrypted with algorithm 4 - not supported by PyPDF2)
- graph_ocred.pdf (from graph.pdf)
- jbig2.pdf (congress.jpg, converted to JBIG2 encoding)
- multipage.pdf (from several other files)
- palette.pdf (congress.jpg, converted to a 256-color palette)
- skew.pdf (from c02-22.pdf)
- skew-encrypted.pdf (skew.pdf with encryption - access supported by PyPDF2)
.. _`Wikimedia: LinnSequencer`: https://upload.wikimedia.org/wikipedia/en/b/b7/LinnSequencer_hardware_MIDI_sequencer_brochure_page_2_300dpi.jpg
.. _`Project Gutenberg`: https://www.gutenberg.org/files/76/76-h/76-h.htm#c2
.. _`US Congressional Records`: http://www.baxleystamps.com/litho/meiji/courts_1871.jpg
.. _`Wikimedia: Pandas text analysis.png`: https://en.wikipedia.org/wiki/File:Pandas_text_analysis.png
.. _`Wikimedia: JPEG2000 Lichtenstein`: https://en.wikipedia.org/wiki/JPEG_2000#/media/File:Jpeg2000_2-level_wavelet_transform-lichtenstein.png
.. _`Linux (Wikipedia Article)`: https://de.wikipedia.org/wiki/Linux
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
%PDF-1.3
This is not a valid PDF file
%%EOF
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More