Exit
  • Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
  • 한국 커뮤니티
0

Remove DocumentAncestors recursively for all SmartObjects in a PSD

Community Beginner ,
Jul 01, 2022 Jul 01, 2022

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

 

 

 

TOPICS
Actions and scripting , Windows
4.6K
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Community Beginner , Jul 07, 2022 Jul 07, 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-document
...
Translate
Adobe
Mentor ,
Jul 01, 2022 Jul 01, 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.
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Jul 03, 2022 Jul 03, 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?

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 02, 2022 Jul 02, 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

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Jul 03, 2022 Jul 03, 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. 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 04, 2022 Jul 04, 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.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Jul 04, 2022 Jul 04, 2022

Exactly. I am searching for a way to remove the DocumentAncestors for nested smart objects. 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 04, 2022 Jul 04, 2022

@m3zli wrote:

Exactly. I am searching for a way to remove the DocumentAncestors for nested smart objects. 


 

I know, why do you think that I stopped at the 1st level? That is a potential minefield that I didn't wish to enter! The loops, the checks, the ever-increasing drive space and memory issues... 

 

Unless I had a really, really strong need to automate, I'd just suck it up and use a keyboard shortcut to a script or action that will remove ancestors and close/save... Then manually go down that rabbit hole into Wonderland! For me, it would be less painful than trying to "do it right" with a fully automated script.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Jul 04, 2022 Jul 04, 2022

The problems with dirve space and memory are real... I just found a solution which might work as I expect it to. 

Will post it after some testing. Thanks for your input!

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Jul 07, 2022 Jul 07, 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;
    }  
}

 

 

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 08, 2022 Jul 08, 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...

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 09, 2022 Jul 09, 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

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Mentor ,
Jul 10, 2022 Jul 10, 2022

The script takes into account only embedded layers with a 'psd' or 'psb' source file format.

As I understand it, the purpose of such a restriction is to avoid opening embedded smart objects by an external program.

I think that @m3zli should expand the list of valid file extensions in order to make the script more universal.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 10, 2022 Jul 10, 2022

I didn't check the code, some of my test files were .png so that is what I used without thinking this through. Once I added a .png extension all the data was cleaned.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Jul 10, 2022 Jul 10, 2022

I had the list expanded before, but didn't need to clean anything besides the PSDs and PSBs. But you're right, for others and for general use, the list of extensions should be expanded.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 11, 2022 Jul 11, 2022

@m3zli – I have marked your script as a correct answer and have updated my blogpost to include the link. If you have a repo or other location where the script will be kept or maintained please post the link and I'll update my blogpost. Thank you for contributing!

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Aug 20, 2023 Aug 20, 2023

Hi, @m3zli , How exactly to run the script in PS? I have a huge problem with some PSD files with same problem right now.

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 20, 2023 Aug 20, 2023
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Aug 20, 2023 Aug 20, 2023

@m3zli You saved my life today and really do not know how much to thank you for the scrip you provided! Too long to explain what I was into the last month and how I struggled with work till I find the problem and then resolved it with your script. I have inherited many files in my new job, and it seems all of them were affected by excessive DocumentAncestors bloating metadata. At the same time I was transferring it through smart objects into new files I created and it was a pain infinite. Started to upgrade my computer, bought 64GB more RAM, installed new clean Windows on a new drive, changed video card and many other things.....I can't believe how this small issue can affect someone's work and even me personally. Thank you ,thank you!!!

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Nov 11, 2023 Nov 11, 2023

@StoyanovI'm glad i could help. Would have never thought this could be of use to someone 🙂

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Mar 25, 2024 Mar 25, 2024

Does this work on TIF files or only on PSD and PSB? If it only works on PSD and PSB, then maybe there's an updated version of this? Because it is 2 years old

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Mar 25, 2024 Mar 25, 2024
quote

Does this work on TIF files or only on PSD and PSB? If it only works on PSD and PSB, then maybe there's an updated version of this? Because it is 2 years old


By @JuDrus


Age has nothing to do with it. The script works on smart object layers. The SO has to be readable by Photoshop. There is nothing in the code excluding TIFF files (unless the SO is a TIFF file). Have you had problems processing TIFF files vs. PSD or PSB?

 

EDIT: You can update the following function to include TIF/TIFF extensions:

 

function layerIsPhotoshopObject(layerID) 
{
	var ext = getSmartObjectFileExtension(layerID);  
    $.writeln("Object with ID " + layerID + " Ext: " + ext);
	switch (ext)  
		{  
        case "psb":
        case "psd":
        case "tif":
        case "tiff":
            return true;
  		default:  
			return false;
    }  
}

 

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Mar 25, 2024 Mar 25, 2024

After a new Photoshop came out (AI thingy), I thought maybe the script is no longer supported, but good to know that age has nothing to do with it.

 

About my TIF question... I always save my project files as TIF instead of PSD, so that is why I asked if it works with it.

 

So if I understand correctly, this script checks everything in the opened Photoshop file and doesn't matter what kind of file it is?

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Mar 25, 2024 Mar 25, 2024

The original script only processed embedded PSD and PSB files.

 

I posted an example of adding TIF and TIFF extensions to part of the code.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Mar 26, 2024 Mar 26, 2024

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:

2024-03-26 10-56-14.png

 

After clicking OK, this popped up:

2024-03-26 10-56-23.png

 

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...

2024-03-26 11-05-16.png

 

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...

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines