MacOS – How to create a script with Automator to watermark each PDF in a folder with a different watermark

applescriptautomatorbashmacospdf

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:

#!/usr/bin/python
# 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)
        
        CGContextSaveGState(ctx)
        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)
        CGContextRestoreGState(ctx)

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)
                    CGContextEndPage(ctx)
    
        del pdf
        CGPDFContextClose(ctx)
    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
    try:
        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:
        usage()
        sys.exit(2)

    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)
        
        else:
            print "Unknown argument: %s" % (option)

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

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

if __name__ == "__main__":
    print sys.argv
    main(sys.argv[1:])

Best Answer

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"

p2wf="${HOME}/Pictures/Watermarks"
wext="png"
fqp2pyf="${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

    d="${f%/*}"
    fn="${f##*/}"
    n="${fn%.*}"
    e="${fn##*.}"

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

done  

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.