• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
5

Export many files at once in webp format Photoshop

New Here ,
Feb 24, 2023 Feb 24, 2023

Copy link to clipboard

Copied

Hello, 

I have a question about webp format in photoshop. I want to export many files at once in webp format in Photoshop but I don't find the webp format in photoshop options. Is it normal? 

 

Best regards

Capture d’écran 2023-02-24 120429.jpg

TOPICS
Actions and scripting , macOS , Windows

Views

30.5K

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 2 Correct answers

Community Expert , Feb 24, 2023 Feb 24, 2023

WebP was introduced in Ps2022 as a native Save As (a Copy) option, not under Export As/Quick Export. You will either need to set up a batch action to save directly to WebP or to convert from PNG to WebP or use a custom script to save or convert to WebP.

 

In previous versions, there were 3rd party plugins from Google or other developers.

 

batch-webp.png

 

 

Votes

Translate

Translate
Community Expert , Mar 13, 2023 Mar 13, 2023

Update: A GUI version of the script here...

__________

 

Here is a batch script to save as WebP.

 

Features:

* Optionally run an action (edit the code to enable)

* Optionally ask to overwrite existing files (edit the code to enable)

* Set an input and output folder

* Set supported input file types (edit the code)

* Automatically converts non-RGB mode to sRGB space, 8 bpc

* RGB files automatically converted to 8 bpc

* Option to convert RGB mode to sRGB space (edit the code to enable)

* WebP lossy

...

Votes

Translate

Translate
Adobe
replies 107 Replies 107
Community Expert ,
Jun 19, 2024 Jun 19, 2024

Copy link to clipboard

Copied

quote

If I do this as a batch action theres no way to set the images for example to be about 50kb upon output is there? The images in my folder that are to be convered range from 90kb to 20mb


By @melajuana


Not with this code.

 

To hit a target size on drive, the script would need to save, check the size and resave multiple times with decreasing quality levels to try to hit the target. This is certainly possible, however, it's obviously time consuming.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Jun 19, 2024 Jun 19, 2024

Copy link to clipboard

Copied

So much variables this depends on. First try to make test saves, don't save with extra data options. So no extras, no xnpndata etc. find the correct file size. Than it very much depends per image. The more color data is in your image, the heavier it is. By this I mean. When there are many colors and tints and hues, an image tends to be bigger

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Aug 20, 2024 Aug 20, 2024

Copy link to clipboard

Copied

Your script has been a gift. Thank you @Stephen_A_Marsh .

 

I have browse almost all the threads about it and didn´t find how could I change the output resolution. Is that possible?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Aug 20, 2024 Aug 20, 2024

Copy link to clipboard

Copied

Use a script to resize it or use fit command. You can simple record an action for this and than run this script. That's not so hard to make your self. No need for a custom build script for such a small thing

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 20, 2024 Aug 20, 2024

Copy link to clipboard

Copied

@schroef 

 

Yes, my batch script does offer an option to run a user defined action set/action to provide additional processing without requiring extra code.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Aug 21, 2024 Aug 21, 2024

Copy link to clipboard

Copied

I've tried, but it seems I can´t make it work. Code is not my thing.

PS stops at the first image without even performing the action. 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 20, 2024 Aug 20, 2024

Copy link to clipboard

Copied

@carmen_maría_9071 

 

Please clarify which of the following are required:

 

1) Resize the image, resampling to a desired target size on the longest edge, such as 1920px

 

2) As #1 but also setting a specific print metadata resolution value such as 300ppi

 

3) Resize to a target PPI such as 300ppi, without resampling the image pixels 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Aug 21, 2024 Aug 21, 2024

Copy link to clipboard

Copied

@Stephen_A_Marsh Option 2. Thank you.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 21, 2024 Aug 21, 2024

Copy link to clipboard

Copied

@carmen_maría_9071 

 

The following changes to the script from page 2 of this thread will fit the image to 1920px on the longest edge, proportionally scaling the short edge and also setting the resolution metadata at 300ppi.

 

/*
Batch Save As WebP.jsx
https://community.adobe.com/t5/photoshop-ecosystem-discussions/export-many-files-at-once-in-webp-format-photoshop/m-p/13604411
v1.0 - 14th March 2023, Stephen Marsh
v1.1 - 11th January 2024: Added a "fit image" to 1920px step
v1.2 - 10th February 2024: Added an explicit step to change to RGB mode for non-RGB images
*/

#target photoshop

// Optionally run a specified action
//var actionName = "Molten Lead"; // Action to run, change as needed
//var actionSet = "Default Actions"; // Action set to run, change as needed

// Ensure that version 2022 or later is being used
var versionNumber = app.version.split(".");
var versionCheck = parseInt(versionNumber);

