Metadata CLI

Description:
Often used alongside mp3-download, an interactive tool for batch injecting and editing MP3 metadata using exiftool and kid3-cli.
Also permits batch renaming of files based on existing metadata.

Usage:
 metadata -d,  –display   ─────── Display current metadata for all MP3s
 metadata -i,  –inject    ─────── Inject metadata from filenames into all MP3s
 metadata -r,  –rename    ─────── Rename MP3s using their embedded metadata

 metadata -al, –album <album-name>          ─────── Set the album name for all MP3s
 metadata -ar, –artist <artist-name>        ─────── Set the artist name for all MP3s

 metadata -ti, –title <title> <file.mp3>    ─────── Set the title for a single MP3
 metadata -tr, –track <track> <file.mp3>    ─────── Set the track number for a single MP3

 metadata -dc,  –depend   ─────── Run a dependency check
 metadata -h,  –help      ─────── Display this help message

Flags:
 metadata -q,  –quiet     ─────── Suppress metadata display after an operation

Examples:
   metadata -ti “New Title” “song.mp3”
   metadata -tr 05 “song.mp3”

   metadata -al “Album Name”
   metadata -ar “Artist Name”
   metadata -i
   metadata -i -q
   metadata -d

Filename formats for –inject:
 00 – Artist – Title.mp3
 Artist – Title.mp3

Download metadata.sh

#!/bin/bash
#
#
# Dependencies:
#   exiftool
#     sudo apt install exiftool
#
#     sudo apt install kid3-cli
#
set -e

# Set Version
VERSION="metadata 3.0.2"

####################
# COLOUR VARIABLES
####################

# Terminal Formatting
RESET="\033[0m"
BOLD="\033[1m"

# UI Colours
CLR_RED="\033[38;5;196m"  # Bright Red
CLR_GREEN="\033[38;5;46m" # Bright Lime Green

CLR_GOLD="\033[38;5;226m"         # Gold
CLR_PURPLE="\033[38;5;141m"       # Light Purple / Lavender
CLR_LABEL="\033[38;5;81m"         # Bright Sky Blue
CLR_LABEL2="\033[38;5;2m"         # Dark Forest Green
CLR_PROMPT="${BOLD}\033[38;5;51m" # Aqua + Bold
CLR_TEXT="\033[38;5;254m"         # Light Grey (optional)


# Setup Quiet Flag
QUIET=false

for arg in "$@"; do
    case "$arg" in
        -q | --quiet)
            QUIET=true
            ;;
    esac
done

####################
# DEPENDENCY CHECK
####################

dependency_check() {

    local depend_found="0"

    # Check kid3-cli exists
    if ! command -v kid3-cli >/dev/null 2>&1; then
        echo -e "${CLR_RED}Error:${RESET} kid3-cli is not installed - Required for updating metadata"
        echo
        echo "Install it with:"
        echo -e "${CLR_TEXT}  sudo apt install kid3-cli${RESET}"
        echo -e "${CLR_TEXT}  or your package manager equivelant${RESET}"

        echo
        depend_found="1"
    fi

    # Check exiftool exists
    if ! command -v exiftool >/dev/null 2>&1; then
        echo
        echo -e "${CLR_RED}Error:${RESET} exiftool is not installed - Required for displaying metadata"
        echo
        echo "Install it with:"
        echo -e "${CLR_TEXT}  sudo apt install exiftool${RESET}"
        echo -e "${CLR_TEXT}  or your package manager equivelant${RESET}"
        echo
        depend_found="1"
    fi

    if [[ $depend_found -eq "0" && "$1" == "-d" ]]; then
        echo ""
        echo "───────────────────────────────────────────────────────"
        echo -e " ▶ ${CLR_GREEN}All dependencies are met - metadata is ready to use!${RESET}"
        echo "───────────────────────────────────────────────────────"
        echo ""
        exit 0
    fi

    if [[ $depend_found -eq 1 ]]; then
        exit 1
    fi
}

if [[ "$1" != "-d" && "$1" != "--depend" ]]; then
    dependency_check
fi

