Table of Contents
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:
- ~/.config/beets/config.yaml
# # 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/
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, 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
With the plugin installed:
- Follow the 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
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 ":[’”‐–—]"