Skip to main content
Known Participant
November 17, 2022
Answered

Convert all notes/annotations to text layers in Photoshop scripting

  • November 17, 2022
  • 2 replies
  • 872 views

Hey! 

Does anyone have an existing script for converting all notes/annotations to text layers?
It can be all notes to a single text layer or multiple.
I currently have a script to select all text layers, send them to a group, search their contents for keywords, and create a txt file if they contain the keywords. I am looking to add a snippet of code to the top of this script in order to cover the off chance a note/annotation was used instead of a text layer.

Looking through the Adobe Javascript Ref 2020 document, I did not see anything in the native DOM for adobe, so I am assuming this will be an AM event. 

If it helps, here is the code for creating a note I copied from ScriptListener/Clean SL. 

select3(true, true);
function select3(dontRecord, forceNotify) {
	var c2t = function (s) {
		return app.charIDToTypeID(s);
	};

	var s2t = function (s) {
		return app.stringIDToTypeID(s);
	};

	var descriptor = new ActionDescriptor();
	var reference = new ActionReference();

	reference.putClass( s2t( "textAnnotTool" ));
	descriptor.putReference( c2t( "null" ), reference );
	descriptor.putBoolean( s2t( "dontRecord" ), dontRecord );
	descriptor.putBoolean( s2t( "forceNotify" ), forceNotify );
	executeAction( s2t( "select" ), descriptor, DialogModes.NO );
}

// =======================================================
historyStateChanged2(645, "New Note", true, 2);
function historyStateChanged2(ID, name2, hasEnglish, itemIndex) {
	var s2t = function (s) {
		return app.stringIDToTypeID(s);
	};

	var descriptor = new ActionDescriptor();

	descriptor.putInteger( s2t( "documentID" ), 621 );
	descriptor.putInteger( s2t( "ID" ), ID );
	descriptor.putString( s2t( "name" ), name2 );
	descriptor.putBoolean( s2t( "hasEnglish" ), hasEnglish );
	descriptor.putInteger( s2t( "itemIndex" ), itemIndex );
	executeAction( s2t( "historyStateChanged" ), descriptor, DialogModes.NO );
}

// =======================================================
make(521, 659, 240, 140);
function make(horizontal, vertical, horizontal2, vertical2) {
	var c2t = function (s) {
		return app.charIDToTypeID(s);
	};

	var s2t = function (s) {
		return app.stringIDToTypeID(s);
	};

	var descriptor = new ActionDescriptor();
	var descriptor2 = new ActionDescriptor();
	var descriptor3 = new ActionDescriptor();
	var descriptor4 = new ActionDescriptor();
	var reference = new ActionReference();

	reference.putClass( s2t( "annotation" ));
	descriptor.putReference( c2t( "null" ), reference );
	descriptor3.putUnitDouble( s2t( "horizontal" ), s2t( "pixelsUnit" ), horizontal );
	descriptor3.putUnitDouble( s2t( "vertical" ), s2t( "pixelsUnit" ), vertical );
	descriptor2.putObject( s2t( "location" ), c2t( "Pnt " ), descriptor3 );
	descriptor4.putUnitDouble( s2t( "horizontal" ), s2t( "pixelsUnit" ), horizontal2 );
	descriptor4.putUnitDouble( s2t( "vertical" ), s2t( "pixelsUnit" ), vertical2 );
	descriptor2.putObject( s2t( "size" ), s2t( "offset" ), descriptor4 );
	descriptor2.putEnumerated( s2t( "annotType" ), s2t( "annotType" ), s2t( "annotText" ));
	descriptor.putObject( s2t( "using" ), s2t( "annotation" ), descriptor2 );
	executeAction( s2t( "make" ), descriptor, DialogModes.NO );
}

// =======================================================
historyStateChanged3(646, "Edit Note", true, 3);
function historyStateChanged3(ID, name2, hasEnglish, itemIndex) {
	var s2t = function (s) {
		return app.stringIDToTypeID(s);
	};

	var descriptor = new ActionDescriptor();

	descriptor.putInteger( s2t( "documentID" ), 621 );
	descriptor.putInteger( s2t( "ID" ), ID );
	descriptor.putString( s2t( "name" ), name2 );
	descriptor.putBoolean( s2t( "hasEnglish" ), hasEnglish );
	descriptor.putInteger( s2t( "itemIndex" ), itemIndex );
	executeAction( s2t( "historyStateChanged" ), descriptor, DialogModes.NO );
}

// =======================================================
set("TEXT\r");
function set(text) {
	var c2t = function (s) {
		return app.charIDToTypeID(s);
	};

	var s2t = function (s) {
		return app.stringIDToTypeID(s);
	};

	var descriptor = new ActionDescriptor();
	var descriptor2 = new ActionDescriptor();
	var reference = new ActionReference();

	reference.putIndex( s2t( "annotation" ), 0 );
	descriptor.putReference( c2t( "null" ), reference );
	descriptor2.putData( s2t( "textData" ), String.fromCharCode( 255, 254, 84, 0, 69, 0, 88, 0, 84, 0, 13, 0 ) );
	descriptor2.putString( s2t( "text" ), text );
	descriptor.putObject( s2t( "to" ), s2t( "annotation" ), descriptor2 );
	executeAction( s2t( "set" ), descriptor, DialogModes.NO );
}
This topic has been closed for replies.
Correct answer jazz-y

@c.pfaffenbichler, that's right - we can't access notes content by DOM or AM, but we know that they are stored in an image file (i rarely work with binary data and admit that not all things in the code are perfect, but it seems to work)

#target photoshop
var s2t = stringIDToTypeID,
    doc = new AM(),
    f = new File(Folder.temp.fsName + "/" + "annotations.psd");
if (doc.checkNotes()) {
    doc.duplicate('annotations', true);
    doc.setImageSize(1);
    activeDocument.xmpMetadata.rawData = '';
    doc.saveAs(f);
    doc.closeDocument();
    if (f) {
        f.open("r");
        f.encoding = "BINARY";
        var s = f.read();
        f.close();
        f.remove();
        i = s.indexOf('8BIMAnno')
        if (i) {
            var blockLength = hextoDec(toHex(s, i + 8, 4)),
                annotations = [];
            s = s.substr(i + 12, blockLength);
            do {
                var i = s.indexOf('txtC');
                if (i >= 0) {
                    blockLength = hextoDec(toHex(s, i + 4, 4)) - 2;
                    s = s.substr(i + 10);
                    if (blockLength) {
                        var content = '';
                        do {
                            content += String.fromCharCode(hextoDec(toHex(s, 0, 2)))
                            blockLength -= 2
                            s = s.substr(2)
                        } while (blockLength)
                        annotations.push(content);
                    }
                } else break;
            } while (true)
            for (var i = 0; i < annotations.length; i++) {
                activeDocument.artLayers.add()
                activeDocument.activeLayer.kind = LayerKind.TEXT
                activeDocument.activeLayer.textItem.contents = annotations[i]
            }
        }
    }
}
function toHex(s, from, bits) {
    s = s.substr(from, bits);
    var h = '';
    for (var i = 0; i < bits; i++) h += (('0' + s.charCodeAt(i).toString(16)).slice(-2));
    return h
}
function hextoDec(val) {
    var hex = val.split('').reverse().join(''),
        dec = 0;
    for (var i = 0; i < hex.length; i++) {
        dec += parseInt(hex[i], 16) * Math.pow(16, i);
    }
    return dec;
}
function AM() {
    var s2t = stringIDToTypeID;
    this.checkNotes = function () {
        try {
            (r = new ActionReference()).putEnumerated(s2t('annotation'), s2t('ordinal'), s2t('targetEnum'));
            (d = new ActionDescriptor()).putReference(s2t('null'), r);
            executeAction(s2t('delete'), d, DialogModes.NO);
            (r = new ActionReference()).putEnumerated(s2t('historyState'), s2t('ordinal'), s2t('previous'));
            (d = new ActionDescriptor()).putReference(s2t('null'), r);
            executeAction(s2t('select'), d, DialogModes.NO)
            return true
        } catch (e) {
            return false
        }
    }
    this.duplicate = function (title, merged) {
        (r = new ActionReference()).putEnumerated(s2t("document"), s2t("ordinal"), s2t("targetEnum"));
        (d = new ActionDescriptor()).putReference(s2t("target"), r);
        d.putString(s2t("name"), title);
        if (merged) d.putBoolean(s2t("merged"), merged);
        executeAction(s2t("duplicate"), d, DialogModes.NO);
    }
    this.setImageSize = function (width) {
        (d = new ActionDescriptor()).putUnitDouble(s2t("width"), s2t("pixelsUnit"), width);
        d.putBoolean(s2t("constrainProportions"), true);
        d.putEnumerated(s2t("interpolation"), s2t("interpolationType"), s2t("bilinear"));
        executeAction(s2t("imageSize"), d, DialogModes.NO);
    }
    this.saveAs = function (pth) {
        (d = new ActionDescriptor());
        (d1 = new ActionDescriptor()).putObject(s2t("as"), s2t("photoshop35Format"), d);
        d1.putPath(s2t("in"), pth);
        d1.putBoolean(s2t("embedProfiles"), false);
        executeAction(s2t("save"), d1, DialogModes.NO);
    }
    this.closeDocument = function () {
        (d = new ActionDescriptor()).putEnumerated(s2t('saving'), s2t('yesNo'), s2t('no'));
        executeAction(s2t('close'), d, DialogModes.NO)
    }
}

 

2 replies

jazz-yCorrect answer
Legend
December 1, 2022

@c.pfaffenbichler, that's right - we can't access notes content by DOM or AM, but we know that they are stored in an image file (i rarely work with binary data and admit that not all things in the code are perfect, but it seems to work)

#target photoshop
var s2t = stringIDToTypeID,
    doc = new AM(),
    f = new File(Folder.temp.fsName + "/" + "annotations.psd");
if (doc.checkNotes()) {
    doc.duplicate('annotations', true);
    doc.setImageSize(1);
    activeDocument.xmpMetadata.rawData = '';
    doc.saveAs(f);
    doc.closeDocument();
    if (f) {
        f.open("r");
        f.encoding = "BINARY";
        var s = f.read();
        f.close();
        f.remove();
        i = s.indexOf('8BIMAnno')
        if (i) {
            var blockLength = hextoDec(toHex(s, i + 8, 4)),
                annotations = [];
            s = s.substr(i + 12, blockLength);
            do {
                var i = s.indexOf('txtC');
                if (i >= 0) {
                    blockLength = hextoDec(toHex(s, i + 4, 4)) - 2;
                    s = s.substr(i + 10);
                    if (blockLength) {
                        var content = '';
                        do {
                            content += String.fromCharCode(hextoDec(toHex(s, 0, 2)))
                            blockLength -= 2
                            s = s.substr(2)
                        } while (blockLength)
                        annotations.push(content);
                    }
                } else break;
            } while (true)
            for (var i = 0; i < annotations.length; i++) {
                activeDocument.artLayers.add()
                activeDocument.activeLayer.kind = LayerKind.TEXT
                activeDocument.activeLayer.textItem.contents = annotations[i]
            }
        }
    }
}
function toHex(s, from, bits) {
    s = s.substr(from, bits);
    var h = '';
    for (var i = 0; i < bits; i++) h += (('0' + s.charCodeAt(i).toString(16)).slice(-2));
    return h
}
function hextoDec(val) {
    var hex = val.split('').reverse().join(''),
        dec = 0;
    for (var i = 0; i < hex.length; i++) {
        dec += parseInt(hex[i], 16) * Math.pow(16, i);
    }
    return dec;
}
function AM() {
    var s2t = stringIDToTypeID;
    this.checkNotes = function () {
        try {
            (r = new ActionReference()).putEnumerated(s2t('annotation'), s2t('ordinal'), s2t('targetEnum'));
            (d = new ActionDescriptor()).putReference(s2t('null'), r);
            executeAction(s2t('delete'), d, DialogModes.NO);
            (r = new ActionReference()).putEnumerated(s2t('historyState'), s2t('ordinal'), s2t('previous'));
            (d = new ActionDescriptor()).putReference(s2t('null'), r);
            executeAction(s2t('select'), d, DialogModes.NO)
            return true
        } catch (e) {
            return false
        }
    }
    this.duplicate = function (title, merged) {
        (r = new ActionReference()).putEnumerated(s2t("document"), s2t("ordinal"), s2t("targetEnum"));
        (d = new ActionDescriptor()).putReference(s2t("target"), r);
        d.putString(s2t("name"), title);
        if (merged) d.putBoolean(s2t("merged"), merged);
        executeAction(s2t("duplicate"), d, DialogModes.NO);
    }
    this.setImageSize = function (width) {
        (d = new ActionDescriptor()).putUnitDouble(s2t("width"), s2t("pixelsUnit"), width);
        d.putBoolean(s2t("constrainProportions"), true);
        d.putEnumerated(s2t("interpolation"), s2t("interpolationType"), s2t("bilinear"));
        executeAction(s2t("imageSize"), d, DialogModes.NO);
    }
    this.saveAs = function (pth) {
        (d = new ActionDescriptor());
        (d1 = new ActionDescriptor()).putObject(s2t("as"), s2t("photoshop35Format"), d);
        d1.putPath(s2t("in"), pth);
        d1.putBoolean(s2t("embedProfiles"), false);
        executeAction(s2t("save"), d1, DialogModes.NO);
    }
    this.closeDocument = function () {
        (d = new ActionDescriptor()).putEnumerated(s2t('saving'), s2t('yesNo'), s2t('no'));
        executeAction(s2t('close'), d, DialogModes.NO)
    }
}

 

c.pfaffenbichler
Community Expert
Community Expert
December 2, 2022

Thanks for the insight! 

Hopefully the original poster will drop by to acknowledge your effort. 

c.pfaffenbichler
Community Expert
Community Expert
December 1, 2022