exportdynamic.js
Modified: February 14, 2026 7:58 PM Category: Coding Notes List: Projects Created: November 18, 2025 8:37 AM Master Type: Notes Hide: No Starred: No Status: Unassigned
#target photoshop
// ---------------------------
// Get arguments from Bash/AppleScript
// arguments[0] = POS_DIR
// arguments[1] = NEG_DIR
// arguments[2] = INPUT_FOLDER
// ---------------------------
var posFolder = arguments[0];
var negFolder = arguments[1] || "";
var inputFolder = arguments[2];
if (!posFolder) { alert("❌ No output folder for Positives passed"); quit(); }
if (!inputFolder) { alert("❌ No input folder passed"); quit(); }
var folder = new Folder(inputFolder);
var allFiles = folder.getFiles("*.psd");
var files = [];
// Only push real, visible PSD files
for (var i = 0; i < allFiles.length; i++) {
var f = allFiles[i];
if (f instanceof File && f.exists && f.name[0] !== "." && !f.name.match(/^._/)) {
files.push(f);
}
}
app.displayDialogs = DialogModes.NO;
// ---------------------------
// Helpers
function pause(ms) { $.sleep(ms); }
function selectBaseLayer(doc) {
var layerToSelect = null;
for (var i = 0; i < doc.layers.length; i++) {
if (doc.layers[i].name === "L1" || doc.layers[i].name === "Background" || doc.layers[i].name === "Layer 0") {
layerToSelect = doc.layers[i];
break;
}
}
if (layerToSelect) { doc.activeLayer = layerToSelect; pause(150); }
else {
for (var i = 0; i < doc.layers.length; i++) {
if (doc.layers[i].kind == LayerKind.NORMAL) { doc.activeLayer = doc.layers[i]; pause(100); break; }
}
}
}
function hideAllExceptBottomLayer(doc, keepHueSat) {
var hiddenLayers = [];
function recurse(layerContainer, baseLayer) {
for (var i = 0; i < layerContainer.layers.length; i++) {
var lyr = layerContainer.layers[i];
if (lyr === baseLayer) continue;
if (keepHueSat && lyr.name.match(/^Hue\/Saturation \d+/)) continue;
if (lyr.typename === "ArtLayer" || lyr.typename === "LayerSet") {
if (lyr.visible) {
lyr.visible = false;
hiddenLayers.push(lyr);
}
}
if (lyr.typename === "LayerSet") recurse(lyr, baseLayer);
}
}
var baseLayer = null;
for (var i = doc.layers.length - 1; i >= 0; i--) {
var lyr = doc.layers[i];
if (lyr.visible) {
baseLayer = lyr;
break;
}
}
if (!baseLayer) {
alert("⚠️ No visible base layer found. Nothing will be kept visible.");
return hiddenLayers;
}
recurse(doc, baseLayer);
return hiddenLayers;
}
function hideHueSaturationLayers(doc) {
var hiddenLayers = [];
function recurse(layerContainer) {
for (var i = 0; i < layerContainer.layers.length; i++) {
var lyr = layerContainer.layers[i];
if (lyr.name.match(/^Hue\/Saturation \d+/) && lyr.visible) {
lyr.visible = false;
hiddenLayers.push(lyr);
}
if (lyr.typename === "LayerSet") recurse(lyr);
}
}
recurse(doc);
return hiddenLayers;
}
function restoreLayers(layers) {
for (var i = 0; i < layers.length; i++) {
try { layers[i].visible = true; } catch(e) {}
}
}
function saveMergedTIFF(doc, folderPath, suffix) {
try {
var tempDoc = doc.duplicate();
tempDoc.flatten();
try {
if (tempDoc.colorProfileName != "Adobe RGB (1998)") {
tempDoc.convertProfile("Adobe RGB (1998)", Intent.RELATIVECOLORIMETRIC, true, true);
pause(100);
}
} catch(e) {}
var outFolder = new Folder(folderPath);
if (!outFolder.exists) outFolder.create();
var saveName = doc.name.replace(/\.[^\.]+$/, "") + suffix + ".tif";
var saveFile = new File(folderPath + "/" + saveName);
var tiffOpts = new TiffSaveOptions();
tiffOpts.layers = false;
tiffOpts.embedColorProfile = true;
tiffOpts.byteOrder = ByteOrder.IBM;
tiffOpts.imageCompression = TIFFEncoding.TIFFLZW;
tiffOpts.alphaChannels = false;
tiffOpts.transparency = false;
tiffOpts.saveImagePyramid = false;
tempDoc.saveAs(saveFile, tiffOpts, true, Extension.LOWERCASE);
tempDoc.close(SaveOptions.DONOTSAVECHANGES);
pause(150);
} catch(e) { alert("❌ Failed to save TIFF: " + e); }
}
// ---------------------------
// Main batch loop
for (var i = 0; i < files.length; i++) {
var f = files[i];
if (!(f instanceof File)) continue;
try {
$.sleep(200);
app.open(f);
} catch(e) {
alert("⚠️ Skipping file, couldn't open: " + f.fullName + "\nError: " + e);
continue;
}
var doc = app.activeDocument;
try {
// Ensure RGB mode
if (doc.mode != DocumentMode.RGB) doc.changeMode(ChangeMode.RGB);
// Switch bit depth based on filename
if (f.name.match(/-small\.psd$/i)) {
if (doc.bitsPerChannel != BitsPerChannelType.EIGHT) {
doc.bitsPerChannel = BitsPerChannelType.EIGHT;
}
} else {
if (doc.bitsPerChannel != BitsPerChannelType.SIXTEEN) {
doc.bitsPerChannel = BitsPerChannelType.SIXTEEN;
}
}
pause(100);
selectBaseLayer(doc);
if (doc.name.match(/p_Processed\.psd$/i)) {
// Prints branch: hide all but base, but keep Hue/Saturation visible
var hiddenPrints = hideAllExceptBottomLayer(doc, true);
saveMergedTIFF(doc, posFolder, "");
restoreLayers(hiddenPrints);
} else {
// Digital Positive: hide only Hue/Saturation layers
var hiddenPos = hideHueSaturationLayers(doc);
saveMergedTIFF(doc, posFolder, "-A");
restoreLayers(hiddenPos);
// Digital Negative: hide all except base
if (negFolder) {
var hiddenNeg = hideAllExceptBottomLayer(doc, false);
saveMergedTIFF(doc, negFolder, "");
restoreLayers(hiddenNeg);
}
}
} catch(e) { alert("❌ Error processing " + doc.name + ": " + e); }
doc.close(SaveOptions.DONOTSAVECHANGES);
}