What's new

Read / Write metadata to image / layer

#1
Good Evening All,

This is my first post here. I am a very well versed python programmer, but not quite as competent in photoshop (yet). I am looking to batch read/write basic metadata (a ~5-25 character string, for example) to either

1) a photoshop file
2) a specific layer in a photoshop file

Ideally, this metadata string would not be visible when opening the layer / file in the program, but if it is and is not too obtrusive or easy to edit/overwrite accidentally, that is fine. From what I gather from the specification, this should be possible. I have first tried to do it with tagged blocks using the 'psd-tools' library, but I can not seem to find a layer-writing function approach that does not product an error when opening the file in PS. I am also fine with creating my own parser for the photoshop file or using some kind of built in batch mode / cmdline call if it exists.

If anyone has any general pointers or keywords to look for to start to accomplish this, please let me know if you have a chance!

Thanks for reading
 

Paul MR

The Script Master
#3
Metadata can be added to the file without open it in Photoshop, but not to a layer. To add metadata to a layer the file must be opened in Photoshop.
All these can be accomplished with Extendscript. Using Python or any other COM you might have some difficulties.
 
#4
Metadata can be added to the file without open it in Photoshop, but not to a layer. To add metadata to a layer the file must be opened in Photoshop.
All these can be accomplished with Extendscript. Using Python or any other COM you might have some difficulties.
Hi Paul MR, thanks for that. I guess before trying to automate it I would like to see how to just do it manually for the layers, and perhaps try to write my own parser for these purposes if it is not supported. Batch running Photoshop every time might be very time intensive. I have trouble even finding how to manually add metadata or XMP data to a specific layer, though I have seen some very old articles that describe that it is possible, such as this: http://blogs.adobe.com/jnack/2008/10/per-layer_metadata_comes_to_photoshop.html . Does anyone know if it is possible to do with a simple script on in the interface?
 

Paul MR

The Script Master
#5
Here is an example of setting per layer metadata.
N.B. To use per layer metadata it must be the active layer!
It uses an external library
Code:
setCommentMetadata("This is a comment");

function setCommentMetadata(comm){
if (ExternalObject.AdobeXMPScript == undefined) ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript"); 
if( app.activeDocument.activeLayer.isBackgroundLayer){
          alert( "Can not place metadata on a background layer." );
          return;
     }               
 try{
xmp = new XMPMeta( app.activeDocument.activeLayer.xmpMetadata.rawData );
    } catch( e ) {
    xmp = new XMPMeta();
    }
try{
    xmp.setProperty( XMPConst.NS_EXIF, "userComment", comm );
    } catch( e ) {
alert( "Unable to place metadata on selected layer.\n" + e );
          }        
    app.activeDocument.activeLayer.xmpMetadata.rawData = xmp.serialize();
};
There is not much difference to get/set of the documents xmp
Code:
if (ExternalObject.AdobeXMPScript == undefined) ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");     
var xmp = new XMPMeta(activeDocument.xmpMetadata.rawData);       
xmp.deleteProperty(XMPConst.NS_XMP, "Label");   
xmp.setProperty(XMPConst.NS_XMP, "Label","Approved");  
app.activeDocument.xmpMetadata.rawData = xmp.serialize();
In Photoshop there are a few fields that you can get/set via DOM
Code:
app.activeDocument.info.author
app.activeDocument.info.caption
app.activeDocument.info.captionWriter
app.activeDocument.info.headline
app.activeDocument.info.instructions
app.activeDocument.info.keywords
app.activeDocument.info.author
app.activeDocument.info.authorPosition
app.activeDocument.info.credit
app.activeDocument.info.source
app.activeDocument.info.category
app.activeDocument.info.supplementalCategories
app.activeDocument.info.title
app.activeDocument.info.creationDate
app.activeDocument.info.city
app.activeDocument.info.provinceState
app.activeDocument.info.country
app.activeDocument.info.transmissionReference
app.activeDocument.info.copyrightNotice
app.activeDocument.info.ownerUrl
//Note keywords is an array
app.activeDocument.info.keywords = ["Keyword1","Keyword 2","Etc"];
You can access any schema in the document or layer metadata, they are the same. You can also create you own schema, as an example see this old script of mine:-
https://raw.githubusercontent.com/Paul-Riggott/PS-Scripts/master/GuideSets.jsx

