PDF Utility - (Rotate, Extract, Autorotate, Reduce and Optimize - Batch Processing)

Post your working scripts, libraries and tools.
TheOriginalAbe
Posts: 4
Joined: 25 Mar 2024, 16:33

PDF Utility - (Rotate, Extract, Autorotate, Reduce and Optimize - Batch Processing)

08 May 2024, 12:01

I made this script to manipulate PDF files. I made this script to help with things that otherwise take too long (typical workflow is to open a PDF editor and make edits then save the file which is just too long).

First this script requires a few dependencies.
1. GuiResizer V2 (viewtopic.php?f=83&t=113921 by FanaticGuru) (download the ahk file and store in the same directory as my PDF Utility.)
2. Ghostscript (used for reduce and optimization operations) (https://ghostscript.com/releases/gsdnld.html)
3. qpdf (handles rotations and extractions) (https://github.com/qpdf/qpdf/releases) (NOTE: when installing make sure to pick an option which adds qpdf to your system path)
4. ocrmypdf (install by running the below in powershell) (requires pip so need to install Python first if you dont have it already)

Code: Select all

winget install -e --id Python.Python.3.12
(close and reopen powershell)

Code: Select all

pip3 install ocrmypdf
5. Tesseract OCR (install by running the below in powershell)

Code: Select all

winget install -e --id UB-Mannheim.TesseractOCR
6. (OPTIONAL) unpaper (for windows) (used for the --clean tag in ocrmypdf which greatly improves the accuracy of the autorotate feature) (NOTE: I could not figure out how to build unpaper on windows from source despite many hours spent trying. If anyone here is able to figure that out please let me know what you did. I ended up finding a github repo from someone who was able to build it on windows and just provided the binary. I'm not going to link to that repo as I fear this may count as closed source and don't want to risk violating forum rules. Either way if you are able to build it yourself then just make sure to add it to your system path by going to environment variables in windows, and modify the below script as per my comments which mention "unpaper", also let me know what you did to build it - that would be nice. Here is a link to the official github repo https://github.com/unpaper/unpaper)

The script is triggered by the hotstring "!QQ" but you can change that to something which you find more comfortable - this just goes along with the theme of how my other hotstrings are setup.

Couple more notes:
1. the autorotate feature is set to ignore pages which have selectable text (my thinking behind this is that if I have a PDF which has selectable text then it was generated by a program - and so should already be in the correct orientation). If you want to change this behavior you can modify this line to change the --skip-text tag to be --force-ocr instead (see below). If you want to actually embed OCR text, modify autorotate sensitivity, or do anything else other than the specific things my script makes it do - you can read the ocrmypdf documentation and then modify the below to suit your needs. (https://ocrmypdf.readthedocs.io/en/latest/index.html)

Code: Select all

; change this 
Command .= "ocrmypdf --output-type=pdf --rotate-pages --rotate-pages-threshold=.1 --tesseract-timeout=0 --skip-text --optimize=0 --oversample=300 `"" TempOutputFileName "`" `"" TempOutputFileName "`""
; to this
Command .= "ocrmypdf --output-type=pdf --rotate-pages --rotate-pages-threshold=.1 --tesseract-timeout=0 ---force-ocr --optimize=0 --oversample=300 `"" TempOutputFileName "`" `"" TempOutputFileName "`""
2. You can drag in as many pdf files as you want - there is not a limit on how many the script can process, it will handle it just fine and there are progress bars to show the progress as it goes.

This is my first forum post after discovering AHK & this forum over 5 years ago. I know my code can definitely be improved - and I'm not master at this by any means. That being said I welcome your criticisms and critiques. I am definitely looking forward to learning from this community & to sharing more of the tools I've made in the future.


Here is the code for the script.

Code: Select all

;### PDF Utility by TheOriginalAbe 2024 ###

#Requires AutoHotKey v2.0
#SingleInstance Force
#Include GuiReSizer_v2.ahk

global version := "1.0.13"
global TempPdfFilePath := A_ScriptDir "\Temp\Temp.pdf"
global TempCmdOut := A_ScriptDir "\Temp\Temp.txt"
global TempPdfBat := A_ScriptDir "\Temp\Temp.bat"
global TempPath := A_ScriptDir "\Temp\"
global Files := Array()

if !FileExist(TempPath)
    DirCreate(TempPath)

:*C:!QQ::
{
    CreatePDFGui()
}

CreatePDFGui(){


    aPDFGui := Gui("+LastFound +DPIScale +AlwaysOnTop",  "Abe's PDF Utility")
    aPDFGui.OnEvent("Escape", aPDFGui_Escape)
    aPDFGui.OnEvent("Size", GuiReSizer)
    aPDFGui.OnEvent("DropFiles", aPDFGui_DropFiles)

    aPDFGui.editObj := {}
    aPDFGui.btnObj := {}
    aPDFGui.txtObj := {}
    aPDFGui.cbObj := {}
    aPDFGui.ProgressObj := {}

    aPDFGui.txtObj.File := aPDFGui.Add("Text","","File:")
    aPDFGui.txtObj.File.X := 8

    aPDFGui.editObj.SelectedFile := aPDFGui.Add("Edit", "yp w400 vSelectedFile")
    aPDFGui.btnObj.SelectFile := aPDFGui.Add("Button", "yp vSelectFileButton", "Browse")
    aPDFGui.btnObj.SelectFile.OnEvent("Click", SelectFileButton_Clicked)

    aPDFGui.txtObj.Rotation := aPDFGui.Add("Text","","Rotation:")
    aPDFGui.txtObj.Rotation.Anchor := aPDFGui.txtObj.File
    aPDFGui.txtObj.Rotation.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.txtObj.Rotation, xp := 0)

    aPDFGui.txtObj.PageRange := aPDFGui.Add("Text","","Page Range (e.g. 1-4,8,10-12,15-z)")
    aPDFGui.txtObj.PageRange.Anchor := aPDFGui.txtObj.Rotation
    aPDFGui.txtObj.PageRange.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.txtObj.PageRange, xp := 0 , yp := 1)

    aPDFGui.txtObj.Plus90 := aPDFGui.Add("Text","","+90 ↻")
    aPDFGui.txtObj.Plus90.Anchor := aPDFGui.txtObj.PageRange
    aPDFGui.txtObj.Plus90.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.txtObj.Plus90, xp := 0, yp := 1.20)

    aPDFGui.editObj.Plus90PageRange := aPDFGui.Add("Edit", "vPlus90PageRange")
    aPDFGui.editObj.Plus90PageRange.Anchor := aPDFGui.txtObj.Plus90
    aPDFGui.editObj.Plus90PageRange.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.editObj.Plus90PageRange, xp := 1.01, yp := 0)

    aPDFGui.txtObj.Minus90 := aPDFGui.Add("Text","","-90 ↺")
    aPDFGui.txtObj.Minus90.Anchor := aPDFGui.editObj.Plus90PageRange
    aPDFGui.txtObj.Minus90.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.txtObj.Minus90, xp := 1, yp := 0)

    aPDFGui.editObj.Minus90PageRange := aPDFGui.Add("Edit", "vMinus90PageRange")
    aPDFGui.editObj.Minus90PageRange.Anchor := aPDFGui.txtObj.Minus90
    aPDFGui.editObj.Minus90PageRange.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.editObj.Minus90PageRange, xp := 1.01, yp := 0)

    aPDFGui.txtObj.180 := aPDFGui.Add("Text","","180")
    aPDFGui.txtObj.180.Anchor := aPDFGui.editObj.Minus90PageRange
    aPDFGui.txtObj.180.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.txtObj.180, xp := 1.01, yp := 0)

    aPDFGui.editObj.180PageRange := aPDFGui.Add("Edit", "v180PageRange")
    aPDFGui.editObj.180PageRange.Anchor := aPDFGui.txtObj.180
    aPDFGui.editObj.180PageRange.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.editObj.180PageRange, xp := 1.01, yp := 0)

    aPDFGui.btnObj.Rotate := aPDFGui.Add("Button", "vRotateButton", "Rotate")
    aPDFGui.btnObj.Rotate.OnEvent("Click", RotateButton_Clicked)
    aPDFGui.btnObj.Rotate.Anchor := aPDFGui.editObj.180PageRange
    aPDFGui.btnObj.Rotate.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.btnObj.Rotate, xp := 1.01, yp := 0)

    aPDFGui.txtObj.Extraction := aPDFGui.Add("Text","","Extraction:")
    aPDFGui.txtObj.Extraction.Anchor := aPDFGui.txtObj.Plus90
    aPDFGui.txtObj.Extraction.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.txtObj.Extraction, xp := 0, yp := 2.5)

    aPDFGui.txtObj.PageRange2 := aPDFGui.Add("Text","","Page Range (e.g. 1-4,8,10-12,15-z)")
    aPDFGui.txtObj.PageRange2.Anchor := aPDFGui.txtObj.Extraction
    aPDFGui.txtObj.PageRange2.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.txtObj.PageRange2, xp := 0 , yp := 1.20)

    aPDFGui.txtObj.OutputFileName := aPDFGui.Add("Text","","Output Filename (without extension) (*Change Required)")
    aPDFGui.txtObj.OutputFileName.Anchor := aPDFGui.txtObj.PageRange2
    aPDFGui.txtObj.OutputFileName.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.txtObj.OutputFileName, xp := 1.10 , yp := 0)

    aPDFGui.editObj.ExtractionPageRange := aPDFGui.Add("Edit", "vExtractionPageRange")
    aPDFGui.editObj.ExtractionPageRange.OnEvent("Change", Edit_Change)
    aPDFGui.editObj.ExtractionPageRange.Anchor := aPDFGui.txtObj.PageRange2
    aPDFGui.editObj.ExtractionPageRange.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.editObj.ExtractionPageRange, xp := 0, yp := 1.20, wp := 1)

    aPDFGui.editObj.OutputFileName := aPDFGui.Add("Edit", "vOutputFileName")
    aPDFGui.editObj.OutputFileName.OnEvent("Change", Edit_Change)
    aPDFGui.editObj.OutputFileName.Anchor := aPDFGui.txtObj.OutputFileName
    aPDFGui.editObj.OutputFileName.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.editObj.OutputFileName, xp := 0, yp := 1.20, wp := 1)

    aPDFGui.btnObj.Split := aPDFGui.Add("Button", "vSplitButton", "Split")
    aPDFGui.btnObj.Split.OnEvent("Click", SplitButton_Clicked)
    aPDFGui.btnObj.Split.Anchor := aPDFGui.editObj.OutputFileName
    aPDFGui.btnObj.Split.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.btnObj.Split, xp := 1.01, yp := 0)
    aPDFGui.btnObj.Split.Visible := false

    aPDFGui.cbObj.ReduceAndOptimize := aPDFGui.Add("CheckBox", "vReduceAndOptimize", "Reduce and Optimize Output File")
    aPDFGui.cbObj.ReduceAndOptimize.OnEvent("Click", AutoCompletePageRange)
    aPDFGui.cbObj.ReduceAndOptimize.Anchor := aPDFGui.editObj.ExtractionPageRange
    aPDFGui.cbObj.ReduceAndOptimize.AnchorIn := false
    GuiResizer.FormatOpt(aPDFGui.cbObj.ReduceAndOptimize, xp := 0, yp := 1.20)

    aPDFGui.cbObj.AutoRotate := aPDFGui.Add("CheckBox", "vAutoRotate", "Auto Rotate")
    aPDFGui.cbObj.AutoRotate.OnEvent("Click", AutoCompletePageRange)
    aPDFGui.cbObj.AutoRotate.Anchor := aPDFGui.cbObj.ReduceAndOptimize
    aPDFGui.cbObj.AutoRotate.AnchorIn := false
    GuiResizer.FormatOpt(aPDFGui.cbObj.AutoRotate, xp := 1.01, yp := 0)

    aPDFGui.ProgressObj.Progress := aPDFGui.Add("Progress", "w158 cNavy Background06ce39 vProgress", 0) ; Navy and Green
    aPDFGui.ProgressObj.Progress.Anchor := aPDFGui.cbObj.AutoRotate
    aPDFGui.ProgressObj.Progress.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.ProgressObj.Progress, xp := 1.01, yp := 0.1, , hp := 0.6)

    aPDFGui.ProgressObj.ProgressDocs := aPDFGui.Add("Progress", "w158 cNavy Background06ce39 vProgressDocs", 0) ; Navy and Green
    aPDFGui.ProgressObj.ProgressDocs.Anchor := aPDFGui.ProgressObj.Progress
    aPDFGui.ProgressObj.ProgressDocs.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.ProgressObj.ProgressDocs, xp := 0, yp := 1, , hp := 1)

    aPDFGui.txtObj.Stats := aPDFGui.Add("Text","w64","")
    aPDFGui.txtObj.Stats.Anchor := aPDFGui.ProgressObj.Progress
    aPDFGui.txtObj.Stats.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.txtObj.Stats, xp := 1.02, yp := 0, , )

    aPDFGui.txtObj.TimeElapsed := aPDFGui.Add("Text","w264","")
    aPDFGui.txtObj.TimeElapsed.Anchor := aPDFGui.editObj.Minus90PageRange
    aPDFGui.txtObj.TimeElapsed.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.txtObj.TimeElapsed, xp := 0.05, yp := 1.60, , )

    aPDFGui.SetFont(,"Lucida Sans Unicode")

    aPDFGui.editObj.StdErr := aPDFGui.Add("Edit", "h67 w486 ReadOnly vStdErr")
    aPDFGui.editObj.StdErr.Anchor := aPDFGui.cbObj.ReduceAndOptimize
    aPDFGui.editObj.StdErr.AnchorIn := false
    GuiReSizer.FormatOpt(aPDFGui.editObj.StdErr, xp := 0, yp := 1.60)


    aPDFGui.Show("H250 W500")
}

