Reading and setting multiple finder labels

I currently use FinderLabelMBS to read and set a folderitem Finder label. However, sometimes a file has multiple labels assigned. I’m looking for a way (with or without MBS) to read all the labels currently assigned to a folderitem. I also need to assign several labels to folderitem.

Googling around I found an Applescript to do it, but I would prefer to avoid using Applescript if possible.

Hi Bruno,
I have run into the same problem. Files end up with multiple labels, and user-defined labels cannot be cleared by setting f.FinderLabelMBS = 0. Did you ever find a solution?

Yes I did, let me dig for it and I’ll post it back later today.

Ok so I ended up going the command-line way using “xattr”. It allows to display or change file extended attributes. Labels are one of the extended attributes.

I can’t describe all parts in details, but I’ll try to explain what I can.as once everything worked, I have not documented what each component does. But in short,

Reading the labels:

xattr -p com.apple.metadata:_kMDItemUserTags /path/to/file | xxd -r -p | plutil -convert json - -o -

In more details:

xattr -p com.apple.metadata:_kMDItemUserTags /path/to/file
xattr allows to read and manipulate extended attributes. Labels are one of them. It outputs the result as a hex dump.

  • -p : Print the value associated with the attribute name
  • com.apple.metadata:_kMDItemUserTags is the attribute that stores the labels
  • /path/to/file : valid shell path (properly escaped)

xxd -r -p
xxd is a convert utility from hex dump to binary data

  • -r : reverse convert (from hex dump to binary)
  • -p : output as plain text

plutil -convert json - -o -
plutil is a utility to work with property lists and will perform the final conversion in json so it’s easier to work with.

  • -convert json : convert the property list to json
    • : take standard in as input
  • -o : allows to specify a path for output (in our case, will be redirected)
    • : output is redirected to standard out, which will end up in the result property of the Xojo shell.

The final output is a string in json format which looks like:

["Yellow\ 5","Red\ 6","Work"]

Each item contains the label name followed by the color code (if there’s a color). The name and color are separated by the "
" string. The color codes are: 0=none | 1=gray | 2=green | 3=purple | 4=blue | 5=yellow | 6=red

Writing labels

To set the tags, rebuild the string in the same format. The same utilities are used in reverse order with some different options, but you can basically figure out the process. Any new label will still be assigned and shown, but not available in the label popup list in the finder.

xattr -xw com.apple.metadata:_kMDItemUserTags $(echo '[labelname1,labelname2,labelname3]' | plutil -convert binary1 - -o - | xxd -p -c 256 -u) /path/to/file

My needs were to replicate the labels from one file to another. I have an audio converter application and I needed the converted files to have the same labels as the originals. So I have created a small class that reads the labels, and then sets them on another file. It does not allow to manually define the labels, but it would be trivial to add.

Here’s some sample code (adapted from my class but not tested)

[code]Private Sub ReadLabels(targetFile as FolderItem)
// READ FILE LABELS
dim theLabels() as string
dim theColors() as integer

if targetFile <> nil and targetFile.Exists = true then
dim s as new shell
s.Execute “xattr -p com.apple.metadata:_kMDItemUserTags " + targetFile.ShellPath + " | xxd -r -p | plutil -convert json - -o -”
if s.ErrorCode = 0 then

  dim rawResult as string = s.Result
  dim jsonLabels as new JSONItem(rawResult)
  
  dim lastItem as integer
  lastItem = jsonLabels.Count - 1
  
  for i as integer = 0 to lastItem
    
    dim item as string
    item = jsonLabels.Value(i).StringValue
    
    dim parts() as string
    parts = item.Split(&u0A)
    
    theLabels.Append parts(0)

    if parts.Ubound > 0 then
      theColors.Append(parts(1).val)
    else
      theColors.Append(0)
    end if
    
  next
  
end if

end if

// do your stuff with the labels and the color codes

// theLabels contains an array of label names
// theColors contains the matching color codes

End Sub[/code]

To write the labels for a given file, create a properly formatted string as: [“hot
1”,“projectX”,“mytag1”]

Private Sub SetLabels(targetFile as FolderItem,jsonlist as string)  
  // WRITE FILE LABELS
  dim cmd as string = "xattr -xw com.apple.metadata:_kMDItemUserTags $(echo '" + _
  jsonlist + "' | plutil -convert binary1 - -o - | xxd -p -c 256 -u) " + _
  targetFile.ShellPath
  
  dim s as new Shell
  s.Execute cmd
  
End Sub

In terminal, use “man” if you want to learn more about the different tools used:

man xattr man xxd man plutil

Thanks Bruno, this is extremely useful. Much appreciated!

I’m almost there, but curiously, if a file has 2 or more labels calling SetLabels with jsonlist = “” or “[]” removes all but one label. I cannot remove all labels. Even a 2nd call won’t do it.

