Organizing Music Files With SongRec and Shazam

by Robin

This article is aimed at music loving *NIX enthusiasts who don't fancy streaming services like Spotify.

Chances are you've collected quite a bunch of songs wearing charming names like 8017143879426925093.mp3.  It may be impossible to extract the corresponding name, album and artist data directly from the music files.  Therefore, a workaround may be required.

Enter SongRec (github.com/marin-m/SongRec), a command line tool using Shazam to retrieve the data we're looking for.  We should be able to transform a flat directory containing unreadable file names into a neatly organized directory structure like the following:

- Artist name
  - Album name 1
    - Song name 1.1
    - Song name 1.2
  - Album name 2
    - Song name 2.1

I've written a little Bash script taking either a file name or a directory name as its single argument.  In the latter case, the first level of the directory will be searched for files containing the "mp3" extension.  SongRec, however, supports all kinds of extensions, so this script can easily be tweaked to support those as well.

Obviously, the SongRec application is required in order to run it.  You might need FFmpeg as well.

What will we do when the connection to Shazam times out for some reason?  We will move on.

What will we do when a song is not recognized by Shazam?  We will move on.

What will we do when a song has been recognized but the album or artist name has not been?  We will call album or artist name "Unknown".

What will happen to compilation albums?  Its songs will, unfortunately, be split up amongst all the different albums from which the songs originate.

What will happen when the script is run again?  Only new and previously unrecognized songs will be fed to Shazam, since the other ones will have been moved to a deeper directory structure by then.

Without further ado, the source:

songrec.sh:

#!/bin/bash
TIMEOUT=10

function renameSong() {
  fullSongPath=$1
  songFileBaseName=$(basename "${fullSongPath}")
  songFileExtension="${songFileBaseName##*.}"

  if [ "${songFileExtension}" == "mp3" ]; then
    if [ -f "${fullSongPath}" ]; then
      echo "Trying to recognize song: ${fullSongPath}"
      songRecOutput=$(timeout ${TIMEOUT} songrec recognize "${fullSongPath}" --csv)
      if [ $? -eq 0 ]; then
        songRecOutput=$(echo -e "${songRecOutput}" | tail -1)
        parsedMetaData=$(parseMetaData "${songRecOutput}")
        readarray -d "|" -t parsedMetaDataParts <<< "${parsedMetaData}" newSongPath="$(dirname "${fullSongPath}")/"${parsedMetaDataParts[0]}"/"${parsedMetaDataParts[1]}""
        mkdir -p "${newSongPath}"
        songName=$(echo "${parsedMetaDataParts[2]}" | tr -d '\n')
        mv -f "${fullSongPath}" "${newSongPath}"/"${songName}"."${songFileExtension}"
      else
        echo "Failed to recognize song within "${TIMEOUT}" seconds.  Skipping."
      fi
    else
      echo "Failed to find song: "${fullSongPath}""
    fi

    echo "-------------------------------------------------------"

    else
      echo "The following extension is not supported: "${songFileExtension}".  Skipping."
  fi
}

