If the keywords for a photo in its XMP sidecar are changed by an external program, the photo shows under both the the old keywords and new keywords when searching by keyword or clicking the keyword in the Collection Pane. Only the correct (new) keywords are shown in the Metadata Editor.
I tried both Regenerate Metadata Cache and Rebuild Keyword Tree and neither fixed the problem. I also tried adding the old keyword back onto the photo in the Metadata Editor in Aspect and then removing it again from there and that did not work either.
I don’t know if it matters, but the keywords are stored in the XMP as dc:subject→rdf:Bag→rdf:li elements with pipe characters (|) separating them into hierarchies.
Steps required to reproduce
Add a photo with an XMP sidecar containing a keyword tag to Aspect.
Close Aspect and change the keyword using an external program.
Re-open Aspect.
Note that the photo is now shown under both the old and new keywords.
I tried to reproduce this, but didn’t really manage to do so, except for a short while after loading the library. But after the file synchronization was done, the keyword tree always got refreshed and matched the keywords that were actually in the XMP file.
Another thing I tried was to write the initial keyword(s) into the image file itself and only have the modified keyword in the XMP file, but the contents of the image file got correctly ignored in that case, too.
One thing that is a bit suspicious is that, as far as I understand, the <dc:subject> element is only supposed to contain plain keywords, whereas the <lr:hierarchicalSubject> element is used to store hierarchical keywords with the | separator. However, I tried to use different combinations of the two fields and the application always correctly matched what was being displayed in the metadata pane and in the keyword tree.
Could this be an issue with a lengthy “synchronizing with file system” activity? This is required to finish before the changes to the keyword tree get refreshed (although I’ll see if I can change the menu entry so that it always performs the update immediately).
I guess one thing to note is that in my case other photos have both the old and new keywords, so the old keyword doesn’t go away, it’s still there. It just shouldn’t apply to the changed photo. If you’re just testing with a single photo and/or unique keywords that only apply to that photo, maybe that is why you aren’t able to reproduce the problem.
I just checked and my XMP files do use dc:subject. That is my fault though. I wrote a Python script that uses exiftool to copy XMP:PersonInImagetags into keywords and it uses dc:subject which seemed to work. I have been planning to change it to use Aspect collections instead of keywords though. What tags do collections use?
Collections are separate from the metadata of the file (with the idea that those might not be something that you’d want to share when sending the image to others). For adding an image to a collection, you could either write an addon/snippet that reads the keywords from somewhere and then uses the library API to add the image to the corresponding collections, or you could modify the Aspect Library File.sdl (https://sdlang.org/) file directly (adding the file ID to the items entry of the respective collection).
I wouldn’t really recommend directly editing the library catalog file, because mapping the file elements to their paths in the file system requires a bit of extra logic and care must be taken to ensure the version attribute is as expected to avoid potential issues when the format gets upgraded one day.
Adding a file to a collection via script would look something like this:
// adds a file to the collection that matches the given
// hierarchical name (hierarcy levels separated by |)
function addFileToCollection(path, collection_path)
{
var file = getFileForPath(path);
if (!file) {
alert("File not found: " + path);
return;
}
var collection = getCollection(collection_path);
if (!collection) {
alert("Collection not found: " + collection_path);
return;
}
addToCollection(collection, file);
}
function addToCollection(collection, image)
{
// the image needs to be part of all parent collections as well
while (collection && collection.type() == "Collection") {
collection.items().add(image);
collection = collection.owner();
}
}
function getCollection(collection_path)
{
function getRec(items, sub_path)
{
var ret = null;
items.forEach(function(itm) {
if (itm.caption() == sub_path[0]) {
if (sub_path.length > 1)
ret = getRec(itm.subCollections(), sub_path.slice(1));
else ret = itm;
}
});
return ret;
}
return getRec(library.rootNode().groupItems(), collection_path.split("|"));
}
function getFileForPath(path)
{
var ret = null;
library.rootNode().events().forEach(function(evt) {
evt.files().forEach(function(fil) {
if (fil.fullLocalPath() == path)
ret = fil;
});
});
library.rootNode().shoeBox().folders().forEach(function(sbf) {
sbf.files().forEach(function(fil) {
if (fil.fullLocalPath() == path)
ret = fil;
});
});
return ret;
}
addFileToCollection(
"/home/user/My Library/2025/Individual Photos/image.jpg",
"foo|bar");