Copy link to clipboard
Copied
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;
}
}
This is my final script, which could reduce a 800MB PSD to about 380MB.
/*
Hacked together by m3zli
04.07.2022
Interesting posts, which helped me write this script:
https://community.adobe.com/t5/photoshop/inflated-jpg-file-size-photoshop-document-ancestors-metadata/m-p/8055434
https://feedback.photoshop.com/photoshop_family/topics/rasterize_all_smart_objects_in_a_psd_file_to_shrink_down_its_size
https://community.adobe.com/t5/photoshop-ecosystem-discussions/remove-document
...
Copy link to clipboard
Copied
@JuDrus wrote:
And what it does with linked files? Because 3 out of my 13 linked files disappeared completely from my computer... not from layers, but from my project "linked files" folder...
I ran a script and got this error:
After clicking OK, this popped up:
And after clicking this OK, I saw that there were red question marks on a couple of my layers, so I thought "oh... what? how did they linked off? ok I will relink them"... But when I tried to relink them, these files were no longer in my "link files" folder... I also checked Recycle Bin, but they were not there either...
So what happened and why did my linked files disappeared out of this world?
I'm glad that I tested it on my fresh project and it was only simple balloon files that I will be able to re-download, but if this would happen on a more complex file that was created from scratch, I would jump out of a window...
If you read this entire topic, then you'll know as much as I do!
I haven't tested with linked files or TIFF.
Copy link to clipboard
Copied
@m3zli Maybe you as creator could comment on this?
Copy link to clipboard
Copied
Hey,
I'm currently not maintaining this script, as I had a usecase for this nearly 2 years ago, but am not the one using it. Just from guessing it could probably be a problem with a newer version, as this script was tested on a veeery old CS 5 version. Sorry this wasn't helpful at all, but as said, I have no current usecase for now and won't look into this until I might face the same problem.
Copy link to clipboard
Copied
@JuDrus I don't know how much programming knowledge you have, but I think I can explain to you how to fix the problem. As I own no valid Photoshop license right now, I am not able to rewrite and test it myself, sorry.
I am currently not fully hands on the topic, but here's what I think happens:
- tiff/tif files seem to work different than psd or psb SmartObjects
- it looks like they don't create an overhead, like psd or psb files, when iterating(opening) into them (based on your screenshot)
- the tiff/tif file gets incorrectly removed, because that's the standard procedure for psd and psb files
- the tiff/tif file is now broken, because we deleted its content
maybe that helps, but I think I can do no more from here on (till I get a new PS license at least 😉 )
Copy link to clipboard
Copied
I have just taken a look at the code... I think that the answer is simple (I have performed limited testing). There is a "garbage collection" step that was intended to work with temp files for embedded smart objects, however, if the script is run on a linked smart object then the garbage collection deletes the linked file without recovery.
A simple, quick fix is to disable the offending step, changing the following line:
removePSB(path);
To:
//removePSB(path);
Another option would be to test if the smart object is linked, and if so skip the garbage collection.
Copy link to clipboard
Copied
Here is an edited version with a conditional check for the smart object being embedded. I have performed limited testing and this appears to stop linked smart objects being deleted from disk:
/*
https://community.adobe.com/t5/photoshop-ecosystem-discussions/remove-documentancestors-recursively-for-all-smartobjects-in-a-psd/td-p/13043605
Remove DocumentAncestors recursively for all SmartObjects in a PSD.jsx
Hacked together by m3zli
04.07.2022
Edited by Stephen Marsh
25th April 2024 - Conditional check for embedded smart object added to avoid deleting links smart object files
Interesting posts, which helped me write this script:
https://community.adobe.com/t5/photoshop/inflated-jpg-file-size-photoshop-document-ancestors-metadata/m-p/8055434
https://feedback.photoshop.com/photoshop_family/topics/rasterize_all_smart_objects_in_a_psd_file_to_shrink_down_its_size
https://community.adobe.com/t5/photoshop-ecosystem-discussions/remove-documentancestors-recursively-for-all-smartobjects-in-a-psd/m-p/13047853#M655448
https://prepression.blogspot.com/2017/06/metadata-bloat-photoshopdocumentancestors.html
https://community.adobe.com/t5/framemaker-discussions/directory-manipulation-as-part-of-an-extend-script/m-p/7822479
Adobe Photoshop CC JavaScript Reference 2020
*/
#target photoshop
var count = 0;
var doc = app.activeDocument;
doc.suspendHistory("Delete Ancestors Metadata", "processSOLayers(doc)");
function processSOLayers(theParent)
{
$.writeln("Entering Parent: " + theParent.name );
count++;
$.writeln( "Depth: " + count);
var soLayers = getSmartObjectLayers();
for (var m = 0; m < soLayers.length; m++)
{
var layerID = soLayers[m];
selectById(layerID);
if( layerIsPhotoshopObject(layerID) )
{
$.writeln("Opening SmartObject ID: " + layerID);
var smartObject = openSmartObject();
var path = smartObject.fullName;
if( smartObject.id !== theParent.id)
{
processSOLayers(smartObject);
deleteDocAncestors(smartObject);
smartObject.close(SaveOptions.SAVECHANGES);
$.writeln("Saved SmartObject ID: " + layerID);
// Garbage collection step modified to avoid deleting linked smart objects!
var ref = new ActionReference();
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
var layerDesc = executeActionGet(ref);
ref.putProperty(charIDToTypeID("Prpr"), stringIDToTypeID("smartObject"));
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
var so = executeActionGet(ref).getObjectValue(stringIDToTypeID("smartObject"));
var soMoreDesc = layerDesc.getObjectValue(stringIDToTypeID('smartObjectMore'));
if (so.getBoolean(stringIDToTypeID("linked")) === false) {
removePSB(path);
}
}
}
}
$.writeln("Exiting parent: " + theParent.name );
count--;
$.writeln( "Depth: " + count);
return;
}
deleteDocAncestors(doc);
doc.close(SaveOptions.SAVECHANGES);
function selectById(id)
{
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putIdentifier(charIDToTypeID('Lyr '), id);
desc.putReference(charIDToTypeID('null'), ref);
executeAction(charIDToTypeID('slct'), desc, DialogModes.NO);
}
function getSmartObjectLayers()
{
var ids = [];
var layers, desc, type, id;
try
{
activeDocument.backgroundLayer;
layers = 0;
}
catch (e)
{
layers = 1;
}
while (true)
{
ref = new ActionReference();
ref.putIndex(charIDToTypeID('Lyr '), layers);
try
{
desc = executeActionGet(ref);
}
catch (err)
{
break;
}
type = desc.getInteger(stringIDToTypeID("layerKind"));
id = desc.getInteger(stringIDToTypeID("layerID"));
if (type == 5 ) ids.push(id);
layers++;
}
return ids;
}
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 openSmartObject ()
{
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 removePSB(path)
{
var file = new File(path);
if(file.exists)
{
file.remove();
$.writeln("Removed file: " + path);
}
file.close();
}
function getSmartObjectFileExtension(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 layerIsPhotoshopObject(layerID)
{
var ext = getSmartObjectFileExtension(layerID);
$.writeln("Object with ID " + layerID + " Ext: " + ext);
switch (ext)
{
case "psb":
case "psd":
case "png":
return true;
default:
return false;
}
}
Copy link to clipboard
Copied
Hello, thank you for this amazing script, it works wonderful!!
Would you mind helping me edit this script to ignore Generative Fill layers? Currently for it to work i have to merge all generative fill layer :< It would be life saving for me!!
Copy link to clipboard
Copied
Hello, thank you for this amazing script, it works wonderful!!
Would you mind helping me edit this script to ignore Generative Fill layers? Currently for it to work i have to merge all generative fill layer :< It would be life saving for me!!
By @Kuro Kei
I have not tested this update, please test on a copy, just in case... use at your own risk! :]
Feedback is appreciated.
/*
https://community.adobe.com/t5/photoshop-ecosystem-discussions/remove-documentancestors-recursively-for-all-smartobjects-in-a-psd/td-p/13043605
Remove DocumentAncestors recursively for all SmartObjects in a PSD 16july2025.jsx
Hacked together by m3zli
04.07.2022
Interesting posts, which helped me write this script:
https://community.adobe.com/t5/photoshop/inflated-jpg-file-size-photoshop-document-ancestors-metadata/m-p/8055434
https://feedback.photoshop.com/photoshop_family/topics/rasterize_all_smart_objects_in_a_psd_file_to_shrink_down_its_size
https://community.adobe.com/t5/photoshop-ecosystem-discussions/remove-documentancestors-recursively-for-all-smartobjects-in-a-psd/m-p/13047853#M655448
https://prepression.blogspot.com/2017/06/metadata-bloat-photoshopdocumentancestors.html
https://community.adobe.com/t5/framemaker-discussions/directory-manipulation-as-part-of-an-extend-script/m-p/7822479
Adobe Photoshop CC JavaScript Reference 2020
Edited by Stephen Marsh
25th April 2024 - Conditional check for embedded smart object added to avoid deleting links smart object files
16th July 2025 - Conditional check for generative smart object added at request
*/
#target photoshop
var count = 0;
var doc = app.activeDocument;
doc.suspendHistory("Delete Ancestors Metadata", "processSOLayers(doc)");
function processSOLayers(theParent) {
$.writeln("Entering Parent: " + theParent.name);
count++;
$.writeln("Depth: " + count);
var soLayers = getSmartObjectLayers();
for (var m = 0; m < soLayers.length; m++) {
var layerID = soLayers[m];
selectById(layerID);
if (layerIsPhotoshopObject(layerID)) {
$.writeln("Opening SmartObject ID: " + layerID);
var smartObject = openSmartObject();
var path = smartObject.fullName;
if (smartObject.id !== theParent.id) {
processSOLayers(smartObject);
deleteDocAncestors(smartObject);
smartObject.close(SaveOptions.SAVECHANGES);
$.writeln("Saved SmartObject ID: " + layerID);
// Garbage collection step modified to avoid deleting linked smart objects!
var ref = new ActionReference();
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
var layerDesc = executeActionGet(ref);
ref.putProperty(charIDToTypeID("Prpr"), stringIDToTypeID("smartObject"));
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
var so = executeActionGet(ref).getObjectValue(stringIDToTypeID("smartObject"));
var soMoreDesc = layerDesc.getObjectValue(stringIDToTypeID('smartObjectMore'));
if (so.getBoolean(stringIDToTypeID("linked")) === false) {
removePSB(path);
}
// Garbage collection step modifed to avoid processing generative smart object layers
if (soMoreDesc.hasKey(stringIDToTypeID('generativeDocInfo')) === false) {
removePSB(path);
}
}
}
}
$.writeln("Exiting parent: " + theParent.name);
count--;
$.writeln("Depth: " + count);
return;
}
deleteDocAncestors(doc);
doc.close(SaveOptions.SAVECHANGES);
function selectById(id) {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putIdentifier(charIDToTypeID('Lyr '), id);
desc.putReference(charIDToTypeID('null'), ref);
executeAction(charIDToTypeID('slct'), desc, DialogModes.NO);
}
function getSmartObjectLayers() {
var ids = [];
var layers, desc, type, id;
try {
activeDocument.backgroundLayer;
layers = 0;
}
catch (e) {
layers = 1;
}
while (true) {
ref = new ActionReference();
ref.putIndex(charIDToTypeID('Lyr '), layers);
try {
desc = executeActionGet(ref);
}
catch (err) {
break;
}
type = desc.getInteger(stringIDToTypeID("layerKind"));
id = desc.getInteger(stringIDToTypeID("layerID"));
if (type == 5) ids.push(id);
layers++;
}
return ids;
}
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 openSmartObject() {
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 removePSB(path) {
var file = new File(path);
if (file.exists) {
file.remove();
$.writeln("Removed file: " + path);
}
file.close();
}
function getSmartObjectFileExtension(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 layerIsPhotoshopObject(layerID) {
var ext = getSmartObjectFileExtension(layerID);
$.writeln("Object with ID " + layerID + " Ext: " + ext);
switch (ext) {
case "psb":
case "psd":
case "png":
return true;
default:
return false;
}
}
Copy link to clipboard
Copied
exiftool can already do this: exiftool -r -overwrite_original -XMP-photoshop:DocumentAncestors= [filepath.psd]
Copy link to clipboard
Copied
No, it cannot.
This topic is about DocumentAncestors in smart objects nested in a document. Exiftool works only with the metadata block of the main document.
Copy link to clipboard
Copied
Ahh, that's unfortunate. Still, there is a workaround: make smart objects into linked PSB assets, run the exiftool on that, then re-embed them into the PSD. Not as elegant, but it would work.
Find more inspiration, events, and resources on the new Adobe Community
Explore Now