{ pkgs, lib, config, ... }: { programs.beets = { enable = true; package = pkgs.beets.override { pluginOverrides = { beatport.enable = true; }; }; settings = { library = "${config.xdg.dataHome}/beets/library.db"; art_filename = "cover"; directory = "${config.xdg.userDirs.music}/beets"; ui = { color = true; }; import = { # move, instead of copying or linking the files copy = false; move = true; link = false; # Show more detail, when beet needs to ask for something detail = true; set_fields = { # Normalize the author field to be Title Cased. artist = "%title{$artist}"; artist_sort = "%title{artist_sort}"; artist_credit = "%title{artist_credit}"; albumartist = "%title{albumartist}"; albumartist_sort = "%title{albumartist_sort}"; albumartist_credit = "%title{albumartist_credit}"; }; incremental = false; # Write the metadata to the files write = true; log = "${config.xdg.dataHome}/beets/beetslog.txt"; }; paths = { default = "$genre/$first_artist/$album/$track $title"; singleton = "$genre/$first_artist_singleton/$title"; comp = "$genre/$album/$track $title"; "albumtype:soundtrack" = "Soundtracks/$genre/$album/$track $title"; }; # Plugin config lastgenre = { prefer_specific = false; # Lookup the track, not the album source = "track"; }; fetchart = {}; lyrics = { # Always fetch lyrics (and update them, if some were found) force = true; }; hook = { hooks = [ { # Also generate the replaygain for the album variant (so selecting between # track and album becomes possible) event = "import"; command = "beet replaygain --album"; } ]; }; replaygain = { auto = true; backend = "ffmpeg"; r128_targetlevel = 89; # Re-calculate the replay gain value even for files, that already have one set. overwrite = true; }; duplicates = { keys = ["acoustid_fingerprint"]; }; fuzzy = { # The prefix denoting that a search should be run in fuzzy mode prefix = "."; }; ihate = { warn = [ "title:commentary" ]; }; smartplaylist = { relative_to = config.services.mpd.musicDirectory; playlist_dir = config.services.mpd.playlistDirectory; forward_slash = false; # Show the real m3u file paths, when running `--pretend` pretend_paths = true; playlists = [ { name = "artists-$first_artist.m3u"; query = ""; } { name = "ratings-good.m3u"; query = "rating:0.7..1.0"; } { name = "ratings-mediocre.m3u"; query = "rating:0.4..0.7"; } { name = "ratings-bad.m3u"; query = "rating:0.0..0.4"; } { name = "not_played.m3u"; query = "-play_count: artist:"; } ]; }; item_fields = { # Taken from https://github.com/trapd00r/configs/blob/4f3dada5700846cca6c2869e6fa6b3c795b87b67/beets/config.yaml first_artist = /* python */ '' # import an album to another artists directory, like: # Tom Jones │1999│ Burning Down the House [Single, CD, FLAC] # to The Cardigans/+singles/Tom Jones & the Cardigans │1999│ Burning Down the House [Single, CD, FLAC] # https://github.com/beetbox/beets/discussions/4012#discussioncomment-1021414 # beet import --set myartist='The Cardigans' # we must first check to see if myartist is defined, that is, given on # import time, or we raise an NameError exception. try: myartist except NameError: import re return re.split(',|\\s+(feat(.?|uring)|&|(Vs|Ft).)', albumartist, 1, flags=re.IGNORECASE)[0] else: return myartist ''; first_artist_singleton = /* python */ '' try: myartist except NameError: import re return re.split(',|\\s+(feat(.?|uring)|&|(Vs|Ft).)', artist, 1, flags=re.IGNORECASE)[0] else: return myartist ''; }; # scrub = { # auto = true; # }; plugins = [ # Remove all previous tags before import (this is useful to ensure, that # the metadata in the libary.db is synced with the tags on disk) # # FIXME: I think, that this also removes the deezer id, which is not ideal # <2024-08-07> # "scrub" # Calculate replay gain "replaygain" # Alows to use inline python for parsing tags "inline" # Show tags on files/queries "info" # Create playlist from `play_count`/`skip_count` (gathered by the `mpdstats` # plugin) # Note that this should come _before_ the `mpdupdate` plugin, to ensure that # `mpdupgate` can propagate changed playlist to `mpd`. "smartplaylist" # Warn, when importing a matching item "ihate" # Allow fuzzy searching "fuzzy" # Filter out duplicates "duplicates" # Generate fingerprints "chroma" # Download album art "fetchart" # Fetches tags from `last.fm` and adds them as genres to imported music "lastgenre" # Run commands on events "hook" # Fetch lyrics "lyrics" # Allow beets to understand deezer id's "deezer" "mpdstats" # Transfer MPD stats to beets "mpdupdate" # Update MPD database on import ]; musicbrainz = { # Search for deezer id's and use them in the autotagger external_ids = { deezer = true; }; }; # Log-on config # TODO: Add this, to upload the generated fingerprints (to help improve their # database) <2024-08-07> # acoustid = { # apikey = "TODO"; # }; }; mpdIntegration = { enableStats = true; enableUpdate = true; host = config.home.sessionVariables.MPD_HOST; }; }; # Use the json formatter instead of the YAML one, as the YAML formatter mangles the # longer python inline strings. # YAML is a superset of JSON. xdg.configFile."beets/config.yaml".source = lib.mkForce ((pkgs.formats.json {}).generate "beets-config" config.programs.beets.settings); }