And even xattr -d com.apple.metadata:_kMDItemUserTags myFile won’t do it (even tho xattr -l then reports that the com.apple.metadata:_kMDItemUserTags attribute is in fact gone, but the label in Finder remains, and must be removed manually from the Finder).

Do you know of a way to remove ALL labels from a file in one shot?

Thanks again
Peter.

Hmmm. Let me check this tonight, I’ll get back with my findings.

An addendum: I just noticed that if a user manually sets a label from the Finder, this label:

  1. cannot be removed by a call to SetLabels with jsonlist = “”
  2. is not even reported by ReadLabels ie. rawResult = “[]” even tho the label is clearly showing in the Finder.

So it almost seems like if you set the labels in code, you can read them and remove them. If you set them manually in the Finder, they are inaccessible to Read/SetLabels methods.

Very weird!

P.

… I couldn’t help but check immediately :slight_smile: There is a legacy finder info attribute that stores single labels (and other info). Meaning that sometimes, the label info can be stored there instead of the new com.apple.metadata:_kMDItemUserTags attribute.

To remove all labels with a simpler command:

xattr -d com.apple.metadata:_kMDItemUserTags /path/to/file

This will leave the last single label if stored in com.apple.FinderInfo. To remove it:

xattr -d com.apple.FinderInfo /path/to/file

BUT com.apple.FinderInfo also stores other info. Although it’s legacy info from the Classic environment, you should check if there’s anything useful for you. One example is the 4 character file type code. Here’s a link to a page that documents this attribute

From my (non scientific) experiments, it seems that setting the first label from the Finder will set it using com.apple.FinderInfo IF this attribute is present. After removing it, if you set it again in the Finder, it’s then correctly stored in the new attribute.

I haven’t poked around to decode the com.apple.FinderInfo attribute to extract the label set there, but it should be feasible.

thanks Bruno, this makes sense and explains the odd behavior. Just for the record, creating a manual label from the Finder will resist clearance by xattr -d com.apple.metadata:_kMDItemUserTags. You must also execute xattr -d com.apple.FinderInfo in addition. This is on a freshly created file from TextEdit so no chance of some old legacy attributes hanging around. Odd that Apple would store labels under different com.apple.XXX files.

Best
Peter.

Yeah, my tests are similar with new files created on Mojave. Sorry if I misled you in thinking that old files were the culprit. It seems that sometimes the Finder still creates the old attribute, so probably some legacy code buried in MacOS :slight_smile: I would guess that the first label uses the old attribute, and that any additional label get set with the new one.

Glad it works for you. I’m going to make a more elegant class to read set and clear the labels. I can send it to you if you’re interested.

Thanks Bruno, I’d appreciate looking at your class. One other point I just noticed during testing: seems that xattr is a python script and unfortunately, compared to FinderLabelMBS, it is extremely slow, at least on a large folder tree (3000 files: 15 min and still running :frowning: ) on a remote server. So there is no easy way to efficiently manage ALL labels it seems. Just an FYI if your application also includes complex hierarchies.

Cheers,
Peter.

My guess is that the slow process comes from instantiating a shell with every call. Try to make a call with a wildcard to process a batch of files. I did a quick test locally directly in terminal and processing 539 files with xattr -d com.apple.metadata:_kMDItemUserTags took 148ms and xattr -d com.apple.FinderInfo took 151ms. I know a file server adds quite a bit of lag but maybe you’ll want to see if a wildcard helps. Try it directly in the terminal, transpose in a shell in Xojo after.

That’s not the problem: I ran a single command with the -r flag and finally had to kill my app after 20 min.

But you’re right about the remote volume issue: 3000 files on a local drive took 1 second with -r.

So the command itself is not the cause of the delay. If the MBS plugin take less time, then we’ll have to bow before our master Christian once again :-))

Yes indeed, however, FinderLabelMBS only gives you access to one “set” of labels unfortunately. Christian, is there a way for MBS to handle them all?

So MBS does work faster? (just curious)

If I extract a list of items in a folder and set each one at a time using FinderLabelMBS it’s faster than xattr with the -r flag, BUT FinderLabelMBS only seems to manipulate the classic labels, not the com.apple.metadata:_kMDItemUserTags, so it’s not that useful because you end up with dual labels, a mix of new and old, and it becomes a mess. I wish we could just pass an array of numbers (= color indexes) to a function that would stamp none, one or several color labels to a folderItem, and quickly, taking care of new and old sets at once.

I have searched a bit but the only other way I found (at a high level anyways) is using Applescript. I doubt it’ll be faster, but it may be worth a try.

At this point, you may want to contact Christian about this. He may be willing to update its classes to support both old and new attributes.

As for me, I’ll work on a clean class during the Christmas Holiday (for fun). If it can be useful, even if it’s slow, I’ll happily pass it along. It can work as you described, but it’ll still be slow on a remote volume.

I would also be interested in seeing your class. I only need read ability, but I can see how we ability would be useful too.