beetbox/beets

Error in `%titlecase` documentation

Closed

#6,697 opened on May 31, 2026

 (0 comments) (0 reactions) (0 assignees)Python (1,837 forks)batch import
good first issuetitlecase

Repository metrics

Stars
 (12,133 stars)
PR merge metrics
 (Avg merge 26d 4h) (51 merged PRs in 30d)

Description

Problem

I think there's a typo in the documentation for the titlecase plugin's %titlecase template function. For every other template function it is documented as %func{arg,arg}.1 Titlecase also is not mentioned in the list of plugins providing templates.2

The documentation for the titlecase plugin specifies it with parentheses. https://github.com/beetbox/beets/blob/26ab6b26361e8c9d77cdf04ba9cf5ca64bbbc722/docs/plugins/titlecase.rst#L34-L40

I didn't catch this initially when I added the titlecase plugin to my config leading to results like %titlecase(Smiths, The) as part of the directory and file names.

Running this command in verbose (-vv) mode:

$ beet -vv import -q -L artist:"The Smiths"

Led to this problem:

user configuration: /home/tom/.config/beets/config.yaml
data directory: /home/tom/.config/beets
plugin paths: []
Loading plugins: chroma, convert, discogs, duplicates, edit, fuzzy, info, mbsync, musicbrainz, permissions, replace, spotify, the, titlecase, unimported, web
Sending event: pluginload
library database: /home/tom/.config/beets/musiclibrary.db
library directory: /home/tom/Music
Sending event: library_opened
Sending event: import_begin
Parsed query: AndQuery([SubstringQuery('artist', 'The Smiths', fast=True)])
Parsed sort: NullSort()
yielding album 1436: The Smiths - Hatful of Hollow
Sending event: import_task_created
Sending event: import_task_start
# [...] output of unrelated plugins cut
/home/tom/Music/Smiths, The/Hatful of Hollow (1 items)
Sending event: import_task_before_choice
Importing as-is.
Sending event: import_task_choice
Replacing item 6502: /home/tom/Music/Smiths, The/Hatful of Hollow/How Soon Is Now_.mp4
Sending event: database_change
Sending event: database_change
Sending event: album_removed
Sending event: item_removed
1 of 1 items replaced
Sending event: database_change
Sending event: database_change
Sending event: database_change
Sending event: database_change
Reimported album 1453. Preserving attribute ['added']. Path: /home/tom/Music/Smiths, The/Hatful of Hollow
Reimported album 1453. Preserving flexible attributes ['data_source', 'media']. Path: /home/tom/Music/Smiths, The/Hatful of Hollow
Reimported item 6538. Preserving attribute ['added']. Path: /home/tom/Music/Smiths, The/Hatful of Hollow/How Soon Is Now_.mp4
Reimported item 6538. Preserving flexible attributes ['data_source', 'spotify_time_signature', 'spotify_valence', 'spotify_danceability', 'spotify_artist_id', 'spotify_track_id', 'spotify_key', 'spotify_acousticness', 'spotify_liveness', 'spotify_energy', 'spotify_updated', 'spotify_speechiness', 'spotify_track_popularity', 'spotify_tempo', 'spotify_instrumentalness', 'track_alt', 'spotify_loudness', 'spotify_mode']. Path: /home/tom/Music/Smiths, The/Hatful of Hollow/How Soon Is Now_.mp4
Sending event: database_change
Parsed query: AndQuery([NoneQuery('album_id', True)])
Parsed sort: NullSort()
Parsed query: AndQuery([BooleanQuery('comp', 1, fast=True)])
Parsed sort: NullSort()
the: "The Smiths" -> "Smiths, The"
Sending event: before_item_moved
Sending event: item_moved
Sending event: database_change
Sending event: database_change
Sending event: database_change
Sending event: import_task_files
Sending event: album_imported
permissions: setting file permissions on /home/tom/Music/%titlecase(Smiths, The)/%titlecase(Hatful of Hollow)/%titlecase(How Soon Is Now_).mp4
permissions: setting directory permissions on /home/tom/Music/%titlecase(Smiths, The)/%titlecase(Hatful of Hollow)
permissions: setting directory permissions on /home/tom/Music/%titlecase(Smiths, The)
Sending event: import
Sending event: cli_exit

I was able to fix this and reimport my library automatically with beet import -q -m -L, which resolved the filename issues.

Setup

  • OS: Arch Linux
  • Python version: 3.14.5
  • beets version: 2.11.0
  • Turning off plugins made problem go away (yes/no): Not applicable

My configuration (output of beet config) is:

Related sections only
# --------------- Main ---------------

library: ~/.config/beets/musiclibrary.db
directory: ~/Music/
include: [secrets.yaml]
paths:
    default: '%titlecase{%the{$albumartist}}/%titlecase{$album}/%titlecase{$title}'
    singleton: '%titlecase{%the{$albumartist}}/%titlecase{$album}/%titlecase{$title}'

import:
    copy: yes
    quiet_fallback: asis
    incremental: yes
    incremental_skip_later: yes
    resume: ask
    languages: en
    log: ~/.config/beets/import.log
# [...]
# --------------- Plugins ---------------

plugins:
- chroma
- convert
- discogs
- duplicates
- edit
- fuzzy
- info
- mbsync
- musicbrainz
- permissions
- replace
- spotify
- the
- titlecase
- unimported
- web
# [...]
titlecase:
    auto: no
    preserve: []
    replace:
        "\u2013": '-'
        "\u2014": '-'
        "\u2026": '...'
        "\u2018": '''' # This is "'" in the file
        "\u2019": '''' # Not sure why `beet config`
        "\u201B": '''' # prints them as ''''
        "\u2039": ''''
        "\u203A": ''''
        "\u201A": ''''
        "\u275B": ''''
        "\u275C": ''''
        "\u275F": ''''
        "\u276E": ''''
        "\u276F": ''''
        "\uFF07": ''''
        "\xAB": '"'
        "\xBB": '"'
        "\u201C": '"'
        "\u201D": '"'
        "\u201E": '"'
        "\u201F": '"'
        "\u275D": '"'
        "\u275E": '"'
        "\u301E": '"'
        "\u301F": '"'
        "\u2E42": '"'
        "\uFF02": '"'
    fields: []
    separators: []
    force_lowercase: no
    small_first_last: yes
    the_artist: yes
    all_caps: no
    all_lowercase: no
    after_choice: no

Footnotes

  1. https://beets.readthedocs.io/en/v2.11.0/reference/pathformat.html#template-functions

  2. https://beets.readthedocs.io/en/v2.11.0/reference/pathformat.html#templ-plugins

Contributor guide