3 Commits
4 changed files with 51 additions and 17 deletions
+4
View File
@@ -113,6 +113,10 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
## Version History ## Version History
- v0.9rc3: Third release candidate
- Switched to parallel processing in optimization
- Removed unused imports and variables
- Added `ruff` as a development dependency
- v0.9rc2: Second release candidate - v0.9rc2: Second release candidate
- Improved documentation - Improved documentation
- Added optimization level descriptions - Added optimization level descriptions
+8 -16
View File
@@ -1,11 +1,7 @@
#!/usr/bin/env -S uv run #!/usr/bin/env -S uv run
import os import os
import shutil
import subprocess import subprocess
from pathlib import Path
from typing import List, Tuple
import click import click
import cv2 import cv2
import img2pdf import img2pdf
@@ -13,7 +9,6 @@ import numpy as np
from pdf2image import convert_from_path from pdf2image import convert_from_path
import settings import settings
from settings import OptimizationLevel
from PIL import Image from PIL import Image
import concurrent.futures import concurrent.futures
@@ -23,8 +18,6 @@ class Settings:
OPTIPNG_OPTIMIZATION_LEVEL = 7 OPTIPNG_OPTIMIZATION_LEVEL = 7
PDF_BORDER_SIZE = 50 PDF_BORDER_SIZE = 50
settings = Settings()
def ensure_temp_dir(): def ensure_temp_dir():
"""Ensure temporary directory exists and return its path.""" """Ensure temporary directory exists and return its path."""
temp_dir = "temp_processed_images" temp_dir = "temp_processed_images"
@@ -282,7 +275,7 @@ def run_optipng(file_path: str) -> bool:
bool: True if successful, False otherwise bool: True if successful, False otherwise
""" """
try: try:
result = subprocess.run( subprocess.run(
['optipng', f'-o{settings.OPTIPNG_OPTIMIZATION_LEVEL}', file_path], ['optipng', f'-o{settings.OPTIPNG_OPTIMIZATION_LEVEL}', file_path],
capture_output=True, capture_output=True,
text=True, text=True,
@@ -307,13 +300,12 @@ def optimize(level):
2: Level 1 + convert to 1-bit monochrome 2: Level 1 + convert to 1-bit monochrome
3: Level 2 + optipng optimization 3: Level 2 + optipng optimization
""" """
temp_dir = ensure_temp_dir()
temp_files = get_temp_files() temp_files = get_temp_files()
if not temp_files: if not temp_files:
print("No pages found in temporary directory. Run 'extract' first.") print("No pages found in temporary directory. Run 'extract' first.")
return return
opt_level = OptimizationLevel(level) opt_level = settings.OptimizationLevel(level)
total_files = len(temp_files) total_files = len(temp_files)
successful = { successful = {
@@ -325,10 +317,10 @@ def optimize(level):
print(f"Processing {total_files} images at optimization level {level}...") print(f"Processing {total_files} images at optimization level {level}...")
# Step 1: Convert to monochrome if level >= 2 # Step 1: Convert to monochrome if level >= 2
if int(opt_level) >= int(OptimizationLevel.MONOCHROME): if int(opt_level) >= int(settings.OptimizationLevel.MONOCHROME):
print("\nConverting to monochrome...") print("\nConverting to monochrome...")
with concurrent.futures.ThreadPoolExecutor() as executor: with concurrent.futures.ThreadPoolExecutor() as executor:
futures = {executor.submit(convert_to_monochrome, file_path, (opt_level == OptimizationLevel.MONOCHROME)): file_path for file_path in temp_files} futures = {executor.submit(convert_to_monochrome, file_path, (opt_level == settings.OptimizationLevel.MONOCHROME)): file_path for file_path in temp_files}
for future in concurrent.futures.as_completed(futures): for future in concurrent.futures.as_completed(futures):
file_path = futures[future] file_path = futures[future]
try: try:
@@ -343,7 +335,7 @@ def optimize(level):
# Step 2: Always trim whitespace # Step 2: Always trim whitespace
print("\nTrimming whitespace from images...") print("\nTrimming whitespace from images...")
with concurrent.futures.ThreadPoolExecutor() as executor: with concurrent.futures.ThreadPoolExecutor() as executor:
futures = {executor.submit(trim_whitespace, file_path, (opt_level == OptimizationLevel.TRIM)): file_path for file_path in temp_files} futures = {executor.submit(trim_whitespace, file_path, (opt_level == settings.OptimizationLevel.TRIM)): file_path for file_path in temp_files}
for future in concurrent.futures.as_completed(futures): for future in concurrent.futures.as_completed(futures):
file_path = futures[future] file_path = futures[future]
try: try:
@@ -356,7 +348,7 @@ def optimize(level):
print(f"{os.path.basename(file_path)} generated an exception: {exc}") print(f"{os.path.basename(file_path)} generated an exception: {exc}")
# Step 3: Run optipng if level = 3 # Step 3: Run optipng if level = 3
if opt_level == OptimizationLevel.FULL: if opt_level == settings.OptimizationLevel.FULL:
if check_optipng_installed(): if check_optipng_installed():
print("\nOptimizing PNG files with optipng...") print("\nOptimizing PNG files with optipng...")
with concurrent.futures.ThreadPoolExecutor() as executor: with concurrent.futures.ThreadPoolExecutor() as executor:
@@ -377,9 +369,9 @@ def optimize(level):
# Print summary # Print summary
print("\nOptimization complete!") print("\nOptimization complete!")
print(f"Successfully trimmed: {successful['trim']}/{total_files} images") print(f"Successfully trimmed: {successful['trim']}/{total_files} images")
if int(opt_level) >= int(OptimizationLevel.MONOCHROME): if int(opt_level) >= int(settings.OptimizationLevel.MONOCHROME):
print(f"Successfully converted to monochrome: {successful['monochrome']}/{total_files} images") print(f"Successfully converted to monochrome: {successful['monochrome']}/{total_files} images")
if opt_level == OptimizationLevel.FULL and check_optipng_installed(): if opt_level == settings.OptimizationLevel.FULL and check_optipng_installed():
print(f"Successfully optimized with optipng: {successful['optipng']}/{total_files} images") print(f"Successfully optimized with optipng: {successful['optipng']}/{total_files} images")
@cli.command() @cli.command()
+6 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "notes-cleaner" name = "notes-cleaner"
version = "0.9rc2" version = "0.9rc3"
description = "Add your description here" description = "Add your description here"
readme = "README.md" readme = "README.md"
license = "MIT" license = "MIT"
@@ -12,3 +12,8 @@ dependencies = [
"click>=8.1.7", "click>=8.1.7",
"pydantic>=2.10.3", "pydantic>=2.10.3",
] ]
[dependency-groups]
dev = [
"ruff>=0.8.1",
]
Generated
+33
View File
@@ -40,12 +40,20 @@ dependencies = [
{ name = "pydantic" }, { name = "pydantic" },
] ]
[package.dev-dependencies]
dev = [
{ name = "ruff" },
]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "click", specifier = ">=8.1.7" }, { name = "click", specifier = ">=8.1.7" },
{ name = "pydantic", specifier = ">=2.10.3" }, { name = "pydantic", specifier = ">=2.10.3" },
] ]
[package.metadata.requires-dev]
dev = [{ name = "ruff", specifier = ">=0.8.1" }]
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.10.3" version = "2.10.3"
@@ -99,6 +107,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/df/c3/b15fb833926d91d982fde29c0624c9f225da743c7af801dace0d4e187e71/pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5", size = 1882983 }, { url = "https://files.pythonhosted.org/packages/df/c3/b15fb833926d91d982fde29c0624c9f225da743c7af801dace0d4e187e71/pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5", size = 1882983 },
] ]
[[package]]
name = "ruff"
version = "0.8.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/95/d0/8ff5b189d125f4260f2255d143bf2fa413b69c2610c405ace7a0a8ec81ec/ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f", size = 3313222 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a2/d6/1a6314e568db88acdbb5121ed53e2c52cebf3720d3437a76f82f923bf171/ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5", size = 10532605 },
{ url = "https://files.pythonhosted.org/packages/89/a8/a957a8812e31facffb6a26a30be0b5b4af000a6e30c7d43a22a5232a3398/ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087", size = 10278243 },
{ url = "https://files.pythonhosted.org/packages/a8/23/9db40fa19c453fabf94f7a35c61c58f20e8200b4734a20839515a19da790/ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209", size = 9917739 },
{ url = "https://files.pythonhosted.org/packages/e2/a0/6ee2d949835d5701d832fc5acd05c0bfdad5e89cfdd074a171411f5ccad5/ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871", size = 10779153 },
{ url = "https://files.pythonhosted.org/packages/7a/25/9c11dca9404ef1eb24833f780146236131a3c7941de394bc356912ef1041/ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1", size = 10304387 },
{ url = "https://files.pythonhosted.org/packages/c8/b9/84c323780db1b06feae603a707d82dbbd85955c8c917738571c65d7d5aff/ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5", size = 11360351 },
{ url = "https://files.pythonhosted.org/packages/6b/e1/9d4bbb2ace7aad14ded20e4674a48cda5b902aed7a1b14e6b028067060c4/ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d", size = 12022879 },
{ url = "https://files.pythonhosted.org/packages/75/28/752ff6120c0e7f9981bc4bc275d540c7f36db1379ba9db9142f69c88db21/ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26", size = 11610354 },
{ url = "https://files.pythonhosted.org/packages/ba/8c/967b61c2cc8ebd1df877607fbe462bc1e1220b4a30ae3352648aec8c24bd/ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1", size = 12813976 },
{ url = "https://files.pythonhosted.org/packages/7f/29/e059f945d6bd2d90213387b8c360187f2fefc989ddcee6bbf3c241329b92/ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c", size = 11154564 },
{ url = "https://files.pythonhosted.org/packages/55/47/cbd05e5a62f3fb4c072bc65c1e8fd709924cad1c7ec60a1000d1e4ee8307/ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa", size = 10760604 },
{ url = "https://files.pythonhosted.org/packages/bb/ee/4c3981c47147c72647a198a94202633130cfda0fc95cd863a553b6f65c6a/ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540", size = 10391071 },
{ url = "https://files.pythonhosted.org/packages/6b/e6/083eb61300214590b188616a8ac6ae1ef5730a0974240fb4bec9c17de78b/ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9", size = 10896657 },
{ url = "https://files.pythonhosted.org/packages/77/bd/aacdb8285d10f1b943dbeb818968efca35459afc29f66ae3bd4596fbf954/ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5", size = 11228362 },
{ url = "https://files.pythonhosted.org/packages/39/72/fcb7ad41947f38b4eaa702aca0a361af0e9c2bf671d7fd964480670c297e/ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790", size = 8803476 },
{ url = "https://files.pythonhosted.org/packages/e4/ea/cae9aeb0f4822c44651c8407baacdb2e5b4dcd7b31a84e1c5df33aa2cc20/ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6", size = 9614463 },
{ url = "https://files.pythonhosted.org/packages/eb/76/fbb4bd23dfb48fa7758d35b744413b650a9fd2ddd93bca77e30376864414/ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737", size = 8959621 },
]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.12.2" version = "4.12.2"