Skip to main content
Inspiring
July 1, 2022
Answered

Remove DocumentAncestors recursively for all SmartObjects in a PSD

  • July 1, 2022
  • 4 replies
  • 4951 views

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 (JJMackKukurykus 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;
    }  
}

 

 

 

Correct answer m3zli

This is my final script, which could reduce a 800MB PSD to about 380MB.

@Stephen Marsh 

 

 

/*

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

*/

#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);
                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":
            return true;
  		default:  
			return false;
    }  
}

 

 

 

4 replies

Known Participant
August 19, 2022

exiftool can already do this: exiftool -r -overwrite_original -XMP-photoshop:DocumentAncestors= [filepath.psd]

Legend
August 19, 2022

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.

Known Participant
October 18, 2022

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.

m3zliAuthorCorrect answer
Inspiring
July 7, 2022

This is my final script, which could reduce a 800MB PSD to about 380MB.

@Stephen Marsh 

 

 

/*

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

*/

#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);
                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":
            return true;
  		default:  
			return false;
    }  
}

 

 

 

Stephen Marsh
Community Expert
Community Expert
July 8, 2022

Thank you for sharing, obviously the desire of cleaning this via an automated method outweighed the pain of creating the automation!

 

I'll take it for a test spin...

Stephen Marsh
Community Expert
Community Expert
July 10, 2022

@m3zli – Unfortunately this isnt working to remove all docAncestors metadata.

 

The test file can be downloaded here (116MB):

 

https://shared-assets.adobe.com/link/ffea3930-d725-4b46-4c74-e35a9fc6cccc

 

Stephen Marsh
Community Expert
Community Expert
July 3, 2022

@m3zli – Perhaps take a look at some of the scripts that I have collected on the subject:

 

https://prepression.blogspot.com/2017/06/metadata-bloat-photoshopdocumentancestors.html

 

m3zliAuthor
Inspiring
July 4, 2022

Thank you, I already read your article. But sadly the exiftool doesn't work or works different than I would like it to (only cleaning 13kb). I also checked the other options, that's where I got some of my code from. 

Stephen Marsh
Community Expert
Community Expert
July 4, 2022

@m3zli wrote:

Thank you, I already read your article. But sadly the exiftool doesn't work or works different than I would like it to (only cleaning 13kb). I also checked the other options, that's where I got some of my code from. 


 

That is correct, ExifTool only works with the parent file, not the embedded child smart object layer data.

 

I wrote a script to edit and remove the bloat from a smart object layer, presuming that it contains a single editable image that is not a smart object of a smart object etc (nested smart objects, Russian Doll). So I presume that you are looking to scrub the data from nested smart objects.

Legend
July 1, 2022
  1. Have you checked how this condition works?
    if( layerIsPsObject(layerID) )
    Is it possible that the fileReference does not contain the extension that you expect and as a result the changes in the document are not saved?
  2. Each time you open a smart object, a temporary file is created in the TEMP folder with the same name as the layer. It is not deleted until the main document is closed. If you have a lot of Smart Objects, opening each one in sequence can drastically reduce disk space. Try writing a function that gets the path of opened smart object and deletes it after the smart object is closed.
  3. I can't say what the problem is, but I noticed several times that when working with a large number of layers, some DOM objects suddenly become undefined. Perhaps this is due to the work of the garbage collector. I can assume that if you completely rewrite the code on ActionManager, then this will solve the problem.
m3zliAuthor
Inspiring
July 4, 2022

Thank you for your input! 

I will re-check the layerIsPsObject function and try to delete the created temporary files.

But how would I get started with the ActionManager and what are the benefits of using it?