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
- Open the Terminal app on your Mac.
- Type
nano cleanup.shand press Enter. - 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
-
Save the file in nano: Press Ctrl + O, then Enter.
-
Exit nano: Press Ctrl + X.
-
Make it executable:
bashchmod +x cleanup.sh -
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
- Open MusicBrainz Picard.
- Go to Settings (Cmd + ,) > File Naming.
- Check "Move files when saving".
- Set Destination Directory to your final, clean music folder.
- Check "Rename files when saving".
- 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)
- Single Disc:
2. Tag & Artwork Settings
- Go to Tags in the settings sidebar.
- Check "Clear existing tags" (removes junk metadata).
- Check "Remove ID3v2.3 tags" (forces current standards).
- 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
- Drag your cleaned folder into Picard's left panel.
- Click Cluster (groups files by existing tags).
- Select the clusters and click Scan (listens to audio to identify tracks).
- Review matches in the right panel.
- Green/Gold icons: Good match.
- Red icons: Needs manual review.
- Select the matched albums and click Save.
- Picard will move the files to your new library location.
- Cleanup: Run the strict_clean.sh script on your source folder again to remove the empty folders left behind by the move.
Last updated