Copy link to clipboard
Copied
I have a workflow that I want to convert to a batch process. These are the steps:
Here is an example of my windows file system diectory:
Any help with this would be greatly appreciated!
@Deleted User
The following script will get you most of the way there. It currently saves in PSD. The image naming may also need tweaking. I don't have time now to make the tweaks, but as the script is 99% there anyway, it will not take too long to fine tune. I'll come back to you, however do you want save as JPEG or Export Save for Web and what options?
/*
Stack N Number of Docs to Layers.jsx
Stephen Marsh
20th August 2021 Version
A generic, skeleton "framework" script to help fast-trac
...
Copy link to clipboard
Copied
@Deleted User
The following script will get you most of the way there. It currently saves in PSD. The image naming may also need tweaking. I don't have time now to make the tweaks, but as the script is 99% there anyway, it will not take too long to fine tune. I'll come back to you, however do you want save as JPEG or Export Save for Web and what options?
/*
Stack N Number of Docs to Layers.jsx
Stephen Marsh
20th August 2021 Version
A generic, skeleton "framework" script to help fast-track development of similar scripts for combining multiple
"sequence" single-layer files to layers.
This script requires input files from a single folder to be alpha/numeric sorting in order to stack in the correct
set quantity.
Example: File-01.jpg File-02.jpg etc, FileA1.tif FileA2.tif etc, File1a.tif File1b.tif etc.
A minimum of 2 or more files per stack is required. The quantity of input files must be evenly divisible by the stack quantity.
A named action set and action can be set on line 135 to "do something" with the stacked layers.
*/
#target photoshop
// app.documents.length === 0
if (!app.documents.length) {
try {
// Save and disable dialogs
var restoreDialogMode = app.displayDialogs;
app.displayDialogs = DialogModes.NO;
// Main script function
(function () {
// Select the input folder
var inputFolder = Folder.selectDialog('Please select the folder with files to process');
if (inputFolder === null) return;
// Limit the file format input, add or remove as required
var fileList = inputFolder.getFiles(/\.(png|jpg|jpeg|tif|tiff|psd|psb)$/i);
// Force alpha-numeric list sort
// Use .reverse() for the first filename in the merged file
// Remove .reverse() for the last filename in the merged file
fileList.sort().reverse();
//////////////////////////// Static Set Quantity - No GUI ////////////////////////////
// var setQty = 2;
//////////////////////////////////////////////////////////////////////////////////////
// or...
//////////////////////////// Variable Set Quantity - GUI /////////////////////////////
// Loop the input prompt until a number is entered
var origInput;
while (isNaN(origInput = prompt('No. of files per set (minimum 2):', '2')));
// Test if cancel returns null, then terminate the script
if (origInput === null) {
alert('Script cancelled!');
return
}
// Test if an empty string is returned, then terminate the script
if (origInput === '') {
alert('A value was not entered, script cancelled!');
return
}
// Test if a value less than 2 is returned, then terminate the script
if (origInput < 2) {
alert('A value less than 2 was entered, script cancelled!');
return
}
// Convert decimal input to integer
var setQty = parseInt(origInput);
//////////////////////////////////////////////////////////////////////////////////////
// Validate that the file list is not empty
var inputCount = fileList.length;
var cancelScript1 = (inputCount === 0);
if (cancelScript1 === true) {
alert('Zero input files found, script cancelled!');
return;
}
// Validate the input count vs. output count - Thanks to Kukurykus for the advice to test using % modulus
var cancelScript2 = !(inputCount % setQty);
alert(inputCount + ' input files stacked into sets of ' + setQty + ' will produce ' + inputCount / setQty + ' output files.');
// Test if false, then terminate the script
if (cancelScript2 === false) {
alert('Script cancelled as the quantity of input files are not evenly divisible by the set quantity.');
return;
}
// Select the output folder
var outputFolder = Folder.selectDialog("Please select the folder to save to");
if (outputFolder === null) {
alert('Script cancelled!');
return;
}
// or
/*
// Create the output sub-directory
var outputFolder = Folder(decodeURI(inputFolder + '/Output Sets Folder'));
if (!outputFolder.exists) outputFolder.create();
*/
// Loop through and open the file sets
while (fileList.length) {
// Sets of N quantity files
for (var a = 0; a < setQty; a++) {
try {
app.open(fileList.pop());
} catch (e) { }
}
// Set the base doc layer name
app.activeDocument = documents[0];
docNameToLayerName();
// Stack all open docs to the base doc
while (app.documents.length > 1) {
app.activeDocument = documents[1];
docNameToLayerName();
app.activeDocument.activeLayer.duplicate(documents[0]);
app.activeDocument = documents[0];
// Do something to the stacked active layer (blend mode, opacity etc)
// app.activeDocument.activeLayer.blendMode = BlendMode.MULTIPLY;
app.documents[1].close(SaveOptions.DONOTSAVECHANGES);
}
////////////////////////////////// Start doing stuff //////////////////////////////////
// app.doAction("My Action", "My Action Set Folder");
/*
// Average stacked layers
app.runMenuItem(stringIDToTypeID("selectAllLayers"));
var idnewPlacedLayer = stringIDToTypeID("newPlacedLayer");
executeAction(idnewPlacedLayer, undefined, DialogModes.NO);
var idapplyImageStackPluginRenderer = stringIDToTypeID("applyImageStackPluginRenderer");
var desc1217 = new ActionDescriptor();
var idimageStackPlugin = stringIDToTypeID("imageStackPlugin");
var idavrg = charIDToTypeID("avrg");
desc1217.putClass(idimageStackPlugin, idavrg);
var idname = stringIDToTypeID("name");
desc1217.putString(idname, """Mean""");
executeAction(idapplyImageStackPluginRenderer, desc1217, DialogModes.NO);
*/
////////////////////////////////// Finish doing stuff //////////////////////////////////
// Delete XMP metadata to reduce final file size of output files
removeXMP();
// Save name + suffix & save path
var Name = app.activeDocument.name.replace(/\.[^\.]+$/, '');
var saveFile = File(outputFolder + '/' + Name + '_x' + setQty + '-Sets' + '.psd');
// var saveFile = File(outputFolder + '/' + Name + '_x' + setQty + '-Sets' + '.jpg');
// Call the save function
savePSD(saveFile);
//saveTIFF(saveFile);
//saveJPEG(saveFile);
//savePNG(saveFile);
// Close all open files without saving
while (app.documents.length) {
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
// Functions
function savePSD(saveFile) {
psdSaveOptions = new PhotoshopSaveOptions();
psdSaveOptions.embedColorProfile = true;
psdSaveOptions.alphaChannels = true;
psdSaveOptions.layers = true;
psdSaveOptions.annotations = true;
psdSaveOptions.spotColors = true;
// Save as
app.activeDocument.saveAs(saveFile, psdSaveOptions, true, Extension.LOWERCASE);
}
/* Not currently used, a placeholder to swap in/out as needed
function saveTIFF(saveFile) {
tiffSaveOptions = new TiffSaveOptions();
tiffSaveOptions.embedColorProfile = true;
tiffSaveOptions.byteOrder = ByteOrder.IBM;
tiffSaveOptions.transparency = true;
// Change layers to false to save without layers
tiffSaveOptions.layers = true;
tiffSaveOptions.layerCompression = LayerCompression.ZIP;
tiffSaveOptions.interleaveChannels = true;
tiffSaveOptions.alphaChannels = true;
tiffSaveOptions.annotations = true;
tiffSaveOptions.spotColors = true;
tiffSaveOptions.saveImagePyramid = false;
// Image compression = NONE | JPEG | TIFFLZW | TIFFZIP
tiffSaveOptions.imageCompression = TIFFEncoding.TIFFLZW;
// Save as
app.activeDocument.saveAs(saveFile, tiffSaveOptions, true, Extension.LOWERCASE);
}
*/
/* Not currently used, a placeholder to swap in/out as needed
function saveJPEG(saveFile) {
jpgSaveOptions = new JPEGSaveOptions();
jpgSaveOptions.embedColorProfile = true;
jpgSaveOptions.formatOptions = FormatOptions.STANDARDBASELINE;
jpgSaveOptions.matte = MatteType.NONE;
jpgSaveOptions.quality = 10;
// Save as
activeDocument.saveAs(saveFile, jpgSaveOptions, true, Extension.LOWERCASE);
}
*/
/* Not currently used, a placeholder to swap in/out as needed
function savePNG(saveFile) {
var pngOptions = new PNGSaveOptions();
pngOptions.compression = 0; // 0-9
pngOptions.interlaced = false;
// Save as
app.activeDocument.saveAs(saveFile, pngOptions, true, Extension.LOWERCASE);
}
*/
function docNameToLayerName() {
var layerName = app.activeDocument.name.replace(/\.[^\.]+$/, '');
app.activeDocument.activeLayer.name = layerName;
}
function removeXMP() {
if (!documents.length) return;
if (ExternalObject.AdobeXMPScript == undefined) ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
var xmp = new XMPMeta(activeDocument.xmpMetadata.rawData);
XMPUtils.removeProperties(xmp, "", "", XMPConst.REMOVE_ALL_PROPERTIES);
app.activeDocument.xmpMetadata.rawData = xmp.serialize();
}
}
// Restore saved dialogs
app.displayDialogs = restoreDialogMode;
// End of script notification
app.beep();
// Ensure that the following file format filter matches the save format
// .getFiles(/\.(tif|tiff)$/i);
var outputList = outputFolder.getFiles(/\.(psd)$/i);
// var outputList = outputFolder.getFiles(/\.(jpg|jpeg)$/i);
alert('Script completed!' + '\n' + outputList.length + ' combined files saved to:' + '\n' + outputFolder.fsName);
// Open the output folder in the Finder or Explorer
// outputFolder.execute();
}());
} catch (e) {
// Restore saved dialogs
app.displayDialogs = restoreDialogMode;
alert("If you see this message, something went wrong!");
}
}
else {
alert('Stack "N" Number of Sets:' + '\n' + 'Please close all open documents before running this script!');
}
EDIT: Original script updated 20th August 2021
Copy link to clipboard
Copied
Wow @Stephen_A_Marsh !!! That worked so great!
I did change the saved output to jpeg and it worked perfectly!
jpgSaveOptions = new JPEGSaveOptions();
jpgSaveOptions.embedColorProfile = true;
jpgSaveOptions.formatOptions = FormatOptions.STANDARDBASELINE;
jpgSaveOptions.matte = MatteType.NONE;
jpgSaveOptions.quality = 8;
app.activeDocument.saveAs(saveFile, jpgSaveOptions, true, Extension.LOWERCASE);
You just saved me a ton of time! Thank you soooo much!
Copy link to clipboard
Copied
@Deleted User
Sorry, I didn't see this reply when I posted the updated script. I'm glad you got there!
This type of question comes up so frequently, that I wanted to be able to create a basic skeleton/framework script that could be easily adapted for each specific request.
Copy link to clipboard
Copied
Photoshop Scripting will be required for automating a Photoshop process like you describe. Action can not hack a process that required logic to get file lists of image files in folders match pairs of files open pen pair and handles problems that may arise. Actions are a sequence of recorded Photoshop steps with recorded step settings. Photoshop Batch can open image files in a folder, play an action and save an output file. One image file at a time. Batch can not open pairs of files.
Copy link to clipboard
Copied
Copy link to clipboard
Copied
@Deleted User
As promised, I have now had time to complete the previous generic script into what you require.
This script will save as JPEG to quality level 10, you can easily change this in the code. It will also name the output directory as you require. The only thing you need to do is to change the action set and action name to match your naming (line 115: "My Action", "My Action Set Folder").
/*
Batch Processing: Load 2 files as layers, play action, export image, repeat for every "file pair"
https://community.adobe.com/t5/photoshop-ecosystem/batch-processing-load-2-files-as-layers-play-action-export-image-repeat-for-every-quot-file-pair/m-p/12317165#M573845
Stephen Marsh - 2021
Notes:
Stacks alpha-numeric sorting input files into pairs, runs an action, save as JPEG quality level 10
Change line 115 to reference your action set and action:
app.doAction("My Action", "My Action Set Folder");
*/
#target photoshop
if (app.documents.length === 0) {
try {
// Save and disable dialogs
var restoreDialogMode = app.displayDialogs;
app.displayDialogs = DialogModes.NO;
// Call the main script function
batchSetProcessing();
// Main script function
function batchSetProcessing() {
// Select the input folder
inputFolder = Folder.selectDialog('Please select the folder with files to process');
if (inputFolder === null) return;
// Limit the file format input, add or remove as required
var fileList = inputFolder.getFiles(/\.(png|jpg|jpeg|tif|tiff|psd|psb)$/i);
// Force alpha-numeric list sort
// Use .reverse() for the first filename in the merged file
// Remove .reverse() for the last filename in the merged file
fileList.sort().reverse();
//////////////////////////// Static Set Quantity - No GUI ////////////////////////////
var setQty = 2;
//////////////////////////////////////////////////////////////////////////////////////
// Validate that the file list is not empty
var inputCount = fileList.length;
var cancelScript1 = (inputCount === 0);
if (cancelScript1 === true) {
alert('Zero input files found, script cancelled!');
return;
}
// Validate the input count vs. output count
// Thanks to Kukurykus for the advice to test using % modulus
var cancelScript2 = !(inputCount % setQty);
alert(inputCount + ' input files stacked into sets of ' + setQty + ' will produce ' + inputCount / setQty + ' output files.');
// Test if false, then terminate the script
if (cancelScript2 === false) {
alert('Script cancelled as the quantity of input files are not evenly divisible by the set quantity.');
return;
}
// Create the output sub-directory
var outputFolder = Folder(decodeURI(inputFolder + '/ExportedJPEGs'));
if (!outputFolder.exists) outputFolder.create();
// Loop through and open the file sets
while (fileList.length) {
// Sets of N quantity files
for (var a = 0; a < setQty; a++) {
try {
app.open(fileList.pop());
} catch (e) { }
}
////////////////////////////////// Start doing stuff //////////////////////////////////
// Set the base doc layer name
app.activeDocument = documents[0];
docNameToLayerName();
// Stack all open docs to the base doc
while (app.documents.length > 1) {
app.activeDocument = documents[1];
docNameToLayerName();
app.activeDocument.activeLayer.duplicate(documents[0]);
app.activeDocument = documents[0];
// Do something to the active layer (blend mode, opacity etc)
// app.activeDocument.activeLayer.blendMode = BlendMode.MULTIPLY;
app.documents[1].close(SaveOptions.DONOTSAVECHANGES);
}
function docNameToLayerName() {
var layerName = app.activeDocument.name.replace(/\.[^\.]+$/, '');
app.activeDocument.activeLayer.name = layerName;
}
// Do something with the layer stack, either script or action based...
app.doAction("My Action", "My Action Set Folder");
// app.activeDocument.flatten();
////////////////////////////////// Finish doing stuff //////////////////////////////////
// https://prepression.blogspot.com/2017/06/metadata-bloat-photoshopdocumentancestors.html
deleteDocumentAncestorsMetadata();
function deleteDocumentAncestorsMetadata() {
// https://forums.adobe.com/message/8456985#8456985
if (ExternalObject.AdobeXMPScript === undefined) ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
var xmp = new XMPMeta(activeDocument.xmpMetadata.rawData);
xmp.deleteProperty(XMPConst.NS_PHOTOSHOP, "DocumentAncestors");
app.activeDocument.xmpMetadata.rawData = xmp.serialize();
}
// Save name + suffix & save path
var Name = app.activeDocument.name.replace(/\.[^\.]+$/, '');
var saveFile = File(outputFolder + '/' + Name + '_x' + setQty + '-Sets' + '.jpg');
// Call the JPEG save function
saveJPEG(saveFile);
function saveJPEG(saveFile) {
jpgSaveOptions = new JPEGSaveOptions();
jpgSaveOptions.embedColorProfile = true;
jpgSaveOptions.formatOptions = FormatOptions.STANDARDBASELINE;
jpgSaveOptions.matte = MatteType.NONE;
jpgSaveOptions.quality = 10;
// Save as
activeDocument.saveAs(saveFile, jpgSaveOptions, true, Extension.LOWERCASE);
}
// Close all open files without saving
while (app.documents.length) {
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
}
// Restore saved dialogs
app.displayDialogs = restoreDialogMode;
// End of script notification
// Ensure that the following file format filter matches the save format
var outputList = outputFolder.getFiles(/\.(jpg|jpeg)$/i);
alert('Script completed!' + '\n' + outputList.length + ' combined files saved to:' + '\n' + outputFolder.fsName);
// Open the output folder
outputFolder.execute();
}
} catch (e) {
// Restore saved dialogs
app.displayDialogs = restoreDialogMode;
alert("If you see this message, something went unexpectedly wrong!");
}
}
else {
alert('Stack Into Sets of 2:' + '\n' + 'Please close all open documents before running this script!');
}
Copy link to clipboard
Copied
Copy link to clipboard
Copied
You didn't read the instructions on how to correctly save the script file, you saved it as a Rich Text Document, not as a Plain Text File.
If these simple instructions are too abbreviated, you may need to read on...
Copy link to clipboard
Copied
P.S. This script is for file pairs from a single input folder.
You'll need a different script for 1 input file from 2 separate folders...