####################
# HELP / INFO TEXT
####################
usage() {
    echo
    echo -e "${CLR_PURPLE}${BOLD}${VERSION}${RESET}"
    echo
    echo -e "${BOLD}Description:${RESET}${CLR_TEXT}"
    echo -e "Often used alongside mp3-download, an interactive tool for batch injecting and editing MP3 metadata using exiftool and kid3-cli."
    echo -e "Also permits batch renaming of files based on existing metadata.${RESET}"
    echo
    echo -e "${BOLD}Usage:${RESET}${CLR_TEXT}"
    echo -e "  metadata -d,  --display   ─────── Display current metadata for all MP3s"
    echo -e "  metadata -i,  --inject    ─────── Inject metadata from filenames into all MP3s"
    echo -e "  metadata -r,  --rename    ─────── Rename MP3s using their embedded metadata"
    echo
    echo -e "  metadata -al, --album <album-name>          ─────── Set the album name for all MP3s"
    echo -e "  metadata -ar, --artist <artist-name>        ─────── Set the artist name for all MP3s"
    echo
    echo -e "  metadata -ti, --title <title> <file.mp3>    ─────── Set the title for a single MP3"
    echo -e "  metadata -tr, --track <track> <file.mp3>    ─────── Set the track number for a single MP3"
    echo
    echo -e "  metadata -dc,  --depend   ─────── Run a dependency check"
    echo -e "  metadata -h,  --help      ─────── Display this help message${RESET}"
    echo
    echo -e "${BOLD}Flags:${RESET}${CLR_TEXT}"
    echo -e "  metadata -q,  --quiet     ─────── Suppress metadata display after an operation${RESET}"
    echo
    echo -e "${BOLD}Examples:${RESET}${CLR_TEXT}"
    echo -e "    metadata -ti \"New Title\" \"song.mp3\""
    echo -e "    metadata -tr 05 \"song.mp3\""
    echo
    echo -e "    metadata -al \"Album Name\""
    echo -e "    metadata -ar \"Artist Name\""
    echo -e "    metadata -i"
    echo -e "    metadata -i -q"

    echo -e "    metadata -d${RESET}"
    echo
    echo -e "${BOLD}Filename formats for --inject:${RESET}${CLR_TEXT}"
    echo -e "  00 - Artist - Title.mp3"
    echo -e "  Artist - Title.mp3${RESET}"
    echo

    exit 1
}

######################
# HELPER FUNCTINONS
######################

# Catch User CTRL+C Interrupt
handle_interrupt() {
    echo
    echo -e "${CLR_RED}Cancelled by user${RESET}"
    exit 1
}
trap handle_interrupt INT

run_format_select() {
    if [[ "$FORMAT" == "1" ]]; then
        run_cmd_track
    else
        run_cmd
    fi
}

