Process Folder

Script for Adobe Photoshop

This code is my template for whipping up quick scripts to attack the challenge of the day. It's open source so anyone is free to alter it however suits your needs. The script is the bare essentials to select a folder and do something to the files in it. No options for what is done, except the choice to include subfolders. I simply plug in the code I want run on each file, and get it done. Here I'm sharing it so you can do the same. Sometimes we find a nice snippet of code to solve a problem, but we want it done to more than one image. Replace the code between the markers in the 'processFile' function, and run the script on a folder of images. Or build on the template to make your own specialized solution.

  • Run script code on a folder of images
  • Option to include files in subfolders
  • Adapt open source to customize or create other scripts
Download
Process Folder
Help me keep making new scripts by supporting my work. Click the PayPal button to contribute any amount you choose. Thank you. William Campbell

How-to Video

NOTE: video is a programming tutorial that demonstrates how to use Adobe Photoshop Scripting Listener. During the tutorial, this script, Process Folder, is featured to show a practical use for code generated by Scripting Listener. The code used in this script may be from a trusted online source, or use Scripting Listener to record your own steps in Photoshop, to create a customized solution.

How to use the script

The interface is minimal. A button to select the folder, and the option to include subfolders. Click the OK button to begin. While processing, press ESC to cancel.

Example task

In the source code (below), between the markers I've added an example task. This adds a Levels adjustment layer, and sets it to 50-1.5-200. The unrealistic values can be changed to anything more meaningful. I just wanted something for demonstration purposes that clearly shows the images have been altered.

You don't have to be a programmer to customize this script. Edit the file in any text editor, and it's clear to see the begin and end markers, between which add code to run on each file. We often find snippets of code on the Adobe forum when searching for a solution to some challenge. Copy and paste and give it a go. Make sure to have backup copies of the images, just in case. I can't guarantee what code you might plug into the script, and what it might do to your images. Always make backups first!

Or use the script as a template, as I do. Copy the file to a new name, add the desired code to run on each file, and it's your own specialized solution. Near the top of the script is the variable "title." That is what shows in the title bar of the window and alerts. Change it to the name of your choice.

I've added many comments to the code to help make things clearer. Read through to get a feel for what's going on inside the script. Contact me any time for help, or ideas for new scripts.

Source code

(download button below)

/*

Process Folder
Copyright 2023 William Campbell
All Rights Reserved
https://www.marspremedia.com/contact

Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

*/

