Skip to content

Commit b5347cc

Browse files
feat: Add recipe system
1 parent 3f6ca1e commit b5347cc

14 files changed

Lines changed: 1529 additions & 15 deletions

.pre-commit-config.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ repos:
77
- id: trailing-whitespace
88
- id: check-yaml
99
- id: check-added-large-files
10-
- id: double-quote-string-fixer
1110

1211
# Python-specific hooks
1312
- repo: https://github.com/google/yapf

README.md

Lines changed: 120 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,20 @@ Features
1111
+ [x] Image inputs (e.g. PNG, JPG, etc.)
1212
+ [ ] Markdown inputs
1313
+ [x] **Merge PDFs**: Merge multiple PDFs into one PDF
14-
+ [x] **Split PDFs**: Split a PDF into multiple PDFs, each containing a range of pages from
15-
the original PDF
14+
+ [x] **Split PDFs**: Split a PDF into multiple PDFs, each containing a range of pages
1615
+ [x] **Export as image**: Export designated pages from a PDF as image files
1716
+ [x] **Remove pages**: Remove designated pages from a PDF
17+
+ [x] **Extract text**: Export text from a PDF file and optionally save it to a text file
18+
+ [x] **Recipe system**: Chain multiple operations together using YAML recipe files
19+
+ [ ] Add watermark to a PDF
1820
+ [ ] Encrypt a PDF
1921
+ [ ] Decrypt a PDF
20-
+ [ ] Add watermark to a PDF
2122
+ [ ] Extract images from a PDF
22-
+ [x] **Extract text**: Export text from a PDF file and optionally save it to a text file
2323
+ [ ] Extract links from a PDF
24+
+ [ ] Set PDF metadata (title, author, etc.)
2425