// Fail
if (versionCheck < 23) {
    alert("You must use Photoshop 2022 or later to save using native WebP format...");

// Pass
} else {
    // Set the input and output folders
    var inputFolder = Folder.selectDialog("Please select the input folder:");
    var outputFolder = Folder.selectDialog("Please select the output folder:");
    
    // Limit the input files, add or remove extensions as required
    var fileList = inputFolder.getFiles(/\.(webp|tif|tiff|jpg|jpeg|psd|psb|png)$/i);
    fileList.sort();
    var savedDisplayDialogs = app.displayDialogs;
    app.displayDialogs = DialogModes.NO;
    
    // Set the file processing counter
    var fileCounter = 0;
    
    // Process the input files
    for (var i = 0; i < fileList.length; i++) {
        
        var doc = open(fileList[i]);
        // If the doc isn't in RGB mode
        if (activeDocument.mode !== DocumentMode.RGB) {
            
            // Convert to sRGB & 8 bpc
            activeDocument.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC, true, false);
            activeDocument.changeMode(ChangeMode.RGB);
            activeDocument.bitsPerChannel = BitsPerChannelType.EIGHT;
            
            // Run the optional action
            //app.doAction(actionName, actionSet);

            // Fit image to 1920px
            fitImage(1920, 1920);

            // Save as a copy and close
            saveWebP("compressionLossy", 75, true, true, true, true);
            activeDocument.close(SaveOptions.DONOTSAVECHANGES);
            
            // Increment the file saving counter
            fileCounter++;

        // If the doc is in RGB mode
        } else {
            // Convert to sRGB & 8 bpc
            //activeDocument.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC, true, false);
            activeDocument.bitsPerChannel = BitsPerChannelType.EIGHT;
            
            // Run the optional action
            //app.doAction(actionName, actionSet);

            // Fit image to 1920px
            fitImage(1920, 1920);
            
            // Save as a copy and close
            saveWebP("compressionLossy", 75, true, true, true, true);
            activeDocument.close(SaveOptions.DONOTSAVECHANGES);
            
            // Increment the file saving counter
            fileCounter++;
        }
    };


    /* NEARESTNEIGHBOR | BILINEAR | BICUBIC | BICUBICSMOOTHER | BICUBICSHARPER | BICUBICAUTOMATIC */
    function fitImage(fWidth, fHeight) {
        if (activeDocument.height.value > activeDocument.width.value) {
            activeDocument.resizeImage(null, UnitValue(fHeight, "px"), 300, ResampleMethod.BICUBIC);
        } else {
            activeDocument.resizeImage(UnitValue(fWidth, "px"), null, 300, ResampleMethod.BICUBIC);
        }
    }

    app.displayDialogs = savedDisplayDialogs;
    alert('Script completed!' + '\n' + fileCounter + ' files saved to:' + '\r' + outputFolder.fsName);

    function saveWebP(compType, compValue, xmpData, exifData, psData, asCopy) {
        /*
        v1.1 - 12th March 2023, Stephen Marsh
        https://community.adobe.com/t5/photoshop-ecosystem-discussions/saving-webp-image-by-script/td-p/13642577
        */
        // Doc and path save variables
        var WebPDocName = activeDocument.name.replace(/\.[^\.]+$/, ''); // Remove file extension
        var WebPSavePath = outputFolder + "/" + WebPDocName + ".webp" // Change path as needed
        var WebPFile = new File(WebPSavePath); // Create the file object

        /*
        // Check for existing file object
        if (WebPFile.exists) {
            // true = 'No' as default active button
            if (!confirm("File exists, overwrite: Yes or No?", true))
                // throw alert("Script cancelled!");
                throw null;
        }
        */

        function s2t(s) {
            return app.stringIDToTypeID(s);
        }
        var descriptor = new ActionDescriptor();
        var descriptor2 = new ActionDescriptor();

        // Compression parameters = "compressionLossless" | "compressionLossy"
        descriptor2.putEnumerated(s2t("compression"), s2t("WebPCompression"), s2t(compType)); // string variable
        var WebPCompIsLossless = false; // set the default flag for compression
        if (WebPCompIsLossless == false) {
            // 0 (lowest lossy quality) - 100 (highest lossy quality)
            descriptor2.putInteger(s2t("quality"), compValue); //  number variable
        }

        // Metadata options
        descriptor2.putBoolean(s2t("includeXMPData"), xmpData); // Boolean param moved to function call
        descriptor2.putBoolean(s2t("includeEXIFData"), exifData); // Boolean param moved to function call
        descriptor2.putBoolean(s2t("includePsExtras"), psData); // Boolean param moved to function call

        // WebP format and save path
        descriptor.putObject(s2t("as"), s2t("WebPFormat"), descriptor2);
        descriptor.putPath(s2t("in"), WebPFile); // Save path variable

        // Save As = false | Save As a Copy = true
        descriptor.putBoolean(s2t("copy"), asCopy); // Boolean param moved to function call

        // The extension
        descriptor.putBoolean(s2t("lowerCase"), true);

        // Execute the save
        executeAction(s2t("save"), descriptor, DialogModes.NO); // Change NO to ALL for dialog
    }
}

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Aug 21, 2024 Aug 21, 2024

Copy link to clipboard

Copied

I know. That's what I've been using, but I can´t choose the dpi output just like I set the image size or the compression rate. I'm asking precisely because I need a resolution lower than 300 dpi, images are going to be displayed only in digital devices.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 21, 2024 Aug 21, 2024

Copy link to clipboard

Copied

quote

I know. That's what I've been using, but I can´t choose the dpi output just like I set the image size or the compression rate. I'm asking precisely because I need a resolution lower than 300 dpi, images are going to be displayed only in digital devices.


By @carmen_maría_9071

 

The longest edge is 1920px, set by the code here:

 

// Fit image to 1920px
fitImage(1920, 1920);

 

This is the size regardless of the PPI print metadata value.

 

 To change the PPI metadata, just change the following 2 instances of 300 to whatever you want, 72 or 96 etc.

 

/* NEARESTNEIGHBOR | BILINEAR | BICUBIC | BICUBICSMOOTHER | BICUBICSHARPER | BICUBICAUTOMATIC */
function fitImage(fWidth, fHeight) {
    if (activeDocument.height.value > activeDocument.width.value) {
        activeDocument.resizeImage(null, UnitValue(fHeight, "px"), 300, ResampleMethod.BICUBIC);
    } else {
        activeDocument.resizeImage(UnitValue(fWidth, "px"), null, 300, ResampleMethod.BICUBIC);
    }
}

 

Note that the resolution metadata value set by the script has no impact on the pixel values of the longest edge.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Aug 21, 2024 Aug 21, 2024

Copy link to clipboard

Copied

Thank you!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Sep 12, 2024 Sep 12, 2024

Copy link to clipboard

Copied

I used chatGPT to add a UI with inputs to customize maxsize, resolution and compression quality:

/*
Batch Save As WebP.jsx
https://community.adobe.com/t5/photoshop-ecosystem-discussions/export-many-files-at-once-in-webp-format-photoshop/m-p/13604411
v1.0 - 14th March 2023, Stephen Marsh
v1.1 - 11th January 2024: Added a "fit image" to 1920px step
v1.2 - 10th February 2024: Added an explicit step to change to RGB mode for non-RGB images
v1.3 - 12th September 2024: Added ScriptUI and user input for max image size, resolution and quality
*/

#target photoshop

// Ensure that version 2022 or later is being used
var versionNumber = app.version.split(".");
var versionCheck = parseInt(versionNumber);

// Fail if Photoshop version is less than 2022
if (versionCheck < 23) {
    alert("You must use Photoshop 2022 or later to save using native WebP format...");
} else {
    // Create the ScriptUI dialog
    var dialog = new Window("dialog", "Batch Save As WebP");

    // Create a panel to hold the input fields
    var mainPanel = dialog.add("panel");
    mainPanel.orientation = "column";
    mainPanel.alignChildren = "left";

    // Input folder selection
    var inputGroup = mainPanel.add("group");
    inputGroup.orientation = "row";
    inputGroup.add("statictext", undefined, "Input Folder:");
    var inputFolderInput = inputGroup.add("edittext", undefined, "");
    inputFolderInput.characters = 30;
    var inputBrowseButton = inputGroup.add("button", undefined, "Browse...");

    // Output folder selection
    var outputGroup = mainPanel.add("group");
    outputGroup.orientation = "row";
    outputGroup.add("statictext", undefined, "Output Folder:");
    var outputFolderInput = outputGroup.add("edittext", undefined, "");
    outputFolderInput.characters = 30;
    var outputBrowseButton = outputGroup.add("button", undefined, "Browse...");

    // Maximum image size
    var sizeGroup = mainPanel.add("group");
    sizeGroup.orientation = "row";
    sizeGroup.add("statictext", undefined, "Max Image Size (px):");
    var maxSizeInput = sizeGroup.add("edittext", undefined, "1920");
    maxSizeInput.characters = 10;

    // Resolution input
    var resolutionGroup = mainPanel.add("group");
    resolutionGroup.orientation = "row";
    resolutionGroup.add("statictext", undefined, "Resolution (DPI):");
    var resolutionInput = resolutionGroup.add("edittext", undefined, "72");
    resolutionInput.characters = 10;

    // Compression quality
    var qualityGroup = mainPanel.add("group");
    qualityGroup.orientation = "row";
    qualityGroup.add("statictext", undefined, "Quality (0-100):");
    var qualityInput = qualityGroup.add("edittext", undefined, "75");
    qualityInput.characters = 10;

    // OK and Cancel buttons
    var buttonGroup = dialog.add("group");
    buttonGroup.orientation = "row";
    var okButton = buttonGroup.add("button", undefined, "OK");
    var cancelButton = buttonGroup.add("button", undefined, "Cancel");

    // Folder browsing functionality
    inputBrowseButton.onClick = function () {
        var inputFolder = Folder.selectDialog("Select the input folder:");
        if (inputFolder) {
            inputFolderInput.text = inputFolder.fsName;
        }
    };

    outputBrowseButton.onClick = function () {
        var outputFolder = Folder.selectDialog("Select the output folder:");
        if (outputFolder) {
            outputFolderInput.text = outputFolder.fsName;
        }
    };

    // OK button action
    okButton.onClick = function () {
        var inputFolder = Folder(inputFolderInput.text);
        var outputFolder = Folder(outputFolderInput.text);
        var maxSize = parseInt(maxSizeInput.text) || 1920;
        var resolution = parseInt(resolutionInput.text) || 72;
        var quality = parseInt(qualityInput.text) || 75;

        // Ensure input/output folders are set
        if (!inputFolder.exists || !outputFolder.exists) {
            alert("Please select valid input and output folders.");
            return;
        }

        dialog.close(1); // Close the dialog with "OK"
        processFiles(inputFolder, outputFolder, maxSize, resolution, quality);
    };

    // Cancel button action
    cancelButton.onClick = function () {
        dialog.close(0); // Close the dialog with "Cancel"
    };

    // Show the dialog
    if (dialog.show() == 1) {
        // Continue processing if OK is pressed
    } else {
        // Do nothing if Cancel is pressed
    }

    // Main function to process the files
    function processFiles(inputFolder, outputFolder, maxSize, resolution, quality) {
        // Limit the input files, add or remove extensions as required
        var fileList = inputFolder.getFiles(/\.(webp|tif|tiff|jpg|jpeg|psd|psb|png)$/i);
        fileList.sort();
        var savedDisplayDialogs = app.displayDialogs;
        app.displayDialogs = DialogModes.NO;

        // Set the file processing counter
        var fileCounter = 0;

        // Process the input files
        for (var i = 0; i < fileList.length; i++) {
            
            var doc = open(fileList[i]);

            // If the doc isn't in RGB mode
            if (activeDocument.mode !== DocumentMode.RGB) {
                activeDocument.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC, true, false);
                activeDocument.changeMode(ChangeMode.RGB);
                activeDocument.bitsPerChannel = BitsPerChannelType.EIGHT;
            }
            
            // Fit image to the specified max size
            fitImage(maxSize, maxSize, resolution);

            // Save as WebP and close
            saveWebP(outputFolder, quality, true, true, true, true);
            activeDocument.close(SaveOptions.DONOTSAVECHANGES);

            // Increment the file saving counter
            fileCounter++;
        }

        // Function to resize image based on dimensions and resolution
        function fitImage(fWidth, fHeight, resolution) {
            if (activeDocument.height.value > activeDocument.width.value) {
                activeDocument.resizeImage(null, UnitValue(fHeight, "px"), resolution, ResampleMethod.BICUBIC);
            } else {
                activeDocument.resizeImage(UnitValue(fWidth, "px"), null, resolution, ResampleMethod.BICUBIC);
            }
        }

        app.displayDialogs = savedDisplayDialogs;
        alert('Script completed!' + '\n' + fileCounter + ' files saved to:' + '\r' + outputFolder.fsName);
    }

    // Function to save as WebP
    function saveWebP(outputFolder, quality, xmpData, exifData, psData, asCopy) {
        var WebPDocName = activeDocument.name.replace(/\.[^\.]+$/, ''); // Remove file extension
        var WebPSavePath = outputFolder + "/" + WebPDocName + ".webp"; // Define save path
        var WebPFile = new File(WebPSavePath); // Create file object

        function s2t(s) {
            return app.stringIDToTypeID(s);
        }
        var descriptor = new ActionDescriptor();
        var descriptor2 = new ActionDescriptor();

        // Compression parameters = "compressionLossless" | "compressionLossy"
        descriptor2.putEnumerated(s2t("compression"), s2t("WebPCompression"), s2t("compressionLossy")); // Lossy compression
        descriptor2.putInteger(s2t("quality"), quality); // Set quality (0-100)

        // Metadata options
        descriptor2.putBoolean(s2t("includeXMPData"), xmpData);
        descriptor2.putBoolean(s2t("includeEXIFData"), exifData);
        descriptor2.putBoolean(s2t("includePsExtras"), psData);

        // WebP format and save path
        descriptor.putObject(s2t("as"), s2t("WebPFormat"), descriptor2);
        descriptor.putPath(s2t("in"), WebPFile);

        // Save As a Copy
        descriptor.putBoolean(s2t("copy"), asCopy);
        descriptor.putBoolean(s2t("lowerCase"), true);

        // Execute save action
        executeAction(s2t("save"), descriptor, DialogModes.NO);
    }
}

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 12, 2024 Sep 12, 2024

Copy link to clipboard

Copied

quote

I used chatGPT to add a UI with inputs to customize maxsize, resolution and compression quality:


By @filduarte

 

Thank you for extending my script!

 

I have never tried adding scriptUI to a script using AI, my expectations are obviously limited...

 

What did you do, paste in the previous code and add a prompt to add the GUI? Did you have to specify which variables in the existing script needed to have UI controls etc?

 

Can you share the prompt/s?

 

Did you have to tidy up the code, or did it work correctly first go?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Sep 12, 2024 Sep 12, 2024

Copy link to clipboard

Copied

I used several prompts because I made the first prompt before testing the original script and the resulting script ended opening up a prompt to each input field 😂

I pasted the previous code and added this prompt:
The script for photoshop below serves to convert images into webp. Change it to display text fields to set the maximum image size (keeping the default value in 1920px) and resolution (standard at 72).

Then it took 3 more prompts to get to the result I posted:

  • This script opens several prompts one followed by the other. Is it possible to display only one window with all the inputs?
  • Redo by also inserting in the dialog window a field to define image quality in compression and the input folder and output folder selection fields
  • Is it possible to insert the labels of the fields next to the respective fields, the Browse buttons next to the respective text fields, and the OK and Cancel buttons next to each other?

 

I tried to make the layout more organized, align the labels and inputs like a table where the first column has the labels and the second has the inputs, but it didn't worked out.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 13, 2024 Sep 13, 2024

Copy link to clipboard

Copied

@filduarte 

 

Thank you, these LLM’s have certainly come a long way in a short time! I like scripting, but detest building a GUI, so this is great stuff. It's not so much the GUI build, it's also linking up the GUI elements to the underlying code that I dislike.

 

I just tested this and I was blown away with the results. As I understand the code, I could direct the generative AI to pinpoint certain variables and control how I wanted them, rather than leaving that up to the AI.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Sep 14, 2024 Sep 14, 2024

Copy link to clipboard

Copied

Yeah I feel you on that. It's so tedious to get it to work. Takes of hours of testing and debugg9ng..

 

If you than want to expand it using descriptor so it stores the variables, it gets even worse. If you need a preset system, dang that's also tons of work. Yet, somehow I do like this process and it sure does make me feel good when 8 accomplish on the job

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Sep 14, 2024 Sep 14, 2024

Copy link to clipboard

Copied

Is AI cclever" enough to understand which variable is for what and use the inputs as a variable. That's genius!

I always scripture website by Joonas, but it's very tedious work. Especially big scripts with lots of options. Then. Adding presets and storage of the files expands the difficultly and time needed to test and take out errors.

 

Very cool you were able to do this

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Sep 14, 2024 Sep 14, 2024

Copy link to clipboard

Copied

Can you show a screen grab of the dialog?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 14, 2024 Sep 14, 2024

Copy link to clipboard

Copied

quote

Can you show a screen grab of the dialog?


By @schroef

 

Although you directed this request to @filduarte here are some screenshots of some of the prompts that I used. As this was my first time adding a GUI via AI, I wasn't sure if I should be more active or passive in the prompts. Am I overthinking things or not giving enough detail? SO much depends on the prompt, as well as the model. 

 

prompts.png

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 13, 2024 Sep 13, 2024

Copy link to clipboard

Copied

@filduarte 

 

You can change the following entries in your code from "edittext" to "editnumber" to ensure that only numbers are entered into the appropriate GUI fields...

 

_________ 

 

From:

var maxSizeInput = sizeGroup.add("edittext", undefined, "1920");

To:

var maxSizeInput = sizeGroup.add("editnumber", undefined, "1920");

 

_________ 

 

From:

var resolutionInput = resolutionGroup.add("edittext", undefined, "72");

To:

var resolutionInput = resolutionGroup.add("editnumber", undefined, "72");

 

_________ 

 

From:

var qualityInput = qualityGroup.add("edittext", undefined, "75");

To:

var qualityInput = qualityGroup.add("editnumber", undefined, "75");

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Sep 13, 2024 Sep 13, 2024

Copy link to clipboard

Copied

Nice!
Oh, and thank you for making this awesome script! It's saving me a lot of time.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 13, 2024 Sep 13, 2024

Copy link to clipboard

Copied

You're welcome!

 

Thank you for opening my eyes to adding scriptUI through AI.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Sep 13, 2024 Sep 13, 2024

Copy link to clipboard

Copied

You're welcome as well!
One last contribution: this one sets the output folder automatically to a webp subfolder in the input folder if the user don't choose an output folder. It saves a bit more of time 😃

 

/*
Batch Save As WebP.jsx
https://community.adobe.com/t5/photoshop-ecosystem-discussions/export-many-files-at-once-in-webp-format-photoshop/m-p/13604411
v1.0 - 14th March 2023, Stephen Marsh
v1.1 - 11th January 2024: Added a "fit image" to 1920px step
v1.2 - 10th February 2024: Added an explicit step to change to RGB mode for non-RGB images
v1.3 - 12th September 2024: Added ScriptUI with inputs for input/output folders, max image size, resolution and quality
*/

#target photoshop

// Ensure that version 2022 or later is being used
var versionNumber = app.version.split(".");
var versionCheck = parseInt(versionNumber);

// Fail if Photoshop version is less than 2022
if (versionCheck < 23) {
    alert("You must use Photoshop 2022 or later to save using native WebP format...");
} else {
    // Create the ScriptUI dialog
    var dialog = new Window("dialog", "Batch Save As WebP");

    // Create a panel to hold the input fields
    var mainPanel = dialog.add("panel");
    mainPanel.orientation = "column";
    mainPanel.alignChildren = "left";

    // Input folder selection
    var inputGroup = mainPanel.add("group");
    inputGroup.orientation = "row";
    inputGroup.add("statictext", undefined, "Input Folder:");
    var inputFolderInput = inputGroup.add("edittext", undefined, "");
    inputFolderInput.characters = 30;
    var inputBrowseButton = inputGroup.add("button", undefined, "Browse...");

    // Output folder selection
    var outputGroup = mainPanel.add("group");
    outputGroup.orientation = "row";
    outputGroup.add("statictext", undefined, "Output Folder:");
    var outputFolderInput = outputGroup.add("edittext", undefined, "[Input Folder]/webp");
    outputFolderInput.characters = 30;
    var outputBrowseButton = outputGroup.add("button", undefined, "Browse...");

    // Maximum image size
    var sizeGroup = mainPanel.add("group");
    sizeGroup.orientation = "row";
    sizeGroup.add("statictext", undefined, "Max Image Size (px):");
    var maxSizeInput = sizeGroup.add("editnumber", undefined, "1920");
    maxSizeInput.characters = 10;

    // Resolution input
    var resolutionGroup = mainPanel.add("group");
    resolutionGroup.orientation = "row";
    resolutionGroup.add("statictext", undefined, "Resolution (DPI):");
    var resolutionInput = resolutionGroup.add("editnumber", undefined, "72");
    resolutionInput.characters = 10;

    // Compression quality
    var qualityGroup = mainPanel.add("group");
    qualityGroup.orientation = "row";
    qualityGroup.add("statictext", undefined, "Quality (0-100):");
    var qualityInput = qualityGroup.add("editnumber", undefined, "75");
    qualityInput.characters = 10;

    // OK and Cancel buttons
    var buttonGroup = dialog.add("group");
    buttonGroup.orientation = "row";
    var okButton = buttonGroup.add("button", undefined, "OK");
    var cancelButton = buttonGroup.add("button", undefined, "Cancel");

    // OutputFolderInput Placeholder behavior
    outputFolderInput.onSetFocus = function () {
        if (outputFolderInput.text === "[Input Folder]/webp") {
            outputFolderInput.text = "";
        }
    };
    
    outputFolderInput.onKillFocus = function () {
        if (outputFolderInput.text === "") {
            outputFolderInput.text = "[Input Folder]/webp";
        }
    };

    // Folder browsing functionality
    inputBrowseButton.onClick = function () {
        var inputFolder = Folder.selectDialog("Select the input folder:");
        if (inputFolder) {
            inputFolderInput.text = inputFolder.fsName;
        }
    };

    outputBrowseButton.onClick = function () {
        var outputFolder = Folder.selectDialog("Select the output folder:");
        if (outputFolder) {
            outputFolderInput.text = outputFolder.fsName;
        }
    };

    // OK button action
    okButton.onClick = function () {
        var inputFolder = Folder(inputFolderInput.text);
        var outputFolder = Folder(inputFolder.fsName + '/webp');
        if (outputFolderInput.text !== "[Input Folder]/webp"
            && outputFolderInput.text !== ""
        ) {
            outputFolder = Folder(outputFolderInput.text);
        }
        var maxSize = parseInt(maxSizeInput.text) || 1920;
        var resolution = parseInt(resolutionInput.text) || 72;
        var quality = parseInt(qualityInput.text) || 75;

        // Ensure input/output folders are set
        if (!inputFolder.exists) {
            alert("Please select valid input folder.");
            return;
        }

        // Create the output folder if needed
        if (!outputFolder.exists) {
            outputFolder.create();
        }

        dialog.close(1); // Close the dialog with "OK"
        processFiles(inputFolder, outputFolder, maxSize, resolution, quality);
    };

    // Cancel button action
    cancelButton.onClick = function () {
        dialog.close(0); // Close the dialog with "Cancel"
    };

    // Show the dialog
    if (dialog.show() == 1) {
        // Continue processing if OK is pressed
    } else {
        // Do nothing if Cancel is pressed
    }

    // Main function to process the files
    function processFiles(inputFolder, outputFolder, maxSize, resolution, quality) {
        // Limit the input files, add or remove extensions as required
        var fileList = inputFolder.getFiles(/\.(webp|tif|tiff|jpg|jpeg|psd|psb|png)$/i);
        fileList.sort();
        var savedDisplayDialogs = app.displayDialogs;
        app.displayDialogs = DialogModes.NO;

        // Set the file processing counter
        var fileCounter = 0;

        // Process the input files
        for (var i = 0; i < fileList.length; i++) {
            
            var doc = open(fileList[i]);

            // If the doc isn't in RGB mode
            if (activeDocument.mode !== DocumentMode.RGB) {
                activeDocument.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC, true, false);
                activeDocument.changeMode(ChangeMode.RGB);
                activeDocument.bitsPerChannel = BitsPerChannelType.EIGHT;
            }
            
            // Fit image to the specified max size
            fitImage(maxSize, maxSize, resolution);

            // Save as WebP and close
            saveWebP(outputFolder, quality, true, true, true, true);
            activeDocument.close(SaveOptions.DONOTSAVECHANGES);

            // Increment the file saving counter
            fileCounter++;
        }

        // Function to resize image based on dimensions and resolution
        function fitImage(fWidth, fHeight, resolution) {
            if (activeDocument.height.value > activeDocument.width.value) {
                activeDocument.resizeImage(null, UnitValue(fHeight, "px"), resolution, ResampleMethod.BICUBIC);
            } else {
                activeDocument.resizeImage(UnitValue(fWidth, "px"), null, resolution, ResampleMethod.BICUBIC);
            }
        }

        app.displayDialogs = savedDisplayDialogs;
        alert('Script completed!' + '\n' + fileCounter + ' files saved to:' + '\r' + outputFolder.fsName);
    }

    // Function to save as WebP
    function saveWebP(outputFolder, quality, xmpData, exifData, psData, asCopy) {
        var WebPDocName = activeDocument.name.replace(/\.[^\.]+$/, ''); // Remove file extension
        var WebPSavePath = outputFolder + "/" + WebPDocName + ".webp"; // Define save path
        var WebPFile = new File(WebPSavePath); // Create file object

        function s2t(s) {
            return app.stringIDToTypeID(s);
        }
        var descriptor = new ActionDescriptor();
        var descriptor2 = new ActionDescriptor();

        // Compression parameters = "compressionLossless" | "compressionLossy"
        descriptor2.putEnumerated(s2t("compression"), s2t("WebPCompression"), s2t("compressionLossy")); // Lossy compression
        descriptor2.putInteger(s2t("quality"), quality); // Set quality (0-100)

        // Metadata options
        descriptor2.putBoolean(s2t("includeXMPData"), xmpData);
        descriptor2.putBoolean(s2t("includeEXIFData"), exifData);
        descriptor2.putBoolean(s2t("includePsExtras"), psData);

        // WebP format and save path
        descriptor.putObject(s2t("as"), s2t("WebPFormat"), descriptor2);
        descriptor.putPath(s2t("in"), WebPFile);

        // Save As a Copy
        descriptor.putBoolean(s2t("copy"), asCopy);
        descriptor.putBoolean(s2t("lowerCase"), true);

        // Execute save action
        executeAction(s2t("save"), descriptor, DialogModes.NO);
    }
}

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 14, 2024 Sep 14, 2024

Copy link to clipboard

Copied

I have been playing around with adding a GUI and some additional options since the first script was posted...

webp-1-6.png

 

/*
Batch Save As WebP scriptUI GUI v1-7.jsx
https://community.adobe.com/t5/photoshop-ecosystem-discussions/export-many-files-at-once-in-webp-format-photoshop/td-p/13604411/page/4#U14859793
v1.0 - 14th March 2023, Stephen Marsh
v1.1 - 11th January 2024: Added a "fit image" defaulting to 1920px step
v1.2 - 10th February 2024: Added an explicit step to change to RGB mode
v1.3 - 12th September 2024: User "filduarte" Added ScriptUI and user input for max image size, resolution and quality
v1.4 - 13th September 2024: Added various GUI control options. All files saved as 8bpc sRGB, overwriting existing WebP files of the same name without warning
v1.5 - 21st September 2024: Changed the dropdown to use label names rather than compression values as names. Removed the Save a Copy checkbox.
v1.6 - 11th October 2024: Added dropdown menus to optionally run an action on each file before saving as WebP. Cosmetic GUI layout changes to reduce the vertical depth of the window.
v1.7 - 5th November 2024: Moved the folder selection buttons to the left of the path display fields. 
*/

#target photoshop

// Ensure that version 2022 or later is being used
var versionNumber = app.version.split(".");
var versionCheck = parseInt(versionNumber[0]);

if (versionCheck < 23) {
    alert("You must use Photoshop 2022 or later to save using native WebP format...");

} else {
    // Create the ScriptUI dialog window
    var theDialogWin = new Window("dialog", "WebP Batch Processor (v1.7)");

    theDialogWin.orientation = "column";
    theDialogWin.alignChildren = "left";

    var infoText = theDialogWin.add("statictext", undefined, "The WebP files will be converted to sRGB 8 bits/channel");
    infoText.alignment = "center";

    // Input Folder Section
    var inputFolderPanel = theDialogWin.add("panel", undefined, "Input Folder");
    inputFolderPanel.orientation = "column";
    inputFolderPanel.alignChildren = ["fill", "top"]; // Align all children to fill the width
    inputFolderPanel.alignment = ["fill", "top"]; // Make the panel itself fill the width

    inputFolderPanel.add("statictext", undefined, "Select the source image folder to process:");
    var inputFolderRow = inputFolderPanel.add("group"); // Create a row for input field and button
    inputFolderRow.orientation = "row";
    inputFolderRow.alignment = ["fill", "top"]; // Make the row fill the width

    var inputFolderButton = inputFolderRow.add("button", undefined, "Browse...");
    inputFolderButton.alignment = ["left", "top"]; // Align button to the right

    var inputFolderText = inputFolderRow.add("edittext", undefined, "");
    inputFolderText.characters = 30;
    inputFolderText.alignment = ["fill", "top"]; // Make the input field fill available width

    // Process Subfolders Checkbox
    var recursiveCheckbox = inputFolderPanel.add("checkbox", undefined, "Include all subfolders");
    recursiveCheckbox.value = false; // Default to not recursive

    // Output Folder Section
    var outputFolderPanel = theDialogWin.add("panel", undefined, "Output Folder");
    outputFolderPanel.orientation = "column";
    outputFolderPanel.alignChildren = ["fill", "top"]; // Align all children to fill the width
    outputFolderPanel.alignment = ["fill", "top"]; // Make the panel itself fill the width

    outputFolderPanel.add("statictext", undefined, "Select the location to save the WebP files:");
    var outputFolderRow = outputFolderPanel.add("group"); // Create a row for output field and button
    outputFolderRow.orientation = "row";
    outputFolderRow.alignment = ["fill", "top"]; // Make the row fill the width

    var outputFolderButton = outputFolderRow.add("button", undefined, "Browse...");
    outputFolderButton.alignment = ["left", "top"]; // Align button to the right

    var outputFolderText = outputFolderRow.add("edittext", undefined, "");
    outputFolderText.characters = 30;
    outputFolderText.alignment = ["fill", "top"]; // Make the input field fill available width

    // Mirror Directory Structure Checkbox
    var mirrorStructureCheckbox = outputFolderPanel.add("checkbox", undefined, "Retain the source subfolder structure");
    mirrorStructureCheckbox.value = false; // Default to not mirroring
    mirrorStructureCheckbox.enabled = false; // Disable by default

    // Enable/Disable "Mirror Structure" based on the recursive option
    recursiveCheckbox.onClick = function () {
        mirrorStructureCheckbox.enabled = recursiveCheckbox.value;
    };

    // Create a panel for the compression settings
    var compressionPanel = theDialogWin.add("panel", undefined, "Compression Settings");
    compressionPanel.orientation = "column";
    compressionPanel.alignChildren = ["left", "top"]; // Align controls to the top left
    compressionPanel.margins = 15; // Add padding around the content
    compressionPanel.alignment = ["fill", "top"]; // Make the panel full window width

    // Create a main group to hold all controls in a single row
    var mainGroup = compressionPanel.add("group");
    mainGroup.orientation = "row"; // Arrange controls in a row
    mainGroup.alignChildren = ["left", "center"]; // Center align children vertically
    mainGroup.alignment = ["fill", "top"]; // Make the group fill the panel's width
    mainGroup.spacing = 10; // Add space between controls

    // Compression Type Dropdown (compressionLossy | compressionLossless)
    mainGroup.add("statictext", undefined, "Compression Type:");
    var compTypeDropdown = mainGroup.add("dropdownlist", undefined, ["Lossy", "Lossless"]);
    compTypeDropdown.selection = 0; // Default to Lossy

    // Compression Quality Label
    mainGroup.add("statictext", undefined, "Compression Quality:");

    // Compression Quality Slider and Text
    var qualityGroup = mainGroup.add("group"); // Create a group for the slider and text
    qualityGroup.orientation = "row"; // Arrange slider and text in a row
    qualityGroup.alignChildren = ["left", "center"]; // Center align children vertically
    qualityGroup.spacing = 10; // Add space between the slider and the text

    var compValueSlider = qualityGroup.add("slider", undefined, 75, 0, 100); // Start, Min, Max
    compValueSlider.size = [281, 20]; // Size of the slider
    var compValueText = qualityGroup.add("edittext", undefined, "75"); // Change to edittext for user input
    compValueText.characters = 4; // Set the number of characters for the input field

    // Update the edittext and slider to reflect the current value
    compValueSlider.onChanging = function () {
        var sliderValue = Math.round(compValueSlider.value);
        compValueText.text = sliderValue.toString(); // Update the edittext
    };

    // Update the slider based on edittext input
    compValueText.onChange = function () {
        var inputValue = parseInt(compValueText.text, 10);
        if (!isNaN(inputValue) && inputValue >= 0 && inputValue <= 100) {
            compValueSlider.value = inputValue; // Update the slider
        } else {
            compValueText.text = Math.round(compValueSlider.value).toString(); // Reset to the current slider value if invalid
        }
    };

    // Function to toggle the slider based on compression type
    function updateCompressionControls() {
        if (compTypeDropdown.selection.text === "Lossless") {
            compValueSlider.enabled = false;
            compValueText.enabled = false;
        } else {
            compValueSlider.enabled = true;
            compValueText.enabled = true;
        }
    }

    // Initialize control states
    updateCompressionControls();

    // Disable compression quality slider when "Lossless" is selected
    compTypeDropdown.onChange = function () {
        updateCompressionControls();
    };

    // Create a panel for all checkboxes
    var checkboxPanel = theDialogWin.add("panel", undefined, "Options");
    checkboxPanel.orientation = "column";
    checkboxPanel.alignChildren = ["left", "top"];
    checkboxPanel.margins = 15;
    checkboxPanel.alignment = ["fill", "top"]; // Make panel full width

    // Create a group for the three columns within the panel
    var checkboxGroup = checkboxPanel.add("group");
    checkboxGroup.orientation = "row";
    checkboxGroup.alignChildren = ["left", "top"];
    checkboxGroup.alignment = ["left", "top"]; // Make the group full width
    checkboxGroup.spacing = 20; // Add spacing between the columns

    // Left column
    var leftColumn = checkboxGroup.add("group");
    leftColumn.orientation = "column";
    leftColumn.alignChildren = ["left", "top"]; // Ensure elements fill the width

    // Fit Image Checkbox and Input
    var fitImageGroup = leftColumn.add("group");
    fitImageGroup.orientation = "row";
    fitImageGroup.alignChildren = ["left", "center"];
    fitImageGroup.alignment = ["fill", "top"]; // Make the group full width
    var fitImageCheckbox = fitImageGroup.add("checkbox", undefined, "Fit Image (px):");
    fitImageCheckbox.value = false;
    var fitImageInput = fitImageGroup.add("editnumber", undefined, "1920");
    fitImageInput.characters = 5;
    fitImageInput.enabled = fitImageCheckbox.value;
    fitImageCheckbox.onClick = function () {
        fitImageInput.enabled = fitImageCheckbox.value;
    };

    // XMP Checkbox
    var xmpDataCheckbox = leftColumn.add("checkbox", undefined, "Include XMP Data");
    xmpDataCheckbox.value = false;

    // Middle column
    var middleColumn = checkboxGroup.add("group");
    middleColumn.orientation = "column";
    middleColumn.alignChildren = ["fill", "top"]; // Ensure elements fill the width

    // PPI Checkbox and Input
    var ppiGroup = middleColumn.add("group");
    ppiGroup.orientation = "row";
    ppiGroup.alignChildren = ["left", "center"];
    ppiGroup.alignment = ["left", "top"]; // Make the group full width
    var ppiCheckbox = ppiGroup.add("checkbox", undefined, "PPI Value:");
    ppiCheckbox.value = false;
    var ppiInput = ppiGroup.add("editnumber", undefined, "300");
    ppiInput.characters = 5;
    ppiInput.enabled = ppiCheckbox.value;
    ppiCheckbox.onClick = function () {
        ppiInput.enabled = ppiCheckbox.value;
    };

    // Include EXIF Data Checkbox in the center column
    var exifDataCheckbox = middleColumn.add("checkbox", undefined, "Include EXIF Data");
    exifDataCheckbox.value = false;

    // Right column
    var rightColumn = checkboxGroup.add("group");
    rightColumn.orientation = "column";
    rightColumn.alignChildren = ["fill", "top"]; // Ensure elements fill the width

    // Photoshop Data Checkbox
    var psDataCheckbox = rightColumn.add("checkbox", undefined, "Include Photoshop Data");
    psDataCheckbox.value = false;

    // Create a panel to contain the action dropdowns
    var actionPanel = theDialogWin.add("panel", undefined, "Run Action");
    actionPanel.orientation = "column";
    actionPanel.alignChildren = ["fill", "top"]; // Align children to fill width
    actionPanel.margins = 15; // Add padding around the content
    actionPanel.alignment = ["fill", "top"]; // Ensure the panel stretches full width

    // Set the width for the dropdown action menus
    var dropdownWidth = 350; // Set dropdown width

    // Create a group for the action controls
    var actionGroup = actionPanel.add("group");
    actionGroup.orientation = "row"; // Set group to row orientation
    actionGroup.alignChildren = ["left", "top"]; // Align children to the left and top
    actionGroup.alignment = ["fill", "top"]; // Ensure the group stretches full width

    // Create a group for the Action Set label and dropdown
    var actionSetColumn = actionGroup.add("group");
    actionSetColumn.orientation = "column"; // Set column orientation for Action Set
    actionSetColumn.alignChildren = ["left", "top"]; // Align children to the left
    actionSetColumn.alignment = ["fill", "top"]; // Ensure this column stretches full width

    // Add Action Set label and dropdown in the Action Set column
    //actionSetColumn.add('statictext', undefined, 'Action Set:').alignment = ['left', 'center'];
    var actionSetDropdown = actionSetColumn.add('dropdownlist', undefined, []);
    actionSetDropdown.preferredSize.width = dropdownWidth; // Set width

    // Create a group for the Action label and dropdown
    var actionLabelColumn = actionGroup.add("group");
    actionLabelColumn.orientation = "column"; // Set column orientation for Action
    actionLabelColumn.alignChildren = ["left", "top"]; // Align children to the left
    actionLabelColumn.alignment = ["fill", "top"]; // Ensure this column stretches full width

    // Add Action label and dropdown in the Action column
    //actionLabelColumn.add('statictext', undefined, 'Action:').alignment = ['left', 'center'];
    var actionDropdown = actionLabelColumn.add('dropdownlist', undefined, []);
    actionDropdown.preferredSize.width = dropdownWidth; // Set width

    // Force the dropdowns to resize to match the specified width
    actionSetDropdown.onResizing = actionSetDropdown.onResize = function () {
        this.size.width = dropdownWidth; // Ensure dropdown maintains the specified width
    };

    actionDropdown.onResizing = actionDropdown.onResize = function () {
        this.size.width = dropdownWidth; // Ensure dropdown maintains the specified width
    };

    // Populate the action set dropdown
    actionSetDropdown.add('item', ''); // Add a blank/null option as the default
    var actionSets = getActionSets();
    for (var i = 0; i < actionSets.length; i++) {
        actionSetDropdown.add('item', actionSets[i]);
    }

    // Automatically select the blank option for both action set and action dropdowns
    actionSetDropdown.selection = actionSetDropdown.items[0];
    actionDropdown.add('item', ''); // Add a blank/null option as the default

    // When the action set is changed, update the action dropdown
    actionSetDropdown.onChange = function () {
        actionDropdown.removeAll();

        if (actionSetDropdown.selection && actionSetDropdown.selection.text != '') {
            // Populate actions for the selected action set
            var actions = getActions(actionSetDropdown.selection.text);
            for (var i = 0; i < actions.length; i++) {
                actionDropdown.add('item', actions[i]);
            }
            // Automatically select the first action if the set isn't null
            if (actions.length > 0) {
                actionDropdown.selection = actionDropdown.items[0]; // Select the first action
            }
        } else {
            // If the action set is null, add a blank option for action dropdown
            actionDropdown.add('item', '');
            actionDropdown.selection = actionDropdown.items[0]; // Select the null action
        }
    };

    // OK and Cancel Buttons
    var buttonGroup = theDialogWin.add("group");
    buttonGroup.alignment = "right";
    var cancelButton = buttonGroup.add("button", undefined, "Cancel");
    var okButton = buttonGroup.add("button", undefined, "OK");

    // Input Folder Browse Button functionality
    inputFolderButton.onClick = function () {
        var inputFolder = Folder.selectDialog("Select the input folder:");
        if (inputFolder) {
            inputFolderText.text = inputFolder.fsName;
        }
    };

    // Output Folder Browse Button functionality
    outputFolderButton.onClick = function () {
        var outputFolder = Folder.selectDialog("Select the output folder:");
        if (outputFolder) {
            outputFolderText.text = outputFolder.fsName;
        }
    };

    // Cancel Button functionality
    cancelButton.onClick = function () {
        theDialogWin.close();
    };

    // OK Button functionality
    okButton.onClick = function () {
        theDialogWin.close();
        app.refresh();
        // Restore the Photoshop panels
        app.togglePalettes();
        if (inputFolderText.text === "" || outputFolderText.text === "") {
            alert("Ensure that you select both input and output folders and other options before pressing OK!");
            // Restore the Photoshop panels
            app.togglePalettes();
            return;
        }

        // Assign selected folders to variables
        var inputFolder = new Folder(inputFolderText.text);
        var outputFolder = new Folder(outputFolderText.text);

        // Map dropdown selection to original compression type values
        var compType = compTypeDropdown.selection.text === "Lossless" ? "compressionLossless" : "compressionLossy";
        var compValue = Math.round(compValueSlider.value);
        var xmpData = xmpDataCheckbox.value;
        var exifData = exifDataCheckbox.value;
        var psData = psDataCheckbox.value;

        // Gather fitImage and PPI parameters
        var fitValue = fitImageCheckbox.value ? parseInt(fitImageInput.text) : null;
        var ppi = ppiCheckbox.value ? parseInt(ppiInput.text) : null;

        // Process files with the recursive and mirror structure flags
        processFiles(inputFolder, outputFolder, compType, compValue, xmpData, exifData, psData, fitValue, ppi, recursiveCheckbox.value, mirrorStructureCheckbox.value);
        theDialogWin.close();
    };

    theDialogWin.show();
}


