Monday, 18 March 2013

Making an MP3 CD in Linux

Sometimes it is difficult to be an open source enthusiast.  Alongside my enthusiasm for free and open source software, I am also a fan of high quality audio.  To me, it makes little sense to store my music in any format other than ogg vorbis files: apart from FLAC and other lossless formats, which consume too much hard disk space for a music collection as vast as mine, vorbis has, to my ears, the best sound quality.

The problem is that my portable music player does not recognise vorbis files, nor does the CD player in my car.  The common thread between them (the portable music player recognises FLAC and MP3, the car CD player WMA and MP3) is support for MP3 files.

The way I chose to do this is to create a directory/folder into which I place ogg files, so that they may be converted to MP3.  This folder is then maintained by deleting songs with which I am bored, and adding new songs that I would want to take wherever I go.

The important thing is not to cut and paste the songs into the folder, because we want to delete the original vorbis files, as our player does not support them.  So, we want to copy the files into the folder, rather than move them, and delete vorbis files that take up space unnecessarily.  Please bear this in mind when using the BASH script below, as I can't take any responsibility for losses arising from the use of this script.  I merely share this here because it works for me.  If you can make it work better, please feel free to create your own modified version.

With that in mind, here is the BASH script...

mp3encoder=$(lame --version | grep "(")

chmod +w *.ogg
chmod +w *.flac
for f in *.flac; do
    oggenc -q10 "$f" && rm "$f"
for f in *.ogg; do
    songartist=$(vorbiscomment -l "$f" | grep -i '\<ARTIST=' | head --lines=1 - | cut --delimiter="=" --fields=2- | iconv --from-code=UTF-8)
    songtitle=$(vorbiscomment -l "$f" | grep -i '\<TITLE=' | cut --delimiter="=" --fields=2- | iconv --from-code=UTF-8)
    songalbum=$(vorbiscomment -l "$f" | grep -i '\<ALBUM=' | head --lines=1 - | cut --delimiter="=" --fields=2- | iconv --from-code=UTF-8)
    songnumber=$(vorbiscomment -l "$f" | grep -i '\<TRACKNUMBER=' | cut --delimiter="=" --fields=2-)
    songyear=$(vorbiscomment -l "$f" | grep -i '\<DATE=' | cut --delimiter="=" --fields=2-)
    songgenre=$(vorbiscomment -l "$f" | grep -i '\<GENRE=' | cut --delimiter="=" --fields=2- | iconv --from-code=UTF-8)
    albumartist=$(vorbiscomment -l "$f" | grep -i '\<ALBUMARTIST=' | head --lines=1 - | cut --delimiter="=" --fields=2- | iconv --from-code=UTF-8)
    #decode the ogg to a wav file
    sox -S --norm "$f" -r 44100 "${f%%ogg}wav"
    #normalize, while keeping as much dynamic range as possible
    normalize-audio -a -12dBFS -l -6dBFS "${f%%ogg}wav"
    normalize-audio -a -12dBFS -l -6dBFS "${f%%ogg}wav"
    normalize-audio -a -12dBFS -l -6dBFS "${f%%ogg}wav"
    #show the vorbis tags
    vorbiscomment -l "$f" && rm "$f"

#create the MP3 with optimal settings for listening in car

    echo ""
    echo "Encoding $songtitle, by $songartist from $songalbum ($albumartist)"
    echo ""

    lame -q 0 -V 5 --strictly-enforce-ISO --add-id3v2 --clipdetect --ta "$songartist" --tt "$songtitle" --tl "$songalbum" --tn "$songnumber" --ty "$songyear" --tv "TENC=$mp3encoder" --tv "TPE2=$albumartist" --tg "$songgenre" "${f%%ogg}wav" "${f%%ogg}mp3" && rm "${f%%ogg}wav"

    #clean up
newname=$(echo "$songartist - $songtitle" | tr '/?: .' '-_-__')
mv -f "${f%%ogg}mp3" "$newname.mp3"
    unset songartist
    unset songtitle
    unset songalbum
    unset songnumber
    unset songyear
    unset songgenre
    unset albumartist
