Converting WMA to MP3 in bulk with FFmpeg

My dad asked me to convert his music in bulk to MP3, for better compatibility with all of his devices. He had previously ripped his CD collection using Window Media Player, which produces Window Media Audio (WMA) files by default.

I thought this should be relatively simple in Linux using FFmpeg, which I was aware of but had never used directly. I found many, many, example scripts on the Web but none that I both understood and would run without errors. After a few hours (days) of reading man pages, learning about Bash and tinkering, I had a working script that I was happy with and understood.

Appeal to reason

Running scripts from the Internet that you don’t understand is a recipe for disaster. This is the complete script below that I used to convert the files; you use it at your own risk. I hope it serves as a useful example.

#!/bin/bash

dir=/home/alasdair/music

find "$dir" -type f -iname "*.wma" -print0 | \
while IFS= read -r -d '' ifile; do
    ofile="${ifile/%.[wW][mM][aA]}".mp3
    if [ ! -f "$ofile" ]
    then
        ffmpeg -i "$ifile" -aq 5 "$ofile" < /dev/null
    fi
done

I’ll go through it line by line and explain what is happening.

Finding the WMA files

I have assigned the path where the music is stored to a variable called dir. It’s convenient to have the path in a single place, at the top, where I can easily find and change it.

dir=/home/alasdair/music

I'm using the find command to find and list all of the WMA files in the music directory and its subdirectories (artsit, album etc).

find "$dir" -type f -iname "*.wma" -print0 | \

I have used the following parameters:

The pipe (vertical bar) character takes the output from this find command and uses it as the input for the next command. The final backslash means that this line is to be continued on the next line.

Looping through each file found

I now want to

while IFS= read -r -d '' ifile; do
    …
done

I’m using the read command to read the list of files from standard input, in this case piped from the output of find.

Each file path will be stored in the variable ifile, code between do and done will be run and then the loop will repeated for each file.

Naming the output file

I am saving each MP3 file to the same folder as the original WMA file and with the same name, except that the .wma extension will be replaced with .mp3. I'll explain the evolution of this line in stages.

The output file variable could be assigned the same value as the input file like this. Note the use of double quotes again.

ofile="$ifile"

There is also an alternate curly bracket syntax that can be used.

ofile="${file}"

Changing the file extension

This advantage of the latter syntax is that it allows us to specify a pattern in the variable that will be found and replaced. The pattern is given after the first slash and the replacement after the second slash. In this case to replace .wmv with .mp3.

ofile="${ifile/%.wma/.mp3}"

Think through the consequences

However if the file did not have a .wma extension then it would not be replaced by .mp3: the original file name would be used as-is. This could lead to a confusing situation where the original file is overwritten and now has the wrong file extension.

For this reason I have not provided a replacement and the .wma extension will removed (replaced with nothing) if found. I then add the .mp3 extension at the end, regardless of any replacement that has or has not taken place.

ofile="${ifile/%.wma}".mp3

Finished at LAST?

We're getting closer now but there is still a problem. The pattern matching is case sensitive and so a file extension of .WMA would not be matched. This is exactly the sort of situation that would lead to the original file being overwritten as disscussed previously.

I could convert the file names to upper or lower case but as that can be avoided, I don't find it an acceptable solution. I have rewritten it like this instead. The square brackets mean that any of the enclosed characters, in this instance both uppercase and lowercase versions, will match.

ofile="${ifile/%.[wW][mM][aA]}.mp3"

Checking if the file already exists

I don’t want to overwrite an existing files, so I am going to check that the output file doesn't already exist first.

if [ ! -f "$ofile" ]
then
    …
fi

Converting the file to MP3

Both the input and output file variables are double quoted as they may contain spaces amongst other characters.

ffmpeg -i "$ifile" -aq 5 "$ofile" < /dev/null

Comments

Your email address will not be published. I need it to send you a verification link. It will also be sent to Gravatar to check if you have one.