Parsing ID3 tags in Bash and renaming MP3s
(Subtitle: Using Bash when I probably shouldn’t…)
First, let’s define a bunch of useful functions. We’ll later use these functions to extract the relevant sections of our ID3 tags. I’m going to assume you are using your interpreter directly, so lets copy & paste the following lines straight into your terminal:
function tag() { tail -c 128 "$1" | head -c 3; }
function title() { tail -c 125 "$1" | head -c 30 | rtrim; }
function artist() { tail -c 95 "$1" | head -c 30 | rtrim; }
function album() { tail -c 65 "$1" | head -c 30 | rtrim; }
function year() { tail -c 35 "$1" | head -c 4 | rtrim; }
function comment() { tail -c 31 "$1" | head -c 28 | rtrim; }
function track() { tail -c 3 "$1" | hexdump -ve '/1 "%02i "' \
| awk '{ if ($1 == 0 && $2 != 0) print $2" " }'; }
function genre() { tail -c 1 "$1" | hexdump -ve '/1 "%03i"'; }
function rtrim() { sed 's/\([^ ]*\) *$/\1/'; }
function filt() { tr -d '/*?'; }
Now lets say the current directory is full of MP3s that we would like to move to an appropriate folder and give a more appropriate filename (in my case, I had my corrupt old iPod from which I was attempting to extract all the music I could). The structure I’m aiming for is:
ARTIST – ALBUM / TRACK_NUMBER ARTIST – SONG_NAME
Making use of our functions defined above, this for loop ought to do the trick:
for file in *.mp3; do [ "$(tag "$file")" == "TAG" ] && \
( artist="$(artist "$file" | filt)"; \
album="$(album "$file" | filt)"; \
title="$(title "$file" | filt)"; \
track="$(track "$file" | filt)"; \
dir="$artist - $album"; \
newfile="$track$artist - $title.mp3"; \
\
mkdir -p "$dir"; \
mv "$file" "$dir/$newfile"); \
done
And indeed this “one liner” did a reasonable job on my MP3 collection, YMMV.
Of course this only works for ID3v1 tags and doesn’t support “extended” v1 tags.
I can’t imagine ID3v2 possibly being parsed in bash, but I’d like to be proved wrong
cool