// Main processing function with optional recursion and mirroring structure
function processFiles(inputFolder, outputFolder, compType, compValue, xmpData, exifData, psData, fitValue, ppi, recursive, mirrorStructure) {
    var fileList = getFilesRecursive(inputFolder, recursive);
    fileList.sort();
    var savedDisplayDialogs = app.displayDialogs;
    app.displayDialogs = DialogModes.NO;

    var inputFileCounter = 0;
    var fileCounter = 0;

    for (var i = 0; i < fileList.length; i++) {
        // Script running notification window - courtesy of William Campbell
        // https://www.marspremedia.com/download?asset=adobe-script-tutorial-11.zip
        // https://youtu.be/JXPeLi6uPv4?si=Qx0OVNLAOzDrYPB4
        var working = new Window("palette");
        working.preferredSize = [300, 80];
        working.add("statictext");
        working.t = working.add("statictext");
        working.add("statictext");
        working.display = function (message) {
            this.t.text = message || "Script running, please wait...";
            this.show();
            app.refresh();
        };
        working.display();

        // Open the files for processing
        open(fileList[i]);

        // Create mirrored subfolder structure in output folder if requested
        var relativePath = fileList[i].parent.fsName.replace(inputFolder.fsName, "");
        var targetFolder = outputFolder;
        if (mirrorStructure && relativePath !== "") {
            targetFolder = new Folder(outputFolder + "/" + relativePath);
            if (!targetFolder.exists) {
                targetFolder.create();
            }
        }

        // File processing options
        // Bitmap mode input
        if (activeDocument.mode == DocumentMode.BITMAP) {
            activeDocument.changeMode(ChangeMode.GRAYSCALE);
            activeDocument.changeMode(ChangeMode.RGB);
            activeDocument.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC, true, false);
            activeDocument.bitsPerChannel = BitsPerChannelType.EIGHT;
            // Indexed Color, CMYK or Lab mode input
        } else if (activeDocument.mode == DocumentMode.INDEXEDCOLOR || activeDocument.mode == DocumentMode.CMYK || activeDocument.mode == DocumentMode.LAB) {
            activeDocument.changeMode(ChangeMode.RGB);
            activeDocument.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC, true, false);
            activeDocument.bitsPerChannel = BitsPerChannelType.EIGHT;
        } else {
            activeDocument.changeMode(ChangeMode.RGB);
            activeDocument.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC, true, false);
            activeDocument.bitsPerChannel = BitsPerChannelType.EIGHT;
        }

        // Run the selected action on each processed file
        if (actionSetDropdown.selection && actionSetDropdown.selection.text != '' &&
            actionDropdown.selection && actionDropdown.selection.text != '') {
            runAction(actionSetDropdown.selection.text, actionDropdown.selection.text);
        }

        // Fit image to specified value and/or change PPI if enabled
        if (fitValue !== null || ppi !== null) {
            fitImage(fitValue, ppi);
        }

        // Save as a copy and close
        saveWebP(compType, compValue, xmpData, exifData, psData, targetFolder);
        activeDocument.close(SaveOptions.DONOTSAVECHANGES);

        // Increment the counters
        inputFileCounter++;
        fileCounter++;

        // Ensure Photoshop has focus before closing the running script notification window
        app.bringToFront();
        working.close();
    }

    // End of script notifications
    app.displayDialogs = savedDisplayDialogs;
    app.beep();
    alert('Script completed!' + '\r' + inputFileCounter + ' source files saved as ' + fileCounter + ' WebP files!');

    // Restore the Photoshop panels
    app.togglePalettes();
}


