Metadata-Version: 2.3
Name: voice-memo-export
Version: 0.2.0
Summary: A Python tool for exporting Apple Voice Memos effortlessly. Provides a simple solution to back up your voice memos.
License: MIT
Author: Robin Schulz
Author-email: bulletinmybeard@gmail.com
Requires-Python: >=3.12,<3.14
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Dist: aiosqlite (>=0.22.1,<0.23.0)
Requires-Dist: colorama (>=0.4.6,<0.5.0)
Requires-Dist: jinja2 (>=3.1.2,<4.0.0)
Requires-Dist: psutil (>=7.2.2,<8.0.0)
Requires-Dist: pydantic-settings (>=2.14.1,<3.0.0)
Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
Requires-Dist: python-slugify (>=8.0.4,<9.0.0)
Project-URL: Homepage, https://rschu.me/
Project-URL: Repository, https://github.com/bulletinmybeard/voice-memo-export
Description-Content-Type: text/markdown

# voice-memo-export

[![CI](https://github.com/bulletinmybeard/voice-memo-export/actions/workflows/ci.yml/badge.svg)](https://github.com/bulletinmybeard/voice-memo-export/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/bulletinmybeard/voice-memo-export?display_name=tag&sort=semver)](https://github.com/bulletinmybeard/voice-memo-export/releases)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/)
[![Platform](https://img.shields.io/badge/platform-macOS-lightgrey)](https://www.apple.com/macos/)
[![Poetry](https://img.shields.io/badge/packaging-poetry-informational)](https://python-poetry.org/)

[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![mypy](https://img.shields.io/badge/types-mypy-2A6DB0.svg)](https://mypy-lang.org/)

A Python CLI tool to export Apple Voice Memos on macOS to a directory of your choice, with customizable filenames.

## Why this script?

Recording many voice memos on Apple devices will over time clutter every device synced via iCloud and eats into your iCloud storage. This tool gives you a clean local copy you can archive, organise, or back up however you want.

The script is **non-destructive**: original recordings in the Voice Memos library are never modified or deleted.

## Features

- Command-line interface for quick exports
- Export all Voice Memos in one run
- Preserves original recording timestamps on exported files
- Flexible filename formatting using Jinja2 variables and filters
- Auto-detects the Voice Memos database location (overridable via `--source`)
- Skips files that already exist at the destination, so re-runs are safe
- Tested on macOS Monterey / Ventura / Sonoma / Sequoia / Tahoe

## Prerequisites

- macOS Monterey (12) or later
- Python 3.12 or higher
- Poetry 1.8.0 or higher
- **Full Disk Access** granted to your terminal application (see below)

### Granting Full Disk Access

Recent versions of macOS protect the Voice Memos database under TCC (Transparency, Consent, and Control). Without Full Disk Access, the script cannot read the source database and will fail with a permission error.

To grant access:

1. Open **System Settings** → **Privacy & Security** → **Full Disk Access**
2. Add your terminal application (Terminal, iTerm2, Warp, etc.)
3. Restart the terminal

If you run the script via an IDE's integrated terminal, that IDE needs the permission instead.

## Installation

Install Poetry if you haven't already:

```bash
curl -sSL https://install.python-poetry.org | python3 -
```

Clone this repository:

```bash
git clone https://github.com/bulletinmybeard/voice-memo-export.git
cd voice-memo-export
```

Install dependencies:

```bash
poetry install
```

## Usage

### Basic usage

Run with default settings (exports to `~/Voice Memos Export`):

```bash
poetry run vmexport
```

Run with custom settings:

```bash
poetry run vmexport \
  --output ~/Documents/VoiceMemoBackup \
  --format "{{ZDATE.strftime('%Y%m%d')}}_{{ZENCRYPTEDTITLE}}"
```

Get help:

```bash
poetry run vmexport --help
```

### CLI arguments

| Argument   | Short | Description                                  | Default                                                                                          |
| ---------- | ----- | -------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `--source` | `-s`  | Path to the Voice Memos source directory     | Auto-detected based on macOS version                                                             |
| `--output` | `-o`  | Path to the export folder                    | `~/Voice Memos Export`                                                                           |
| `--format` | `-f`  | Jinja2 template for the exported filename    | `{{ZDATE.strftime('%Y%m%d%H%M%S')}}_{{ZENCRYPTEDTITLE\|replace(' ', '_')}}_{{ZUNIQUEID}}`        |

### Output

Exported files keep their original `.m4a` extension; no transcoding is performed. The extension is appended automatically, so your `--format` template should describe the filename stem only (no `.m4a` needed).

If a file with the same name already exists at the destination, it's skipped. This makes incremental exports safe: run the command again later and only new memos are copied.

## Filename format

The `--format` option customises the filename of each exported memo using a Jinja2 template.

### Available variables

| Variable                     | Description                                          | Example value                            |
| ---------------------------- | ---------------------------------------------------- | ---------------------------------------- |
| `{{ZDATE}}`                  | Recording date and time (datetime object)            | `2024-03-17 14:30:22`                    |
| `{{ZDURATION}}`              | Duration in seconds (float)                          | `180.5`                                  |
| `{{ZENCRYPTEDTITLE}}`        | Memo title                                           | `Meeting Notes`                          |
| `{{ZCUSTOMLABEL}}`           | Custom label, if set                                 | `Important`                              |
| `{{ZCUSTOMLABELFORSORTING}}` | Custom label used for sorting                        | `Work - Project A`                       |
| `{{ZUNIQUEID}}`              | Unique identifier for the memo                       | `A1B2C3D4-E5F6-G7H8-I9J0-K1L2M3N4O5P6`   |
| `{{ZFLAGS}}`                 | Internal flags (integer)                             | `4`                                      |

### Filters and functions

Standard Jinja2 filters are available, plus a custom `slugify` filter added by this tool.

| Filter / function | Description                       | Example usage                                    | Example output                       |
| ----------------- | --------------------------------- | ------------------------------------------------ | ------------------------------------ |
| `strftime()`      | Format a datetime                 | `{{ZDATE.strftime('%Y-%m-%d')}}`                 | `2024-03-17`                         |
| `replace()`       | Replace characters                | `{{ZENCRYPTEDTITLE\|replace(' ', '_')}}`         | `Meeting_Notes`                      |
| `truncate()`      | Limit string length               | `{{ZENCRYPTEDTITLE\|truncate(20, true, '')}}`    | `Meeting Notes for P`                |
| `slugify` *(custom)* | Slugify a string               | `{{ZENCRYPTEDTITLE\|slugify}}`                   | `ai-machine-learning-trends-2024`    |
| `lower`           | Convert to lowercase              | `{{ZENCRYPTEDTITLE\|lower}}`                     | `meeting notes`                      |
| `upper`           | Convert to uppercase              | `{{ZENCRYPTEDTITLE\|upper}}`                     | `MEETING NOTES`                      |

When using these in the shell, escape special characters as needed.

### Example templates

#### Basic, with date and title

```text
{{ZENCRYPTEDTITLE}}_{{ZDATE.strftime('%Y-%m-%d_%H-%M-%S')}}
```

Result: `My Voice Memo_2024-03-17_14-30-00.m4a`

#### Using duration and a short ID suffix

```text
{{ZDATE.strftime('%Y%m%d')}}_{{ZDURATION|int}}s_{{ZUNIQUEID[-8:]}}
```

Result: `20240317_180s_A1B2C3D4.m4a`

#### Conditional formatting

```text
{% if ZCUSTOMLABEL %}{{ZCUSTOMLABEL}}_{% endif %}{{ZDATE.strftime('%Y-%m-%d')}}
```

Result: `Important_2024-03-17.m4a` (when `ZCUSTOMLABEL` is set) or `2024-03-17.m4a` (when it isn't)

#### Complex formatting

```text
{{ZDATE.strftime('%Y-%m-%d')}}_{{ZENCRYPTEDTITLE|replace(' ', '_')|truncate(20, true, '')}}_{{ZUNIQUEID[-8:]}}
```

Result: `2024-03-17_My_Voice_Memo_Wit_A1B2C3D4.m4a`

### More example commands

Export all voice memos to the default location:

```bash
poetry run vmexport
```

Export to a specific folder with a custom filename format:

```bash
poetry run vmexport \
  --output ~/Downloads/VoiceMemoBackup \
  --format "{{ZDATE.strftime('%Y%m%d')}}_{{ZENCRYPTEDTITLE}}"
```

Export from a custom source path with a complex filename format:

```bash
poetry run vmexport \
  --source /path/to/custom/VoiceMemos \
  --format "{{ZDATE.strftime('%Y-%m-%d')}}_{{ZENCRYPTEDTITLE|replace(' ', '_')|truncate(20, true, '')}}_{{ZUNIQUEID[-8:]}}"
```

Use conditional formatting in the filename:

```bash
poetry run vmexport \
  --format "{% if ZENCRYPTEDTITLE %}{{ZENCRYPTEDTITLE}}_{% endif %}{{ZDATE.strftime('%Y-%m-%d')}}_{{ZDURATION|int}}s"
```

## Troubleshooting

### Unable to find the Voice Memos database

Make sure you've recorded at least one memo with the Voice Memos app. If the database still isn't found, point the script at it explicitly with `--source`.

### Permission denied / operation not permitted

Almost always a Full Disk Access issue. See [Granting Full Disk Access](#granting-full-disk-access) above. Remember to add the actual terminal binary you're running from, and to restart it afterwards.

### Database is locked

Quit the Voice Memos app before running the export. The app holds an exclusive lock on the SQLite database while open.

### Write errors at the destination

Confirm the path passed to `--output` exists or can be created, and that your user has write permission there.

## License

This project is licensed under the [MIT License](LICENSE).

