I am trying to create a script to watermark a group a PDF files in a folder, each PDF has to have a unique watermark.

All the PDFs are in one folder, all the watermarks are in .png located in another folder. I currently have watermark script which I found on Apple Forums, but this was made to watermark all files with one watermark only.

What I need is a way to watermark each PDF file with it's own different watermark (there are 400 pdfs and 400 different watermarks)

Automator Screenshot

Here is the contents of the watermark tool.py:

# Watermark each page in a PDF document

import sys #, os
import getopt
import math
from Quartz.CoreGraphics import *
from Quartz.ImageIO import *

def drawWatermark(ctx, image, xOffset, yOffset, angle, scale, opacity):
    if image:
        imageWidth = CGImageGetWidth(image)
        imageHeight = CGImageGetHeight(image)
        imageBox = CGRectMake(0, 0, imageWidth, imageHeight)
        CGContextSetAlpha(ctx, opacity)
        CGContextTranslateCTM(ctx, xOffset, yOffset)
        CGContextScaleCTM(ctx, scale, scale)
        CGContextTranslateCTM(ctx, imageWidth / 2, imageHeight / 2)
        CGContextRotateCTM(ctx, angle * math.pi / 180)
        CGContextTranslateCTM(ctx, -imageWidth / 2, -imageHeight / 2)
        CGContextDrawImage(ctx, imageBox, image)

def createImage(imagePath):
    image = None
    # provider = CGDataProviderCreateWithFilename(imagePath)    # FIXED: replaced by the following CGDataProviderCreateWithURL()
    url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, imagePath, len(imagePath), False)
    provider = CGDataProviderCreateWithURL(url)
    if provider:
        imageSrc = CGImageSourceCreateWithDataProvider(provider, None)
        if imageSrc:
            image = CGImageSourceCreateImageAtIndex(imageSrc, 0, None)
    if not image:
        print "Cannot import the image from file %s" % imagePath
    return image

def watermark(inputFile, watermarkFiles, outputFile, under, xOffset, yOffset, angle, scale, opacity, verbose):
    images = map(createImage, watermarkFiles)
    ctx = CGPDFContextCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, outputFile, len(outputFile), False), None, None)
    if ctx:
        pdf = CGPDFDocumentCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, inputFile, len(inputFile), False))
        if pdf:
            for i in range(1, CGPDFDocumentGetNumberOfPages(pdf) + 1):
                image = images[i % len(images) - 1]
                page = CGPDFDocumentGetPage(pdf, i)
                if page:
                    mediaBox = CGPDFPageGetBoxRect(page, kCGPDFMediaBox)
                    if CGRectIsEmpty(mediaBox):
                        mediaBox = None
                    CGContextBeginPage(ctx, mediaBox)
                    if under:
                        drawWatermark(ctx, image, xOffset, yOffset, angle, scale, opacity)
                    CGContextDrawPDFPage(ctx, page)
                    if not under:
                        drawWatermark(ctx, image, xOffset, yOffset, angle, scale, opacity)
        del pdf
    del ctx

def main(argv):
    verbose = False
    readFilename = None
    writeFilename = None
    under = False
    xOffset = 0.0   # FIXED: changed to float value
    yOffset = 0.0   # FIXED: changed to float value
    angle = 0.0     # FIXED: changed to float value
    scale = 1.0     # FIXED: added
    opacity = 1.0
    # Parse the command line options
        options, args = getopt.getopt(argv, "vutx:y:a:p:s:i:o:", ["verbose", "under", "over", "xOffset=", "yOffset=", "angle=", "opacity=", "scale=", "input=", "output=", ])
    except getopt.GetoptError:

    for option, arg in options:
        print option, arg
        if option in ("-i", "--input") :
            if verbose:
                print "Reading pages from %s." % (arg)
            readFilename = arg
        elif option in ("-o", "--output") :
            if verbose:
                print "Setting %s as the output." % (arg)
            writeFilename = arg
        elif option in ("-v", "--verbose") :
            print "Verbose mode enabled."
            verbose = True
        elif option in ("-u", "--under"):
            print "watermark under PDF"
            under = True
        elif option in ("-t", "--over"):    # FIXED: changed to "-t" from "t"
            print "watermark over PDF"
            under = False
        elif option in ("-x", "--xOffset"):
            xOffset = float(arg)
        elif option in ("-y", "--yOffset"):
            yOffset = float(arg)
        elif option in ("-a", "--angle"):
            angle = -float(arg)
        elif option in ("-s", "--scale"):
            scale = float(arg)
        elif option in ("-p", "--opacity"):
            opacity = float(arg)
            print "Unknown argument: %s" % (option)

    if (len(args) > 0):
        watermark(readFilename, args, writeFilename, under, xOffset, yOffset, angle, scale, opacity, verbose);
        shutil.copyfile(readFilename, writeFilename);

def usage():
    print "Usage: watermark --input <file> --output <file> <watermark files>..."

if __name__ == "__main__":
    print sys.argv
  • A few questions...how are you associating the watermark file with the PDF file? Do you need to watermark each page of the PDF or just the first page? Where does this watermark have to be placed? – Allan Nov 28 '20 at 21:20
  • Each PDF has only one page, each watermark has to be placed on the center of the page. Usually, I can achieve this by doing the watermarks one by one. But doing this for 400 files takes a considerable amount of time. The problem is that the watermark scripts were written to use only one watermark for multiple files, or for one PDF file with multiple pages. – Lauro Mota Nov 29 '20 at 11:28
  • Do the watermark image files and the corresponding image files have the same name ( for e.g. 1.png for 1.pdf etc.)? – sfxedit Nov 30 '20 at 21:02
  • Yes they all have corresponding names, I put the files in two different folders. – Lauro Mota Dec 1 '20 at 4:30

I tested this under macOS Catalina using the code from the tool.py in the OP and the value of the options shown for it, while using PDF documents and graphic image files for the watermark, where the files are in different locations. It works for me as coded.

The following example shell script code assumes that for each PDF document there is a corresponding graphic image file of the same name with a different extension with all watermark files being in a different directory.

You will need to set the value of three variables at the top of the script, p2wf, wext and fqp2pyf, while the rest of the values from the original script are maintained except where they have already been modified.

In the Automator workflow, using a Get Specified Finder Items action for the PDF documents and a Run Shell Script action set as shown in your OP:

  • Shell: [/bin/bash]
  • Pass input: [as arguments]

Replace any existing code in the Run Shell Script action, default or otherwise, with:

    # Path to watermark files.
    # Example: "${HOME}/Pictures/Watermarks"
    # Watermark file extension, e.g.: png
    # Fully qualified pathname to python 'tool.py' file.
    # Example: "${HOME}/bin/tool.py"


    # f = fully qualified pathname
    # d = directory pathname
    # fn = filename with extension
    # n = filename without extension
    # e = filename extension

for f in "$@"; do


    "${fqp2pyf}" \
    --over \
    --xOffset 56 \
    --yOffset 58 \
    --angle 0 \
    --scale 0.11 \
    --opacity 1 \
    --input "${f}" \
    --output "${d}/${n} (COPY).${e}" \


Note: From past experience, I never use ~ for the home directory in scripts. I use e.g., $HOME or ${HOME} as ~ can actually be problematic in some situations.


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .