-1

I have a script that, when I right click on a folder, combines all pngs/jpgs/tifs inside the folder into a PDF and renames the PDF to the name of the folder it resides in.

cd %~dpnx1
for %%a in (.) do set currentfolder=%%~na
start cmd /k magick "*.{png,jpg,tif}" "%currentfolder%.pdf"

However, I have quite a lot of folders and currently have to do this one by one. How can I create a function where I can right click on a folder, which searches subfolders and combines the jpgs to PDF? So in the example below, Im wanting to create 3 PDFS (Folder A, Folder B and Folder C) by right clicking and running batch on the parent folder.

Example:

  • Parent folder (one that I would right click and run script from)
  • |- Folder A
  • ||- test1.jpg
  • ||- test2.jpg
  • ||- test3.jpg
  • |- Folder B
  • ||- example1.jpg
  • || - example2.jpg
  • |- Folder C
  • || Folder D
  • |||- temp.jpg
  • |||- temp2.jpg

I have also recently moved to Mac so I'm looking to use zsh. I've had some help to attempt to use the following myself but no luck:

   #!/bin/bash

# Set the output directory
output_dir='./pdfs/'

# Make the output directory if it doesn't exist
mkdir -p "$output_dir"

# Check if an input directory was provided as a command-line argument
if [ $# -eq 0 ]
then
  # Use the current directory as the input directory if none was provided
  input_dir='./'
else
  # Use the first command-line argument as the input directory
  input_dir="$1"
fi

# Find all the directories in the input directory
find "$input_dir" -type d | while read dir; do
  # Extract the base directory name
  dirname=$(basename "$dir")
  # Create a PDF file with the same name as the base directory name
  output_file="$output_dir/$dirname.pdf"
  # Find all the JPEG files in the current directory
  find "$dir" -type f -name '*.jpg' | while read file; do
    # Convert the JPEG file to PDF and append it to the output file
    convert "$file" "$file.pdf"
  done
  # Concatenate all the PDF files in the current directory into a single PDF
  gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile="$output_file" "$dir"/*.pdf
  # Remove the temporary PDF files
  rm "$dir"/*.pdf
done

Hope you can help. Thank you.

user747372
  • 29
  • 5

2 Answers2

2

There are several aspects to this question, and judging by your attempted solution, they will all be non-trivial for you. It is more than a normal question so I'll just give you an outline so you can tackle it in chunks. You'll need to:

  • install homebrew
  • install ImageMagick
  • use Automator to make a workflow for right-click
  • learn some bash scripting to recurse through directories
  • learn some ImageMagick to make PDFs

Install homebrew

Go to here and follow instructions to install homebrew. I am not repeating the instructions here as they may change.

You'll likely need to install Xcode command-line tools with:

xcode-select --install

You'll need to set your PATH properly afterwards. Don't omit this step.


Install ImageMagick

You'll need to do:

brew install imagemagick

Setup workflow with Automator for right-click

Next you need to make a script that will be executed when you right-click on a directory. It will look like this when we have done it. I right-clicked on the Junk directory on my desktop and went down to Quick Actions and across to makePDFs.

enter image description here

So, in order to do that you need to start the Automator by clicking SPACE and typing Automator and hitting ENTER when it guesses.

Then select New Document and Quick Action. Now navigate the orange areas in the diagram till you find Run Shell Script then drag Run Shell Script over to the right side and drop it in the blue zone. Go on the Edit menu and click and then Save As and enter makePDFs in the box. This is the name that will appear in future on your right-click menu.

enter image description here

Now set the options in the green box like I have done.

Now replace all the code in the blue box with the code copied from below:

#!/bin/bash
################################################################################
# Recurse into all subdirectories specified in parameter and make PDF in each
# directory of all images found in there.
################################################################################
# Add ImageMagick from homebrew to PATH
PATH=$PATH:/opt/homebrew/bin

# Check we got a directory as parameter
if [ $# -ne 1 ] ; then
   >&2 echo "Usage: $0 DIRECTORY"
   exit 1
fi

# Find and process all subdirectories
shopt -s nullglob
while read -rd $'\0' dir; do
   # Start a subshell so we don't have to cd back somewhere
   ( 
      cd "$dir" || exit 1
      # Make list of all images in directory
      declare -a images
      for image in *.jpg *.png *.tif ; do
         images+=("$image")
      done
      numImages=${#images[@]}
      if [ $numImages -gt 0 ] ; then
         pdfname=${PWD##*/}  
         magick "${images[@]}" "${pdfname}.pdf"
      fi
   )
done < <(find "$1" -type d -print0)

Finally, set the options like I did in the cyan coloured box.

enter image description here

