Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e083f39e8 | ||
|
|
825338a2a3 | ||
|
|
a2f88923ab |
@@ -10,10 +10,11 @@ A command-line tool for processing and cleaning scanned musical score PDFs. This
|
|||||||
- **PNG Optimization**: Optimize PNG files using optipng (if installed)
|
- **PNG Optimization**: Optimize PNG files using optipng (if installed)
|
||||||
- **Modular Processing**: Process your files step by step or all at once
|
- **Modular Processing**: Process your files step by step or all at once
|
||||||
- **High Quality Output**: Preserve image quality throughout the process
|
- **High Quality Output**: Preserve image quality throughout the process
|
||||||
|
- **Professional PDF Output**: Generate A4-sized PDFs with proper borders and centered content
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Ensure you have Python 3.8+ installed
|
1. Ensure you have Python 3.12+ installed
|
||||||
2. Install uv (recommended) or pip
|
2. Install uv (recommended) or pip
|
||||||
3. Clone this repository:
|
3. Clone this repository:
|
||||||
```bash
|
```bash
|
||||||
@@ -55,15 +56,20 @@ Automatically detects and corrects page rotation by analyzing staff lines.
|
|||||||
|
|
||||||
### Optimize Pages
|
### Optimize Pages
|
||||||
```bash
|
```bash
|
||||||
./pdf_cleaner.py optimize
|
./pdf_cleaner.py optimize [--level {1,2,3}]
|
||||||
```
|
```
|
||||||
Trims excess white space and optionally runs PNG optimization (requires optipng).
|
Processes pages with different optimization levels:
|
||||||
|
- Level 1: Only trims excess white space
|
||||||
|
- Level 2: Trims white space and converts to 1-bit monochrome
|
||||||
|
- Level 3: All optimizations + PNG optimization (requires optipng)
|
||||||
|
|
||||||
|
Default level is 1 if not specified.
|
||||||
|
|
||||||
### Create Final PDF
|
### Create Final PDF
|
||||||
```bash
|
```bash
|
||||||
./pdf_cleaner.py finalize output.pdf
|
./pdf_cleaner.py finalize output.pdf
|
||||||
```
|
```
|
||||||
Combines all processed pages into a final PDF and cleans up temporary files.
|
Combines all processed pages into a final PDF with proper A4 sizing, borders, and centered content.
|
||||||
|
|
||||||
### Typical Workflow
|
### Typical Workflow
|
||||||
```bash
|
```bash
|
||||||
@@ -83,7 +89,10 @@ Combines all processed pages into a final PDF and cleans up temporary files.
|
|||||||
3. **Optimization**:
|
3. **Optimization**:
|
||||||
- Detects content boundaries and removes excess white space
|
- Detects content boundaries and removes excess white space
|
||||||
- Optionally runs optipng for additional file size reduction
|
- Optionally runs optipng for additional file size reduction
|
||||||
4. **Finalization**: Combines processed images back into a PDF using img2pdf
|
4. **Finalization**:
|
||||||
|
- Combines processed images into a professional A4-sized PDF
|
||||||
|
- Adds configurable borders around content
|
||||||
|
- Centers content on each page while maintaining aspect ratio
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
@@ -100,4 +109,15 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[Insert chosen license here]
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
|
- v0.9rc2: Second release candidate
|
||||||
|
- Improved documentation
|
||||||
|
- Added optimization level descriptions
|
||||||
|
- Fixed aspect ratio in PDF output
|
||||||
|
- v0.9rc1: First release candidate with full functionality
|
||||||
|
- Professional PDF output with A4 sizing and borders
|
||||||
|
- Complete image processing pipeline
|
||||||
|
- Configurable settings
|
||||||
+41
-27
@@ -15,6 +15,7 @@ from pdf2image import convert_from_path
|
|||||||
import settings
|
import settings
|
||||||
from settings import OptimizationLevel
|
from settings import OptimizationLevel
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
import concurrent.futures
|
||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
TRIM_PADDING_PIXELS = 20
|
TRIM_PADDING_PIXELS = 20
|
||||||
@@ -323,40 +324,53 @@ 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: Always trim whitespace
|
# Step 1: Convert to monochrome if level >= 2
|
||||||
print("\nTrimming whitespace from images...")
|
|
||||||
for i, file_path in enumerate(temp_files, 1):
|
|
||||||
print(f"[{i}/{total_files}] Processing {os.path.basename(file_path)}...", end='', flush=True)
|
|
||||||
# Only optimize if this is the final step (level 1)
|
|
||||||
if trim_whitespace(file_path, is_final=(opt_level == OptimizationLevel.TRIM)):
|
|
||||||
successful['trim'] += 1
|
|
||||||
print(" ")
|
|
||||||
else:
|
|
||||||
print(" ")
|
|
||||||
|
|
||||||
# Step 2: Convert to monochrome if level >= 2
|
|
||||||
if int(opt_level) >= int(OptimizationLevel.MONOCHROME):
|
if int(opt_level) >= int(OptimizationLevel.MONOCHROME):
|
||||||
print("\nConverting to monochrome...")
|
print("\nConverting to monochrome...")
|
||||||
for i, file_path in enumerate(temp_files, 1):
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
print(f"[{i}/{total_files}] Converting {os.path.basename(file_path)}...", end='', flush=True)
|
futures = {executor.submit(convert_to_monochrome, file_path, (opt_level == OptimizationLevel.MONOCHROME)): file_path for file_path in temp_files}
|
||||||
# Only optimize if this is the final step (level 2)
|
for future in concurrent.futures.as_completed(futures):
|
||||||
if convert_to_monochrome(file_path, is_final=(opt_level == OptimizationLevel.MONOCHROME)):
|
file_path = futures[future]
|
||||||
successful['monochrome'] += 1
|
try:
|
||||||
print(" ")
|
if future.result():
|
||||||
else:
|
successful['monochrome'] += 1
|
||||||
print(" ")
|
print(f"Converted {os.path.basename(file_path)}")
|
||||||
|
else:
|
||||||
|
print(f"Failed to convert {os.path.basename(file_path)}")
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"{os.path.basename(file_path)} generated an exception: {exc}")
|
||||||
|
|
||||||
|
# Step 2: Always trim whitespace
|
||||||
|
print("\nTrimming whitespace from images...")
|
||||||
|
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}
|
||||||
|
for future in concurrent.futures.as_completed(futures):
|
||||||
|
file_path = futures[future]
|
||||||
|
try:
|
||||||
|
if future.result():
|
||||||
|
successful['trim'] += 1
|
||||||
|
print(f"Trimmed {os.path.basename(file_path)}")
|
||||||
|
else:
|
||||||
|
print(f"Failed to trim {os.path.basename(file_path)}")
|
||||||
|
except Exception as 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 == OptimizationLevel.FULL:
|
||||||
if check_optipng_installed():
|
if check_optipng_installed():
|
||||||
print("\nOptimizing PNG files with optipng...")
|
print("\nOptimizing PNG files with optipng...")
|
||||||
for i, file_path in enumerate(temp_files, 1):
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
print(f"[{i}/{total_files}] Optimizing {os.path.basename(file_path)}...", end='', flush=True)
|
futures = {executor.submit(run_optipng, file_path): file_path for file_path in temp_files}
|
||||||
if run_optipng(file_path):
|
for future in concurrent.futures.as_completed(futures):
|
||||||
successful['optipng'] += 1
|
file_path = futures[future]
|
||||||
print(" ")
|
try:
|
||||||
else:
|
if future.result():
|
||||||
print(" ")
|
successful['optipng'] += 1
|
||||||
|
print(f"Optimized {os.path.basename(file_path)}")
|
||||||
|
else:
|
||||||
|
print(f"Failed to optimize {os.path.basename(file_path)}")
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"{os.path.basename(file_path)} generated an exception: {exc}")
|
||||||
else:
|
else:
|
||||||
print("\nNote: optipng not found. Skipping PNG optimization.")
|
print("\nNote: optipng not found. Skipping PNG optimization.")
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "notes-cleaner"
|
name = "notes-cleaner"
|
||||||
version = "0.9rc1"
|
version = "0.9rc2"
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
Reference in New Issue
Block a user