def test_optimize_images_task_failure(tmp_path, font): tmp_fl = tmp_path / "tmp.jpg" dst = tmp_path / "out.jpg" # send an unreadable file tmp_fl.touch(mode=0o377) task = Task( src_path=str(tmp_fl.resolve()), quality=50, remove_transparency=False, reduce_colors=False, max_colors=256, max_w=0, max_h=0, keep_exif=False, convert_all=False, conv_big=False, force_del=False, bg_color=(255, 255, 255), grayscale=False, no_size_comparison=True, fast_mode=False, ) with pytest.raises(Exception): run_optimize_images_task(task, tmp_fl, dst) assert not tmp_fl.exists() assert not dst.exists()
def on_created(self, event): t = self.t if '~temp~' in event.src_path: return if event.is_directory: return if event.src_path in self.paths_to_ignore: return self.paths_to_ignore.append(event.src_path) if is_image(event.src_path) and '~temp~' not in event.src_path: self.wait_for_write_finish(event.src_path) self.new_files += 1 img_task = Task(event.src_path, t.quality, t.remove_transparency, t.reduce_colors, t.max_colors, t.max_w, t.max_h, t.keep_exif, t.convert_all, t.conv_big, t.force_del, t.bg_color, t.grayscale, t.no_size_comparison, t.fast_mode) r = do_optimization(img_task) self.total_src_size += r.orig_size if r.was_optimized: self.optimized_files += 1 self.total_bytes_saved += r.orig_size - r.final_size show_file_status(r, self.line_width, self.icons)
def on_created(self, event): if (event.is_directory or not is_image(event.src_path) or '~temp~' in event.src_path or event.src_path in self.paths_to_ignore): return self.paths_to_ignore.append(event.src_path) self.wait_for_write_finish(event.src_path) self.new_files += 1 task = self.task img_task = Task(event.src_path, task.quality, task.remove_transparency, task.reduce_colors, task.max_colors, task.max_w, task.max_h, task.keep_exif, task.convert_all, task.conv_big, task.force_del, task.bg_color, task.grayscale, task.no_size_comparison, task.fast_mode) result: TaskResult = do_optimization(img_task) self.total_src_size += result.orig_size if result.was_optimized: self.optimized_files += 1 self.total_bytes_saved += result.orig_size - result.final_size show_file_status(result, self.line_width, self.icons)
def optimize(filepath): remove_transparency = True reduce_colors = False max_colors = 255 max_w = 0 max_h = 0 keep_exif = True convert_all = False conv_big = False force_del = False bg_color = (255, 255, 255) # By default, apply a white background grayscale = False ignore_size_comparison = False fast_mode = False task = Task( filepath, constants.DEFAULT_QUALITY, remove_transparency, reduce_colors, max_colors, max_w, max_h, keep_exif, convert_all, conv_big, force_del, bg_color, grayscale, ignore_size_comparison, fast_mode ) r = do_optimization(task) total_src_size = r.orig_size report = json_report(r, filepath) print(report) return report
def optimize_jpeg( src: pathlib.Path, dst: pathlib.Path, quality: Optional[int] = 85, fast_mode: Optional[bool] = True, keep_exif: Optional[bool] = True, grayscale: Optional[bool] = False, **options, ) -> bool: """method to optimize JPEG files using a pure python external optimizer quality: JPEG quality (integer between 1 and 100) values: 50 | 55 | 35 | 100 | XX keep_exif: Whether to keep EXIF data in JPEG (boolean) values: True | False grayscale: Whether to convert image to grayscale (boolean) values: True | False fast_mode: Whether to use faster but weaker compression (boolean) values: True | False""" ensure_matches(src, "JPEG") # use a temporary file as source as optimization is done destructively tmp_path = get_temporary_copy(src) # generate JPEG task for optimize_images optimization_task = Task( src_path=str(tmp_path.resolve()), quality=quality, # remove_transparency is specific to PNG and hence is ignored, but we need to supply the default value remove_transparency=False, # reduce_colors is specific to PNG and hence is ignored, but we need to supply the default value reduce_colors=False, # max_colors is specific to PNG and hence is ignored, but we need to supply the default value max_colors=256, # max_w and max_h is 0 because we have a better image resizing function in scraperlib already max_w=0, max_h=0, keep_exif=keep_exif, # convert_all is specific to PNG and hence is ignored, but we need to supply the default value convert_all=False, # convert_big is specific to PNG and hence is ignored, but we need to supply the default value conv_big=False, # force_del is specific to PNG and hence is ignored, but we need to supply the default value force_del=False, # bg_color is specific to PNG and hence is ignored, but we need to supply the default value bg_color=(255, 255, 255), grayscale=grayscale, no_size_comparison=True, fast_mode=fast_mode, ) # optimize the image return run_optimize_images_task(optimization_task, tmp_path, dst)
def main(): appstart = timer() line_width, our_pool_executor, workers = adjust_for_platform() (src_path, recursive, quality, remove_transparency, reduce_colors, max_colors, max_w, max_h, keep_exif, convert_all, conv_big, force_del, bg_color, grayscale, ignore_size_comparison, fast_mode) = get_args() found_files = 0 optimized_files = 0 total_src_size = 0 total_bytes_saved = 0 # Optimize all images in a directory if os.path.isdir(src_path): recursion_txt = 'Recursively searching' if recursive else 'Searching' opt_msg = 'and optimizing image files' exif_txt = '(keeping exif data) ' if keep_exif else '' print(f"\n{recursion_txt} {opt_msg} {exif_txt}in:\n{src_path}\n") tasks = (Task(img_path, quality, remove_transparency, reduce_colors, max_colors, max_w, max_h, keep_exif, convert_all, conv_big, force_del, bg_color, grayscale, ignore_size_comparison, fast_mode) for img_path in search_images(src_path, recursive=recursive)) with our_pool_executor(max_workers=workers) as executor: for r in executor.map(do_optimization, tasks): found_files += 1 total_src_size += r.orig_size if r.was_optimized: optimized_files += 1 total_bytes_saved += r.orig_size - r.final_size show_file_status(r, line_width) # Optimize a single image elif os.path.isfile(src_path): found_files += 1 img_task = Task(src_path, quality, remove_transparency, reduce_colors, max_colors, max_w, max_h, keep_exif, convert_all, conv_big, force_del, bg_color, grayscale, ignore_size_comparison, fast_mode) r = do_optimization(img_task) total_src_size = r.orig_size if r.was_optimized: optimized_files = 1 total_bytes_saved = r.orig_size - r.final_size show_file_status(r, line_width) else: print( "No image files were found. Please enter a valid path to the " "image file or the folder containing any images to be processed.") exit() if found_files: time_passed = timer() - appstart show_final_report(found_files, optimized_files, total_src_size, total_bytes_saved, time_passed) else: print( "No supported image files were found in the specified directory.\n" )
def optimize_batch(src_path, watch_dir, recursive, quality, remove_transparency, reduce_colors, max_colors, max_w, max_h, keep_exif, convert_all, conv_big, force_del, bg_color, grayscale, ignore_size_comparison, fast_mode, jobs, output_config): appstart = timer() line_width, our_pool_executor, workers = adjust_for_platform() if jobs != 0: workers = jobs found_files = 0 optimized_files = 0 skipped_files = 0 total_src_size = 0 total_bytes_saved = 0 if watch_dir: if not os.path.isdir(os.path.abspath(src_path)): msg = "\nPlease specify a valid path to an existing folder." raise OIInvalidPathError(msg) watch_task = Task(src_path, quality, remove_transparency, reduce_colors, max_colors, max_w, max_h, keep_exif, convert_all, conv_big, force_del, bg_color, grayscale, ignore_size_comparison, fast_mode, output_config) from optimize_images.watch import watch_for_new_files watch_for_new_files(watch_task) return # Optimize all images in a directory elif os.path.isdir(src_path): if not output_config.quiet_mode: icons = IconGenerator() recursion_txt = 'Recursively searching' if recursive else 'Searching' opt_msg = 'and optimizing image files' exif_txt = '(keeping exif data) ' if keep_exif else '' print(f"\n{recursion_txt} {opt_msg} {exif_txt}in:\n{src_path}\n") tasks = (Task(img_path, quality, remove_transparency, reduce_colors, max_colors, max_w, max_h, keep_exif, convert_all, conv_big, force_del, bg_color, grayscale, ignore_size_comparison, fast_mode, output_config) for img_path in search_images(src_path, recursive=recursive)) num_images, tasks = count_gen(tasks) with our_pool_executor(max_workers=workers) as executor: current_img = '' try: for result in executor.map(do_optimization, tasks): current_img = result.img found_files += 1 total_src_size += result.orig_size if result.was_optimized: optimized_files += 1 total_bytes_saved += result.orig_size - result.final_size else: skipped_files += 1 if result.output_config.quiet_mode: continue if result.output_config.show_overall_progress: cur_time_passed = round(timer() - appstart) perc_done = found_files / num_images * 100 message = f"[{cur_time_passed:.1f}s {perc_done:.1f}%] {icons.optimized} {optimized_files} {icons.skipped} {skipped_files}, saved {human(total_bytes_saved)}" print(message, end='\r') else: show_file_status(result, line_width, icons) except concurrent.futures.process.BrokenProcessPool as bppex: show_img_exception(bppex, current_img) except KeyboardInterrupt: msg = "\b \n\n == Operation was interrupted by the user. ==\n" raise OIKeyboardInterrupt(msg) # Optimize a single image elif os.path.isfile(src_path) and '~temp~' not in src_path: found_files += 1 img_task = Task(src_path, quality, remove_transparency, reduce_colors, max_colors, max_w, max_h, keep_exif, convert_all, conv_big, force_del, bg_color, grayscale, ignore_size_comparison, fast_mode, output_config) result = do_optimization(img_task) total_src_size = result.orig_size if result.was_optimized: optimized_files = 1 total_bytes_saved = result.orig_size - result.final_size if not result.output_config.quiet_mode: icons = IconGenerator() show_file_status(result, line_width, icons) else: msg = "\nNo image files were found. Please enter a valid path to the " \ "image file or the folder containing any images to be processed." raise OIImagesNotFoundError(msg) if found_files: time_passed = timer() - appstart show_final_report(found_files, optimized_files, total_src_size, total_bytes_saved, time_passed, output_config) else: msg = "\nNo supported image files were found in the specified directory." raise OIImagesNotFoundError(msg)
def main(): appstart = timer() line_width, our_pool_executor, workers = adjust_for_platform() (watch_dir, src_path, recursive, quality, remove_transparency, reduce_colors, max_colors, max_w, max_h, keep_exif, convert_all, conv_big, force_del, bg_color, grayscale, ignore_size_comparison, fast_mode, jobs) \ = get_args() if jobs != 0: workers = jobs found_files = 0 optimized_files = 0 total_src_size = 0 total_bytes_saved = 0 if watch_dir: if not os.path.isdir(os.path.abspath(src_path)): print("\nPlease secify a valid path to an existing folder.") sys.exit(1) watch_task = Task(src_path, quality, remove_transparency, reduce_colors, max_colors, max_w, max_h, keep_exif, convert_all, conv_big, force_del, bg_color, grayscale, ignore_size_comparison, fast_mode) watch_for_new_files(watch_task) sys.exit() # Optimize all images in a directory elif os.path.isdir(src_path): icons = IconGenerator() recursion_txt = 'Recursively searching' if recursive else 'Searching' opt_msg = 'and optimizing image files' exif_txt = '(keeping exif data) ' if keep_exif else '' print(f"\n{recursion_txt} {opt_msg} {exif_txt}in:\n{src_path}\n") tasks = (Task(img_path, quality, remove_transparency, reduce_colors, max_colors, max_w, max_h, keep_exif, convert_all, conv_big, force_del, bg_color, grayscale, ignore_size_comparison, fast_mode) for img_path in search_images(src_path, recursive=recursive)) with our_pool_executor(max_workers=workers) as executor: current_img = '' try: for result in executor.map(do_optimization, tasks): current_img = result.img found_files += 1 total_src_size += result.orig_size if result.was_optimized: optimized_files += 1 total_bytes_saved += result.orig_size - result.final_size show_file_status(result, line_width, icons) except concurrent.futures.process.BrokenProcessPool as bppex: show_img_exception(bppex, current_img) except KeyboardInterrupt: print( "\b \n\n == Operation was interrupted by the user. ==\n") # Optimize a single image elif os.path.isfile(src_path) and '~temp~' not in src_path: icons = IconGenerator() found_files += 1 img_task = Task(src_path, quality, remove_transparency, reduce_colors, max_colors, max_w, max_h, keep_exif, convert_all, conv_big, force_del, bg_color, grayscale, ignore_size_comparison, fast_mode) result = do_optimization(img_task) total_src_size = result.orig_size if result.was_optimized: optimized_files = 1 total_bytes_saved = result.orig_size - result.final_size show_file_status(result, line_width, icons) else: print( "\nNo image files were found. Please enter a valid path to the " "image file or the folder containing any images to be processed.") sys.exit() if found_files: time_passed = timer() - appstart show_final_report(found_files, optimized_files, total_src_size, total_bytes_saved, time_passed) else: print( "\nNo supported image files were found in the specified directory.\n" )
def optimize_png( src: pathlib.Path, dst: pathlib.Path, reduce_colors: Optional[bool] = False, max_colors: Optional[int] = 256, fast_mode: Optional[bool] = True, remove_transparency: Optional[bool] = False, background_color: Optional[Tuple[int, int, int]] = (255, 255, 255), grayscale: Optional[bool] = False, **options, ) -> bool: """method to optimize PNG files using a pure python external optimizer Arguments: reduce_colors: Whether to reduce colors using adaptive color pallette (boolean) values: True | False max_colors: Maximum number of colors if reduce_colors is True (integer between 1 and 256) values: 35 | 64 | 256 | 128 | XX fast_mode: Whether to use faster but weaker compression (boolean) values: True | False remove_transparency: Whether to remove transparency (boolean) values: True | False background_color: Background color if remove_transparency is True (tuple containing RGB values) values: (255, 255, 255) | (221, 121, 108) | (XX, YY, ZZ) grayscale: Whether to convert image to grayscale (boolean) values: True | False""" ensure_matches(src, "PNG") # use a temporary file as source as optimization is done destructively tmp_path = get_temporary_copy(src) # generate PNG task for optimize_images optimization_task = Task( src_path=str(tmp_path.resolve()), # quality is specific to JPEG and hence is ignored, but we need to supply the default value quality=90, remove_transparency=remove_transparency, reduce_colors=reduce_colors, # max_colors is ignored if reduce_colors is False, but we need to provide a default value max_colors=max_colors, # max_w and max_h is 0 because we have a better image resizing function in scraperlib already max_w=0, max_h=0, # keep_exif is specific to JPEG and hence is ignored, but we need to supply the default value keep_exif=False, # convert_all converts all PNG to JPEG, hence set to False convert_all=False, # conv_big converts big PNG images to JPEG, hence set to False conv_big=False, # force_del deletes the original PNG after convertion to JPEG if the above two options are used, hence kept False force_del=False, # bg_color is only used if remove_transparency is True, but we need to supply a default value always bg_color=background_color, grayscale=grayscale, no_size_comparison=True, fast_mode=fast_mode, ) # optimize the image return run_optimize_images_task(optimization_task, tmp_path, dst)