beetbox / beetbox/beets

Template parsing problem prohibits curly braces in function calls

Open
#2,676 2 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Python
Stars
15.7k
Forks
2.1k
Avg merge
4d 21h
Merged PRs (30d)
31

Description

### Problem

Using curly braces in `%aunique` causes the final format to be incomplete. Below some examples.

```sh
$ beet ls -f '$albumartist/$albumartist - $album%aunique{albumartist album year, label catalognum} [$format]/$track. $title' album::^Franz Ferdinand
Franz Ferdinand/Franz Ferdinand - Franz Ferdinand [237] [MP3]/01. Jacqueline
Franz Ferdinand/Franz Ferdinand - Franz Ferdinand [678] [MP3]/01. Jacqueline
$ beet ls -f '$albumartist/$albumartist - $album%aunique{albumartist album year, label catalognum,{}} [$format]/$track. $title' album::^Franz Ferdinand
Franz Ferdinand/Franz Ferdinand - Franz Ferdinand 237} [$format]/$track. $title
Franz Ferdinand/Franz Ferdinand - Franz Ferdinand 678} [$format]/$track. $title
$ beet ls -f '$albumartist/$albumartist - $album%aunique{albumartist album year, label catalognum,()} [$format]/$track. $title' album::^Franz Ferdinand
# without spaces works, but...
Franz Ferdinand/Franz Ferdinand - Franz Ferdinand (237) [MP3]/01. Jacqueline
Franz Ferdinand/Franz Ferdinand - Franz Ferdinand (678) [MP3]/01. Jacqueline
$ beet ls -f '$albumartist/$albumartist - $album%aunique{albumartist album year, label catalognum, ()} [$format]/$track. $title' album::^Franz Ferdinand
# it doesn't show brackets, not even the default ones []
Franz Ferdinand/Franz Ferdinand - Franz Ferdinand 237 [MP3]/01. Jacqueline
Franz Ferdinand/Franz Ferdinand - Franz Ferdinand 678 [MP3]/01. Jacqueline
```
This can also be replicated with other functions.
```sh
$ beet ls -f '%left{beets, 1}'
b
$ beet ls -f '%left{{}, 1}, %right{{},1}'
, 1}, %right{{},1}
$ beet ls -f '%upper{beet {} rocks}, %left{{}, 1}, %right{{}, 1}'
BEET { rocks}, %left{{}, 1}, %right{{}, 1}
$ beet ls -f '%left{foo{}bar,1}'
bar,1}
```

### Setup

* OS: Raspbian + Docker
* Python version: 3.6.1
* beets version: 1.4.5
* Turning off plugins made problem go away (yes/no): no

My configuration (output of `beet config`) is:

```yaml
library: library.db
directory: /music

import:
autotag: yes
copy: no
default_action: apply
delete: no
duplicate_action: ask
flat: no
group_albums: no
hardlink: no
incremental: yes
languages: [en, it, fr]
link: no
log: beet.log
move: yes
none_rec_action: ask
quiet: no
quiet_fallback: skip
resume: ask
singleton: yes
write: yes

plugins:
- chroma
- convert
- copyartifacts
- discogs
- duplicates
- edit
- embedart
- fetchart
- fromfilename
- info
- inline
- lastgenre
- missing
- replaygain
- scrub
- web
discogs:
user_token: REDACTED
apikey: REDACTED
apisecret: REDACTED
tokenfile: discogs_token.json
source_weight: 0.5

sort_album: albumartist+ album+
sort_item: artist+ album+ disc+ track+
sort_case_insensitive: yes

paths:
default: $albumartist/$albumartist - $album%ifdef{albumdisambig, - $albumdisambig} (%ifdef{disco_year,$disco_year,$original_year})%ifdef{tags, - $tags}%aunique{albumartist album year, catalognum label} [$format%ifdef{profile, $profile}]/%if{$multidisc,$disc-}$track. $title
singleton: Singles/$artist/$title
comp: Compilations/$album%aunique{}/$track. $title
albumtype_soundtrack: Soundtracks/$album%aunique{}/$track. $title
ext:log: $albumpath/$album%if{$multidisc, - Disc $disc}
ext:cue: $albumpath/$album%if{$multidisc, - Disc $disc}
ext:jpg: $albumpath/original_cover
item_fields:
multidisc: 1 if disctotal > 1 else 0
disco_year: original_year if original_year > 0 else year
album_fields:
disco_year: original_year if original_year > 0 else year
profile: "total = 0\nfor item in items:\n total += item.bitrate\nabr = total / len(items) / 1000 \nif abr > 480:\n return None\nelif abr < 480 and abr >= 320:\n return '320'\nelif abr < 320 and abr >= 220:\n return 'V0' \nelif abr < 220 and abr >= 170 and abr != 192:\n return 'V2'\nelif abr == 192:\n return '192'\nelif abr < 170:\n return int(abr)\n"
tags: "import re\nmaps = {'live': 'Live', 'ep': 'EP', 'remix': 'Remix'}\nmatch = re.search(r'(live|ep|remix)', albumtype, re.I)\nif match:\n # return match.expand('\\\\1')\n return maps.get(match.group(0).lower())\n"
copyartifacts:
extensions: .cue .log .jpg
print_ignored: yes

replace:
'[\\/]': _
^\.: _
'[\x00-\x1f]': _
'[<>\*\|]': _
\.$: _
\s+$: ''
^\s+: ''
':': "\u2236"
\?: "\uFE16"
'[\xE8-\xEB]': e
'[\xEC-\xEF]': i
'[\xE2-\xE6]': a
'[\xF2-\xF6]': o
'[\xF8]': o
'[\u275C\u02BC]': ''''
'[\u2018\u2019\u201a\u201b\u2039\u203a]': ''''
'[\u201c\u201d\u201e\u201f\u00ab\u00bb]': '"'
chroma:
auto: yes
embedart:
auto: yes
ifempty: yes
maxwidth: 0
compare_threshold: 0
remove_art_file: no
fetchart:
auto: yes
minwidth: 0
maxwidth: 0
enforce_ratio: no
cautious: no
cover_names:
- cover
- front
- art
- album
- folder
sources:
- filesystem
- coverart
- itunes
- amazon
- albumart
google_key: REDACTED
google_engine: 001442825323518660753:hrh5ch1gjzm
fanarttv_key: REDACTED
store_source: no
lastgenre:
auto: yes
source: album
whitelist: yes
min_weight: 10
count: 1
fallback:
canonical: no
force: yes
separator: ', '
prefer_specific: no
convert:
auto: no
ffmpeg: /usr/bin/ffmpeg
opts: -ab 320k -ac 2 -ar 48000
max_bitrate: 320
threads: 1
dest:
pretend: no
format: mp3
formats:
aac:
command: ffmpeg -i $source -y -vn -acodec aac -aq 1 $dest
extension: m4a
alac:
command: ffmpeg -i $source -y -vn -acodec alac $dest
extension: m4a
flac: ffmpeg -i $source -y -vn -acodec flac $dest
mp3: ffmpeg -i $source -y -vn -aq 2 $dest
opus: ffmpeg -i $source -y -vn -acodec libopus -ab 96k $dest
ogg: ffmpeg -i $source -y -vn -acodec libvorbis -aq 3 $dest
wma: ffmpeg -i $source -y -vn -acodec wmav2 -vn $dest
tmpdir:
quiet: no
embed: yes

paths: {}
never_convert_lossy_files: no
copy_album_art: no
album_art_maxwidth: 0
replaygain:
auto: no
backend: audiotools
overwrite: no
targetlevel: 89
r128: [Opus]
scrub:
auto: no
web:
host: 0.0.0.0
port: 8337
cors: ''
reverse_proxy: no
include_paths: no
edit:
albumfields: album albumartist
itemfields: track title artist album
ignore_fields: id path
missing:
count: no
total: no
album: no
pathfields: {}
duplicates:
album: no
checksum: ''
copy: ''
count: no
delete: no
format: ''
full: no
keys: []
merge: no
move: ''
path: no
tiebreak: {}
strict: no
tag: ''

```

Contributor guide

Open the contributing guide

Research direction

Reproduce the reported beet ls format commands, then trace the template parser and function-call handling used by the CLI. The fix is done when nested curly braces and spaces in function arguments parse correctly and the shown commands produce complete paths and values.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.