Phillip Briggs

Q: Applescript to Duplicate PDF (x) number of times

Is is possible to duplicate a PDF a variable number of times?

 

Ideally I want to take a single page PDF and duplicate the page {n} number of times within the PDF file, to create a multiple page PDF.

 

I'd be launching this from Esko Automation Engine and passing the PDF and the variable quantity to the script - applescript or shell script.

 

Any help would be greatly appreciated!

Posted on Aug 8, 2016 12:21 AM

Close

Q: Applescript to Duplicate PDF (x) number of times

  • All replies
  • Helpful answers

Page 1 Next
  • by VikingOSX,

    VikingOSX VikingOSX Aug 8, 2016 6:02 AM in response to Phillip Briggs
    Level 7 (20,606 points)
    Mac OS X
    Aug 8, 2016 6:02 AM in response to Phillip Briggs

    I have a couple of solutions that can be used in a Terminal Bash script where you can specify the one-page source PDF, and the number of duplications of that one-page that you seek, and the application will update the PDF in-place with those additional pages.

    1. Python
      1. Requires OS X System Python due to its using the scripting bridge to Quartz PDFKit.
      2. Usage: script.py ~/some-one-page.pdf 4
        1. adds four additional clones of the original page to the specified PDF
    2. AppleScript/Objective-C
      1. Compiled and saved from Script Editor as Application
      2. Usage: open pdf_dup.app --args -pdf ~/Desktop/p51.pdf -dupCnt 4
      3. See this previous post.

     

    Tested with Python 2.7.10 on OS X 10.11.6

    #!/usr/bin/python

    # coding: utf-8

    # Update in-place, a one-page PDF with n-duplicate additional pages

    # Usage: pdfdup.py ~/Desktop/p51.pdf 4

     

    from Quartz import PDFDocument, NSURL

    import os

    import sys

     

    if len(sys.argv) < 3:

        sys.exit("Usage: %s one-page.pdf dup_page_count".format(sys.argv[0]))

     

    pdf_file = os.path.expanduser(sys.argv[1])

    if sys.argv[2].isdigit():

        duplicate_pages = int(sys.argv[2])

    else:

        sys.exit("Require number of pages to duplicate.")

     

    url = NSURL.fileURLWithPath_(pdf_file)

    pdf = PDFDocument.alloc().initWithURL_(url)

     

    if pdf.pageCount() == 1:

        for newpage in xrange(0, duplicate_pages):

            pdf.insertPage_atIndex_(pdf.pageAtIndex_(0), newpage)

    else:

        sys.exit("PDF contained more than one page")

    pdf.writeToFile_(pdf_file)

    sys.exit()

     

    AppleScript/Objective-C

     

    -- pdf_duplicate.applescript

    -- Updates one-page PDF in-place with specified count of original page duplicates

    -- VikingOSX, Aug 2016, Apple Support Community

     

    -- Usage: open pdf_duplicate.app --args -pdf ~/Desktop/p51.pdf -dupCnt 4

     

    use scripting additions

    use framework "Foundation"

    use framework "Quartz"

    use AppleScript version "2.4" -- Yosemite or later

     

    try

         set argx to current application's NSUserDefaults's standardUserDefaults()

         set pdfArg to (argx's stringForKey:"pdf") as text

         set dupArg to (argx's integerForKey:"dupCnt") as integer

    on error errmsg number errnbr

         my error_handler(errnbr, errmsg)

         return

    end try

    if pdfArg is "" or dupArg is 0 then

         my error_handler(-1, "Bogus user input")

    end if

    -- prepend users HOME directory to ~/relative paths

    set tpath to current application's NSString's stringWithString:pdfArg

    set stdpath to tpath's stringByStandardizingPath() as text

     

    set anURL to current application's NSURL's fileURLWithPath:stdpath

    set pdf to current application's PDFDocument's alloc()'s initWithURL:anURL

    set page_count to pdf's pageCount()

     

    if page_count = 1 then

         repeat with newpage from 1 to dupArg

              (pdf's insertPage: (pdf's pageAtIndex:0) atIndex:newpage)

         end repeat

    end if

    pdf's writeToFile:stdpath

    return

     

    on error_handler(nbr, msg)

         return display alert "[ " & nbr & " ] " & msg as critical giving up after 10

    end error_handler

  • by VikingOSX,

    VikingOSX VikingOSX Aug 8, 2016 7:23 AM in response to VikingOSX
    Level 7 (20,606 points)
    Mac OS X
    Aug 8, 2016 7:23 AM in response to VikingOSX

    Here is an updated AppleScript/Objective-C version that does more input validity checks. It works the same as previously specified.

     

    -- pdf_duplicate.applescript

    -- Updates one-page PDF in-place with specified count of original page duplicates

    -- VikingOSX, Aug 2016, Apple Support Community

    -- Version 1.2 : Added input validity checks.

    -- Usage: open pdf_duplicate.app --args -pdf ~/Desktop/p51.pdf -dupCnt 4

     

    use scripting additions

    use framework "Foundation"

    use framework "Quartz"

    use AppleScript version "2.4" -- Yosemite or later

     

    set argx to current application's NSUserDefaults's standardUserDefaults()

    set pdfArg to (argx's stringForKey:"pdf") as text

    set dupArg to (argx's integerForKey:"dupCnt") as integer

     

    if pdfArg is missing value or dupArg is 0 then

         my error_handler(-128, "Missing or invalid arguments")

         return

    end if

     

    try

      -- prepend users HOME directory to ~/relative paths, or pass full paths through as is

      set tpath to current application's NSString's stringWithString:pdfArg

      set stdpath to tpath's stringByStandardizingPath() as text

     

      set filemgr to current application's NSFileManager's defaultManager()

      if not (filemgr's fileExistsAtPath:stdpath) then

           my error_handler(-128, "Specified input PDF does not exist.")

           return

      end if

     

      set anURL to current application's NSURL's fileURLWithPath:stdpath

      set pdf to current application's PDFDocument's alloc()'s initWithURL:anURL

     

      if pdf's pageCount() = 1 then

           repeat with newpage from 1 to dupArg

                (pdf's insertPage: (pdf's pageAtIndex:0) atIndex:newpage)

           end repeat

           pdf's writeToFile:stdpath

      else

           my err_handler(-128, "PDF has more than a single page")

           return

      end if

     

    on error errmsg number errnbr

         my error_handler(errnbr, errmsg)

         return

    end try

    return

     

    on error_handler(nbr, msg)

         return display alert "[ " & nbr & " ] " & msg as critical giving up after 10

    end error_handler

  • by Phillip Briggs,

    Phillip Briggs Phillip Briggs Aug 8, 2016 7:33 AM in response to VikingOSX
    Level 1 (24 points)
    Mac OS X
    Aug 8, 2016 7:33 AM in response to VikingOSX

    Thanks for the quick response VikingOSX!

     

    I can get the python script to work fine, as an executable script, but for some reason the AppleScript/Objective-C solution won't let me save it as an app? I get the error:

    The document “Untitled” could not be saved as “pdf_duplicate”. C and Objective-C pointers cannot be saved in scripts.

     

    However, I am hoping to run the python script from a shell script or applescript from my Esko Automation Engine, which passes the arguments from within the workflow, rather than user input.

     

    This is a script that I currently use for splitting PDFs (that Hiroto and yourself helped me with before) and I was hoping to modify it to duplicate PDFs - but I can not figure out how to replace the Ruby script with the required python script!

     

    #!/bin/bash

    #

    #   for Esko Automation Engine Script Runner

    #

    #   $1   : input files separatated by :

    #   $2   : output directory

    #   $3.. : additional parameters

    #   exit : 0 = OK, 1 = Warning, 2 = Error

    #

     

     

    $ruby <<'EOF' - "$@"

    #

    #   ARGV[0]   : input files separatated by :

    #   ARGV[1]   : output directory

    #   ARGV[2..] : additional parameters

    #

    require 'osx/cocoa'

    include OSX

    require_framework 'PDFKit'

     

     

    ret = 0

    outdir = ARGV[1].chomp('/')

     

     

    ARGV[0].split(':').select {|f| f =~ /\.pdf$/i }.each do |f|

        url = NSURL.fileURLWithPath(f)

        doc = PDFDocument.alloc.initWithURL(url)

        path = doc.documentURL.path

        pcnt = doc.pageCount

        bname = File.basename(f, File.extname(f))

       

        (0 .. (pcnt - 1)).each do |i|

            page = doc.pageAtIndex(i)

            page.string.to_s =~ /1000\S*/

            name = $&

            unless name

                ret = [ret, 1].max

                $stderr.puts "No matching string in page #{i + 1} of #{path}."

                next # ignore this page

            end

            newname = name[0..-1]

     

     

            doc1 = PDFDocument.alloc.initWithData(page.dataRepresentation) # doc for this page

            unless doc1.writeToFile("#{outdir}/#{bname}_#{newname}.pdf")

                ret = [ret, 2].max

                $stderr.puts "Failed to save page #{i + 1} of #{path}."

            end

        end

    end

    exit ret

    EOF

  • by VikingOSX,

    VikingOSX VikingOSX Aug 8, 2016 8:39 AM in response to Phillip Briggs
    Level 7 (20,606 points)
    Mac OS X
    Aug 8, 2016 8:39 AM in response to Phillip Briggs

    The AppleScript/Objective-C code is not Objective-C (*.m), or C++ (*.C). It is AppleScript (*.applescript).

     

    Copy and paste the v1.2 AppleScript/Objective-C code into your Script Editor (Launchpad : Other : Script Editor). Click the hammer icon to compile it, and then two saves: 1) Save... to pdfdup.applescript using File Format: Text., and 2) Option + File menu : Save as..., to same name, but now File format: Application — somewhere in your default search path, or your Desktop.

     

    The way it is written cannot be run interactively as it will not prompt for a filename. So, in the Terminal, run it as the usage line shows in the source code.

     

    Ruby also can use the Scripting Bridge, and for the most part, accesses the same Cocoa PDF library routines that Python, or the AppleScript/Objective-C code does. However, Apple broke the Ruby Scripting Bridge after Mavericks, and has not fixed it. There is an opensource RubyCocoa framework that can be installed into Mavericks and Yosemite, but not El Capitan (without compiling/installing it from sources).

  • by Phillip Briggs,

    Phillip Briggs Phillip Briggs Aug 8, 2016 8:47 AM in response to VikingOSX
    Level 1 (24 points)
    Mac OS X
    Aug 8, 2016 8:47 AM in response to VikingOSX

    aah I see, I did not realise that was the method. I will have a play with that.

     

    In regards to using the python script from within Esko Automation Engine I have tried this:

     

    #!/usr/bin/python

    # coding: utf-8

    # Update in-place, a one-page PDF with n-duplicate additional pages

    #

    #   for Esko Automation Engine Script Runner

    #

    #   $1   : input files separatated by :

    #   $2   : output directory

    #   $3.. : additional parameters

    #   exit : 0 = OK, 1 = Warning, 2 = Error

    #

    #   ARGV[0]   : input files separatated by :

    #   ARGV[1]   : output directory

    #   ARGV[2..] : additional parameters

     

     

    from Quartz import PDFDocument, NSURL

    import os

    import sys

    if len(sys.argv) < 3:

      sys.exit("Usage: %s one-page.pdf dup_page_count".format(sys.argv[0]))

    pdf_file = os.path.expanduser(sys.argv[1])

    if sys.argv[2].isdigit():

      duplicate_pages = int(sys.argv[2])

    else:

      sys.exit("Require number of pages to duplicate.")

    url = NSURL.fileURLWithPath_(pdf_file)

    pdf = PDFDocument.alloc().initWithURL_(url)

    if pdf.pageCount() == 1:

      for newpage in xrange(0, duplicate_pages):

      pdf.insertPage_atIndex_(pdf.pageAtIndex_(0), newpage)

    else:

      sys.exit("PDF contained more than one page")

    pdf.writeToFile_(pdf_file)

    sys.exit()

     

     

    But I'm not getting the variables into the script properly - it returns the message: "Require number of pages to duplicate."

  • by VikingOSX,

    VikingOSX VikingOSX Aug 8, 2016 9:06 AM in response to Phillip Briggs
    Level 7 (20,606 points)
    Mac OS X
    Aug 8, 2016 9:06 AM in response to Phillip Briggs

    That's because it wasn't written with any prior awareness of the input format that Eskoo requires. So I will have to rewrite some of the Python logic to accommodate the updated input format.

     

    Right now, it is noon, and I have to take the neighbors to the airport shortly, so I won't get back to the Python code until later this afternoon. Afterwards, the fix and testing should not take long. I will repost an update here as soon as it is done.

  • by Phillip Briggs,

    Phillip Briggs Phillip Briggs Aug 8, 2016 9:08 AM in response to VikingOSX
    Level 1 (24 points)
    Mac OS X
    Aug 8, 2016 9:08 AM in response to VikingOSX

    wow that is so good of you

     

    Sorry I did not explain better first time

     

    Always grateful for your help!

  • by VikingOSX,Helpful

    VikingOSX VikingOSX Aug 9, 2016 12:57 AM in response to Phillip Briggs
    Level 7 (20,606 points)
    Mac OS X
    Aug 9, 2016 12:57 AM in response to Phillip Briggs

    Phillip,

     

    Have recoded the Python script as a HERE document in a Bash script, and added functionality for it to work in an Esko script. Still testing and debugging.

     

    Think I am going to stop for today and resume tomorrow morning. Short night, long day.

  • by Hiroto,

    Hiroto Hiroto Aug 9, 2016 12:57 AM in response to Phillip Briggs
    Level 5 (7,281 points)
    Aug 9, 2016 12:57 AM in response to Phillip Briggs

    Hello

     

    I remember that rubycocoa script.

     

    Using Ruby, Rename PDF with text from input PDF file

    https://discussions.apple.com/thread/6994395

     

    Now you might try something like the following script which employs pyobjc in lieu of rubycocoa.

     

     

    #!/bin/bash
    # 
    #   for Esko Automation Engine Script Runner
    # 
    #   $1   : input files separatated by :
    #   $2   : output directory
    #   $3.. : additional parameters
    #   exit : 0 = OK, 1 = Warning, 2 = Error
    #
    
    /usr/bin/python <<'EOF' - "$@"
    # coding: utf-8
    # 
    #   file:
    #       duplicate_pdf_pages.py
    #   
    #   usage:
    #       duplicate_pdf_pages.py pdf[:pdf...] [pdf[:pdf...] ...] output_directory number_of_duplicates
    #           argv[1:-2] : sets of source pdf file(s) separated by :
    #           argv[-2]   : output directory
    #           argv[-1]   : number of duplicate pages to generate
    # 
    #       e.g.,
    #           ./duplicate_pdf_pages.py a.pdf:b.pdf:c.pdf d.pdf e.pdf outdir 3
    # 
    import sys, os, re
    from Foundation import NSURL
    from Quartz.PDFKit import PDFDocument
    
    def usage():
        sys.stderr.write('Usage: %s pdf[:pdf...] [pdf[:pdf...] ...] output_directory number_of_duplicates\n'
            % os.path.basename(sys.argv[0]))
        sys.exit(2)
    
    def main():
        uargv = [ a.decode('utf-8') for a in sys.argv ]
        if len(uargv) < 3: usage()
        odir = uargv[-2].rstrip('/') if os.path.isdir(uargv[-2]) else None
        if not odir:
            sys.stderr.write('Not a directory: %s\n' % uargv[-2].encode('utf-8'))
            usage()
        try:
            npage = int(uargv[-1])
        except ValueError as e:
            sys.stderr.write('Invalid duplicate number: %s\n' % uargv[-1].encode('utf-8'))
            usage()
        if not npage in range(1, 1000):
            sys.stderr.write('Duplicate number out of range [1, 1000): %d\n' % npage)
            usage()
    
        ret = 0
        aa = [ a.split(':') for a in uargv[1:-2] ]  # list of lists of pdfs
        aa = [ b for a in aa for b in a ]           # flattened list of pdfs
        for f in [ a for a in aa if re.search(r'\.pdf$', a, re.I | re.U) ]:
            url = NSURL.fileURLWithPath_(f)
            doc = PDFDocument.alloc().initWithURL_(url)
            if not doc:
                ret = max(ret, 2)
                sys.stderr.write('Not a pdf file: %s\n' % f.encode('utf-8'))
                continue
            path = doc.documentURL().path()
            bname, ext = os.path.splitext(os.path.basename(path))
    
            f1 = '%s/%s%s' % (odir, bname, ext)
            doc1 = PDFDocument.alloc().init()
            for i in range(0, doc.pageCount()):
                page = doc.pageAtIndex_(i)
                for k in range(0, npage):
                    doc1.insertPage_atIndex_(page, doc1.pageCount())
                if not doc1.writeToFile_(f1):
                    ret = max(2, ret)
                    sys.stderr.write('Failed to save: %s\n' % (f1.encode('utf-8')))
    
        sys.exit(ret)
    
    main()
    EOF
    

     

     

     

    Briefly tested with pyobjc 2.2b3 and python 2.6.1 under OS X 10.6.8.

     

    Good luck,

    H

     

    PS. Fixed a code a little.

  • by Phillip Briggs,

    Phillip Briggs Phillip Briggs Aug 9, 2016 1:00 AM in response to VikingOSX
    Level 1 (24 points)
    Mac OS X
    Aug 9, 2016 1:00 AM in response to VikingOSX

    Thanks

  • by Phillip Briggs,

    Phillip Briggs Phillip Briggs Aug 9, 2016 1:08 AM in response to Hiroto
    Level 1 (24 points)
    Mac OS X
    Aug 9, 2016 1:08 AM in response to Hiroto

    Have tested that and it works great!

     

    I see that the argv are defined as [-1], [-2] etc. Is that the standard method in python?

     

    If I add other python scripts, with more variables coming from Esko, what would they be defined as for example - or do they just follow sequentially?

  • by VikingOSX,Solvedanswer

    VikingOSX VikingOSX Aug 9, 2016 2:44 AM in response to Phillip Briggs
    Level 7 (20,606 points)
    Mac OS X
    Aug 9, 2016 2:44 AM in response to Phillip Briggs

    Phillip,

     

    Don't confuse this script with Hiroto's RubyCocoa conversion to PyObjC of his original code — with what I am posting here. Different application goals.

     

    The following Python code has been "hardened somewhat for input errors, and is designed to return error codes per Eskoo. It takes one or more single page PDF files, and based on those specified documents, an output directory location, and an integer duplication factor — all specified on the command-line, it will update each PDF in-place with the inserted duplications of the original document page. It will work either as the Bash script that I have posted below, or as a standalone Python program.

     

    Assumption: The script is being run in the same folder as the input files, or a relative or full path is specified for each input file.

    #!/bin/bash

    #

    # $1 : input files separated by ':'

    # $2 : output directory

    # $3 : additional parameters - duplication count

    # exit : 0 = OK, 1 = Warning, 2 = Error

    #

    # Criterion:      Process one or more PDF files that contain only one page.

    #                     Based on supplied integer value, insert n-tuple additional

    #                     copies of this first page into the PDF, and update same.

    #

    # Usage:         pdf_duplicate.sh file1.pdf:file2.pdf:filen.pdf /path/to/outdir dup_cnt

    # Standalone: pdf_duplicate.py <same arguments as above>

    # Required:    OS X System Python with Scripting Bridge support

    #

    # V1.2, tested with Python 2.7.10 on OS X 10.11.6

    # VikingOSX, Aug 2016, Apple Support Communites

     

    python <<EOF - "$@"

    # coding: utf-8

     

    from Foundation import NSURL

    from Quartz.PDFKit import PDFDocument

    import os

    import sys

     

    rtn = 0

     

    def usage():

        print("Usage: {} file.pdf path/to/outdir dupe_cnt".format(sys.argv[0]))

        print("Usage: {} file1.pdf:file2.pdf:filen.pdf path/to/outdir dupe_cnt".

                format(sys.argv[0]))

        print("Usage: {} ~/path/file1.pdf:~/path/filen.pdf path/to/outdir dup_cnt".

                format(sys.argv[0]))

        sys.exit(2)

     

    def main():

     

        if len(sys.argv) < 4:

            usage()

            sys.exit(2)

     

        if sys.argv[1].count(':'):

            pdf_files = [os.path.expanduser(x) for x in sys.argv[1].split(':')]

        else:

            pdf_files = os.path.expanduser(sys.argv[1])

     

        if os.path.isdir(os.path.expanduser(sys.argv[2])):

            out_dir = os.path.expanduser(sys.argv[2]).rstrip('/')

        else:

            print("{} Output directory: {} - does not exist".

                  format(sys.argv[0], sys.argv[2]))

            sys.exit(2)

     

        if sys.argv[3].isdigit():

            dup_page_cnt = int(sys.argv[3])

        else:

            print("{} Duplication count must be an integer".format(sys.argv[0]))

            sys.exit(2)

     

        for name in pdf_files:

            url = NSURL.fileURLWithPath_(name)

            pdf = PDFDocument.alloc().initWithURL_(url)

            if not pdf:

                print("Skipping: {} - Not a valid PDF file".format(name))

                rtn = 2

                continue

     

            if pdf.pageCount() == 1:

                for newpage in range(0, dup_page_cnt):

                    pdf.insertPage_atIndex_(pdf.pageAtIndex_(0), newpage)

            else:

                print("Skipping: {} - Already more than one page".format(name))

                rtn = 2

                continue

     

            outname = os.path.join(out_dir, os.path.basename(name))

            if not pdf.writeToFile_(outname):

                print("Could not save PDF: {}".format(name))

                rtn = 2

     

    if __name__ == '__main__':

        main()

        sys.exit(rtn)

    EOF

    exit

  • by Phillip Briggs,

    Phillip Briggs Phillip Briggs Aug 9, 2016 3:00 AM in response to VikingOSX
    Level 1 (24 points)
    Mac OS X
    Aug 9, 2016 3:00 AM in response to VikingOSX

    Thanks Viking, that's very helpful and it works perfectly.

     

    It is very interesting to see how both scripts work.

     

    I am so grateful for the help I receive on this forum - I wish I could repay you both

  • by Phillip Briggs,

    Phillip Briggs Phillip Briggs Aug 9, 2016 3:01 AM in response to Hiroto
    Level 1 (24 points)
    Mac OS X
    Aug 9, 2016 3:01 AM in response to Hiroto

    Thanks again!

Page 1 Next