My digital music collection cleanup & organization strategy

Created by brian on

This guide outlines a two-part workflow to clean up a messy music directory and organize it into a strict, minimalist library structure on macOS.

For reference, this is how I generally organize my tunes:

Music
| Music-General
| Music-Holidays
| cleanup.sh
| backup.sh

Part 1: The Cleanup Script

Goal: Remove all non-audio files (images, logs, .DS_Store, etc.) and delete empty folders, while preserving the actual music files.

1. Create the Script

  1. Open the Terminal app on your Mac.
  2. Type nano cleanup.sh and press Enter.
  3. Copy and paste the code below into the window:
bash
#!/bin/zsh # --- Configuration --- # Audio extensions to KEEP. KEEP_EXTENSIONS=("flac" "mp3" "m4a" "wav" "aiff" "ogg" "wma" "ape" "alac" "aac") # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' BLUE='\033[1;34m' NC='\033[0m' if [ -z "$1" ]; then echo -e "${RED}Error: No directory provided.${NC}" echo "Usage: ./cleanup.sh /path/to/music" exit 1 fi TARGET_DIR="$1" if [ ! -d "$TARGET_DIR" ]; then echo -e "${RED}Error: Directory not found:${NC} $TARGET_DIR" exit 1 fi # Get absolute path of this script to prevent self-deletion SCRIPT_PATH=${0:A} # ========================================== # STAGE 1: SCAN AND GROUP NON-AUDIO FILES # ========================================== echo -e "${CYAN}=== STAGE 1: Scanning for non-audio files ===${NC}" # Master list for deletion FILES_TO_DELETE=() # Associative array for grouping files by extension for the preview typeset -A PREVIEW_GROUPS while IFS= read -r file; do # 1. Self-Preservation Check if [[ "${file:A}" == "$SCRIPT_PATH" ]]; then continue fi # 2. Extract Extension filename=$(basename "$file") if [[ "$filename" == *.* ]]; then ext="${filename##*.}" ext_lower="${ext:l}" else ext="No Extension" ext_lower="no_extension" fi # 3. Handle .DS_Store explicitly if [[ "$filename" == ".DS_Store" ]]; then FILES_TO_DELETE+=("$file") PREVIEW_GROUPS[SYSTEM_FILES]+="$file"$'\n' continue fi # 4. Check if Audio is_audio=false for valid_ext in $KEEP_EXTENSIONS; do if [[ "$ext_lower" == "$valid_ext" ]]; then is_audio=true break fi done # 5. If Not Audio, Add to Lists if [ "$is_audio" = false ]; then FILES_TO_DELETE+=("$file") # Add to the group string (using uppercase ext as key) group_key="${ext:u}" PREVIEW_GROUPS[$group_key]+="$file"$'\n' fi done < <(find "$TARGET_DIR" -type f) # ========================================== # PREVIEW PHASE # ========================================== if [ ${#FILES_TO_DELETE[@]} -eq 0 ]; then echo -e "${GREEN}No non-audio files found.${NC}" else echo -e "${YELLOW}The following non-audio files have been found:${NC}" echo "" # Iterate over the group keys (File Types) for key in ${(k)PREVIEW_GROUPS}; do # Count lines in this group variable # We trim the trailing newline to get accurate count content="${PREVIEW_GROUPS[$key]}" file_list=("${(@f)content}") count=${#file_list[@]} echo -e "${BLUE}=== $key ($count files) ===${NC}" # Print the files in this group echo -n "${PREVIEW_GROUPS[$key]}" | while read -r line; do # Indent files slightly for readability if [[ -n "$line" ]]; then echo " $line" fi done echo "" done echo -e "${YELLOW}Total files to delete:${NC} ${#FILES_TO_DELETE[@]}" echo "---------------------------------------------------" read -q "response?Confirm delete ALL above files? (y/n) " echo "" if [[ "$response" == "y" ]]; then echo -e "${CYAN}Deleting...${NC}" for f in "${FILES_TO_DELETE[@]}"; do rm "$f" done echo -e "${GREEN}Files deleted.${NC}" else echo -e "${RED}Skipping file deletion.${NC}" fi fi echo "---------------------------------------------------" # ========================================== # STAGE 2: DELETE EMPTY DIRECTORIES # ========================================== echo -e "${CYAN}=== STAGE 2: Scanning for empty directories ===${NC}" EMPTY_DIRS=() while IFS= read -r dir; do # Protect root target dir if [[ "${dir:A}" == "${TARGET_DIR:A}" ]]; then continue fi EMPTY_DIRS+=("$dir") done < <(find "$TARGET_DIR" -type d -empty) if [ ${#EMPTY_DIRS[@]} -eq 0 ]; then echo -e "${GREEN}No empty directories found.${NC}" exit 0 fi echo -e "${YELLOW}The following EMPTY directories will be REMOVED:${NC}" for d in "${EMPTY_DIRS[@]}"; do echo -e "${RED}[REMOVE]${NC} $d" done echo -e "${YELLOW}Total empty folders:${NC} ${#EMPTY_DIRS[@]}" echo "" read -q "response?Confirm remove empty folders? (y/n) " echo "" if [[ "$response" == "y" ]]; then count=0 for d in "${EMPTY_DIRS[@]}"; do rmdir "$d" 2>/dev/null if [ $? -eq 0 ]; then ((count++)) fi done echo -e "${GREEN}Success! Removed $count empty folders.${NC}" else echo -e "${GREEN}Operation cancelled. Empty folders remain.${NC}" fi

