Found this thread while trying to do direct encoding of MP3s from FLAC source files. Boehj’s answer provides a decent scripting option, but I personally prefer to use FFmpeg, so this is the Bash script I came up with to handle this task. Tested and works great in macOS Sierra (10.12.2).
Perquisites: You should have ffmpeg
and lame
already installed on your Mac. The easiest way to do this is via Homebrew. First make sure you have Homebrew installed like this:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Then run this command to install ffmpeg
and lame
:
brew install ffmpeg lame
Once that is done you are ready to run this script. This script will look for FLAC files in the directory path/to/FLAC/files
but that can be changed to simply be .
if the FLAC files are in the same directory you are running this script in. When it runs it will create an mp3/
subdirectory where all of the MP3 files will be placed.
find -E "path/to/FLAC/files" -type f -iregex ".*\.(FLAC)$" |\
while read full_audio_filepath
do
# Break up the full audio filepath stuff into different directory and filename components.
audio_dirname=$(dirname "${full_audio_filepath}");
audio_basename=$(basename "${full_audio_filepath}");
audio_filename="${audio_basename%.*}";
# audio_extension="${audio_basename##*.}";
# Set the MP3
mp3_dirpath="${audio_dirname}/mp3";
mp3_filepath="${mp3_dirpath}/${audio_filename}.mp3";
# Create the child MP3 directory.
mkdir -p "${mp3_dirpath}";
# Get the track metadata.
mp3_title=$(ffprobe 2> /dev/null -show_format "${full_audio_filepath}" | grep -i TAG:TITLE= | cut -d '=' -f 2- );
mp3_artist=$(ffprobe 2> /dev/null -show_format "${full_audio_filepath}" | grep -i TAG:ARTIST= | cut -d '=' -f 2- );
mp3_album=$(ffprobe 2> /dev/null -show_format "${full_audio_filepath}" | grep -i TAG:ALBUM= | cut -d '=' -f 2- );
mp3_year=$(ffprobe 2> /dev/null -show_format "${full_audio_filepath}" | grep -i TAG:YEAR= | cut -d '=' -f 2- );
mp3_track=$(ffprobe 2> /dev/null -show_format "${full_audio_filepath}" | grep -i TAG:TRACK= | cut -d '=' -f 2- | sed 's/^0*//' );
mp3_tracktotal=$(ffprobe 2> /dev/null -show_format "${full_audio_filepath}" | grep -i TAG:TRACKTOTAL= | cut -d '=' -f 2- | sed 's/^0*//' );
mp3_genre=$(ffprobe 2> /dev/null -show_format "${full_audio_filepath}" | grep -i TAG:GENRE= | cut -d '=' -f 2- );
# Where the magic happens.
ffmpeg -y -v quiet -nostdin -i "${full_audio_filepath}" -ar 44100 -sample_fmt s16 -ac 2 -f s16le -acodec pcm_s16le - | \
lame --quiet --add-id3v2 --pad-id3v2 --tt "${mp3_title}" --ta "${mp3_artist}" --tl "${mp3_album}" --tn "${mp3_track}"/"${mp3_tracktotal}" --tg "${mp3_genre}" -r -m s --lowpass 19.7 -V 3 --vbr-new -q 0 -b 96 --scale 0.99 --athaa-sensitivity 1 - "${mp3_filepath}";
done
Some notes on things I learned “The Hard Way™” so others can gain from what I did differently in this script compared to others on the Internet.
- The
grep
commands for tag parsing (using FFprobe which is installed with FFmpeg) are case insensitive using the -i
option to make it grep -i
.
- The following
cut
command is now limited to dividing the output only based on the first =
in a tag name with the -f 2-
option which makes the command cut -d '=' -f 2-
. For example, Pavement has a song titled “5-4=Unity” and if only the second chunk were selected via cut that title would have been truncated to “5-4”.
- For track—and total track—numbers I added an extra pipe to
sed
which gets rid of leading zeros: sed 's/^0*//'
.
- In similar scripts around the Internet, the FFmpeg output is something like
-f wav
and that would actually compress the FFmpeg output which makes no sense in a pipe setup where LAME is going to re-encode it. Instead the output here is set to -f s16le -acodec pcm_s16le
which is basically RAW output; perfect for piping audio to another process like this.
- To deal with RAW output on the LAME side of the pipe, I had to add the
-r
option.
- Also note the
--tt
, --ta
, --tl
, --tn
and --tg
ID3v2 tag options for LAME. When audio is streamed/piped from one process into LAME the the metadata from the source file is lost. One suggested option is to get FFmpeg to save the metadata to a text file by setting the option with -f ffmetadata "[metadata filename here]"
and then running FFmpeg again with the something like this: -i "[metadata filename here]" -map_metadata 1 -c:a copy [destination mp3 file] id3v2_version 3 -write_id3v1 1
. That works, but note the requirement for a destination file. Seems like FFmpeg only imports metadata when it can copy the file which seems like a very wasteful process. Using FFprobe to get values and then setting them in LAME with --tt
, --ta
, --tl
, --tn
and --tg
options works better; all the metadata is written in place so duplicate file needs to be generated.