2

I use an Automator application to move pdf files to a directory, then copy them to another directory, then convert the copy into a thumbnail using only the 1st page, with the file name remaining exactly the same as the original except with a .png extension instead of .pdf

I had this working under Mavericks, but it stopped working when I upgraded to Mojave, then I eventually got it to work again using the solution here. How to render only first page in PDF as image and then resize?

Now I'm trying to upgrade to Monterey, and it's broken again, and I can't figure it out this time.

Any ideas would be awesome. If possible, I'd like to come up with a solution that Apple won't break every couple years, but experience has taught me that is unlikely.

I've been messing with this for days.

Screenshot of my application attached below.

And here is the complete script I'm using.


#! /usr/bin/python
#
import sys
import os
import tempfile
from Quartz.CoreGraphics import *
from os.path import splitext
from os.path import basename
from os.path import join


def createPDFDocumentWithPath(path):
    return CGPDFDocumentCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, path, len(path), False))

def main(argv):

    for input_pdf_filename in argv:
        doc = createPDFDocumentWithPath(input_pdf_filename)
        page_one = CGPDFDocumentGetPage(doc, 1)
        mediaBox = CGPDFPageGetBoxRect(page_one, kCGPDFMediaBox)
        if CGRectIsEmpty(mediaBox):
            mediaBox = None

        file_name, extension = splitext(basename(input_pdf_filename))
        output_path = join(tempfile.gettempdir(), file_name + extension)
        writeContext = CGPDFContextCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, output_path, len(output_path), True), None, None)
        CGContextBeginPage(writeContext, mediaBox)
        CGContextDrawPDFPage(writeContext, page_one)
        CGContextEndPage(writeContext)
        CGPDFContextClose(writeContext)
        del writeContext

        print output_path


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

enter image description here


1
  • I've added a bit to my answer that should fix the error.
    – benwiggy
    Commented Jan 11, 2023 at 10:46

2 Answers 2

1

I think it had something to do with Quartz not being included in Monterey.

Quartz is part of macOS's core graphics framework. It is definitely included in Monterey.

However, python2 has been long deprecated, and Apple finally removed it in Monterey 12.3. You will either need to install python2 yourself from python.org (and then call /usr/local/bin/python, or /usr/bin/env python); or install python 3 and update your scripts to python3 (again, calling them correctly.)

You will also need to install pyobjC, which is the python library that bridges the OS's objects with python. (Quartz isn't missing from the OS; but the Quartz python module is missing from your python installation.)

Your script probably doesn't need much modification to make it run in python3, save for the first line.

You will find an host of python3 scripts for manipulating PDFs, including converting to PNG, here:

https://github.com/benwiggy/PDFsuite

You're getting an error because python2 and python3 handle strings and data in slightly different ways. You may need to encode the path string before using it in the PDF creation API:

pathNonU = filename.encode('utf-8')
return CGPDFDocumentCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, path, len(pathNonU), False))
8
  • I installed Python3, and PyObjC, which eventually got me to where I was last time with the quartz error (Details added to post above) Commented Jan 11, 2023 at 9:56
  • Then I tried to use the pdf2png script from your GitHub, which appears to work great, but I don't know how to get it to only look at the 1st page and have it keep the same file name. I'll mess with it more as soon as I get the chance, but I'm thinking adjusting the new script is probably my best bet. Thank you. Commented Jan 11, 2023 at 10:00
  • Sorry, I'm not getting this. My experience is limited to copying and pasting, then making minor changes through trial and error. I Tried adding those lines, along with several other combinations of those lines and the existing similar lines, and I keep getting, syntax error, expected [this], not defined, etc. I have almost no idea what I'm doing. ¯_(ツ)_/¯ Commented Jan 12, 2023 at 8:10
  • I tried installing Python2, but now I'm back to "ImportError: No module named Quartz.CoreGraphics” " and I can't figure out how to install the module. Commented Jan 12, 2023 at 8:26
  • @ChrisBanakis You'll need to import pyobjC for python2, separately from python3, using pip (not pip3). Realistically, you can't expect to work with python scripts without some understanding of python. There are any number of web resources for learning python, as well as books.
    – benwiggy
    Commented Jan 12, 2023 at 10:09
0

I ended up hacking something together using an existing Python3 script, and duct taping it together in Automator.

I'm sure I'll be back here in a few years when Apple breaks it again, but I'll take the win.