2. Save and Run

  1. Save the file in nano: Press Ctrl + O, then Enter.

  2. Exit nano: Press Ctrl + X.

  3. Make it executable:

    bash
    chmod +x cleanup.sh
  4. Run the script on your target folder:

    bash
    ./cleanup.sh "/Users/yourname/Downloads/MusicToClean"

Part 2: MusicBrainz Picard Setup

Goal: Rename files, fix metadata, and organize them into a Artist / Album (Year) / Track structure automatically.

1. File Naming Settings

  1. Open MusicBrainz Picard.
  2. Go to Settings (Cmd + ,) > File Naming.
  3. Check "Move files when saving".
  4. Set Destination Directory to your final, clean music folder.
  5. Check "Rename files when saving".
  6. Paste the following code into the "Name files like this" box:
python
$if2(%albumartist%,%artist%)/ $if(%album%,%album%$if(%date%, \\($left(%date%,4)\\)))/ $if($gt(%totaldiscs%,1),$num(%discnumber%,2)-,)$num(%tracknumber%,2) \- %title%

Naming Logic:

  • Root Folder: Artist Name.
  • Album Folder: "Album Title (Year)". (Handles missing years gracefully).
  • File Name:
    • Single Disc: 01 - Title.mp3
    • Multi-Disc: 01-01 - Title.mp3 (Disc 01, Track 01)

2. Tag & Artwork Settings

  1. Go to Tags in the settings sidebar.
    • Check "Clear existing tags" (removes junk metadata).
    • Check "Remove ID3v2.3 tags" (forces current standards).
  2. Go to Cover Art.
    • Uncheck "Save cover images as separate files" (Keep folders clean).
    • Check "Embed cover images into tags" (Save art inside the audio file).
    • Check “Cover Art Archive: Release Group” (Fallback to release group images, when release image is not available)

3. Execution Workflow

  1. Drag your cleaned folder into Picard's left panel.
  2. Click Cluster (groups files by existing tags).
  3. Select the clusters and click Scan (listens to audio to identify tracks).
  4. Review matches in the right panel.
    • Green/Gold icons: Good match.
    • Red icons: Needs manual review.
  5. Select the matched albums and click Save.
    • Picard will move the files to your new library location.
  6. Cleanup: Run the strict_clean.sh script on your source folder again to remove the empty folders left behind by the move.

Last updated

>

Comments (0)

Loading...

Join the conversation!

Sign up for free to leave a comment.