Let me know if you want details of get/set without opening a file.
All these methods use the External Library.
You might be able to invoke the library by doing a "doJavaScript" function if it exists in Python?
 
#6
Hi Paul_MR,

I really appreciate the starting reference - it is more than I could have asked for. I will take quite a while to go over all of this and see if I can produce something more comprehensive.

As an aside, I have been using one of the few python file parsers to try to input some small extra data to a specific layer. For example this one: https://psd-tools.readthedocs.io/en/latest/ While doing more research into this, I have found that there seems to be a 'layer_id' tagged block to use (key "lyid"). As the metadata I needed was to store a unique ID anyway, I was wondering if it would be acceptable to write my own ID into this field? So far it seems to work for me but I am not too familiar with PSD internals to know if this might cause some other ill effect down the road.

Thanks very much again, I will definitely be making use of some of your samples!
 

Paul MR

The Script Master
#7
Layer ID's are unique to each layer and amending this field could cause problems.
Code:
alert(activeDocument.activeLayer.id);
There is a layer metadata field that holds the time of the last update of the layer I.E.
Code:
alert(layerTime());

function layerTime(){
var ref = new ActionReference();
ref.putProperty(charIDToTypeID("Prpr"),stringIDToTypeID("metadata"));
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") ); 
var lt = executeActionGet(ref).getObjectValue(stringIDToTypeID("metadata")).getDouble(stringIDToTypeID("layerTime"));//seconds
return new Date(lt*1000.0).toLocaleString(); //change to milliseconds
};
Good luck with your project.
 

Paul MR

The Script Master
#8
A bit more information that might help you.

This code will dump the xmp details of a document in Photoshop to the Desktop, this will show the different metadata formats that you would need to populate in the correct format.
Code:
#target photoshop;
if(documents.length){
var xmpInfo = new File(Folder.desktop + "/xmpInfo.txt");
if (ExternalObject.AdobeXMPScript == undefined) ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");     
var xmp = new XMPMeta(activeDocument.xmpMetadata.rawData);  
xmpInfo.open("w");
xmpInfo.encoding="UTF-8";
xmpInfo.writeln(xmp.dumpObject());
xmpInfo.close();
};
If you have Extendscript Toolkit installed, look under "Help" and you will find "Javascript Tools Guide CC" that covers XMP

