CLI tool
kanzure/pdfparanoia avatar
kanzure/pdfparanoia

pdfparanoia strips publisher watermarks from academic PDFs

pdf watermark removal library for academic papers

568 stars49 forksPythonLicense varies

At a glance

What is it?
pdfparanoia is a Python library and command line tool that removes the per-page watermarks publishers stamp into academic PDFs, together with the identifying metadata they carry. It handles a fixed list of publishers well, and the string scrubbing path is the part that generalises.
Who is it for?
Adopt pdfparanoia when your pipeline handles PDFs from the publishers it supports and you need the file to stop carrying your institution, name or IP address. Do not adopt it as a general PDF sanitiser, since the plugin list is fixed and the string scrubbing path only removes text it is told about.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 169 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Watermarks that carry your identity off a downloaded paper

Some publishers stamp every page of a downloaded academic paper with a watermark. The point is not decoration. According to the README, the mark can include private information such as institution names, personal names, IP addresses, timestamps and other identifying details, which is how a subscription provider traces a PDF back to the reader who downloaded it. That is the problem pdfparanoia addresses: it removes those watermarks, and it also scrubs metadata, so the file no longer carries the trail that identifies where it came from. The audience is researchers and the people who build tools around their reading, a graduate student who wants to share a figure without handing out their institution's name, or a pipeline that indexes papers and should not be storing reader identifiers. It is also the audience that has to decide whether removing the mark is allowed, which is a question about the licence terms of the publisher rather than about the code. Nothing in the README takes a position on that, and neither should a tool that changes the file.

Publisher plugins for a fixed list, and a plugin for arbitrary strings

The mechanism has two halves. The first half is a set of publisher-specific plugins that recognise the watermark of a particular provider and strip it. The supported list in the README is AIP, IEEE, JSTOR, RSC, and SPIE, with the last one qualified as sort of. That qualifier is doing real work in the sentence, and there is no sample file in the repository to test against, so treat SPIE as the least predictable of the five. The second half arrived later and is the more interesting piece. The changelog entry for 0.0.18 describes a pikepdf-based CustomStrings plugin that does TJ-kerning-aware content-stream scrubbing along with XMP and DocInfo metadata scrubbing, exposed through the --scrub-strings and --only-scrub-metadata flags. The kerning awareness matters because PDF text is drawn with positioning operators between fragments of a word, so a naive string match on the content stream misses text that is split across show-text operations. Custom strings are what make the tool useful for a provider that is not on the list, provided you know what the watermark says.

Two dependencies and a two step install

Installation is described as simple, and the primary route is a pip install:

bash
sudo pip install pdfparanoia

The README also gives the source route for a checkout:

bash
sudo python setup.py install

Both commands use sudo, which is a bad habit to copy into a script, and a virtual environment is the safer way to run either of them. The dependency picture is spelled out in the README: pdfminer.six for the publisher watermark plugins, and pikepdf for the custom string and metadata scrubbing, both installed automatically by pip. The setup.py file makes the version split explicit. On Python 3 the dependency is pdfminer.six, described in a comment as the actively maintained Python 3 fork, while older interpreters get the pdfminer release from 2013, and pikepdf is required in both cases. The README states that the library is written for Python 2.7+ or Python 3. That claim is worth checking against your own environment rather than trusting, since the package metadata still lists classifiers for Python 3.1, 3.2 and 3.3, which tells you the packaging metadata was written years ago and has not been revisited. The repository also carries a Makefile whose install target runs python3 setup.py install, which is a slightly friendlier default than the sudo form in the README.

Calling scrub() on an open file handle

The library entry point takes a PDF and returns the scrubbed bytes, which you then write out yourself. The README gives this example:

python
import pdfparanoia

pdf = pdfparanoia.scrub(open("nmat91417.pdf", "rb"))

with open("output.pdf", "wb") as file_handler:
    file_handler.write(pdf)

Two details are visible in that snippet and both are worth copying. The input is opened in binary mode, which is required because the function works on bytes rather than on text, and the output is written to a separate file handle opened in write binary mode. The example does not show the input handle being closed, so if you adopt this pattern, use a context manager on the read side too, otherwise you are relying on garbage collection to release the file descriptor. The second detail is what is not shown. The example passes no publisher argument and no strings, so the behaviour depends on which plugins fire for the file at hand, and there is no documented way in the README to ask for a specific publisher only. If you need predictable output for a batch job, treat the plugin selection as something you verify per document rather than as a switch you set once.

The command line takes a path, a pipe, or a string to erase

The CLI, added in version 0.0.11 according to the changelog, accepts the PDF either as a path in the first argument slot or on standard input, and it is verbose on request:

bash
pdfparanoia --verbose input.pdf -o output.pdf

The pipe form is the same tool reading from a stream and writing to another:

bash
cat input.pdf | pdfparanoia > output.pdf

That is the shape you want in a shell pipeline that processes documents one at a time, since it avoids temporary files entirely. The string scrubbing flags are where the tool goes beyond the publisher list. The -s flag removes a specific string from all page content streams and from metadata:

bash
pdfparanoia input.pdf -s "My Institution" --only-scrub-metadata -o output.pdf

The flag combination shown there is the metadata-only variant, which leaves page text untouched, while dropping --only-scrub-metadata broadens the same flag to the content streams as well. The README notes that multiple -s flags can be combined, that matching is case-insensitive by default with a --case-sensitive option to opt out, and that the -o flag is safe to use with the same path as the input file, which removes the usual objection to in-place editing. A sensible first command for a real document is the verbose run on a copy, so you can see which plugin matched before you trust the output.

Where pdfparanoia is the wrong tool

The scope is narrow and the failure mode is quiet. A publisher that is not on the list gets no dedicated plugin, and unless the watermark is a plain string that your -s flag can match, the file comes out essentially unchanged with no error to tell you. Since the verbose flag prints what happened, a batch job should check that output rather than assume every document was cleaned. The fixed publisher list is the other limit: the tool knows AIP, IEEE, JSTOR, RSC and SPIE, and nothing about any other provider, and the repository ships no test corpus of sample PDFs, so a new publisher is a research project rather than a configuration change. A second wrong-tool case is documents that are not PDFs at all, or PDFs whose watermarks are images rather than text, since a content-stream scrubber has nothing to match on an embedded picture. A third is legal: a publisher's watermark is often there precisely to enforce the terms under which you got the file, and removing it can breach the licence your institution agreed to, even though the tool works perfectly. That is a judgement for the person who obtained the document, and no amount of accuracy in the code changes it.

String scrubbing against changing a rendered page

The alternative approach is to stop parsing the PDF and change the picture instead. Render each page to an image, detect the mark in the raster output, and cover it, which handles an image based watermark, a vector drawing or a stamp that no text match can find. The difference in approach is fundamental. The text path works inside the document structure, so the output stays a real PDF with selectable text, searchable content and intact metadata objects, and the only thing removed is what you named. The raster path works on pixels, so it can defeat marks that carry no text at all, but the page it produces is an image of a page: text selection stops working, the file grows, and any machine reading of that document now depends on OCR. That is a heavy price to pay for a watermark you could have named. A third approach, closer to the second, is to avoid the problem rather than clean up after it: obtain the file from a source that does not stamp it, such as a preprint server or a licensed bulk feed, so the identifying data is never present. If you have to handle documents from many publishers with unknown marks, the three approaches combine: use the publisher plugins where they apply, fall back to -s for a known string, and reserve raster work for the stubborn remainder.

BSD terms, a nosetests target, and the last push on 2026-05-07

The README says the project is released under a BSD licence, and setup.py declares license="BSD" with a matching classifier, while the licence field in the repository settings is empty, so the source files and the setup metadata are your reference. The dependency set is the thing to keep an eye on, because a BSD licensed wrapper inherits whatever the licences of pdfminer.six and pikepdf require, and those are separate projects with their own terms. The repository carries a Makefile with a test target that runs nosetests, a clean target that removes build artefacts and compiled files, an install target and an upload target, and the top level also has a tests/ directory, a requirements.txt pinning pdfminer, a setup.py, a MANIFEST.in and a bin/ directory that holds the console script. The test runner is the giveaway about age: nosetests is long deprecated, and combined with the old Python classifiers it suggests the suite has not been kept in step with modern tooling. On maintenance, the repository is not archived and the last push was on 2026-05-07, with no GitHub releases, so the version you install from PyPI and the version on the master branch are only as fresh as that date. The changelog in the README records the last feature work at 0.0.18, the CustomStrings plugin, with publisher support added in earlier entries for RSC, SPIE, JSTOR, IEEE and AIP.

Editorial conclusion

Adopt pdfparanoia when your pipeline handles PDFs from the publishers it supports and you need the file to stop carrying your institution, name or IP address. Do not adopt it as a general PDF sanitiser, since the plugin list is fixed and the string scrubbing path only removes text it is told about. Before you build a workflow on it, check three things: the licence in the repository, since the README says BSD while the licence field in the repository settings is empty, whether removing the publisher mark is permitted by the terms under which you obtained the paper, and the behaviour of the tool on the specific publisher you care about, because the README lists SPIE with a qualifier and offers no sample files to test against.

Frequently asked questions

Which publishers does pdfparanoia support?

The README lists AIP, IEEE, JSTOR, RSC and SPIE, with SPIE qualified as sort of. Anything outside that list relies on the custom string scrubbing path rather than on a dedicated plugin.

What does pdfparanoia need to install?

The README states that pdfminer.six and pikepdf are installed automatically by pip, with pdfminer.six used for the publisher watermark plugins and pikepdf for the custom string and metadata scrubbing. Installation itself is a pip install of pdfparanoia.

How do I remove a specific string from a PDF with pdfparanoia?

Pass the string with the -s flag, which removes it from page content streams and metadata, or add --only-scrub-metadata to leave page text untouched. The README notes that multiple -s flags can be combined and that matching is case-insensitive unless you pass --case-sensitive.

Can pdfparanoia edit a file in place?

Yes. The README states that the -o flag is safe to use with the same path as the input file, so the output can overwrite the input without corrupting it.

How do I use pdfparanoia from Python?

Import the package and pass an open binary file to pdfparanoia.scrub, then write the returned bytes to an output file. The README gives that example with open("nmat91417.pdf", "rb") as the input.

Official sources

  1. Issues
  2. kanzure/pdfparanoia on GitHub
  3. Project website
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/kanzure-pdfparanoia.svg)](https://hysenlabs.com/projects/kanzure-pdfparanoia)

Was this useful?