Metadata-Version: 2.4
Name: beets-genrecanon
Version: 0.1.2
Summary: User-controlled canonical genre mapping for beets
Author: Gerald Cox
License-Expression: GPL-2.0-or-later
Project-URL: Homepage, https://codeberg.org/gbcox/beets-genrecanon
Project-URL: Repository, https://codeberg.org/gbcox/beets-genrecanon
Project-URL: Issues, https://codeberg.org/gbcox/beets-genrecanon/issues
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Multimedia :: Sound/Audio
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: beets>=2.0
Requires-Dist: mutagen>=1.45
Requires-Dist: PyYAML>=6.0
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Requires-Dist: pytest-cov>=5; extra == "test"
Dynamic: license-file

# beets-genrecanon

`beets-genrecanon` is a beets plugin that maps detailed source genres to
user-defined canonical genre categories.

It preserves the original `genre` value and stores the broader category
separately as `genre_canon`. This makes it possible to build consistent
genre-based playlists without replacing useful metadata or relying on an
online genre service.

The initial release supports FLAC files only.

## Why use it?

MusicBrainz and other tag sources may describe similar music using many
different genres. For example:

```yaml
Rock:
  - Progressive Rock
  - Symphonic Rock
  - Classic Rock
```

The original genre remains unchanged, while the plugin writes:

```text
GENRE=Symphonic Rock
GENRE_CANON=Rock
```

The canonical categories and mappings are entirely controlled by the user.

## Features

- Preserves the original `GENRE` tag.
- Writes a separate `GENRE_CANON` FLAC tag.
- Stores the value in the beets `genre_canon` flexible field.
- Uses a fully customizable `genres.yaml` taxonomy.
- Detects missing, stale, and duplicate `GENRE_CANON` values.
- Does not rewrite tags that already match.
- Prompts the user when an unknown genre is encountered.
- Updates `genres.yaml` only after the proposed mappings are confirmed.
- Validates the complete import before modifying any FLAC files.
- Supports beets import pretend mode.
- Performs parallel metadata reads using the available logical CPUs.
- Requires no network service.

## Installation

Install from PyPI into the same Python environment as beets:

```bash
python -m pip install beets-genrecanon
```

For development, clone the repository and install it in editable mode:

```bash
python -m pip install -e '.[test]'
```

Enable the plugin in the beets configuration:

```yaml
plugins:
  - genrecanon
```

## Configuration

```yaml
genrecanon:
  auto: yes
  mapping: ~/.config/beets/genres.yaml
  threads: 16
```

Options:

- `auto`: Run canonicalization automatically at the beginning of an import.
- `mapping`: Location of the user-maintained genre taxonomy.
- `threads`: Number of concurrent FLAC metadata readers. If omitted, the
  default is the number of logical CPUs available to Python.

The default mapping location is `genres.yaml` in the beets configuration
directory. The exact directory varies by platform.

### Starting a new taxonomy

Create `genres.yaml` from the packaged starter taxonomy:

```bash
beet genrecanon --init
```

The file is created at the location specified by `genrecanon.mapping`. If no
location is configured, it is created as `genres.yaml` in the beets
configuration directory.

The command refuses to overwrite an existing mapping file.

The taxonomy is intended to be customized. Categories may be renamed, added,
removed, or reorganized as desired.

## Importing music

Run a normal as-is import:

```bash
beet import -A /path/to/music
```

Before beets imports the files, the plugin:

1. Reads all FLAC metadata under the import path.
2. Determines the expected canonical genre for each file.
3. Leaves already-correct files untouched.
4. Reports genres that are missing from `genres.yaml`.
5. Allows unknown genres to be assigned to an existing or new category.
6. Confirms and atomically updates `genres.yaml`.
7. Revalidates the affected files.
8. Writes only missing, stale, or duplicate `GENRE_CANON` tags.
9. Copies `GENRE_CANON` into the beets `genre_canon` field during import.

The plugin does not begin writing FLAC tags while unresolved genres remain.

## Pretend mode

Preview an import without changing `genres.yaml`, FLAC files, or the beets
database:

```bash
beet import -A --pretend /path/to/music
```

Pretend mode reports files that are unchanged, would be updated, contain
unmapped genres, or could not be read. It does not prompt for classifications.

## Synchronizing existing library items

Synchronize embedded `GENRE_CANON` tags into the beets database:

```bash
beet genrecanon
```

Limit synchronization with a beets query:

```bash
beet genrecanon 'path:/path/to/music'
```

Preview the database changes:

```bash
beet genrecanon -p 'path:/path/to/music'
```

This command does not derive genres or modify FLAC tags. It synchronizes
existing embedded `GENRE_CANON` values into the beets database.

## Querying canonical genres

List tracks assigned to a canonical genre:

```bash
beet ls 'genre_canon:Rock'
```

Display both genre fields:

```bash
beet ls -f '$artist - $title | genre=$genre | genre_canon=$genre_canon'
```

## Platform support

The plugin is written entirely in Python and is intended to work wherever
beets, Mutagen, and PyYAML are available, including Linux, macOS, and Windows.

Only FLAC tag canonicalization is currently supported.

## License

GNU General Public License, version 2 or later.
