---
name: music-library-management
description: Manage local music libraries — tag, rename, and organize files with beets and mutagen. Absorbed music-renamer (mutagen-based in-place rename workflow).
category: media
---

# Music Library Management

Tag, rename, and organize local music files. Covers beets (music library manager) for metadata-aware
workflows and mutagen for fast bulk operations when files already have clean ID3 tags.

## Triggers

- User wants to rename/tag/organize music files
- User asks about beets, mutagen, or music file metadata
- User has a collection of mp3/flac/m4a files that need cleanup

## Prerequisites

Install beets (includes mutagen as a dependency):

```bash
pip install --break-system-packages beets
```

Config lives at `~/.config/beets/config.yaml`.

## Workflow Decision

### Use beets when:
- Files need MusicBrainz tagging (missing or wrong metadata)
- You want to reorganize files into a standard Artist/Album/Track structure
- You need beets' query and library features long-term
- Small batch (<100 files) — even with MusicBrainz timeout, it's manageable

### Use mutagen directly when:
- Files already have clean ID3 tags (artist, title, album present)
- Bulk renaming is the only goal (format change, not metadata enrichment)
- Large batch (100+ files) — beets import is too slow per-file
- In-place renaming (keep directory structure, just fix filenames)

## Beets Workflow

### Configuration (`~/.config/beets/config.yaml`)

```yaml
directory: /path/to/music/root
library: /path/to/music/root/musiclibrary.db

import:
    copy: no
    move: no        # no for import-only; yes when ready to reorganize
    write: yes
    quiet: yes      # skip confirmation prompts

# Treat loose singles as a compilation
singletons:
    album: Singles
    albumartist: Various Artists
    compilation: yes

paths:
    singleton: %(artist)s - %(title)s
    comp: Compilations/%(album)s/%(artist)s - %(title)s
    default: %(albumartist)s/%(album)s/%(track)02d - %(title)s
```

### Import files into the library

```bash
# Slow — hits MusicBrainz per file. Use -A (no autotag) if tags are already good.
beet import -A -q --singletons /path/to/music/dir/
```

### Query and rename already-imported files

```bash
# Dry run: see what would change
beet ls -f '$path  ||  $artist - $title' 'query'

# Actually rename (moves per path template in config)
beet move 'query'
```

For in-place renaming with beets, the path template must match the directory
structure. Beets always uses `directory + path_template` — it cannot rename
within arbitrary subdirectories without moving.

## Mutagen Bulk Rename

When beets import is too slow and files already have ID3 tags, use the script at
`scripts/bulk-rename.py`. It walks a directory tree, reads artist/title from tags
via mutagen, and renames in-place to `Artist - Title.ext`.

## Linked files

- `scripts/bulk-rename.py` — Bulk in-place rename using mutagen ID3 tags
- `scripts/rename_by_tags.py` — Alternative rename script (absorbed from music-renamer)

The script handles:
- Collisions (appends `(1)`, `(2)` to duplicates in the same directory)
- Special characters (strips `/` and null bytes)
- Files already correctly named (skips)

## Quick Rename Workflow (mutagen, absorbed from `music-renamer`)

For the specific use case of renaming files in-place using embedded ID3 tags
(files already have good tags, just need cleaner filenames), use the script at
`scripts/rename_by_tags.py`. It walks a directory tree, reads artist/title from
mutagen, and renames to `Artist - Title.ext`.

### Trigger for this workflow

User asks to rename/organize music files, clean up filenames, strip number prefixes
from downloaded music.

### Step 1 — Determine scope

**CRITICAL**: Confirm which directories the user wants renamed. Never assume "all music."
Use `find` with a `-regex` pattern to count files with number prefixes:

```bash
find /path/to/music -type f -regex ".*/[0-9]+\. .*"
```

Files with number prefixes (e.g., `123. Title.mp3`) are the unrenamed ones.

### Step 2 — Run the rename script

Use `scripts/rename_by_tags.py` via `execute_code`. The script:
- Walks a base directory recursively
- Reads artist/title from ID3 tags via mutagen
- Renames files to `Artist - Title.ext` in-place (same directory)
- Skips files already in the correct format
- Handles collisions by appending `(1)`, `(2)` etc.
- Handles slashes in artist/title by replacing with `-`

### Step 3 — Verify

```bash
ls /path/to/music/some-folder/ | head -10
find /path/to/music -regex ".*/[0-9]+\. .*" | wc -l  # should be 0
```

### When to use mutagen vs beets

| Scenario | Tool |
|---|---|
| Files already have good ID3 tags, just need renaming | **mutagen** (scripts/rename_by_tags.py) |
| Files have NO tags, need to be matched against MusicBrainz | **beets import** (with autotag) |
| Small batch (< 50 files) needing autotag | **beets import** is fine |
| Large batch (> 100 files) | **mutagen** — beets import will time out |

### User preferences

- Ray prefers mutagen over beets for bulk renaming
- Always confirm directory scope — don't expand beyond what was asked

## Pitfalls

- **Beets import is slow**: Even with `-A` (no autotag), beets still does per-file
  MusicBrainz lookups. Expect ~1-3 seconds per file. For 1000+ files, mutagen is
  the right choice.
- **Beets `move` vs in-place**: `beet move` respects `directory + path_template`.
  It cannot do truly in-place renames within arbitrary subdirectories. For in-place
  renames, use mutagen or a script as above.
- **`beet update`**: Updates metadata FROM files TO library — not the reverse.
  Does not update stored paths after external renames.
- **Config YAML quoting**: Beets path templates use `%(var)s` syntax. The YAML
  linter may warn about unquoted `%` characters but beets parses them correctly.
