utitools 0.4.0
Utilities for working with Uniform Type Identifiers (UTIs)
Requires Python: >=3.9

# utitools

`utitools` is a simple Python module designed primarily for use on macOS. It allows you to:

- Retrieve the Uniform Type Identifier (UTI) for a given file suffix.
- Get the preferred file extension for a given UTI.

While designed for macOS, `utitools` also works on other platforms by falling back to a cached dictionary for UTI and extension mappings loaded via a CSV file.

## Features

- Works with CoreServices for macOS versions <= 11 (Big Sur) using Objective-C bridge for working with UTIs.
- Uses the `UniformTypeIdentifiers` framework for macOS >= 12 (Monterey).
- On platforms other than macOS, falls back to a cached dictionary for UTI and extension mappings loaded via a CSV file.
- Provides utility functions to convert between file extensions and UTIs.

## Installation

You can install `utitools` from PyPI using pip:

```bash
pip install utitools
```

Alternatively, you can install it from the source code:

1. Clone this repository.
   ```bash
   git clone https://github.com/rhettbull/utitools.git
   ```

2. Install [flit](https://flit.readthedocs.io/en/latest/) if you don't already have it.
   ```bash
   python3 -m pip install flit
   ```

3. Run `flit install` from the root of the repository.
   ```bash
   cd utitools
   flit install
   ```

## Usage

Here are the available functions:

### 1. `uti_for_suffix(suffix: str) -> str | None`

Get the UTI for a given file suffix.

```pycon
>>> from utitools import uti_for_suffix
>>> uti_for_suffix(".jpeg")
'public.jpeg'
>>> uti_for_suffix("jpg")
'public.jpeg'
>>>
```

### 2. `preferred_suffix_for_uti(uti: str) -> str | None`

Get the preferred file extension for a given UTI.

```pycon
>>> from utitools import preferred_suffix_for_uti
>>> preferred_suffix_for_uti("public.jpeg")
'.jpeg'
>>>
```

### 3. `uti_for_path(path: str | os.PathLike) -> str | None`

Get the UTI for a file at the given path based on its file extension.

```pycon
>>> from utitools import uti_for_path
>>> uti_for_path("/tmp/screenshot.png")
'public.png'
>>>
```

### 4. `content_type_tree_for_uti(uti: str) -> list[str]`

Get the UTI content type tree for a given UTI. This is hierarchical list of UTIs that the given UTI conforms to.

```pycon
>>> from utitools import content_type_tree_for_uti
>>> content_type_tree_for_uti("public.heic")
['public.heic', 'public.heif-standard', 'public.image', 'public.data', 'public.item', 'public.content']
>>>
```

### 5. `conforms_to_uti(uti1: str, uti2: str) -> bool`

Return True if `uti1` conforms to `uti2`, otherwise False.

This is useful for checking if a UTI conforms to a parent UTI. For example, to check if a given UTI is an image:

```pycon
>>> from utitools import conforms_to_uti
>>> conforms_to_uti("public.jpeg", "public.image")
True
>>> conforms_to_uti("public.jpeg", "public.video")
False
>>>
```

These functions can be combined in useful ways. For example, the following shows a simple `is_image()` function that provides a quick way to check if a file is an image:

```pycon
>>> from utitools import uti_for_path, conforms_to_uti
>>> def is_image(path):
...     return conforms_to_uti(uti_for_path(path), "public.image")
...
>>> is_image("img_1234.jpg")
True
>>> is_image("img_1234.txt")
False
>>>
```

## macOS Version Compatibility

The code path of `utitools` changes depending on the macOS version:

- **macOS ≤ 11 (Big Sur)**: Uses the deprecated methods `UTTypeCopyPreferredTagWithClass` and `UTTypeCreatePreferredIdentifierForTag` from `CoreServices`.
- **macOS ≥ 12 (Monterey)**: Uses the modern `UniformTypeIdentifiers` module.

## Non-macOS Usage

On non-macOS platforms, `utitools` does not have direct access to macOS UTI APIs. Instead, it relies on a cached dictionary loaded from a CSV (`uti.csv`) containing mappings of file extensions and UTIs. This provides a level of compatibility for platforms like Windows or Linux.

The CSV file must be generated using the script `generate_uti_csv.py` on macOS. The script calls the macOS APIs for every possible file extension under a specified lenght and writes the mappings to the CSV file. The CSV file must then be placed in `src/utitools`. This file is then used by `utitools` on non-macOS platforms. This is a hack but it works. PRs are welcome to improve this or provide a native way to get UTIs on non-macOS platforms.

There is also a `uti_tree.json` file that is used to look up content trees on non-macOS systems. This file is generated by the `generate_uti_tree.py` script which must be run on macOS then placed in the `src/utitools` directory.

I use this library primarily to get UTIs for various image and video formats on macOS so this process is sufficient for my uses.

On non-macOS platforms, only UTIs known to macOS Ventura for suffixes up to 6 characters are supported. This is because the CSV file is generated using the `generate_uti_csv.py` script which only generates mappings for suffixes up to 6 characters. This covers all my use cases. PRs are welcome to improve this.


## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

## Packaging with pyinstaller or other tools

To package `utitools` with `pyinstaller` or other tools, you may need to include the `uti.csv` and `uti_tree.json` files in your distribution manifest. These files are located in the `src/utitools` directory. Directions will depend on the tool you are using.

---

Feel free to contribute or submit issues via [GitHub issues](https://github.com/rhettbull/utitools/issues).