Offline conversion
PDF page operations.
A PDF goes in and a PDF comes out, with the pages rearranged. Not a conversion — its own namespace, on your machine, with no account.
Why this is a separate namespace
Everything else under convilyn.local answers "turn this format into that
format". These functions do not: the format never changes. Merging two PDFs,
pulling out pages 1–3, or rotating a sideways scan are page operations, and
giving them their own namespace keeps convert() from growing a pages=
argument that means nothing for a .docx.
from convilyn.local import pdf
pdf.merge(["cover.pdf", "body.pdf"], "combined.pdf")
pdf.select("report.pdf", "summary.pdf", pages="1-3,10")
pdf.burst("scan.pdf", "pages/") # one file per page
pdf.rotate("sideways.pdf", "upright.pdf", degrees=270)
pdf.encrypt("draft.pdf", "sealed.pdf", password="…")The operations
| Function | What it does |
|---|---|
merge(sources, output) | Concatenate several PDFs in the order given |
select(source, output, pages=…) | Keep only the pages named |
burst(source, out_dir, pages=…) | One file per page, zero-padded (page_001.pdf) |
rotate(source, output, degrees=…) | Quarter turns only — 90, 180, 270 |
compress(source, output) | Lossless stream recompression |
encrypt(source, output, password=…) | Password-protect |
decrypt(source, output, password=…) | Remove protection you can already open |
page_count(source) | How many pages |
extract_text(source, pages=…) | The text layer, if there is one |
Every function returns the Path it wrote (burst returns a tuple of them), so
you can chain without restating filenames.
Page ranges are 1-based, as printed
pages takes single pages, inclusive ranges, or both:
pdf.select("report.pdf", "extract.pdf", pages="3")
pdf.select("report.pdf", "extract.pdf", pages="1-5")
pdf.select("report.pdf", "extract.pdf", pages="1-3,7,10-12")Page 1 is the first page — the number printed on the page selector in every PDF reader. There is no zero-indexed variant, deliberately: an off-by-one that silently drops the first page of a legal filing is not worth the consistency with Python slicing.
What extract_text does and does not do
text = pdf.extract_text("report.pdf", pages="1-2")It returns the PDF's text layer. A scanned page has none, so it returns an empty string.
That is the honest answer rather than a failure. Recovering glyphs from a picture is optical character recognition — a different operation, with a cost that grows with the page count rather than the file count, which is why it runs in the platform's metered workflows and not here.
Best practice. Check for an empty result and route those documents onward rather than assuming the file was broken:
text = pdf.extract_text("scan.pdf")
if not text.strip():
... # no text layer — this one needs the metered pathFrom the shell
convilyn local pdf merge cover.pdf body.pdf -o combined.pdf
convilyn local pdf select report.pdf summary.pdf --pages 1-3,10
convilyn local pdf split scan.pdf --out-dir pages/
convilyn local pdf rotate sideways.pdf upright.pdf --degrees 270
convilyn local pdf info report.pdf --textprotect and unlock prompt for the password when --password is omitted, so
it stays out of your shell history:
$ convilyn local pdf protect draft.pdf sealed.pdf
Password: ********
✓ sealed.pdfErrors
Everything here raises PdfOperationError, which subclasses
convilyn.ConvilynError like the rest of the SDK — so an existing
except ConvilynError: block already covers it.
from convilyn.local import pdf
from convilyn.local.errors import PdfOperationError, MissingDependencyError
try:
pdf.merge(["a.pdf", "b.pdf"], "out.pdf")
except MissingDependencyError as exc:
print(exc) # names the extra to install
except PdfOperationError as exc:
print(exc) # a wrong password, an unreadable page range, a corrupt fileA wrong password on decrypt is an error, not an empty file.