A [pseudo] multithreaded FLAC to MP3 transcoder for Linux & UNIX.
FLAC, the Free Lossless Audio Codec, is an excellent format for audio archival. Unfortunately, it is not supported by all audio devices (especially cheap and/or underpowered ones). On those players which do support it, FLAC still consumes more storage space than lossy codecs such as MP3. For these reasons, it's handy to be able to quickly convert a group of FLAC audio files to the ubiquitous MP3 format. To this end, I decided to write flac-distiller.sh.
| Application | Information |
|---|---|
| bash | The Bourne Again SHell, available in most if not every distribution of Linux and UNIX (though not always installed by default). |
| lame (or compatible) | e.g. lame, twolame, toolame, etc. Usually included in your distribution's repositories.1) If not, see http://lame.sourceforge.net/ |
| flac | The FLAC decoder, usually included in your distribution's repositories as simply 'flac'. If not, see http://flac.sourceforge.net/ |
| metaflac | Optional, but highly recommended; this little app is how we extract the meta info from FLAC files. Usually bundled with 'flac'. |
Once the requirements are met, you simply need to place the script someplace in your path. There are two ways to accomplish this:
In either case, you will probably need root privileges first, e.g.:
su -
…or:
sudo su -
If you will be copying and pasting the script, use your favorite text editor to create the file in question. In this example, I'll use vim:
vim /usr/local/bin/flac-distiller.sh
…then paste in the code on this page, save, and exit.2) Finally, make the resulting file executable:
chmod 755 /usr/local/bin/flac-distiller.sh
Alternately, you could skip the text editor and just grab the file directly:
wget http://tuxhelp.org/pub/scripts/flac-distiller.sh -O /usr/local/bin/flac-distiller.sh && chmod 755 /usr/local/bin/flac-distiller.sh
Or the same, but with curl instead of wget:
curl http://tuxhelp.org/pub/scripts/flac-distiller.sh > /usr/local/bin/flac-distiller.sh && chmod 755 /usr/local/bin/flac-distiller.sh
Usage is easy; just run flac-distiller.sh from within the directory containing your FLAC files. If the processes completes uneventfully, it will exit silently.3) Freshly transcoded MP3 files should appear in your present working directory.
Alternately, you may specify the path to a single FLAC file, a list of FLAC files, or a directory containing one or more FLAC files. For example:
flac-distiller.sh /home/chris/music/My-FLAC-Album/
In this example, I will transcode the album “Bach Violin Concertos” by Lara St. John,4) which is in my common/Temp directory. Notice the path here is relative; it could be absolute, a list of files to be transcoded, or left off entirely (if the FLAC files are in my present working directory).
I would like to see how long the whole process took, and I'd like to see a visual representation of my system resource usage during transcoding. To accomplish these goals, I could call my script from the 'time' utility, then pipe it through htop:5)
time flac-distiller.sh common/Temp/Lara\ St\ John/Bach\ Violin\ Concertos/ | htop
…while the files are transcoding, I'd see the lovely, color-coded htop system monitor in action:
After transcoding finished, htop would automatically exit and I'd be shown the output from 'time':
real 0m37.308s user 2m11.568s sys 0m1.763s
So in this example, transcoding took 2 minutes and 11 seconds of CPU time, but only 37 seconds of actual time to complete.
Suppose you'd like to transcode the FLAC files in several directories without having to specify each one by name. We could accomplish this with a simple 'for' loop:
for d in $(ls) ; do flac-distiller.sh $d ; done
If we wanted to know how long the whole process took, we could prefix the whole thing with the 'time' utility:
time `for d in $(ls) ; do flac-distiller.sh $d ; done`
…or alternately, show how long it took to encode each individual directory:
for d in $(ls) ; do echo "$d took:" && time flac-distiller.sh $d && echo "" ; done
Ahh, the power of UNIX.
#!/bin/bash ######################################################### # A [pseudo] multithreaded FLAC to MP3 transcoder # ######################################################### # By Christopher A. Wadge, 09/04/2010 # # Some code by Nick Sklavenitis, 09/18/2007 # # # # http://tuxhelp.org/scripts/flac-distiller # # # # Licensed under the GPL version 3. A copy of the GPL # # version 3 is included with this script. If the file, # # COPYING, is not included, you can find the GPL # # version 3 at the following URL online: # # # # http://www.gnu.org/licenses/gpl-3.0.html # ######################################################### # This script's name: PROGRAM_NAME="flac-distiller.sh" # Date of last revision: PROGRAM_DATE="12/30/11" ## VARIABLES ## # The following are pre-determined variables that are not answered by the script. ##### ## Executable Paths ## # MP3 encoder (e.g. lame/twolame/toolame) binary locale: MP3ENCODER="/usr/bin/lame" # FLAC decoder binary locale: FLACDECODER="/usr/bin/flac" # FLAC meta info reader locale: METAFLAC="/usr/bin/metaflac" ## Encoder Variables ## # Should we automatically copy meta info from FLAC files as MP3 tags? COPYMETA="true" # Nice priority level (19 to -20, 19 being the lowest): PROCNICE="10" # You can append additional values to the $MP3ENCODER encoding process if you wish: XTRAOPTS="--vbr-new -h -V 0" # Max number of encoder threads to use (leave blank for auto-detection): THREADMAX="" ## !!! Attention Users: Editing below this line is not advised unless you really know what you're doing. ## FUNCTIONS ## # Define each function that we will call in scripting later ##### Error () { echo "[FATAL] Unfortunately we've encountered an unrecoverable error. Now quitting." exit 1 } Print_Help () { echo "" echo "==== $PROGRAM_NAME ($PROGRAM_DATE) ====" echo "" echo "Description: A multi-threaded FLAC to MP3 transcoder" echo "" echo "Usage: $0 <path to '.flac' file(s) or directory>" echo " You may also specify a wildcard, e.g. '*.flac'." echo " Sans arguments, it will search the current directory." echo "" echo "Dependencies: - An MP3 encoder, e.g. 'lame', 'twolame', or 'toolame'" echo " - A LAME decoder (see: http://lame.sourceforge.net/)" echo " - The Bourne Again SHell (aka 'BASH')" echo " + Optional (but highly recommended): 'metaflac'" echo "" exit 1 } Detect_Threads () { if [ -z $THREADMAX ] ; then if [ `uname -a | grep -c Linux` = 1 ] ; then THREADMAX=`cat /proc/cpuinfo | grep -c processor` elif [ `uname -a | egrep -c '(BSD|Darwin)'` = 1 ] ; then THREADMAX=`sysctl -a | grep hw.ncpu | grep -v "=" | cut -d " " -f2` fi elif (( "$THREADMAX" >= "1" )) ; then THREADMAX="$THREADMAX" else echo "[WARNING] Unable to detect maximum supported CPU threads; falling back to 1 thread to be safe." echo " You can specify the thread count manually in the 'VARIABLES' section of this script." THREADMAX="1" fi } Sanity_Check () { if [ ! -e /bin/bash ] ; then echo "[ERROR] It doesn't appear that you have the Bourne Again SHell (aka 'BASH')!" echo " The [over-hyped] 'DASH' shell is too primitive to interpret this script." echo " Please use your package manager to install 'bash' and try again." NOTSANE=1 fi if [ ! -e $MP3ENCODER ] ; then echo "[ERROR] The MP3 Encoder binary is not in the expected location: '$MP3ENCODER'" echo " If no encoder is installed, 'twolame' is quite fast: http://www.twolame.org/" echo " Otherwise, you can correct the path by editing the VARIABLES section of this script." NOTSANE=1 fi if [ ! -e $FLACDECODER ] ; then echo "[ERROR] The FLAC decoder binary is not in the expected location: '$FLACDECODER'" echo " If no FLAC decoder is installed, try installing 'flac' with your package manager." echo " Otherwise, you can correct the path by editing the VARIABLES section of this script." NOTSANE=1 fi if [ " $COPYMETA " = " true " ] ; then if [ ! -e $METAFLAC ] ; then echo "[WARNING] The 'metaflac' binary is not in the expected location: '$METAFLAC'" echo " This is a non-critical error. However, metaflac is strongly recommended." COPYMETA="false" fi fi if [ ! -z $NOTSANE ] ; then echo "[ERROR] Missing dependencies prevent this script from proceeding!" Error fi } MP3_Encode () { for file ; do [ -r "$file" ] || { echo '[ERROR] Unable to read file: \"$file\"' >&1 ; Error ; } FILEOUT=$(echo "$file" | sed s'/[.][Ff][Ll][Aa][Cc]//') if [ " $COPYMETA " = " true " ] ; then #This section pulls the Tag info from flac and stores it as a variable. TITLE="`$METAFLAC --show-tag=TITLE "$file" | awk -F = '{ printf($2) }'`" ALBUM="`$METAFLAC --show-tag=ALBUM "$file" | awk -F = '{ printf($2) }'`" ARTIST="`$METAFLAC --show-tag=ARTIST "$file" | awk -F = '{ printf($2) }'`" TRACKNUMBER="`$METAFLAC --show-tag=TRACKNUMBER "$file" | awk -F = '{ printf($2) }'`" GENRE="`$METAFLAC --show-tag=GENRE "$file" | awk -F = '{ printf($2) }'`" COMMENT="`$METAFLAC --show-tag=COMMENT "$file" | awk -F = '{ printf($2) }'`" DATE="`$METAFLAC --show-tag=DATE "$file" | awk -F = '{ printf($2) }'`" #This section handles the conversion of the Flac file to MP3 $FLACDECODER -dcs "$file" | $MP3ENCODER --quiet $XTRAOPTS \ --tt "$TITLE" \ --tn "$TRACKNUMBER" \ --tg "$GENRE" \ --ty "$DATE" \ --ta "$ARTIST" \ --tl "$ALBUM" \ --add-id3v2 \ - "$FILEOUT".mp3 || { echo "[ERROR] Transcoding failed for '$file'" >&1 ; Error ; } else $FLACDECODER -dcs "$file" | $MP3ENCODER --quiet $XTRAOPTS \ - "$FILEOUT".mp3 || { echo "[ERROR] Transcoding failed for '$file'" >&1 ; Error ; } fi ; done } ## SCRIPTING ## # Here's where we actually do all the work ##### if [ " $1 " = " --help " ] ; then Print_Help fi Sanity_Check Detect_Threads export MP3ENCODER export FLACDECODER export XTRAOPTS export COPYMETA if [ " $COPYMETA " = " true " ] ; then export METAFLAC fi export -f Error export -f MP3_Encode find "${@}" -maxdepth 1 -name '*[.][Ff][Ll][Aa][Cc]' -print0 | xargs -0 -n 1 -P $THREADMAX nice -n $PROCNICE bash -c 'MP3_Encode "$@"' -- exit 0