====== Beets ======
https://github.com/beetbox/beets/
Note: Updating python requests will cause conflicts with the distro installed pip version. Best to uninstall it completely and get the latest using easy_install:
#mint21
sudo apt-get install libchromaprint1 mp3val flac python3-acoustid
sudo apt-get remove python-pip
sudo apt-get install python3-setuptools
sudo easy_install pip
pip install beets
pip install requests
pip install beets[fetchart,lyrics,lastgenre]
pip install beets-yearfixer
pip install https://github.com/ocelma/python-itunes/archive/master.zip
pip install discogs-client
pip install beets-web-import
pip install python3-discogs-client
# see notes below to patch copyartifacts
pip install beets-copyartifacts
#Buggy as of 19/Sept/2020:
pip install beets-copyartifacts3
pip install beets-extrafiles
#Mint22:
sudo apt-get install python3-acoustid libchromaprint1 mp3val flac
pipx install beets
pipx inject beets flask=2.2.5 discogs_client requests pylast pyacoustid pycairo pygobject python3-discogs-client discogs-client beets-copyartifacts beets-extrafiles
pipx inject beets beets-yearfixer beets-web-import
pipx inject beets git+https://github.com/edgars-supe/beets-importreplace.git
to upgrade beets going forward, use:
pip install -U beets
As beets 1.6.0 is a bit buggy and 3 years old, use latest source snapshot tarball installation:
pip uninstall beets
pip install https://github.com/beetbox/beets/tarball/master
====== copyartifacts python3 patch ======
2to3-2.7 -w ~/.local/lib/python3.10/site-packages/beetsplug/copyartifacts.py
#Mint22:
sudo apt-get install 2to3
2to3 -w ~/.local//share/pipx/venvs/beets/lib/python3.12/site-packages/beetsplug/copyartifacts.py
then adjust as following:
#line 104:
file_ext = os.path.splitext(filename)[1].decode('utf8')
#line 143:
print(' ', str(os.path.basename(f.decode('utf8'))))
#line 147:
print('Copying artifact: {0}'.format(os.path.basename(dest_file.decode('utf8'))))
#line 156:
print('Moving artifact: {0}'.format(os.path.basename(dest_file.decode('utf8'))))
#add line 127
dest_file = beets.util.bytestring_path(dest_file)
Change the init:
vi ~/.local/lib/python3.10/site-packages/beets/autotag/__init__.py
#change line 75 from
def unidecode_punc_only(text):
#to
def unicode_punc_only(text):
====== Unicode in Tag Patch ======
NOTE: use import-replace plugin instead!
MusicBrainz uses unicode characters for apostrophes, dashes etc rather than ascii versions. A beets plugin for filenames (asciify) solves some issues with this, but tags are unaffected. This patch for v1.5.0/v1.6.0 on python 3.8 solves this for imports:
pip install unidecode
#beets version 1.6.0
cat > beets-unidecode.patch << EOF
20a21
> from unidecode import unidecode
73a75,85
> def unidecode_punc_only(text):
> """Change unicode punctuation to ascii equivalent.
> """
> result = u""
> for character in text:
> if character.isalpha():
> result += character
> else:
> result += unidecode(character)
> return result
>
77,80c89,92
< item.artist = track_info.artist
< item.artist_sort = track_info.artist_sort
< item.artist_credit = track_info.artist_credit
< item.title = track_info.title
---
> item.artist = unicode_punc_only(track_info.artist)
> item.artist_sort = unicode_punc_only(track_info.artist_sort)
> item.artist_credit = unicode_punc_only(track_info.artist_credit)
> item.title = unicode_punc_only(track_info.title)
105,110c117,122
< item.artist = (track_info.artist_credit or
< track_info.artist or
< album_info.artist_credit or
< album_info.artist)
< item.albumartist = (album_info.artist_credit or
< album_info.artist)
---
> item.artist = (unicode_punc_only(track_info.artist_credit) or
> unicode_punc_only(track_info.artist) or
> unicode_punc_only(album_info.artist_credit) or
> unicode_punc_only(album_info.artist))
> item.albumartist = (unicode_punc_only(album_info.artist_credit) or
> unicode_punc_only(album_info.artist))
112,113c124,125
< item.artist = (track_info.artist or album_info.artist)
< item.albumartist = album_info.artist
---
> item.artist = (unicode_punc_only(track_info.artist) or unicode_punc_only(album_info.artist))
> item.albumartist = unicode_punc_only(album_info.artist)
116c128
< item.album = album_info.album
---
> item.album = unicode_punc_only(album_info.album)
119,123c131,135
< item.artist_sort = track_info.artist_sort or album_info.artist_sort
< item.artist_credit = (track_info.artist_credit or
< album_info.artist_credit)
< item.albumartist_sort = album_info.artist_sort
< item.albumartist_credit = album_info.artist_credit
---
> item.artist_sort = unicode_punc_only(track_info.artist_sort) or unicode_punc_only(album_info.artist_sort)
> item.artist_credit = (unicode_punc_only(track_info.artist_credit) or
> unicode_punc_only(album_info.artist_credit))
> item.albumartist_sort = unicode_punc_only(album_info.artist_sort)
> item.albumartist_credit = unicode_punc_only(album_info.artist_credit)
149c161
< item.title = track_info.title
---
> item.title = unicode_punc_only(track_info.title)
EOF
sudo patch -b ~/.local/lib/python3.10/site-packages/beets/autotag/__init__.py beets-unidecode.patch
====== Show path in duplicate import warning ======
vi ~/.local/lib/python3.10/site-packages/beets/ui/commands.py
#add as line 476 in beets 1.6.0, around line 740 in beets 1.6.1:
summary_parts.append(displayable_path(item.path))
====== Beets tips ======
http://ngokevin.com/blog/beets/
https://beets.readthedocs.io/en/v1.4.9/guides/advanced.html#fetch-album-art-genres-and-lyrics
https://beets.readthedocs.io/en/v1.4.9/guides/tagger.html
https://beets.readthedocs.io/en/v1.4.9/plugins/chroma.html
https://beets.readthedocs.io/en/v1.4.9/plugins/fromfilename.html
Using beets as an autotagger, without importing the files:
i.e. just point beets to a directory, have it tag with data from MusicBrainz, but don't save anything to the database, don't rename or move the files, etc.
There's no option to avoid writing to the database - but it can be configured to not move or copy the files:
import:
copy: no
move: no
Then you can just ignore that the database exists at all. Or just ''alias beet='beet ; rm ~/.config/beets/library.db'' or something to delete it every time.
If you want a temporary configuration for any reason, you can always use ''beet -c /path/to/file.yaml'' instead of copying around files.
====== Config File ======
Default config file of beets:
https://raw.githubusercontent.com/beetbox/beets/master/beets/config_default.yaml
Personal adjustment:
#
# Beets configuration file
#
# Tip: Import works best by first importing albums, ideally one album at a time, then singles to address duplicates properly
#
library: /home/wuff/.config/beets/beetslibrary.db
directory: /media/music/beets2
import:
# write metadata to music files
write: yes
# copy imported files from source to the music directory
copy: yes
# move imported files from source to the music directory - takes precedence over copy!
move: yes
link: no
hardlink: no
#deprecated by move function
#delete: no
resume: ask
incremental: no
incremental_skip_later: no
from_scratch: no
quiet_fallback: skip
none_rec_action: ask
timid: no
#log:
log: /home/wuff/.config/beets/beetslog.txt
# use auto-tagging where possible do not require confirmation on strong matches
autotag: yes
# no output, use quiet_fallback - skip - as default action
quiet: no
#default to full album import, singleton mode via command line -s switch
singletons: no
# default selected action on command line for return for a match query
default_action: apply
languages: []
detail: no
#multi disk albums split in multiple directories can be seen as flat using cmd option --flat
flat: no
group_albums: no
pretend: false
search_ids: []
duplicate_action: ask
#duplicate verbose uses format_item settings
duplicate_verbose_prompt: yes
bell: no
set_fields: {}
art: yes
# files matching these patterns are deleted from source after import
clutter: ["Thumbs.DB", ".DS_Store", "desktop.ini", "*.m3u", "*.nfo", "*.pls", "*.torrent"]
# files/directories matching one of these patterns are ignored during import
ignore: [".*", "*~", "System Volume Information", "lost+found", "cover.jpg"]
ignore_hidden: yes
# replace special characters in generated filenames
replace:
'[\\/]': '-'
'^\.': _
'[\x00-\x1f]': _
'[<>\?\*\|]': _
'[:]': ' -'
# dot at end of directories causes problems with some clients on samba shares
'\.$': _
'\s+$': ''
'^\s+': ''
'^-': _
# no special quotes thank you
'[\u2018\u2019]': ''''
'[\u201c\u201d]': '"'
'[\x91\x92]': "'"
# double quotes to single quote
'"': ''''
#replacement character for path separator (forward/backslack), precedence over replace function
path_sep_replace: '-'
#replacement character for drive separator (colon?), NO precedence over replace function
drive_sep_replace: '-'
#default false, set to yes, all special characters or accents are converted to ascii equivalent.
#avoids having to wrap all paths with asciify function
asciify_paths: true
art_filename: folder # results in "folder.jpg", default "cover.jpg"
max_filename_length: 0 # 0 unlimited
#add more details for duplicate album names
aunique:
keys: albumartist album
disambiguators: albumtype year label catalognum albumdisambig releasegroupdisambig
bracket: '[]'
overwrite_null:
album: []
track: []
plugins: [fetchart,lyrics,lastgenre,embedart,bucket,the,chroma,ftintitle,discogs,scrub,inline,fromfilename,edit,importadded,duplicates,replaygain,web,bpd,webimport,yearfixer,zero,badfiles,importreplace]
#copyartifacts broken
pluginpath: []
threaded: yes
timeout: 5.0
# per disc numbering no = 2nd disk starts with N+1 track number
# if set to yes, second disk track starts at 1 again.
# If "per disk", make sure tracknames do not collide ("paths" setting) and include disks in path (and/or inline plugin).
per_disc_numbering: yes
verbose: 0 #for debug info. equivalent to -v. can be used twice for even more details -vv
terminal_encoding: utf8
# use the release-date of the original (first) release of an album?
original_date: yes
artist_credit: no
id3v23: no
va_name: "Various Artists"
ui:
terminal_width: 80
length_diff_thresh: 10.0 #undocumented
color: yes
colors:
text_success: green
text_warning: yellow
text_error: red
text_highlight: red
text_highlight_minor: lightgray
action_default: turquoise
action: blue
#Define how to show items using the list command (formerly list_format_item/list_format_album)
format_item: %upper{$artist} - $album - $track. $title ($length)
format_album: %upper{$albumartist} - $album [$year] ($totaltracks)
#format_item: $artist - $album - $title
time_format: '%Y-%m-%d %H:%M:%S'
#Track length is displayed as "M:SS" rather than a raw number of seconds. Queries on track length also accept this format. To use raw seconds, set format_raw_length to yes
format_raw_length: no
#Default sort order when fetching items from database
sort_album: albumartist+ album+
sort_item: artist+ album+ disc+ track+
sort_case_insensitive: yes
#used by inline plugin to define specific template variables for paths
item_fields:
disc_and_track: f'{disc:02}-{track:02}' if disctotal > 1 else f'{track:02}'
artist_and_title: f'{artist} - {title}' if artist != albumartist else title
original_date: "'-'.join(filter(None, (original_year and f'{original_year:04}', original_month and f'{original_month:02}', original_day and f'{original_day:02}')))"
album_year: f'{year:04}'
album_fields:
totaltracks: str(items[0].tracktotal) + ' tracks'
album_ends_with_ep: 1 if str({album})[-2:] == "EP" else 0
# Paths and filenames for music files relative to music directory
# setting asciify to true in general settings avoids having to wrap individual paths with %asciify{} function.
# In addition to default, comp, and singleton, you can condition path queries based on beets queries (eg albumtype:soundtrack). The queries are tested in the order they appear in the configuration file.
paths:
default: Album/%bucket{%the{$albumartist},alpha}/%the{$albumartist}/$albumartist - $album%aunique{} [$year]/$albumartist - $disc_and_track - $title
singleton: Non-Album/%bucket{%the{$artist}}/$artist - $title
albumtype:ep: Album/%bucket{%the{$albumartist},alpha}/%the{$albumartist}/$albumartist - $album%aunique{}%if{$album_ends_with_ep,, EP} [$year]/$albumartist - $disc_and_track - $title
albumtype:soundtrack: Soundtracks/$album%aunique{} [$year]/$disc_and_track - $artist - $title
comp: Compilations/%bucket{$year}/%the{$album%aunique{}}/$disc_and_track - $artist - $title
statefile: state.pickle
musicbrainz:
host: musicbrainz.org
ratelimit: 1
ratelimit_interval: 1.0
searchlimit: 20
extra_tags: []
match:
strong_rec_thresh: 0.1 # match 90% or better for auto import (default is 0.04 = 96%)
medium_rec_thresh: 0.25
rec_gap_thresh: 0.25
max_rec:
missing_tracks: medium
unmatched_tracks: medium
# https://discourse.beets.io/t/can-beets-fix-tracks-which-are-wrongly-named/671/4
distance_weights:
source: 2.0
artist: 3.0
album: 3.0
media: 1.0
mediums: 1.0
year: 1.0
country: 0.5
label: 0.5
catalognum: 0.5
albumdisambig: 0.5
album_id: 5.0
tracks: 4.0 #default 2
missing_tracks: 0.9
unmatched_tracks: 0.4 #default 0.6
track_title: 3.0
track_artist: 2.0
track_index: 1.0
track_length: 2.0
track_id: 5.0
preferred:
countries: ['XW', 'US', 'GB|UK', 'JP', 'DE']
media: ['Digital Media', 'CD', 'File']
original_year: yes
ignored: []
required: []
#ignore matches from video sources instead of audio
ignored_media: ['Data CD', 'DVD', 'DVD-Video', 'Blu-ray', 'HD-DVD',
'VCD', 'SVCD', 'UMD', 'VHS']
ignore_data_tracks: yes
ignore_video_tracks: yes
track_length_grace: 10
track_length_max: 30
embedart:
auto: yes
compare_threshold: 20
ifempty: no #only embed if empty: default no
maxwidth: 800
remove_art_file: no
fetchart:
auto: yes
cover_names: front folder cover album
enforce_ratio: 2%
bucket:
bucket_alpha: ['0-9', 'A-C', 'D-F', 'G-J', 'K-N', 'O-R', 'S', 'T-Z' ]
bucket_year: ['1960s', '1970s', '1980s', '1990s', '2000s', '2010s', '2020s']
bucket_alpha_regex:
'0-9': ^[^a-zA-Z]
lyrics:
auto: yes
on_import: true
processes: 10
force: no
fallback: ''
#moves featured artists from artists to the title field
ftintitle:
auto: yes
format: feat. {0}
#Scrub plugin removes all non-beet tags
scrub:
auto: no
#copies additional files from source folder (except ignored files)
copyartifacts:
extensions: .cue .jpg .gif .png .pdf
print_ignored: yes
edit:
itemfields: track title artist album year albumartist artist_sort artist_credit
albumfields: album albumartist year
importadded:
preserve_mtime: yes #after import reset mtime to original value
preserve_write_mtime: yes #after writing to files reset mtime
duplicates:
# creating checksums is expensive
#checksum: no
#By default, the tie-breaking procedure favors the most complete metadata attribute set. If you would like to consider the lower bitrates as duplicates, for example, set
tiebreak: { items: [bitrate] }
chroma:
auto: yes
#Plugin needs gstreamer and python-gi installed, or mp3gain or aacgain
replaygain:
auto: yes #no=disable during import
backend: gstreamer
overwrite: yes #re-analyze files with replaygain tag default no
targetlevel: 89 #decibel target level default 89
#manual analysis beet replaygain -wf /my/music/directory
#Note this takes about 10 seconds for 7 files which is about 6h for 15k songs.
web:
host: 0.0.0.0
port: 8337
webimport:
host: 0.0.0.0
port: 8338
bpd:
host: 127.0.0.1
port: 6600
password: seekrit
volume: 30
acoustid:
apikey: 9NQqkUUg6p
# null/remove specific tag fields on import (not when importing as-is)
# regex condition on comments field, this removes useless comments:
zero:
fields: comments
comments: [EAC, LAME, from.+collection, 'ripped by']
update_database: true
# check integrity of files on import, note that mp3val
# required for mp3s: apt-get install mp3val
# mp3val is quite verbose, multiple stream errors indicate issues
badfiles:
check_on_import: yes
commands:
mp3: mp3val -f -si -nb -t
#replace unicode apostrophes from mb with ascii version in tags
importreplace:
replacements:
- item_fields: title artist artist_sort artist_credit
album_fields: album artist artist_sort artist_credit
replace:
'[\u2018-\u201B]': ''''
'[\u201C-\u201F]': '"'
'[\u2010-\u2015]': '-'
====== Path template information ======
https://beets.readthedocs.io/en/v1.4.7/reference/pathformat.html#template-functions
usage:
beet import /media/downloads/Thirty\ Seconds\ to\ Mars\ -\ AMERICA\ \(2018\)
To rename files/folders after change in path/filename config, use:
beet move
To use a separate config file use:
beet -c beets-wulf-config.yaml import xxx
Dry run/Pretend:
beet move -p
Update db after deleting files
beet update
Update db after changing ID3 tags (singleton example)
beet import -A -C -W -I -s /media/music/beets2/Non-Album/
Update id3 tags after changing db
beet write
====== Addons ======
https://github.com/martinkirch/drop2beets
https://amp.reddit.com/r/airsonic/comments/f1yb86/beets_plugin_for_trigger_library_update/
Web Import:
pip install beets-webimport
~/.local/lib/python3.10/site-packages/beetsplug/webimport
====== Submit album to MusicBrainz ======
https://thesynack.com/posts/managing-music/
Although it is possible to manually add albums (MusicBrainz calls them releases) to MusicBrainz, there is a much easier method: MusicBrainz’s GUI music tagger, [[https://picard.musicbrainz.org/|Picard]]. This tagger is also quite capable, and some may prefer its GUI interface if the CLI scares you.
Picard offers a fast way to contribute releases to MusicBrainz. To use it, go to the plugins settings inside of Picard and install Add Cluster As Release.
installing the plugin
{{:config:pasted:20201101-001957.png}}
With the plugin installed:
- Follow the [[https://picard.musicbrainz.org/quick-start/|quick-start instructions]] to create clusters out of music you wish to contribute.
- Right-click and select plugins -> Add Cluster As Release. This will pull up the contribution form in your browser with tons of information already filled out.
- Fill in any missing information you can find.
- Submit the edit.
After a few moments, try re-tagging the album with beets. If everything worked properly, beets should find the release you contributed and finish tagging it. Repeat this process for any albums you wish to add to MusicBrainz.
====== beets web ======
depends on flask
pip install flask
add web to plugins in beets config.yaml
Define port in [web] section
[web]
host: 127.0.0.1 #or 0.0.0.0
port: 8337
for a remote web interface, CORS is required
pip install flask-cors
start server on localhost:8337 using
beet web
or
beet web hostname port
https://github.com/irskep/summertunes
====== beet edit ======
edit plugin:
add config option: editor_command
vi ~/.local/lib/python3.10/site-packages/beetsplug/edit.py:
def edit, line 45
if self.config['editor_command'].as_str():
cmd = util.shlex_split(self.config['editor_command'].as_str())
else:
cmd = util.shlex_split(util.editor_command())
Problem: editor env variable not always read?
echo $EDITOR
vi
beet edit --all george michael sun go down
No changes; aborting.
but:
EDITOR=vi beet edit --all george michael sun go down
env | grep EDITOR
export EDITOR=vi
env | grep EDITOR
EDITOR=vi
====== Album Art Downloader ======
https://sourceforge.net/projects/album-art/
https://community.metabrainz.org/t/update-cover-art-only/513486/7
Force replace coverart of album:
beet fetchart -f carrie underwood carnival ride
beet clearart carrie underwood carnival ride
beet embedart carrie underwood carnival ride
====== Puddletag ======
ID3 Tag editor with embed cover support
sudo add-apt-repository ppa:ubuntuhandbook1/apps
sudo apt-get update
sudo apt-get install puddletag
====== KID3 QT ======
ID3 Tag editor with embed cover support
sudo add-apt-repository ppa:ufleisch/kid3
sudo apt-get update
sudo apt-get install kid3-qt
====== Find/reimport items ======
To find db entries not linked to musicbrainz, you can use a query.
You can match an empty field with regular expressions:
beet ls mb_albumid::^$
http://beets.readthedocs.org/en/v1.2.1/reference/query.html#regular-expressions
Additionally, there's no need to move unmatched files to a separate directory to re-import them. You can use the -L flag to re-import albums matching a query:
beet imp -L mb_albumid::^$
====== FtInTitle notes ======
https://beets.readthedocs.io/en/stable/plugins/ftintitle.html
The FtInTitle plugin identifies the main artist using the albumartist field and compares this with the artist field. If they are identical, the addon will not do anything. The albumartist needs to contain the main artist only.
====== yearfixer ======
https://github.com/adamjakab/BeetsPluginYearFixer
https://pypi.org/project/beets-yearfixer/
finds and sets the original year.
usage:
beet yearfixer [options] [QUERY...]
====== importreplace ======
A plugin for beets to perform regex replacements during import, particularly useful to replace unicode apostrophes, double quotes, single quotes and hyphens/en dash from musicbrainz db with ascii equivalents.
https://github.com/edgars-supe/beets-importreplace
Installation:
pip install git+https://github.com/edgars-supe/beets-importreplace.git
add config and add importreplace to plugins config string:
#replace unicode apostrophes from mb with ascii version in tags
importreplace:
replacements:
- item_fields: title artist artist_sort artist_credit
album_fields: album artist artist_sort artist_credit
replace:
'[\u2018-\u201B]': ''''
'[\u201C-\u201F]': '"'
'[\u2010-\u2015]': '-'
As this plugin works for import only, use the following to retag/reimport after installation, make sure to copy&paste as the below uses the unicode characters:
#for albums:
beet import -L ":[’”‐–—]"
#for singletons:
beet import -L -s ":[’”‐–—]"