///// Helper Functions /////

// Recurse into folders and files
function getFilesRecursive(folder, recursive) {
    var fileList = [];
    var allFiles = folder.getFiles();
    for (var i = 0; i < allFiles.length; i++) {
        var file = allFiles[i];
        if (file instanceof Folder && recursive) {
            fileList = fileList.concat(getFilesRecursive(file, recursive));
        } else if (file instanceof File && /\.(webp|tif|tiff|jpg|jpeg|psd|psb|png|tga)$/i.test(file.name)) {
            fileList.push(file);
        }
    }
    return fileList;
}

// Fit Image
function fitImage(fitValue, ppi) {
    // NEARESTNEIGHBOR | BILINEAR | BICUBIC | BICUBICSMOOTHER | BICUBICSHARPER | BICUBICAUTOMATIC
    if (fitValue !== null && ppi !== null) {
        // Both fit value and PPI are specified
        if (activeDocument.height.value > activeDocument.width.value) {
            activeDocument.resizeImage(null, UnitValue(fitValue, "px"), ppi, ResampleMethod.BICUBIC);
        } else {
            activeDocument.resizeImage(UnitValue(fitValue, "px"), null, ppi, ResampleMethod.BICUBIC);
        }
    } else if (fitValue !== null) {
        // Only fit value is specified
        if (activeDocument.height.value > activeDocument.width.value) {
            activeDocument.resizeImage(null, UnitValue(fitValue, "px"), activeDocument.resolution, ResampleMethod.BICUBIC);
        } else {
            activeDocument.resizeImage(UnitValue(fitValue, "px"), null, activeDocument.resolution, ResampleMethod.BICUBIC);
        }
    } else if (ppi !== null) {
        // Only PPI is specified
        activeDocument.resizeImage(undefined, undefined, ppi, ResampleMethod.NONE);
    }
}

// Save WebP Format
function saveWebP(compType, compValue, xmpData, exifData, psData, outputFolder) {
    // https://community.adobe.com/t5/photoshop-ecosystem-discussions/saving-webp-image-by-script/td-p/13642577
    function s2t(s) {
        return app.stringIDToTypeID(s);
    }
    var WebPDocName = activeDocument.name.replace(/\.[^\.]+$/, ''); // Remove file extension
    var WebPSavePath = outputFolder + "/" + WebPDocName + ".webp"; // Save path
    var WebPFile = new File(WebPSavePath);
    var descriptor = new ActionDescriptor();
    var descriptor2 = new ActionDescriptor();
    descriptor2.putEnumerated(s2t("compression"), s2t("WebPCompression"), s2t(compType));
    if (compType === "compressionLossy") {
        descriptor2.putInteger(s2t("quality"), compValue);
    }
    descriptor2.putBoolean(s2t("includeXMPData"), xmpData);
    descriptor2.putBoolean(s2t("includeEXIFData"), exifData);
    descriptor2.putBoolean(s2t("includePsExtras"), psData);
    descriptor.putObject(s2t("as"), s2t("WebPFormat"), descriptor2);
    descriptor.putPath(s2t("in"), WebPFile);
    descriptor.putBoolean(s2t("copy"), true); // asCopy
    descriptor.putBoolean(s2t("lowerCase"), true);
    executeAction(s2t("save"), descriptor, DialogModes.NO);
}

// Get available action sets
function getActionSets() {
    var actionSets = [];
    var i = 1;
    while (true) {
        try {
            var ref = new ActionReference();
            ref.putIndex(charIDToTypeID('ASet'), i);
            var desc = executeActionGet(ref);
            var name = desc.getString(charIDToTypeID('Nm  '));
            actionSets.push(name);
            i++;
        } catch (e) {
            break;
        }
    }
    return actionSets;
}

// Get actions from a specific action set
function getActions(actionSet) {
    var actions = [];
    var i = 1;
    while (true) {
        try {
            var ref = new ActionReference();
            ref.putIndex(charIDToTypeID('Actn'), i);
            ref.putName(charIDToTypeID('ASet'), actionSet);
            var desc = executeActionGet(ref);
            var actionName = desc.getString(charIDToTypeID('Nm  '));
            actions.push(actionName);
            i++;
        } catch (e) {
            break;
        }
    }
    return actions;
}

// Run the selected action
function runAction(actionSet, actionName) {
    var actionRef = new ActionReference();
    actionRef.putName(charIDToTypeID('Actn'), actionName);
    actionRef.putName(charIDToTypeID('ASet'), actionSet);
    var desc = new ActionDescriptor();
    desc.putReference(charIDToTypeID('null'), actionRef);
    executeAction(charIDToTypeID('Ply '), desc, DialogModes.NO);
}

 

https://prepression.blogspot.com/2017/11/downloading-and-installing-adobe-scripts.html

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines