Copy link to clipboard
Copied
Good Morning,
I was wondering if there is a script that can find a keyword that was assigned to a phtos and replace it with a new one.
I know that if I reaname a keywrods, it will not be renamed in the assigned photos, and bridge will create back the the kewords that I renamed so I never do that. I usually go to find a specific keyword, change it it the photos that I found and assign the new one. But this is a lot of work. So I was thinking maybe there is a script for that: I will crate a new keywords and in the script I will ask to find and replace the old keyword with the new one I crated.
I hope I manged to describe my question.
good day,
Shlomit
Copy link to clipboard
Copied
Yes, there is. If you replace it with a new keyword, you will need to convert it to a persistent keyword if you wish for it to be part of your library.
// https://stackoverflow.com/questions/47797219/edit-script-for-editing-metadata-in-adobe-bridge-how-should-i-put-key-words
#target bridge
if( BridgeTalk.appName == "bridge" ) {
keyReplace = MenuElement.create("command", "Add-Replace-Remove Keyword", "at the end of tools");
}
keyReplace.onSelect = function () {
mainReplaceKeyword();
}
function mainReplaceKeyword(){
if(app.version.substr(0,app.version.indexOf('.'))==1){
alert("Sorry You Need CS3 or CS4 to run this script!");
return;
}
var dlg =
"dialog{text:'Script Interface',bounds:[100,100,500,310],"+
"panel0:Panel{bounds:[10,10,390,200] , text:'' ,properties:{borderStyle:'etched',su1PanelCoordinates:true},"+
"title:StaticText{bounds:[60,10,350,40] , text:'Add/Replace/Remove Keyword' ,properties:{scrolling:undefined,multiline:undefined}},"+
"panel1:Panel{bounds:[10,40,370,150] , text:'' ,properties:{borderStyle:'etched',su1PanelCoordinates:true},"+
"addKey:Checkbox{bounds:[20,10,160,31] , text:'Add Keyword' },"+
"statictext1:StaticText{bounds:[20,40,119,60] , text:'Replace' ,properties:{scrolling:undefined,multiline:undefined}},"+
"From:EditText{bounds:[120,40,350,60] , text:'' ,properties:{multiline:false,noecho:false,readonly:false}},"+
"statictext2:StaticText{bounds:[20,80,90,97] , text:'With' ,properties:{scrolling:undefined,multiline:undefined}},"+
"To:EditText{bounds:[120,80,350,100] , text:'' ,properties:{multiline:false,noecho:false,readonly:false}}},"+
"button0:Button{bounds:[10,160,180,181] , text:'Ok' },"+
"button1:Button{bounds:[200,160,370,181] , text:'Cancel' }}};";
var win = new Window(dlg,"Replace Keyword");
win.center();
win.panel0.title.graphics.font = ScriptUI.newFont("Times","BOLDITALIC",20);
g = win.graphics;
b=win.panel0.title.graphics;
var myBrush = g.newBrush(g.BrushType.SOLID_COLOR, [0.99, 0.99, 0.20, 1]);
g.backgroundColor = myBrush;
var myPen =b.newPen (g.PenType.SOLID_COLOR, [0.00, 0.00, 0.99, 1],lineWidth=1);
var myPen2 =b.newPen (g.PenType.SOLID_COLOR, [0.99, 0.00, 0.00, 1],lineWidth=1);
g.foregroundColor = myPen;
b.foregroundColor = myPen2;
win.panel0.panel1.From.active=true;
win.panel0.panel1.addKey.onClick = function() {
if(win.panel0.panel1.addKey.value) {
win.panel0.panel1.statictext1.text = "New Keyword";
win.panel0.panel1.From.active=true;
win.panel0.panel1.statictext2.visible=false;
win.panel0.panel1.To.visible=false;
}
if(!win.panel0.panel1.addKey.value) {
win.panel0.panel1.statictext1.text = "Replace";
win.panel0.panel1.statictext2.visible=true;
win.panel0.panel1.To.visible=true;
win.panel0.panel1.From.active=true;
}
}
win.center();
var done = false;
while (!done) {
var x = win.show();
if (x == 0 || x == 2) {
win.canceled = true;
done = true;
} else if (x == 1) {
done = true;
var result = valiDate();
if(result != true) {
alert(result);
return;
}else{
var Replace = win.panel0.panel1.From.text;
var With = win.panel0.panel1.To.text;
var addKey = win.panel0.panel1.addKey.value;
processKeyword(Replace,With,addKey);
}
}
}
function valiDate(){
if(win.panel0.panel1.From.text =='') return "No Keyword Entered!";
return true;
}
function processKeyword(Replace,Witha,ddKey){
try{
loadXMPScript();
}catch(e){
alert("Can not load XMPScript\r" + e.message);
}
var items = app.document.selections;
for (var i = 0; i < items.length; i++){
var file=new Thumbnail(items[i]);
try{
var xmpFile = new XMPFile(file.path, XMPConst.UNKNOWN,XMPConst.OPEN_FOR_UPDATE);
}catch(e){
alert("Problem opening xmp for update:-\r" + file.path +"\r" +e.message);
return;
}
try{
var xmp = xmpFile.getXMP();
}catch(e){
alert("Problem opening xmp data:-\r" + e.message);
return;
}
try{
var tmpCount = xmp.countArrayItems(XMPConst.NS_DC, "subject");
}catch(e){
alert("Cannot get count \r" + e.message);
}
if(addKey){
xmp.appendArrayItem(XMPConst.NS_DC, "subject", Replace, 0,XMPConst.ARRAY_IS_ORDERED);
}
if(tmpCount >0 && !addKey){
for (var a =0;a<tmpCount;a++){
var Keyword = xmp.getArrayItem(XMPConst.NS_DC,'subject', a+1);
if(Keyword == Replace && With == '') {
xmp.deleteArrayItem(XMPConst.NS_DC,'subject', a+1);
}
if(Keyword == Replace && With != ''){
xmp.setArrayItem(XMPConst.NS_DC,'subject', a+1,With);
}
}
}
if (xmpFile.canPutXMP(xmp)) {
xmpFile.putXMP(xmp);
}else{
alert(e.message);
}
xmpFile.closeFile(XMPConst.CLOSE_UPDATE_SAFELY);
}
unloadXMPScript();
}
}
function loadXMPScript()
{
var results = new XMPLibMsg("XMPScript Library already loaded", 0, false);
if (!ExternalObject.AdobeXMPScript)
{
try
{
ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');
results.message = "XMPScript Library loaded";
}
catch (e)
{
alert("Could not load AdobeXMPScript \r" + e.message);
results.message = "ERROR Loading AdobeXMPScript: " + e;
results.line = e.line;
results.error = true;
}
}
return results;
}
function unloadXMPScript()
{
var results = new XMPLibMsg("XMPScript Library not loaded", 0, false);
if( ExternalObject.AdobeXMPScript )
{
try
{
ExternalObject.AdobeXMPScript.unload();
ExternalObject.AdobeXMPScript = undefined;
results.message = "XMPScript Library successfully unloaded";
}
catch (e)
{
results.message = "ERROR unloading AdobeXMPScript: " + e;
results.line = e.line;
results.error = true;
}
}
return results;
}
function XMPLibMsg (inMessage, inLine, inError)
{
this.message = inMessage;
this.line = inLine;
this.error = inError;
}
Select the files, then Tools > Add-Replace-Remove Keyword
https://prepression.blogspot.com/2017/11/downloading-and-installing-adobe-scripts.html
Copy link to clipboard
Copied
@Shlomit Heymann – so how did the script work for you?
Copy link to clipboard
Copied
@Stephen_A_Marsh thanks for posting this. I tried this script and it worked well.
@Shlomit Heymann let us know if it solved your problem.
Copy link to clipboard
Copied
Dear Stepehn,
I'm sorry for my late reply. In the meanwhile, I have made a terriabl mistake with the keywords, and got really under pressure and now I will have to fix many photos manually.
The script you created works well, but it is my fault that I did not explained what I really need. This script will create new keywords which can later defined as perssistant. But what I need is the ability to select an existed kewrod from my keywords list hirarchy and to replace it with another keyword that exsist in same or different hirarchy too without creating a new keyword.
For instance: I'm taking flower photos, so I have a keywords folder for Family type, Flower Name, Color, wild flowers, succullents etc.. Each folder has sub fulders and sometimes those has more subfolders.
I also have a few same keywords that appears in different hirachy and I know now it was a mistake. So I want to find the keyword in one hirarcy and replace it with a different hirarchy and this is why I need the abily to select the keywords for an existed list and also the replace.
Again, Thank you so much
Shlomit
Copy link to clipboard
Copied
I'm sorry to hear that you are having issues. Please do test with care before using on many images...
I have tried a different script, it appears to do what you require, please take a look at this animation before/after to see if it does what I believe you need:
Copy link to clipboard
Copied
Good Morning Stephen,
The problem is that I have the same keywords in different hirarchies (It was wrong to do so but I did).
In your animation it finds the A1 in the A hirarchy becuase it exsit only there. But if you had another A1 in the Holiday Hirarchy (for instance), which one will it find? Or if you have another A3 in the Holiday hyrarchy too, to which A3 will be replaced?
This is why I wanted a possebilty to find through a specific hyrarchy otherwise It will not make the change I need.
good day and thank you
Shlomit
Copy link to clipboard
Copied
@Shlomit Heymann – Ah... Now I understand. Hierarchical + duplicate keywords do add to the complexity!
Let me think about that, all is not lost.
OK, I have just tried with ExifTool and it is easy to reassign the incorrect hierarchical keyword to the correct one in both the standard -XMP-dc:Subject and in the -XMP-lr:HierarchicalSubject fields.
A "simple" conditional command can search through a folder or sub-folders of images and check for the same hierarchical sub-keyword in the incorrect top-level keyword set and reassign the keyword to the correct sub-keyword in the correct top-level keyword.
@Lumigraphics or @gregreser – do you have any ideas?
Copy link to clipboard
Copied
There are five keyword fields- two used by Lightroom, one written by a camera, one Microsoft, and dc:subject.
If you use hierarchical keywords, you won't have duplicates. A|1|a and B|1|a are different. I'd clean up keywords first and then worry about re-assigning things.
Finally, its easy to copy keywords from files and export, maybe consider doing search and replace in a text editor if its getting hairy and then reimport them.
Copy link to clipboard
Copied
I would export the keywords from the files and edit them in a spreadsheet. I find it easy to analyze and batch edit in a spreadsheet, so that would be my choice.
@Shlomit Heymann are you familiar with spreadsheets (Excel or Google Sheets)? There are several scripts to export/import via Bridge (I have one I can share) as well as ExifTool.
Copy link to clipboard
Copied
Good Morning and thank you 🙂
Sort of. I did download once a script so I can fix a spreadshit, but it was many years ago and I'm not sure I remember. But maybe I can learn. Looking at bridge I see that have installed "VRA Core Metadata" and "Metadata (DIY Custom Metadata). I think I have corresponded back than with the script editor as I had problem with Hebrew keywords and he fixed something in the script, it was many years ago.
Copy link to clipboard
Copied
One can also do this manually. Setup Bridge to show content from all sub-folders and to hide folders, then use Find as follows, select all found/filtered files and swap over the keywords:
Copy link to clipboard
Copied
Yes, I think you might be able to use existing Bridge tools to accomplish your goals as @Stephen_A_Marsh suggests. You seem to know what the incorrect keywords are, so you could use the advanced search to find them and then manually edit the keywords in the Bridge metadata panel.
There are always many ways to approach these problems. Let us know if our suggestions are helpful because you know your metadata best.
Copy link to clipboard
Copied
@Shlomit Heymann – Just to confirm, does the following illustrate the issue?
So you need to find all images that use Test-B|Sub-Test-A and change them to Test-A|Sub-Test-A ?
Copy link to clipboard
Copied
Good Morning Stephen and everybody who are trying to help.
You are so kind! Thank you.
Yes, This illustrates the issue.
Shlomit 🤗
Copy link to clipboard
Copied
Here is the conditional ExifTool command-line code from my previous example (Mac):
exiftool -if '$XMP-dc:Subject eq "Test-B|Sub-Test-A"' -XMP-dc:Subject='Test-A|Sub-Test-A' -execute -if '$XMP-lr:HierarchicalSubject eq "Test-B|Sub-Test-A"' -XMP-lr:HierarchicalSubject='Test-A|Sub-Test-A' -execute -common_args 'PATH TO FILE OR FOLDER'
This command is formatted for the Mac OS. For Windows, the single straight quotes ' would need to be swapped for double straight quotes " and the double straight quotes would need to be swapped for single straight quotes. Here it is for Win:
exiftool -if "$XMP-dc:Subject eq 'Test-B|Sub-Test-A'" -XMP-dc:Subject="Test-A|Sub-Test-A" -execute -if "$XMP-lr:HierarchicalSubject eq 'Test-B|Sub-Test-A'" -XMP-lr:HierarchicalSubject="Test-A|Sub-Test-A" -execute -common_args "PATH TO FILE OR FOLDER"
The 'PATH TO FILE OR FOLDER' would need to be changed to your test folder or file path (always backup and or work on duplicates).
Files successfully modified will create a backup copy with _original at the end of the filename, such as myFile.jpg_original and once you are happy a further command could be added to not create these _original backup files.
It is also possible to recursively scan into all sub-directories under a main/root directory and to also exclude certain file types and or sub-directories from being processed.
ExifTool is powerful, however, not everybody is comfortable with using command-line interfaces.
Copy link to clipboard
Copied
Thank you. Im on Mac but I don't know (yet) what to do with it... 🙂 I will consulty with a friend who is better than me in these things.
Copy link to clipboard
Copied
Download and install ExifTool from the link previously provided.
Run Terminal.app
Paste in the code above*, making sure there is a space at the end, but don't paste in 'PATH TO FILE OR FOLDER'
Then you can drag your test file or test folder into the Terminal window and press return/enter. Always work on copies while you get the hang of things.
*You will obviously need to change the hierarchical keywords.
Copy link to clipboard
Copied
And as a final note, the first example below is a production script that grabs keywords from dc:subject and lr:hierarchicalKeywords, sorts, removes duplicates, and writes them back into those two namespaces.
The second example is a production script to capture keywords from files and write to a text file for import. Just as an example of whats involved if you work with keywords in a script.
/*
Utility Pack Scripts created by David M. Converse ©2018-21
This script removes duplicate and empty keyword tags and rewrites in alphabetical order
Last modifed 6/11/2021
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#target bridge
if(BridgeTalk.appName == 'bridge'){
try{
//create menu
var kwOpt = MenuElement.create('command', 'Keyword Optimizer', 'at the end of Tools');
kwOpt.onSelect = function(){
var kwList = app.document.selections;
var i = 0;
var j = 1;
var k = 1;
var kw_clean = []; //optimized keywords array
if (ExternalObject.AdobeXMPScript == undefined) ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');
for(i = 0; i < kwList.length; i++){ //loop through selected files
if(kwList[i].hasMetadata){ //only process files with metadata
k = 1;
kw_clean = [];
//XMP stores keywords in three namespaces. We don't process the Microsoft keyword namespace.
var kw = kwList[i].synchronousMetadata.read(XMPConst.NS_DC, 'subject').toString();
var lrkw = kwList[i].synchronousMetadata.read('http://ns.adobe.com/lightroom/1.0/', 'hierarchicalSubject').toString();
if(kw != '' || lrkw != ''){ //at least one namespace has keywords
kw = kw.split(','); //make arrays out of keyword string
lrkw = lrkw.split(',');
kw = kw.concat(lrkw); //place all keywords in one array and sort
kw.sort();
if(kw[0] != ''){
kw_clean[0] = kw[0];
}
else{ //flag empty keywords
k = 0;
}
for(j = 1; j < kw.length; j++){ //eliminate duplicates and write into new array
if(kw[(j - 1)] != kw[j] && kw[j] != ''){
kw_clean[k] = kw[j];
k++;
}
}
var md = kwList[i].synchronousMetadata;
var xmp = new XMPMeta(md.serialize());
//delete old keywords in both namespaces
XMPUtils.removeProperties(xmp, XMPConst.NS_DC, 'subject', XMPConst.REMOVE_ALL_PROPERTIES);
XMPUtils.removeProperties(xmp, 'http://ns.adobe.com/lightroom/1.0/', 'hierarchicalSubject', XMPConst.REMOVE_ALL_PROPERTIES);
for(j = 0; j < kw_clean.length; j++){ //write optimized list of keywords to both spaces
xmp.appendArrayItem(XMPConst.NS_DC, 'subject', kw_clean[j], 0,XMPConst.PROP_IS_ARRAY);
xmp.appendArrayItem('http://ns.adobe.com/lightroom/1.0/', 'hierarchicalSubject', kw_clean[j], 0, XMPConst.PROP_IS_ARRAY);
}
var updatedPacket = xmp.serialize(XMPConst.SERIALIZE_OMIT_PACKET_WRAPPER | XMPConst.SERIALIZE_USE_COMPACT_FORMAT);
kwList[i].metadata = new Metadata(updatedPacket);
}
}
}
alert('Keyword Optimization complete.');
}
}
catch(e){
alert(e + ' ' + e.line);
}
}
/*
Utility Pack Scripts created by David M. Converse ©2018-21
This script reads keywords from files and saves to a text file for import into Bridge
Last modifed 2/24/2021
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#target bridge
if(BridgeTalk.appName == 'bridge'){
try{
var KeywordCapture = new Object; //id object for this script
#include "prefsReader.jsxinc" //shared prefs read code
//create menu
if(useMenu == true){ //use Tools submenu
if(MenuElement.find('utilPack') == null){ //submenu not yet created
MenuElement.create('menu', 'Utility Script Pack', 'at the end of Tools', 'utilPack');
}
var kwCap = MenuElement.create('command', 'Keyword Capture', 'at the end of utilPack');
}
else{
var kwCap = MenuElement.create('command', 'Keyword Capture', 'at the end of Tools');
}
kwCap.onSelect = function(){
var kwList = app.document.selections;
var i = 0; //counters
var j = 1;
var k = 1;
var l = 0;
var kw_clean = []; //optimized keywords array
var kw_line = ''; //keyword
var kw_tabs = []; //hierarchy depth
var kw_leading = ''; //exported file requires tab delimiter for hierarchical keywords
var kwCapLogFile = File(''); //file to write list to
kwCapLogFile = new File('~/Desktop/kwCapture.txt').saveDlg('Create Keyword Capture File', '*.txt'); //create text file
kwCapLogFile.open('w:');
if (ExternalObject.AdobeXMPScript == undefined) ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');
for(i = 0; i < kwList.length; i++){ //loop through selected files
if(kwList[i].hasMetadata){ //only process files with metadata
k = 1;
kw_clean = [];
kw_tabs = [];
//XMP stores keywords in three namespaces. We don't process the Microsoft keyword namespace.
var kw = kwList[i].synchronousMetadata.read(XMPConst.NS_DC, 'subject').toString();
var lrkw = kwList[i].synchronousMetadata.read('http://ns.adobe.com/lightroom/1.0/', 'hierarchicalSubject').toString();
if(kw != '' || lrkw != ''){ //at least one namespace has keywords
kw = kw.split(','); //make arrays out of keyword string
lrkw = lrkw.split(',');
kw = kw.concat(lrkw); //place all keywords in one array and sort
kw.sort();
if(kw[0] != ''){
kw_clean[0] = kw[0];
}
else{ //flag empty keywords
k = 0;
}
for(j = 1; j < kw.length; j++){ //eliminate duplicates and write into new array
if(kw[(j - 1)] != kw[j] && kw[j] != ''){
kw_clean[k] = kw[j];
k++;
}
}
for(j = 0; j < kw_clean.length; j++){ //write optimized list of keywords to file
kw_leading = ''; //reset leading tabs
kw_tabs = kw_clean[j].match(/\|/g); //how deep is the hierarchy
if(kw_tabs != null){ //hierarchical kw
for(l = 0; l < kw_tabs.length; l++){
kw_clean[j] = kw_clean[j].replace('|', '\r' + kw_leading + '\t'); //we process "|" as a delimiter, edit script to use others
kw_leading = kw_leading + '\t'; //each new level requires one more leading tab
}
}
if(kw_line != ''){
kw_line = kw_line + '\r' + kw_clean[j];
}
else{ //first line for file, don't add extra return
kw_line = kw_clean[j];
}
}
kwCapLogFile.writeln(kw_line); //write to file
kw_line = ''; //reset for next file
}
}
}
kwCapLogFile.close();
kwCapLogFile.open('r:');
var kwFin = kwCapLogFile.read();
kwCapLogFile.close();
kwCapLogFile.open('w:');
kwFin = kwFin.replace(/\n\t/g, '\t');
kwFin = kwFin.split('\n');
kwFin = kwFin.sort();
for(i = 0; i < kwFin.length; i++){
if(kwFin[i] == kwFin[i + 1]){
kwFin[i] = '';
}
}
kwFin = kwFin.sort();
kwFin = kwFin.join('\r');
kwFin = kwFin.replace(/\r\r/g, '');
kwFin = kwFin.replace(/\t/g, '\r\t');
kwFin = kwFin.replace(/\t\r/g, '\t');
kwCapLogFile.write(kwFin);
kwCapLogFile.close();
alert('Keyword capture completed');
}
}
catch(e){
alert(e + ' ' + e.line);
}
}