Osxmetadata
Python package to read and write various MacOS extended attribute metadata such as tags/keywords and Finder comments from files. Includes CLI tool for reading/writing metadata.
Install / Use
/learn @RhetTbull/OsxmetadataREADME
osxmetadata
What is osxmetadata?
osxmetadata provides a simple interface to access various metadata about MacOS / OS X files. Currently supported metadata attributes include tags/keywords, Finder comments, authors, etc.
Why osxmetadata?
Apple provides rich support for file metadata through the MDItem class and the NSURL getResourceValue:forKey:error: method. However, Apple does not provide a way to easily set much of the metadata. For example, while there is a documented MDItem MDItemCopyAttribute to copy metadata attributes such as kMDItemAuthors, Apple does not provide a public interface to set this data. Other data, such as Finder comments, can only be set through sending AppleScript commands to the Finder and others, like Finder tags can be retrieved but cannot be set through the public API.
osxmetadata provides a unified interface to get and set most of the metadata available on your Mac from python. It uses a combination of documented and undocumented APIs to access the metadata. It also provides a simple interface to set Finder tags and Finder comments.
MacOS provides some tools to view these various metadata attributes. For example, mdls lists the MDItem Spotlight metadata associated with a file but doesn't let you edit the data. osxmetadata makes it easy to to both view and manipulate the macOS metadata attributes, either programmatically or through an included osxmetadata command line tool.
Supported operating systems
Only works on MacOS. Tested on macOS 13.5.1 (Ventura); should work on all versions of macOS 10.15 and later. Supported on Python 3.10+.
Installation instructions
Installation using pipx
If you aren't familiar with installing python applications, I recommend you install osxmetadata with pipx. If you use pipx, you will not need to create a virtual environment as pipx takes care of this. The easiest way to do this on a Mac is to use homebrew:
- Open
Terminal(search forTerminalin Spotlight or look inApplications/Utilities) - Install
homebrewaccording to instructions at https://brew.sh/ - Type the following into Terminal:
brew install pipx - Then type this:
pipx install osxmetadata - Now you should be able to run
osxmetadataby typing:osxmetadata
Once you've installed osxmetadata with pipx, to upgrade to the latest version:
pipx upgrade osxmetadata
Installation using pip
You can also install directly from pypi:
pip install osxmetadata
Once you've installed osxmetadata with pip, to upgrade to the latest version:
pip install --upgrade osxmetadata
Installation from git repository
OSXMetaData uses setuptools, thus simply run:
git clone https://github.com/RhetTbull/osxmetadata.git
cd osxmetadata
pip install poetry
poetry install
I recommend you create a virtual environment before installing osxmetadata.
Using the API
>>> import datetime
>>> import pathlib
>>> from osxmetadata import *
>>> pathlib.Path("test_file.txt").touch()
>>> md = OSXMetaData("test_file.txt")
>>> md.set(kMDItemAuthors, ["Jane Smith", "John Doe"])
>>> md.get(kMDItemAuthors)
['Jane Smith', 'John Doe']
>>> md.kMDItemFinderComment = "This is my comment"
>>> md.kMDItemFinderComment
'This is my comment'
>>> md.tags = [Tag("Test", FINDER_COLOR_NONE), Tag("ToDo", FINDER_COLOR_RED)]
>>> md.tags
[Tag(name='Test', color=0), Tag(name='ToDo', color=6)]
>>> md[kMDItemDueDate] = datetime.datetime(2022,10,1)
>>> md[kMDItemDueDate]
datetime.datetime(2022, 10, 1, 0, 0)
>>>
Somewhat contrary to the Zen of Python, osxmetadata provides more than one way to access metadata attributes. You can get and set metadata attributes using the get()/set() getter/setter methods, using the attribute name as a dictionary key on the OSXMetaData object, or using the attribute name as an attribute on the OSXMetaData() object. For example, the following are all equivalent:
OSXMetaData.get(attribute)- get the value of the metadata attributeattributeOSXMetaData[attribute]- get the value of the metadata attributeattributeOSXMetaData.attribute- get the value of the metadata attributeattribute
As are the following:
OSXMetaData.set(attribute, value)- set the value of the metadata attributeattributetovalueOSXMetaData[attribute] = value- set the value of the metadata attributeattributetovalueOSXMetaData.attribute = value- set the value of the metadata attributeattributetovalue
This allows you to use osxmetadata in accordance with your own code style preferences.
Supported attribute names include all attributes defined for MDItem and all resource keys defined for NSURL. Additionally, the metadata constants defined in the MDImporter are supported as well as the following additional attributes:
_kMDItemUserTags- list of Finder tagskMDItemDownloadedDate- list of datetime objects for when the file was downloaded
Additionally, osxmetadata defines a "shortcut name" attribute for each MDItem attribute that can be used as a shortcut OSXMetaData class attribute. The shortcut name is the lowercase value of text following kMDItem for each attribute. For example, kMDItemAuthors has a short name of authors so you can set the authors like this:
>>> from osxmetadata import *
>>> md = OSXMetaData("test_file.txt")
>>> md.authors = ["Jane Smith", "John Doe"]
>>> md.authors
['Jane Smith', 'John Doe']
>>>
and kMDItemDueDate would have a short name of duedate:
>>> from osxmetadata import *
>>> import datetime
>>> md = OSXMetaData("test_file.txt")
>>> md.duedate = datetime.datetime(2022, 10, 1)
>>> md.duedate
datetime.datetime(2022, 10, 1, 0, 0)
>>>
The names of all supported attributes are available in the osxmetadata.ALL_ATTRIBUTES set:
>>> from osxmetadata import ALL_ATTRIBUTES
>>> "kMDItemDueDate" in ALL_ATTRIBUTES
True
>>> "NSURLTagNamesKey" in ALL_ATTRIBUTES
True
>>> "findercomment" in ALL_ATTRIBUTES
True
>>>
The class attributes are handled dynamically which, unfortunately, means that IDEs like PyCharm and Visual Studio Code cannot provide tab-completion for them.
[!NOTE] When writing or updating metadata with OSXMetaData, the OS will take some time to update the metadata on disk; in my testing, this can be as short as 100ms or as long as 3s. This means that if you read the metadata immediately after writing it, you may not see the updated metadata. If your use case requires the use of immediate read after write, you may need to implement a delay in your code to allow the OS time to update the metadata on disk. This appears to be an OS limitation and not something that can be controlled by osxmetadata.
## Finder Tags
Unlike other attributes, which are mapped to native Python types appropriate for the source Objective C type, Finder tags (`_kMDItemUserTags` or `tags`) have two components: a name (str) and a color ID (unsigned int in range 0 to 7) representing a color tag in the Finder. Reading tags returns a list of `Tag` namedtuples and setting tags requires a list of `Tag` namedtuples.
```pycon
>>> from osxmetadata import *
>>> md = OSXMetaData("test_file.txt")
>>> md.tags = [Tag("Test", FINDER_COLOR_NONE), Tag("ToDo", FINDER_COLOR_RED)]
>>> md.tags
[Tag(name='Test', color=0), Tag(name='ToDo', color=6)]
>>> md.get("_kMDItemUserTags")
[Tag(name='Test', color=0), Tag(name='ToDo', color=6)]
>>>
Tag names (but not colors) can also be accessed through the NSURLTagNamesKey resource key and the label color ID is accessible through NSURLLabelNumberKey; the localized label color name is accessible through NSURLLocalizedLabelKey though these latter two resource keys only return a single color whereas a file may have more than one color tag. For most purposes, I recommend using the tags attribute as it is more convenient and provides access to both the name and color ID of the tag.
>>> from osxmetadata import *
>>> md = OSXMetaData("test_file.txt")
>>> md.tags = [Tag("Test", FINDER_COLOR_NONE), Tag("ToDo", FINDER_COLOR_RED)]
>>> md.tags
[Tag(name='Test', color=0), Tag(name='ToDo', color=6)]
>>> md.NSURLTagNamesKey
(
Test,
ToDo
)
>>> md.NSURLLabelNumberKey
6
>>> md.NSURLLocalizedLabelKey
'Red'
>>> md.NSURLTagNamesKey = ["NewTag"]
>>> md.NSURLTagNamesKey
(
NewTag
)
>>> md.tags
[Tag(name='NewTag', color=0)]
>>>
Create a Tag namedtuple
Tag(name, color)
name: tag name (str)color: color ID for Finder color label associated with tag (int)
Valid color constants (exported by osxmetadata):
FINDER_COLOR_NONE= 0FINDER_COLOR_GRAY= 1FINDER_COLOR_GREEN= 2FINDER_COLOR_PURPLE= 3FINDER_COLOR_BLUE= 4FINDER_COLOR_YELLOW= 5
