Remove DocumentAncestors recursively for all SmartObjects in a PSD
I am currently working on a script that can clean the DocumentAncestors in a .psd file.
Since there are SmartObjects in the SmartObjects of the PSD, I am current trying to recursively iterate trough all layers, check if a layer is a SmartObject and if so, iterate into it and check if any further SmartObjects exist. If no more SmartObjects exist, the script starts to delete the DocumentAncestors recursively.
The problems:
- it seems to iterate correctly through all the layers, but the removal not always seems to work, since there are many DocumentAncestor entries left in the PSD afterwards
- the procedure is also very heavy on the woking volumes. I am currently trying to remove the Ancestors of a 800MB PSD and the working volumes need >150GB of space
- after about 5-10 min, the variable which holds the current SmartObject becomes invalid
The code implements several different little scripts and parts of users found on here (JJMack, Kukurykus just to name a few)
#target photoshop
var count = 0;
const doneIDs = [];
var doc = app.activeDocument;
doc.suspendHistory("Delete Ancestors Metadata", "processSOLayers(doc)");
function processSOLayers(theParent)
{
$.writeln("Entering Parent: " + theParent.name );
count++;
$.writeln( "Depth: " + count);
for (var m = 0; m < theParent.layers.length; m++)
{
var theLayer = theParent.layers[m];
if (theLayer.typename === "ArtLayer")
{
if (theLayer.kind === LayerKind.SMARTOBJECT)
{
var theVisibility = theLayer.visible;
app.activeDocument.activeLayer = theLayer;
var layerID = getLayerId();
if( layerIsPsObject(layerID) )
{
$.writeln("Opening SmartObject: " + theLayer.name);
var smartObject = openSmartObject(theLayer);
if( smartObject.id !== theParent.id && !isAlreadyDone(smartObject.id))
{
processSOLayers(smartObject);
deleteDocAncestors(smartObject);
smartObject.close(SaveOptions.SAVECHANGES);
$.writeln("Saved SmartObject: " + theLayer.name + " ID: " + theLayer.id);
}
else
{
smartObject.close(SaveOptions.DONOTSAVECHANGES);
}
}
theLayer.visible = theVisibility;
}
//alert("SO doc cleaned");
}
else
{
//Layerset
processSOLayers(theLayer);
$.writeln("Exiting LayerSet: " + theLayer.name );
}
}
$.writeln("Exiting parent: " + theParent.name );
count--;
$.writeln( "Depth: " + count);
return;
}
deleteDocAncestors(doc);
//alert("Parent doc cleaned");
function deleteDocAncestors(document)
{
if (ExternalObject.AdobeXMPScript === undefined) ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
var xmp = new XMPMeta(document.xmpMetadata.rawData);
xmp.deleteProperty(XMPConst.NS_PHOTOSHOP, "DocumentAncestors");
document.xmpMetadata.rawData = xmp.serialize();
$.writeln("Cleaned Ancestors...")
}
function isAlreadyDone(id)
{
for( var i = 0; i < doneIDs.length; ++i)
{
if( doneIDs[i] === id)
return true;
}
doneIDs.push(id);
return false;
}
function getLayerId()
{
var ref = new ActionReference();
ref.putEnumerated(charIDToTypeID('Lyr '), charIDToTypeID('Ordn'), charIDToTypeID('Trgt')); // reference is active layer
var layerDesc = executeActionGet(ref);
var layerID = layerDesc.getInteger(stringIDToTypeID('layerID'));
return layerID;
}
function openSmartObject (theLayer)
{
try
{
var idplacedLayerEditContents = stringIDToTypeID( "placedLayerEditContents" );
var desc2 = new ActionDescriptor();
executeAction( idplacedLayerEditContents, desc2, DialogModes.NO );
return app.activeDocument;
}
catch(e)
{
alert(e.name + " " + e.message);
return undefined;
}
};
function smartobject_file_ext(id)
{
try
{
var r = new ActionReference();
r.putProperty(stringIDToTypeID("property"), stringIDToTypeID("smartObject"));
r.putIdentifier(stringIDToTypeID("layer"), id);
var name = executeActionGet(r).getObjectValue(stringIDToTypeID("smartObject")).getString(stringIDToTypeID("fileReference"));
var n = name.lastIndexOf(".");
if (n < 0) return "";
return name.substr(n+1).toLowerCase();
}
catch (e)
{
$.writeln(e);
return "error";
}
}
function layerIsPsObject(layerID)
{
var ext = smartobject_file_ext(layerID);
switch (ext)
{
case "psb":
case "psd":
return true;
default:
return false;
}
}