# EXIT if folder contains no .MP3 files
mp3_check() {
    shopt -s nullglob
    files=(*.mp3)

    if [[ ${#files[@]} -eq 0 ]]; then
        echo -e "${CLR_RED}Error: No MP3 files found in this directory.${RESET}"
        exit 1
    fi
}

success_msg() {
    echo
    echo "──────────────────────────────"
    echo -e "${CLR_GREEN} ▶ Metadata Updated ${RESET}"
    echo "──────────────────────────────"
    echo
}

######################
# FUNCTINONS
######################

run_cmd() {
    echo
    echo -e "${CLR_PURPLE}  ▶ Injecting Metadata, please wait a moment...${RESET}"
    for filename in *.mp3; do

        TRACK_ARTIST=$(echo "${filename}" | sed -E 's/^(.*) - .*.mp3$/\1/')

        TRACK_TITLE=$(echo "${filename}" | sed -E 's/^.* - (.*).mp3$/\1/')

        if [[ "$ALBUM_UPDATE" == "y" || "$ALBUM_UPDATE" == "Y" ]]; then
            kid3-cli -c "set album \"${TRACK_ALBUM}\"" -c "set title \"${TRACK_TITLE}\"" -c "set artist \"${TRACK_ARTIST}\"" "$filename"
        else
            kid3-cli -c "set title \"${TRACK_TITLE}\"" -c "set artist \"${TRACK_ARTIST}\"" "$filename"
        fi

    done

    success_msg

    sleep 2
    if ! $QUIET; then
        run_display
    fi
}

run_cmd_track() {
    echo
    echo -e " ${CLR_PURPLE}  ▶ Injecting Metadata, please wait a moment...${RESET}"
    echo
    for filename in *.mp3; do

        TRACK_NUMBER=$(echo "${filename}" | sed -E 's/^([0-9]{2}) - .* - .*.mp3$/\1/')

        TRACK_ARTIST=$(echo "${filename}" | sed -E 's/^[0-9]{2} - (.*) - .*.mp3$/\1/')

        TRACK_TITLE=$(echo "${filename}" | sed -E 's/^[0-9]{2} - .* - (.*).mp3$/\1/')

        if [[ "$ALBUM_UPDATE" == "y" || "$ALBUM_UPDATE" == "Y" ]]; then
            kid3-cli -c "set album \"${TRACK_ALBUM}\"" -c "set title \"${TRACK_TITLE}\"" -c "set artist \"${TRACK_ARTIST}\"" -c "set track \"${TRACK_NUMBER}\"" "$filename"
        else
            kid3-cli -c "set title \"${TRACK_TITLE}\"" -c "set artist \"${TRACK_ARTIST}\"" -c "set track \"${TRACK_NUMBER}\"" "$filename"
        fi

    done

    success_msg

    sleep 1
    if ! $QUIET; then
        run_display
    fi

}

run_display() {
    for f in *.mp3; do

        title=$(exiftool -s3 -Title "$f" | sed 's#[/\\:*?"<>|]#-#g')
        artist=$(exiftool -s3 -Artist "$f" | sed 's#[/\\:*?"<>|]#-#g')
        TRACK_ALBUM=$(exiftool -s3 -Album "$f" | sed 's#[/\\:*?"<>|]#-#g')
        track=$(exiftool -s3 -Track "$f" | sed 's#[/\\:*?"<>|]#-#g')

        echo -e "${BOLD}${CLR_LABEL}$f${RESET}"
        echo -e "${CLR_LABEL2}Artist:${RESET} $artist"
        echo -e "${CLR_LABEL2}Title:${RESET}  $title"
        echo -e "${CLR_LABEL2}Album:${RESET}  $TRACK_ALBUM"
        echo -e "${CLR_LABEL2}Track:${RESET}  $track"
        echo
        echo "────"
        echo

    done
}

######################
# FUNCTIONS - RENAME
######################

cmd_rename() {
    echo " "
    echo -e "${BOLD}${CLR_GOLD} Embedded metadata suggests these filenames:${RESET}"
    echo " "
    run_rename_test

    ask_proceed
    echo
    echo "──────────────────────────────"
    echo -e "${CLR_GREEN} Tracks Renamed Successfully${RESET}"
    echo "──────────────────────────────"
    echo
    ls -lah
    echo
}

run_rename_test() {
    for f in *.mp3; do
        track=$(exiftool -s3 -Track "$f" | sed 's#/.*##')
        artist=$(exiftool -s3 -Artist "$f" | sed 's#[/\\:*?"<>|]#-#g')
        title=$(exiftool -s3 -Title "$f" | sed 's#[/\\:*?"<>|]#-#g')

        if [[ "$track" =~ ^[0-9]$ ]]; then
            track="0$track"
        fi

        new="$track - $artist - $title.mp3"
        echo "$new"
    done
}

ask_proceed() {
    echo " "
    echo -en "${CLR_PROMPT}Does everything look right? [Y/n]: ${RESET}"
    read -r CONFIRM
    if [[ "$CONFIRM" == "n" || "$CONFIRM" == "N" ]]; then
        echo " "
        echo -e "${CLR_RED}Cancelled:${RESET} By User Request"
        exit 1
    else
        echo ""
        echo -e "${CLR_PURPLE}  ▶ Renaming Files, please wait a moment...${RESET}"
        echo ""

        run_rename
    fi
}

run_rename() {

    for f in *.mp3; do
        track=$(exiftool -s3 -Track "$f" | sed 's#/.*##')
        artist=$(exiftool -s3 -Artist "$f" | sed 's#[/\\:*?"<>|]#-#g')
        title=$(exiftool -s3 -Title "$f" | sed 's#[/\\:*?"<>|]#-#g')

        if [[ "$track" =~ ^[0-9]$ ]]; then
            track="0$track"
        fi

        new="$track - $artist - $title.mp3"

        if [[ "$f" == "$new" ]]; then
            echo "Skipping: $f (already correct)"
        else
            mv -- "$f" "$new"
        fi
    done
}

##############################
# INDIVIDUAL METATADATA EDITS
##############################

# Update ALBUM metadata ONLY & exit
cmd_album() {

    if [[ -z "$1" ]]; then
        echo -e "${CLR_RED}Error:${RESET} No Album Name Specified."
        exit 1
    fi

    ALBUM_NAME="${*:1}"

    echo ""
    echo -e " ${CLR_GOLD}Setting album:${RESET} \"$ALBUM_NAME\""
    echo ""

    echo -e "${CLR_PURPLE}  ▶ Injecting Metadata, please wait a moment...${RESET}"
    echo ""

    for filename in *.mp3; do
        kid3-cli -c "set album \"$ALBUM_NAME\"" "$filename"
    done

    if ! $QUIET; then
        run_display
    fi

    success_msg
    exit 0
}


# Update ARTIST metadata ONLY & exit
cmd_artist() {
    if [[ -z "$1" ]]; then
        echo -e "${CLR_RED}Error:${RESET} No Artist Name Specified."
        exit 1
    fi

    ARTIST_NAME="${*:1}"

    echo ""
    echo -e " ${CLR_GOLD}Setting Artist:${RESET} \"$ARTIST_NAME\""
    echo ""

    echo -e "${CLR_PURPLE}  ▶ Injecting Metadata, please wait a moment...${RESET}"
    echo ""

    for filename in *.mp3; do
        kid3-cli -c "set artist \"$ARTIST_NAME\"" "$filename"
    done

    if ! $QUIET; then
        run_display
    fi

    success_msg
    exit 0
}


# Update SINGLE TRACK metadata ONLY & exit
cmd_track() {
    if [[ -z "$2" ]]; then
        echo -e "${CLR_RED}Error: ${RESET}No file specified."
        exit 1
    fi

     local track_input="$1"
     local file_input="$2"

    echo ""
    echo -e " ${CLR_GOLD}Setting Track #:${RESET} \"$track_input\""
    echo ""

    echo -e "${CLR_PURPLE}  ▶ Injecting Metadata, please wait a moment...${RESET}"
    echo ""

    kid3-cli -c "set track \"$track_input\"" "$file_input"

    title=$(exiftool -s3 -Title "$file_input" | sed 's#[/\\:*?"<>|]#-#g')
    artist=$(exiftool -s3 -Artist "$file_input" | sed 's#[/\\:*?"<>|]#-#g')
    TRACK_ALBUM=$(exiftool -s3 -Album "$file_input" | sed 's#[/\\:*?"<>|]#-#g')
    track=$(exiftool -s3 -Track "$file_input" | sed 's#[/\\:*?"<>|]#-#g')

    if ! $QUIET; then
        echo -e "${BOLD}${CLR_LABEL}${file_input}${RESET}"
        echo -e "${CLR_LABEL2}Artist:${RESET} $artist"
        echo -e "${CLR_LABEL2}Title:${RESET}  $title"
        echo -e "${CLR_LABEL2}Album:${RESET}  $TRACK_ALBUM"
        echo -e "${CLR_LABEL2}Track:${RESET}  $track"
        echo
        echo "────"
        echo
    fi

    success_msg
    exit 0
}


# Update SINGLE TITLE metadata ONLY & exit
cmd_title() {

    if [[ $# -lt 2 ]]; then
        echo -e "${CLR_RED}Error: ${RESET}Title and file must be specified."
        exit 1
    fi

    local file_input="${@: -1}"
    local title_input="${*:1:$#-1}"

    echo ""
    echo -e " ${CLR_GOLD}Setting Title:${RESET} \"${title_input}\""
    echo ""

    echo -e "${CLR_PURPLE}  ▶ Injecting Metadata, please wait a moment...${RESET}"
    echo ""

    kid3-cli -c "set title \"${title_input}\"" "${file_input}"

    title=$(exiftool -s3 -Title "$file_input" | sed 's#[/\\:*?"<>|]#-#g')
    artist=$(exiftool -s3 -Artist "$file_input" | sed 's#[/\\:*?"<>|]#-#g')
    TRACK_ALBUM=$(exiftool -s3 -Album "$file_input" | sed 's#[/\\:*?"<>|]#-#g')
    track=$(exiftool -s3 -Track "$file_input" | sed 's#[/\\:*?"<>|]#-#g')

    if ! $QUIET; then
        echo -e "${BOLD}${CLR_LABEL}${file_input}${RESET}"
        echo -e "${CLR_LABEL2}Artist:${RESET} $artist"
        echo -e "${CLR_LABEL2}Title:${RESET}  $title"
        echo -e "${CLR_LABEL2}Album:${RESET}  $TRACK_ALBUM"
        echo -e "${CLR_LABEL2}Track:${RESET}  $track"
        echo
        echo "────"
        echo
    fi

    success_msg
    exit 0

}

#########################
# INTERACTIVE INJECT TOOL
########################

cmd_inject() {
    mp3_check
    echo
    if ! $QUIET; then
        echo -e "${BOLD}${CLR_GOLD}Current Metadata: ${RESET}"
        sleep 1.5
        echo
        run_display
    fi

    echo -en "${CLR_PROMPT}Update the metadata using the filenames? (Y/n)${RESET} "

    read -r UPDATE
    if [[ "$UPDATE" == "n" || "$UPDATE" == "N" ]]; then
        echo -e "${CLR_RED} Exiting: ${RESET}User Requested"
        exit 1
    else

        echo
        echo -e "${CLR_PROMPT}Select filename format: ${RESET}"
        echo
        echo -e "${BOLD} [1] ${RESET}${CLR_GOLD}00 - Band - Song.mp3${RESET}"
        echo -e "${BOLD} [2] ${RESET}${CLR_GOLD}Band - Song.mp3${RESET}"
        echo

        while true; do
            echo -ne "${CLR_PROMPT}Enter choice [1-2]:${RESET} "
            read -r FORMAT
            case "$FORMAT" in
                1 | 2) break ;;
                *) echo -e "${CLR_RED}Invalid input. Please enter 1 or 2.${RESET}" ;;
            esac
        done

        echo
        echo -e "${CLR_GOLD}Format Selected:${RESET}"

        case "$FORMAT" in
            1)
                echo "  00 - Band - Song.mp3"
                ;;
            2)
                echo "  Band - Song.mp3"
                ;;
        esac

        echo

        # ASK IF THE ALBUM NAME NEEDS CHANGING ALSO
        echo
        echo -en "${CLR_PROMPT}Change the ALBUM name? (Y/N): ${RESET}"
        read -r ALBUM_UPDATE

        if [[ "$ALBUM_UPDATE" == "y" || "$ALBUM_UPDATE" == "Y" ]]; then
            echo""
            echo -e "${CLR_GOLD}  New ALBUM name Confirmed."
            echo
            echo -e "${CLR_PROMPT}Specify Album name: ${RESET}"
            read -r USER_INPUT
            TRACK_ALBUM="$USER_INPUT"
            run_format_select
        else
            run_format_select
        fi
    fi
}


####################
# COMMANDS
####################
case "$1" in
     -d | --display)
        run_display
        ;;
    -r | --rename)
        cmd_rename
        ;;
     -i | --inject)
        cmd_inject
        ;;
    -al | --album)
        shift
        cmd_album "$@"
        ;;
    -ar | --artist)
        shift
        cmd_artist "$@"
        ;;
    -tr | --track)
        shift
        cmd_track "$@"
        ;;
    -ti | --title)
        shift
        cmd_title "$@"
        ;;
    -dc | --depend)
        shift
        dependency_check -d
        ;;
    *)
        usage
        ;;
esac