diff --git a/ocrmypdf/__main__.py b/ocrmypdf/__main__.py index c3a006da..3cd37577 100755 --- a/ocrmypdf/__main__.py +++ b/ocrmypdf/__main__.py @@ -27,7 +27,8 @@ from .helpers import is_iterable_notstr, re_symlink, is_file_writable from .exec import tesseract, qpdf, ghostscript from . import PROGRAM_NAME, VERSION -from .exceptions import ExitCode, ExitCodeException, MissingDependencyError +from .exceptions import ExitCode, ExitCodeException, MissingDependencyError, \ + InputFileError, BadArgsError, OutputFileAccessError from . import exceptions as ocrmypdf_exceptions from ._unicodefun import verify_python3_env @@ -479,6 +480,12 @@ def check_options_metadata(options, log): )) +def check_options_pillow(options, log): + PIL.Image.MAX_IMAGE_PIXELS = int(options.max_image_mpixels * 1000000) + if PIL.Image.MAX_IMAGE_PIXELS == 0: + PIL.Image.MAX_IMAGE_PIXELS = None + + def check_options(options, log): try: check_options_languages(options, log) @@ -488,6 +495,7 @@ def check_options(options, log): check_options_preprocessing(options, log) check_options_ocr_behavior(options, log) check_options_advanced(options, log) + check_options_pillow(options, log) except ValueError as e: log.error(e) sys.exit(ExitCode.bad_args) @@ -625,6 +633,21 @@ def do_ruffus_exception(ruffus_five_tuple, options, log): def traverse_ruffus_exception(e_args, options, log): """Walk through a RethrownJobError and find the first exception. + Ruffus flattens exception to 5 element tuples. Because of a bug + in <= 2.6.3 it may present either the single: + (task, job, exc, value, stack) + or something like: + [[(task, job, exc, value, stack)]] + + Generally cross-process exception marshalling doesn't work well + and ruffus doesn't support because BaseException has its own + implementation of __reduce__ that attempts to reconstruct the + exception based on e.__init__(e.args). + + Attempting to log the exception directly marshalls it to the logger + which is probably in another process, so it's better to log only + data from the exception at this point. + The exit code will be based on this, even if multiple exceptions occurred at the same time.""" @@ -656,6 +679,9 @@ def check_closed_streams(options): """ + if sys.version_info[0:3] >= (3, 6, 4): + return True # Issued fixed in Python 3.6.4+ + if sys.stderr is None: sys.stderr = open(os.devnull, 'w') @@ -695,6 +721,42 @@ def log_page_orientations(pdfinfo, _log): _log.info('Page orientations detected: ' + ' '.join(orientations)) +def preamble(_log): + _log.debug('ocrmypdf ' + VERSION) + _log.debug('tesseract ' + tesseract.version()) + _log.debug('qpdf ' + qpdf.version()) + + +def check_input_file(options, _log, start_input_file): + if options.input_file == '-': + # stdin + _log.info('reading file from standard input') + with open(start_input_file, 'wb') as stream_buffer: + from shutil import copyfileobj + copyfileobj(sys.stdin.buffer, stream_buffer) + else: + try: + re_symlink(options.input_file, start_input_file, _log) + except FileNotFoundError: + _log.error("File not found - " + options.input_file) + raise InputFileError() + + +def check_output_file(options, _log): + if options.output_file == '-': + if sys.stdout.isatty(): + _log.error(textwrap.dedent("""\ + Output was set to stdout '-' but it looks like stdout + is connected to a terminal. Please redirect stdout to a + file.""")) + raise BadArgsError() + elif not is_file_writable(options.output_file): + _log.error( + "Output file location (" + options.output_file + ") " + + "is not a writable file.") + raise OutputFileAccessError() + + def run_pipeline(): options = parser.parse_args() options.verbose_abbreviated_path = 1 @@ -706,16 +768,9 @@ def run_pipeline(): _log, _log_mutex = proxy_logger.make_shared_logger_and_proxy( logging_factory, __name__, logger_args) - _log.debug('ocrmypdf ' + VERSION) - _log.debug('tesseract ' + tesseract.version()) - _log.debug('qpdf ' + qpdf.version()) - + preamble(_log) check_options(options, _log) - PIL.Image.MAX_IMAGE_PIXELS = int(options.max_image_mpixels * 1000000) - if PIL.Image.MAX_IMAGE_PIXELS == 0: - PIL.Image.MAX_IMAGE_PIXELS = None - # Complain about qpdf version < 7.0.0 # Suppress the warning if in the test suite, since there are no PPAs # for qpdf 7.0.0 for Ubuntu trusty (i.e. Travis) @@ -744,31 +799,8 @@ def run_pipeline(): start_input_file = os.path.join( work_folder, 'origin') - if options.input_file == '-': - # stdin - _log.info('reading file from standard input') - with open(start_input_file, 'wb') as stream_buffer: - from shutil import copyfileobj - copyfileobj(sys.stdin.buffer, stream_buffer) - else: - try: - re_symlink(options.input_file, start_input_file, _log) - except FileNotFoundError: - _log.error("File not found - " + options.input_file) - return ExitCode.input_file - - if options.output_file == '-': - if sys.stdout.isatty(): - _log.error(textwrap.dedent("""\ - Output was set to stdout '-' but it looks like stdout - is connected to a terminal. Please redirect stdout to a - file.""")) - return ExitCode.bad_args - elif not is_file_writable(options.output_file): - _log.error( - "Output file location (" + options.output_file + ") " + - "is not a writable file.") - return ExitCode.file_access_error + check_input_file(options, _log, start_input_file) + check_output_file(options, _log) manager = JobContextManager() manager.register('JobContext', JobContext) # pylint: disable=no-member @@ -784,22 +816,6 @@ def run_pipeline(): except ruffus_exceptions.RethrownJobError as e: if options.verbose: _log.debug(str(e)) # stringify exception so logger doesn't have to - - # Ruffus flattens exception to 5 element tuples. Because of a bug - # in <= 2.6.3 it may present either the single: - # (task, job, exc, value, stack) - # or something like: - # [[(task, job, exc, value, stack)]] - # - # Generally cross-process exception marshalling doesn't work well - # and ruffus doesn't support because BaseException has its own - # implementation of __reduce__ that attempts to reconstruct the - # exception based on e.__init__(e.args). - # - # Attempting to log the exception directly marshalls it to the logger - # which is probably in another process, so it's better to log only - # data from the exception at this point. - exitcode = traverse_ruffus_exception(e.args, options, _log) if exitcode is None: _log.error("Unexpected ruffus exception: " + str(e)) diff --git a/ocrmypdf/exceptions.py b/ocrmypdf/exceptions.py index f32ef981..fe249fcf 100644 --- a/ocrmypdf/exceptions.py +++ b/ocrmypdf/exceptions.py @@ -22,6 +22,10 @@ class ExitCodeException(Exception): exit_code = ExitCode.other_error +class BadArgsError(ExitCodeException): + exit_code = ExitCode.bad_args + + class PdfMergeFailedError(ExitCodeException): exit_code = ExitCode.input_file @@ -38,6 +42,10 @@ class DpiError(ExitCodeException): exit_code = ExitCode.input_file +class OutputFileAccessError(ExitCodeException): + exit_code = ExitCode.file_access_error + + class PriorOcrFoundError(ExitCodeException): exit_code = ExitCode.already_done_ocr diff --git a/ocrmypdf/helpers.py b/ocrmypdf/helpers.py index 3310a034..2f8c0ecc 100644 --- a/ocrmypdf/helpers.py +++ b/ocrmypdf/helpers.py @@ -71,10 +71,9 @@ def is_file_writable(test_file): # defaults to strict=False. This implements strict=False like behavior # for Python 3.5. if sys.version_info[0:2] <= (3, 5): - resolve = lambda: Path(os.path.realpath(str(p))) + p = Path(os.path.realpath(str(p))) else: - resolve = lambda: p.resolve(strict=False) - p = resolve() + p = p.resolve(strict=False) # p.is_file() throws an exception in some cases if p.exists() and p.is_file():