AutoCompletePageRange(ctrlObj, info){
    aPDFGui := ctrlObj.Gui
    SelectedFile := aPDFGui.editObj.SelectedFile.Value
    SplitPath SelectedFile, &name, &dir, &ext, &name_no_ext, &drive
    if ((aPDFGui.cbObj.AutoRotate.Value) or (aPDFGui.cbObj.ReduceAndOptimize.Value)){
        if (aPDFGui.editObj.ExtractionPageRange.Value = "")
            aPDFGui.editObj.ExtractionPageRange.Value := "1-z"

        if ((aPDFGui.editObj.OutputFileName.Value = name_no_ext))
            aPDFGui.editObj.OutputFileName.Value := "out-" name_no_ext
    } else {
        if (aPDFGui.editObj.ExtractionPageRange.Value = "1-z")
            aPDFGui.editObj.ExtractionPageRange.Value := ""

        if (aPDFGui.editObj.OutputFileName.Value = "out-" name_no_ext)
            aPDFGui.editObj.OutputFileName.Value := name_no_ext
    }
    Edit_Change(ctrlObj, info)
}

aPDFGui_DropFiles(aPDFGui, ctrlObj, FileArray, x, y){
    if (Files.Has(1)){                                  ; clearing any existing data from Files array.
        Files.RemoveAt(1,Files.Length)
    }

    for i, DroppedFile in FileArray {                   ; reconstructing files array.
        Files.push DroppedFile
    }

    aPDFGui.editObj.SelectedFile.Value := Files[1]
    SplitPath aPDFGui.editObj.SelectedFile.Value, &name, &dir, &ext, &name_no_ext, &drive

    if (ext != "pdf"){
        MsgBox "Files must be PDFs",,"0x1000"
        return
    }       
    
    aPDFGui.editObj.OutputFileName.Value := name_no_ext
    aPDFGui.btnObj.Split.Visible := false

    AutoCompletePageRange(aPDFGui.editObj.ExtractionPageRange, info := "")    ; the actual ctrlObj used here doesn't matter - so i just picked one
}

Edit_Change(ctrlObj, info){
    aPDFGui := ctrlObj.Gui
    SelectedFile := aPDFGui.editObj.SelectedFile.Value
    SplitPath SelectedFile, &name, &dir, &ext, &name_no_ext, &drive
    if ((aPDFGui.editObj.OutputFileName.Value = name_no_ext) or (aPDFGui.editObj.ExtractionPageRange.Value = "")){
        aPDFGui.btnObj.Split.Visible := false
    }

    if (((aPDFGui.editObj.OutputFileName.Value != name_no_ext) or (Files.Length > 1)) and (aPDFGui.editObj.ExtractionPageRange.Value != "")){
        aPDFGui.btnObj.Split.Visible := true
    }
}

SelectFileButton_Clicked(ctrlObj, info){                                    ; need to update to allow for selecting more than 1 file
    aPDFGui := ctrlObj.Gui
    aPDFGui.editObj.SelectedFile.Value := FileSelect(1,,"Select PDF","PDF Files (*.pdf)")
    SelectedFile := aPDFGui.editObj.SelectedFile.Value
    SplitPath SelectedFile, &name, &dir, &ext, &name_no_ext, &drive
    aPDFGui.editObj.OutputFileName.Value := name_no_ext
}

RotateButton_Clicked(ctrlObj, info){
    aPDFGui := ctrlObj.Gui

    aPDFGui.txtObj.TimeElapsed.Visible := false
    aPDFGui.StartTime := A_TickCount

    Command := ""
    
    Loop Files.Length {
        aPDFGui.editObj.SelectedFile.Value := Files[A_Index]
        SelectedFile := aPDFGui.editObj.SelectedFile.Value
        Plus90PageRange := aPDFGui.editObj.Plus90PageRange.Value
        Minus90PageRange := aPDFGui.editObj.Minus90PageRange.Value
        PageRange180 := aPDFGui.editObj.180PageRange.Value
        aPDFGui.btnObj.Rotate.Visible := false

        Try FileDelete TempPdfFilePath

        if (SelectedFile = "") {
            ctrlObj.Visible := true
            return
        }

        SplitPath SelectedFile, &name, &dir, &ext, &name_no_ext, &drive
        aPDFGui.editObj.OutputFileName.Value := name_no_ext

        if ((Plus90PageRange = "") and (Minus90PageRange = "") and (PageRange180 = "")) {
            aPDFGui.btnObj.Rotate.Visible := true
            return
        }

        if (Plus90PageRange != "") {
            Command .= "qpdf `"" SelectedFile "`" --replace-input --rotate=+90:" Plus90PageRange
            Command .= "`n"
        }

        if (Minus90PageRange != "") {
            Command .= "qpdf `"" SelectedFile "`" --replace-input --rotate=-90:" Minus90PageRange
            Command .= "`n"
        }

        if (PageRange180 != "") {
            Command .= "qpdf `"" SelectedFile "`" --replace-input --rotate=+180:" PageRange180
            Command .= "`n"
        }

        if (aPDFGui.cbObj.ReduceAndOptimize.Value = 1){
            Command .= "gswin64c -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/ebook -dWriteXRefStm=false -dWriteObjStms=false -dDetectDuplicateImages=true -dFastWebView=true -dNOPAUSE -dQUIET -dBATCH -o `"" TempPdfFilePath "`" `"" SelectedFile "`" 2>NUL" 
            Command .= " && move `"" TempPdfFilePath "`" `"" SelectedFile "`""
            Command .= "`n"
        }

    }

    RunWaitOne(aPDFGui, Command)

    aPDFGui.editObj.SelectedFile.Value := Files[1]
    SelectedFile := aPDFGui.editObj.SelectedFile.Value
    SplitPath SelectedFile, &name, &dir, &ext, &name_no_ext, &drive
    
    If (Files.Length > 1){
        aPDFGui.editObj.OutputFileName.Value := name_no_ext
    }

    ctrlObj.Visible := true
    ctrlObj.Focus()
    
}

UpdateTimer(aPDFGui, StartTime){

    SecondsElapsed := Round(((A_TickCount - StartTime)/1000))

    DisplayMinutes := Floor(SecondsElapsed / 60)
    DisplaySeconds := Round(SecondsElapsed - (DisplayMinutes * 60))
    if (DisplayMinutes > 0){
        DisplayTimeElapsed := "Time Elapsed: " DisplayMinutes "m " DisplaySeconds "s"
    } else {
        DisplayTimeElapsed := "Time Elapsed: " DisplaySeconds "s"
    }
    aPDFGui.txtObj.TimeElapsed.Value := DisplayTimeElapsed

}

SplitButton_Clicked(ctrlObj, info){
    aPDFGui := ctrlObj.Gui

    aPDFGui.txtObj.TimeElapsed.Visible := false
    aPDFGui.StartTime := A_TickCount

    aPDFGui.ProgressObj.ProgressDocs.Opt("Range0-" Files.Length)
    aPDFGui.ProgressObj.ProgressDocs.Value := 0

    Pages := 0
    Command := ""

    Loop Files.Length {
        aPDFGui.editObj.SelectedFile.Value := Files[A_Index]
        SelectedFile := aPDFGui.editObj.SelectedFile.Value
        SplitPath SelectedFile, &name, &dir, &ext, &name_no_ext, &drive
        ExtractionPageRange := aPDFGui.editObj.ExtractionPageRange.Value
        if ((Files.Length > 1) or InStr(aPDFGui.editObj.OutputFileName.Value, "out-") > 0){
            aPDFGui.editObj.OutputFileName.Value := "out-" name_no_ext
        }
        TempOutputFileName := TempPath aPDFGui.editObj.OutputFileName.Value ".pdf"
        OutputFileName := aPDFGui.editObj.OutputFileName.Value
        aPDFGui.btnObj.Split.Visible := false

        Try FileDelete TempPdfFilePath

        if (SelectedFile = "") {
            ctrlObj.Visible := true
            return
        }

        if (ExtractionPageRange = ""){
            MsgBox "Extraction Page Range cannot be blank"
            return
        }

        if (OutputFileName = ""){
            MsgBox "Output File Name cannot be blank"
            return
        }

        Command .= "qpdf `"" SelectedFile "`" --decrypt --remove-restrictions --linearize --optimize-images --pages --file=. --range=" ExtractionPageRange " -- `"" TempOutputFileName "`""
        Command .= "`n"

        Command .= "(echo|set /p dummyName=Pages:>&2 && qpdf --show-npages `"" TempOutputFileName "`" >&2)"
        Command .= "`n"

        if (aPDFGui.cbObj.ReduceAndOptimize.Value = 1){
            Command .= "gswin64c -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/ebook -dWriteXRefStm=false -dWriteObjStms=false -dDetectDuplicateImages=true -dFastWebView=true -dNOPAUSE -dQUIET -dBATCH -o `"" TempPdfFilePath "`" `"" TempOutputFileName "`" 2>NUL" 
            Command .= " && move `"" TempPdfFilePath "`" `"" TempOutputFileName "`""
            Command .= "`n"
        }

        if (aPDFGui.cbObj.AutoRotate.Value = 1){
            ; was set to --optimize=1 but found that it completely mangled one of my documents.
            
            ; If you dont have unpaper then keep this line.
            Command .= "ocrmypdf --output-type=pdf --rotate-pages --rotate-pages-threshold=.1 --tesseract-timeout=0 --skip-text --optimize=0 --oversample=300 `"" TempOutputFileName "`" `"" TempOutputFileName "`""
            
            ; If you have unpaper then comment or remove line above this one and uncomment the line below this one for better autorotate accuracy.
            ;Command .= "ocrmypdf --output-type=pdf --rotate-pages --rotate-pages-threshold=.1 --clean --tesseract-timeout=0 --skip-text --optimize=0 --oversample=300 `"" TempOutputFileName "`" `"" TempOutputFileName "`"" 
            Command .= "`n"
        }

        Command .= "move `"" TempOutputFileName "`" `"" dir "\" OutputFileName "." ext "`""
        Command .= " && (echo|set/p dummyName=EndDoc>&2) && (echo:>&2)"
        Command .= "`n"
    }

    RunWaitOne(aPDFGui, Command)

    aPDFGui.editObj.SelectedFile.Value := Files[1]
    SelectedFile := aPDFGui.editObj.SelectedFile.Value
    SplitPath SelectedFile, &name, &dir, &ext, &name_no_ext, &drive

    If (Files.Length > 1){
        aPDFGui.editObj.OutputFileName.Value := name_no_ext
    }
    
    ctrlObj.Visible := true
    ctrlObj.Focus()

}