mp3gain -r -k -p -s i *.mp3
for f in *.mp3; do
songtitle=$(soxi "$f" | grep "\<Title=" | cut --delimiter="=" --fields=2- | iconv --from-code=UTF-8)
songartist=$(soxi "$f" | grep "\<Artist=" | cut --delimiter="=" --fields=2- | iconv --from-code=UTF-8)
mv "$f" "$songartist - $songtitle.mp3"
for f in *.mp3; do
mp3total=`echo $(($mp3total+1))`

echo ""
echo "This directory contains $mp3total MP3s, making a total of "$(ls -l -h | grep total | cut --characters 7-)""
echo "Brasero says our target size is 702M for a CD-R"
echo "Smallest file in this directory is "$(ls -S | tail --lines=1)""
echo "Largest file in this directory is "$(ls -S | head --lines=1)""
echo ""

Let's go through what's happening here.  First of all, we copy the string identifying our version of LAME into a variable, for pasting into an ID3 field later.  I've noticed that MP3 files created with iTunes have this field set, so why not our files created with LAME?  We then "chmod +w" our files, just in case some of them have the read only flag set.  Next, we convert any FLAC files, if we have them, into ogg vorbis format, so that the script can work on one type of file from this point.

The FOR loop in the next section steps through every ogg file in the folder, and performs the conversion, starting with a bunch of statements which obtain vorbis tags, via vorbiscomment, format them correctly, and store them in variables that we will use later to tag our MP3 file.  Next, we decode the ogg file to a WAV file, so that any modifications can be performed losslessly at this stage.  You will notice that the SOX line contains a normalisation filter and sets the sample frequency to 44100Hz (CD quality).  Sometimes, the conversion process creates clipping artefacts, which we don't want, so the --norm filter checks the file during conversion and sets a limiter to ensure this isn't happening.  Setting each file to a sample rate of 44100Hz is also sensible, because I have downloaded files in the past which have strange sample rates and 44.1kHz files are likely to play on just about any device.

Next, we come to something which is very important for collections of this nature.  We don't want to be adjusting the volume level between songs, especially while driving, so we need a way of getting them to a similar volume.  A setting of -12dB from full scale, and a hard limit of -6dB from full scale is the default for the normalize-audio program, and it's a sensible setting.  Modern CDs, from those I have tested, have an average setting of -8dBFS and a limiter of -3dBFS, if one is used at all!  Apparently, this phenomenon is known as the "loudness wars", where each record label is trying to produce louder and louder discs.  It's idiotic, because it requires the heavy-handed use of a compression filter, so each part of the music is at a similar volume - distortion creeps in, sound quality suffers, and the listening experience is generally unpleasant, especially when listening for prolonged periods.  Until record companies and studios can be convinced to go back to a time when dynamic range (the difference between the soft and loud parts of a recording) was more important than outright loudness, at least reducing the overall volume of the recording will help matters a little.

It is arguable that adding a low pass and high pass filter to the earlier SOX command is acceptable, and perhaps desirable, for portable listening or in-car environments.  In theory, this would lead to the volume being more level between recordings, as the frequency response is more uniform.  My personal thought on this, as an audio enthusiast, is that I want to modify the audio characteristics of the recording as little as possible, and the normalisation process seems to work to an acceptable standard without low pass or high pass filters being used.

After printing a string which contains the name of the song, artist and album, so that we know the tags have been transferred correctly, we get onto the business of encoding the MP3.  I have used a variable bit rate setting of 5, which equates to around 128kbps, but you can set a constant bit rate of 128kbps by changing "-V 5" to "-b 128" or any other of the allowable bit rates.  Variable bit rate will render an MP3 file of better quality than a constant bit rate file, though there are still players which do not correctly decode VBR MP3s.  When this process is complete, we remove the WAV file.

To finish, we rename the MP3 to a sensible standard format, unset variables in preparation for the next file and run every MP3 file in the folder through mp3gain (just in case we added files which were already in MP3 format, or we need replaygain information to be added).  When our files are converted, the script displays a total size for the MP3s in the folder, so that we know whether they will fill our device or go beyond the storage space we have available.

I hope this script may prove useful, if only to introduce the reader to some principles of BASH scripting.  If you can improve upon it, or want to modify it to your own requirements, please do so.  When I have the time to improve upon my BASH skills, I will probably make my own modifications.

No comments:

Post a Comment