@Ian Jaffray
Here is an updated script to save each auto aligned layer as a PNG file using a suffix of "_ALIGNED". You can just change line 100 from:
var suffix = "_ALIGNED";
To:
var suffix = "";
To remove the suffix altogether (or change it to whatever you want within the quote marks).
I would generally advise that you work in smaller batches than 4K, perhaps just 1K (or fewer) input files at a time... But you might luck out and run all 4K input files in one go without hitting memory issues.
I'm using Save As for the PNG. The compression level is set on line 103. Resolution metadata is retained, however, depending on your version of Photoshop the ICC profile may not be retained (bug). If needed, the Save As code could be swapped out to use Save for Web instead, however, that would strip the resolution metadata but it would retain the ICC profile.
New code replacing the previous script:
/*
Auto Align Image Pair Layers to PNG.jsx
https://community.adobe.com/t5/photoshop-ecosystem-discussions/is-scripting-the-only-way-to-align-about-4000-image-pairs/td-p/14244613
v1.0 - 20th November 2023, Stephen Marsh
Based on:
Stack N Number of Document Sets to Layers - Top Level Folder.jsx
20th January 2023 Version, Stephen Marsh
*/
#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|webp)$/i);
// Force alpha-numeric list sort
// Use fileList.sort().reverse(); for the first filename in the merged file
fileList.sort();
// Static Set Quantity
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);
// Test if false, then terminate the script
if (cancelScript2 === false) {
alert('Script cancelled as the quantity of input files are not evenly divisible by 2.');
return;
}
// Select the output folder
var outputFolder = Folder.selectDialog("Please select the folder to save to");
if (outputFolder === null) {
alert('Script cancelled!');
return;
}
// Set the file processing counter
var fileCounter = 0;
// 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];
app.runMenuItem(stringIDToTypeID('selectAllLayers'));
autoAlign();
app.documents[1].close(SaveOptions.DONOTSAVECHANGES);
}
// Delete XMP metadata to reduce final file size of output files
//removeXMP();
// Loop over layers and save to PNG
for (var i = 0; i < app.activeDocument.layers.length; i++) {
for (var j = 0; j < app.activeDocument.layers.length; j++) {
app.activeDocument.layers[j].visible = false;
}
var layerIndex = i;
app.activeDocument.layers[layerIndex].visible = true;
var layerName = app.activeDocument.layers[layerIndex].name;
var suffix = "_ALIGNED";
var file = new File(outputFolder + "/" + layerName + suffix + ".png");
var saveOptions = new PNGSaveOptions();
saveOptions.compression = 0; // 0-9
saveOptions.interlaced = false;
app.activeDocument.saveAs(file, saveOptions, true, Extension.LOWERCASE);
// Increment the file saving counter
fileCounter++;
}
// Close all open files without saving
while (app.documents.length) {
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
///// Functions /////
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();
}
function autoAlign() {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putEnumerated(charIDToTypeID('Lyr '), charIDToTypeID('Ordn'), charIDToTypeID('Trgt'));
desc.putReference(charIDToTypeID('null'), ref);
desc.putEnumerated(charIDToTypeID('Usng'), charIDToTypeID('ADSt'), stringIDToTypeID('ADSContent'));
desc.putEnumerated(charIDToTypeID('Aply'), stringIDToTypeID('projection'), charIDToTypeID('Auto'));
desc.putBoolean(stringIDToTypeID('vignette'), false);
desc.putBoolean(stringIDToTypeID('radialDistort'), false);
executeAction(charIDToTypeID('Algn'), desc, DialogModes.NO);
}
}
// Restore saved dialogs
app.displayDialogs = restoreDialogMode;
// End of script notification
app.beep();
alert('Script completed!' + '\n' + fileCounter + ' PNG files aligned and 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!" + "\r" + e + ' ' + e.line);
}
}
else {
alert('Auto Align Image Pair Layers to PNG:' + '\n' + 'Please close all open documents before running this script!');
}