RunWaitOne(aPDFGui, command) {

    Try FileDelete TempPdfBat

    FileAppend
    (
        Command
    ), TempPdfBat

    ; WshShell object: http://msdn.microsoft.com/en-us/library/aew9yb99 ¬
    shell := ComObject("WScript.Shell")
    ; Execute a single command via cmd.exe
    exec := shell.Exec(A_ComSpec " /Q /K echo off")
    exec.StdIn.WriteLine("`"" TempPdfBat "`"" "`nexit")  ; Always exit at the end!
    aPDFGui.editObj.StdErr.Value := ""
    line := ""
    Pages := 0
    ArrStdErr := {}
    aPDFGui.ProgressObj.Progress.Value := 0
    While (!exec.StdErr.AtEndOfStream)
    {
        line := trim(exec.StdErr.Readline())
        replacedline := StrReplace(StrReplace(StrReplace(StrReplace(StrReplace(StrReplace(StrReplace(StrReplace(line,"\u21e6","⇦"),"\u21e7","⇧"),"\u21e8","⇨"),"\u21e9","⇩"),"\u21ba", "↺"), "\u21bb", "↻"),"\u2b11", "⬑"), "\u2b0f", "⬏") ; i couldn't figure out any other way to get the unicode symbols to display properly.
        aPDFGui.editObj.StdErr.Value .= replacedline
        aPDFGui.editObj.StdErr.Value .= "`n"
        SendMessage(0x115,7,0,aPDFGui.editObj.StdErr,aPDFGui)
        ;0x115 is WM_VSCROLL
        ;7 is SB_BOTTOM
        ArrStdErr := StrSplit(replacedline, " ")
        Try {
            check := (IsNumber(ArrStdErr[1]) and (ArrStdErr[2] != "Weight")) or (ArrStdErr[1] = "EndDoc")
            docCheck := (ArrStdErr[1] = "EndDoc")
        } catch {
            check := false
            docCheck := false
            
        }

        try {
            if (ArrStdErr[1] = "Pages:"){
                Pages := ArrStdErr[2] + 1       ; added 1 to the page counter to account for the file move operation at the end - sometimes this can take a while and so the idea is that the loading bar will show complete only when the output file is available.
                aPDFGui.ProgressObj.Progress.Opt("Range0-" Pages)
                aPDFGui.ProgressObj.Progress.Value := 0
                aPDFGui.txtObj.Stats.Value := aPDFGui.ProgressObj.Progress.Value "/" Pages ", " aPDFGui.ProgressObj.ProgressDocs.Value "/" Files.Length
            }
        }   

        If (check){
            aPDFGui.ProgressObj.Progress.Value += 1
            aPDFGui.txtObj.Stats.Value := aPDFGui.ProgressObj.Progress.Value "/" Pages ", " aPDFGui.ProgressObj.ProgressDocs.Value "/" Files.Length
        }

        If (docCheck){
            aPDFGui.ProgressObj.Progress.Value := Pages
            aPDFGui.ProgressObj.ProgressDocs.Value += 1
            aPDFGui.txtObj.Stats.Value := aPDFGui.ProgressObj.Progress.Value "/" Pages ", " aPDFGui.ProgressObj.ProgressDocs.Value "/" Files.Length
        }

        UpdateTimer(aPDFGui, aPDFGui.StartTime)
        aPDFGui.txtObj.TimeElapsed.Visible := true
    }

    aPDFGui.Flash

    Try FileDelete TempCmdOut

    FileAppend
    (
        "STDERR: " aPDFGui.editObj.StdErr.Value 
        "CMD: " Command
    ), TempCmdOut

    return exec.StdOut.ReadAll()
}

aPDFGui_Escape(aPDFGui){
    aPDFGui.Destroy()
    aPDFGui := unset
}
Here are screenshots of how it looks.
Screenshot.png
Screenshot.png (14.51 KiB) Viewed 403 times
This was after running 3 files - total of 21.7 MB, the output of the 3 files was 2.88 MB and only took 37 seconds to reduce and autorotate them.
Screenshot.png
Screenshot.png (14.51 KiB) Viewed 403 times


Thanks for reading
-TheOriginalAbe

Code: Select all

Changelog
v1.0.13 - 05/15/2024 - if only changing one file then don't reset output file name - also clear the stderr edit after each user initiated operation.
v1.0.12 - 05/13/2024 - updated to fix pages progress bar not updating correctly when reducing and optimizing multiple files.
v1.0.11 - updated to change the timer implementation from a settimer to instead be a function call after every readline from StdErr. Also added a taskbar flash when job is complete.
Attachments
PDFUtility.ahk
(19.77 KiB) Downloaded 1 time
Screenshot2.png
Screenshot2.png (20.67 KiB) Viewed 403 times
Last edited by TheOriginalAbe on 15 May 2024, 10:50, edited 3 times in total.
User avatar
joedf
Posts: 8982
Joined: 29 Sep 2013, 17:08
Location: Canada
Contact:

Re: PDF Utility - (Rotate, Extract, Autorotate, Reduce and Optimize - Batch Processing)

10 May 2024, 09:13

I'm always glad to see another free and open-source PDF utility. Too many "free" ones that become paid-ware or end up with "premium" versions...
Nice work :thumbup:
Image Image Image Image Image
Windows 10 x64 Professional, Intel i5-8500, NVIDIA GTX 1060 6GB, 2x16GB Kingston FURY Beast - DDR4 3200 MHz | [About Me] | [About the AHK Foundation] | [Courses on AutoHotkey]
[ASPDM - StdLib Distribution] | [Qonsole - Quake-like console emulator] | [LibCon - Autohotkey Console Library]

Return to “Scripts and Functions (v2)”

Who is online

Users browsing this forum: No registered users and 14 guests