Here is a Bridge script that will create a jpg and populate it with IPTC fields.
Code:
#target bridge;
if (ExternalObject.AdobeXMPScript == undefined) ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
var jpeg = new File(Folder.desktop+ "/TestMe.jpg" );
var bitmap = new BitmapData( 100, 100 );
app.syncronousMode = true;
bitmap.exportTo( jpeg );
app.syncronousMode = false;
var xmp = new XMPMeta();
try {
     xmp.appendArrayItem( XMPConst.NS_DC, "creator", "Creator", 0, XMPConst.ARRAY_IS_ORDERED );
     xmp.setLocalizedText( XMPConst.NS_DC, "title", null, "en-US", "Title" );
     xmp.setProperty(XMPConst.NS_PHOTOSHOP, "AuthorsPosition", "Author Position");
     xmp.setProperty(XMPConst.NS_PHOTOSHOP, "Headline", "Headline");
     xmp.setLocalizedText( XMPConst.NS_DC, "description", null, "x-default", "Caption/Description" ); 
     xmp.setProperty(XMPConst.NS_PHOTOSHOP, "DateCreated", new XMPDateTime(new Date()), XMPConst.XMPDATE);
     var Keys=["Keyword1","Keyword2","Etc."];
     for(var s in Keys){
     xmp.appendArrayItem(XMPConst.NS_DC, "subject", Keys[s], 0,XMPConst.PROP_IS_ARRAY);
      }
     xmp.setStructField( XMPConst.NS_IPTC_CORE, "CreatorContactInfo", XMPConst.NS_IPTC_CORE, "CiAdrCtry", "England" );
     xmp.setStructField( XMPConst.NS_IPTC_CORE, "CreatorContactInfo", XMPConst.NS_IPTC_CORE, "CiEmailWork", "Joe.Bloggs@email.com" );
     xmp.setStructField( XMPConst.NS_IPTC_CORE, "CreatorContactInfo", XMPConst.NS_IPTC_CORE, "CiAdrCity","Leeds");
     xmp.setStructField( XMPConst.NS_IPTC_CORE, "CreatorContactInfo", XMPConst.NS_IPTC_CORE, "CiAdrExtadr","Address");
     xmp.setStructField( XMPConst.NS_IPTC_CORE, "CreatorContactInfo", XMPConst.NS_IPTC_CORE, "CiAdrPcode","LS12PP");
     xmp.setStructField( XMPConst.NS_IPTC_CORE, "CreatorContactInfo", XMPConst.NS_IPTC_CORE, "CiAdrRegion","West Yorkshire");
     xmp.setStructField( XMPConst.NS_IPTC_CORE, "CreatorContactInfo", XMPConst.NS_IPTC_CORE, "CiTelWork","0132999999");
     xmp.setStructField( XMPConst.NS_IPTC_CORE, "CreatorContactInfo", XMPConst.NS_IPTC_CORE, "CiUrlWork","www.mysite.co.uk");
     xmp.setProperty( XMPConst.NS_IPTC_CORE, "Iptc4xmpCore:Location","Sub Location");
     xmp.setProperty( XMPConst.NS_IPTC_CORE, "Iptc4xmpCore:IntellectualGenre","Itellectual Genre");
     xmp.appendArrayItem( XMPConst.NS_IPTC_CORE, "Iptc4xmpCore:SubjectCode","IPTC Subject Code",0,XMPConst.PROP_IS_ARRAY);
     xmp.appendArrayItem( XMPConst.NS_IPTC_CORE, "Iptc4xmpCore:Scene","IPTC Scene Code",0,XMPConst.PROP_IS_ARRAY);
     xmp.setProperty( XMPConst.NS_IPTC_CORE, "Iptc4xmpCore:CountryCode", "ISO Country Code" );
     xmp.setProperty(XMPConst.NS_PHOTOSHOP, "City", "London");
     xmp.setProperty(XMPConst.NS_PHOTOSHOP, "State", "State/Province");
     xmp.setProperty(XMPConst.NS_PHOTOSHOP, "Country", "Britain");
     xmp.setProperty(XMPConst.NS_PHOTOSHOP, "CaptionWriter", "Description Writer");
     xmp.setProperty(XMPConst.NS_PHOTOSHOP, "TransmissionReference", "Job Identifier");
     xmp.setProperty(XMPConst.NS_PHOTOSHOP, "Instructions", "Instructions");
     xmp.setProperty(XMPConst.NS_PHOTOSHOP, "Credit", "Credit Line");
     xmp.setProperty(XMPConst.NS_PHOTOSHOP, "Source", "Source");
     xmp.setLocalizedText( XMPConst.NS_DC, "rights", null, "x-default", "Copyright Notice" ); 
     xmp.appendArrayItem( XMPConst.NS_XMP_RIGHTS, "UsageTerms","Rights Usage",0,XMPConst.PROP_IS_ARRAY);
     var xmpFile = new XMPFile( jpeg.fsName, XMPConst.FILE_UNKNOWN, XMPConst.OPEN_FOR_UPDATE | XMPConst.OPEN_USE_SMART_HANDLER );
     if( xmpFile.canPutXMP( xmp ) )
     {
               xmpFile.putXMP(xmp);
     }
     xmpFile.closeFile(XMPConst.CLOSE_UPDATE_SAFELY);
} catch( e ) {
          $.writeln("ERROR SETTING METADATA: \n" + e + "\n"+e.line );
}
 

Top