def run(input_file, output_file, dpi, mode_args): args_unpaper = ['unpaper', '-v', '--dpi', str(round(dpi, 6))] + mode_args with TemporaryDirectory() as tmpdir: input_pnm, output_pnm = _setup_unpaper_io(Path(tmpdir), input_file) # To prevent any shenanigans from accepting arbitrary parameters in # --unpaper-args, we: # 1) run with cwd set to a tmpdir with only unpaper's files # 2) forbid the use of '/' in arguments, to prevent changing paths # 3) append absolute paths for the input and output file # This should ensure that a user cannot clobber some other file with # their unpaper arguments (whether intentionally or otherwise) args_unpaper.extend([os.fspath(input_pnm), os.fspath(output_pnm)]) external_run( args_unpaper, close_fds=True, check=True, stderr=STDOUT, # unpaper writes logging output to stdout and stderr stdout=PIPE, # and cannot send file output to stdout cwd=tmpdir, logs_errors_to_stdout=True, ) try: with Image.open(output_pnm) as imout: imout.save(output_file, dpi=(dpi, dpi)) except (FileNotFoundError, OSError): raise SubprocessOutputError( "unpaper: failed to produce the expected output file. " + " Called with: " + str(args_unpaper)) from None
def run(input_file, output_file, dpi, mode_args): args_unpaper = ['unpaper', '-v', '--dpi', str(dpi)] + mode_args SUFFIXES = {'1': '.pbm', 'L': '.pgm', 'RGB': '.ppm'} with TemporaryDirectory() as tmpdir, Image.open(input_file) as im: 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 as e: im.close() raise MissingDependencyError( "Could not convert image with type " + im.mode) from e try: suffix = SUFFIXES[im.mode] except KeyError: raise MissingDependencyError( "Failed to convert image to a supported format.") from e input_pnm = Path(tmpdir) / f'input{suffix}' output_pnm = Path(tmpdir) / f'output{suffix}' im.save(input_pnm, format='PPM') # To prevent any shenanigans from accepting arbitrary parameters in # --unpaper-args, we: # 1) run with cwd set to a tmpdir with only unpaper's files # 2) forbid the use of '/' in arguments, to prevent changing paths # 3) append absolute paths for the input and output file # This should ensure that a user cannot clobber some other file with # their unpaper arguments (whether intentionally or otherwise) args_unpaper.extend([os.fspath(input_pnm), os.fspath(output_pnm)]) try: proc = external_run( args_unpaper, check=True, close_fds=True, universal_newlines=True, stderr=STDOUT, cwd=tmpdir, stdout=PIPE, ) except CalledProcessError as e: log.debug(e.output) raise e from e else: log.debug(proc.stdout) # unpaper sets dpi to 72; fix this try: with Image.open(output_pnm) as imout: imout.save(output_file, dpi=(dpi, dpi)) except (FileNotFoundError, OSError): raise SubprocessOutputError( "unpaper: failed to produce the expected output file. " + " Called with: " + str(args_unpaper)) from None