Files
SnapOtter/packages/ai/python/doc_scrub_meta.py
T
SnapOtterandGitHub 8b3f1e6884 fix: stamp SnapOtter as Producer on generated PDFs (#416)
Conversion engines wrote their own names into PDF metadata: LibreOffice,
Ghostscript, pdfcpu, WeasyPrint, and PDFKit all stamped Producer/Creator
on generated files. A new doc_scrub_meta docs-profile script (PyMuPDF)
rewrites both fields to SnapOtter and drops the stale XMP copy; the
worker applies it to the 25 PDF-generating tools before outputs reach
object storage. Best effort by design: any failure keeps the original
bytes and only logs a warning.

Deliberately untouched: tools that edit the user's own PDF and preserve
its metadata (qpdf edits, sign, flatten), encrypted outputs (copied
through), and pdfa-convert, where a metadata rewrite risks PDF/A
conformance.

Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7
2026-07-04 03:05:32 +00:00

50 lines
1.7 KiB
Python

"""Set the PDF Producer/Creator to SnapOtter on generated documents.
Conversion engines stamp themselves (LibreOffice, Ghostscript, pdfcpu,
WeasyPrint, PDFKit); from the user's point of view SnapOtter produced the
file. Only runs for tools that GENERATE PDFs; pass-through edits keep the
user's original metadata untouched.
Args: {"path": in, "out": out}. Prints {"ok": true}."""
import json
import sys
def main():
args = json.loads(sys.argv[1]) if len(sys.argv) > 1 else {}
path, out = args.get("path"), args.get("out")
if not path or not out:
print(json.dumps({"error": "missing path/out"}))
sys.exit(1)
try:
import fitz
except ImportError:
print(json.dumps({"error": "PyMuPDF not installed"}))
sys.exit(1)
try:
doc = fitz.open(path)
if doc.needs_pass:
# Encrypted output: pass it through untouched rather than
# re-encrypting. `out` must always exist for the caller.
doc.close()
import shutil
shutil.copyfile(path, out)
print(json.dumps({"ok": True, "skipped": "encrypted"}))
return
meta = doc.metadata or {}
meta["producer"] = "SnapOtter"
meta["creator"] = "SnapOtter"
doc.set_metadata(meta)
# XMP often carries the engine's own pdf:Producer; DocInfo is the
# canonical source for these files, so drop the stale copy.
doc.del_xml_metadata()
doc.save(out)
doc.close()
print(json.dumps({"ok": True}))
except Exception as exc: # noqa: BLE001
print(json.dumps({"error": str(exc)}))
sys.exit(1)
if __name__ == "__main__":
main()