mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
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
This commit is contained in:
@@ -98,6 +98,7 @@ DOCS_SCRIPTS = {
|
||||
"doc_metadata",
|
||||
"doc_html_pdf",
|
||||
"doc_sign",
|
||||
"doc_scrub_meta",
|
||||
}
|
||||
|
||||
if DISPATCHER_PROFILE == "docs":
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user