diff --git a/README.md b/README.md index b59f231..d3fddfe 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The command line tool can also be run via `python -m osxmetadata`. Running it w ``` Usage: osxmetadata [OPTIONS] FILE - + Read/write metadata from file(s). Options: @@ -116,6 +116,10 @@ blue, yellow, red, orange. If color is not specified but a tag of the same name has already been assigned a color in the Finder, the same color will automatically be assigned. +com.apple.FinderInfo (finderinfo) value is a key:value dictionary. To set +finderinfo, pass value in format key1:value1,key2:value2,etc. For example: +'osxmetadata --set finderinfo color:2 file.ext'. + Short Name Description authors kMDItemAuthors, com.apple.metadata:kMDItemAuthors; The author, or authors, of the contents of the file. A list of @@ -148,15 +152,22 @@ duedate kMDItemDueDate, com.apple.metadata:kMDItemDueDate; The date zone), or 2020-04-14T12:00:00-07:00 (ISO 8601 with timezone offset). Times without timezone offset are assumed to be in local timezone. -findercomment kMDItemFinderComment, - com.apple.metadata:kMDItemFinderComment; Finder comments for - this file. A string. -finderinfo FinderInfo, com.apple.FinderInfo; Color tag set by the +findercolor findercolor, com.apple.FinderInfo; Color tag set by the Finder. Colors can also be set by _kMDItemUserTags. This is controlled by the Finder and it's recommended you don't directly access this attribute. If you set or remove a color tag via _kMDItemUserTag, osxmetadata will automatically handle processing of FinderInfo color tag. +findercomment kMDItemFinderComment, + com.apple.metadata:kMDItemFinderComment; Finder comments for + this file. A string. +finderinfo finderinfo, com.apple.FinderInfo; Info set by the Finder, + for example tag color. Colors can also be set by + _kMDItemUserTags. com.apple.FinderInfo is controlled by the + Finder and it's recommended you don't directly access this + attribute. If you set or remove a color tag via + _kMDItemUserTag, osxmetadata will automatically handle + processing of FinderInfo color tag. headline kMDItemHeadline, com.apple.metadata:kMDItemHeadline; A publishable entry providing a synopsis of the contents of the file. A string. @@ -206,8 +217,9 @@ Information about commonly used MacOS metadata attributes is available from [App |kMDItemDescription|description|com.apple.metadata:kMDItemDescription|A description of the content of the resource. The description may include an abstract, table of contents, reference to a graphical representation of content or a free-text account of the content. A string.| |kMDItemDownloadedDate|downloadeddate|com.apple.metadata:kMDItemDownloadedDate|The date the item was downloaded. A datetime.datetime object. If datetime.datetime object lacks tzinfo (i.e. it is timezone naive), it will be assumed to be in local timezone.| |kMDItemDueDate|duedate|com.apple.metadata:kMDItemDueDate|The date the item is due. A datetime.datetime object. If datetime.datetime object lacks tzinfo (i.e. it is timezone naive), it will be assumed to be in local timezone.| +|findercolor|findercolor|com.apple.FinderInfo|Color tag set by the Finder. Colors can also be set by _kMDItemUserTags. This is controlled by the Finder and it's recommended you don't directly access this attribute. If you set or remove a color tag via _kMDItemUserTag, osxmetadata will automatically handle processing of FinderInfo color tag.| |kMDItemFinderComment|findercomment|com.apple.metadata:kMDItemFinderComment|Finder comments for this file. A string.| -|FinderInfo|finderinfo|com.apple.FinderInfo|Color tag set by the Finder. Colors can also be set by _kMDItemUserTags. This is controlled by the Finder and it's recommended you don't directly access this attribute. If you set or remove a color tag via _kMDItemUserTag, osxmetadata will automatically handle processing of FinderInfo color tag.| +|finderinfo|finderinfo|com.apple.FinderInfo|Info set by the Finder, for example tag color. Colors can also be set by _kMDItemUserTags. com.apple.FinderInfo is controlled by the Finder and it's recommended you don't directly access this attribute. If you set or remove a color tag via _kMDItemUserTag, osxmetadata will automatically handle processing of FinderInfo color tag.| |kMDItemHeadline|headline|com.apple.metadata:kMDItemHeadline|A publishable entry providing a synopsis of the contents of the file. A string.| |kMDItemKeywords|keywords|com.apple.metadata:kMDItemKeywords|Keywords associated with this file. For example, “Birthday”, “Important”, etc. This differs from Finder tags (_kMDItemUserTags) which are keywords/tags shown in the Finder and searchable in Spotlight using "tag:tag_name". A list of strings.| |kMDItemParticipants|participants|com.apple.metadata:kMDItemParticipants|The list of people who are visible in an image or movie or written about in a document. A list of strings.| diff --git a/osxmetadata/__main__.py b/osxmetadata/__main__.py index 17feedd..e44f204 100644 --- a/osxmetadata/__main__.py +++ b/osxmetadata/__main__.py @@ -15,9 +15,14 @@ import osxmetadata from ._version import __version__ -from .attributes import _LONG_NAME_WIDTH, _SHORT_NAME_WIDTH, ATTRIBUTES +from .attributes import ( + _LONG_NAME_WIDTH, + _SHORT_NAME_WIDTH, + ATTRIBUTE_DISPATCH, + ATTRIBUTES, +) from .backup import load_backup_file, write_backup_file -from .classes import _AttributeList, _AttributeTagsList, _AttributeFinderInfo +from .classes import _AttributeFinderInfo, _AttributeList, _AttributeTagsList from .constants import ( _BACKUP_FILENAME, _COLORNAMES_LOWER, @@ -826,11 +831,13 @@ def process_single_file( attribute_list = md.list_metadata() for attr in attribute_list: try: - attribute = ATTRIBUTES[attr] - value = md.get_attribute_str(attribute.name) - click.echo( - f"{attribute.name:{_SHORT_NAME_WIDTH}}{attribute.constant:{_LONG_NAME_WIDTH}} = {value}" - ) + attribute_names = ATTRIBUTE_DISPATCH[attr] + for name in attribute_names: + attribute = ATTRIBUTES[name] + value = md.get_attribute_str(attribute.name) + click.echo( + f"{attribute.name:{_SHORT_NAME_WIDTH}}{attribute.constant:{_LONG_NAME_WIDTH}} = {value}" + ) except KeyError: click.echo( f"{'UNKNOWN':{_SHORT_NAME_WIDTH}}{attr:{_LONG_NAME_WIDTH}} = THIS ATTRIBUTE NOT HANDLED", diff --git a/osxmetadata/_version.py b/osxmetadata/_version.py index 6684500..9c4586c 100644 --- a/osxmetadata/_version.py +++ b/osxmetadata/_version.py @@ -1,3 +1,3 @@ """ osxmetadata version """ -__version__ = "0.99.20" +__version__ = "0.99.21" diff --git a/osxmetadata/attributes.py b/osxmetadata/attributes.py index 860034b..fd31a7a 100644 --- a/osxmetadata/attributes.py +++ b/osxmetadata/attributes.py @@ -66,284 +66,284 @@ ATTRIBUTES = { "authors": Attribute( - "authors", - "kMDItemAuthors", - kMDItemAuthors, - str, - True, - False, - _AttributeList, - True, - True, - "The author, or authors, of the contents of the file. A list of strings.", - None, + name="authors", + short_constant="kMDItemAuthors", + constant=kMDItemAuthors, + type_=str, + list=True, + as_list=False, + class_=_AttributeList, + append=True, + update=True, + help="The author, or authors, of the contents of the file. A list of strings.", + api_help=None, ), "comment": Attribute( - "comment", - "kMDItemComment", - kMDItemComment, - str, - False, - False, - str, - True, - False, - "A comment related to the file. This differs from the Finder comment, " + name="comment", + short_constant="kMDItemComment", + constant=kMDItemComment, + type_=str, + list=False, + as_list=False, + class_=str, + append=True, + update=False, + help="A comment related to the file. This differs from the Finder comment, " + "kMDItemFinderComment. A string.", - None, + api_help=None, ), "copyright": Attribute( - "copyright", - "kMDItemCopyright", - kMDItemCopyright, - str, - False, - False, - str, - True, - False, - "The copyright owner of the file contents. A string.", - None, + name="copyright", + short_constant="kMDItemCopyright", + constant=kMDItemCopyright, + type_=str, + list=False, + as_list=False, + class_=str, + append=True, + update=False, + help="The copyright owner of the file contents. A string.", + api_help=None, ), "creator": Attribute( - "creator", - "kMDItemCreator", - kMDItemCreator, - str, - False, - False, - str, - True, - False, - "Application used to create the document content (for example “Word”, “Pages”, " + name="creator", + short_constant="kMDItemCreator", + constant=kMDItemCreator, + type_=str, + list=False, + as_list=False, + class_=str, + append=True, + update=False, + help="Application used to create the document content (for example “Word”, “Pages”, " + "and so on). A string.", - None, + api_help=None, ), "description": Attribute( - "description", - "kMDItemDescription", - kMDItemDescription, - str, - False, - False, - str, - True, - False, - "A description of the content of the resource. The description may include an abstract, " + name="description", + short_constant="kMDItemDescription", + constant=kMDItemDescription, + type_=str, + list=False, + as_list=False, + class_=str, + append=True, + update=False, + help="A description of the content of the resource. The description may include an abstract, " + "table of contents, reference to a graphical representation of content or a " + "free-text account of the content. A string.", - None, + api_help=None, ), "downloadeddate": Attribute( - "downloadeddate", - "kMDItemDownloadedDate", - kMDItemDownloadedDate, - datetime.datetime, - True, - False, - _AttributeList, - False, - False, - "The date the item was downloaded. A date in ISO 8601 format, " + name="downloadeddate", + short_constant="kMDItemDownloadedDate", + constant=kMDItemDownloadedDate, + type_=datetime.datetime, + list=True, + as_list=False, + class_=_AttributeList, + append=False, + update=False, + help="The date the item was downloaded. A date in ISO 8601 format, " "time and timezone offset are optional: e.g. " + "2020-04-14T12:00:00 (ISO 8601 w/o timezone), " + "2020-04-14 (ISO 8601 w/o time and time zone), or " + "2020-04-14T12:00:00-07:00 (ISO 8601 with timezone offset). " + "Times without timezone offset are assumed to be in local timezone.", - "The date the item was downloaded. A datetime.datetime object. " + api_help="The date the item was downloaded. A datetime.datetime object. " + "If datetime.datetime object lacks tzinfo (i.e. it is timezone naive), it " + "will be assumed to be in local timezone.", ), "findercomment": Attribute( - "findercomment", - "kMDItemFinderComment", - kMDItemFinderComment, - str, - False, - False, - str, - True, - False, - "Finder comments for this file. A string.", - None, + name="findercomment", + short_constant="kMDItemFinderComment", + constant=kMDItemFinderComment, + type_=str, + list=False, + as_list=False, + class_=str, + append=True, + update=False, + help="Finder comments for this file. A string.", + api_help=None, ), "headline": Attribute( - "headline", - "kMDItemHeadline", - kMDItemHeadline, - str, - False, - False, - str, - True, - False, - "A publishable entry providing a synopsis of the contents of the file. A string.", - None, + name="headline", + short_constant="kMDItemHeadline", + constant=kMDItemHeadline, + type_=str, + list=False, + as_list=False, + class_=str, + append=True, + update=False, + help="A publishable entry providing a synopsis of the contents of the file. A string.", + api_help=None, ), "keywords": Attribute( - "keywords", - "kMDItemKeywords", - kMDItemKeywords, - str, - True, - False, - _AttributeList, - True, - True, - "Keywords associated with this file. For example, “Birthday”, “Important”, etc. " + name="keywords", + short_constant="kMDItemKeywords", + constant=kMDItemKeywords, + type_=str, + list=True, + as_list=False, + class_=_AttributeList, + append=True, + update=True, + help="Keywords associated with this file. For example, “Birthday”, “Important”, etc. " + "This differs from Finder tags (_kMDItemUserTags) which are keywords/tags shown " + 'in the Finder and searchable in Spotlight using "tag:tag_name". ' + "A list of strings.", - None, + api_help=None, ), "tags": Attribute( - "tags", - "_kMDItemUserTags", - _kMDItemUserTags, - str, - True, - False, - _AttributeTagsList, - True, - True, - 'Finder tags; searchable in Spotlight using "tag:tag_name". ' + name="tags", + short_constant="_kMDItemUserTags", + constant=_kMDItemUserTags, + type_=str, + list=True, + as_list=False, + class_=_AttributeTagsList, + append=True, + update=True, + help='Finder tags; searchable in Spotlight using "tag:tag_name". ' + "If you want tags/keywords visible in the Finder, use this instead of kMDItemKeywords. " + "A list of Tag objects.", - None, + api_help=None, ), "wherefroms": Attribute( - "wherefroms", - "kMDItemWhereFroms", - kMDItemWhereFroms, - str, - True, - False, - _AttributeList, - True, - True, - "Describes where the file was obtained from (e.g. URL downloaded from). " + name="wherefroms", + short_constant="kMDItemWhereFroms", + constant=kMDItemWhereFroms, + type_=str, + list=True, + as_list=False, + class_=_AttributeList, + append=True, + update=True, + help="Describes where the file was obtained from (e.g. URL downloaded from). " + "A list of strings.", - None, + api_help=None, ), "finderinfo": Attribute( - "finderinfo", - "finderinfo", - FinderInfo, - str, - False, - False, - _AttributeFinderInfo, - False, - False, - "Info set by the Finder, for example tag color. Colors can also be set by _kMDItemUserTags. " + name="finderinfo", + short_constant="finderinfo", + constant=FinderInfo, + type_=str, + list=False, + as_list=False, + class_=_AttributeFinderInfo, + append=False, + update=False, + help="Info set by the Finder, for example tag color. Colors can also be set by _kMDItemUserTags. " + f"{FinderInfo} is controlled by the Finder and it's recommended you don't directly access this attribute. " + "If you set or remove a color tag via _kMDItemUserTag, osxmetadata will automatically handle " + "processing of FinderInfo color tag.", - None, + api_help=None, ), "duedate": Attribute( - "duedate", - "kMDItemDueDate", - kMDItemDueDate, - datetime.datetime, - False, - False, - datetime.datetime, - False, - False, - "The date the item is due. A date in ISO 8601 format, " + name="duedate", + short_constant="kMDItemDueDate", + constant=kMDItemDueDate, + type_=datetime.datetime, + list=False, + as_list=False, + class_=datetime.datetime, + append=False, + update=False, + help="The date the item is due. A date in ISO 8601 format, " "time and timezone offset are optional: e.g. " + "2020-04-14T12:00:00 (ISO 8601 w/o timezone), " + "2020-04-14 (ISO 8601 w/o time and time zone), or " + "2020-04-14T12:00:00-07:00 (ISO 8601 with timezone offset). " + "Times without timezone offset are assumed to be in local timezone.", - "The date the item is due. A datetime.datetime object. " + api_help="The date the item is due. A datetime.datetime object. " + "If datetime.datetime object lacks tzinfo (i.e. it is timezone naive), it " + "will be assumed to be in local timezone.", ), "rating": Attribute( - "rating", - "kMDItemStarRating", - kMDItemStarRating, - int, - False, - False, - int, - False, - False, - "User rating of this item. " + name="rating", + short_constant="kMDItemStarRating", + constant=kMDItemStarRating, + type_=int, + list=False, + as_list=False, + class_=int, + append=False, + update=False, + help="User rating of this item. " + "For example, the stars rating of an iTunes track. An integer.", - None, + api_help=None, ), "participants": Attribute( - "participants", - "kMDItemParticipants", - kMDItemParticipants, - str, - True, - False, - _AttributeList, - True, - True, - "The list of people who are visible in an image or movie or written about in a document. " + name="participants", + short_constant="kMDItemParticipants", + constant=kMDItemParticipants, + type_=str, + list=True, + as_list=False, + class_=_AttributeList, + append=True, + update=True, + help="The list of people who are visible in an image or movie or written about in a document. " + "A list of strings.", - None, + api_help=None, ), "projects": Attribute( - "projects", - "kMDItemProjects", - kMDItemProjects, - str, - True, - False, - _AttributeList, - True, - True, - "The list of projects that this file is part of. For example, if you were working on a movie all of the files could be marked as belonging to the project “My Movie”. " + name="projects", + short_constant="kMDItemProjects", + constant=kMDItemProjects, + type_=str, + list=True, + as_list=False, + class_=_AttributeList, + append=True, + update=True, + help="The list of projects that this file is part of. For example, if you were working on a movie all of the files could be marked as belonging to the project “My Movie”. " + "A list of strings.", - None, + api_help=None, ), "version": Attribute( - "version", - "kMDItemVersion", - kMDItemVersion, - str, - False, - False, - str, - True, - False, - "The version number of this file. A string.", - None, + name="version", + short_constant="kMDItemVersion", + constant=kMDItemVersion, + type_=str, + list=False, + as_list=False, + class_=str, + append=True, + update=False, + help="The version number of this file. A string.", + api_help=None, ), "stationary": Attribute( - "stationary", - "kMDItemFSIsStationery", - kMDItemFSIsStationery, - bool, - False, - False, - bool, - False, - False, - "Boolean indicating if this file is stationery.", - None, + name="stationary", + short_constant="kMDItemFSIsStationery", + constant=kMDItemFSIsStationery, + type_=bool, + list=False, + as_list=False, + class_=bool, + append=False, + update=False, + help="Boolean indicating if this file is stationery.", + api_help=None, ), "findercolor": Attribute( - "findercolor", - "findercolor", - FinderInfo, - str, - False, - False, - _AttributeFinderColor, - False, - False, - "Color tag set by the Finder. Colors can also be set by _kMDItemUserTags. " + name="findercolor", + short_constant="findercolor", + constant=FinderInfo, + type_=str, + list=False, + as_list=False, + class_=_AttributeFinderColor, + append=False, + update=False, + help="Color tag set by the Finder. Colors can also be set by _kMDItemUserTags. " + "This is controlled by the Finder and it's recommended you don't directly access this attribute. " + "If you set or remove a color tag via _kMDItemUserTag, osxmetadata will automatically handle " + "processing of FinderInfo color tag.", - None, + api_help=None, ), } @@ -352,6 +352,14 @@ _LONG_NAME_WIDTH = max(len(x.constant) for x in ATTRIBUTES.values()) + 10 _CONSTANT_WIDTH = 21 + 5 # currently longest is kMDItemDownloadedDate + +ATTRIBUTE_DISPATCH = {} +for attr in ATTRIBUTES.values(): + try: + ATTRIBUTE_DISPATCH[attr.constant].append(attr.name) + except KeyError: + ATTRIBUTE_DISPATCH[attr.constant] = [attr.name] + # also add entries for attributes by constant and short constant # do this after computing widths above _temp_attributes = {} @@ -363,7 +371,6 @@ if _temp_attributes: ATTRIBUTES.update(_temp_attributes) - def validate_attribute_value(attribute, value): """validate that value is compatible with attribute.type_ and convert value to correct type diff --git a/osxmetadata/classes.py b/osxmetadata/classes.py index f7218f7..1269c12 100644 --- a/osxmetadata/classes.py +++ b/osxmetadata/classes.py @@ -48,6 +48,10 @@ def set_value(self, value): self.data = value self._write_data() + def get_value(self): + self._load_data() + return self.data + def _load_data(self): self._values = [] try: @@ -182,6 +186,10 @@ def set_value(self, value): self.data = value self._write_data() + def get_value(self): + self._load_data() + return self.data + def _load_data(self): self._tags = {} @@ -291,8 +299,12 @@ def set_value(self, value): self.data = {"color": value} self._write_data() + def get_value(self): + self._load_data() + return self.data.get("color", None) + def __repr__(self): - return repr(self.data.get("color", None)) + return repr(self.get_value()) def __eq__(self, other): self._load_data() diff --git a/osxmetadata/osxmetadata.py b/osxmetadata/osxmetadata.py index 108b5f7..709f36e 100644 --- a/osxmetadata/osxmetadata.py +++ b/osxmetadata/osxmetadata.py @@ -293,15 +293,8 @@ def get_attribute(self, attribute_name): attribute = ATTRIBUTES[attribute_name] # user tags and finderinfo need special processing - if attribute.name == "tags": - self.tags._load_data() - return self.tags.data - elif attribute.name == "finderinfo": - self.finderinfo._load_data() - return self.finderinfo.data - elif attribute.name == "findercolor": - self.findercolor._load_data() - return self.findercolor.data + if attribute.name in ["tags", "finderinfo", "findercolor"]: + return getattr(self, attribute.name).get_value() # must be a "normal" metadata attribute try: