getting and setting mac file and folder finder labels from Python
Question:
I have been trying to find out how to get and set the colour of file labels from python.
The closest thing I’ve found to a solution was this, but I can’t seem to find the module macfile anywhere. Am I just not looking hard enough?
Is there a different way to achieve this if not?
Answers:
You can do this in python using the xattr module.
Here is an example, taken mostly from this question:
from xattr import xattr
colornames = {
0: 'none',
1: 'gray',
2: 'green',
3: 'purple',
4: 'blue',
5: 'yellow',
6: 'red',
7: 'orange',
}
attrs = xattr('./test.cpp')
try:
finder_attrs = attrs['com.apple.FinderInfo']
color = finder_attrs[9] >> 1 & 7
except KeyError:
color = 0
print colornames[color]
Since I have colored this file with the red label, this prints 'red'
for me. You can use the xattr module to also write a new label back to disk.
If you follow favoretti’s link and then scroll down a bit, there’s a link to
https://github.com/danthedeckie/display_colors, which does this via xattr
, but without the binary manipulations. I rewrote his code a bit:
from xattr import xattr
def set_label(filename, color_name):
colors = ['none', 'gray', 'green', 'purple', 'blue', 'yellow', 'red', 'orange']
key = u'com.apple.FinderInfo'
attrs = xattr(filename)
current = attrs.copy().get(key, chr(0)*32)
changed = current[:9] + chr(colors.index(color_name)*2) + current[10:]
attrs.set(key, changed)
set_label('/Users/chbrown/Desktop', 'green')
The macfile
module is part of the appscript
module, and was renamed to mactypes
in “2006-11-20 — 0.2.0”
Using this module, here are two functions to get and set the finder labels with appscript version 1.0:
from appscript import app
from mactypes import File as MacFile
# Note these label names could be changed in the Finder preferences,
# but the colours are fixed
FINDER_LABEL_NAMES = {
0: 'none',
1: 'orange',
2: 'red',
3: 'yellow',
4: 'blue',
5: 'purple',
6: 'green',
7: 'gray',
}
def finder_label(path):
"""Get the Finder label colour for the given path
>>> finder_label("/tmp/example.txt")
'green'
"""
idx = app('Finder').items[MacFile(path)].label_index.get()
return FINDER_LABEL_NAMES[idx]
def set_finder_label(path, label):
"""Set the Finder label by colour
>>> set_finder_label("/tmp/example.txt", "blue")
"""
label_rev = {v:k for k, v in FINDER_LABEL_NAMES.items()}
available = label_rev.keys()
if label not in available:
raise ValueError(
"%r not in available labels of %s" % (
label,
", ".join(available)))
app('Finder').items[MacFile(path)].label_index.set(label_rev[label])
if __name__ == "__main__":
# Touch file
path = "blah"
open(path, "w").close()
# Toggle label colour
if finder_label(path) == "green":
set_finder_label(path, "red")
else:
set_finder_label(path, "green")
I don’t know if this question is still relevant to anybody but there is a new package “mac-tag” that solves this issue.
pip install mac-tag
and then you have functions like:
function __doc__
mac_tag.add(tags, path) # add tags to path(s)
mac_tag.find(tags, path=None) # return a list of all paths with tags, limited to path(s) if present
mac_tag.get(path) # return dict where keys are paths, values are lists of tags. equivalent of tag -l
mac_tag.match(tags, path) # return a list of paths with with matching tags
mac_tag.parse_list_output(out) # parse tag -l output and return dict
mac_tag.remove(tags, path) # remove tags from path(s)
mac_tag.update(tags, path) # set path(s) tags. equivalent of `tag -s
complete documentation at: https://pypi.org/project/mac-tag/
Suppose you have this folder:
Notice that some files have multiple tags. One tag is Work
which is a custom tag.
There are three ways that I have used to get the Finder tags into Python.
First, use xattr
to generate a binary plist
of extended attributes from the key 'com.apple.metadata:_kMDItemUserTags'
and then convert that to a list with plistlib:
import xattr
import plistlib
def get_tags_xattr(fn):
try:
bpl=xattr.getxattr(fn, 'com.apple.metadata:_kMDItemUserTags')
rtr=[e.partition('n')[0]
for e in plistlib.loads(bpl, fmt=plistlib.FMT_BINARY)]
return rtr if rtr else None
except OSError:
return None
The second is to use the program tag which can be installed with brew
:
from subprocess import run, PIPE
def get_tags_tag(fn):
if 'com.apple.metadata:_kMDItemUserTags' in xattr.listxattr(fn):
rtr=run(['tag', '-g', '--no-name', fn],
stdout=PIPE).stdout.decode('utf-8').splitlines()
return rtr if rtr else None
return None
The third is to use the PyPi module osxmetadata:
import osxmetadata
def get_tags_osxmeta(fn):
tags=[t.name for t in osxmetadata.OSXMetaData(fn).tags]
if tags:
return tags
return None
Here is a demo of all three:
from pathlib import Path
p=Path('/tmp/test')
for fn in (fn for fn in p.glob('*') if fn.is_file()):
print(fn, get_tags_osxmeta(fn),
get_tags_tag(fn),
get_tags_xattr(fn))
Prints on that sample folder:
/tmp/test/DSC_2930-m.jpg ['Red', 'Green'] ['Green', 'Red'] ['Red', 'Green']
/tmp/test/DSC_2929.JPG None None None
/tmp/test/DSC_2939.JPG None None None
/tmp/test/DSC_2938.JPG ['Red'] ['Red'] ['Red']
/tmp/test/DSC_2937.JPG None None None
/tmp/test/DSC_2942-m.jpg ['Red', 'Orange', 'Gray', 'Work'] ['Gray', 'Orange', 'Red', 'Work'] ['Red', 'Orange', 'Gray', 'Work']
/tmp/test/DSC_2934.JPG ['Red'] ['Red'] ['Red']
/tmp/test/DSC_2930.JPG None None None
/tmp/test/DSC_2931.JPG None None None
/tmp/test/DSC_2933.JPG None None None
/tmp/test/DSC_2932.JPG ['Red'] ['Red'] ['Red']
/tmp/test/DSC_2941.JPG None None None
/tmp/test/DSC_2942.JPG None None None
The best of those (that which I use most often) is the osxmetadata module. It is fast and very flexible. If you just need the tags, the other ways work well too. The first is likely the fastest.
I have been trying to find out how to get and set the colour of file labels from python.
The closest thing I’ve found to a solution was this, but I can’t seem to find the module macfile anywhere. Am I just not looking hard enough?
Is there a different way to achieve this if not?
You can do this in python using the xattr module.
Here is an example, taken mostly from this question:
from xattr import xattr
colornames = {
0: 'none',
1: 'gray',
2: 'green',
3: 'purple',
4: 'blue',
5: 'yellow',
6: 'red',
7: 'orange',
}
attrs = xattr('./test.cpp')
try:
finder_attrs = attrs['com.apple.FinderInfo']
color = finder_attrs[9] >> 1 & 7
except KeyError:
color = 0
print colornames[color]
Since I have colored this file with the red label, this prints 'red'
for me. You can use the xattr module to also write a new label back to disk.
If you follow favoretti’s link and then scroll down a bit, there’s a link to
https://github.com/danthedeckie/display_colors, which does this via xattr
, but without the binary manipulations. I rewrote his code a bit:
from xattr import xattr
def set_label(filename, color_name):
colors = ['none', 'gray', 'green', 'purple', 'blue', 'yellow', 'red', 'orange']
key = u'com.apple.FinderInfo'
attrs = xattr(filename)
current = attrs.copy().get(key, chr(0)*32)
changed = current[:9] + chr(colors.index(color_name)*2) + current[10:]
attrs.set(key, changed)
set_label('/Users/chbrown/Desktop', 'green')
The macfile
module is part of the appscript
module, and was renamed to mactypes
in “2006-11-20 — 0.2.0”
Using this module, here are two functions to get and set the finder labels with appscript version 1.0:
from appscript import app
from mactypes import File as MacFile
# Note these label names could be changed in the Finder preferences,
# but the colours are fixed
FINDER_LABEL_NAMES = {
0: 'none',
1: 'orange',
2: 'red',
3: 'yellow',
4: 'blue',
5: 'purple',
6: 'green',
7: 'gray',
}
def finder_label(path):
"""Get the Finder label colour for the given path
>>> finder_label("/tmp/example.txt")
'green'
"""
idx = app('Finder').items[MacFile(path)].label_index.get()
return FINDER_LABEL_NAMES[idx]
def set_finder_label(path, label):
"""Set the Finder label by colour
>>> set_finder_label("/tmp/example.txt", "blue")
"""
label_rev = {v:k for k, v in FINDER_LABEL_NAMES.items()}
available = label_rev.keys()
if label not in available:
raise ValueError(
"%r not in available labels of %s" % (
label,
", ".join(available)))
app('Finder').items[MacFile(path)].label_index.set(label_rev[label])
if __name__ == "__main__":
# Touch file
path = "blah"
open(path, "w").close()
# Toggle label colour
if finder_label(path) == "green":
set_finder_label(path, "red")
else:
set_finder_label(path, "green")
I don’t know if this question is still relevant to anybody but there is a new package “mac-tag” that solves this issue.
pip install mac-tag
and then you have functions like:
function __doc__
mac_tag.add(tags, path) # add tags to path(s)
mac_tag.find(tags, path=None) # return a list of all paths with tags, limited to path(s) if present
mac_tag.get(path) # return dict where keys are paths, values are lists of tags. equivalent of tag -l
mac_tag.match(tags, path) # return a list of paths with with matching tags
mac_tag.parse_list_output(out) # parse tag -l output and return dict
mac_tag.remove(tags, path) # remove tags from path(s)
mac_tag.update(tags, path) # set path(s) tags. equivalent of `tag -s
complete documentation at: https://pypi.org/project/mac-tag/
Suppose you have this folder:
Notice that some files have multiple tags. One tag is Work
which is a custom tag.
There are three ways that I have used to get the Finder tags into Python.
First, use xattr
to generate a binary plist
of extended attributes from the key 'com.apple.metadata:_kMDItemUserTags'
and then convert that to a list with plistlib:
import xattr
import plistlib
def get_tags_xattr(fn):
try:
bpl=xattr.getxattr(fn, 'com.apple.metadata:_kMDItemUserTags')
rtr=[e.partition('n')[0]
for e in plistlib.loads(bpl, fmt=plistlib.FMT_BINARY)]
return rtr if rtr else None
except OSError:
return None
The second is to use the program tag which can be installed with brew
:
from subprocess import run, PIPE
def get_tags_tag(fn):
if 'com.apple.metadata:_kMDItemUserTags' in xattr.listxattr(fn):
rtr=run(['tag', '-g', '--no-name', fn],
stdout=PIPE).stdout.decode('utf-8').splitlines()
return rtr if rtr else None
return None
The third is to use the PyPi module osxmetadata:
import osxmetadata
def get_tags_osxmeta(fn):
tags=[t.name for t in osxmetadata.OSXMetaData(fn).tags]
if tags:
return tags
return None
Here is a demo of all three:
from pathlib import Path
p=Path('/tmp/test')
for fn in (fn for fn in p.glob('*') if fn.is_file()):
print(fn, get_tags_osxmeta(fn),
get_tags_tag(fn),
get_tags_xattr(fn))
Prints on that sample folder:
/tmp/test/DSC_2930-m.jpg ['Red', 'Green'] ['Green', 'Red'] ['Red', 'Green']
/tmp/test/DSC_2929.JPG None None None
/tmp/test/DSC_2939.JPG None None None
/tmp/test/DSC_2938.JPG ['Red'] ['Red'] ['Red']
/tmp/test/DSC_2937.JPG None None None
/tmp/test/DSC_2942-m.jpg ['Red', 'Orange', 'Gray', 'Work'] ['Gray', 'Orange', 'Red', 'Work'] ['Red', 'Orange', 'Gray', 'Work']
/tmp/test/DSC_2934.JPG ['Red'] ['Red'] ['Red']
/tmp/test/DSC_2930.JPG None None None
/tmp/test/DSC_2931.JPG None None None
/tmp/test/DSC_2933.JPG None None None
/tmp/test/DSC_2932.JPG ['Red'] ['Red'] ['Red']
/tmp/test/DSC_2941.JPG None None None
/tmp/test/DSC_2942.JPG None None None
The best of those (that which I use most often) is the osxmetadata module. It is fast and very flexible. If you just need the tags, the other ways work well too. The first is likely the fastest.