Now save the whole workflow again and everything should work nicely.

Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • I got `"The action “Run Shell Script” encountered an error: “-: line 28: magick: command not found”` – user747372 Jan 09 '23 at 06:17
  • Did you install **ImageMagick**? What happens if you run the following command in your Terminal `type magick` ? – Mark Setchell Jan 09 '23 at 07:59
  • `magick is /usr/local/bin/magick` – user747372 Jan 09 '23 at 08:58
  • So that means you'll need a line like this near the top of your script `PATH=$PATH:/usr/local/bin` Then, later on, you can use `magick ...` without its full path. – Mark Setchell Jan 09 '23 at 09:06
  • Thanks a lot! It seems to work now, but I've noticed it got the order incorrect. My files in this case are called `2011 Preview (1).jpg` through to `2011 Preview (55).jpg` – It started off at (1) correctly but the last four pages are using images (8) (9) (10) and (11). Do you have any idea why that might be? And any idea how to fix it? Thank you – user747372 Jan 09 '23 at 09:15
  • It's just the order the shell prints things in. The simplest is normally to zero-pad the numbers all to the same width (e.g. 2 digits here) so they sort correctly, i.e. `2011 Preview (01).jpg` and `2011 Preview (02).jpg` and so on through `2011 Preview(55).jpg` – Mark Setchell Jan 09 '23 at 09:22
  • Now I get: `The action “Run Shell Script” encountered an error: “-: -c: line 33: unexpected EOF while looking for matching )' -: -c: line 34: syntax error: unexpected end of file”` – user747372 Jan 09 '23 at 10:39
  • It's hard to debug a shell script from Automator, so copy all the code (in blue box) and save it in a file called `go.sh` in your home directory. Make that file executable with `chmod +x ~/go.sh` Now we can run it simply from the Terminal and make it process all files in a directory called `Fred` on your Desktop with `~/go.sh ~/Desktop/Fred` So can you see if you can get that to work? Also, put the actual script you are running into your question by clicking [edit] so I can see it. – Mark Setchell Jan 09 '23 at 11:32
  • Note that if you make `go.sh` with **TextEdit**, you must press `SHIFT+COMMAND+T` to make it **plain text**. You can do the same thing on **TextEdit** `Format` menu. – Mark Setchell Jan 09 '23 at 11:48
1

For bash, try this (tested on Linux):

for d in */; do convert "$d"/*.{png,jpg,tif} "$d/${d%/}.pdf" ; done

In slo-mo:

  • for d in */: loop on all directories in current directory (the / restrict matches to directories)). Variable $d contains the directory name, and name has a final /.
  • "$d"/*.{png,jpg,tif}: all files with png, jpg or tif extension in the directory "$d"
  • "$d/${d%/}.pdf": the directory name, a slash, the directory name with the ending slash removed, and .pdf`

If you look carefully, the explicit /s in the code aren't necessary since there is already one at the end of $d, but leaving them in makes the code a bit more readable and the multiple '//' are coalesced into a single one.

This code may however complain that there are no png/jpg/tif. A slightly different form makes it behave more nicely:

shopt -s extglob # this is possibly already set by default
for d in */; do convert "$d"/*.@(png|jpg|tif) "$d/${d%/}".pdf ; done

for zsh this could be (untested!):

# shopt -s extglob # no shopt necessary
for d in */; do convert "$d"/*.png(N)  "$d"/*.jpg(N) "$d"/*.tif(N) "$d/${d%/}".pdf ; done

with the caveat that if no pattern matches, the command will just be convert whatever_output.pdf and you will get the built-in help.

The difference is that *.{png,jpg,tif} is expanded to *.png *.jpg *.tif before any pattern matching is done, so this represents three file patterns and the shell tries to match each pattern in turn (and leaves the literal pattern in case there is no match), while *.@(png|jpg|tif) is a single file pattern that matches any of the three extensions. This can also make a difference for you because the files do not appear in the same order, *.{png,jpg,tif} lists all the PNG, then the JPG,then the TIF, while *.@(png|jpg|tif) has them all sorted in alphabetical order without regard for the extension.

xenoid
  • 8,396
  • 3
  • 23
  • 49
  • I got `zsh: command not found: shopt zsh: no matches found: Folder Name//*.@(png|jpg|tif)` – user747372 Jan 09 '23 at 06:19
  • It means that your shell interpreter is `zsh` and not `bash`, so instead replace the pattern by `*.png(N) *.jpg(N) *.tif(N)` (and remove the shopt command). – xenoid Jan 09 '23 at 08:15
  • It is also possible that you could call `bash`, even if the default shell is `zsh`, so putting the code in a `the_script` file and calling `bash the_script`. – xenoid Jan 09 '23 at 08:17
  • So like this? `shopt -s extglob # this is possibly already set by default for d in */; do convert "$d"/*.@(*.png(N) *.jpg(N) *.tif(N)) "$d/${d%/}".pdf ; done` – user747372 Jan 09 '23 at 09:01
  • See augmented answer – xenoid Jan 09 '23 at 09:43