(function () {

    var title = "Process Folder";

    if (!/photoshop/i.test(app.name)) {
        alert("Script for Photoshop", title, false);
        return;
    }

    app.displayDialogs = DialogModes.ERROR;

    // Script variables.
    var count;
    var doneMessage;
    var error;
    var folderInput;
    var progress;
    var timeBegin;
    var timeEnd;

    // Reusable UI variables.
    var g; // group
    var p; // panel
    var w; // window

    // Permanent UI variables.
    var btnCancel;
    var btnFolderInput;
    var btnOk;
    var cbIncludeSubfolders;
    var txtFolderInput;

    // SETUP

    // CREATE PROGRESS WINDOW

    progress = new Window("palette", "Progress");
    progress.t = progress.add("statictext");
    progress.t.preferredSize.width = 450;
    progress.b = progress.add("progressbar");
    progress.b.preferredSize.width = 450;
    progress.add("statictext", undefined, "Press ESC to cancel");
    progress.display = function (message) {
        message && (this.t.text = message);
        this.show();
        app.refresh();
    };
    progress.increment = function () {
        this.b.value++;
    };
    progress.set = function (steps) {
        this.b.value = 0;
        this.b.minvalue = 0;
        this.b.maxvalue = steps;
    };

    // CREATE USER INTERFACE

    w = new Window("dialog", title);
    w.alignChildren = "fill";
    p = w.add("panel");
    p.margins = [18, 18, 18, 18];
    p.alignChildren = "left";
    g = p.add("group");
    btnFolderInput = g.add("button", undefined, "Folder...");
    txtFolderInput = g.add("statictext", undefined, "", {
        truncate: "middle"
    });
    txtFolderInput.preferredSize.width = 350;
    cbIncludeSubfolders = p.add("checkbox", undefined, "Include subfolders");

    // Action Buttons
    g = w.add("group");
    g.alignment = "center";
    btnOk = g.add("button", undefined, "OK");
    btnCancel = g.add("button", undefined, "Cancel");

    // Panel Copyright
    p = w.add("panel");
    p.add("statictext", undefined, "Copyright 2023 William Campbell");

    // UI ELEMENT EVENT HANDLERS

    btnFolderInput.onClick = function () {
        var f = Folder.selectDialog("Select input folder", txtFolderInput.text);
        if (f) {
            txtFolderInput.text = Folder.decode(f.fullName);
        }
    };
    btnOk.onClick = function () {
        folderInput = new Folder(txtFolderInput.text);
        if (!(folderInput && folderInput.exists)) {
            alert("Select input folder", " ", false);
            return;
        }
        w.close(1);
    };
    btnCancel.onClick = function () {
        w.close(0);
    };

    // DISPLAY THE DIALOG

    if (w.show() == 1) {
        doneMessage = "";
        try {
            timeBegin = new Date();
            process();
            timeEnd = new Date();
            doneMessage = doneMessage || count + " files processed in " + ((timeEnd - timeBegin) / 1000) + " seconds";
        } catch (e) {
            if (/User cancel/.test(e.message)) {
                doneMessage = "Canceled";
            } else {
                error = error || e;
                doneMessage = "An error has occurred.\nLine " + error.line + ": " + error.message;
            }
        }
        app.bringToFront();
        progress.close();
        doneMessage && alert(doneMessage, title, error);
    }

    //====================================================================
    //               END PROGRAM EXECUTION, BEGIN FUNCTIONS
    //====================================================================

    function getFiles(folder, subfolders, extensions) {
        // folder = folder object, not folder name.
        // subfolders = true to include subfolders.
        // extensions = string, extensions to include.
        // Combine multiple extensions with RegExp OR i.e. jpg|psd|tif
        // extensions case-insensitive.
        // extensions undefined = any.
        // Ignores hidden files and folders.
        var f;
        var files;
        var i;
        var pattern;
        var results = [];
        if (extensions) {
            pattern = new RegExp("\." + extensions + "$", "i");
        } else {
            // Any extension.
            pattern = new RegExp(
                "\.8PBS|AFX|AI|ARW|BLZ|BMP|CAL|CALS|CIN|CR2|CRW|CT|DCM|DCR|DCS|DCX|DDS|" +
                "DIB|DIC|DNG|DPX|EPS|EPSF|EXR|FFF|FIF|GIF|HDP|HDR|HEIC|HEIF|ICB|ICN|ICO|" +
                "ICON|IIQ|IMG|J2C|J2K|JIF|JIFF|JP2|JPC|JPE|JPEG|JPF|JPG|JPS|JPX|JXR|KDK|" +
                "KMZ|KODAK|MOS|MRW|NCR|NEF|ORF|PAT|PBM|PCT|PCX|PDD|PDF|PDP|PEF|PGM|PICT|" +
                "PMG|PNG|PPM|PS|PSB|PSD|PSDC|PSID|PVR|PXR|RAF|RAW|RLE|RSR|SCT|SRF|TGA|TIF|" +
                "TIFF|TRIF|U3D|VDA|VST|WBMP|WDP|WEBP|WMP|X3F$", "i");
        }
        files = folder.getFiles();
        for (i = 0; i < files.length; i++) {
            f = files[i];
            if (!f.hidden) {
                if (f instanceof Folder && subfolders) {
                    // Recursive (function calls itself).
                    results = results.concat(getFiles(f, subfolders, extensions));
                } else if (f instanceof File && pattern.test(f.name)) {
                    // Ignore EPS if not raster.
                    if (/\.(eps)$/i.test(f.name)) {
                        // Determine if creator is Photoshop.
                        f.open("r");
                        // If Photoshop, creator will be early.
                        // Save time and read only first 512 characters.
                        var data = f.read(512);
                        f.close();
                        if (!/Adobe Photoshop/.test(data)) {
                            // NOT Photoshop EPS.
                            continue;
                        }
                    }
                    results.push(f);
                }
            }
        }
        return results;
    }

    function process() {
        var files;
        var i;
        app.displayDialogs = DialogModes.NO;
        progress.display("Reading folder...");
        try {
            files = getFiles(folderInput, cbIncludeSubfolders.value);
            if (!files.length) {
                doneMessage = "No files found in selected folder";
                return;
            }
            progress.set(files.length);
            count = 0;
            for (i = 0; i < files.length; i++) {
                progress.increment();
                progress.display(File.decode(files[i].name));
                processFile(files[i]);
                count++;
            }
        } finally {
            app.displayDialogs = DialogModes.ERROR;
        }
    }

    function processFile(file) {
        var doc;
        try {
            doc = app.open(file);
        } catch (e) {
            if (/User cancel/.test(e.message)) {
                throw e;
            }
            // Can't open the file. Ignore it.
            return;
        }
        //
        // Do something to the open 'doc'.
        // And because 'doc' was just opened, it is 'app.activeDocument',
        // the typical target of code generated by Scripting Listener.
        //
        // ====================== MARKER: BEGIN PER FILE TASK ==========================
        // Replace the code between the markers with any other for another task.

        // EXAMPLE TASK:
        // Create 'Levels' adjustment layer.
        // Set black=50, gamma=1.5, white=200
        // These are extreme values for demonstration purposes.
        // Change to more reasonable values suited to the project.

        var black = 50;
        var gamma = 1.5;
        var white = 200;

        var desc1;
        var desc2;
        var desc3;
        var list1;
        var list2;
        var ref1;
        var ref2;

        // Make Levels adjustment layer
        // It becomes 'app.activeLayer'
        desc1 = new ActionDescriptor();
        ref1 = new ActionReference();
        ref1.putClass(charIDToTypeID("AdjL"));
        desc1.putReference(charIDToTypeID("null"), ref1);
        desc2 = new ActionDescriptor();
        desc3 = new ActionDescriptor();
        desc3.putEnumerated(stringIDToTypeID("presetKind"), stringIDToTypeID("presetKindType"), stringIDToTypeID("presetKindDefault"));
        desc2.putObject(charIDToTypeID("Type"), charIDToTypeID("Lvls"), desc3);
        desc1.putObject(charIDToTypeID("Usng"), charIDToTypeID("AdjL"), desc2);
        executeAction(charIDToTypeID("Mk  "), desc1, DialogModes.NO);

        // Set black, gamma, and white of app.activeLayer
        // (activeLayer should be the Levels adjustment layer just created)
        desc1 = new ActionDescriptor();
        ref1 = new ActionReference();
        ref1.putEnumerated(charIDToTypeID("AdjL"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
        desc1.putReference(charIDToTypeID("null"), ref1);
        desc2 = new ActionDescriptor();
        desc2.putEnumerated(stringIDToTypeID("presetKind"), stringIDToTypeID("presetKindType"), stringIDToTypeID("presetKindCustom"));
        list1 = new ActionList();
        desc3 = new ActionDescriptor();
        ref2 = new ActionReference();
        ref2.putEnumerated(charIDToTypeID("Chnl"), charIDToTypeID("Chnl"), charIDToTypeID("Cmps"));
        desc3.putReference(charIDToTypeID("Chnl"), ref2);
        list2 = new ActionList();
        list2.putInteger(black); // <================================= black value
        list2.putInteger(white); // <================================= white value
        desc3.putList(charIDToTypeID("Inpt"), list2);
        desc3.putDouble(charIDToTypeID("Gmm "), gamma); // <========== gamma value
        list1.putObject(charIDToTypeID("LvlA"), desc3);
        desc2.putList(charIDToTypeID("Adjs"), list1);
        desc1.putObject(charIDToTypeID("T   "), charIDToTypeID("Lvls"), desc2);
        executeAction(charIDToTypeID("setd"), desc1, DialogModes.NO);

        // Done!

        // ======================= MARKER: END PER FILE TASK ===========================

        // Close doc when done.
        // If process is only to gather information, don't save.
        // If something was changed, save changes.
        // HAVE A BACKUP OF THE IMAGES! Don't take chances.
        // Change to the desired SaveOptions listed below:
        //     SaveOptions.SAVECHANGES
        //     SaveOptions.DONOTSAVECHANGES
        //     SaveOptions.PROMPTTOSAVECHANGES
        doc.close(SaveOptions.SAVECHANGES);
    }

})();
Help me keep making new scripts by supporting my work. Click the PayPal button to contribute any amount you choose. Thank you. William Campbell
Download
Process Folder

For help installing scripts, see How to Install and Use Scripts in Adobe Creative Cloud Applications.

IMPORTANT: scripts are developed for the latest Adobe Creative Cloud applications. Many scripts work in CC 2018 and later, even some as far back as CS6, but may not perform as expected, or run at all, when used in versions prior to 2018. Photoshop features Select Subject and Preserve Details 2.0 definitely fail prior to CC 2018 (version 19) as the features do not exist in earlier versions. For best results use the latest versions of Adobe Creative Cloud applications.

IMPORTANT: by downloading any of the scripts on this page you agree that the software is provided without any warranty, express or implied. USE AT YOUR OWN RISK. Always make backups of important data.

IMPORTANT: fees paid for software products are the purchase of a non-exclusive license to use the software product and do not grant the purchaser any degree of ownership of the software code. Author of the intellectual property and copyright holder William Campbell retains 100% ownership of all code used in all software products regardless of the inspiration for the software product design or functionality.