25-
If you want any other feature to be added, feel free to open an [issue](https://github.com/MPCodeWriter21/PDF-To-Image/issues)
26-
or fork the repo and make a [pull request](https://github.com/MPCodeWriter21/PDF-To-Image/pulls)
26+
If you want any other feature to be added, feel free to open an [issue](https://github.com/MPCodeWriter21/PDF-Helper/issues)
27+
or fork the repo and make a [pull request](https://github.com/MPCodeWriter21/PDF-Helper/pulls)
2728
after adding your contribution.
2829

2930
Usage
@@ -67,8 +68,8 @@ Bundle multiple files into one PDF:
6768
```bash
6869
pdf-helper bundle <input_file_1> <input_file_2>... <input_file_n> <output_file>
6970

70-
# E.g. Merge PDFs 1, 2 and 3 into a new PDF
71-
pdf-helper merge 1.pdf 2.pdf 3.pdf new.pdf
71+
# E.g. Bundle PDFs 1, 2 and 3 into a new PDF
72+
pdf-helper bundle 1.pdf 2.pdf 3.pdf new.pdf
7273

7374
# E.g. Take 1.png, 2.jpg, and 3.png and create a PDF named 123.pdf and override
7475
# if already exists
@@ -130,6 +131,117 @@ pdf-helper extract-text <input_file> -o <output_file_name>
130131
pdf-helper extract-text my-pdf.pdf -o my-text.txt
131132
```
132133

134+
### Run Recipes
135+
136+
The recipe system lets you chain multiple PDF operations together in a single run
137+
using a YAML file. This unlocks features not available through individual CLI
138+
commands (e.g. selecting specific pages per file when bundling).
139+
140+
```bash
141+
pdf-helper run-recipe <recipe_file.yaml>
142+
143+
# E.g. Run a simple recipe
144+
pdf-helper run-recipe remove-pages.yaml
145+
146+
# E.g. Run with force overwrite and verbose logging
147+
pdf-helper run-recipe bundle-workflow.yaml --force --verbose
148+
```
149+
150+
#### Recipe File Format
151+
152+
A recipe is a YAML file with a `steps` list. Each step has an `id`, an
153+
`operation`, input/output paths, and operation-specific options. Steps can
154+
reference each other's outputs using `{ step: step_id }`.
155+
156+
```yaml
157+
name: "Remove specific pages"
158+
description: "Removes pages 2, 4, 6 from a PDF."
159+
version: "1.0"
160+
161+
steps:
162+
- id: clean
163+
operation: remove_pages
164+
input: document.pdf
165+
pages_to_remove: [2, 4, 6]
166+
output: cleaned.pdf
167+
```
168+
169+
#### Supported Operations
170+
171+
| Operation | Status | Description |
172+
|---|---|---|
173+
| `bundle` | Available | Bundle files with optional per-file page selection |
174+
| `remove_pages` | Available | Remove pages by 1-based index |
175+
| `split_pdf` | Available | Split at given page boundaries |
176+
| `pdf_to_image` | Available | Render pages as PNG images |
177+
| `extract_text` | Available | Extract text content |
178+
| `watermark` | Planned | Add text watermark (graceful fallback) |
179+
| `encrypt` | Planned | Password-protect PDF (graceful fallback) |
180+
| `metadata` | Planned | Set title/author/keywords (graceful fallback) |
181+
182+
Operations marked *Planned* are not yet implemented — the recipe runner
183+
logs a warning and copies the input file through, so pipelines don't break.
184+
185+
#### Advanced Example: Multi-step Pipeline
186+
187+
```yaml
188+
name: "Split, Convert, and Extract Pipeline"
189+
version: "1.0"
190+
191+
settings:
192+
temp_dir: "./.recipe-tmp"
193+
194+
steps:
195+
# Step 1: Split the PDF at pages 5 and 10
196+
- id: split
197+
operation: split_pdf
198+
input: report.pdf
199+
split_points: [5, 10]
200+
output_dir: .
201+
output_prefix: "report_part_"
202+
203+
# Step 2: Convert the second chunk to images
204+
- id: to_images
205+
operation: pdf_to_image
206+
input:
207+
step: split
208+
file: report_part_2.pdf
209+
pages: "1-3"
210+
scale: 3
211+
output: ./output/images
212+
213+
# Step 3: Extract text from the first chunk
214+
- id: extract
215+
operation: extract_text
216+
input:
217+
step: split
218+
file: report_part_1.pdf
219+
pages: "1-4"
220+
max_characters: 5000
221+
reverse_lines: true
222+
output: ./output/chapter-1-text.txt
223+
```
224+
225+
#### Recipe Settings
226+
227+
| Setting | Default | Description |
228+
|---|---|---|
229+
| `temp_dir` | `./.recipe-tmp` | Directory for intermediate files |
230+
| `overwrite` | `false` | Overwrite existing output files |
231+
| `cleanup_temp` | `false` | Remove temp directory after completion |
232+
233+
Input values (e.g. passwords) can be sourced from environment variables or
234+
prompted at runtime:
235+
236+
```yaml
237+
inputs:
238+
password:
239+
env: PDF_PASSWORD
240+
prompt: "Enter output PDF password"
241+
```
242+
243+
See [`examples/recipes/`](examples/recipes) for more example recipe files.
244+
133245
About
134246
-----
135247

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# yaml-language-server: $schema=../../schemas/recipe-schema.json
2+
name: "Bundle with Page Selection"
3+
description: "Bundles specific pages from multiple PDFs together with images into a single PDF. Page selection per file is not available through the CLI."
4+
version: "1.0"
5+
6+
steps:
7+
- id: assemble
8+
operation: bundle
9+
inputs:
10+
- cover.pdf
11+
- path: chapter-1.pdf
12+
pages: [1, 2, 3]
13+
- logo.png
14+
- path: chapter-2.pdf
15+
pages: "1-5,8,10-12"
16+
- path: appendix.pdf
17+
pages: [1, 3, 5]
18+
output: compiled.pdf

examples/recipes/remove-pages.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# yaml-language-server: $schema=../../schemas/recipe-schema.json
2+
name: "Remove Specific Pages"
3+
description: "Removes a set of pages (1-based) from a PDF and writes the result."
4+
version: "1.0"
5+
6+
steps:
7+
- id: clean
8+
operation: remove_pages
9+
input: document.pdf
10+
pages_to_remove: [2, 4, 6]
11+
output: cleaned.pdf
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# yaml-language-server: $schema=../../schemas/recipe-schema.json
2+
name: "Secure Report Publishing Workflow"
3+
description: >
4+
A complete publishing pipeline:
5+
1. Bundles selected pages from multiple reports
6+
2. Adds a CONFIDENTIAL watermark across every page
7+
3. Encrypts the output with a password
8+
4. Sets author/title metadata
9+
Combines existing, planned, and future features into a single orchestrated run.
10+
version: "1.0"
11+
12+
inputs:
13+
password:
14+
env: PDF_PASSWORD
15+
prompt: "Enter output PDF password"
16+
17+
settings:
18+
temp_dir: "./.recipe-tmp"
19+
20+
steps:
21+
- id: assemble
22+
operation: bundle
23+
inputs:
24+
- cover-page.pdf
25+
- path: executive-summary.pdf
26+
pages: "1-3"
27+
- path: financial-report.pdf
28+
pages: [1, 2, 3, 5, 7, 10, 11, 12]
29+
output: "{temp_dir}/assembled.pdf"
30+
31+
- id: watermark
32+
operation: watermark
33+
input:
34+
step: assemble
35+
text: "CONFIDENTIAL"
36+
position: center
37+
opacity: 0.15
38+
rotation: 30.0
39+
font_size: 48
40+
output: "{temp_dir}/watermarked.pdf"
41+
42+
- id: encrypt
43+
operation: encrypt
44+
input:
45+
step: watermark
46+
password:
47+
input: password
48+
algorithm: AES-256
49+
permissions:
50+
printing: false
51+
copying: false
52+
modifying: false
53+
output: "{temp_dir}/encrypted.pdf"
54+
55+
- id: set_metadata
56+
operation: metadata
57+
input:
58+
step: encrypt
59+
title: "Annual Financial Report 2026"
60+
author: "Finance Department"
61+
subject: "Q4 Financial Summary"
62+
keywords: ["finance", "annual", "2026", "confidential"]
63+
producer: "PDF-Helper Recipe System v1.0"
64+
output: final-report.pdf
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# yaml-language-server: $schema=../../schemas/recipe-schema.json
2+
name: "Split, Convert, and Extract Pipeline"
3+
description: "Splits a PDF at given points, converts the second chunk to PNG images, and extracts text from the first chunk — all in one recipe run."
4+
version: "1.0"
5+
6+
settings:
7+
temp_dir: "./.recipe-tmp"
8+
9+
steps:
10+
- id: split
11+
operation: split_pdf
12+
input: report.pdf
13+
split_points: [5, 10]
14+
output_dir: .
15+
output_prefix: "report_part_"
16+
17+
- id: to_images
18+
operation: pdf_to_image
19+
input:
20+
step: split
21+
file: report_part_2.pdf
22+
pages: "1-3"
23+
scale: 3
24+
output: ./output/images
25+
26+
- id: extract
27+
operation: extract_text
28+
input:
29+
step: split
30+
file: report_part_1.pdf
31+
pages: "1-4"
32+
max_characters: 5000
33+
reverse_lines: true
34+
output: ./output/chapter-1-text.txt

pyproject.toml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "PDF-Helper"
3-
version = "0.2.2"
3+
version = "0.3.0"
44
authors = [
55
{name = "CodeWriter21(Mehrad Pooryoussof)", email = "CodeWriter21@gmail.com"}
66
]
@@ -11,7 +11,16 @@ requires-python = ">=3.10"
1111
dependencies = [
1212
"log21>=3.3.2",
1313
"pypdfium2>=4.30.0",
14-
"Pillow>=11.0.0"
14+
"Pillow>=11.0.0",
15+
"pyyaml>=6.0.3",
16+
]
17+
18+
[project.optional-dependencies]
19+
test = ["pytest>=9.1.1"]
20+
21+
[dependency-groups]
22+
dev = [
23+
"pytest>=9.1.1",
1524
]
1625

1726
[project.scripts]
@@ -46,3 +55,10 @@ line-length = 88
4655

4756
[tool.ruff.lint]
4857
extend-select = ["C4", "SIM", "TCH", "ANN", "N", "B"]
58+
59+
[tool.ruff.lint.per-file-ignores]
60+
"src/pdf_helper/*" = ["ANN101"]
61+
"tests/*" = ["ANN"]
62+
63+
[tool.pytest.ini_options]
64+
testpaths = ["tests"]

0 commit comments

Comments
 (0)