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.1I'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:
#!/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." fiCode: 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" } } }