function trim() {
  var="$*"
  var="${var#"${var%%[![:space:]]*}"}"
  var="${var%"${var##*[![:space:]]}"}"
  printf '%s' "${var}"
}

function fetchCsvPart() {
  text=$1
  csvPartNr=$2
  csvPart=$(echo "${text}" | awk -v idx="${csvPartNr}" 'BEGIN {FPAT = "([^,]*)|(\"[^\"]+\")"}{printf("%s", $idx)}')
  echo "${csvPart//\"/}"
}

function parseMetaData() {
  metaData=$1
  albumName=$(fetchCsvPart "${metaData}" "2")
  artistAndSongName=$(fetchCsvPart "${metaData}" "1")
  readarray -d "-" -t artistAndSongNameParts <<< "${artistAndSongName}"
  artistName=$(trim "${artistAndSongNameParts[0]}")
  songName=$(trim "${artistAndSongNameParts[1]}")
  songName="${songName//[\/]/ _ }"

  if [ "${artistName}" == "" ]; then
    artistName="Unknown"
  fi

  if [ "${albumName}" == "" ]; then
    albumName="Unknown"
  fi

  echo "${artistName}|${albumName}|${songName}"
}

# Main

IFS=$(echo -en "\n\b")

if [ $# -gt 0 ]; then
  fsArg=$(realpath "$1")
  if [ -f "${fsArg}" ]; then
    renameSong "${fsArg}"
  elif [ -d "${fsArg}" ]; then
    songFilePaths=$(find "${fsArg}" -maxdepth 1 -mindepth 1 -type f -exec readlink -f {} \;)
    for songFilePath in "${songFilePaths}"; do
      renameSong "${songFilePath}"
    done
  else
    echo "The provided argument does not seem to be a valid file or directory: "${fsArg}""
  fi
else
  echo "Please provide either a song file name or a directory containing one or more song files."
fi

Code: songrec.sh




I can't get script to work properly?  There might be a syntax error somewhere but I didn't fiddle with it enougth to fix it.

SongRec Examples:

tmp$ ./songrec.sh audio.mp3
Trying to recognize song: /tmp/audio.mp3
mkdir: cannot create directory '': No such file or directory
mv: cannot move '/tmp/audio.mp3' to '/Die, Die My Darling.mp3': Permission denied
-------------------------------------------------------

# Convert OGG to MP3, if required
$ ffmpeg -i audio.ogg -acodec libmp3lame audio.mp3

$ songrec recognize audio.mp3
Metallica - Die, Die My Darling

$ songrec recognize audio.mp3 --csv
song_name,album,recognition_date,track_key,release_year,genre
"Metallica - Die, Die My Darling",Garage Inc.,Wed May 10 22:09:10 2023,5937490,1998,Metal

$ songrec audio-file-to-recognized-song audio.mp3
{
  "location": {
    "accuracy": 0.01,
    "altitude": 300.0,
    "latitude": 45.0,
    "longitude": 2.0
  },
  "matches": [
    {
      "frequencyskew": -0.0013683438,
      "id": "211897540",
      "offset": 67.626898437,
      "timeskew": -0.0015865564
    }
  ],
  "tagid": "9af916fe-aeb9-4331-adfa-20735118cb6c",
  "timestamp": 147509488,
  "timezone": "Europe/Paris",
  "track": {
    "albumadamid": "579370472",
    "artists": [
      {
        "adamid": "3996865",
        "id": "42"
      }
    ],
    "genres": {
      "primary": "Metal"
    },
    "highlightsurls": {
      "artisthighlightsurl": "https://cdn.shazam.com/video/v3/en/US/android/3996865/highlights?affiliate=mttnagencyid%3Ds2n%26mttnsiteid%3D125115%26mttn3pid%3DApple-Shazam%26mttnsub1%3DShazam_android_am%26mttnsub2%3D5348615A-616D-3235-3830-44754D6D5973%26itscg%3D30201%26app%3Dmusic%26itsct%3DShazam_android_am"
    },
    "hub": {
      "actions": [
        {
          "id": "579370551",
          "name": "apple",
          "type": "applemusicplay"
        },
        {
          "name": "apple",
          "type": "uri",
          "uri": "https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview125/v4/15/e4/2d/15e42d4b-9bab-fa69-1c7f-56dc29e02414/mzaf_12541352701869078004.plus.aac.p.m4a"
        }
      ],
      "displayname": "APPLE MUSIC",
      "explicit": false,
      "image": "https://images.shazam.com/static/icons/hub/android/v5/applemusic_{scalefactor}.png",
      "options": [
        {
          "actions": [
            {
              "name": "hub:applemusic:deeplink",
              "type": "intent",
              "uri": "intent://music.apple.com/us/album/die-die-my-darling/579370472?i=579370551&mttnagencyid=s2n&mttnsiteid=125115&mttn3pid=Apple-Shazam&mttnsub1=Shazam_android_am&mttnsub2=5348615A-616D-3235-3830-44754D6D5973&itscg=30201&app=music&itsct=Shazam_android_am#Intent;scheme=http;package=com.apple.android.music;action=android.intent.action.VIEW;end"
            },
            {
              "id": "579370551",
              "name": "hub:applemusic:connect",
              "type": "applemusicconnect",
              "uri": "https://unsupported.shazam.com"
            },
            {
              "name": "hub:applemusic:androidstore",
              "type": "uri",
              "uri": "https://play.google.com/store/apps/details?id=com.apple.android.music&referrer=utm_source=https%3A%2F%2Fmusic.apple.com%2Fsubscribe%3Fmttnagencyid%3Ds2n%26mttnsiteid%3D125115%26mttn3pid%3DApple-Shazam%26mttnsub1%3DShazam_android_am%26mttnsub2%3D5348615A-616D-3235-3830-44754D6D5973%26itscg%3D30201%26app%3Dmusic%26itsct%3DShazam_android_am"
            }
          ],
          "beacondata": {
            "providername": "applemusic",
            "type": "open"
          },
          "caption": "OPEN",
          "colouroverflowimage": false,
          "image": "https://images.shazam.com/static/icons/hub/android/v5/overflow-open-option_{scalefactor}.png",
          "listcaption": "Open in Apple Music",
          "overflowimage": "https://images.shazam.com/static/icons/hub/android/v5/applemusic-overflow_{scalefactor}.png",
          "providername": "applemusic",
          "type": "open"
        }
      ],
      "providers": [
        {
          "actions": [
            {
              "name": "hub:spotify:searchdeeplink",
              "type": "uri",
              "uri": "spotify:search:Die%2C%20Die%20My%20Darling%20Metallica"
            }
          ],
          "caption": "Open in Spotify",
          "images": {
            "default": "https://images.shazam.com/static/icons/hub/android/v5/spotify_{scalefactor}.png",
            "overflow": "https://images.shazam.com/static/icons/hub/android/v5/spotify-overflow_{scalefactor}.png"
          },
          "type": "SPOTIFY"
        },
        {
          "actions": [
            {
              "name": "hub:youtubemusic:androiddeeplink",
              "type": "uri",
              "uri": "https://music.youtube.com/search?q=Die%2C+Die+My+Darling+Metallica&feature=shazam"
            }
          ],
          "caption": "Open in YouTube Music",
          "images": {
            "default": "https://images.shazam.com/static/icons/hub/android/v5/youtubemusic_{scalefactor}.png",
            "overflow": "https://images.shazam.com/static/icons/hub/android/v5/youtubemusic-overflow_{scalefactor}.png"
          },
          "type": "YOUTUBEMUSIC"
        },
        {
          "actions": [
            {
              "name": "hub:deezer:searchdeeplink",
              "type": "uri",
              "uri": "deezer-query://www.deezer.com/play?query=%7Btrack%3A%27Die%2C+Die+My+Darling%27%20artist%3A%27Metallica%27%7D"
            }
          ],
          "caption": "Open in Deezer",
          "images": {
            "default": "https://images.shazam.com/static/icons/hub/android/v5/deezer_{scalefactor}.png",
            "overflow": "https://images.shazam.com/static/icons/hub/android/v5/deezer-overflow_{scalefactor}.png"
          },
          "type": "DEEZER"
        }
      ],
      "type": "APPLEMUSIC"
    },
    "images": {
      "background": "https://is3-ssl.mzstatic.com/image/thumb/AMCArtistImages112/v4/7c/ec/2d/7cec2d74-d50a-445e-eea6-f6097a01cea7/00e0f5e1-2dd6-4f71-bbdf-d2cbdd7560eb_ami-identity-7650ac390e73210afeaf26aae0ec7e10-2022-11-28T21-57-07.638Z_cropped.png/800x800cc.jpg",
      "coverart": "https://is3-ssl.mzstatic.com/image/thumb/Music125/v4/ec/2f/09/ec2f0972-db2a-ef0d-52eb-7167fee36d5a/0075596229968.jpg/400x400cc.jpg",
      "coverarthq": "https://is3-ssl.mzstatic.com/image/thumb/Music125/v4/ec/2f/09/ec2f0972-db2a-ef0d-52eb-7167fee36d5a/0075596229968.jpg/400x400cc.jpg",
      "joecolor": "b:071823p:cee9f8s:97d2eet:a6bfcdq:7aadc6"
    },
    "isrc": "USEV19800009",
    "key": "5937490",
    "layout": "5",
    "relatedtracksurl": "https://cdn.shazam.com/shazam/v3/en/US/android/-/tracks/track-similarities-id-5937490?startFrom=0&pageSize=20&connected=",
    "sections": [
      {
        "metadata": [
          {
            "text": "Garage Inc.",
            "title": "Album"
          },
          {
            "text": "Blackened Recordings",
            "title": "Label"
          },
          {
            "text": "1998",
            "title": "Released"
          }
        ],
        "metapages": [
          {
            "caption": "Metallica",
            "image": "https://is3-ssl.mzstatic.com/image/thumb/AMCArtistImages112/v4/7c/ec/2d/7cec2d74-d50a-445e-eea6-f6097a01cea7/00e0f5e1-2dd6-4f71-bbdf-d2cbdd7560eb_ami-identity-7650ac390e73210afeaf26aae0ec7e10-2022-11-28T21-57-07.638Z_cropped.png/800x800cc.jpg"
          },
          {
            "caption": "Die, Die My Darling",
            "image": "https://is3-ssl.mzstatic.com/image/thumb/Music125/v4/ec/2f/09/ec2f0972-db2a-ef0d-52eb-7167fee36d5a/0075596229968.jpg/400x400cc.jpg"
          }
        ],
        "tabname": "Song",
        "type": "SONG"
      },
      {
        "tabname": "Video",
        "type": "VIDEO",
        "youtubeurl": "https://cdn.shazam.com/video/v3/-/US/android/5937490/youtube/video?q=Metallica+%22Die,+Die+My+Darling%22"
      },
      {
        "tabname": "Related",
        "type": "RELATED",
        "url": "https://cdn.shazam.com/shazam/v3/en/US/android/-/tracks/track-similarities-id-5937490?startFrom=0&pageSize=20&connected="
      }
    ],
    "share": {
      "avatar": "https://is3-ssl.mzstatic.com/image/thumb/AMCArtistImages112/v4/7c/ec/2d/7cec2d74-d50a-445e-eea6-f6097a01cea7/00e0f5e1-2dd6-4f71-bbdf-d2cbdd7560eb_ami-identity-7650ac390e73210afeaf26aae0ec7e10-2022-11-28T21-57-07.638Z_cropped.png/800x800cc.jpg",
      "href": "https://www.shazam.com/track/5937490/die-die-my-darling",
      "html": "https://www.shazam.com/snippets/email-share/5937490?lang=en&country=US",
      "image": "https://is3-ssl.mzstatic.com/image/thumb/Music125/v4/ec/2f/09/ec2f0972-db2a-ef0d-52eb-7167fee36d5a/0075596229968.jpg/400x400cc.jpg",
      "snapchat": "https://www.shazam.com/partner/sc/track/5937490",
      "subject": "Die, Die My Darling - Metallica",
      "text": "I used Shazam to discover Die, Die My Darling by Metallica.",
      "twitter": "I used @Shazam to discover Die, Die My Darling by Metallica."
    },
    "subtitle": "Metallica",
    "title": "Die, Die My Darling",
    "type": "MUSIC",
    "url": "https://www.shazam.com/track/5937490/die-die-my-darling",
    "urlparams": {
      "{trackartist}": "Metallica",
      "{tracktitle}": "Die%2C+Die+My+Darling"
    }
  }
}
Return to $2600 Index