If you drag and drop a pdf onto the application, it moves the file to another folder, then creates a copy of it in another folder, then converts the copy to a png of the 1st page from the pdf using the exact same name except *.png, then moves the png to it's final folder.

It's not pretty, but if anyone needs it, it works.

Here's the Python3 script after I finished butchering it. (I'm sure there's plenty of stuff in there that doesn't need to be there, but it's working, so I'm gonna quit while I'm ahead)

#!/usr/bin/env python3

"""
PDF2PNG v.3.0: Creates a bitmap image from each page of each PDF supplied to it.
by Ben Byram-Wigfield
Now written for python3. You may need to install pyobjc with pip3.
"""
import os, sys
import Quartz as Quartz
# from LaunchServices import (kUTTypeJPEG, kUTTypeTIFF, kUTTypePNG, kCFAllocatorDefault) 

kUTTypeJPEG = 'public.jpeg'
kUTTypeTIFF = 'public.tiff'
kUTTypePNG = 'public.png'
kCFAllocatorDefault = None

resolution = 300.0 #dpi
scale = resolution/72.0

cs = Quartz.CGColorSpaceCreateWithName(Quartz.kCGColorSpaceSRGB)
whiteColor = Quartz.CGColorCreate(cs, (1, 1, 1, 1))
# Options: Quartz.kCGImageAlphaNoneSkipLast (no trans), Quartz.kCGImageAlphaPremultipliedLast 
transparency = Quartz.kCGImageAlphaNoneSkipLast

#Save image to file
def writeImage (image, url, type, options):
    destination = Quartz.CGImageDestinationCreateWithURL(url, type, 1, None)
    Quartz.CGImageDestinationAddImage(destination, image, options)
    Quartz.CGImageDestinationFinalize(destination)
    return

if __name__ == '__main__':

    for filename in sys.argv[1:]:
        filenameNonU = filename.encode('utf8')
        pdf = Quartz.CGPDFDocumentCreateWithProvider(Quartz.CGDataProviderCreateWithFilename(filenameNonU))
        print(filenameNonU)
        numPages = Quartz.CGPDFDocumentGetNumberOfPages(pdf)
        shortName = os.path.splitext(filename)[0]
        prefix = os.path.splitext(os.path.basename(filename))[0]
                # For each page, create a file
        for i in range (1, numPages+0):
            page = Quartz.CGPDFDocumentGetPage(pdf, i)
            if page:    
        #Get mediabox
                mediaBox = Quartz.CGPDFPageGetBoxRect(page, Quartz.kCGPDFMediaBox)
                x = Quartz.CGRectGetWidth(mediaBox)
                y = Quartz.CGRectGetHeight(mediaBox)
                x *= scale
                y *= scale
                r = Quartz.CGRectMake(0,0,x, y)
        # Create a Bitmap Context, draw a white background and add the PDF
                writeContext = Quartz.CGBitmapContextCreate(None, int(x), int(y), 8, 0, cs, transparency)
                Quartz.CGContextSaveGState (writeContext)
                Quartz.CGContextScaleCTM(writeContext, scale,scale)
                Quartz.CGContextSetFillColorWithColor(writeContext, whiteColor)
                Quartz.CGContextFillRect(writeContext, r)
                Quartz.CGContextDrawPDFPage(writeContext, page)
                Quartz.CGContextRestoreGState(writeContext)
        # Convert to an "Image"
                image = Quartz.CGBitmapContextCreateImage(writeContext) 
        # Create unique filename per page
                outFile = filename
                outFile_nonU = outFile.encode('utf8')
                url = Quartz.CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, outFile_nonU, len(outFile_nonU), False)
        # kUTTypeJPEG, kUTTypeTIFF, kUTTypePNG
                type = kUTTypePNG
        # See the full range of image properties on Apple's developer pages.
                options = {
                    Quartz.kCGImagePropertyDPIHeight: resolution,
                    Quartz.kCGImagePropertyDPIWidth: resolution
                    }
                writeImage (image, url, type, options)
            
                del page

And here's the complete Automator workflow. (The Shell dropdown under Run Shell Script is blank because I'm on a different computer which doesn't have Python3, but it should have the python3 path selected there to work)

enter image description here

1
  • Evidently I spoke too soon. I thought this worked, but whenever a 1-page pdf is passed to it, it generates a corrupt file. Back to the drawing board. Commented Jan 19, 2023 at 9:18

You must log in to answer this question.

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